diff --git a/console-webapp/src/app/app.module.ts b/console-webapp/src/app/app.module.ts
index 5801d4996..f18508009 100644
--- a/console-webapp/src/app/app.module.ts
+++ b/console-webapp/src/app/app.module.ts
@@ -31,6 +31,8 @@ import SettingsContactComponent, {
ContactDetailsDialogComponent,
} from './settings/contact/contact.component';
import { HttpClientModule } from '@angular/common/http';
+import { RegistrarComponent } from './registrar/registrar.component';
+import { RegistrarGuard } from './registrar/registrar.guard';
@NgModule({
declarations: [
@@ -41,6 +43,7 @@ import { HttpClientModule } from '@angular/common/http';
SettingsComponent,
SettingsContactComponent,
ContactDetailsDialogComponent,
+ RegistrarComponent,
],
imports: [
HttpClientModule,
@@ -50,7 +53,7 @@ import { HttpClientModule } from '@angular/common/http';
AppRoutingModule,
BrowserAnimationsModule,
],
- providers: [BackendService],
+ providers: [BackendService, RegistrarGuard],
bootstrap: [AppComponent],
})
export class AppModule {}
diff --git a/console-webapp/src/app/registrar/registrar.component.html b/console-webapp/src/app/registrar/registrar.component.html
new file mode 100644
index 000000000..610525928
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.component.html
@@ -0,0 +1,17 @@
+
+
+ {{ registrarService.activeRegistrarId === "" ? "Select" : "Switch" }}
+ registrar:
+
+
+ Registrar
+
+
+ {{ registrar }}
+
+
+
+
diff --git a/console-webapp/src/app/registrar/registrar.component.less b/console-webapp/src/app/registrar/registrar.component.less
new file mode 100644
index 000000000..faa0146e9
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.component.less
@@ -0,0 +1,11 @@
+.console-app {
+ &__registrar {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 1rem;
+ }
+ &__title {
+ margin-right: 1rem;
+ }
+}
diff --git a/console-webapp/src/app/registrar/registrar.component.spec.ts b/console-webapp/src/app/registrar/registrar.component.spec.ts
new file mode 100644
index 000000000..861a0f1b3
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.component.spec.ts
@@ -0,0 +1,44 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// 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
+//
+// http://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 { RegistrarComponent } from './registrar.component';
+import { BackendService } from '../shared/services/backend.service';
+import { ActivatedRoute } from '@angular/router';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('RegistrarComponent', () => {
+ let component: RegistrarComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [RegistrarComponent],
+ imports: [HttpClientTestingModule],
+ providers: [
+ BackendService,
+ { provide: ActivatedRoute, useValue: {} as ActivatedRoute },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(RegistrarComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/console-webapp/src/app/registrar/registrar.component.ts b/console-webapp/src/app/registrar/registrar.component.ts
new file mode 100644
index 000000000..2008b0379
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.component.ts
@@ -0,0 +1,45 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// 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
+//
+// http://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 } from '@angular/core';
+import { RegistrarService } from './registrar.service';
+import { ActivatedRoute, Router } from '@angular/router';
+
+@Component({
+ selector: 'app-registrar',
+ templateUrl: './registrar.component.html',
+ styleUrls: ['./registrar.component.less'],
+})
+export class RegistrarComponent {
+ private lastActiveRegistrarId: string;
+
+ constructor(
+ private route: ActivatedRoute,
+ protected registrarService: RegistrarService,
+ private router: Router
+ ) {
+ this.lastActiveRegistrarId = registrarService.activeRegistrarId;
+ }
+
+ ngDoCheck() {
+ if (
+ this.registrarService.activeRegistrarId &&
+ this.registrarService.activeRegistrarId !== this.lastActiveRegistrarId &&
+ this.route.snapshot.paramMap.get('nextUrl')
+ ) {
+ this.lastActiveRegistrarId = this.registrarService.activeRegistrarId;
+ this.router.navigate([this.route.snapshot.paramMap.get('nextUrl')]);
+ }
+ }
+}
diff --git a/console-webapp/src/app/registrar/registrar.guard.spec.ts b/console-webapp/src/app/registrar/registrar.guard.spec.ts
new file mode 100644
index 000000000..0e668b046
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.guard.spec.ts
@@ -0,0 +1,64 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// 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
+//
+// http://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 { TestBed } from '@angular/core/testing';
+
+import { RegistrarGuard } from './registrar.guard';
+import { Router, RouterStateSnapshot } from '@angular/router';
+import { RegistrarService } from './registrar.service';
+
+describe('RegistrarGuard', () => {
+ let guard: RegistrarGuard;
+ let dummyRegistrarService: RegistrarService;
+ let routeSpy: Router;
+ let dummyRoute: RouterStateSnapshot = {} as RouterStateSnapshot;
+
+ beforeEach(() => {
+ routeSpy = jasmine.createSpyObj('Router', ['navigate']);
+ dummyRegistrarService = { activeRegistrarId: '' } as RegistrarService;
+
+ TestBed.configureTestingModule({
+ providers: [
+ RegistrarGuard,
+ { provide: Router, useValue: routeSpy },
+ { provide: RegistrarService, useValue: dummyRegistrarService },
+ ],
+ });
+ });
+
+ it('should not be able to activate when activeRegistrarId is empty', () => {
+ guard = TestBed.inject(RegistrarGuard);
+ const res = guard.canActivate(dummyRoute);
+ expect(res).toBeFalsy();
+ });
+
+ it('should be able to activate when activeRegistrarId is not empty', () => {
+ TestBed.overrideProvider(RegistrarService, {
+ useValue: { activeRegistrarId: 'value' },
+ });
+ guard = TestBed.inject(RegistrarGuard);
+ const res = guard.canActivate(dummyRoute);
+ expect(res).toBeTrue();
+ });
+
+ it('should navigate to registrars when activeRegistrarId is empty', () => {
+ const dummyRoute = { url: '/value' } as RouterStateSnapshot;
+ guard = TestBed.inject(RegistrarGuard);
+ guard.canActivate(dummyRoute);
+ expect(routeSpy.navigate).toHaveBeenCalledOnceWith([
+ '/registrars',
+ { nextUrl: '/value' },
+ ]);
+ });
+});
diff --git a/console-webapp/src/app/registrar/registrar.guard.ts b/console-webapp/src/app/registrar/registrar.guard.ts
new file mode 100644
index 000000000..44dd1923c
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.guard.ts
@@ -0,0 +1,34 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// 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
+//
+// http://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 { Injectable } from '@angular/core';
+import { Router, RouterStateSnapshot } from '@angular/router';
+import { RegistrarService } from './registrar.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class RegistrarGuard {
+ constructor(
+ private router: Router,
+ private registrarService: RegistrarService
+ ) {}
+
+ canActivate(state: RouterStateSnapshot): Promise | boolean {
+ if (this.registrarService.activeRegistrarId) {
+ return true;
+ }
+ return this.router.navigate([`/registrars`, { nextUrl: state.url }]);
+ }
+}
diff --git a/console-webapp/src/app/registrar/registrar.service.spec.ts b/console-webapp/src/app/registrar/registrar.service.spec.ts
new file mode 100644
index 000000000..6cd69b888
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.service.spec.ts
@@ -0,0 +1,35 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// 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
+//
+// http://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 { TestBed } from '@angular/core/testing';
+
+import { RegistrarService } from './registrar.service';
+import { BackendService } from '../shared/services/backend.service';
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+
+describe('RegistrarService', () => {
+ let service: RegistrarService;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [HttpClientTestingModule],
+ providers: [BackendService],
+ });
+ service = TestBed.inject(RegistrarService);
+ });
+
+ it('should be created', () => {
+ expect(service).toBeTruthy();
+ });
+});
diff --git a/console-webapp/src/app/registrar/registrar.service.ts b/console-webapp/src/app/registrar/registrar.service.ts
new file mode 100644
index 000000000..2257233c3
--- /dev/null
+++ b/console-webapp/src/app/registrar/registrar.service.ts
@@ -0,0 +1,29 @@
+// Copyright 2023 The Nomulus Authors. All Rights Reserved.
+//
+// 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
+//
+// http://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 { Injectable } from '@angular/core';
+import { BackendService } from '../shared/services/backend.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class RegistrarService {
+ activeRegistrarId: string = '';
+ registrars: string[] = [];
+ constructor(private backend: BackendService) {
+ this.backend.getRegistrars().subscribe((r) => {
+ this.registrars = r;
+ });
+ }
+}
diff --git a/console-webapp/src/app/settings/contact/contact-details.component.html b/console-webapp/src/app/settings/contact/contact-details.component.html
index 5810355a2..73ffcd40a 100644
--- a/console-webapp/src/app/settings/contact/contact-details.component.html
+++ b/console-webapp/src/app/settings/contact/contact-details.component.html
@@ -1,11 +1,12 @@
Contact details