You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by rs...@apache.org on 2023/05/03 16:07:56 UTC

[trafficcontrol] branch master updated: Template linting for TPv2 (#7462)

This is an automated email from the ASF dual-hosted git repository.

rshah pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new b96f7d34e7 Template linting for TPv2 (#7462)
b96f7d34e7 is described below

commit b96f7d34e7b7c5d9c056a6b184a3ae491417a829
Author: ocket8888 <oc...@apache.org>
AuthorDate: Wed May 3 10:07:49 2023 -0600

    Template linting for TPv2 (#7462)
    
    * Add template linting rules
    
    * increase allowable complexity, change to warning, eliminate no-call-expression
    
    * fix missing button types
    
    also, some links incorrectly used `button` tags instead of `a` tags;
    fixed those too
    
    * Remove duplicate attribute
    
    * fixed unbound labels
    
    * rework complex conditional in template
    
    * Fix login button has incorrect type
    
    * Remove extraneous space
    
    * Fix e2e tests looking for button id that no longer exists
    
    * Fix button title
---
 experimental/traffic-portal/.eslintrc.json         | 51 +++++++++++++++++++++-
 .../tests/deliveryServices/ds.invalidate.spec.ts   |  4 +-
 .../asns/detail/asn-detail.component.html          |  2 +-
 .../cache-group-table.component.html               |  2 +-
 .../core/currentuser/currentuser.component.html    |  4 +-
 .../core/currentuser/currentuser.component.spec.ts | 44 +++++++++++++++++++
 .../app/core/currentuser/currentuser.component.ts  | 15 +++++++
 .../update-password-dialog.component.html          |  2 +-
 .../deliveryservice/deliveryservice.component.html |  2 +-
 .../new-invalidation-job-dialog.component.html     |  2 +-
 .../new-delivery-service.component.html            | 24 +++++-----
 .../phys-loc/table/phys-loc-table.component.html   |  2 +-
 .../server-details/server-details.component.html   |  2 +-
 .../users/user-details/user-details.component.html |  2 +-
 .../user-registration-dialog.component.html        |  2 +-
 .../src/app/core/users/users.component.html        |  2 +-
 .../src/app/login/login.component.html             |  2 +-
 .../reset-password-dialog.component.html           |  2 +-
 .../collection-choice-dialog.component.html        |  6 +--
 .../decision-dialog/decision-dialog.component.html |  4 +-
 .../dialogs/text-dialog/text-dialog.component.html |  2 +-
 .../generic-table/generic-table.component.html     |  4 +-
 .../navigation/tp-header/tp-header.component.html  | 18 ++++----
 23 files changed, 154 insertions(+), 46 deletions(-)

diff --git a/experimental/traffic-portal/.eslintrc.json b/experimental/traffic-portal/.eslintrc.json
index 4311f5802f..aec9f06b96 100644
--- a/experimental/traffic-portal/.eslintrc.json
+++ b/experimental/traffic-portal/.eslintrc.json
@@ -372,7 +372,56 @@
 			"extends": [
 				"plugin:@angular-eslint/template/recommended"
 			],
-			"rules": {}
+			// Rules that are commented out in this block are not available
+			// until Angular 15, and should be un-commented once that upgrade is
+			// done.
+			"rules": {
+				"@angular-eslint/template/accessibility-alt-text": "error",
+				"@angular-eslint/template/accessibility-elements-content": "error",
+				// "@angular-eslint/template/accessibility-interactive-supports-focus": "error",
+				"@angular-eslint/template/accessibility-label-has-associated-control": "error",
+				// "@angular-eslint/template/accessibility-role-has-required-aria": "error",
+				"@angular-eslint/template/accessibility-table-scope": "error",
+				"@angular-eslint/template/accessibility-valid-aria": "error",
+				// I want to see the results of this before committing to it,
+				// which is difficult to do until I can actually use it.
+				// "@angular-eslint/template/attributes-order": "warn",
+				"@angular-eslint/template/banana-in-box": "error",
+				"@angular-eslint/template/button-has-type": "error",
+				// Currently a warning because of suspected false positives.
+				"@angular-eslint/template/click-events-have-key-events": "warn",
+				"@angular-eslint/template/conditional-complexity": [
+					"error",
+					{
+						"maxComplexity": 2
+					}
+				],
+				// Warning because it's difficult to know exactly when a
+				// refactor is strictly necessary and also because fixing this
+				// would be a huge changeset.
+				"@angular-eslint/template/cyclomatic-complexity": [
+					"warn",
+					{
+						"maxComplexity": 8
+					}
+				],
+				"@angular-eslint/template/eqeqeq": "error",
+				"@angular-eslint/template/mouse-events-have-key-events": "error",
+				"@angular-eslint/template/no-any": "error",
+				// Currently a warning because of rampant use, and fixing such
+				// usages would be out-of-scope for the PR adding this linting,
+				// IMO.
+				"@angular-eslint/template/no-autofocus": "warn",
+				"@angular-eslint/template/no-distracting-elements": "error",
+				"@angular-eslint/template/no-duplicate-attributes": "error",
+				// "@angular-eslint/template/no-inline-styles": "warn",
+				// "@angular-eslint/template/no-interpolation-in-attributes": "error",
+				"@angular-eslint/template/no-negated-async": "error",
+				"@angular-eslint/template/no-positive-tabindex": "error",
+				// Warning because not always necessary, and appears to have no
+				// options to control when it is required.
+				"@angular-eslint/template/use-track-by-function": "warn"
+			}
 		}
 	]
 }
