diff --git a/modules/ui/src/app/app.component.html b/modules/ui/src/app/app.component.html index 969824523..98dd85f1c 100644 --- a/modules/ui/src/app/app.component.html +++ b/modules/ui/src/app/app.component.html @@ -14,93 +14,120 @@ limitations under the License. --> - - -
- - - - - - - -
-
+ + + +
+ + + + + + + +
+
- - - - - -

Testrun

-
- - -
-
- - - - No ports are detected. Please define a valid ones using - - - Selected port is missing! Please define a valid one using - + + + + + +

Testrun

+
+ + + +
+
+ + + + No ports are detected. Please define a valid ones using + + + Selected port is missing! Please define a valid one using + + System settings + panel. + + + + Step 1: To perform a device test, please, select ports in Testrun > panel. - - - Step 1: To perform a device test, please, select ports in - System settings - panel. - - - Step 2: To perform a device test please - Create a Device - first. - - - Step 3: Once device is created, you are able to - start testing. - - -
-
+ + Step 2: To perform a device test please + Create a Device + first. + + + Step 3: Once device is created, you are able to + start testing. + + +
+
+
+ Testrun (closeSettingEvent)="closeSetting(vm.hasDevices)"> -
+ + + + + { let component: AppComponent; @@ -94,6 +95,7 @@ describe('AppComponent', () => { 'fetchDevices', 'getTestModules', 'testrunInProgress', + 'fetchCertificates', ]); mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [ @@ -113,6 +115,7 @@ describe('AppComponent', () => { BypassComponent, CalloutComponent, MatIconTestingModule, + CertificatesComponent, ], providers: [ { provide: TestRunService, useValue: mockService }, @@ -648,6 +651,27 @@ describe('AppComponent', () => { expect(spyToggle).toHaveBeenCalledTimes(0); }); + + it('should render certificates button', () => { + const generalSettingsButton = compiled.querySelector( + '.app-toolbar-button-certificates' + ); + + expect(generalSettingsButton).toBeDefined(); + }); + + it('should call settingsDrawer open on click settings button', () => { + fixture.detectChanges(); + + const settingsBtn = compiled.querySelector( + '.app-toolbar-button-certificates' + ) as HTMLButtonElement; + spyOn(component.certDrawer, 'open'); + + settingsBtn.click(); + + expect(component.certDrawer.open).toHaveBeenCalledTimes(1); + }); }); @Component({ diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 8b1e87cd0..4334662b3 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -54,7 +54,10 @@ export class AppComponent { private openedSettingFromToggleBtn = true; @ViewChild('settingsDrawer') public settingsDrawer!: MatDrawer; + @ViewChild('certDrawer') public certDrawer!: MatDrawer; @ViewChild('toggleSettingsBtn') public toggleSettingsBtn!: HTMLButtonElement; + @ViewChild('toggleCertificatesBtn') + public toggleCertificatesBtn!: HTMLButtonElement; @ViewChild('navigation') public navigation!: ElementRef; @ViewChild('settings') public settings!: GeneralSettingsComponent; viewModel$ = this.appStore.viewModel$; @@ -71,6 +74,7 @@ export class AppComponent { ) { this.appStore.getDevices(); this.appStore.getSystemStatus(); + this.appStore.getCertificates(); this.matIconRegistry.addSvgIcon( 'devices', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_LOGO_URL) @@ -107,6 +111,10 @@ export class AppComponent { this.appStore.setIsOpenStartTestrun(); } + async closeCertificates(): Promise { + await this.certDrawer.close(); + } + async closeSetting(hasDevices: boolean): Promise { return await this.settingsDrawer.close().then(() => { if (hasDevices) { @@ -146,6 +154,10 @@ export class AppComponent { await this.settingsDrawer.open(); } + async openCert() { + await this.certDrawer.open(); + } + consentShown() { this.appStore.setContent(); } diff --git a/modules/ui/src/app/app.module.ts b/modules/ui/src/app/app.module.ts index 42ade0ddd..60354e593 100644 --- a/modules/ui/src/app/app.module.ts +++ b/modules/ui/src/app/app.module.ts @@ -44,6 +44,7 @@ import { EffectsModule } from '@ngrx/effects'; import { AppEffects } from './store/effects'; import { CdkTrapFocus } from '@angular/cdk/a11y'; import { SettingsDropdownComponent } from './pages/settings/components/settings-dropdown/settings-dropdown.component'; +import { CertificatesComponent } from './pages/certificates/certificates.component'; @NgModule({ declarations: [AppComponent, GeneralSettingsComponent], @@ -71,6 +72,7 @@ import { SettingsDropdownComponent } from './pages/settings/components/settings- EffectsModule.forRoot([AppEffects]), CdkTrapFocus, SettingsDropdownComponent, + CertificatesComponent, ], providers: [ { diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index d53840850..a11f85943 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -32,6 +32,7 @@ import SpyObj = jasmine.SpyObj; import { device } from './mocks/device.mock'; import { setDevices, setTestrunStatus } from './store/actions'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from './mocks/progress.mock'; +import { certificate } from './mocks/certificate.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -57,7 +58,11 @@ describe('AppStore', () => { let mockService: SpyObj; beforeEach(() => { - mockService = jasmine.createSpyObj(['fetchDevices', 'fetchSystemStatus']); + mockService = jasmine.createSpyObj([ + 'fetchDevices', + 'fetchSystemStatus', + 'fetchCertificates', + ]); TestBed.configureTestingModule({ providers: [ @@ -107,6 +112,15 @@ describe('AppStore', () => { appStore.updateIsStatusLoaded(true); }); + + it('should update certificates', (done: DoneFn) => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.certificates).toEqual([certificate]); + done(); + }); + + appStore.updateCertificates([certificate]); + }); }); describe('selectors', () => { @@ -122,6 +136,7 @@ describe('AppStore', () => { isMenuOpen: true, interfaces: {}, settingMissedError: null, + certificates: [], }); done(); }); @@ -184,5 +199,22 @@ describe('AppStore', () => { appStore.getSystemStatus(); }); }); + + describe('fetchCertificates', () => { + const certificates = [certificate]; + + beforeEach(() => { + mockService.fetchCertificates.and.returnValue(of(certificates)); + }); + + it('should update certificates', done => { + appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { + expect(store.certificates).toEqual(certificates); + done(); + }); + + appStore.getCertificates(); + }); + }); }); }); diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index d214d4848..ee5c952fe 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -38,6 +38,7 @@ import { } from './store/actions'; import { TestrunStatus } from './model/testrun-status'; import { SettingMissedError, SystemInterfaces } from './model/setting'; +import { Certificate } from './model/certificate'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export interface AppComponentState { @@ -45,11 +46,13 @@ export interface AppComponentState { isStatusLoaded: boolean; isTestrunStarted: boolean; systemStatus: TestrunStatus | null; + certificates: Certificate[]; } @Injectable() export class AppStore extends ComponentStore { private consentShown$ = this.select(state => state.consentShown); private isStatusLoaded$ = this.select(state => state.isStatusLoaded); + private certificates$ = this.select(state => state.certificates); private hasDevices$ = this.store.select(selectHasDevices); private hasConnectionSetting$ = this.store.select( selectHasConnectionSettings @@ -72,6 +75,7 @@ export class AppStore extends ComponentStore { isMenuOpen: this.isMenuOpen$, interfaces: this.interfaces$, settingMissedError: this.settingMissedError$, + certificates: this.certificates$, }); updateConsent = this.updater((state, consentShown: boolean) => ({ @@ -84,6 +88,11 @@ export class AppStore extends ComponentStore { isStatusLoaded, })); + updateCertificates = this.updater((state, certificates: Certificate[]) => ({ + ...state, + certificates, + })); + setContent = this.effect(trigger$ => { return trigger$.pipe( tap(() => { @@ -128,6 +137,17 @@ export class AppStore extends ComponentStore { ); }); + getCertificates = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunService.fetchCertificates().pipe( + tap((certificates: Certificate[]) => { + this.updateCertificates(certificates); + }) + ); + }) + ); + }); constructor( private store: Store, private testRunService: TestRunService @@ -137,6 +157,7 @@ export class AppStore extends ComponentStore { isStatusLoaded: false, isTestrunStarted: false, systemStatus: null, + certificates: [], }); } } diff --git a/modules/ui/src/app/mocks/certificate.mock.ts b/modules/ui/src/app/mocks/certificate.mock.ts new file mode 100644 index 000000000..b2a38847b --- /dev/null +++ b/modules/ui/src/app/mocks/certificate.mock.ts @@ -0,0 +1,22 @@ +/** + * 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. + */ +import { Certificate } from '../model/certificate'; + +export const certificate = { + name: 'iot.bms.google.com', + organisation: 'Google, Inc.', + expires: '2024-09-01T09:00:12Z', +} as Certificate; diff --git a/modules/ui/src/app/model/certificate.ts b/modules/ui/src/app/model/certificate.ts new file mode 100644 index 000000000..b3abd24d6 --- /dev/null +++ b/modules/ui/src/app/model/certificate.ts @@ -0,0 +1,20 @@ +/** + * 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. + */ +export interface Certificate { + name: string; + organisation: string; + expires: string; +} diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.html b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.html new file mode 100644 index 000000000..2f1a4c778 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.html @@ -0,0 +1,19 @@ +
+ workspace_premium +
+

{{ certificate.name }}

+

{{ certificate.organisation }}

+

+ {{ certificate.expires | date: 'dd MMM yyyy' }} +

+
+ + +
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 new file mode 100644 index 000000000..d3300ea6d --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.scss @@ -0,0 +1,67 @@ +/** + * 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; +@import '../../../../theming/colors'; +@import '../../../../theming/variables'; + +:host:first-child .certificate-item-container { + border-top: 1px solid $lighter-grey; +} +.certificate-item-container { + display: grid; + grid-template-columns: 24px minmax(200px, 1fr) 24px; + gap: 16px; + box-sizing: border-box; + height: 88px; + padding: 12px 0; + border-bottom: 1px solid $lighter-grey; +} + +.certificate-item-icon { + color: $grey-700; +} + +.certificate-item-delete { + padding: 0; + margin-left: -50%; + border-radius: 4px; + color: $grey-700; + display: flex; + align-items: start; + justify-content: center; + & ::ng-deep .mat-mdc-button-persistent-ripple { + border-radius: 4px; + } +} + +.certificate-item-information { + overflow: hidden; + p { + font-family: $font-secondary, sans-serif; + margin: 0; + } + .certificate-item-name { + font-size: 16px; + color: $grey-800; + height: 24px; + } + .certificate-item-organisation, + .certificate-item-expires { + font-size: 14px; + color: $grey-700; + height: 20px; + } +} diff --git a/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts new file mode 100644 index 000000000..7705605e3 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.spec.ts @@ -0,0 +1,56 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CertificateItemComponent } from './certificate-item.component'; +import { certificate } from '../../../mocks/certificate.mock'; + +describe('CertificateItemComponent', () => { + let component: CertificateItemComponent; + let fixture: ComponentFixture; + let compiled: HTMLElement; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CertificateItemComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CertificateItemComponent); + compiled = fixture.nativeElement as HTMLElement; + component = fixture.componentInstance; + component.certificate = certificate; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('DOM tests', () => { + it('should have certificate name', () => { + const name = compiled.querySelector('.certificate-item-name'); + + expect(name?.textContent?.trim()).toEqual('iot.bms.google.com'); + }); + + it('should have certificate organization', () => { + const organization = compiled.querySelector( + '.certificate-item-organisation' + ); + + expect(organization?.textContent?.trim()).toEqual('Google, Inc.'); + }); + + it('should have certificate expire date', () => { + const date = compiled.querySelector('.certificate-item-expires'); + + expect(date?.textContent?.trim()).toEqual('01 Sep 2024'); + }); + + it('should have delete button', () => { + const deleteButton = fixture.nativeElement.querySelector( + '#main .test-button' + ) as HTMLButtonElement; + + expect(deleteButton).toBeDefined(); + }); + }); +}); 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 new file mode 100644 index 000000000..ccdbf43cc --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificate-item/certificate-item.component.ts @@ -0,0 +1,16 @@ +import { Component, Input } from '@angular/core'; +import { Certificate } from '../../../model/certificate'; +import { MatIcon } from '@angular/material/icon'; +import { DatePipe } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; + +@Component({ + selector: 'app-certificate-item', + standalone: true, + imports: [MatIcon, DatePipe, MatButtonModule], + templateUrl: './certificate-item.component.html', + styleUrl: './certificate-item.component.scss', +}) +export class CertificateItemComponent { + @Input() certificate!: Certificate; +} diff --git a/modules/ui/src/app/pages/certificates/certificates.component.html b/modules/ui/src/app/pages/certificates/certificates.component.html new file mode 100644 index 000000000..d61e2867f --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificates.component.html @@ -0,0 +1,45 @@ + +
+

Certificates

+ +
+
+ +
+ +
+ +
diff --git a/modules/ui/src/app/pages/certificates/certificates.component.scss b/modules/ui/src/app/pages/certificates/certificates.component.scss new file mode 100644 index 000000000..aa0903cde --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificates.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; +@import '../../../theming/colors'; +@import '../../../theming/variables'; + +:host { + display: flex; + flex-direction: column; + height: 100%; + 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: $dark-grey; + } + + &-button { + min-width: 24px; + width: 24px; + height: 24px; + margin: 4px; + padding: 8px !important; + box-sizing: content-box; + + .close-button-icon { + width: 24px; + height: 24px; + margin: 0; + } + } +} + +.certificates-drawer-content { + overflow: hidden; + flex: 1; + display: grid; + grid-template-rows: auto 1fr auto; +} + +.browse-files-button { + margin: 18px 16px; + padding: 8px 24px; + font-size: 14px; + font-weight: 500; + line-height: 20px; + letter-spacing: 0.25px; +} + +.content-certificates { + padding: 0 16px; + border-bottom: 1px solid $lighter-grey; + overflow-y: scroll; +} + +.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; + } +} diff --git a/modules/ui/src/app/pages/certificates/certificates.component.spec.ts b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts new file mode 100644 index 000000000..ead4cca0b --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificates.component.spec.ts @@ -0,0 +1,98 @@ +/** + * 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. + */ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CertificatesComponent } from './certificates.component'; +import { MatIconTestingModule } from '@angular/material/icon/testing'; +import { MatIcon } from '@angular/material/icon'; +import { certificate } from '../../mocks/certificate.mock'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; +import SpyObj = jasmine.SpyObj; + +describe('CertificatesComponent', () => { + let component: CertificatesComponent; + let mockLiveAnnouncer: SpyObj; + let fixture: ComponentFixture; + + beforeEach(async () => { + mockLiveAnnouncer = jasmine.createSpyObj(['announce']); + await TestBed.configureTestingModule({ + imports: [CertificatesComponent, MatIconTestingModule, MatIcon], + providers: [{ provide: LiveAnnouncer, useValue: mockLiveAnnouncer }], + }).compileComponents(); + + fixture = TestBed.createComponent(CertificatesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + 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 emit closeSettingEvent when close button clicked', () => { + const headerCloseButton = fixture.nativeElement.querySelector( + '.close-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' + ) as HTMLButtonElement; + + expect(uploadCertificatesButton).toBeDefined(); + }); + + describe('with certificates', () => { + beforeEach(() => { + component.certificates = [certificate, certificate]; + fixture.detectChanges(); + }); + + it('should have certificates list', () => { + const certificateList = fixture.nativeElement.querySelectorAll( + 'app-certificate-item' + ); + + expect(certificateList.length).toEqual(2); + }); + }); + }); +}); diff --git a/modules/ui/src/app/pages/certificates/certificates.component.ts b/modules/ui/src/app/pages/certificates/certificates.component.ts new file mode 100644 index 000000000..6959ad487 --- /dev/null +++ b/modules/ui/src/app/pages/certificates/certificates.component.ts @@ -0,0 +1,41 @@ +/** + * 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. + */ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatIcon } from '@angular/material/icon'; +import { CertificateItemComponent } from './certificate-item/certificate-item.component'; +import { NgForOf } from '@angular/common'; +import { MatButtonModule } from '@angular/material/button'; +import { Certificate } from '../../model/certificate'; +import { LiveAnnouncer } from '@angular/cdk/a11y'; + +@Component({ + selector: 'app-certificates', + standalone: true, + imports: [MatIcon, CertificateItemComponent, NgForOf, MatButtonModule], + templateUrl: './certificates.component.html', + styleUrl: './certificates.component.scss', +}) +export class CertificatesComponent { + @Input() certificates: Certificate[] = []; + @Output() closeCertificatedEvent = new EventEmitter(); + + constructor(private liveAnnouncer: LiveAnnouncer) {} + + closeCertificates() { + this.liveAnnouncer.announce('The certificates panel is closed.'); + this.closeCertificatedEvent.emit(); + } +} diff --git a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss index 7cd228c75..16093e336 100644 --- a/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss +++ b/modules/ui/src/app/pages/settings/components/settings-dropdown/settings-dropdown.component.scss @@ -38,11 +38,11 @@ letter-spacing: 0.2px; &.top { - color: #3c4043; + color: $grey-800; } &.bottom { - color: #5f6368; + color: $grey-700; } } diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts index 84e4d119d..6ffa54e09 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -32,6 +32,8 @@ import { device } from '../mocks/device.mock'; import { NEW_VERSION, VERSION } from '../mocks/version.mock'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { AppState } from '../store/state'; +import { Certificate } from '../model/certificate'; +import { certificate } from '../mocks/certificate.mock'; const MOCK_SYSTEM_CONFIG: SystemConfig = { network: { @@ -444,4 +446,20 @@ describe('TestRunService', () => { ); req.flush(true); }); + + it('fetchCertificates should return certificates', () => { + const certificates = [certificate] as Certificate[]; + + service.fetchCertificates().subscribe(res => { + expect(res).toEqual(certificates); + }); + + const req = httpTestingController.expectOne( + 'http://localhost:8000/system/config/certs/list' + ); + + expect(req.request.method).toBe('GET'); + + req.flush(certificates); + }); }); diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index 2befd91b9..c55b09676 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -29,6 +29,7 @@ import { import { Version } from '../model/version'; import { Store } from '@ngrx/store'; import { AppState } from '../store/state'; +import { Certificate } from '../model/certificate'; const API_URL = `http://${window.location.hostname}:8000`; export const SYSTEM_STOP = '/system/stop'; @@ -211,4 +212,8 @@ export class TestRunService { }) .pipe(map(() => true)); } + + fetchCertificates(): Observable { + return this.http.get(`${API_URL}/system/config/certs/list`); + } }