1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
15+ import { SelectionModel } from '@angular/cdk/collections' ;
1516import { HttpErrorResponse , HttpStatusCode } from '@angular/common/http' ;
16- import { Component , ViewChild , effect } from '@angular/core' ;
17+ import { Component , ViewChild , effect , Inject } from '@angular/core' ;
1718import { MatPaginator , PageEvent } from '@angular/material/paginator' ;
1819import { MatSnackBar } from '@angular/material/snack-bar' ;
1920import { MatTableDataSource } from '@angular/material/table' ;
20- import { Subject , debounceTime } from 'rxjs' ;
21+ import { Subject , debounceTime , take , filter } from 'rxjs' ;
2122import { RegistrarService } from '../registrar/registrar.service' ;
2223import { Domain , DomainListService } from './domainList.service' ;
2324import { RegistryLockComponent } from './registryLock.component' ;
2425import { RegistryLockService } from './registryLock.service' ;
26+ import {
27+ MAT_DIALOG_DATA ,
28+ MatDialog ,
29+ MatDialogRef ,
30+ } from '@angular/material/dialog' ;
31+ import { RESTRICTED_ELEMENTS } from '../shared/directives/userLevelVisiblity.directive' ;
32+
33+ interface DomainResponse {
34+ message : string ;
35+ responseCode : string ;
36+ }
37+
38+ interface DomainData {
39+ [ domain : string ] : DomainResponse ;
40+ }
41+
42+ @Component ( {
43+ selector : 'app-response-dialog' ,
44+ template : `
45+ <h2 mat-dialog-title>{{ data.title }}</h2>
46+ <mat-dialog-content [innerHTML]="data.content" />
47+ <mat-dialog-actions>
48+ <button mat-button (click)="onClose()">Close</button>
49+ </mat-dialog-actions>
50+ ` ,
51+ } )
52+ export class ResponseDialogComponent {
53+ constructor (
54+ public dialogRef : MatDialogRef < ReasonDialogComponent > ,
55+ @Inject ( MAT_DIALOG_DATA )
56+ public data : { title : string ; content : string }
57+ ) { }
58+
59+ onClose ( ) : void {
60+ this . dialogRef . close ( ) ;
61+ }
62+ }
63+
64+ @Component ( {
65+ selector : 'app-reason-dialog' ,
66+ template : `
67+ <h2 mat-dialog-title>
68+ Please provide a reason for {{ data.operation }} the domain(s):
69+ </h2>
70+ <mat-dialog-content>
71+ <mat-form-field appearance="outline" style="width:100%">
72+ <textarea matInput [(ngModel)]="reason" rows="4"></textarea>
73+ </mat-form-field>
74+ </mat-dialog-content>
75+ <mat-dialog-actions>
76+ <button mat-button (click)="onCancel()">Cancel</button>
77+ <button mat-button color="warn" (click)="onDelete()" [disabled]="!reason">
78+ Delete
79+ </button>
80+ </mat-dialog-actions>
81+ ` ,
82+ } )
83+ export class ReasonDialogComponent {
84+ reason : string = '' ;
85+
86+ constructor (
87+ public dialogRef : MatDialogRef < ReasonDialogComponent > ,
88+ @Inject ( MAT_DIALOG_DATA )
89+ public data : { operation : 'deleting' | 'suspending' }
90+ ) { }
91+
92+ onDelete ( ) : void {
93+ this . dialogRef . close ( this . reason ) ;
94+ }
95+
96+ onCancel ( ) : void {
97+ this . dialogRef . close ( ) ;
98+ }
99+ }
25100
26101@Component ( {
27102 selector : 'app-domain-list' ,
@@ -31,8 +106,10 @@ import { RegistryLockService } from './registryLock.service';
31106export class DomainListComponent {
32107 public static PATH = 'domain-list' ;
33108 private readonly DEBOUNCE_MS = 500 ;
109+ isAllSelected = false ;
34110
35111 displayedColumns : string [ ] = [
112+ 'select' ,
36113 'domainName' ,
37114 'creationTime' ,
38115 'registrationExpirationTime' ,
@@ -42,6 +119,7 @@ export class DomainListComponent {
42119 ] ;
43120
44121 dataSource : MatTableDataSource < Domain > = new MatTableDataSource ( ) ;
122+ selection = new SelectionModel < Domain > ( true , [ ] , undefined , this . isChecked ( ) ) ;
45123 isLoading = true ;
46124
47125 searchTermSubject = new Subject < string > ( ) ;
@@ -51,13 +129,18 @@ export class DomainListComponent {
51129 resultsPerPage = 50 ;
52130 totalResults ?: number = 0 ;
53131
132+ reason : string = '' ;
133+
134+ operationResult : DomainData | undefined ;
135+
54136 @ViewChild ( MatPaginator , { static : true } ) paginator ! : MatPaginator ;
55137
56138 constructor (
57139 protected domainListService : DomainListService ,
58140 protected registrarService : RegistrarService ,
59141 protected registryLockService : RegistryLockService ,
60- private _snackBar : MatSnackBar
142+ private _snackBar : MatSnackBar ,
143+ private dialog : MatDialog
61144 ) {
62145 effect ( ( ) => {
63146 this . pageNumber = 0 ;
@@ -134,6 +217,98 @@ export class DomainListComponent {
134217 onPageChange ( event : PageEvent ) {
135218 this . pageNumber = event . pageIndex ;
136219 this . resultsPerPage = event . pageSize ;
220+ this . selection . clear ( ) ;
137221 this . reloadData ( ) ;
138222 }
223+
224+ toggleAllRows ( ) {
225+ if ( this . isAllSelected ) {
226+ this . selection . clear ( ) ;
227+ this . isAllSelected = false ;
228+ return ;
229+ }
230+
231+ this . selection . select ( ...this . dataSource . data ) ;
232+ this . isAllSelected = true ;
233+ }
234+
235+ checkboxLabel ( row ?: Domain ) : string {
236+ if ( ! row ) {
237+ return `${ this . isAllSelected ? 'deselect' : 'select' } all` ;
238+ }
239+ return `${ this . selection . isSelected ( row ) ? 'deselect' : 'select' } row ${
240+ row . domainName
241+ } `;
242+ }
243+
244+ private isChecked ( ) : ( ( o1 : Domain , o2 : Domain ) => boolean ) | undefined {
245+ return ( o1 : Domain , o2 : Domain ) => {
246+ if ( ! o1 . domainName || ! o2 . domainName ) {
247+ return false ;
248+ }
249+
250+ return this . isAllSelected || o1 . domainName === o2 . domainName ;
251+ } ;
252+ }
253+
254+ getElementIdForBulkDelete ( ) {
255+ return RESTRICTED_ELEMENTS . BULK_DELETE ;
256+ }
257+
258+ getOperationMessage ( domain : string ) {
259+ if ( this . operationResult && this . operationResult [ domain ] )
260+ return this . operationResult [ domain ] . message ;
261+ return '' ;
262+ }
263+
264+ sendDeleteRequest ( reason : string ) {
265+ this . isLoading = true ;
266+ this . domainListService
267+ . deleteDomains (
268+ this . selection . selected ,
269+ reason ,
270+ this . registrarService . registrarId ( )
271+ )
272+ . pipe ( take ( 1 ) )
273+ . subscribe ( {
274+ next : ( result : DomainData ) => {
275+ this . isLoading = false ;
276+ const successCount = Object . keys ( result ) . filter ( ( domainName ) =>
277+ result [ domainName ] . responseCode . toString ( ) . startsWith ( '1' )
278+ ) . length ;
279+ const failureCount = Object . keys ( result ) . length - successCount ;
280+ this . dialog . open ( ResponseDialogComponent , {
281+ data : {
282+ title : 'Domain Deletion Results' ,
283+ content : `Successfully deleted - ${ successCount } domain(s)<br/>Failed to delete - ${ failureCount } domain(s)<br/>${
284+ failureCount
285+ ? 'Some domains could not be deleted due to ongoing processes or server errors. '
286+ : ''
287+ } Please check the table for more information.`,
288+ } ,
289+ } ) ;
290+ this . selection . clear ( ) ;
291+ this . operationResult = result ;
292+ this . reloadData ( ) ;
293+ } ,
294+ error : ( err : HttpErrorResponse ) =>
295+ this . _snackBar . open ( err . error || err . message ) ,
296+ } ) ;
297+ }
298+
299+ deleteSelectedDomains ( ) {
300+ const dialogRef = this . dialog . open ( ReasonDialogComponent , {
301+ data : {
302+ operation : 'deleting' ,
303+ } ,
304+ } ) ;
305+
306+ dialogRef
307+ . afterClosed ( )
308+ . pipe (
309+ take ( 1 ) ,
310+ filter ( ( reason ) => ! ! reason )
311+ )
312+ . subscribe ( this . sendDeleteRequest . bind ( this ) ) ;
313+ }
139314}
0 commit comments