diff --git a/experimental/traffic-portal/nightwatch/tests/deliveryServices/ds.invalidate.spec.ts b/experimental/traffic-portal/nightwatch/tests/deliveryServices/ds.invalidate.spec.ts
index c7d8ff52e9..3a755dedc4 100644
--- a/experimental/traffic-portal/nightwatch/tests/deliveryServices/ds.invalidate.spec.ts
+++ b/experimental/traffic-portal/nightwatch/tests/deliveryServices/ds.invalidate.spec.ts
@@ -38,7 +38,7 @@ describe("DS Invalidation Jobs Spec", () => {
 		await browser.waitForElementVisible("tp-new-invalidation-job-dialog")
 			.assert.valueEquals("input[name='startDate']", startDate.toLocaleDateString())
 			.setValue("input[name='regexp']", "/invalidateMe")
-			.click("button#submit");
+			.click("button[type=submit]");
 		await common
 			.assert.textContains("@snackbarEle", "created")
 			.click("simple-snack-bar button");
@@ -51,7 +51,7 @@ describe("DS Invalidation Jobs Spec", () => {
 			.assert.valueEquals("input[name='startDate']", startDate.toLocaleDateString())
 			.assert.valueEquals("input[name='regexp']", "invalidateMe")
 			.setValue("input[name='regexp']", "/invalidateMe2")
-			.click("button#submit");
+			.click("button[type=submit]");
 		await common
 			.assert.textContains("@snackbarEle", "created")
 			.click("simple-snack-bar button");
diff --git a/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.html b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.html
index 64565c879b..e575bff0a4 100644
--- a/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.html
+++ b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.html
@@ -26,7 +26,7 @@ limitations under the License.
 			</mat-form-field>
 			<mat-form-field>
 				<mat-label>Cache Group</mat-label>
-				<mat-select name="cachegroup"[(ngModel)]="asn.cachegroupId" required>
+				<mat-select name="cachegroup" [(ngModel)]="asn.cachegroupId" required>
 					<mat-option *ngFor="let cachegroup of cachegroups" [value]="cachegroup.id">{{cachegroup.name}}</mat-option>
 				</mat-select>
 			</mat-form-field>
diff --git a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.html b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.html
index eff44869e3..1444862745 100644
--- a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.html
+++ b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.html
@@ -25,4 +25,4 @@ limitations under the License.
 	</tp-generic-table>
 </mat-card>
 
-<button class="page-fab" mat-fab title="Create a new Cache Group" *ngIf="auth.hasPermission('CACHE-GROUP:CREATE')" routerLink="new"><mat-icon>add</mat-icon></button>
+<a class="page-fab" mat-fab title="Create a new Cache Group" *ngIf="auth.hasPermission('CACHE-GROUP:CREATE')" routerLink="new"><mat-icon>add</mat-icon></a>
diff --git a/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.html b/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.html
index 3b6157e820..a10e4f98f4 100644
--- a/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.html
+++ b/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.html
@@ -19,7 +19,7 @@ limitations under the License.
 			<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=" [...]
+			<p *ngIf="hasBottomAddress()"><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>{{c [...]
 		</address>
 		<div>
 			<h2>User Theme</h2>
@@ -75,7 +75,7 @@ limitations under the License.
 			</mat-form-field>
 		</mat-card-content>
 		<mat-card-actions>
-			<button mat-raised-button style="grid-area: k;">Submit</button>
+			<button mat-raised-button style="grid-area: k;" type="submit">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>
 		</mat-card-actions>
diff --git a/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.spec.ts b/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.spec.ts
index 851d6d3476..c29f9056b0 100644
--- a/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.spec.ts
+++ b/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.spec.ts
@@ -167,4 +167,48 @@ describe("CurrentuserComponent", () => {
 
 		expect(updateSpy).toHaveBeenCalledTimes(2);
 	}));
+
+	it("determines whether a user has a 'bottom-level' address", () => {
+		component.currentUser = null;
+		expect(component.hasBottomAddress()).toBeFalse();
+		component.currentUser = {
+			addressLine1: null,
+			addressLine2: null,
+			changeLogCount: 0,
+			city: null,
+			company: null,
+			country: null,
+			email: "em@i.l",
+			fullName: "",
+			gid: null,
+			id: 1,
+			lastAuthenticated: null,
+			lastUpdated: new Date(),
+			localUser: false,
+			newUser: false,
+			phoneNumber: null,
+			postalCode: null,
+			publicSshKey: null,
+			registrationSent: null,
+			role: "",
+			stateOrProvince: null,
+			tenant: "",
+			tenantId: 1,
+			ucdn: "",
+			uid: null,
+			username: "",
+		};
+		expect(component.hasBottomAddress()).toBeFalse();
+		component.currentUser.city = "Townsville";
+		expect(component.hasBottomAddress()).toBeTrue();
+		component.currentUser.city = null;
+		component.currentUser.country = "Nationstan";
+		expect(component.hasBottomAddress()).toBeTrue();
+		component.currentUser.country = null;
+		component.currentUser.stateOrProvince = "Provincia";
+		expect(component.hasBottomAddress()).toBeTrue();
+		component.currentUser.stateOrProvince = null;
+		component.currentUser.postalCode = "00000";
+		expect(component.hasBottomAddress()).toBeTrue();
+	});
 });
diff --git a/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.ts b/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.ts
index 1e69ddfc9c..8362d33044 100644
--- a/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.ts
+++ b/experimental/traffic-portal/src/app/core/currentuser/currentuser.component.ts
@@ -153,4 +153,19 @@ export class CurrentuserComponent implements OnInit {
 			this.cancelEdit();
 		}
 	}
+
+	/**
+	 * Checks if the form's user has a "bottom-level" address, meaning any
+	 * combination of state/province, postal code, city, and/or country.
+	 *
+	 * @returns `true` if the user has a "bottom-level" address, `false`
+	 * otherwise.
+	 */
+	public hasBottomAddress(): boolean {
+		if (!this.currentUser) {
+			return false;
+		}
+		const {city, country, stateOrProvince, postalCode} = this.currentUser;
+		return !!(city || country || stateOrProvince || postalCode);
+	}
 }
diff --git a/experimental/traffic-portal/src/app/core/currentuser/update-password-dialog/update-password-dialog.component.html b/experimental/traffic-portal/src/app/core/currentuser/update-password-dialog/update-password-dialog.component.html
index 9636f702b1..cc1ea16d22 100644
--- a/experimental/traffic-portal/src/app/core/currentuser/update-password-dialog/update-password-dialog.component.html
+++ b/experimental/traffic-portal/src/app/core/currentuser/update-password-dialog/update-password-dialog.component.html
@@ -24,7 +24,7 @@ limitations under the License.
 		</mat-form-field>
 	</div>
 	<div mat-dialog-actions>
-		<button mat-stroked-button>Submit</button>
+		<button type="submit" mat-stroked-button>Submit</button>
 		<button type="button" mat-stroked-button color="warn" (click)="cancel()">Cancel</button>
 	</div>
 </form>
diff --git a/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html b/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
index 8570f35aca..832cc5522a 100644
--- a/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
+++ b/experimental/traffic-portal/src/app/core/deliveryservice/deliveryservice.component.html
@@ -35,7 +35,7 @@ limitations under the License.
 				</mat-form-field>
 			</div>
 			<div class="actions">
-				<button name="timespanRefresh" mat-raised-button>Refresh</button>
+				<button type="submit" name="timespanRefresh" mat-raised-button>Refresh</button>
 				<mat-icon *ngIf="steeringTargetsFor.size > 0" [matTooltip]="steeringTargetDisplay()" >assistant_direction</mat-icon>
 			</div>
 		</form>
diff --git a/experimental/traffic-portal/src/app/core/deliveryservice/invalidation-jobs/new-invalidation-job-dialog/new-invalidation-job-dialog.component.html b/experimental/traffic-portal/src/app/core/deliveryservice/invalidation-jobs/new-invalidation-job-dialog/new-invalidation-job-dialog.component.html
index 09e38bc85f..98b8895aed 100644
--- a/experimental/traffic-portal/src/app/core/deliveryservice/invalidation-jobs/new-invalidation-job-dialog/new-invalidation-job-dialog.component.html
+++ b/experimental/traffic-portal/src/app/core/deliveryservice/invalidation-jobs/new-invalidation-job-dialog/new-invalidation-job-dialog.component.html
@@ -36,7 +36,7 @@ limitations under the License.
 		</mat-form-field>
 	</div>
 	<footer mat-dialog-actions>
-		<button mat-raised-button id="submit">Submit</button>
+		<button mat-raised-button type="submit">Submit</button>
 		<button mat-raised-button color="warn" type="button" (click)="cancel()">Cancel</button>
 	</footer>
 </form>
diff --git a/experimental/traffic-portal/src/app/core/deliveryservice/new-delivery-service/new-delivery-service.component.html b/experimental/traffic-portal/src/app/core/deliveryservice/new-delivery-service/new-delivery-service.component.html
index 697b1a6d84..0bf814f6bf 100644
--- a/experimental/traffic-portal/src/app/core/deliveryservice/new-delivery-service/new-delivery-service.component.html
+++ b/experimental/traffic-portal/src/app/core/deliveryservice/new-delivery-service/new-delivery-service.component.html
@@ -16,14 +16,14 @@ limitations under the License.
 		<form #form1="ngForm" ngNativeValidate ngDefaultControl (ngSubmit)="setOriginURL()">
 			<h2>Step 1 - Origin Server</h2>
 			<div class="form-content">
-				<label for="origin">I want to create a Delivery Service for:</label>
+				<label for="origin">I want to create a Delivery Service for</label>
 				<input [formControl]="originURL" type="url" id="origin" name="origin" placeholder="this URL" autofocus title="Must be a URL (should start with `http://` or `https://`)" pattern="(https?|HTTPS?)://[a-zA-Z][a-zA-z0-9\-]*(\.[a-zA-Z][a-zA-z0-9\-]*)*(/[\w\.]+)*/?" required/>
 				<label for="active-immediately">This Delivery Service should become active immediately</label>
 				<input [formControl]="activeImmediately" id="active-immediately" type="checkbox" name="active-immediately" title="This Delivery Service should become active immediately"/>
 			</div>
 			<div class="buttons-holder">
 				<button mat-raised-button color="warn" type="reset">Clear</button>
-				<button mat-raised-button>Next</button>
+				<button type="submit" mat-raised-button>Next</button>
 			</div>
 		</form>
 	</mat-step>
@@ -31,22 +31,22 @@ limitations under the License.
 		<form #form2="ngForm" ngNativeValidate ngDefaultControl (ngSubmit)="setMetaInformation()">
 			<h2>Step 2 - Meta Information</h2>
 			<div class="form-content">
-				<label for="displayName">This Delivery Service's name will be:</label>
+				<label for="displayName">This Delivery Service's name will be</label>
 				<input type="text" autofocus title="This will be the name of the Delivery Service as it appears on the 'Home' screen" name="displayName" id="displayName" (change)="updateDisplayName()" [formControl]="displayName" placeholder="{{deliveryService.displayName}}" required>
-				<label for="longDesc">Please briefly describe this Delivery Service's purpose and function here:</label>
+				<label for="longDesc">Please briefly describe this Delivery Service's purpose and function</label>
 				<textarea id="longDesc" name="longDesc" title="No character limit - be as verbose as you like." required placeholder="e.g. This Delivery Service is for my website's image assets." [formControl]="description" rows="3"></textarea>
 			</div>
 			<div>
 				<input type="checkbox" class="sub-form" id="info-URL-sub-form" hidden/>
 				<label for="info-URL-sub-form" class="sub-form">Add Informational URL</label>
 				<div class="form-content">
-					<label for="infoURL" id="info-URL-label">For more information, people can look at this URL:</label>
+					<label for="infoURL" id="info-URL-label">For more information, people can look at this URL</label>
 					<input [formControl]="infoURL" type="url" name="infoURL" id="infoURL" placeholder="e.g. https://company.jira.com/ticket/9001"/>
 				</div>
 			</div>
 			<div class="buttons-holder">
 				<button mat-raised-button type="reset" matStepperPrevious>Previous</button>
-				<button mat-raised-button>Next</button>
+				<button mat-raised-button type="submit">Next</button>
 			</div>
 		</form>
 	</mat-step>
@@ -54,7 +54,7 @@ limitations under the License.
 		<form ngNativeValidate ngDefaultControl (ngSubmit)="setInfrastructureInformation()">
 			<h2>Step 3 - Infrastructure</h2>
 			<div class="form-content">
-				<label for="cdnObject">This Delivery Service will be a part of this CDN:</label>
+				<label for="cdnObject">This Delivery Service will be a part of this CDN</label>
 				<select [formControl]="cdnObject" name="cdnObject" id="cdnObject" required>
 					<option *ngFor="let cdn of cdns" [selected]="cdn.id === deliveryService.cdnId" [ngValue]="cdn">{{cdn.name}}</option>
 				</select>
@@ -67,22 +67,22 @@ limitations under the License.
 					<select [formControl]="dsType" name="routingType" id="routingType">
 						<option *ngFor="let t of dsTypes" [selected]="t.id === deliveryService.typeId" [ngValue]="t">{{t.description}}</option>
 					</select>
-					<label id="protocol">How to handle request protocols</label>
-					<mat-radio-group aria-labelledby="protocol" [formControl]="protocol" name="protocol">
-						<mat-radio-button *ngFor="let p of protocols" [value]="p" name="protocol">{{protocolToString(p)}}</mat-radio-button>
+					<label for="protocol">How to handle request protocols</label>
+					<mat-radio-group aria-labelledby="protocol" [formControl]="protocol" id="protocol" name="protocol">
+						<mat-radio-button *ngFor="let p of protocols" [value]="p">{{protocolToString(p)}}</mat-radio-button>
 					</mat-radio-group>
 					<label for="disableIPv6">Disable IPv6 Routing</label>
 					<input type="checkbox" id="disableIPv6" name="disableIPv6" [formControl]="disableIPv6"/>
 					<fieldset name="bypass" id="bypass" *ngIf="bypassable(deliveryService)">
 						<legend>Bypass Options</legend>
-						<label>When the Delivery Service traffic becomes too heavy, clients can be redirected here:</label>
+						<label for="bypass-loc">When the Delivery Service traffic becomes too heavy, clients can be redirected here</label>
 						<input type="text" name="bypass-loc" id="bypass-loc" [formControl]="bypassLoc" title="can be either an IP (v4 or v6) address or a Fully Qualified Domain Name" pattern="(^[A-z]([A-z\d\-]*[A-z\d])?(\.[A-z]([A-z\d\-]*[A-z\d])?)*$)|(^(1\d\d|2[0-4]\d|25[0-5]|\d\d?)(\.(1\d\d|2[0-4]\d|25[0-5]|\d\d?)){3}$)|(^[a-fA-F\d]{1,4}(::?([a-fA-F\d]){1,4})*$)" /><!-- Yeah, I know that's pretty gnarly, and it doesn't exactly validate IPv6 addresses but it's a good litmus test -->
 					</fieldset>
 				</div>
 			</div>
 			<div class="buttons-holder">
 				<button mat-raised-button type="reset" matStepperPrevious>Previous</button>
-				<button mat-raised-button>Submit</button>
+				<button mat-raised-button type="submit">Submit</button>
 			</div>
 		</form>
 	</mat-step>
diff --git a/experimental/traffic-portal/src/app/core/servers/phys-loc/table/phys-loc-table.component.html b/experimental/traffic-portal/src/app/core/servers/phys-loc/table/phys-loc-table.component.html
index 82fb885cae..c2600b0bee 100644
--- a/experimental/traffic-portal/src/app/core/servers/phys-loc/table/phys-loc-table.component.html
+++ b/experimental/traffic-portal/src/app/core/servers/phys-loc/table/phys-loc-table.component.html
@@ -26,4 +26,4 @@ limitations under the License.
 	</tp-generic-table>
 </mat-card>
 
-<button class="page-fab" mat-fab title="Create a new Division" *ngIf="auth.hasPermission('PHYSICAL-LOCATION:CREATE')" routerLink="new"><mat-icon>add</mat-icon></button>
+<a class="page-fab" mat-fab title="Create a new Physical Location" *ngIf="auth.hasPermission('PHYSICAL-LOCATION:CREATE')" routerLink="new"><mat-icon>add</mat-icon></a>
diff --git a/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.html b/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.html
index 6528a7f7de..db0d273db2 100644
--- a/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.html
+++ b/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.html
@@ -189,7 +189,7 @@ limitations under the License.
 				</div>
 			</fieldset>
 			<div class="buttons">
-				<button mat-raised-button>Submit</button>
+				<button mat-raised-button type="submit">Submit</button>
 			</div>
 		</form>
 	</mat-card-content>
diff --git a/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.html b/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.html
index a0eb58ca7f..a20e3b44a5 100644
--- a/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.html
+++ b/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.html
@@ -106,7 +106,7 @@ limitations under the License.
 			</mat-form-field>
 		</mat-card-content>
 		<mat-card-actions align="end">
-			<button mat-raised-button color="primary">Save</button>
+			<button mat-raised-button color="primary" type="submit">Save</button>
 		</mat-card-actions>
 	</form>
 </mat-card>
diff --git a/experimental/traffic-portal/src/app/core/users/user-registration-dialog/user-registration-dialog.component.html b/experimental/traffic-portal/src/app/core/users/user-registration-dialog/user-registration-dialog.component.html
index 5c41700a50..819c7a9325 100644
--- a/experimental/traffic-portal/src/app/core/users/user-registration-dialog/user-registration-dialog.component.html
+++ b/experimental/traffic-portal/src/app/core/users/user-registration-dialog/user-registration-dialog.component.html
@@ -32,7 +32,7 @@ limitations under the License.
 		</mat-form-field>
 	</mat-dialog-content>
 	<mat-dialog-actions>
-		<button mat-stroked-button>Submit</button>
+		<button type="submit" mat-stroked-button>Submit</button>
 		<button mat-stroked-button color="warn" type="button" mat-dialog-close>Cancel</button>
 	</mat-dialog-actions>
 </form>
diff --git a/experimental/traffic-portal/src/app/core/users/users.component.html b/experimental/traffic-portal/src/app/core/users/users.component.html
index 5f5eb8e93f..d04532049e 100644
--- a/experimental/traffic-portal/src/app/core/users/users.component.html
+++ b/experimental/traffic-portal/src/app/core/users/users.component.html
@@ -34,7 +34,7 @@ limitations under the License.
 	  <a cdkMenuItem routerLink="new" color="accent" mat-mini-fab aria-label="Create user from scratch" title="Create user from scratch">
 		<mat-icon>person_add</mat-icon>
 	  </a>
-	  <button cdkMenuItem color="accent" mat-mini-fab color="accent" aria-label="Register a new user by email" title="Register a new user by email" (click)="register()">
+	  <button type="button" cdkMenuItem color="accent" mat-mini-fab aria-label="Register a new user by email" title="Register a new user by email" (click)="register()">
 		<mat-icon>contact_mail</mat-icon>
 	  </button>
 	</menu>
diff --git a/experimental/traffic-portal/src/app/login/login.component.html b/experimental/traffic-portal/src/app/login/login.component.html
index 3ade57fa60..20af5767be 100644
--- a/experimental/traffic-portal/src/app/login/login.component.html
+++ b/experimental/traffic-portal/src/app/login/login.component.html
@@ -27,7 +27,7 @@ limitations under the License.
 				<tp-obscured-text-input [autocomplete]="passwordAutocomplete" required="true" [(value)]="p" name="p"></tp-obscured-text-input>
 			</mat-form-field>
 			<div>
-				<button name="login" mat-raised-button color="primary">Login</button>
+				<button name="login" mat-raised-button color="primary" type="submit">Login</button>
 				<button name="clear" mat-raised-button color="warn" type="reset">Clear</button>
 				<button name="reset" mat-button color="accent" type="button" (click)="resetPassword()">Forgot Password</button>
 			</div>
diff --git a/experimental/traffic-portal/src/app/login/reset-password-dialog/reset-password-dialog.component.html b/experimental/traffic-portal/src/app/login/reset-password-dialog/reset-password-dialog.component.html
index f89270fea5..052e4c994c 100644
--- a/experimental/traffic-portal/src/app/login/reset-password-dialog/reset-password-dialog.component.html
+++ b/experimental/traffic-portal/src/app/login/reset-password-dialog/reset-password-dialog.component.html
@@ -21,6 +21,6 @@ limitations under the License.
 	</mat-dialog-content>
 	<mat-dialog-actions>
 		<button mat-raised-button color="warn" type="button" mat-dialog-close>Cancel</button>
-		<button mat-raised-button color="primary">Submit</button>
+		<button mat-raised-button color="primary" type="submit">Submit</button>
 	</mat-dialog-actions>
 </form>
diff --git a/experimental/traffic-portal/src/app/shared/dialogs/collection-choice-dialog/collection-choice-dialog.component.html b/experimental/traffic-portal/src/app/shared/dialogs/collection-choice-dialog/collection-choice-dialog.component.html
index b60e1656f9..a790af2464 100644
--- a/experimental/traffic-portal/src/app/shared/dialogs/collection-choice-dialog/collection-choice-dialog.component.html
+++ b/experimental/traffic-portal/src/app/shared/dialogs/collection-choice-dialog/collection-choice-dialog.component.html
@@ -16,7 +16,7 @@ limitations under the License.
 <mat-dialog-content>
 	<p>{{dialogData.message}}</p>
 	<mat-form-field>
-		<label>{{dialogData.label}}</label>
+		<mat-label>{{dialogData.label}}</mat-label>
 		<mat-select name="{{dialogData.label}}" [(ngModel)]="selected" required>
 			<mat-option *ngFor="let item of dialogData.collection" [value]="item.value">{{item.label}}</mat-option>
 		</mat-select>
@@ -26,6 +26,6 @@ limitations under the License.
 	</mat-form-field>
 </mat-dialog-content>
 <mat-dialog-actions>
-	<button mat-button [disabled]="!selected" [mat-dialog-close]="selected">Confirm</button>
-	<button mat-button [mat-dialog-close]="false">Cancel</button>
+	<button type="submit" mat-button [disabled]="!selected" [mat-dialog-close]="selected">Confirm</button>
+	<button type="button" mat-button [mat-dialog-close]="false">Cancel</button>
 </mat-dialog-actions>
diff --git a/experimental/traffic-portal/src/app/shared/dialogs/decision-dialog/decision-dialog.component.html b/experimental/traffic-portal/src/app/shared/dialogs/decision-dialog/decision-dialog.component.html
index 8b55e5e1e8..284a62fead 100644
--- a/experimental/traffic-portal/src/app/shared/dialogs/decision-dialog/decision-dialog.component.html
+++ b/experimental/traffic-portal/src/app/shared/dialogs/decision-dialog/decision-dialog.component.html
@@ -15,6 +15,6 @@ limitations under the License.
 <h2 mat-dialog-title>{{dialogData.title}}</h2>
 <mat-dialog-content>{{dialogData.message}}</mat-dialog-content>
 <mat-dialog-actions>
-	<button mat-button [mat-dialog-close]="true">Confirm</button>
-	<button mat-button [mat-dialog-close]="false">Cancel</button>
+	<button type="submit" mat-button [mat-dialog-close]="true">Confirm</button>
+	<button type="button" mat-button [mat-dialog-close]="false">Cancel</button>
 </mat-dialog-actions>
diff --git a/experimental/traffic-portal/src/app/shared/dialogs/text-dialog/text-dialog.component.html b/experimental/traffic-portal/src/app/shared/dialogs/text-dialog/text-dialog.component.html
index 3cbb124de0..1a5fab1947 100644
--- a/experimental/traffic-portal/src/app/shared/dialogs/text-dialog/text-dialog.component.html
+++ b/experimental/traffic-portal/src/app/shared/dialogs/text-dialog/text-dialog.component.html
@@ -15,5 +15,5 @@ limitations under the License.
 <h2 mat-dialog-title>{{dialogData.title}}</h2>
 <mat-dialog-content>{{dialogData.message}}</mat-dialog-content>
 <mat-dialog-actions>
-	<button mat-button mat-dialog-close>Ok</button>
+	<button type="submit" mat-button mat-dialog-close>Ok</button>
 </mat-dialog-actions>
diff --git a/experimental/traffic-portal/src/app/shared/generic-table/generic-table.component.html b/experimental/traffic-portal/src/app/shared/generic-table/generic-table.component.html
index 5a21c5396e..20dbf15054 100644
--- a/experimental/traffic-portal/src/app/shared/generic-table/generic-table.component.html
+++ b/experimental/traffic-portal/src/app/shared/generic-table/generic-table.component.html
@@ -19,12 +19,12 @@ limitations under the License.
 	<button mat-flat-button type="button" (click)="download()" title="save as CSV"><fa-icon [icon]="downloadIcon"></fa-icon></button>
 	<button mat-flat-button type="button" (click)="gridAPI.sizeColumnsToFit()">Resize</button>
 	<div class="toggle-columns" role="group" title="Select Table Columns">
-		<button mat-flat-button [matMenuTriggerFor]="menu">
+		<button type="button" mat-flat-button [matMenuTriggerFor]="menu">
 			<fa-icon [icon]="columnsIcon"></fa-icon>&nbsp;
 			<fa-icon [icon]="caretIcon" class="caret" [ngClass]="{'rotate': showMenu}"></fa-icon>
 		</button>
 		<mat-menu #menu="matMenu">
-			<button mat-menu-item *ngFor="let c of columns" (click)="toggleVisibility($event, c.getColId())">
+			<button type="button" mat-menu-item *ngFor="let c of columns" (click)="toggleVisibility($event, c.getColId())">
 				<mat-checkbox [checked]="c.isVisible()" (click)="$event.preventDefault()" [name]="c.getColDef().headerName">
 					{{c.getColDef().headerName}}
 				</mat-checkbox>
diff --git a/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.html b/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.html
index 87d79132cd..87f28829bc 100644
--- a/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.html
+++ b/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.html
@@ -21,37 +21,37 @@ limitations under the License.
 		<ul>
 			<li *ngFor="let nav of horizNavs">
 				<a *ngIf="navShown(nav, 'anchor')" mat-button [routerLink]="navRouterLink(nav)">{{nav.text}}</a>
-				<button *ngIf="navShown(nav, 'button')" mat-button (click)="navClick(nav)">{{nav.text}}</button>
+				<button type="button" *ngIf="navShown(nav, 'button')" mat-button (click)="navClick(nav)">{{nav.text}}</button>
 			</li>
 			<li>
-				<button mat-icon-button [matMenuTriggerFor]="expandedMenu">
+				<button type="button" mat-icon-button [matMenuTriggerFor]="expandedMenu">
 					<mat-icon>manage_accounts</mat-icon>
 				</button>
 				<mat-menu #expandedMenu="matMenu">
-					<button mat-menu-item [matMenuTriggerFor]="themeMenu">Theme</button>
+					<button type="button" mat-menu-item [matMenuTriggerFor]="themeMenu">Theme</button>
 					<div *ngFor="let nav of vertNavs">
 						<a *ngIf="navShown(nav, 'anchor')" mat-menu-item [routerLink]="navRouterLink(nav)">{{nav.text}}</a>
-						<button *ngIf="navShown(nav, 'button')" mat-menu-item (click)="navClick(nav)">{{nav.text}}</button>
+						<button type="button" *ngIf="navShown(nav, 'button')" mat-menu-item (click)="navClick(nav)">{{nav.text}}</button>
 					</div>
 				</mat-menu>
 			</li>
 		</ul>
 	</nav>
 	<nav id="collapsed">
-		<button mat-icon-button [matMenuTriggerFor]="collapsedMenu"><mat-icon>menu</mat-icon></button>
+		<button type="button" mat-icon-button [matMenuTriggerFor]="collapsedMenu"><mat-icon>menu</mat-icon></button>
 		<mat-menu #collapsedMenu="matMenu">
 			<li *ngFor="let nav of horizNavs">
 				<a *ngIf="navShown(nav, 'anchor')" mat-menu-item [routerLink]="navRouterLink(nav)">{{nav.text}}</a>
-				<button *ngIf="navShown(nav, 'button')" mat-menu-item (click)="navClick(nav)">{{nav.text}}</button>
+				<button type="button" *ngIf="navShown(nav, 'button')" mat-menu-item (click)="navClick(nav)">{{nav.text}}</button>
 			</li>
-			<button mat-menu-item [matMenuTriggerFor]="themeMenu">Theme</button>
+			<button type="button" mat-menu-item [matMenuTriggerFor]="themeMenu">Theme</button>
 			<li *ngFor="let nav of vertNavs">
 				<a *ngIf="navShown(nav, 'anchor')" mat-menu-item [routerLink]="navRouterLink(nav)">{{nav.text}}</a>
-				<button *ngIf="navShown(nav, 'button')" mat-menu-item (click)="navClick(nav)">{{nav.text}}</button>
+				<button type="button" *ngIf="navShown(nav, 'button')" mat-menu-item (click)="navClick(nav)">{{nav.text}}</button>
 			</li>
 		</mat-menu>
 	</nav>
 	<mat-menu #themeMenu="matMenu">
-		<button mat-menu-item *ngFor="let theme of themeSvc.themes" (click)="themeSvc.loadTheme(theme)">{{theme.name}}</button>
+		<button type="button" mat-menu-item *ngFor="let theme of themeSvc.themes" (click)="themeSvc.loadTheme(theme)">{{theme.name}}</button>
 	</mat-menu>
 </mat-toolbar>