Add settings to console home page, update settings->security styles (#2144)

This commit is contained in:
Pavlo Tkach 2023-09-14 12:37:54 -04:00 committed by GitHub
parent 6c18ea9cff
commit 5eb44c165c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 156 additions and 77 deletions

View file

@ -45,39 +45,41 @@ import { ResourcesWidgetComponent } from './home/widgets/resources-widget.compon
import { EppWidgetComponent } from './home/widgets/epp-widget.component'; import { EppWidgetComponent } from './home/widgets/epp-widget.component';
import { BillingWidgetComponent } from './home/widgets/billing-widget.component'; import { BillingWidgetComponent } from './home/widgets/billing-widget.component';
import { DomainsWidgetComponent } from './home/widgets/domains-widget.component'; import { DomainsWidgetComponent } from './home/widgets/domains-widget.component';
import { SettingsWidgetComponent } from './home/widgets/settings-widget.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
HomeComponent, BillingWidgetComponent,
TldsComponent,
HeaderComponent,
SettingsComponent,
SettingsContactComponent,
ContactDetailsDialogComponent, ContactDetailsDialogComponent,
RegistrarComponent,
SecurityComponent,
EmptyRegistrar,
RegistrarSelectorComponent,
ContactWidgetComponent, ContactWidgetComponent,
DomainsWidgetComponent, DomainsWidgetComponent,
PromotionsWidgetComponent, EmptyRegistrar,
TldsWidgetComponent,
ResourcesWidgetComponent,
EppWidgetComponent, EppWidgetComponent,
BillingWidgetComponent, HeaderComponent,
HomeComponent,
PromotionsWidgetComponent,
RegistrarComponent,
RegistrarSelectorComponent,
ResourcesWidgetComponent,
SecurityComponent,
SettingsComponent,
SettingsContactComponent,
SettingsWidgetComponent,
TldsComponent,
TldsWidgetComponent,
], ],
imports: [ imports: [
HttpClientModule,
FormsModule,
MaterialModule,
BrowserModule,
AppRoutingModule, AppRoutingModule,
BrowserAnimationsModule, BrowserAnimationsModule,
BrowserModule,
FormsModule,
HttpClientModule,
MaterialModule,
], ],
providers: [ providers: [
GlobalLoaderService,
BackendService, BackendService,
GlobalLoaderService,
RegistrarGuard, RegistrarGuard,
{ {
provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, provide: MAT_FORM_FIELD_DEFAULT_OPTIONS,

View file

@ -3,7 +3,15 @@
<button mat-icon-button aria-label="Open menu" (click)="toggleNavPane()"> <button mat-icon-button aria-label="Open menu" (click)="toggleNavPane()">
<mat-icon>menu</mat-icon> <mat-icon>menu</mat-icon>
</button> </button>
<span>Google Registry</span> <span>
<a
[routerLink]="'/home'"
routerLinkActive="active"
class="console-app__logo"
>
Google Registry
</a>
</span>
<span class="spacer"></span> <span class="spacer"></span>
<app-registrar-selector /> <app-registrar-selector />
<button mat-icon-button aria-label="Open FAQ"> <button mat-icon-button aria-label="Open FAQ">

View file

@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
.console-app__header { .console-app {
margin-top: 0; &__logo {
color: inherit;
text-decoration: none;
}
} }
.spacer { .spacer {
flex: 1; flex: 1;

View file

@ -5,7 +5,7 @@
<div app-contact-widget class="console-app__widget-wrapper__wide"></div> <div app-contact-widget class="console-app__widget-wrapper__wide"></div>
<div app-tlds-widget></div> <div app-tlds-widget></div>
<div app-promotions-widget class="console-app__widget-wrapper__wide"></div> <div app-promotions-widget class="console-app__widget-wrapper__wide"></div>
<div app-promotions-widget class="console-app__widget-wrapper__wide"></div> <div app-settings-widget class="console-app__widget-wrapper__wide"></div>
<div app-resources-widget></div> <div app-resources-widget></div>
<div app-billing-widget></div> <div app-billing-widget></div>
<div app-epp-widget></div> <div app-epp-widget></div>

View file

@ -0,0 +1,38 @@
<mat-card class="console-app__widget-wrapper__wide">
<mat-card-content>
<div class="console-app__widget">
<div class="console-app__widget_left">
<mat-icon class="console-app__widget-icon">settings</mat-icon>
<h1 class="console-app__widget-title">Settings</h1>
<h4 class="secondary-text text-center">
Configure registrar settings, manage console users, and view activity
log.
</h4>
</div>
<div class="console-app__widget_right">
<button mat-button color="primary" class="console-app__widget-link">
Contact Information
</button>
<p class="secondary-text">Manage Primary, Technical, etc contacts.</p>
<button mat-button color="primary" class="console-app__widget-link">
Security
</button>
<p class="secondary-text">
Manage IP allow lists and SSL certificates.
</p>
<button mat-button color="primary" class="console-app__widget-link">
Nomulus Password
</button>
<p class="secondary-text">Reset your Nomulus password.</p>
<button mat-button color="primary" class="console-app__widget-link">
User Management
</button>
<p class="secondary-text">Create and manage console user accounts</p>
<button mat-button color="primary" class="console-app__widget-link">
Registrar Management
</button>
<p class="secondary-text">Create and manage registrar accounts</p>
</div>
</div>
</mat-card-content>
</mat-card>

View file

@ -0,0 +1,23 @@
// 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';
@Component({
selector: '[app-settings-widget]',
templateUrl: './settings-widget.component.html',
})
export class SettingsWidgetComponent {
constructor() {}
}

View file

@ -14,7 +14,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { BackendService } from '../shared/services/backend.service'; import { BackendService } from '../shared/services/backend.service';
import { Subject } from 'rxjs'; import { Observable, Subject, tap } from 'rxjs';
import { import {
GlobalLoader, GlobalLoader,
GlobalLoaderService, GlobalLoaderService,
@ -54,24 +54,33 @@ export class RegistrarService implements GlobalLoader {
private backend: BackendService, private backend: BackendService,
private globalLoader: GlobalLoaderService private globalLoader: GlobalLoaderService
) { ) {
this.backend.getRegistrars().subscribe((r) => { this.loadRegistrars().subscribe((r) => {
this.globalLoader.stopGlobalLoader(this); this.globalLoader.stopGlobalLoader(this);
this.registrars = r;
}); });
this.globalLoader.startGlobalLoader(this); this.globalLoader.startGlobalLoader(this);
} }
public updateRegistrar(registrarId: string) {
this.activeRegistrarId = registrarId;
this.activeRegistrarIdChange.next(registrarId);
}
public get registrar(): Registrar { public get registrar(): Registrar {
return this.registrars.filter( return this.registrars.filter(
(r) => r.registrarId === this.activeRegistrarId (r) => r.registrarId === this.activeRegistrarId
)[0]; )[0];
} }
public updateRegistrar(registrarId: string) {
this.activeRegistrarId = registrarId;
this.activeRegistrarIdChange.next(registrarId);
}
public loadRegistrars(): Observable<Registrar[]> {
return this.backend.getRegistrars().pipe(
tap((registrars) => {
if (registrars) {
this.registrars = registrars;
}
})
);
}
loadingTimeout() { loadingTimeout() {
// TODO: Decide what to do when timeout happens // TODO: Decide what to do when timeout happens
} }

View file

@ -129,7 +129,7 @@ export class ContactDetailsDialogComponent {
operationObservable.subscribe({ operationObservable.subscribe({
complete: this.onCloseCallback.bind(this), complete: this.onCloseCallback.bind(this),
error: (err: HttpErrorResponse) => { error: (err: HttpErrorResponse) => {
this._snackBar.open(err.statusText, undefined, { this._snackBar.open(err.error, undefined, {
duration: 1500, duration: 1500,
}); });
}, },
@ -148,7 +148,8 @@ export default class ContactComponent {
private dialog: MatDialog, private dialog: MatDialog,
private bottomSheet: MatBottomSheet, private bottomSheet: MatBottomSheet,
private breakpointObserver: BreakpointObserver, private breakpointObserver: BreakpointObserver,
public contactService: ContactService public contactService: ContactService,
private _snackBar: MatSnackBar
) { ) {
// TODO: Refactor to registrarId service // TODO: Refactor to registrarId service
this.loading = true; this.loading = true;
@ -170,7 +171,13 @@ export default class ContactComponent {
deleteContact(contact: Contact) { deleteContact(contact: Contact) {
if (confirm(`Please confirm contact ${contact.name} delete`)) { if (confirm(`Please confirm contact ${contact.name} delete`)) {
this.contactService.deleteContact(contact).subscribe(); this.contactService.deleteContact(contact).subscribe({
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, {
duration: 1500,
});
},
});
} }
} }

View file

@ -17,8 +17,10 @@
dataSource.ipAddressAllowList.length > 0 dataSource.ipAddressAllowList.length > 0
" "
> >
<div *ngFor="let item of dataSource.ipAddressAllowList; index as index"> <div
<div>{{ item.value }}</div> *ngFor="let item of dataSource.ipAddressAllowList; index as index"
class="settings-security__ipRecord"
>
<mat-form-field> <mat-form-field>
<input <input
matInput matInput

View file

@ -17,6 +17,9 @@
h1 { h1 {
margin: 0; margin: 0;
} }
&__ipRecord {
margin-bottom: 1rem;
}
&__section { &__section {
display: flex; display: flex;
align-items: stretch; align-items: stretch;

View file

@ -13,9 +13,14 @@
// limitations under the License. // limitations under the License.
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { SecurityService, SecuritySettings } from './security.service'; import {
SecurityService,
SecuritySettings,
apiToUiConverter,
} from './security.service';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { RegistrarService } from 'src/app/registrar/registrar.service';
@Component({ @Component({
selector: 'app-security', selector: 'app-security',
@ -30,33 +35,19 @@ export default class SecurityComponent {
constructor( constructor(
public securityService: SecurityService, public securityService: SecurityService,
private _snackBar: MatSnackBar private _snackBar: MatSnackBar,
public registrarService: RegistrarService
) { ) {
this.loading = true; this.dataSource = apiToUiConverter(this.registrarService.registrar);
this.securityService.fetchSecurityDetails().subscribe({
complete: () => {
this.dataSource = this.securityService.securitySettings;
this.loading = false;
},
error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, {
duration: 1500,
});
this.loading = false;
},
});
} }
enableEdit() { enableEdit() {
this.inEdit = true; this.inEdit = true;
this.dataSource = JSON.parse(
JSON.stringify(this.securityService.securitySettings)
);
} }
disableEdit() { cancel() {
this.inEdit = false; this.inEdit = false;
this.dataSource = this.securityService.securitySettings; this.resetDataSource();
} }
createIpEntry() { createIpEntry() {
@ -68,7 +59,7 @@ export default class SecurityComponent {
this.securityService.saveChanges(this.dataSource).subscribe({ this.securityService.saveChanges(this.dataSource).subscribe({
complete: () => { complete: () => {
this.loading = false; this.loading = false;
this.dataSource = this.securityService.securitySettings; this.resetDataSource();
}, },
error: (err: HttpErrorResponse) => { error: (err: HttpErrorResponse) => {
this._snackBar.open(err.error, undefined, { this._snackBar.open(err.error, undefined, {
@ -76,16 +67,15 @@ export default class SecurityComponent {
}); });
}, },
}); });
this.disableEdit(); this.cancel();
}
cancel() {
this.dataSource = this.securityService.securitySettings;
this.inEdit = false;
} }
removeIpEntry(index: number) { removeIpEntry(index: number) {
this.dataSource.ipAddressAllowList = this.dataSource.ipAddressAllowList =
this.dataSource.ipAddressAllowList?.filter((_, i) => i != index); this.dataSource.ipAddressAllowList?.filter((_, i) => i != index);
} }
resetDataSource() {
this.dataSource = apiToUiConverter(this.registrarService.registrar);
}
} }

View file

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { tap } from 'rxjs'; import { switchMap } from 'rxjs';
import { RegistrarService } from 'src/app/registrar/registrar.service'; import { RegistrarService } from 'src/app/registrar/registrar.service';
import { BackendService } from 'src/app/shared/services/backend.service'; import { BackendService } from 'src/app/shared/services/backend.service';
@ -61,16 +61,6 @@ export class SecurityService {
private registrarService: RegistrarService private registrarService: RegistrarService
) {} ) {}
fetchSecurityDetails() {
return this.backend
.getSecuritySettings(this.registrarService.activeRegistrarId)
.pipe(
tap((securitySettings: SecuritySettingsBackendModel) => {
this.securitySettings = apiToUiConverter(securitySettings);
})
);
}
saveChanges(newSecuritySettings: SecuritySettings) { saveChanges(newSecuritySettings: SecuritySettings) {
return this.backend return this.backend
.postSecuritySettings( .postSecuritySettings(
@ -78,8 +68,8 @@ export class SecurityService {
uiToApiConverter(newSecuritySettings) uiToApiConverter(newSecuritySettings)
) )
.pipe( .pipe(
tap((_) => { switchMap(() => {
this.securitySettings = newSecuritySettings; return this.registrarService.loadRegistrars();
}) })
); );
} }

View file

@ -44,7 +44,7 @@ body {
&-link { &-link {
padding: 0 !important; padding: 0 !important;
text-align: left; text-align: left;
margin-bottom: 0.5rem; height: 20px !important;
} }
&-title { &-title {
color: var(--primary) !important; color: var(--primary) !important;
@ -65,6 +65,10 @@ body {
flex: 1; flex: 1;
border-left: 1px solid var(--secondary); border-left: 1px solid var(--secondary);
padding-left: 20px; padding-left: 20px;
.secondary-text {
margin-bottom: 0.3rem;
font-size: 0.8rem;
}
} }
} }
} }

View file

@ -93,7 +93,7 @@ public class RegistrarsAction implements JsonGetAction {
return; return;
} }
ImmutableList<Registrar> registrars = ImmutableList<Registrar> registrars =
Streams.stream(Registrar.loadAllCached()) Streams.stream(Registrar.loadAll())
.filter(r -> r.getType() == Registrar.Type.REAL) .filter(r -> r.getType() == Registrar.Type.REAL)
.collect(ImmutableList.toImmutableList()); .collect(ImmutableList.toImmutableList());