From 9ce1fef1f8f68fa2b4b61ea54c9637008ff0d863 Mon Sep 17 00:00:00 2001 From: kurilova Date: Thu, 12 Dec 2024 13:05:08 +0000 Subject: [PATCH] Adds Certificates page --- modules/ui/src/app/app-routing.module.ts | 19 +++ modules/ui/src/app/app.component.html | 19 --- modules/ui/src/app/app.component.spec.ts | 26 ---- modules/ui/src/app/app.component.ts | 12 -- .../empty-message.component.html | 7 + .../empty-message.component.scss | 30 +++++ .../empty-message.component.spec.ts | 45 +++++++ .../empty-message/empty-message.component.ts | 13 ++ modules/ui/src/app/model/certificate.ts | 2 +- modules/ui/src/app/model/routes.ts | 2 + .../certificate-item.component.html | 39 ------ .../certificate-item.component.scss | 94 -------------- .../certificate-item.component.ts | 22 ---- .../certificate-upload-button.component.scss | 14 -- .../certificates/certificates.component.html | 33 ++--- .../certificates/certificates.component.scss | 65 +--------- .../certificates.component.spec.ts | 29 +---- .../certificates/certificates.component.ts | 26 ++-- .../pages/certificates/certificates.store.ts | 19 ++- .../certificate-upload-button.component.html | 0 .../certificate-upload-button.component.scss | 19 +++ ...ertificate-upload-button.component.spec.ts | 0 .../certificate-upload-button.component.ts | 0 .../certificates-table.component.html | 120 ++++++++++++++++++ .../certificates-table.component.scss | 94 ++++++++++++++ .../certificates-table.component.spec.ts} | 48 ++++--- .../certificates-table.component.ts | 30 +++++ .../pages/settings/settings.component.html | 9 +- .../pages/settings/settings.component.spec.ts | 3 +- .../app/pages/settings/settings.component.ts | 31 ++++- modules/ui/src/assets/icons/desktop-new.svg | 27 ++++ 31 files changed, 518 insertions(+), 379 deletions(-) create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.html create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.scss create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.spec.ts create mode 100644 modules/ui/src/app/components/empty-message/empty-message.component.ts delete mode 100644 modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.html delete mode 100644 modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss delete mode 100644 modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts delete mode 100644 modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss rename modules/ui/src/app/pages/certificates/{ => components}/certificate-upload-button/certificate-upload-button.component.html (100%) create mode 100644 modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss rename modules/ui/src/app/pages/certificates/{ => components}/certificate-upload-button/certificate-upload-button.component.spec.ts (100%) rename modules/ui/src/app/pages/certificates/{ => components}/certificate-upload-button/certificate-upload-button.component.ts (100%) create mode 100644 modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html create mode 100644 modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss rename modules/ui/src/app/pages/certificates/{certificate-item/certificate-item.component.spec.ts => components/certificates-table/certificates-table.component.spec.ts} (65%) create mode 100644 modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts create mode 100644 modules/ui/src/assets/icons/desktop-new.svg diff --git a/modules/ui/src/app/app-routing.module.ts b/modules/ui/src/app/app-routing.module.ts index a94a90222..a3c05b296 100644 --- a/modules/ui/src/app/app-routing.module.ts +++ b/modules/ui/src/app/app-routing.module.ts @@ -19,13 +19,32 @@ import { DevicesComponent } from './pages/devices/devices.component'; import { CanDeactivateGuard } from './guards/can-deactivate.guard'; import { TestrunComponent } from './pages/testrun/testrun.component'; import { RiskAssessmentComponent } from './pages/risk-assessment/risk-assessment.component'; +import { CertificatesComponent } from './pages/certificates/certificates.component'; import { SettingsComponent } from './pages/settings/settings.component'; +import { GeneralSettingsComponent } from './pages/general-settings/general-settings.component'; export const routes: Routes = [ { path: 'settings', component: SettingsComponent, title: 'Testrun - Settings', + children: [ + { + path: '', + redirectTo: 'general', + pathMatch: 'full', + }, + { + path: 'certificates', + component: CertificatesComponent, + title: 'Testrun - Certificates', + }, + { + path: 'general', + component: GeneralSettingsComponent, + title: 'Testrun - General Settings', + }, + ], }, { path: 'testing', diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index 2aeeb52c2..0ff440418 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -82,16 +82,6 @@

Testrun

- -
diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss deleted file mode 100644 index d84959577..000000000 --- a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2023 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -@use 'colors'; -@use 'variables'; - -:host { - ::ng-deep .mat-mdc-progress-bar { - --mdc-linear-progress-active-indicator-color: #1967d2; - } -} - -:host:first-child .certificate-item-container { - border-top: 1px solid colors.$lighter-grey; -} - -.certificate-item-container { - display: grid; - grid-template-columns: 24px minmax(200px, 1fr) 24px; - gap: 16px; - box-sizing: border-box; - padding: 12px 0; - border-bottom: 1px solid colors.$lighter-grey; -} - -.certificate-item-icon { - color: colors.$grey-700; -} - -.certificate-item-delete { - padding: 0; - height: 24px; - width: 24px; - border-radius: 4px; - color: colors.$grey-700; - display: flex; - align-items: flex-start; - justify-content: center; - & ::ng-deep .mat-mdc-button-persistent-ripple { - border-radius: 4px; - } - &:disabled { - pointer-events: none; - opacity: 0.6; - } -} - -.certificate-item-information { - overflow: hidden; - p { - font-family: variables.$font-secondary, sans-serif; - margin: 0; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - .certificate-item-name { - font-size: 16px; - color: colors.$grey-800; - min-height: 24px; - } - .certificate-item-organisation, - .certificate-item-expires { - font-size: 14px; - color: colors.$grey-700; - min-height: 20px; - } -} - -.certificate-expired { - .certificate-item-icon { - color: colors.$red-700; - } - .certificate-item-name { - color: colors.$red-800; - } - - .certificate-item-organisation, - .certificate-item-expires { - color: colors.$red-700; - } -} diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts deleted file mode 100644 index b22c22a0a..000000000 --- a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { Certificate, CertificateStatus } from '../../../model/certificate'; -import { MatIcon } from '@angular/material/icon'; -import { CommonModule } from '@angular/common'; -import { MatButtonModule } from '@angular/material/button'; -import { MatProgressBarModule } from '@angular/material/progress-bar'; -import { provideAnimations } from '@angular/platform-browser/animations'; - -@Component({ - selector: 'app-certificate-item', - - imports: [MatIcon, MatButtonModule, MatProgressBarModule, CommonModule], - providers: [provideAnimations()], - templateUrl: './certificate-item.component.html', - styleUrl: './certificate-item.component.scss', -}) -export class CertificateItemComponent { - @Input() certificate!: Certificate; - @Output() deleteButtonClicked = new EventEmitter(); - - CertificateStatus = CertificateStatus; -} diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss b/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss deleted file mode 100644 index e48f64ceb..000000000 --- a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -.browse-files-button { - margin: 18px 16px; - padding: 8px 24px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - height: auto; - min-height: 36px; -} - -#default-file-input { - display: none; -} diff --git a/modules/ui/src/app/pages/certificates/certificates.component.html b/modules/ui/src/app/pages/certificates/certificates.component.html index 4b586cc74..e13bbffcb 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.html +++ b/modules/ui/src/app/pages/certificates/certificates.component.html @@ -13,27 +13,16 @@ See the License for the specific language governing permissions and limitations under the License. --> -
-

Certificates

- -
-
+
-
- -
-
+ +
+ + +
diff --git a/modules/ui/src/app/pages/certificates/certificates.component.scss b/modules/ui/src/app/pages/certificates/certificates.component.scss index e32676d54..da30d5440 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.scss +++ b/modules/ui/src/app/pages/certificates/certificates.component.scss @@ -24,67 +24,14 @@ flex: 1 0 auto; } -.certificates-drawer-header { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 12px 16px 24px; - - &-title { - margin: 0; - font-size: 22px; - font-style: normal; - font-weight: 400; - line-height: 28px; - color: colors.$dark-grey; - } - - &-button { - min-width: 24px; - width: 24px; - height: 24px; - margin: 4px; - padding: 8px !important; - box-sizing: content-box; - line-height: normal !important; - - .close-button-icon { - width: 24px; - height: 24px; - margin: 0; - } - - ::ng-deep * { - line-height: inherit !important; - } - } -} - -.certificates-drawer-content { - overflow: hidden; - flex: 1; - display: grid; - grid-template-rows: auto 1fr auto; -} - .content-certificates { - padding: 0 16px; - border-bottom: 1px solid colors.$lighter-grey; + margin: 8px 24px; + border-bottom: 1px solid colors.$outline-variant; overflow-y: scroll; + height: max-content; + max-height: calc(100vh - 344px); } -.certificates-drawer-footer { - padding: 16px 24px 8px 16px; - margin-top: auto; - display: flex; - flex-shrink: 0; - justify-content: flex-end; - - .close-button { - padding: 0 24px; - font-size: 14px; - font-weight: 500; - line-height: 20px; - letter-spacing: 0.25px; - } +.certificates-button-container { + margin: 24px; } diff --git a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts index 1deda3d21..be598c5f9 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts @@ -75,20 +75,6 @@ describe('CertificatesComponent', () => { }); describe('DOM tests', () => { - it('should emit closeSettingEvent when header button clicked', () => { - const headerCloseButton = fixture.nativeElement.querySelector( - '.certificates-drawer-header-button' - ) as HTMLButtonElement; - spyOn(component.closeCertificatedEvent, 'emit'); - - headerCloseButton.click(); - - expect(mockLiveAnnouncer.announce).toHaveBeenCalledWith( - 'The certificates panel is closed.' - ); - expect(component.closeCertificatedEvent.emit).toHaveBeenCalled(); - }); - it('should have upload file button', () => { const uploadCertificatesButton = fixture.nativeElement.querySelector( '.browse-files-button' @@ -99,9 +85,8 @@ describe('CertificatesComponent', () => { describe('with certificates', () => { it('should have certificates list', () => { - const certificateList = fixture.nativeElement.querySelectorAll( - 'app-certificate-item' - ); + const certificateList = + fixture.nativeElement.querySelectorAll('.cdk-row'); expect(certificateList.length).toEqual(2); }); @@ -137,12 +122,10 @@ describe('CertificatesComponent', () => { describe('#focusNextButton', () => { it('should focus next active element if exist', fakeAsync(() => { - const row = window.document.querySelector( - 'app-certificate-item' - ) as HTMLElement; + const row = window.document.querySelector('.cdk-row') as HTMLElement; row.classList.add('certificate-selected'); const nextButton = window.document.querySelector( - '.certificate-selected + app-certificate-item .certificate-item-delete' + '.certificate-selected + .cdk-row .certificate-item-delete' ) as HTMLButtonElement; const buttonFocusSpy = spyOn(nextButton, 'focus'); @@ -152,9 +135,9 @@ describe('CertificatesComponent', () => { flush(); })); - it('should focus navigation close button if next active element does not exist', fakeAsync(() => { + it('should focus upload button if next active element does not exist', fakeAsync(() => { const nextButton = window.document.querySelector( - '.certificates-drawer-header .certificates-drawer-header-button' + '.browse-files-button' ) as HTMLButtonElement; const buttonFocusSpy = spyOn(nextButton, 'focus'); diff --git a/modules/ui/src/app/pages/certificates/certificates.component.ts b/modules/ui/src/app/pages/certificates/certificates.component.ts index bd795f086..9c6efd980 100644 --- a/modules/ui/src/app/pages/certificates/certificates.component.ts +++ b/modules/ui/src/app/pages/certificates/certificates.component.ts @@ -20,25 +20,23 @@ import { Output, inject, } from '@angular/core'; -import { MatIcon } from '@angular/material/icon'; -import { CertificateItemComponent } from './certificate-item/certificate-item.component'; import { CommonModule, DatePipe } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; -import { CdkTrapFocus, LiveAnnouncer } from '@angular/cdk/a11y'; -import { CertificateUploadButtonComponent } from './certificate-upload-button/certificate-upload-button.component'; +import { CdkTrapFocus } from '@angular/cdk/a11y'; import { CertificatesStore } from './certificates.store'; import { SimpleDialogComponent } from '../../components/simple-dialog/simple-dialog.component'; import { Subject, takeUntil } from 'rxjs'; import { MatDialog } from '@angular/material/dialog'; +import { CertificatesTableComponent } from './components/certificates-table/certificates-table.component'; +import { CertificateUploadButtonComponent } from './components/certificate-upload-button/certificate-upload-button.component'; @Component({ selector: 'app-certificates', imports: [ - MatIcon, - CertificateItemComponent, MatButtonModule, CertificateUploadButtonComponent, CommonModule, + CertificatesTableComponent, ], providers: [CertificatesStore, DatePipe], hostDirectives: [CdkTrapFocus], @@ -46,7 +44,6 @@ import { MatDialog } from '@angular/material/dialog'; styleUrl: './certificates.component.scss', }) export class CertificatesComponent implements OnDestroy { - private liveAnnouncer = inject(LiveAnnouncer); store = inject(CertificatesStore); dialog = inject(MatDialog); @@ -59,11 +56,6 @@ export class CertificatesComponent implements OnDestroy { this.destroy$.unsubscribe(); } - closeCertificates() { - this.liveAnnouncer.announce('The certificates panel is closed.'); - this.closeCertificatedEvent.emit(); - } - uploadFile(file: File) { this.store.uploadCertificate(file); } @@ -97,16 +89,16 @@ export class CertificatesComponent implements OnDestroy { focusNextButton() { // Try to focus next interactive element, if exists const next = window.document.querySelector( - '.certificate-selected + app-certificate-item .certificate-item-delete' + '.certificate-selected + .cdk-row .certificate-item-delete' ) as HTMLButtonElement; if (next) { next.focus(); } else { - // If next interactive element doest not exist, close button will be focused - const menuButton = window.document.querySelector( - '.certificates-drawer-header .certificates-drawer-header-button' + // If next interactive element doest not exist, upload button will be focused + const uploadButton = window.document.querySelector( + '.browse-files-button' ) as HTMLButtonElement; - menuButton?.focus(); + uploadButton?.focus(); } } } diff --git a/modules/ui/src/app/pages/certificates/certificates.store.ts b/modules/ui/src/app/pages/certificates/certificates.store.ts index 9dc2baa8f..9d14a014b 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { inject } from '@angular/core'; +import { computed, inject } from '@angular/core'; import { signalStore } from '@ngrx/signals'; import { switchMap, tap } from 'rxjs/operators'; import { catchError, EMPTY, exhaustMap, throwError } from 'rxjs'; @@ -23,9 +23,16 @@ import { TestRunService } from '../../services/test-run.service'; import { NotificationService } from '../../services/notification.service'; import { DatePipe } from '@angular/common'; import { FILE_NAME_LENGTH, getValidationErrors } from './certificate.validator'; -import { withState, withHooks, withMethods, patchState } from '@ngrx/signals'; +import { + withState, + withHooks, + withMethods, + patchState, + withComputed, +} from '@ngrx/signals'; import { tapResponse } from '@ngrx/operators'; import { rxMethod } from '@ngrx/signals/rxjs-interop'; +import { MatTableDataSource } from '@angular/material/table'; const SYMBOLS_PER_SECOND = 9.5; @@ -33,7 +40,12 @@ export const CertificatesStore = signalStore( withState({ certificates: [] as Certificate[], selectedCertificate: '', + displayedColumns: ['name', 'organisation', 'expires', 'status', 'actions'], + dataLoaded: false, }), + withComputed(({ certificates }) => ({ + dataSource: computed(() => new MatTableDataSource(certificates())), + })), withMethods( ( store, @@ -79,7 +91,8 @@ export const CertificatesStore = signalStore( switchMap(() => testRunService.fetchCertificates().pipe( tapResponse({ - next: certificates => patchState(store, { certificates }), + next: certificates => + patchState(store, { certificates, dataLoaded: true }), error: () => patchState(store, { certificates: [] }), }) ) diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.html b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.html similarity index 100% rename from modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.html rename to modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.html diff --git a/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss new file mode 100644 index 000000000..f89d7fd6b --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.scss @@ -0,0 +1,19 @@ +@use 'colors'; +@use 'variables'; + +.browse-files-button { + border-radius: 16px; + padding: 16px 24px; + font-family: variables.$font-text; + font-size: 16px; + font-weight: 500; + line-height: 24px; + letter-spacing: 0.25px; + height: auto; + background: colors.$secondary-container; + color: colors.$on-secondary-container; +} + +#default-file-input { + display: none; +} diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.spec.ts b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.spec.ts similarity index 100% rename from modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.spec.ts rename to modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.spec.ts diff --git a/modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.ts b/modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.ts similarity index 100% rename from modules/ui/src/app/pages/certificates/certificate-upload-button/certificate-upload-button.component.ts rename to modules/ui/src/app/pages/certificates/components/certificate-upload-button/certificate-upload-button.component.ts diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html new file mode 100644 index 000000000..9ee3d9440 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Certificate Name + {{ data.name }} + + Organisation + + {{ data.organisation }} + + Expires + + {{ data.expires | date: 'dd MMM yyyy' }} + + Status + + + {{ data.status }} + + + +
+
+ +
+
+
+ + + + + diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss new file mode 100644 index 000000000..73e462b48 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.scss @@ -0,0 +1,94 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@use '@angular/material' as mat; +@use 'variables'; +@use 'colors'; + +:host { + @include mat.table-overrides( + ( + background-color: colors.$surface, + header-headline-color: colors.$on-surface-variant, + row-item-label-text-color: colors.$on-surface-variant, + header-headline-font: variables.$font-text, + row-item-label-text-font: variables.$font-text, + footer-supporting-text-font: variables.$font-text, + row-item-outline-color: colors.$outline-variant, + ) + ); +} + +.table-cell-actions { + text-align: right; +} + +.cell-result { + font-family: #{variables.$font-text}; + font-weight: 500; + margin: 0; + padding: 6px 12px; + border-radius: 8px; + font-size: 12px; + line-height: 16px; + letter-spacing: 0.3px; + white-space: nowrap; + &.valid { + background: colors.$tertiary-container; + color: colors.$on-tertiary-container; + } + &.expired { + background: colors.$error-container; + color: colors.$on-error-container; + } +} + +.uploading { + background: rgba(196, 199, 197, 0.16); + font-style: italic; +} + +.results-content-empty-message { + display: flex; + flex-direction: column; + align-items: center; + gap: 16px; +} + +.results-content-empty-message-header { + font-weight: 400; + line-height: 28px; + font-size: 22px; + color: colors.$on-surface; +} + +.results-content-empty-message-main { + font-family: variables.$font-secondary; + font-weight: 400; + font-size: 16px; + line-height: 24px; + letter-spacing: 0.1px; + color: colors.$on-surface-variant; +} + +.results-content-empty-message-img { + width: 293px; + height: 154px; + background-image: url(/assets/icons/desktop-new.svg); +} + +.certificates-content-empty { + height: calc(100vh - 344px); +} diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.spec.ts similarity index 65% rename from modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts rename to modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.spec.ts index 5840aa2bb..bcd275ed9 100644 --- a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.spec.ts @@ -1,25 +1,38 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CertificateItemComponent } from './certificate-item.component'; +import { CertificatesTableComponent } from './certificates-table.component'; +import { MatTableDataSource } from '@angular/material/table'; import { certificate, certificate_uploading, -} from '../../../mocks/certificate.mock'; +} from '../../../../mocks/certificate.mock'; -describe('CertificateItemComponent', () => { - let component: CertificateItemComponent; - let fixture: ComponentFixture; +describe('CertificatesTableComponent', () => { let compiled: HTMLElement; + let component: CertificatesTableComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [CertificateItemComponent], + imports: [CertificatesTableComponent], }).compileComponents(); - fixture = TestBed.createComponent(CertificateItemComponent); - compiled = fixture.nativeElement as HTMLElement; + fixture = TestBed.createComponent(CertificatesTableComponent); component = fixture.componentInstance; - component.certificate = certificate; + fixture.componentRef.setInput( + 'dataSource', + new MatTableDataSource([certificate]) + ); + fixture.componentRef.setInput('selectedCertificate', ''); + fixture.componentRef.setInput('dataLoaded', true); + fixture.componentRef.setInput('displayedColumns', [ + 'name', + 'organisation', + 'expires', + 'status', + 'actions', + ]); + compiled = fixture.nativeElement as HTMLElement; fixture.detectChanges(); }); @@ -29,7 +42,7 @@ describe('CertificateItemComponent', () => { describe('DOM tests', () => { it('should have certificate name', () => { - const name = compiled.querySelector('.certificate-item-name'); + const name = compiled.querySelector('.cdk-row .mat-column-name'); expect(name?.textContent?.trim()).toEqual('iot.bms.google.com'); }); @@ -37,14 +50,14 @@ describe('CertificateItemComponent', () => { describe('uploaded certificate', () => { it('should have certificate organization', () => { const organization = compiled.querySelector( - '.certificate-item-organisation' + '.cdk-row .mat-column-organisation' ); expect(organization?.textContent?.trim()).toEqual('Google, Inc.'); }); it('should have certificate expire date', () => { - const date = compiled.querySelector('.certificate-item-expires'); + const date = compiled.querySelector('.cdk-row .mat-column-expires'); expect(date?.textContent?.trim()).toEqual('01 Sep 2024'); }); @@ -76,16 +89,13 @@ describe('CertificateItemComponent', () => { describe('uploading certificate', () => { beforeEach(() => { - component.certificate = certificate_uploading; + fixture.componentRef.setInput( + 'dataSource', + new MatTableDataSource([certificate_uploading]) + ); fixture.detectChanges(); }); - it('should have loader', () => { - const loader = compiled.querySelector('mat-progress-bar'); - - expect(loader).not.toBeNull(); - }); - it('should have disabled delete button', () => { const deleteButton = fixture.nativeElement.querySelector( '.certificate-item-delete' diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts new file mode 100644 index 000000000..639f1ba6b --- /dev/null +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.ts @@ -0,0 +1,30 @@ +import { Component, input, output } from '@angular/core'; +import { Certificate } from '../../../../model/certificate'; +import { MatTableDataSource, MatTableModule } from '@angular/material/table'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { CommonModule } from '@angular/common'; +import { EmptyMessageComponent } from '../../../../components/empty-message/empty-message.component'; + +@Component({ + selector: 'app-certificates-table', + imports: [ + MatIconModule, + MatTableModule, + MatButtonModule, + CommonModule, + EmptyMessageComponent, + ], + templateUrl: './certificates-table.component.html', + styleUrl: './certificates-table.component.scss', +}) +export class CertificatesTableComponent { + dataSource = input.required>(); + selectedCertificate = input(); + dataLoaded = input(false); + displayedColumns = input([]); + deleteButtonClicked = output(); + trackByName(index: number, item: Certificate) { + return item.name; + } +} diff --git a/modules/ui/src/app/pages/settings/settings.component.html b/modules/ui/src/app/pages/settings/settings.component.html index b1cd8a1cb..68ad9d5b4 100644 --- a/modules/ui/src/app/pages/settings/settings.component.html +++ b/modules/ui/src/app/pages/settings/settings.component.html @@ -20,7 +20,10 @@

Settings

- General content - Certificates content + class="tab-group" + (selectedTabChange)="onTabChange($event)" + [selectedIndex]="selectedIndex"> + + + diff --git a/modules/ui/src/app/pages/settings/settings.component.spec.ts b/modules/ui/src/app/pages/settings/settings.component.spec.ts index 0f5d81c01..d93a3bb0c 100644 --- a/modules/ui/src/app/pages/settings/settings.component.spec.ts +++ b/modules/ui/src/app/pages/settings/settings.component.spec.ts @@ -17,6 +17,7 @@ import { SettingsComponent } from './settings.component'; * limitations under the License. */ import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; describe('SettingsComponent', () => { let component: SettingsComponent; @@ -24,7 +25,7 @@ describe('SettingsComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [SettingsComponent, NoopAnimationsModule], + imports: [SettingsComponent, NoopAnimationsModule, RouterTestingModule], }).compileComponents(); fixture = TestBed.createComponent(SettingsComponent); diff --git a/modules/ui/src/app/pages/settings/settings.component.ts b/modules/ui/src/app/pages/settings/settings.component.ts index 53385ee1c..e88ad73c8 100644 --- a/modules/ui/src/app/pages/settings/settings.component.ts +++ b/modules/ui/src/app/pages/settings/settings.component.ts @@ -13,16 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { MatToolbarModule } from '@angular/material/toolbar'; import { CommonModule } from '@angular/common'; -import { MatTabsModule } from '@angular/material/tabs'; +import { MatTabChangeEvent, MatTabsModule } from '@angular/material/tabs'; +import { ActivatedRoute, Router, RouterModule } from '@angular/router'; +import { Routes } from '../../model/routes'; @Component({ selector: 'app-settings', - imports: [CommonModule, MatToolbarModule, MatTabsModule], + imports: [CommonModule, MatToolbarModule, MatTabsModule, RouterModule], templateUrl: './settings.component.html', - styleUrl: './settings.component.scss', + styleUrls: ['./settings.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class SettingsComponent {} +export class SettingsComponent implements OnInit { + private routes = [Routes.General, Routes.Certificates]; + selectedIndex = 0; + constructor( + private router: Router, + private route: ActivatedRoute + ) {} + + ngOnInit(): void { + const currentRoute = this.router.url; + this.selectedIndex = this.routes.findIndex(route => + currentRoute.includes(route) + ); + } + + onTabChange(event: MatTabChangeEvent): void { + const index = event.index; + this.router.navigate([this.routes[index]], { relativeTo: this.route }); + } +} diff --git a/modules/ui/src/assets/icons/desktop-new.svg b/modules/ui/src/assets/icons/desktop-new.svg new file mode 100644 index 000000000..fe5787129 --- /dev/null +++ b/modules/ui/src/assets/icons/desktop-new.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +