This is an automated email from the ASF dual-hosted git repository.
ocket8888 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 5508e64770 Added ASN details for TPV2 (#7407)
5508e64770 is described below
commit 5508e64770af4df08ba0f59e25a89da6ad1531d1
Author: Rima Shah <[email protected]>
AuthorDate: Wed Mar 22 12:54:58 2023 -0600
Added ASN details for TPV2 (#7407)
* Created asn folder along with table and detail folder & files.
* Removed table folder & files and added code to detail folder.
* Added code for asn details
* Fix go lint
* Fix E2E tests..
* Update based on review comment.
* Updated comment
---
.../traffic-portal/nightwatch/globals/globals.ts | 2 +
.../page_objects/cacheGroups/asnDetail.ts | 43 ++++++++
.../tests/cacheGroups/asns/detail.spec.ts | 44 +++++++++
.../src/app/api/cache-group.service.ts | 34 +++++--
.../src/app/api/testing/cache-group.service.ts | 44 ++++++++-
.../asns/detail/asn-detail.component.html | 42 ++++++++
.../asns/detail/asn-detail.component.scss | 26 +++++
.../asns/detail/asn-detail.component.spec.ts | 77 +++++++++++++++
.../asns/detail/asn-detail.component.ts | 110 +++++++++++++++++++++
.../cache-group-table.component.ts | 2 +-
.../traffic-portal/src/app/core/core.module.ts | 3 +
11 files changed, 415 insertions(+), 12 deletions(-)
diff --git a/experimental/traffic-portal/nightwatch/globals/globals.ts
b/experimental/traffic-portal/nightwatch/globals/globals.ts
index 73b4df34a3..75f22f4a9f 100644
--- a/experimental/traffic-portal/nightwatch/globals/globals.ts
+++ b/experimental/traffic-portal/nightwatch/globals/globals.ts
@@ -16,6 +16,7 @@ import * as https from "https";
import axios, { AxiosError } from "axios";
import { NightwatchBrowser } from "nightwatch";
+import type { AsnDetailPageObject } from
"nightwatch/page_objects/cacheGroups/asnDetail";
import type { AsnsPageObject } from
"nightwatch/page_objects/cacheGroups/asnsTable";
import type { CacheGroupDetailPageObject } from
"nightwatch/page_objects/cacheGroups/cacheGroupDetails";
import type { CacheGroupsPageObject } from
"nightwatch/page_objects/cacheGroups/cacheGroupsTable";
@@ -84,6 +85,7 @@ declare module "nightwatch" {
regionDetail: () => RegionDetailPageObject;
regionsTable: () => RegionsPageObject;
asnsTable: () => AsnsPageObject;
+ asnDetail: () => AsnDetailPageObject;
};
deliveryServices: {
deliveryServiceCard: () =>
DeliveryServiceCardPageObject;
diff --git
a/experimental/traffic-portal/nightwatch/page_objects/cacheGroups/asnDetail.ts
b/experimental/traffic-portal/nightwatch/page_objects/cacheGroups/asnDetail.ts
new file mode 100644
index 0000000000..5ed7516fa0
--- /dev/null
+++
b/experimental/traffic-portal/nightwatch/page_objects/cacheGroups/asnDetail.ts
@@ -0,0 +1,43 @@
+/*
+ * 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 ASN Details.
+ */
+export type AsnDetailPageObject = EnhancedPageObject<{}, typeof
asnDetailPageObject.elements>;
+
+const asnDetailPageObject = {
+ elements: {
+ asn: {
+ selector: "input[name='asn']"
+ },
+ cachegroup: {
+ selector: "mat-select[name='cachegroup']"
+ },
+ id: {
+ selector: "input[name='id']"
+ },
+ lastUpdated: {
+ selector: "input[name='lastUpdated']"
+ },
+
+ saveBtn: {
+ selector: "button[type='submit']"
+ }
+ },
+};
+
+export default asnDetailPageObject;
diff --git
a/experimental/traffic-portal/nightwatch/tests/cacheGroups/asns/detail.spec.ts
b/experimental/traffic-portal/nightwatch/tests/cacheGroups/asns/detail.spec.ts
new file mode 100644
index 0000000000..aa1f6a20bf
--- /dev/null
+++
b/experimental/traffic-portal/nightwatch/tests/cacheGroups/asns/detail.spec.ts
@@ -0,0 +1,44 @@
+/*
+ * 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("Asn Detail Spec", () => {
+ it("Test Asn", () => {
+ const page = browser.page.cacheGroups.asnDetail();
+
browser.url(`${page.api.launchUrl}/core/asns/${browser.globals.testData.asn.id}`,
res => {
+ browser.assert.ok(res.status === 0);
+ page.waitForElementVisible("mat-card")
+ .assert.enabled("@asn")
+ .assert.enabled("@cachegroup")
+ .assert.enabled("@saveBtn")
+ .assert.not.enabled("@id")
+ .assert.not.enabled("@lastUpdated")
+ .assert.valueEquals("@asn",
String(browser.globals.testData.asn.asn))
+ .assert.valueEquals("@id",
String(browser.globals.testData.asn.id));
+ });
+ });
+
+ it("New asn", () => {
+ const page = browser.page.cacheGroups.asnDetail();
+ browser.url(`${page.api.launchUrl}/core/asns/new`, res => {
+ browser.assert.ok(res.status === 0);
+ page.waitForElementVisible("mat-card")
+ .assert.enabled("@asn")
+ .assert.enabled("@cachegroup")
+ .assert.enabled("@saveBtn")
+ .assert.not.elementPresent("@id")
+ .assert.not.elementPresent("@lastUpdated")
+ .assert.valueEquals("@asn", "1");
+ });
+ });
+});
diff --git a/experimental/traffic-portal/src/app/api/cache-group.service.ts
b/experimental/traffic-portal/src/app/api/cache-group.service.ts
index 632e58ae21..21d903f45d 100644
--- a/experimental/traffic-portal/src/app/api/cache-group.service.ts
+++ b/experimental/traffic-portal/src/app/api/cache-group.service.ts
@@ -14,6 +14,7 @@
import { HttpClient } from "@angular/common/http";
import { Injectable } from "@angular/core";
import type {
+ RequestASN,
ResponseASN,
RequestDivision,
ResponseDivision,
@@ -428,14 +429,35 @@ export class CacheGroupService extends APIService {
}
/**
- * Deletes an existing asn.
+ * Replaces the current definition of a ASN with the one given.
*
- * @param asnOrId Id of the asn to delete.
- * @returns The deleted asn.
+ * @param asn The new ASN.
+ * @returns The updated ASN.
*/
- public async deleteASN(asnOrId: number | ResponseASN): Promise<void> {
- const id = typeof(asnOrId) === "number" ? asnOrId : asnOrId.id;
- await this.delete("asns/", undefined, { id : String(id)
}).toPromise();
+ public async updateASN(asn: ResponseASN): Promise<ResponseASN> {
+ const path = `asns/${asn.id}`;
+ return this.put<ResponseASN>(path, asn).toPromise();
+ }
+
+ /**
+ * Creates a new ASN.
+ *
+ * @param asn The ASN to create.
+ * @returns The created ASN.
+ */
+ public async createASN(asn: RequestASN): Promise<ResponseASN> {
+ return this.post<ResponseASN>("asns", asn).toPromise();
+ }
+
+ /**
+ * Deletes an existing ASN.
+ *
+ * @param asn The ASN to be deleted or ID of the ASN to delete.
+ * @returns The deleted ASN.
+ */
+ public async deleteASN(asn: ResponseASN | number): Promise<void> {
+ const id = typeof(asn) === "number" ? asn : asn.id;
+ return this.delete(`asns/${id}`).toPromise();
}
constructor(http: HttpClient) {
diff --git
a/experimental/traffic-portal/src/app/api/testing/cache-group.service.ts
b/experimental/traffic-portal/src/app/api/testing/cache-group.service.ts
index a87c0a06a0..fbe62ee514 100644
--- a/experimental/traffic-portal/src/app/api/testing/cache-group.service.ts
+++ b/experimental/traffic-portal/src/app/api/testing/cache-group.service.ts
@@ -16,13 +16,14 @@ import type {
CacheGroupQueueRequest,
CacheGroupQueueResponse,
CDN,
+ RequestASN,
+ ResponseASN,
RequestCacheGroup,
+ ResponseCacheGroup,
RequestCoordinate,
+ ResponseCoordinate,
RequestDivision,
RequestRegion,
- ResponseASN,
- ResponseCacheGroup,
- ResponseCoordinate,
ResponseDivision,
ResponseRegion,
} from "trafficops-types";
@@ -698,13 +699,46 @@ export class CacheGroupService {
return this.asns;
}
+ /**
+ * Replaces the current definition of a ASN with the one given.
+ *
+ * @param asn The new ASN.
+ * @returns The updated ASN.
+ */
+ public async updateASN(asn: ResponseASN): Promise<ResponseASN> {
+ const id = this.asns.findIndex(a => a.id === asn.id);
+ if (id === -1) {
+ throw new Error(`no such ASN: ${asn.id}`);
+ }
+ this.asns[id] = asn;
+ return asn;
+ }
+
+ /**
+ * Creates a new ASN.
+ *
+ * @param asn The ASN to create.
+ * @returns The created ASN.
+ */
+ public async createASN(asn: RequestASN): Promise<ResponseASN> {
+ const sn = {
+ ...asn,
+ cachegroup: this.cacheGroups.find(cg => cg.id ===
asn.cachegroupId)?.name ?? "",
+ cachegroupId: asn.cachegroupId,
+ id: ++this.lastID,
+ lastUpdated: new Date()
+ };
+ this.asns.push(sn);
+ return sn;
+ }
+
/**
* Deletes an existing asn.
*
- * @param asn Id of the asn to delete.
+ * @param asn The ASN to be deleted or ID of the ASN to delete..
* @returns The deleted asn.
*/
- public async deleteASN(asn: number | ResponseASN): Promise<ResponseASN>
{
+ public async deleteASN(asn: ResponseASN | number): Promise<ResponseASN>
{
const index = this.asns.findIndex(a => a.asn === asn);
if (index === -1) {
throw new Error(`no such asn: ${asn}`);
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
new file mode 100644
index 0000000000..64565c879b
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.html
@@ -0,0 +1,42 @@
+<!--
+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="!asn"></tp-loading>
+ <form ngNativeValidate (ngSubmit)="submit($event)" *ngIf="asn">
+ <mat-card-content>
+ <mat-form-field *ngIf="!new">
+ <mat-label>ID</mat-label>
+ <input matInput type="text" name="id" disabled
readonly [defaultValue]="asn.id" />
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>ASN</mat-label>
+ <input matInput type="number" name="asn"
required [(ngModel)]="asn.asn" />
+ </mat-form-field>
+ <mat-form-field>
+ <mat-label>Cache Group</mat-label>
+ <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>
+ <mat-form-field *ngIf="!new">
+ <mat-label>Last Updated</mat-label>
+ <input matInput type="text" name="lastUpdated"
disabled readonly [defaultValue]="asn.lastUpdated" /> </mat-form-field>
+ </mat-card-content>
+ <mat-card-actions align="end">
+ <button mat-raised-button type="button" *ngIf="!new"
color="warn" (click)="deleteAsn()">Delete</button>
+ <button mat-raised-button type="submit"
color="primary">Save</button>
+ </mat-card-actions>
+ </form>
+</mat-card>
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.scss
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.scss
new file mode 100644
index 0000000000..fdfbde7654
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.scss
@@ -0,0 +1,26 @@
+/*
+* 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 {
+ margin: 1em auto;
+ width: 80%;
+ min-width: 350px;
+
+ mat-card-content {
+ display: grid;
+ grid-template-columns: 1fr;
+ row-gap: 2em;
+ margin: 1em auto 50px;
+ }
+}
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.spec.ts
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.spec.ts
new file mode 100644
index 0000000000..10f2a3492a
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.spec.ts
@@ -0,0 +1,77 @@
+/*
+* 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 { APITestingModule } from "src/app/api/testing";
+import { AsnDetailComponent } from
"src/app/core/cache-groups/asns/detail/asn-detail.component";
+import { NavigationService } from
"src/app/shared/navigation/navigation.service";
+
+describe("AsnDetailComponent", () => {
+ let component: AsnDetailComponent;
+ let fixture: ComponentFixture<AsnDetailComponent>;
+ let route: ActivatedRoute;
+ let paramMap: jasmine.Spy;
+
+ const headerSvc = jasmine.createSpyObj([],{headerHidden: new
ReplaySubject<boolean>(), headerTitle: new ReplaySubject<string>()});
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ AsnDetailComponent ],
+ imports: [ APITestingModule, RouterTestingModule,
MatDialogModule ],
+ providers: [ { provide: NavigationService, useValue:
headerSvc } ]
+ })
+ .compileComponents();
+
+ route = TestBed.inject(ActivatedRoute);
+ paramMap = spyOn(route.snapshot.paramMap, "get");
+ paramMap.and.returnValue(null);
+ fixture = TestBed.createComponent(AsnDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it("should create", () => {
+ expect(component).toBeTruthy();
+ expect(paramMap).toHaveBeenCalled();
+ });
+
+ it("new asn", async () => {
+ paramMap.and.returnValue("new");
+
+ fixture = TestBed.createComponent(AsnDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(paramMap).toHaveBeenCalled();
+ expect(component.asn).not.toBeNull();
+ expect(component.asn.asn).toBe(1);
+ expect(component.new).toBeTrue();
+ });
+
+ it("existing asn", async () => {
+ paramMap.and.returnValue("1");
+
+ fixture = TestBed.createComponent(AsnDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ await fixture.whenStable();
+ expect(paramMap).toHaveBeenCalled();
+ expect(component.asn).not.toBeNull();
+ expect(component.asn.asn).toBe(0);
+ expect(component.new).toBeFalse();
+ });
+});
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.ts
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.ts
new file mode 100644
index 0000000000..cb06437296
--- /dev/null
+++
b/experimental/traffic-portal/src/app/core/cache-groups/asns/detail/asn-detail.component.ts
@@ -0,0 +1,110 @@
+/*
+* 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 { Location } from "@angular/common";
+import { Component, OnInit } from "@angular/core";
+import { MatDialog } from "@angular/material/dialog";
+import { ActivatedRoute } from "@angular/router";
+import { ResponseASN, ResponseCacheGroup } from "trafficops-types";
+
+import { CacheGroupService } 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";
+
+/**
+ * AsnDetailComponent is the controller for the ASN add/edit form.
+ */
+@Component({
+ selector: "tp-asn-detail",
+ styleUrls: ["./asn-detail.component.scss"],
+ templateUrl: "./asn-detail.component.html"
+})
+export class AsnDetailComponent implements OnInit {
+ public new = false;
+ public asn!: ResponseASN;
+ public cachegroups!: Array<ResponseCacheGroup>;
+ constructor(private readonly route: ActivatedRoute, private readonly
cacheGroupService: CacheGroupService,
+ private readonly location: Location, private readonly dialog:
MatDialog,
+ private readonly header: NavigationService) {
+ }
+
+ /**
+ * Angular lifecycle hook where data is initialized.
+ */
+ public async ngOnInit(): Promise<void> {
+ this.cachegroups = await
this.cacheGroupService.getCacheGroups();
+ const ID = this.route.snapshot.paramMap.get("id");
+ if (ID === null) {
+ console.error("missing required route parameter 'id'");
+ return;
+ }
+
+ if (ID === "new") {
+ this.header.headerTitle.next("New ASN");
+ this.new = true;
+ this.asn = {
+ asn: 1,
+ cachegroup: "test",
+ cachegroupId: 1,
+ id: 1,
+ lastUpdated: new Date()
+ };
+ return;
+ }
+ const numID = parseInt(ID, 10);
+ if (Number.isNaN(numID)) {
+ console.error("route parameter 'id' was non-number:",
ID);
+ return;
+ }
+
+ this.asn = await this.cacheGroupService.getASNs(numID);
+ this.header.headerTitle.next(`ASN: ${this.asn.asn}`);
+ }
+
+ /**
+ * Deletes the current ASN.
+ */
+ public async deleteAsn(): Promise<void> {
+ if (this.new) {
+ console.error("Unable to delete new ASN");
+ return;
+ }
+ const ref = this.dialog.open(DecisionDialogComponent, {
+ data: {message: `Are you sure you want to delete ASN
${this.asn.asn} with id ${this.asn.id}`,
+ title: "Confirm Delete"}
+ });
+ ref.afterClosed().subscribe(result => {
+ if(result) {
+ this.cacheGroupService.deleteASN(this.asn.id);
+ this.location.back();
+ }
+ });
+ }
+
+ /**
+ * Submits new/updated ASN.
+ *
+ * @param e HTML form submission event.
+ */
+ public async submit(e: Event): Promise<void> {
+ e.preventDefault();
+ e.stopPropagation();
+ if(this.new) {
+ this.asn = await
this.cacheGroupService.createASN(this.asn);
+ this.new = false;
+ } else {
+ this.asn = await
this.cacheGroupService.updateASN(this.asn);
+ }
+ }
+
+}
diff --git
a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.ts
b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.ts
index 3c91deffd5..cadd191ca0 100644
---
a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.ts
+++
b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-table/cache-group-table.component.ts
@@ -15,7 +15,7 @@
import { Component, type OnInit } from "@angular/core";
import { FormControl } from "@angular/forms";
import { MatDialog } from "@angular/material/dialog";
-import {ActivatedRoute, type Params } from "@angular/router";
+import { ActivatedRoute, type Params } from "@angular/router";
import type { ColDef } from "ag-grid-community";
import { BehaviorSubject } from "rxjs";
import {
diff --git a/experimental/traffic-portal/src/app/core/core.module.ts
b/experimental/traffic-portal/src/app/core/core.module.ts
index 3a37cac3fe..a599834723 100644
--- a/experimental/traffic-portal/src/app/core/core.module.ts
+++ b/experimental/traffic-portal/src/app/core/core.module.ts
@@ -24,6 +24,7 @@ import { AppUIModule } from "../app.ui.module";
import { AuthenticatedGuard } from "../guards/authenticated-guard.service";
import { SharedModule } from "../shared/shared.module";
+import { AsnDetailComponent } from
"./cache-groups/asns/detail/asn-detail.component";
import { AsnsTableComponent } from
"./cache-groups/asns/table/asns-table.component";
import { CacheGroupDetailsComponent } from
"./cache-groups/cache-group-details/cache-group-details.component";
import { CacheGroupTableComponent } from
"./cache-groups/cache-group-table/cache-group-table.component";
@@ -60,6 +61,7 @@ import { UsersComponent } from "./users/users.component";
export const ROUTES: Routes = [
{ component: DashboardComponent, path: "" },
+ { component: AsnDetailComponent, path: "asns/:id"},
{ component: AsnsTableComponent, path: "asns" },
{ component: DivisionsTableComponent, path: "divisions" },
{ component: DivisionDetailComponent, path: "divisions/:id" },
@@ -114,6 +116,7 @@ export const ROUTES: Routes = [
PhysLocTableComponent,
PhysLocDetailComponent,
AsnsTableComponent,
+ AsnDetailComponent,
DivisionsTableComponent,
DivisionDetailComponent,
RegionsTableComponent,