This is an automated email from the ASF dual-hosted git repository.
shamrick 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 8946fcf008 Zuni profile detail (#7466)
8946fcf008 is described below
commit 8946fcf008406517e0430d7aa667a0538c51d5ab
Author: Kannan.G.B <[email protected]>
AuthorDate: Wed May 10 00:52:05 2023 +0530
Zuni profile detail (#7466)
* initial commit
* latest update for profile details edit, delete
* test case error fix
* latest
* e2e fix
* input to textarea
* order changed
* comments addressed
* comments addressed
* url correction
* e2e fixees
* angular 15 upgrade
* lint fixes
---
.../traffic-portal/nightwatch/globals/globals.ts | 2 +
.../page_objects/profiles/profileDetail.ts | 51 +++++++
.../nightwatch/tests/profiles/detail.spec.ts | 50 +++++++
.../traffic-portal/src/app/api/profile.service.ts | 19 ++-
.../src/app/api/testing/profile.service.ts | 17 ++-
.../asns/table/asns-table.component.html | 2 +-
.../cache-group-table.component.html | 2 +-
.../table/coordinates-table.component.html | 2 +-
.../divisions/table/divisions-table.component.html | 2 +-
.../regions/table/regions-table.component.html | 2 +-
.../core/change-logs/change-logs.component.html | 2 +-
.../traffic-portal/src/app/core/core.module.ts | 3 +
.../app/core/dashboard/dashboard.component.html | 2 +-
.../new-delivery-service.component.html | 4 +-
.../profile-detail/profile-detail.component.html | 61 +++++++++
.../profile-detail/profile-detail.component.scss} | 21 +--
.../profile-detail.component.spec.ts | 81 +++++++++++
.../profile-detail/profile-detail.component.ts | 151 +++++++++++++++++++++
.../profile-table/profile-table.component.html | 4 +-
.../profile-table/profile-table.component.ts | 27 ++++
.../phys-loc/table/phys-loc-table.component.html | 2 +-
.../servers-table/servers-table.component.html | 2 +-
.../status-details/status-details.component.scss | 4 +-
.../statuses-table/statuses-table.component.html | 2 +-
.../core/types/table/types-table.component.html | 2 +-
.../app/core/users/tenants/tenants.component.html | 2 +-
.../src/app/core/users/users.component.html | 2 +-
.../src/app/login/login.component.html | 2 +-
28 files changed, 489 insertions(+), 34 deletions(-)
diff --git a/experimental/traffic-portal/nightwatch/globals/globals.ts
b/experimental/traffic-portal/nightwatch/globals/globals.ts
index d8c3925b58..e842afab71 100644
--- a/experimental/traffic-portal/nightwatch/globals/globals.ts
+++ b/experimental/traffic-portal/nightwatch/globals/globals.ts
@@ -32,6 +32,7 @@ import type { DeliveryServiceCardPageObject } from
"nightwatch/page_objects/deli
import type { DeliveryServiceDetailPageObject } from
"nightwatch/page_objects/deliveryServices/deliveryServiceDetail";
import type { DeliveryServiceInvalidPageObject } from
"nightwatch/page_objects/deliveryServices/deliveryServiceInvalidationJobs";
import type { LoginPageObject } from "nightwatch/page_objects/login";
+import type { ProfileDetailPageObject } from
"nightwatch/page_objects/profiles/profileDetail";
import type { ProfilePageObject } from
"nightwatch/page_objects/profiles/profilesTable";
import type { PhysLocDetailPageObject } from
"nightwatch/page_objects/servers/physLocDetail";
import type { PhysLocTablePageObject } from
"nightwatch/page_objects/servers/physLocTable";
@@ -108,6 +109,7 @@ declare module "nightwatch" {
login: () => LoginPageObject;
profiles: {
profileTable: () => ProfilePageObject;
+ profileDetail: () => ProfileDetailPageObject;
};
servers: {
physLocDetail: () => PhysLocDetailPageObject;
diff --git
a/experimental/traffic-portal/nightwatch/page_objects/profiles/profileDetail.ts
b/experimental/traffic-portal/nightwatch/page_objects/profiles/profileDetail.ts
new file mode 100644
index 0000000000..a8f5593c03
--- /dev/null
+++
b/experimental/traffic-portal/nightwatch/page_objects/profiles/profileDetail.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 { EnhancedPageObject } from "nightwatch";
+
+/**
+ * Defines the PageObject for Profile Details.
+ */
+export type ProfileDetailPageObject = EnhancedPageObject<{}, typeof
profileDetailPageObject.elements>;
+
+const profileDetailPageObject = {
+ elements: {
+ cdn: {
+ selector: "mat-select[name='cdn']"
+ },
+ description: {
+ selector: "textarea[name='description']"
+ },
+ id: {
+ selector: "input[name='id']"
+ },
+ lastUpdated: {
+ selector: "input[name='lastUpdated']"
+ },
+ name: {
+ selector: "input[name='name']"
+ },
+ routingDisabled: {
+ selector: "mat-select[name='routingDisabled']"
+ },
+ saveBtn: {
+ selector: "button[type='submit']"
+ },
+ type: {
+ selector: "mat-select[name='type']"
+ }
+ },
+};
+
+export default profileDetailPageObject;
diff --git
a/experimental/traffic-portal/nightwatch/tests/profiles/detail.spec.ts
b/experimental/traffic-portal/nightwatch/tests/profiles/detail.spec.ts
new file mode 100644
index 0000000000..4c1faf0c5b
--- /dev/null
+++ b/experimental/traffic-portal/nightwatch/tests/profiles/detail.spec.ts
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+describe("Profile Detail Spec", () => {
+ it("Test Profile", () => {
+ const page = browser.page.profiles.profileDetail();
+
browser.url(`${page.api.launchUrl}/core/profiles/${browser.globals.testData.profile.id}`,
res => {
+ browser.assert.ok(res.status === 0);
+ page.waitForElementVisible("mat-card")
+ .assert.enabled("@name")
+ .assert.enabled("@cdn")
+ .assert.enabled("@type")
+ .assert.enabled("@routingDisabled")
+ .assert.enabled("@description")
+ .assert.not.enabled("@id")
+ .assert.not.enabled("@lastUpdated")
+ .assert.enabled("@saveBtn")
+ .assert.valueEquals("@name",
browser.globals.testData.profile.name)
+ .assert.valueEquals("@id",
String(browser.globals.testData.profile.id));
+ });
+ });
+
+ it("New Profile", () => {
+ const page = browser.page.profiles.profileDetail();
+ browser.url(`${page.api.launchUrl}/core/profiles/new`, res => {
+ browser.assert.ok(res.status === 0);
+ page.waitForElementVisible("mat-card")
+ .assert.enabled("@name")
+ .assert.enabled("@cdn")
+ .assert.enabled("@type")
+ .assert.enabled("@routingDisabled")
+ .assert.enabled("@description")
+ .assert.not.elementPresent("@id")
+ .assert.not.elementPresent("@lastUpdated")
+ .assert.enabled("@saveBtn")
+ .assert.valueEquals("@name", "");
+ });
+ });
+});
diff --git a/experimental/traffic-portal/src/app/api/profile.service.ts
b/experimental/traffic-portal/src/app/api/profile.service.ts
index 6eaea53501..01a00b0122 100644
--- a/experimental/traffic-portal/src/app/api/profile.service.ts
+++ b/experimental/traffic-portal/src/app/api/profile.service.ts
@@ -72,17 +72,28 @@ export class ProfileService extends APIService {
}
/**
- * Creates a new type.
+ * Creates a new profile.
*
- * @param profile The type to create.
- * @returns The created type.
+ * @param profile The profile to create.
+ * @returns The created profile.
*/
public async createProfile(profile: RequestProfile):
Promise<ResponseProfile> {
return this.post<ResponseProfile>("profiles",
profile).toPromise();
}
/**
- * Deletes an existing type.
+ * Replaces the current definition of a profile with the one given.
+ *
+ * @param profile The new profile.
+ * @returns The updated profile.
+ */
+ public async updateProfile(profile: ResponseProfile):
Promise<ResponseProfile> {
+ const path = `profiles/${profile.id}`;
+ return this.put<ResponseProfile>(path, profile).toPromise();
+ }
+
+ /**
+ * Deletes an existing profile.
*
* @param profileId Id of the profile to delete.
* @returns The success message.
diff --git a/experimental/traffic-portal/src/app/api/testing/profile.service.ts
b/experimental/traffic-portal/src/app/api/testing/profile.service.ts
index 1f4a96b174..fa2a100873 100644
--- a/experimental/traffic-portal/src/app/api/testing/profile.service.ts
+++ b/experimental/traffic-portal/src/app/api/testing/profile.service.ts
@@ -191,13 +191,28 @@ export class ProfileService {
return t;
}
+ /**
+ * Updates an existing profile.
+ *
+ * @param profile the profile to update.
+ * @returns The success message.
+ */
+ public async updateProfile(profile: ResponseProfile):
Promise<ResponseProfile> {
+ const id = this.profiles.findIndex(d => d.id === profile.id);
+ if (id === -1) {
+ throw new Error(`no such profile: ${profile.id}`);
+ }
+ this.profiles[id] = profile;
+ return profile;
+ }
+
/**
* Deletes an existing profile.
*
* @param id Id of the profile to delete.
* @returns The success message.
*/
- public async deleteProfile(id: number): Promise<ResponseProfile> {
+ public async deleteProfile(id: number | ResponseProfile):
Promise<ResponseProfile> {
const index = this.profiles.findIndex(t => t.id === id);
if (index === -1) {
throw new Error(`no such Type: ${id}`);
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.html
b/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.html
index 37c6e9bb28..48b75f809f 100644
---
a/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.html
+++
b/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search ASNs" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search ASNs" inputmode="search" role="search" accesskey="/" placeholder="Fuzzy
Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="asns | async"
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 fe491c7755..b2fdc94978 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
@@ -13,7 +13,7 @@ limitations under the License.
-->
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Cache Groups" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()"/>
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Cache Groups" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()"/>
</div>
<tp-generic-table
[data]="cacheGroups | async"
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/coordinates/table/coordinates-table.component.html
b/experimental/traffic-portal/src/app/core/cache-groups/coordinates/table/coordinates-table.component.html
index 791b32c58c..75b4e8a2b3 100644
---
a/experimental/traffic-portal/src/app/core/cache-groups/coordinates/table/coordinates-table.component.html
+++
b/experimental/traffic-portal/src/app/core/cache-groups/coordinates/table/coordinates-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Coordinates" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Coordinates" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="coordinates | async"
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/divisions/table/divisions-table.component.html
b/experimental/traffic-portal/src/app/core/cache-groups/divisions/table/divisions-table.component.html
index 6d4835a9b2..80195e9f50 100644
---
a/experimental/traffic-portal/src/app/core/cache-groups/divisions/table/divisions-table.component.html
+++
b/experimental/traffic-portal/src/app/core/cache-groups/divisions/table/divisions-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Divisions" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Divisions" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="divisions | async"
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.html
b/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.html
index 7f1193fba9..abdcd8860f 100644
---
a/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.html
+++
b/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Regions" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Regions" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="regions | async"
diff --git
a/experimental/traffic-portal/src/app/core/change-logs/change-logs.component.html
b/experimental/traffic-portal/src/app/core/change-logs/change-logs.component.html
index 552240e0e4..0bd1a62d99 100644
---
a/experimental/traffic-portal/src/app/core/change-logs/change-logs.component.html
+++
b/experimental/traffic-portal/src/app/core/change-logs/change-logs.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined">
<div>
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Change Logs" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [(ngModel)]="searchText" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Change Logs" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [(ngModel)]="searchText" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="changeLogs | async"
diff --git a/experimental/traffic-portal/src/app/core/core.module.ts
b/experimental/traffic-portal/src/app/core/core.module.ts
index 833519fe07..9d6f1ecf1a 100644
--- a/experimental/traffic-portal/src/app/core/core.module.ts
+++ b/experimental/traffic-portal/src/app/core/core.module.ts
@@ -48,6 +48,7 @@ import {
} from
"./deliveryservice/invalidation-jobs/new-invalidation-job-dialog/new-invalidation-job-dialog.component";
import { NewDeliveryServiceComponent } from
"./deliveryservice/new-delivery-service/new-delivery-service.component";
import { ISOGenerationFormComponent } from
"./misc/isogeneration-form/isogeneration-form.component";
+import { ProfileDetailComponent } from
"./profiles/profile-detail/profile-detail.component";
import { ProfileTableComponent } from
"./profiles/profile-table/profile-table.component";
import { PhysLocDetailComponent } from
"./servers/phys-loc/detail/phys-loc-detail.component";
import { PhysLocTableComponent } from
"./servers/phys-loc/table/phys-loc-table.component";
@@ -95,6 +96,7 @@ export const ROUTES: Routes = [
{ component: StatusesTableComponent, path: "statuses" },
{ component: StatusDetailsComponent, path: "statuses/:id" },
{ component: ISOGenerationFormComponent, path: "iso-gen"},
+ { component: ProfileDetailComponent, path: "profiles/:id"},
{ component: ProfileTableComponent, path: "profiles"},
].map(r => ({...r, canActivate: [AuthenticatedGuard]}));
@@ -141,6 +143,7 @@ export const ROUTES: Routes = [
ISOGenerationFormComponent,
ProfileTableComponent,
CDNDetailComponent,
+ ProfileDetailComponent,
],
exports: [],
imports: [
diff --git
a/experimental/traffic-portal/src/app/core/dashboard/dashboard.component.html
b/experimental/traffic-portal/src/app/core/dashboard/dashboard.component.html
index d4b0f11934..7f61696f5f 100644
---
a/experimental/traffic-portal/src/app/core/dashboard/dashboard.component.html
+++
b/experimental/traffic-portal/src/app/core/dashboard/dashboard.component.html
@@ -12,7 +12,7 @@ See the License for the specific language governing
permissions and
limitations under the License.
-->
<main class="table-page-content">
- <div class="search-container"><input type="search" role="search"
aria-label="Fuzzy Search Delivery Service(s)" autofocus inputmode="search"
name="fuzzControl" [formControl]="fuzzControl" (input)="updateURL($event)"
accesskey="/" placeholder="Fuzzy Search"/></div>
+ <div class="search-container"><input type="search" role="search"
aria-label="Fuzzy Search Delivery Service(s)" inputmode="search"
name="fuzzControl" [formControl]="fuzzControl" (input)="updateURL($event)"
accesskey="/" placeholder="Fuzzy Search"/></div>
<article id="deliveryservices" [hidden]="loading">
<ds-card *ngFor="let ds of filteredDSes; trackBy: tracker; let
first=first; let last=last;" [deliveryService]="ds" [now]="now" [today]="today"
[first]=first [last]=last></ds-card>
</article>
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 0bf814f6bf..1404bbf90e 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
@@ -17,7 +17,7 @@ limitations under the License.
<h2>Step 1 - Origin Server</h2>
<div class="form-content">
<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/>
+ <input [formControl]="originURL" type="url"
id="origin" name="origin" placeholder="this URL" 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>
@@ -32,7 +32,7 @@ limitations under the License.
<h2>Step 2 - Meta Information</h2>
<div class="form-content">
<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>
+ <input type="text" 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</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>
diff --git
a/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.html
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.html
new file mode 100644
index 0000000000..d581b5a358
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.html
@@ -0,0 +1,61 @@
+<!--
+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.
+-->
+
+<mat-card>
+ <tp-loading *ngIf="loading"></tp-loading>
+ <form ngNativeValidate (ngSubmit)="submit($event)" *ngIf="profile">
+ <mat-card-content>
+ <mat-form-field *ngIf="!new">
+ <mat-label>ID</mat-label>
+ <input matInput type="text" name="id" disabled
readonly [defaultValue]="profile['id']" />
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>Name</mat-label>
+ <input matInput type="text" name="name"
[(ngModel)]="profile['name']" required />
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>CDN</mat-label>
+ <mat-select name="cdn" [(ngModel)]="profile['cdn']" required>
+ <mat-option [value]="cdn.id" *ngFor="let cdn of
cdns">{{cdn.name}}</mat-option>
+ </mat-select>
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>Type</mat-label>
+ <mat-select name="type" [(ngModel)]="profile['type']" required>
+ <mat-option [value]="type.value" *ngFor="let type of
types">{{type.value}}</mat-option>
+ </mat-select>
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>Routing Disabled</mat-label>
+ <mat-select name="routingDisabled"
[(ngModel)]="profile['routingDisabled']" required>
+ <mat-option [value]="true">True</mat-option>
+ <mat-option [value]="false">False</mat-option>
+ </mat-select>
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>Description</mat-label>
+ <textarea matInput name="description"
[(ngModel)]="profile['description']" required></textarea>
+ </mat-form-field>
+ <mat-form-field *ngIf="!new">
+ <mat-label>Last Updated</mat-label>
+ <input matInput type="text" name="lastUpdated"
disabled readonly [defaultValue]="profile['lastUpdated']" />
+ </mat-form-field>
+ </mat-card-content>
+
+ <mat-card-actions align="end">
+ <button mat-raised-button type="button" *ngIf="!new"
color="warn" (click)="deleteProfile()">Delete</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/statuses/status-details/status-details.component.scss
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.scss
similarity index 72%
copy from
experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.scss
copy to
experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.scss
index 0a24e3195e..1571bf9b6f 100644
---
a/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.scss
+++
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.scss
@@ -11,15 +11,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-mat-card {
- margin: 1em auto;
- width: 95%;
- min-width: 350px;
- mat-card-content {
- display: grid;
- grid-template-columns: 1fr;
- row-gap: 2em;
- margin: 1em auto 10px;
- }
+.mat-mdc-card {
+ margin: 1em auto;
+ width: 80%;
+ min-width: 350px;
+
+ .mat-mdc-card-content {
+ display: grid;
+ grid-template-columns: 1fr;
+ grid-row-gap: 2em;
+ margin: 1em auto 50px;
+ }
}
diff --git
a/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.spec.ts
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.spec.ts
new file mode 100644
index 0000000000..8bf93f7a07
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.spec.ts
@@ -0,0 +1,81 @@
+/*
+* 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 { ComponentFixture, TestBed } from "@angular/core/testing";
+import { MatDialogModule } from "@angular/material/dialog";
+import { ActivatedRoute } from "@angular/router";
+import { RouterTestingModule } from "@angular/router/testing";
+import { ReplaySubject } from "rxjs";
+
+import { ProfileService } from "src/app/api";
+import { APITestingModule } from "src/app/api/testing";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+import { ProfileDetailComponent } from "./profile-detail.component";
+
+describe("ProfileDetailComponent", () => {
+ let component: ProfileDetailComponent;
+ let fixture: ComponentFixture<ProfileDetailComponent>;
+ let route: ActivatedRoute;
+ let paramMap: jasmine.Spy;
+ let service: ProfileService;
+
+ const navSvc = jasmine.createSpyObj([],{headerHidden: new
ReplaySubject<boolean>(), headerTitle: new ReplaySubject<string>()});
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ ProfileDetailComponent ],
+ imports: [ APITestingModule, RouterTestingModule,
MatDialogModule ],
+ providers: [ { provide: NavigationService, useValue:
navSvc } ]
+ })
+ .compileComponents();
+
+ route = TestBed.inject(ActivatedRoute);
+ paramMap = spyOn(route.snapshot.paramMap, "get");
+ service = TestBed.inject(ProfileService);
+ paramMap.and.returnValue(null);
+ fixture = TestBed.createComponent(ProfileDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ });
+
+ it("new profile", async () => {
+ paramMap.and.returnValue("new");
+
+ fixture = TestBed.createComponent(ProfileDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(paramMap).toHaveBeenCalled();
+ expect(component.profile).not.toBeNull();
+ expect(component.new).toBeTrue();
+ });
+
+ it("existing profile", async () => {
+ const id = 1;
+ paramMap.and.returnValue(id);
+ const profile = await service.getProfiles(id);
+ fixture = TestBed.createComponent(ProfileDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(paramMap).toHaveBeenCalled();
+ expect(component.profile).not.toBeNull();
+ expect(component.profile.name).toBe(profile.name);
+ expect(component.new).toBeFalse();
+ });
+});
diff --git
a/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.ts
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.ts
new file mode 100644
index 0000000000..78e0c362ff
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/profiles/profile-detail/profile-detail.component.ts
@@ -0,0 +1,151 @@
+/*
+* 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 { Component, OnInit } from "@angular/core";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute, Router } from "@angular/router";
+import { ProfileType, ResponseCDN, ResponseProfile } from "trafficops-types";
+
+import { CDNService, ProfileService } from "src/app/api";
+import { DecisionDialogComponent } from
"src/app/shared/dialogs/decision-dialog/decision-dialog.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+/**
+ * ProfileDetailComponent is the controller for the profile add/edit form.
+ */
+@Component({
+ selector: "tp-profile-detail",
+ styleUrls: ["./profile-detail.component.scss"],
+ templateUrl: "./profile-detail.component.html"
+})
+export class ProfileDetailComponent implements OnInit {
+ public new = false;
+
+ /** Loader status for the actions */
+ public loading = true;
+
+ /** All details of profile requested */
+ public profile!: ResponseProfile;
+
+ /** All cdns used for profile creation as input */
+ public cdns!: ResponseCDN[];
+
+ public types = [
+ { value: "ATS_PROFILE" },
+ { value: "TR_PROFILE" },
+ { value: "TM_PROFILE" },
+ { value: "TS_PROFILE" },
+ { value: "TP_PROFILE" },
+ { value: "INFLUXDB_PROFILE" },
+ { value: "RIAK_PROFILE" },
+ { value: "SPLUNK_PROFILE" },
+ { value: "DS_PROFILE" },
+ { value: "ORG_PROFILE" },
+ { value: "KAFKA_PROFILE" },
+ { value: "LOGSTASH_PROFILE" },
+ { value: "ES_PROFILE" },
+ { value: "UNK_PROFILE" },
+ { value: "GROVE_PROFILE" }
+ ];
+
+ /**
+ * Constructor.
+ *
+ * @param api The Profiles API which is used to provide functions for
create, edit and delete profiles.
+ * @param cdnService The CDN service API which is used to provide cdns.
+ * @param dialog Dialog manager
+ * @param navSvc Manages the header
+ * @param route A reference to the route of this view which is used to
get the 'id' query parameter of profile.
+ * @param router Angular router
+ */
+ constructor(
+ private readonly api: ProfileService,
+ private readonly cdnService: CDNService,
+ private readonly dialog: MatDialog,
+ private readonly navSvc: NavigationService,
+ private readonly route: ActivatedRoute,
+ private readonly router: Router
+ ) { }
+
+ /**
+ * Angular lifecycle hook where data is initialized.
+ */
+ public async ngOnInit(): Promise<void> {
+ // Getting id from the route
+ const id = this.route.snapshot.paramMap.get("id");
+
+ this.cdns = await this.cdnService.getCDNs();
+ if (id && id !== "new") {
+ const numID = parseInt(id, 10);
+ if (Number.isNaN(numID)) {
+ throw new Error(`route parameter 'id' was
non-number: ${{ id }}`);
+ } else {
+ this.profile = await
this.api.getProfiles(Number(id));
+ this.navSvc.headerTitle.next(`Profile:
${this.profile.name}`);
+ }
+ this.loading = false;
+ } else {
+ this.new = true;
+ this.navSvc.headerTitle.next("New Profile");
+ this.profile = {
+ cdn: 1,
+ cdnName: "",
+ description: "",
+ id: -1,
+ lastUpdated: new Date(),
+ name: "",
+ routingDisabled: false,
+ type: ProfileType.ATS_PROFILE
+ };
+ this.loading = false;
+ }
+ }
+
+ /**
+ * Submits new/updated profile.
+ *
+ * @param e HTML form submission event.
+ */
+ public async submit(e: Event): Promise<void> {
+ e.preventDefault();
+ e.stopPropagation();
+ if(this.new) {
+ this.profile = await
this.api.createProfile(this.profile);
+ this.new = false;
+ } else {
+ this.profile = await
this.api.updateProfile(this.profile);
+ }
+ }
+
+ /**
+ * Deletes the current profile.
+ */
+ public async deleteProfile(): Promise<void> {
+ if (this.new) {
+ console.error("Unable to delete new profile");
+ return;
+ }
+ const ref = this.dialog.open(DecisionDialogComponent, {
+ data: {
+ message: `Are you sure to delete Profile
${this.profile.name} with id ${this.profile.id}?`,
+ title: "Confirm Delete"
+ }
+ });
+ ref.afterClosed().subscribe(result => {
+ if (result) {
+
this.api.deleteProfile(this.profile.id).then(async () =>
this.router.navigate(["/core/profiles"]));
+ }
+ });
+ }
+}
diff --git
a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.html
b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.html
index fe908502c9..2cd0ba90b4 100644
---
a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.html
+++
b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Profiles" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Profiles" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="profiles | async"
@@ -25,3 +25,5 @@ limitations under the License.
(contextMenuAction)="handleContextMenu($event)">
</tp-generic-table>
</mat-card>
+
+<a class="page-fab" mat-fab title="Create a new Profile"
*ngIf="auth.hasPermission('PROFILE:CREATE')"
routerLink="new"><mat-icon>add</mat-icon></a>
diff --git
a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts
b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts
index 075ee04d23..c33c0c7d9e 100644
---
a/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts
+++
b/experimental/traffic-portal/src/app/core/profiles/profile-table/profile-table.component.ts
@@ -66,11 +66,38 @@ export class ProfileTableComponent implements OnInit {
/** Definitions for the context menu items (which act on augmented
cache-group data). */
public contextMenuItems: Array<ContextMenuItem<ResponseProfile>> = [
+ {
+ href: (profile: ResponseProfile): string =>
`${profile.id}`,
+ name: "Open in New Tab",
+ newTab: true
+ },
+ {
+ href: (type: ResponseProfile): string => `${type.id}`,
+ name: "Edit"
+ },
{
action: "delete",
multiRow: false,
name: "Delete"
},
+ {
+ action: "import-profile",
+ disabled: (): true => true,
+ multiRow: false,
+ name: "Import Profile",
+ },
+ {
+ action: "export-profile",
+ disabled: (): true => true,
+ multiRow: false,
+ name: "Export Profile",
+ },
+ {
+ action: "manage-parameters",
+ disabled: (): true => true,
+ multiRow: false,
+ name: "Manage Parameters",
+ },
{
href: "/core/servers",
name: "View Servers",
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 f6b605593f..eb60ccc806 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
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Physical Locations" autofocus inputmode="search" role="search"
accesskey="/" placeholder="Fuzzy Search" [formControl]="fuzzControl"
(input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Physical Locations" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="physLocations | async"
diff --git
a/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.html
b/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.html
index a466753365..cc133b3273 100644
---
a/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.html
+++
b/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<main>
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl"
aria-label="Fuzzy Search Servers" autofocus inputmode="search" role="search"
accesskey="/" placeholder="Fuzzy Search" [formControl]="fuzzControl"
(input)="updateURL()"/>
+ <input type="search" name="fuzzControl"
aria-label="Fuzzy Search Servers" inputmode="search" role="search"
accesskey="/" placeholder="Fuzzy Search" [formControl]="fuzzControl"
(input)="updateURL()"/>
</div>
<tp-generic-table
[data]="servers | async"
diff --git
a/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.scss
b/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.scss
index 0a24e3195e..87e81b002c 100644
---
a/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.scss
+++
b/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.scss
@@ -11,12 +11,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-mat-card {
+.mat-mdc-card {
margin: 1em auto;
width: 95%;
min-width: 350px;
- mat-card-content {
+ .mat-mdc-card-content {
display: grid;
grid-template-columns: 1fr;
row-gap: 2em;
diff --git
a/experimental/traffic-portal/src/app/core/statuses/statuses-table/statuses-table.component.html
b/experimental/traffic-portal/src/app/core/statuses/statuses-table/statuses-table.component.html
index 0dabb5c1ee..014b69904d 100644
---
a/experimental/traffic-portal/src/app/core/statuses/statuses-table/statuses-table.component.html
+++
b/experimental/traffic-portal/src/app/core/statuses/statuses-table/statuses-table.component.html
@@ -13,7 +13,7 @@ limitations under the License.
-->
<mat-card class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Search
Statuses" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Search
Statuses" inputmode="search" role="search" accesskey="/" placeholder="Fuzzy
Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="statuses | async"
diff --git
a/experimental/traffic-portal/src/app/core/types/table/types-table.component.html
b/experimental/traffic-portal/src/app/core/types/table/types-table.component.html
index ce93df88bf..37eb3064e8 100644
---
a/experimental/traffic-portal/src/app/core/types/table/types-table.component.html
+++
b/experimental/traffic-portal/src/app/core/types/table/types-table.component.html
@@ -14,7 +14,7 @@ limitations under the License.
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Types" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [formControl]="fuzzControl" (input)="updateURL()" />
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Types" inputmode="search" role="search" accesskey="/" placeholder="Fuzzy
Search" [formControl]="fuzzControl" (input)="updateURL()" />
</div>
<tp-generic-table
[data]="types | async"
diff --git
a/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.html
b/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.html
index 40978e7ed5..372cd4fcdb 100644
---
a/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.html
+++
b/experimental/traffic-portal/src/app/core/users/tenants/tenants.component.html
@@ -13,7 +13,7 @@ limitations under the License.
-->
<mat-card appearance="outlined" class="table-page-content">
<div class="search-container">
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Tenants" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [(ngModel)]="searchText" (input)="updateURL()"/>
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Tenants" inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [(ngModel)]="searchText" (input)="updateURL()"/>
</div>
<tp-generic-table
[data]="tenants"
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 34c4a6f5f1..ec79e75eb4 100644
--- a/experimental/traffic-portal/src/app/core/users/users.component.html
+++ b/experimental/traffic-portal/src/app/core/users/users.component.html
@@ -13,7 +13,7 @@ limitations under the License.
-->
<mat-card appearance="outlined">
<div>
- <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Users" autofocus inputmode="search" role="search" accesskey="/"
placeholder="Fuzzy Search" [(ngModel)]="searchText" (input)="updateURL()"/>
+ <input type="search" name="fuzzControl" aria-label="Fuzzy
Search Users" inputmode="search" role="search" accesskey="/" placeholder="Fuzzy
Search" [(ngModel)]="searchText" (input)="updateURL()"/>
</div>
<tp-generic-table
[data]="users"
diff --git a/experimental/traffic-portal/src/app/login/login.component.html
b/experimental/traffic-portal/src/app/login/login.component.html
index 58b3f3360a..b928349559 100644
--- a/experimental/traffic-portal/src/app/login/login.component.html
+++ b/experimental/traffic-portal/src/app/login/login.component.html
@@ -20,7 +20,7 @@ limitations under the License.
<mat-card-content>
<mat-form-field appearance="fill">
<mat-label>Username</mat-label>
- <input matInput required autofocus type="text"
[(ngModel)]="u" name="u"/>
+ <input matInput required type="text"
[(ngModel)]="u" name="u"/>
</mat-form-field>
<mat-form-field appearance="fill">
<mat-label>Password</mat-label>