ocket8888 commented on code in PR #6753: URL: https://github.com/apache/trafficcontrol/pull/6753#discussion_r878326227
########## experimental/traffic-portal/src/app/shared/tp-header/tp-header.component.html: ########## @@ -11,30 +11,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<mat-toolbar color="primary"> - <img src="/assets/logo.svg" alt="Apache Traffic Control logo"/> +<mat-toolbar color="primary" *ngIf="!hidden"> + <img src="/assets/logo.svg" alt="Apache Traffic Control logo" routerLink="/core/"/> Review Comment: [Image links](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img#image_link) should be wrapped in an Anchor element that does the navigation, rather than relying on Javascript bindings. Also in that case the "alt" text should be changed to a description of the navigation being performed rather than a description of the image. ########## experimental/traffic-portal/src/app/shared/theme-manager/theme-manager.service.ts: ########## @@ -0,0 +1,154 @@ +/* +* 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 {EventEmitter, Injectable} from "@angular/core"; + +/** + * Defines a theme. If fileName is null, it is the default theme + */ +export interface Theme { + fileName?: string; + name: string; +} + +/** + * + */ +@Injectable({ + providedIn: "root" +}) +export class ThemeManagerService { + private static readonly STORAGE_KEY = "current-theme-name"; + private static readonly LINK_KEY = "themer"; + + public themeChanged = new EventEmitter<Theme>(); + + /** + * Initialize the theme service + */ + public initTheme(): void { + const themeName = ThemeManagerService.loadStoredTheme(); + if(themeName) { + this.loadTheme(themeName); + } + } + + public readonly themes: Array<Theme> = [{ + name: "Default" + }, + { + fileName: "dark-default-theme.css", + name: "Dark" + } + ]; + + /** + * Given a themes bundle name, load the theme and cache the value + * + * @param theme Theme to load + */ + public loadTheme(theme: Theme): void { + if(theme.fileName === undefined) { + this.clearTheme(); + return; + } + ThemeManagerService.getThemeLinkElement().setAttribute("href", theme.fileName); + ThemeManagerService.storeTheme(theme); + this.themeChanged.emit(theme); + } + + /** + * Revert to the default theme + */ + public clearTheme(): void { + const linkEl = ThemeManagerService.getExistingThemeLinkElement(); + if(linkEl) { + document.head.removeChild(linkEl); + ThemeManagerService.clearStoredTheme(); + this.themeChanged.emit(this.themes[0]); + } + } + + /** + * Stores theme in localStorage + * + * @param theme Theme to be stored + * @private Review Comment: I don't think you need to tag things as `@private` when they're already `private` - unless this is catching some edge case of private statics I don't know about? ########## experimental/traffic-portal/src/app/shared/tp-header/tp-header.component.html: ########## @@ -11,30 +11,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<mat-toolbar color="primary"> - <img src="/assets/logo.svg" alt="Apache Traffic Control logo"/> +<mat-toolbar color="primary" *ngIf="!hidden"> + <img src="/assets/logo.svg" alt="Apache Traffic Control logo" routerLink="/core/"/> <h1>{{title ? title : 'Welcome to Traffic Portal!'}}</h1> <div></div> <nav id="expanded"> <ul> - <li><a routerLink="/core/">Home</a></li> - <li *ngIf="hasPermission('USER:READ')"><a routerLink="/core/users">Users</a></li> - <li *ngIf="hasPermission('SERVER:READ')"><a routerLink="/core/servers">Servers</a></li> - <li><button class="menubutton" type="button" (click)="logout()">Logout</button></li> - <li><a routerLink="/core/me"><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 16 16"><g id="surface1"><path d="M 8 2 C 6.347656 2 5 3.347656 5 5 C 5 6.652344 6.347656 8 8 8 C 9.652344 8 11 6.652344 11 5 C 11 3.347656 9.652344 2 8 2 Z M 8 8 C 5.246094 8 3 10.246094 3 13 L 4 13 C 4 10.785156 5.785156 9 8 9 C 10.214844 9 12 10.785156 12 13 L 13 13 C 13 10.246094 10.753906 8 8 8 Z M 8 3 C 9.109375 3 10 3.890625 10 5 C 10 6.109375 9.109375 7 8 7 C 6.890625 7 6 6.109375 6 5 C 6 3.890625 6.890625 3 8 3 Z "></path></g></svg> - </a></li> + <li><button mat-button routerLink="/core/">Home</button></li> + <li *ngIf="hasPermission('USER:READ')"><button mat-button routerLink="/core/users">Users</button></li> + <li *ngIf="hasPermission('SERVER:READ')"><button mat-button routerLink="/core/servers">Servers</button></li> + <li> + <button mat-icon-button [matMenuTriggerFor]="expandedMenu"> + <mat-icon>manage_accounts</mat-icon> + </button> + <mat-menu #expandedMenu="matMenu"> + <button mat-menu-item routerLink="/core/me">Profile</button> + <button mat-menu-item (click)="logout()">Logout</button> + <button mat-menu-item [matMenuTriggerFor]="themeMenu">Theme</button> + </mat-menu> + </li> </ul> </nav> <nav id="collapsed"> - <input type="checkbox" hidden id="collapsed-menu"/> - <label for="collapsed-menu"></label> - <ul> - <li><a routerLink="/core/">Home</a></li> - <li *ngIf="hasPermission('USER:READ')"><a routerLink="/core/users">Users</a></li> - <li *ngIf="hasPermission('SERVER:READ')"><a routerLink="/core/servers">Servers</a></li> - <li><a href="#">Tools</a></li> - <li><a href="/core/me">Profile</a></li> - <li><button type="button" class="menubutton" (click)="logout()">Logout</button></li> - </ul> + <button mat-icon-button [matMenuTriggerFor]="collapsedMenu"><mat-icon>menu</mat-icon></button> + <mat-menu #collapsedMenu="matMenu"> + <button mat-menu-item routerLink="/core/">Home</button> + <button *ngIf="hasPermission('USER:READ')" mat-menu-item routerLink="/core/users">Users</button> + <button *ngIf="hasPermission('SERVER:READ')" mat-menu-item routerLink="/core/servers">Servers</button> + <button mat-menu-item href="/core/me">Profile</button> Review Comment: Navigation should use Anchor elements, not buttons. ########## experimental/traffic-portal/src/app/shared/generic-table/generic-table.component.scss: ########## @@ -44,9 +44,6 @@ ag-grid-angular { } button { - border-style: solid solid none solid; - border-radius: 0; - padding: 0 1em; } Review Comment: don't use empty rulesets - this whole block should just be removed, looks like. ########## experimental/traffic-portal/src/app/shared/generic-table/generic-table.component.ts: ########## @@ -435,6 +436,7 @@ export class GenericTableComponent<T> implements OnInit, OnDestroy { const visible = column.isVisible(); this.columnAPI.setColumnVisible(col, !visible); } + $event.stopPropagation(); Review Comment: This method can return before it gets to this point - should it stop propagation immediately? Or is propagation only desirable in the case that visibility setting fails? ########## experimental/traffic-portal/src/app/shared/tp-header/tp-header.component.html: ########## @@ -11,30 +11,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<mat-toolbar color="primary"> - <img src="/assets/logo.svg" alt="Apache Traffic Control logo"/> +<mat-toolbar color="primary" *ngIf="!hidden"> + <img src="/assets/logo.svg" alt="Apache Traffic Control logo" routerLink="/core/"/> <h1>{{title ? title : 'Welcome to Traffic Portal!'}}</h1> <div></div> <nav id="expanded"> <ul> - <li><a routerLink="/core/">Home</a></li> - <li *ngIf="hasPermission('USER:READ')"><a routerLink="/core/users">Users</a></li> - <li *ngIf="hasPermission('SERVER:READ')"><a routerLink="/core/servers">Servers</a></li> - <li><button class="menubutton" type="button" (click)="logout()">Logout</button></li> - <li><a routerLink="/core/me"><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 16 16"><g id="surface1"><path d="M 8 2 C 6.347656 2 5 3.347656 5 5 C 5 6.652344 6.347656 8 8 8 C 9.652344 8 11 6.652344 11 5 C 11 3.347656 9.652344 2 8 2 Z M 8 8 C 5.246094 8 3 10.246094 3 13 L 4 13 C 4 10.785156 5.785156 9 8 9 C 10.214844 9 12 10.785156 12 13 L 13 13 C 13 10.246094 10.753906 8 8 8 Z M 8 3 C 9.109375 3 10 3.890625 10 5 C 10 6.109375 9.109375 7 8 7 C 6.890625 7 6 6.109375 6 5 C 6 3.890625 6.890625 3 8 3 Z "></path></g></svg> - </a></li> + <li><button mat-button routerLink="/core/">Home</button></li> + <li *ngIf="hasPermission('USER:READ')"><button mat-button routerLink="/core/users">Users</button></li> + <li *ngIf="hasPermission('SERVER:READ')"><button mat-button routerLink="/core/servers">Servers</button></li> + <li> + <button mat-icon-button [matMenuTriggerFor]="expandedMenu"> + <mat-icon>manage_accounts</mat-icon> + </button> + <mat-menu #expandedMenu="matMenu"> + <button mat-menu-item routerLink="/core/me">Profile</button> Review Comment: navigation should use Anchor elements, not buttons ########## experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.html: ########## @@ -11,19 +11,22 @@ See the License for the specific language governing permissions and limitations under the License. --> -<tp-header [title]="'Servers'"></tp-header> <main> - <div> - <input type="search" name="fuzzControl" aria-label="Fuzzy Search Servers" autofocus inputmode="search" role="search" accesskey="/" placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()"/> - </div> - <tp-generic-table - [data]="servers | async" - [cols]="columnDefs" - [fuzzySearch]="fuzzySubject" - context="servers" - [contextMenuItems]="contextMenuItems" - (contextMenuAction)="handleContextMenu($event)"> - </tp-generic-table> + <mat-card> + <div> + <input type="search" name="fuzzControl" aria-label="Fuzzy Search Servers" autofocus inputmode="search" role="search" accesskey="/" placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()"/> + </div> + <tp-generic-table + [data]="servers | async" + [cols]="columnDefs" + [fuzzySearch]="fuzzySubject" + context="servers" + [contextMenuItems]="contextMenuItems" + (contextMenuAction)="handleContextMenu($event)"> + </tp-generic-table> + </mat-card> </main> +<!-- <tp-update-status *ngIf="changeStatusOpen" [servers]="changeStatusServers" (done)="statusUpdated($event)"></tp-update-status> +--> Review Comment: This should be removed instead of just commented out ########## experimental/traffic-portal/src/app/login/login.component.html: ########## @@ -11,15 +11,29 @@ See the License for the specific language governing permissions and limitations under the License. --> -<header> - <img src="assets/logo.svg" alt="Apache Traffic Control logo"/><h1>Traffic Portal</h1> -</header> -<form name="login" (ngSubmit)="submitLogin()" ngNativeValidate> - <label for="u">Username</label><input required autocomplete="username" autofocus type="text" [formControl]="u" name="u" id="u"/> - <label for="p">Password</label><input required autocomplete="current-password" type="password" [formControl]="p" name="p" id="p"/> - <div> - <button name="login" mat-raised-button>Login</button> - <button name="reset" mat-button color="accent" type="button" (click)="resetPassword()">Forgot Password</button> - <button name="clear" mat-raised-button color="warn" type="reset">Clear</button> - </div> -</form> +<mat-card> + <mat-card-header> + <img src="assets/logo.svg" alt="Apache Traffic Control logo"/> + <h1>Traffic Portal</h1> + </mat-card-header> + <mat-card-content> + <form name="login" (ngSubmit)="submitLogin()" ngNativeValidate> + <mat-form-field appearance="fill"> + <mat-label>Username</mat-label> + <input matInput required autofocus type="text" [formControl]="u" id="u"/> + </mat-form-field> + <mat-form-field appearance="fill"> + <mat-label>Password</mat-label> + <input matInput required [type]="hide ? 'password' : 'text'" [formControl]="p" id="p"/> + <button mat-icon-button matSuffix (click)="hide = !hide"> Review Comment: especially inside a form, a button that doesn't submit the form must have the `type` attribute set to "button". Currently, clicking on this twice submits the form. Frankly idk why it's only every other time it's clicked instead of every time. ########## experimental/traffic-portal/src/app/core/servers/update-status/update-status.component.html: ########## @@ -11,22 +11,20 @@ See the License for the specific language governing permissions and limitations under the License. --> -<dialog open> - <h2>Change status for {{serverName}}</h2> - <hr/> - <!-- method=dialog doesn't work in Firefox - even with the dialog element enabled in about:config--> - <!-- So we need to preventDefault on this event anyway --> - <form ngNativeValidate (ngSubmit)="submit($event)" method="dialog"> - <label for="status">New Status</label> - <select id="status" name="status" [formControl]="statusControl" required> - <option *ngFor="let status of statuses" [ngValue]="status" [disabled]="status.id === currentStatus">{{status.name}}</option> - </select> - <label *ngIf="isOffline" for="offlineReason">Offline Reason</label> - <input [formControl]="offlineReasonControl" id="offlineReason" name="offlineReason" *ngIf="isOffline" required maxlength="100"/> - <div> - <button mat-raised-button color="warn" type="button" (click)="cancel()">Cancel</button> - <button mat-raised-button>Submit</button> - </div> - </form> -</dialog> -<div class="backdrop"></div> +<h2 mat-dialog-title>Change status for {{serverName}}</h2> +<div mat-dialog-content> + <mat-form-field appearance="fill"> + <mat-label for="status">New Status</mat-label> + <mat-select id="status" name="status" [(ngModel)]="status" required> + <mat-option *ngFor="let status of statuses" [value]="status" [disabled]="status.id === currentStatus">{{status.name}}</mat-option> + </mat-select> + </mat-form-field> + <mat-form-field *ngIf="isOffline" appearance="fill"> + <mat-label for="offlineReason">Offline Reason</mat-label> + <input matInput [(ngModel)]="offlineReason" id="offlineReason" name="offlineReason" required maxlength="100"/> + </mat-form-field> +</div> +<div mat-dialog-actions> + <button mat-raised-button disabled="{{status === null || isOffline && offlineReason === ''}}" (click)="submit()">Submit</button> + <button mat-raised-button color="warn" type="button" (click)="cancel()">Cancel</button> +</div> Review Comment: The behavior here is now changed. Whether or not you can submit the form is no longer directly tied to the validity of any form. In fact, there is no longer a form at all, so assistive technologies can't tell the user what information is being submitted or what inputs on the page are tied to the submit button. The submit button has implicitly `type=submit` but has no linked form that it submits. Validation messages aren't displayed; I can see that the button is unavailable, but there is no information as to why in the accessibility details of the page. Compare to this simple document: ```html <!DOCTYPE html> <html> <head> <title>Title</title> </head> <body> <form> <label>Something<input type="text" name="something" required minlength="10" maxlength="100"/></label> <button>Submit</button> </form> </html> ``` You can see in the accessibility details links between the form's validity and the validity of its inputs. This tells the user not only *that* the form cannot be submitted, but also *why*. If you try to submit it anyway, a validation error is displayed to the user - no such message is possible in this mat-dialog as-is. Furthermore, if in the future we decided, for example, that offline reasons should be at least 10 characters long, or that they can't consist of only whitespace it would have been as simple as adding `minlength=10` or `pattern=".*\S.*"` to the input, respectively. Now I have to add that to the input *and* the boolean expression that disables the submit button, which gets progressively more complex until we wind up writing a custom validator for features built into HTML since version 2. tl;dr forms should be in `<form>`s ########## experimental/traffic-portal/src/app/core/currentuser/currentuser.component.html: ########## @@ -11,67 +11,71 @@ See the License for the specific language governing permissions and limitations under the License. --> -<tp-header [title]="currentUser ? currentUser.username : ''"></tp-header> +<mat-card> + <main *ngIf="currentUser && !editMode"> + <h1><span *ngIf=currentUser.fullName>{{currentUser.fullName}}, </span>{{currentUser.roleName}} user</h1> + <address> + <p *ngIf="currentUser.email || currentUser.phoneNumber"><a *ngIf=currentUser.email href="mailto:{{currentUser.email}}">{{currentUser.email}}</a><span *ngIf="currentUser.email && currentUser.phoneNumber">, </span><a *ngIf="currentUser.phoneNumber" href="tel:{{currentUser.phoneNumber}}">{{currentUser.phoneNumber}}</a></p> + <p *ngIf=currentUser.addressLine1>{{currentUser.addressLine1}}</p> + <p *ngIf=currentUser.addressLine2>{{currentUser.addressLine2}}</p> + <p *ngIf=currentUser.company>{{currentUser.company}}</p> + <p *ngIf="currentUser.city || currentUser.country || currentUser.stateOrProvince || currentUser.postalCode"><span *ngIf="currentUser.city">{{currentUser.city}}<span *ngIf="currentUser.country || currentUser.stateOrProvince || currentUser.postalCode"> </span></span><span *ngIf="currentUser.stateOrProvince">{{currentUser.stateOrProvince}}<span *ngIf="currentUser.country || currentUser.postalCode">, </span></span><span *ngIf="currentUser.country">{{currentUser.country}}<span *ngIf="currentUser.postalCode">, </span></span><span *ngIf=currentUser.postalCode>{{currentUser.postalCode}}</span></p> + </address> + <div> + <h2>User Theme</h2> + <button mat-button (click)="themeSvc.loadTheme(theme)" *ngFor="let theme of themeSvc.themes">{{theme.name}}</button> + </div> + </main> -<main *ngIf="currentUser && !editMode"> -<h1><span *ngIf=currentUser.fullName>{{currentUser.fullName}}, </span>{{currentUser.roleName}} user</h1> -<address> - <p *ngIf="currentUser.email || currentUser.phoneNumber"><a *ngIf=currentUser.email href="mailto:{{currentUser.email}}">{{currentUser.email}}</a><span *ngIf="currentUser.email && currentUser.phoneNumber">, </span><a *ngIf="currentUser.phoneNumber" href="tel:{{currentUser.phoneNumber}}">{{currentUser.phoneNumber}}</a></p> - <p *ngIf=currentUser.addressLine1>{{currentUser.addressLine1}}</p> - <p *ngIf=currentUser.addressLine2>{{currentUser.addressLine2}}</p> - <p *ngIf=currentUser.company>{{currentUser.company}}</p> - <p *ngIf="currentUser.city || currentUser.country || currentUser.stateOrProvince || currentUser.postalCode"><span *ngIf="currentUser.city">{{currentUser.city}}<span *ngIf="currentUser.country || currentUser.stateOrProvince || currentUser.postalCode"> </span></span><span *ngIf="currentUser.stateOrProvince">{{currentUser.stateOrProvince}}<span *ngIf="currentUser.country || currentUser.postalCode">, </span></span><span *ngIf="currentUser.country">{{currentUser.country}}<span *ngIf="currentUser.postalCode">, </span></span><span *ngIf=currentUser.postalCode>{{currentUser.postalCode}}</span></p> -</address> -</main> - -<form *ngIf="editMode && editUser" ngNativeValidate (submit)="submitEdit($event)"> - <mat-form-field style="grid-area: a-start;"> - <mat-label>Username</mat-label> - <input matInput [(ngModel)]="editUser.username" name="username" type="text" required/> - </mat-form-field> - <mat-form-field style="grid-area: z;"> - <mat-label>Full Name</mat-label> - <input matInput [(ngModel)]="editUser.fullName" name="fullName" type="text" required/> - </mat-form-field> - <mat-form-field style="grid-area: b;"> - <mat-label>Email</mat-label> - <input matInput [(ngModel)]="editUser.email" name="email" type="email"/> - </mat-form-field> - <mat-form-field style="grid-area: c;"> - <mat-label>Telephone</mat-label> - <input matInput [(ngModel)]="editUser.phoneNumber" name="phoneNumber" type="tel"/> - </mat-form-field> - <mat-form-field style="grid-area: d;"> - <mat-label>Address Line 1</mat-label> - <input matInput [(ngModel)]="editUser.addressLine1" name="addressLine1" type="text"/> - </mat-form-field> - <mat-form-field style="grid-area: e;"> - <mat-label>Address Line 2</mat-label> - <input matInput [(ngModel)]="editUser.addressLine2" name="addressLine2" type="text"/> - </mat-form-field> - <mat-form-field style="grid-area: f;"> - <mat-label>Company</mat-label> - <input matInput [(ngModel)]="editUser.company" name="company" type="text"/> - </mat-form-field> - <mat-form-field style="grid-area: g;"> - <mat-label>City</mat-label> - <input matInput [(ngModel)]="editUser.city" name="city" type="text"/> - </mat-form-field> - <mat-form-field style="grid-area: h;"> - <mat-label>State or Province</mat-label> - <input matInput [(ngModel)]="editUser.stateOrProvince" name="stateOrProvince" type="text"/> - </mat-form-field> - <mat-form-field style="grid-area: i;"> - <mat-label>Country</mat-label> - <input matInput [(ngModel)]="editUser.country" name="country" type="text"/> - </mat-form-field> - <mat-form-field style="grid-area: j;"> - <mat-label>Postal Code</mat-label> - <input matInput [(ngModel)]="editUser.postalCode" name="postalCode" type="text"/> - </mat-form-field> - <button mat-raised-button style="grid-area: k;">Submit</button> - <button mat-raised-button color="accent" style="grid-area: l;" type="button" (click)="updatePassword()">Update Password</button> - <button type="button" mat-raised-button color="warn" (click)="cancelEdit()" style="grid-area: m;">Cancel</button> -</form> + <form *ngIf="editMode && editUser" ngNativeValidate (submit)="submitEdit($event)"> + <mat-form-field style="grid-area: a-start;"> + <mat-label>Username</mat-label> + <input matInput [(ngModel)]="editUser.username" name="username" type="text" required/> + </mat-form-field> + <mat-form-field style="grid-area: z;"> + <mat-label>Full Name</mat-label> + <input matInput [(ngModel)]="editUser.fullName" name="fullName" type="text" required/> + </mat-form-field> + <mat-form-field style="grid-area: b;"> + <mat-label>Email</mat-label> + <input matInput [(ngModel)]="editUser.email" name="email" type="email"/> + </mat-form-field> + <mat-form-field style="grid-area: c;"> + <mat-label>Telephone</mat-label> + <input matInput [(ngModel)]="editUser.phoneNumber" name="phoneNumber" type="tel"/> + </mat-form-field> + <mat-form-field style="grid-area: d;"> + <mat-label>Address Line 1</mat-label> + <input matInput [(ngModel)]="editUser.addressLine1" name="addressLine1" type="text"/> + </mat-form-field> + <mat-form-field style="grid-area: e;"> + <mat-label>Address Line 2</mat-label> + <input matInput [(ngModel)]="editUser.addressLine2" name="addressLine2" type="text"/> + </mat-form-field> + <mat-form-field style="grid-area: f;"> + <mat-label>Company</mat-label> + <input matInput [(ngModel)]="editUser.company" name="company" type="text"/> + </mat-form-field> + <mat-form-field style="grid-area: g;"> + <mat-label>City</mat-label> + <input matInput [(ngModel)]="editUser.city" name="city" type="text"/> + </mat-form-field> + <mat-form-field style="grid-area: h;"> + <mat-label>State or Province</mat-label> + <input matInput [(ngModel)]="editUser.stateOrProvince" name="stateOrProvince" type="text"/> + </mat-form-field> + <mat-form-field style="grid-area: i;"> + <mat-label>Country</mat-label> + <input matInput [(ngModel)]="editUser.country" name="country" type="text"/> + </mat-form-field> + <mat-form-field style="grid-area: j;"> + <mat-label>Postal Code</mat-label> + <input matInput [(ngModel)]="editUser.postalCode" name="postalCode" type="text"/> + </mat-form-field> + <button mat-raised-button style="grid-area: k;">Submit</button> + <button mat-raised-button color="accent" style="grid-area: l;" type="button" (click)="updatePassword()">Update Password</button> + <button type="button" mat-raised-button color="warn" (click)="cancelEdit()" style="grid-area: m;">Cancel</button> Review Comment: should these be in the card actions? ########## experimental/traffic-portal/src/app/core/currentuser/currentuser.component.html: ########## @@ -11,67 +11,71 @@ See the License for the specific language governing permissions and limitations under the License. --> -<tp-header [title]="currentUser ? currentUser.username : ''"></tp-header> +<mat-card> + <main *ngIf="currentUser && !editMode"> + <h1><span *ngIf=currentUser.fullName>{{currentUser.fullName}}, </span>{{currentUser.roleName}} user</h1> + <address> + <p *ngIf="currentUser.email || currentUser.phoneNumber"><a *ngIf=currentUser.email href="mailto:{{currentUser.email}}">{{currentUser.email}}</a><span *ngIf="currentUser.email && currentUser.phoneNumber">, </span><a *ngIf="currentUser.phoneNumber" href="tel:{{currentUser.phoneNumber}}">{{currentUser.phoneNumber}}</a></p> + <p *ngIf=currentUser.addressLine1>{{currentUser.addressLine1}}</p> + <p *ngIf=currentUser.addressLine2>{{currentUser.addressLine2}}</p> + <p *ngIf=currentUser.company>{{currentUser.company}}</p> + <p *ngIf="currentUser.city || currentUser.country || currentUser.stateOrProvince || currentUser.postalCode"><span *ngIf="currentUser.city">{{currentUser.city}}<span *ngIf="currentUser.country || currentUser.stateOrProvince || currentUser.postalCode"> </span></span><span *ngIf="currentUser.stateOrProvince">{{currentUser.stateOrProvince}}<span *ngIf="currentUser.country || currentUser.postalCode">, </span></span><span *ngIf="currentUser.country">{{currentUser.country}}<span *ngIf="currentUser.postalCode">, </span></span><span *ngIf=currentUser.postalCode>{{currentUser.postalCode}}</span></p> + </address> + <div> + <h2>User Theme</h2> + <button mat-button (click)="themeSvc.loadTheme(theme)" *ngFor="let theme of themeSvc.themes">{{theme.name}}</button> Review Comment: The default type of buttons is `submit` - this should be a `button[type=button]` because it doesn't submit a form it just performs an interactive action. ########## experimental/traffic-portal/src/app/core/invalidation-jobs/invalidation-jobs.component.html: ########## @@ -11,32 +11,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<tp-header title="{{deliveryservice ? deliveryservice.displayName : 'Loading'}} - Content Invalidation Jobs"></tp-header> -<ul> - <li class="invalidation-job" *ngFor="let j of jobs"> - <code>{{j.assetUrl}}</code> (active from <time [dateTime]="j.startTime">{{j.startTime | date:'medium'}}</time> to <time [dateTime]="endDate(j)">{{endDate(j) | date:'medium'}})</time> - <button - mat-icon-button - type="button" - color="accent" - title="Edit this content invalidation job" - aria-label="Edit this content invalidation job" - [disabled]="j.startTime <= now" - (click)="editJob(j)" - > - <fa-icon [icon]="editIcon"></fa-icon> - </button> - <button - mat-icon-button - type="button" - color="warn" - title="Delete this content invalidation job" - aria-label="Delete this content invalidation job" - [disabled]="isInProgress(j)" - (click)="deleteJob(j.id)" - > - <fa-icon [icon]="deleteIcon"></fa-icon> - </button> - </li> -</ul> -<button mat-fab type="button" id="new" title="Create a new content invalidation job for this Delivery Service" (click)="newJob()"><fa-icon [icon]="addIcon"></fa-icon></button> +<mat-card> + <ul> + <li class="invalidation-job" *ngFor="let j of jobs"> + <div> + <div class="invalidation-info"> + <span>{{j.assetUrl}} (active from <time [dateTime]="j.startTime">{{j.startTime | date:'medium'}}</time> to <time [dateTime]="endDate(j)">{{endDate(j) | date:'medium'}})</time></span> + </div> + <div class="spacer"></div> Review Comment: This div is unnecessary; the class applies no styling and it appears to exist simply to take up space. Just set the parent element's `justify-content` to `space-between` to achieve the same look without needing useless elements. ########## experimental/traffic-portal/src/app/shared/theme-manager/theme-manager.service.ts: ########## @@ -0,0 +1,154 @@ +/* +* 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 {EventEmitter, Injectable} from "@angular/core"; + +/** + * Defines a theme. If fileName is null, it is the default theme + */ +export interface Theme { + fileName?: string; + name: string; +} + +/** + * + */ +@Injectable({ + providedIn: "root" +}) +export class ThemeManagerService { + private static readonly STORAGE_KEY = "current-theme-name"; + private static readonly LINK_KEY = "themer"; + + public themeChanged = new EventEmitter<Theme>(); + + /** + * Initialize the theme service + */ + public initTheme(): void { + const themeName = ThemeManagerService.loadStoredTheme(); + if(themeName) { + this.loadTheme(themeName); + } + } + + public readonly themes: Array<Theme> = [{ + name: "Default" + }, + { + fileName: "dark-default-theme.css", + name: "Dark" + } + ]; + + /** + * Given a themes bundle name, load the theme and cache the value + * + * @param theme Theme to load + */ + public loadTheme(theme: Theme): void { + if(theme.fileName === undefined) { + this.clearTheme(); + return; + } + ThemeManagerService.getThemeLinkElement().setAttribute("href", theme.fileName); + ThemeManagerService.storeTheme(theme); + this.themeChanged.emit(theme); + } + + /** + * Revert to the default theme + */ + public clearTheme(): void { + const linkEl = ThemeManagerService.getExistingThemeLinkElement(); + if(linkEl) { + document.head.removeChild(linkEl); Review Comment: Access to `document` should be done using [the `DOCUMENT` injection token](https://angular.io/api/common/DOCUMENT). You can see an example in the `NewDeliveryServiceComponent` source file. ########## experimental/traffic-portal/src/app/core/servers/update-status/update-status.component.ts: ########## @@ -106,31 +94,29 @@ export class UpdateStatusComponent implements OnInit { * * @param e The submission event. Review Comment: This param no longer exists (but re: my earlier comment about forms, it should) ########## experimental/traffic-portal/src/app/core/invalidation-jobs/invalidation-jobs.component.html: ########## @@ -11,32 +11,39 @@ See the License for the specific language governing permissions and limitations under the License. --> -<tp-header title="{{deliveryservice ? deliveryservice.displayName : 'Loading'}} - Content Invalidation Jobs"></tp-header> -<ul> - <li class="invalidation-job" *ngFor="let j of jobs"> - <code>{{j.assetUrl}}</code> (active from <time [dateTime]="j.startTime">{{j.startTime | date:'medium'}}</time> to <time [dateTime]="endDate(j)">{{endDate(j) | date:'medium'}})</time> - <button - mat-icon-button - type="button" - color="accent" - title="Edit this content invalidation job" - aria-label="Edit this content invalidation job" - [disabled]="j.startTime <= now" - (click)="editJob(j)" - > - <fa-icon [icon]="editIcon"></fa-icon> - </button> - <button - mat-icon-button - type="button" - color="warn" - title="Delete this content invalidation job" - aria-label="Delete this content invalidation job" - [disabled]="isInProgress(j)" - (click)="deleteJob(j.id)" - > - <fa-icon [icon]="deleteIcon"></fa-icon> - </button> - </li> -</ul> -<button mat-fab type="button" id="new" title="Create a new content invalidation job for this Delivery Service" (click)="newJob()"><fa-icon [icon]="addIcon"></fa-icon></button> +<mat-card> + <ul> Review Comment: should this be a `mat-list`? ########## experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.html: ########## @@ -11,130 +11,189 @@ See the License for the specific language governing permissions and limitations under the License. --> -<tp-header [title]="title"></tp-header> -<div class="actions-container" *ngIf="!isNew"> - <button [disabled]="!isCache()" mat-button type="button">Manage Capabilities</button> - <button [disabled]="!isCache()" mat-button type="button">Manage Delivery Services</button> - <button [disabled]="!isCache()" mat-icon-button type="button" title="queue server updates" *ngIf="!server.updPending"><fa-icon [icon]="updateIcon"></fa-icon></button> - <button [disabled]="!isCache()" mat-icon-button type="button" title="clear server updates" *ngIf="server.updPending"><fa-icon [icon]="clearUpdatesIcon"></fa-icon></button> - <button type="button" mat-icon-button title="change server status" (click)="changeStatus($event)"><fa-icon [icon]="statusChangeIcon"></fa-icon></button> -</div> -<form ngNativeValidate #serverForm="ngForm" (ngSubmit)="submit($event)"> - <label for="id" *ngIf="!isNew">ID</label> - <input type="number" [value]="server.id" readonly *ngIf="!isNew"/> - <label for="lastUpdated" *ngIf="!isNew && server.lastUpdated">Last Updated</label> - <input id="lastUpdated" *ngIf="!isNew && server.lastUpdated" [value]="server.lastUpdated.toLocaleString()" readonly/> - <label for="hostname">Host Name</label> - <input type="text" [(ngModel)]="server.hostName" id="hostname" name="hostname" required maxlength="50"/> - <label for="domainname">Domain Name</label> - <input type="text" [(ngModel)]="server.domainName" id="domainname" name="domainname" required maxlength="50"/> - <label for="cachegroup">Cache Group</label> - <select name="cachegroup" id="cachegroup" [(ngModel)]="server.cachegroupId" required> - <option *ngFor="let cg of cacheGroups" [ngValue]="cg.id">{{cg.name}}</option> - </select> - <label for="cdn">CDN</label> - <select name="cdn" id="cdn" [(ngModel)]="server.cdnId" required> - <option *ngFor="let cdn of cdns" [ngValue]="cdn.id">{{cdn.name}}</option> - </select> - <label for="status">Status</label> - <input type="text" id="status" name="status" readonly [value]="server.status" *ngIf="!isNew"/> - <select id="status" name="status" *ngIf="isNew" [(ngModel)]="server.statusId" required> - <option *ngFor="let status of statuses" [ngValue]="status.id">{{status.name}}</option> - </select> - <label for="profile">Profile</label> - <select id="profile" name="profile" [(ngModel)]="server.profileId" required> - <option *ngFor="let profile of profiles" [ngValue]="profile.id">{{profile.name}}</option> - </select> - <label for="type">Type</label> - <select id="type" name="type" [(ngModel)]="server.typeId" required> - <option *ngFor="let type of types" [ngValue]="type.id">{{type.name}}</option> - </select> - <label for="physLocation">Physical Location</label> - <select id="physLocation" name="physLocation" [(ngModel)]="server.physLocationId" required> - <option *ngFor="let physLocation of physicalLocations" [ngValue]="physLocation.id">{{physLocation.name}}</option> - </select> - <label for="httpport">HTTP Port</label> - <input id="httpport" name="httpport" type="number" [(ngModel)]="server.tcpPort" min="1" max="65535"/> - <label for="httpsport">HTTPS Port</label> - <input id="httpsport" name="httpsport" type="number" [(ngModel)]="server.httpsPort" min="1" max="65535"/> - <fieldset> - <legend (click)="hideInterfaces=!hideInterfaces">Interfaces<button name="addInterfaceBtn" class="add-button" type="button" title="add a new interface" (click)="addInterface($event)"><fa-icon [icon]="addIcon"></fa-icon></button></legend> - <fieldset [hidden]="hideInterfaces" *ngFor="let inf of server.interfaces; index as infInd"> - <legend> - <label for="{{inf.name}}-name">Name</label> - <input type="text" [(ngModel)]="inf.name" required aria-label="Interface name" id="{{inf.name}}-name" name="{{inf.name}}-name" class="interface-name-input"/> - <button class="remove-button" type="button" title="delete this interface" (click)="deleteInterface(infInd)"><fa-icon [icon]="removeIcon"></fa-icon></button> - </legend> - +<mat-card> + <mat-card-content> + <div class="actions-container" *ngIf="!isNew"> + <button [disabled]="!isCache()" mat-button type="button">Manage Capabilities</button> + <button [disabled]="!isCache()" mat-button type="button">Manage Delivery Services</button> + <button [disabled]="!isCache()" mat-icon-button type="button" title="queue server updates" *ngIf="!server.updPending"><fa-icon [icon]="updateIcon"></fa-icon></button> + <button [disabled]="!isCache()" mat-icon-button type="button" title="clear server updates" *ngIf="server.updPending"><fa-icon [icon]="clearUpdatesIcon"></fa-icon></button> + <button type="button" mat-icon-button title="change server status" (click)="changeStatus($event)"><fa-icon [icon]="statusChangeIcon"></fa-icon></button> + </div> + <form ngNativeValidate #serverForm="ngForm" (ngSubmit)="submit($event)"> + <mat-form-field> + <mat-label>Host Name</mat-label> + <input matInput [(ngModel)]="server.hostName" name="hostname" required maxlength="50"/> + </mat-form-field> + <mat-form-field> + <mat-label>Domain Name</mat-label> + <input matInput [(ngModel)]="server.domainName" name="domainname" required maxlength="50"/> + </mat-form-field> + <mat-form-field> + <mat-label>CDN</mat-label> + <mat-select name="cdn" [(ngModel)]="server.cdnId" required> + <mat-option *ngFor="let cdn of cdns" [value]="cdn.id">{{cdn.name}}</mat-option> + </mat-select> + </mat-form-field> + <mat-form-field> + <mat-label>Cache Group</mat-label> + <mat-select name="cachegroup" [(ngModel)]="server.cachegroupId" required> + <mat-option *ngFor="let cg of cacheGroups" [value]="cg.id">{{cg.name}}</mat-option> + </mat-select> + </mat-form-field> + <mat-form-field> + <mat-label>Profile</mat-label> + <mat-select name="profile" [(ngModel)]="server.profileId" required> + <mat-option *ngFor="let profile of profiles" [value]="profile.id">{{profile.name}}</mat-option> + </mat-select> + </mat-form-field> + <mat-form-field> + <mat-label>Physical Location</mat-label> + <mat-select name="physLocation" [(ngModel)]="server.physLocationId" required> + <mat-option *ngFor="let physLocation of physicalLocations" [value]="physLocation.id">{{physLocation.name}}</mat-option> + </mat-select> + </mat-form-field> + <mat-form-field> + <mat-label>Status</mat-label> + <input matInput name="status" disabled [value]="server.status" *ngIf="!isNew"/> + <mat-select name="status" *ngIf="isNew" [(ngModel)]="server.statusId" required> + <mat-option *ngFor="let status of statuses" [value]="status.id">{{status.name}}</mat-option> + </mat-select> + </mat-form-field> + <mat-form-field *ngIf="!isNew && server.offlineReason"> + <mat-label>Offline Reason</mat-label> + <input matInput name="offlineReason" disabled [value]="server.offlineReason"/> + </mat-form-field> + <mat-form-field> + <mat-label>Type</mat-label> + <mat-select name="type" [(ngModel)]="server.typeId" required> + <mat-option *ngFor="let type of types" [value]="type.id">{{type.name}}</mat-option> + </mat-select> + </mat-form-field> <div> - <label for="{{inf.name}}-monitor">Monitor this interface</label> - <input id="{{inf.name}}-monitor" name="{{inf.name}}-monitor" type="checkbox" [(ngModel)]="inf.monitor"/> - <label for="{{inf.name}}-mtu"><abbr title="Maximum Transmission Unit">MTU</abbr></label> - <input id="{{inf.name}}-mtu" name="{{inf.name}}-mtu" type="number" min="1500" max="9000" step="7500" [(ngModel)]="inf.mtu"/> - <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-mtu'], 'min') || hasPropertyError(serverForm[inf.name+'-mtu'], 'max') || hasPropertyError(serverForm[inf.name+'-mtu'], 'step')">Invalid MTU - must be 1500 or 9000</small> --> - <label for="{{inf.name}}-max-bandwidth">Maximum Bandwidth</label> - <input id="{{inf.name}}-max-bandwidth" [(ngModel)]="inf.maxBandwidth" min="0" type="number" name="{{inf.name}}-max-bandwidth"/> - <small class="input-warning" [hidden]="inf.maxBandwidth !== 0">Setting Max Bandwidth to zero will cause cache servers to always be unavailable</small> - <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-max-bandwidth'], 'min')">Cannot be negative</small> --> + <mat-form-field> + <mat-label>HTTP Port</mat-label> + <input matInput name="httpport" type="number" [(ngModel)]="server.tcpPort" min="1" max="65535"/> + </mat-form-field> + <mat-form-field> + <mat-label>HTTPS Port</mat-label> + <input matInput name="httpsport" type="number" [(ngModel)]="server.httpsPort" min="1" max="65535"/> + </mat-form-field> </div> + <mat-form-field> + <mat-label>Rack</mat-label> + <input matInput name="rack" [(ngModel)]="server.rack"/> + </mat-form-field> + <mat-form-field *ngIf="!isNew"> + <mat-label>ID</mat-label> + <input name="serverId" matInput type="number" [value]="server.id" disabled/> + </mat-form-field> + <mat-form-field *ngIf="!isNew && server.lastUpdated"> + <mat-label>Last Updated</mat-label> + <input matInput [value]="server.lastUpdated.toLocaleString()" disabled/> + </mat-form-field> + <mat-form-field *ngIf="!isNew"> + <mat-label>Hash ID</mat-label> + <input matInput disabled name="xmppId" [value]="server.xmppId"/> + </mat-form-field> + <mat-form-field *ngIf="!isNew && server.statusLastUpdated"> + <mat-label>Status Last Updated</mat-label> + <input matInput name="statusLastUpdated" disabled [value]="server.statusLastUpdated.toLocaleString()"/> + </mat-form-field> + <fieldset> + <legend (click)="hideInterfaces=!hideInterfaces">Interfaces<button name="addInterfaceBtn" class="add-button" type="button" title="add a new interface" (click)="addInterface($event)"><fa-icon [icon]="addIcon"></fa-icon></button></legend> + <fieldset [hidden]="hideInterfaces" *ngFor="let inf of server.interfaces; index as infInd"> + <legend> + <label for="{{inf.name}}-name">Name</label> + <input [(ngModel)]="inf.name" required aria-label="Interface name" id="{{inf.name}}-name" name="{{inf.name}}-name" class="interface-name-input"/> + <button class="remove-button" type="button" title="delete this interface" (click)="deleteInterface(infInd)"><fa-icon [icon]="removeIcon"></fa-icon></button> + </legend> + <div> + <mat-checkbox [labelPosition]="'before'" id="{{inf.name}}-monitor" name="{{inf.name}}-monitor" [(ngModel)]="inf.monitor">Monitor this interface</mat-checkbox> + <mat-form-field> + <mat-label><abbr title="Maximum Transmission Unit">MTU</abbr></mat-label> + <input matInput id="{{inf.name}}-mtu" name="{{inf.name}}-mtu" type="number" min="1500" max="9000" step="7500" [(ngModel)]="inf.mtu"/> + </mat-form-field> + <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-mtu'], 'min') || hasPropertyError(serverForm[inf.name+'-mtu'], 'max') || hasPropertyError(serverForm[inf.name+'-mtu'], 'step')">Invalid MTU - must be 1500 or 9000</small> --> + <mat-form-field> + <mat-label>Maximum Bandwidth</mat-label> + <input matInput id="{{inf.name}}-max-bandwidth" [(ngModel)]="inf.maxBandwidth" min="0" type="number" name="{{inf.name}}-max-bandwidth"/> + <mat-error class="input-warning" *ngIf="inf.maxBandwidth !== 0">Setting Max Bandwidth to zero will cause cache servers to always be unavailable</mat-error> + </mat-form-field> + <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-max-bandwidth'], 'min')">Cannot be negative</small> --> + </div> + + <fieldset> + <legend>IP Addresses<button name="addIPBtn" type="button" title="add new IP Address" class="add-button" (click)="addIP(inf)"><fa-icon [icon]="addIcon"></fa-icon></button></legend> + <div *ngFor="let ip of inf.ipAddresses; index as ipInd" [ngClass]="{'bordered-item': ipInd !== 0}"> + <mat-checkbox [labelPosition]="'before'" name="{{inf.name}}-{{ip.address}}-serviceAddress" id="{{inf.name}}-{{ip.address}}-serviceAddress" class="service-addr-cb" [(ngModel)]="ip.serviceAddress">Is a Service Address</mat-checkbox> + <mat-form-field> + <mat-label>Address</mat-label> + <input matInput id="{{inf.name}}-{{ip.address}}" name="{{inf.name}}-{{ip.address}}" class="ip-input" [(ngModel)]="ip.address" [pattern]="validIPPattern" required placeholder="192.0.2.0/27" /> + </mat-form-field> + <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-'+ip.address], 'pattern')">Invalid address</small> + <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-'+ip.address], 'required')">Required</small> + <small class="input-warning" ng-show="isLargeCIDR(ip.address)">Large CIDR detected. IPv4 with CIDR < 27 or IPv6 with CIDR < 64 can be problematic.</small> --> + <mat-form-field> + <mat-label>Gateway</mat-label> + <input matInput id="{{inf.name}}-{{ip.address}}-gateway" name="{{inf.name}}-{{ip.address}}-gateway" [(ngModel)]="ip.gateway" [pattern]="validGatewayPattern"/> + </mat-form-field> + <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-'+ip.address+'-gateway'], 'pattern')">Invalid gateway</small> --> + <button type="button" title="delete this IP address" class="remove-button" style="justify-self: start;" (click)="deleteIP(inf, ipInd)"><fa-icon [icon]="removeIcon"></fa-icon></button> + </div> + </fieldset> + </fieldset> + </fieldset> <fieldset> - <legend>IP Addresses<button name="addIPBtn" type="button" title="add new IP Address" class="add-button" (click)="addIP(inf)"><fa-icon [icon]="addIcon"></fa-icon></button></legend> - <ul> - <li *ngFor="let ip of inf.ipAddresses; index as ipInd" [ngClass]="{'bordered-item': ipInd !== 0}"> - <label for="{{inf.name}}-{{ip.address}}">Address</label> - <input id="{{inf.name}}-{{ip.address}}" name="{{inf.name}}-{{ip.address}}" class="ip-input" type="text" [(ngModel)]="ip.address" [pattern]="validIPPattern" required placeholder="192.0.2.0/27" /> - <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-'+ip.address], 'pattern')">Invalid address</small> - <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-'+ip.address], 'required')">Required</small> - <small class="input-warning" ng-show="isLargeCIDR(ip.address)">Large CIDR detected. IPv4 with CIDR < 27 or IPv6 with CIDR < 64 can be problematic.</small> --> - <label for="{{inf.name}}-{{ip.address}}-gateway">Gateway</label> - <input type="text" id="{{inf.name}}-{{ip.address}}-gateway" name="{{inf.name}}-{{ip.address}}-gateway" [(ngModel)]="ip.gateway" [pattern]="validGatewayPattern"/> - <!-- <small class="input-error" ng-show="hasPropertyError(serverForm[inf.name+'-'+ip.address+'-gateway'], 'pattern')">Invalid gateway</small> --> - <label for="{{inf.name}}-{{ip.address}}-serviceAddress">Is a Service Address</label> - <input type="checkbox" name="{{inf.name}}-{{ip.address}}-serviceAddress" id="{{inf.name}}-{{ip.address}}-serviceAddress" class="service-addr-cb" [(ngModel)]="ip.serviceAddress"/> - <button type="button" title="delete this IP address" class="remove-button" style="justify-self: start;" (click)="deleteIP(inf, ipInd)"><fa-icon [icon]="removeIcon"></fa-icon></button> - </li> - </ul> + <legend (click)="hideILO=!hideILO"><abbr title="Integrated Lights-Out Management">ILO</abbr> Details</legend> + <div [hidden]="hideILO"> + <mat-form-field> + <mat-label><abbr title="Integrated Lights-Out Management">ILO</abbr> IP Address</mat-label> + <input matInput name="iloIP" [(ngModel)]="server.iloIpAddress"/> + </mat-form-field> + <mat-form-field> + <mat-label><abbr title="Integrated Lights-Out Management">ILO</abbr> Gateway IP Address</mat-label> + <input matInput name="iloGateway" [(ngModel)]="server.iloIpGateway"/> + </mat-form-field> + <mat-form-field> + <mat-label><abbr title="Integrated Lights-Out Management">ILO</abbr> IP Netmask</mat-label> + <input matInput name="iloNetmask" [(ngModel)]="server.iloIpNetmask"/> + </mat-form-field> + <mat-form-field> + <mat-label><abbr title="Integrated Lights-Out Management">ILO</abbr> Username</mat-label> + <input matInput name="iloUsername" [(ngModel)]="server.iloUsername"/> + </mat-form-field> + <mat-form-field> + <mat-label><abbr title="Integrated Lights-Out Management">ILO</abbr> Password</mat-label> + <input matInput name="iloPassword" [(ngModel)]="server.iloPassword"/> + </mat-form-field> + </div> </fieldset> - </fieldset> - </fieldset> - <fieldset> - <legend (click)="hideILO=!hideILO"><abbr title="Integrated Lights-Out Management">ILO</abbr> Details</legend> - <div [hidden]="hideILO"> - <label for="iloIP"><abbr title="Integrated Lights-Out Management">ILO</abbr> IP Address</label> - <input type="text" id="iloIP" name="iloIP" [(ngModel)]="server.iloIpAddress"/> - <label for="iloGateway"><abbr title="Integrated Lights-Out Management">ILO</abbr> Gateway IP Address</label> - <input type="text" id="iloGateway" name="iloGateway" [(ngModel)]="server.iloIpGateway"/> - <label for="iloNetmask"><abbr title="Integrated Lights-Out Management">ILO</abbr> IP Netmask</label> - <input type="text" id="iloNetmask" name="iloNetmask" [(ngModel)]="server.iloIpNetmask"/> - <label for="iloUsername"><abbr title="Integrated Lights-Out Management">ILO</abbr> Username</label> - <input type="text" id="iloUsername" name="iloUsername" [(ngModel)]="server.iloUsername"/> - <label for="iloPassword"><abbr title="Integrated Lights-Out Management">ILO</abbr> Password</label> - <input type="text" id="iloPassword" name="iloPassword" [(ngModel)]="server.iloPassword"/> - </div> - </fieldset> - <fieldset> - <legend (click)="hideManagement=!hideManagement">Management Interface Details</legend> - <div [hidden]="hideManagement"> - <label for="mgmtIP">Management IP Address</label> - <input type="text" id="mgmtIP" name="mgmtIP" [(ngModel)]="server.mgmtIpAddress"/> - <label for="mgmtIpGateway">Management Gateway IP Address</label> - <input type="text" id="mgmtIpGateway" name="mgmtIpGateway" [(ngModel)]="server.mgmtIpGateway"/> - <label for="mgmtIpNetmask">Management IP Netmask</label> - <input type="text" id="mgmtIpNetmask" name="mgmtIpNetmask" [(ngModel)]="server.mgmtIpNetmask"/> - </div> - </fieldset> - <label *ngIf="!isNew" for="offlineReason">Offline Reason</label> - <input *ngIf="!isNew" id="offlineReason" name="offlineReason" type="text" readonly [value]="server.offlineReason"/> - <label for="rack">Rack</label> - <input type="text" id="rack" name="rack" [(ngModel)]="server.rack"/> - <label *ngIf="!isNew && server.statusLastUpdated" for="statusLastUpdated">Status Last Updated</label> - <input *ngIf="!isNew && server.statusLastUpdated" type="text" id="statusLastUpdated" name="statusLastUpdated" readonly [value]="server.statusLastUpdated.toLocaleString()"/> - <label *ngIf="!isNew" for="xmppId">Hash ID</label> - <input readonly *ngIf="!isNew" id="xmppId" name="xmppId" [value]="server.xmppId"/> - <div class="buttons"> - <button mat-raised-button>Submit</button> - </div> -</form> + <fieldset> + <legend (click)="hideManagement=!hideManagement">Management Interface Details</legend> + <div [hidden]="hideManagement"> + <mat-form-field> + <mat-label>Management IP Address</mat-label> + <input matInput name="mgmtIP" [(ngModel)]="server.mgmtIpAddress"/> + </mat-form-field> + <mat-form-field> + <mat-label>Management Gateway IP Address</mat-label> + <input matInput name="mgmtIpGateway" [(ngModel)]="server.mgmtIpGateway"/> + </mat-form-field> + <mat-form-field> + <mat-label>Management IP Netmask</mat-label> + <input matInput name="mgmtIpNetmask" [(ngModel)]="server.mgmtIpNetmask"/> + </mat-form-field> + </div> + </fieldset> + <div class="buttons"> + <button mat-raised-button>Submit</button> + </div> + </form> + </mat-card-content> +</mat-card> +<!-- <tp-update-status [servers]="[server]" (done)="doneUpdatingStatus($event)" *ngIf="changeStatusDialogOpen"></tp-update-status> +--> Review Comment: This should be removed instead of just commented out -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
