From 66513a114e1b3e19d199c0a05c0bc17590227db9 Mon Sep 17 00:00:00 2001
From: Pavlo Tkach <3469726+ptkach@users.noreply.github.com>
Date: Fri, 23 Aug 2024 16:53:45 -0400
Subject: [PATCH] Add OT&E UI to the new console (#2536)
---
console-webapp/src/app/app-routing.module.ts | 12 ++-
console-webapp/src/app/app.module.ts | 6 +-
.../src/app/ote/newOte.component.html | 36 +++++++++
.../src/app/ote/newOte.component.scss | 7 ++
.../src/app/ote/newOte.component.ts | 81 +++++++++++++++++++
.../src/app/ote/oteStatus.component.html | 31 +++++++
.../src/app/ote/oteStatus.component.scss | 28 +++++++
.../src/app/ote/oteStatus.component.ts | 62 ++++++++++++++
.../src/app/registrar/registrar.service.ts | 16 ++++
.../registrar/registrarDetails.component.html | 8 ++
.../registrar/registrarDetails.component.ts | 12 ++-
.../registrar/registrarsTable.component.html | 10 +++
.../registrar/registrarsTable.component.scss | 4 +
.../registrar/registrarsTable.component.ts | 10 +++
.../userLevelVisiblity.directive.ts | 3 +-
.../app/shared/services/backend.service.ts | 18 +++++
16 files changed, 340 insertions(+), 4 deletions(-)
create mode 100644 console-webapp/src/app/ote/newOte.component.html
create mode 100644 console-webapp/src/app/ote/newOte.component.scss
create mode 100644 console-webapp/src/app/ote/newOte.component.ts
create mode 100644 console-webapp/src/app/ote/oteStatus.component.html
create mode 100644 console-webapp/src/app/ote/oteStatus.component.scss
create mode 100644 console-webapp/src/app/ote/oteStatus.component.ts
diff --git a/console-webapp/src/app/app-routing.module.ts b/console-webapp/src/app/app-routing.module.ts
index d14bc2cd5..8e2c6e509 100644
--- a/console-webapp/src/app/app-routing.module.ts
+++ b/console-webapp/src/app/app-routing.module.ts
@@ -17,6 +17,9 @@ import { Route, RouterModule } from '@angular/router';
import { BillingInfoComponent } from './billingInfo/billingInfo.component';
import { DomainListComponent } from './domains/domainList.component';
import { HomeComponent } from './home/home.component';
+import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
+import { NewOteComponent } from './ote/newOte.component';
+import { OteStatusComponent } from './ote/oteStatus.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarComponent } from './registrar/registrarsTable.component';
import { ResourcesComponent } from './resources/resources.component';
@@ -26,7 +29,6 @@ import { SettingsComponent } from './settings/settings.component';
import UsersComponent from './settings/users/users.component';
import WhoisComponent from './settings/whois/whois.component';
import { SupportComponent } from './support/support.component';
-import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
export interface RouteWithIcon extends Route {
iconName?: string;
@@ -38,6 +40,14 @@ export const routes: RouteWithIcon[] = [
path: RegistryLockVerifyComponent.PATH,
component: RegistryLockVerifyComponent,
},
+ {
+ path: NewOteComponent.PATH,
+ component: NewOteComponent,
+ },
+ {
+ path: OteStatusComponent.PATH,
+ component: OteStatusComponent,
+ },
{ path: 'registrars', component: RegistrarComponent },
{
path: 'home',
diff --git a/console-webapp/src/app/app.module.ts b/console-webapp/src/app/app.module.ts
index 8f41f6217..11cf6dc8a 100644
--- a/console-webapp/src/app/app.module.ts
+++ b/console-webapp/src/app/app.module.ts
@@ -30,7 +30,10 @@ import { DomainListComponent } from './domains/domainList.component';
import { RegistryLockComponent } from './domains/registryLock.component';
import { HeaderComponent } from './header/header.component';
import { HomeComponent } from './home/home.component';
+import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
import { NavigationComponent } from './navigation/navigation.component';
+import { NewOteComponent } from './ote/newOte.component';
+import { OteStatusComponent } from './ote/oteStatus.component';
import NewRegistrarComponent from './registrar/newRegistrar.component';
import { RegistrarDetailsComponent } from './registrar/registrarDetails.component';
import { RegistrarSelectorComponent } from './registrar/registrarSelector.component';
@@ -54,7 +57,6 @@ import { UserDataService } from './shared/services/userData.service';
import { SnackBarModule } from './snackbar.module';
import { SupportComponent } from './support/support.component';
import { TldsComponent } from './tlds/tlds.component';
-import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component';
@NgModule({
declarations: [
@@ -66,6 +68,8 @@ import { RegistryLockVerifyComponent } from './lock/registryLockVerify.component
HeaderComponent,
HomeComponent,
LocationBackDirective,
+ NewOteComponent,
+ OteStatusComponent,
UserLevelVisibility,
NavigationComponent,
NewRegistrarComponent,
diff --git a/console-webapp/src/app/ote/newOte.component.html b/console-webapp/src/app/ote/newOte.component.html
new file mode 100644
index 000000000..b98a08c47
--- /dev/null
+++ b/console-webapp/src/app/ote/newOte.component.html
@@ -0,0 +1,36 @@
+
Generate OT&E Accounts
+
+ @if (oteCreateResponseFormatted()) {
+
Generated Successfully
+
+
+ Epp Credentials
+ Copy and paste this into an email to the registrars
+
+
+ {{ oteCreateResponseFormatted() }}
+
+
+ } @else {
+
+ }
+
diff --git a/console-webapp/src/app/ote/newOte.component.scss b/console-webapp/src/app/ote/newOte.component.scss
new file mode 100644
index 000000000..02e1bf260
--- /dev/null
+++ b/console-webapp/src/app/ote/newOte.component.scss
@@ -0,0 +1,7 @@
+.console-app__new-ote {
+ max-width: 720px;
+ mat-card-content {
+ white-space: break-spaces;
+ padding: 20px;
+ }
+}
diff --git a/console-webapp/src/app/ote/newOte.component.ts b/console-webapp/src/app/ote/newOte.component.ts
new file mode 100644
index 000000000..7cff6c925
--- /dev/null
+++ b/console-webapp/src/app/ote/newOte.component.ts
@@ -0,0 +1,81 @@
+// Copyright 2024 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 { HttpErrorResponse } from '@angular/common/http';
+import { Component, computed, signal } from '@angular/core';
+import { FormControl, FormGroup, Validators } from '@angular/forms';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { RegistrarService } from '../registrar/registrar.service';
+
+export interface OteCreateResponse extends Map {
+ password: string;
+}
+
+@Component({
+ selector: 'app-ote',
+ templateUrl: './newOte.component.html',
+ styleUrls: ['./newOte.component.scss'],
+})
+export class NewOteComponent {
+ public static PATH = 'new-ote';
+
+ oteCreateResponse = signal(undefined);
+
+ readonly oteCreateResponseFormatted = computed(() => {
+ const oteCreateResponse = this.oteCreateResponse();
+ if (oteCreateResponse) {
+ const { password } = oteCreateResponse;
+ return Object.entries(oteCreateResponse)
+ .filter((entry) => entry[0] !== 'password')
+ .map(
+ ([login, tld]) =>
+ `Login: ${login}\t\tPassword: ${password}\t\tTLD: ${tld}`
+ )
+ .join('\n');
+ }
+ return undefined;
+ });
+
+ createOte = new FormGroup({
+ registrarId: new FormControl('', [Validators.required]),
+ registrarEmail: new FormControl('', [Validators.required]),
+ });
+
+ constructor(
+ protected registrarService: RegistrarService,
+ private _snackBar: MatSnackBar
+ ) {}
+
+ onSubmit() {
+ if (this.createOte.valid) {
+ const { registrarId, registrarEmail } = this.createOte.value;
+ this.registrarService
+ .generateOte(
+ {
+ registrarId,
+ registrarEmail,
+ },
+ registrarId || ''
+ )
+ .subscribe({
+ next: (oteCreateResponse: OteCreateResponse) => {
+ this.oteCreateResponse.set(oteCreateResponse);
+ },
+ error: (err: HttpErrorResponse) => {
+ this._snackBar.open(err.error || err.message);
+ },
+ });
+ }
+ }
+}
diff --git a/console-webapp/src/app/ote/oteStatus.component.html b/console-webapp/src/app/ote/oteStatus.component.html
new file mode 100644
index 000000000..23da40e2e
--- /dev/null
+++ b/console-webapp/src/app/ote/oteStatus.component.html
@@ -0,0 +1,31 @@
+
+ OT&E Status Check
+ @if(isOte()) {
+
+ Status:
+ {{ oteStatusUnfinished().length ? "Unfinished" : "Completed" }}
+
+
+ @if(oteStatusCompleted().length) {
+
+
Completed
+
+ check_box{{ entry.description }}
+
+
+ } @if(oteStatusUnfinished().length) {
+
+
Unfinished
+
+ check_box_outline_blank{{ entry.description }}
+
+
+ }
+
+ } @else {
+
+ Registrar {{ registrarService.registrar()?.registrarId }} is not an OT&E
+ registrar
+
+ }
+
diff --git a/console-webapp/src/app/ote/oteStatus.component.scss b/console-webapp/src/app/ote/oteStatus.component.scss
new file mode 100644
index 000000000..f03e35c3e
--- /dev/null
+++ b/console-webapp/src/app/ote/oteStatus.component.scss
@@ -0,0 +1,28 @@
+.console-app__ote-status {
+ max-width: 730px;
+ display: flex;
+ flex-wrap: wrap;
+ &_completed,
+ &_unfinished {
+ border: 1px solid #ddd;
+ padding: 20px;
+ border-radius: 10px;
+ margin: 0 20px 30px 0;
+ div {
+ display: flex;
+ min-width: 300px;
+ align-items: flex-start;
+ max-width: 300px;
+ margin-bottom: 10px;
+ padding-bottom: 5px;
+ border-bottom: 1px solid #ddd;
+ &:last-child {
+ border: none;
+ }
+ }
+
+ mat-icon {
+ min-width: 30px;
+ }
+ }
+}
diff --git a/console-webapp/src/app/ote/oteStatus.component.ts b/console-webapp/src/app/ote/oteStatus.component.ts
new file mode 100644
index 000000000..e9adfc9c6
--- /dev/null
+++ b/console-webapp/src/app/ote/oteStatus.component.ts
@@ -0,0 +1,62 @@
+// Copyright 2024 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 { HttpErrorResponse } from '@angular/common/http';
+import { Component, computed, signal } from '@angular/core';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { RegistrarService } from '../registrar/registrar.service';
+
+export interface OteStatusResponse {
+ description: string;
+ requirement: number;
+ timesPerformed: number;
+ completed: boolean;
+}
+
+@Component({
+ selector: 'app-ote-status',
+ templateUrl: './oteStatus.component.html',
+ styleUrls: ['./oteStatus.component.scss'],
+})
+export class OteStatusComponent {
+ public static PATH = 'ote-status';
+
+ oteStatusResponse = signal([]);
+
+ oteStatusCompleted = computed(() =>
+ this.oteStatusResponse().filter((v) => v.completed)
+ );
+ oteStatusUnfinished = computed(() =>
+ this.oteStatusResponse().filter((v) => !v.completed)
+ );
+ isOte = computed(
+ () => this.registrarService.registrar()?.type?.toLowerCase() === 'ote'
+ );
+
+ constructor(
+ protected registrarService: RegistrarService,
+ private _snackBar: MatSnackBar
+ ) {
+ this.registrarService
+ .oteStatus(this.registrarService.registrarId())
+ .subscribe({
+ next: (oteStatusResponse: OteStatusResponse[]) => {
+ this.oteStatusResponse.set(oteStatusResponse);
+ },
+ error: (err: HttpErrorResponse) => {
+ this._snackBar.open(err.error || err.message);
+ },
+ });
+ }
+}
diff --git a/console-webapp/src/app/registrar/registrar.service.ts b/console-webapp/src/app/registrar/registrar.service.ts
index a24131cfb..a4c333df4 100644
--- a/console-webapp/src/app/registrar/registrar.service.ts
+++ b/console-webapp/src/app/registrar/registrar.service.ts
@@ -17,6 +17,8 @@ import { Observable, switchMap, tap } from 'rxjs';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Router } from '@angular/router';
+import { OteCreateResponse } from '../ote/newOte.component';
+import { OteStatusResponse } from '../ote/oteStatus.component';
import { BackendService } from '../shared/services/backend.service';
import {
GlobalLoader,
@@ -69,6 +71,7 @@ export interface Registrar
registrarId: string;
registrarName: string;
registryLockAllowed?: boolean;
+ type?: string;
}
@Injectable({
@@ -149,4 +152,17 @@ export class RegistrarService implements GlobalLoader {
loadingTimeout() {
this._snackBar.open('Timeout loading registrars');
}
+
+ generateOte(
+ oteForm: Object,
+ registrarId: string
+ ): Observable {
+ return this.backend
+ .generateOte(oteForm, registrarId)
+ .pipe(tap((_) => this.loadRegistrars()));
+ }
+
+ oteStatus(registrarId: string): Observable {
+ return this.backend.getOteStatus(registrarId);
+ }
}
diff --git a/console-webapp/src/app/registrar/registrarDetails.component.html b/console-webapp/src/app/registrar/registrarDetails.component.html
index de6c78f47..7379d7e69 100644
--- a/console-webapp/src/app/registrar/registrarDetails.component.html
+++ b/console-webapp/src/app/registrar/registrarDetails.component.html
@@ -8,6 +8,14 @@
@if(!inEdit && !registrarNotFound) {
+