Skip to content

Commit 5700a00

Browse files
authored
Add console history frontend (#2832)
1 parent dc9f5b9 commit 5700a00

14 files changed

+421
-81
lines changed

console-webapp/src/app/app-routing.module.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import SecurityComponent from './settings/security/security.component';
2626
import { SettingsComponent } from './settings/settings.component';
2727
import { SupportComponent } from './support/support.component';
2828
import RdapComponent from './settings/rdap/rdap.component';
29+
import { HistoryComponent } from './history/history.component';
2930
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
3031

3132
export interface RouteWithIcon extends Route {
@@ -64,13 +65,18 @@ export const routes: RouteWithIcon[] = [
6465
title: 'Dashboard',
6566
iconName: 'view_comfy_alt',
6667
},
67-
// { path: 'tlds', component: TldsComponent, title: "TLDs", iconName: "event_list" },
6868
{
6969
path: DomainListComponent.PATH,
7070
component: DomainListComponent,
7171
title: 'Domains',
7272
iconName: 'view_list',
7373
},
74+
{
75+
path: HistoryComponent.PATH,
76+
component: HistoryComponent,
77+
// title: 'History',
78+
// iconName: 'history',
79+
},
7480
{
7581
path: SettingsComponent.PATH,
7682
component: SettingsComponent,

console-webapp/src/app/app.module.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,14 @@ import { GlobalLoaderService } from './shared/services/globalLoader.service';
5656
import { UserDataService } from './shared/services/userData.service';
5757
import { SnackBarModule } from './snackbar.module';
5858
import { SupportComponent } from './support/support.component';
59-
import { TldsComponent } from './tlds/tlds.component';
6059
import { ForceFocusDirective } from './shared/directives/forceFocus.directive';
6160
import RdapComponent from './settings/rdap/rdap.component';
6261
import RdapEditComponent from './settings/rdap/rdapEdit.component';
6362
import { PocReminderComponent } from './shared/components/pocReminder/pocReminder.component';
6463
import { PasswordResetVerifyComponent } from './shared/components/passwordReset/passwordResetVerify.component';
6564
import { PasswordInputForm } from './shared/components/passwordReset/passwordInputForm.component';
65+
import { HistoryComponent } from './history/history.component';
66+
import { HistoryListComponent } from './history/historyList.component';
6667

6768
@NgModule({
6869
declarations: [SelectedRegistrarWrapper],
@@ -81,6 +82,8 @@ export class SelectedRegistrarModule {}
8182
EppPasswordEditComponent,
8283
ForceFocusDirective,
8384
HeaderComponent,
85+
HistoryComponent,
86+
HistoryListComponent,
8487
HomeComponent,
8588
LocationBackDirective,
8689
NavigationComponent,
@@ -104,7 +107,6 @@ export class SelectedRegistrarModule {}
104107
SettingsComponent,
105108
SettingsContactComponent,
106109
SupportComponent,
107-
TldsComponent,
108110
UserLevelVisibility,
109111
],
110112
bootstrap: [AppComponent],
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<app-selected-registrar-wrapper>
2+
<div class="history-log">
3+
<h1 class="mat-headline-4" forceFocus>
4+
Registrar Console Activity History
5+
</h1>
6+
<mat-tab-group
7+
[elementId]="getElementIdForUserLog()"
8+
class="history-log__tabs"
9+
>
10+
<mat-tab label="Registrar Activity">
11+
<div class="spacer"></div>
12+
13+
<app-history-list
14+
[historyRecords]="historyService.historyRecordsRegistrar()"
15+
[isLoading]="isLoading"
16+
/>
17+
</mat-tab>
18+
<mat-tab label="User Activity">
19+
<div class="spacer"></div>
20+
<form (ngSubmit)="loadHistory()" #form="ngForm">
21+
<section>
22+
<mat-form-field appearance="outline">
23+
<mat-label>Console User Email: </mat-label>
24+
<input
25+
matInput
26+
id="email"
27+
type="email"
28+
name="consoleUserEmail"
29+
required
30+
email
31+
[(ngModel)]="consoleUserEmail"
32+
#emailControl="ngModel"
33+
/>
34+
</mat-form-field>
35+
</section>
36+
<div class="spacer"></div>
37+
<button
38+
mat-flat-button
39+
color="primary"
40+
type="submit"
41+
aria-label="Search user history"
42+
[disabled]="!form.valid"
43+
>
44+
Search
45+
</button>
46+
</form>
47+
<div class="spacer"></div>
48+
<app-history-list
49+
[historyRecords]="historyService.historyRecordsUser()"
50+
[isLoading]="isLoading"
51+
/>
52+
</mat-tab>
53+
</mat-tab-group>
54+
</div>
55+
56+
<app-history-list
57+
[elementId]="getElementIdForUserLog()"
58+
[isReverse]="true"
59+
[historyRecords]="historyService.historyRecordsUser()"
60+
[isLoading]="isLoading"
61+
/>
62+
</app-selected-registrar-wrapper>
Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
1+
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -12,17 +12,11 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
.console-tlds {
16-
&__cards {
17-
display: flex;
18-
border-top: 1px solid #ddd;
19-
padding: 1rem;
20-
}
21-
&__card {
22-
max-width: 300px;
23-
}
24-
&__card-links {
25-
display: flex;
26-
flex-direction: column;
15+
.history-log {
16+
font-family: "Roboto", sans-serif;
17+
max-width: 760px;
18+
19+
.spacer {
20+
margin: 20px 0;
2721
}
2822
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Component, effect } from '@angular/core';
16+
import { UserDataService } from '../shared/services/userData.service';
17+
import { BackendService } from '../shared/services/backend.service';
18+
import { RegistrarService } from '../registrar/registrar.service';
19+
import { HistoryService } from './history.service';
20+
import { MatSnackBar } from '@angular/material/snack-bar';
21+
import {
22+
GlobalLoader,
23+
GlobalLoaderService,
24+
} from '../shared/services/globalLoader.service';
25+
import { HttpErrorResponse } from '@angular/common/http';
26+
import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive';
27+
28+
@Component({
29+
selector: 'app-history',
30+
templateUrl: './history.component.html',
31+
styleUrls: ['./history.component.scss'],
32+
providers: [HistoryService],
33+
standalone: false,
34+
})
35+
export class HistoryComponent implements GlobalLoader {
36+
public static PATH = 'history';
37+
38+
consoleUserEmail: string = '';
39+
isLoading: boolean = false;
40+
41+
constructor(
42+
private backendService: BackendService,
43+
private registrarService: RegistrarService,
44+
protected historyService: HistoryService,
45+
protected globalLoader: GlobalLoaderService,
46+
protected userDataService: UserDataService,
47+
private _snackBar: MatSnackBar
48+
) {
49+
effect(() => {
50+
if (registrarService.registrarId()) {
51+
this.loadHistory();
52+
}
53+
});
54+
}
55+
56+
getElementIdForUserLog() {
57+
return RESTRICTED_ELEMENTS.ACTIVITY_PER_USER;
58+
}
59+
60+
loadingTimeout() {
61+
this._snackBar.open('Timeout loading records history');
62+
}
63+
64+
loadHistory() {
65+
this.globalLoader.startGlobalLoader(this);
66+
this.isLoading = true;
67+
this.historyService
68+
.getHistoryLog(this.registrarService.registrarId(), this.consoleUserEmail)
69+
.subscribe({
70+
error: (err: HttpErrorResponse) => {
71+
this._snackBar.open(err.error || err.message);
72+
this.isLoading = false;
73+
},
74+
next: () => {
75+
this.globalLoader.stopGlobalLoader(this);
76+
this.isLoading = false;
77+
},
78+
});
79+
}
80+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2025 The Nomulus Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { Injectable, signal } from '@angular/core';
16+
import { BackendService } from '../shared/services/backend.service';
17+
import { tap } from 'rxjs';
18+
19+
export interface HistoryRecord {
20+
modificationTime: string;
21+
type: string;
22+
description: string;
23+
actingUser: {
24+
emailAddress: string;
25+
};
26+
}
27+
28+
@Injectable()
29+
export class HistoryService {
30+
historyRecordsRegistrar = signal<HistoryRecord[]>([]);
31+
historyRecordsUser = signal<HistoryRecord[]>([]);
32+
33+
constructor(private backendService: BackendService) {}
34+
35+
getHistoryLog(registrarId: string, userEmail?: string) {
36+
return this.backendService.getHistoryLog(registrarId, userEmail).pipe(
37+
tap((historyRecords: HistoryRecord[]) => {
38+
if (userEmail) {
39+
this.historyRecordsUser.set(historyRecords);
40+
} else {
41+
this.historyRecordsRegistrar.set(historyRecords);
42+
}
43+
})
44+
);
45+
}
46+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
@if (!isLoading && historyRecords.length == 0) {
2+
<div class="history-list__no-records">
3+
<mat-icon class="history-list__no-records-icon secondary-text"
4+
>apps_outage</mat-icon
5+
>
6+
<h1>No records found</h1>
7+
</div>
8+
} @else {
9+
<mat-card>
10+
<mat-card-content>
11+
<mat-list role="list">
12+
<ng-container *ngFor="let item of historyRecords; let last = last">
13+
<mat-list-item class="history-list__item">
14+
<mat-icon
15+
[ngClass]="getIconClass(item.type)"
16+
class="history-list__icon"
17+
>
18+
{{ getIconForType(item.type) }}
19+
</mat-icon>
20+
21+
<div class="history-list__content">
22+
<div class="history-list__description">
23+
<span class="history-list__description--main">{{
24+
item.type
25+
}}</span>
26+
<div>
27+
<mat-chip
28+
*ngIf="parseDescription(item.description).detail"
29+
class="history-list__chip"
30+
>
31+
{{ parseDescription(item.description).detail }}
32+
</mat-chip>
33+
</div>
34+
</div>
35+
<div class="history-list__user">
36+
<b>User - {{ item.actingUser.emailAddress }}</b>
37+
</div>
38+
</div>
39+
40+
<span class="history-list__timestamp">
41+
{{ item.modificationTime | date : "MMM d, y, h:mm a" }}
42+
</span>
43+
</mat-list-item>
44+
45+
<mat-divider *ngIf="!last"></mat-divider>
46+
</ng-container>
47+
</mat-list>
48+
</mat-card-content>
49+
</mat-card>
50+
}

0 commit comments

Comments
 (0)