mirror of
https://github.com/google/nomulus
synced 2025-12-23 06:15:42 +00:00
Add OT&E UI to the new console (#2536)
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
|
||||
36
console-webapp/src/app/ote/newOte.component.html
Normal file
36
console-webapp/src/app/ote/newOte.component.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<h1 class="mat-headline-4">Generate OT&E Accounts</h1>
|
||||
<div class="console-app__new-ote">
|
||||
@if (oteCreateResponseFormatted()) {
|
||||
<h1>Generated Successfully</h1>
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Epp Credentials</mat-card-title>
|
||||
<mat-card-subtitle
|
||||
>Copy and paste this into an email to the registrars</mat-card-subtitle
|
||||
>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>{{ oteCreateResponseFormatted() }}</p>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
} @else {
|
||||
<form (ngSubmit)="onSubmit()" [formGroup]="createOte">
|
||||
<p>
|
||||
<mat-form-field name="registrarId" appearance="outline">
|
||||
<mat-label>Base Registrar Id: </mat-label>
|
||||
<input matInput type="text" formControlName="registrarId" required />
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<p>
|
||||
<mat-form-field name="registrarEmail" appearance="outline">
|
||||
<mat-label>Contact Email: </mat-label>
|
||||
<input matInput type="text" formControlName="registrarEmail" required />
|
||||
<mat-hint
|
||||
>Will be granted web-console access to the OTE registrars.</mat-hint
|
||||
>
|
||||
</mat-form-field>
|
||||
</p>
|
||||
<button mat-flat-button color="primary" type="submit">Save</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
7
console-webapp/src/app/ote/newOte.component.scss
Normal file
7
console-webapp/src/app/ote/newOte.component.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
.console-app__new-ote {
|
||||
max-width: 720px;
|
||||
mat-card-content {
|
||||
white-space: break-spaces;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
81
console-webapp/src/app/ote/newOte.component.ts
Normal file
81
console-webapp/src/app/ote/newOte.component.ts
Normal file
@@ -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<string, string> {
|
||||
password: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-ote',
|
||||
templateUrl: './newOte.component.html',
|
||||
styleUrls: ['./newOte.component.scss'],
|
||||
})
|
||||
export class NewOteComponent {
|
||||
public static PATH = 'new-ote';
|
||||
|
||||
oteCreateResponse = signal<OteCreateResponse | undefined>(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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
31
console-webapp/src/app/ote/oteStatus.component.html
Normal file
31
console-webapp/src/app/ote/oteStatus.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<app-selected-registrar-wrapper>
|
||||
<h1 class="mat-headline-4">OT&E Status Check</h1>
|
||||
@if(isOte()) {
|
||||
<h1 *ngIf="oteStatusResponse().length">
|
||||
Status:
|
||||
<span>{{ oteStatusUnfinished().length ? "Unfinished" : "Completed" }}</span>
|
||||
</h1>
|
||||
<div class="console-app__ote-status">
|
||||
@if(oteStatusCompleted().length) {
|
||||
<div class="console-app__ote-status_completed">
|
||||
<h1>Completed</h1>
|
||||
<div *ngFor="let entry of oteStatusCompleted()">
|
||||
<mat-icon>check_box</mat-icon>{{ entry.description }}
|
||||
</div>
|
||||
</div>
|
||||
} @if(oteStatusUnfinished().length) {
|
||||
<div class="console-app__ote-status_unfinished">
|
||||
<h1>Unfinished</h1>
|
||||
<div *ngFor="let entry of oteStatusUnfinished()">
|
||||
<mat-icon>check_box_outline_blank</mat-icon>{{ entry.description }}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<h1>
|
||||
Registrar {{ registrarService.registrar()?.registrarId }} is not an OT&E
|
||||
registrar
|
||||
</h1>
|
||||
}
|
||||
</app-selected-registrar-wrapper>
|
||||
28
console-webapp/src/app/ote/oteStatus.component.scss
Normal file
28
console-webapp/src/app/ote/oteStatus.component.scss
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
console-webapp/src/app/ote/oteStatus.component.ts
Normal file
62
console-webapp/src/app/ote/oteStatus.component.ts
Normal file
@@ -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<OteStatusResponse[]>([]);
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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<OteCreateResponse> {
|
||||
return this.backend
|
||||
.generateOte(oteForm, registrarId)
|
||||
.pipe(tap((_) => this.loadRegistrars()));
|
||||
}
|
||||
|
||||
oteStatus(registrarId: string): Observable<OteStatusResponse[]> {
|
||||
return this.backend.getOteStatus(registrarId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,14 @@
|
||||
</button>
|
||||
<div class="spacer"></div>
|
||||
@if(!inEdit && !registrarNotFound) {
|
||||
<button
|
||||
mat-stroked-button
|
||||
(click)="checkOteStatus()"
|
||||
aria-label="Check OT&E account"
|
||||
[elementId]="getElementIdForOteBlock()"
|
||||
>
|
||||
Check OT&E Status
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
|
||||
@@ -18,6 +18,8 @@ import { MatChipInputEvent } from '@angular/material/chips';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { OteStatusComponent } from '../ote/oteStatus.component';
|
||||
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
|
||||
import { Registrar, RegistrarService } from './registrar.service';
|
||||
import { RegistrarComponent, columns } from './registrarsTable.component';
|
||||
|
||||
@@ -70,6 +72,14 @@ export class RegistrarDetailsComponent implements OnInit {
|
||||
];
|
||||
}
|
||||
|
||||
checkOteStatus() {
|
||||
this.router.navigate([OteStatusComponent.PATH]);
|
||||
}
|
||||
|
||||
getElementIdForOteBlock() {
|
||||
return RESTRICTED_ELEMENTS.OTE;
|
||||
}
|
||||
|
||||
removeTLD(tld: string) {
|
||||
this.registrarInEdit.allowedTlds = this.registrarInEdit.allowedTlds?.filter(
|
||||
(v) => v != tld
|
||||
@@ -91,6 +101,6 @@ export class RegistrarDetailsComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
this.subscription && this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,17 @@
|
||||
<div class="console-app__registrars">
|
||||
<div class="console-app__registrars-header">
|
||||
<h1 class="mat-headline-4">Registrars</h1>
|
||||
<div class="spacer"></div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
(click)="createOteAccount()"
|
||||
aria-label="Generate OT&E accounts"
|
||||
[elementId]="getElementIdForOteBlock()"
|
||||
>
|
||||
Create OT&E accounts
|
||||
</button>
|
||||
<button
|
||||
class="console-app__registrars-new"
|
||||
mat-flat-button
|
||||
color="primary"
|
||||
(click)="openNewRegistrar()"
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
min-width: $min-width !important;
|
||||
}
|
||||
|
||||
&__registrars-new {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
&__registrars-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -17,6 +17,8 @@ import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { NewOteComponent } from '../ote/newOte.component';
|
||||
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
|
||||
import { Registrar, RegistrarService } from './registrar.service';
|
||||
|
||||
export const columns = [
|
||||
@@ -103,6 +105,14 @@ export class RegistrarComponent {
|
||||
this.dataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
createOteAccount() {
|
||||
this.router.navigate([NewOteComponent.PATH]);
|
||||
}
|
||||
|
||||
getElementIdForOteBlock() {
|
||||
return RESTRICTED_ELEMENTS.OTE;
|
||||
}
|
||||
|
||||
openDetails(registrarId: string) {
|
||||
this.router.navigate(['registrars/', registrarId], {
|
||||
queryParamsHandling: 'merge',
|
||||
|
||||
@@ -17,10 +17,11 @@ import { UserDataService } from '../services/userData.service';
|
||||
|
||||
export enum RESTRICTED_ELEMENTS {
|
||||
REGISTRAR_ELEMENT,
|
||||
OTE,
|
||||
}
|
||||
|
||||
export const DISABLED_ELEMENTS_PER_ROLE = {
|
||||
NONE: [RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT],
|
||||
NONE: [RESTRICTED_ELEMENTS.REGISTRAR_ELEMENT, RESTRICTED_ELEMENTS.OTE],
|
||||
};
|
||||
|
||||
@Directive({
|
||||
|
||||
@@ -19,6 +19,8 @@ import { Observable, catchError, of, throwError } from 'rxjs';
|
||||
import { DomainListResult } from 'src/app/domains/domainList.service';
|
||||
import { DomainLocksResult } from 'src/app/domains/registryLock.service';
|
||||
import { RegistryLockVerificationResponse } from 'src/app/lock/registryLockVerify.service';
|
||||
import { OteCreateResponse } from 'src/app/ote/newOte.component';
|
||||
import { OteStatusResponse } from 'src/app/ote/oteStatus.component';
|
||||
import {
|
||||
Registrar,
|
||||
SecuritySettingsBackendModel,
|
||||
@@ -198,6 +200,22 @@ export class BackendService {
|
||||
.pipe(catchError((err) => this.errorCatcher<DomainLocksResult[]>(err)));
|
||||
}
|
||||
|
||||
generateOte(
|
||||
oteForm: Object,
|
||||
registrarId: string
|
||||
): Observable<OteCreateResponse> {
|
||||
return this.http.post<OteCreateResponse>(
|
||||
`/console-api/ote?registrarId=${registrarId}`,
|
||||
oteForm
|
||||
);
|
||||
}
|
||||
|
||||
getOteStatus(registrarId: string) {
|
||||
return this.http
|
||||
.get<OteStatusResponse[]>(`/console-api/ote?registrarId=${registrarId}`)
|
||||
.pipe(catchError((err) => this.errorCatcher<OteStatusResponse[]>(err)));
|
||||
}
|
||||
|
||||
verifyRegistryLockRequest(
|
||||
lockVerificationCode: string
|
||||
): Observable<RegistryLockVerificationResponse> {
|
||||
|
||||
Reference in New Issue
Block a user