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">&nbsp;</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">&nbsp;</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">&nbsp;</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 &lt; 27 or IPv6 with CIDR &lt; 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 &lt; 27 
or IPv6 with CIDR &lt; 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]

Reply via email to