diff --git a/CHANGELOG.md b/CHANGELOG.md index d817920ced..069967854a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - [#7872](https://github.com/apache/trafficcontrol/issues/7872) *Traffic Router*: Updated Apache Tomcat from 9.0.43, 9.0.67, 9.0.83, and 9.0.86 to 9.0.87. ### Fixed +- [#7998](https://github.com/apache/trafficcontrol/pull/7998) *Traffic Portalv2* Fixed (create and update) page titles across every feature - [#7984](https://github.com/apache/trafficcontrol/pull/7984) *Traffic Ops* Fixed TO Client cert authentication with respect to returning response cookie. - [#7957](https://github.com/apache/trafficcontrol/pull/7957) *Traffic Ops* Fix the incorrect display of delivery services assigned to ORG servers. - [#7917](https://github.com/apache/trafficcontrol/pull/7917) *Traffic Ops* Removed `Alerts` field from struct `ProfileExportResponse`. diff --git a/experimental/traffic-portal/src/app/api/origin.service.ts b/experimental/traffic-portal/src/app/api/origin.service.ts index d38142253f..76816af3d8 100644 --- a/experimental/traffic-portal/src/app/api/origin.service.ts +++ b/experimental/traffic-portal/src/app/api/origin.service.ts @@ -79,9 +79,7 @@ export class OriginService extends APIService { * @param originOrId The ID of the Origin to delete. * @returns The deleted Origin. */ - public async deleteOrigin( - originOrId: number | RequestOriginResponse - ): Promise { + public async deleteOrigin(originOrId: number | RequestOriginResponse): Promise { const id = typeof originOrId === "number" ? originOrId : originOrId.id; return this.delete(`origins?id=${id}`).toPromise(); } @@ -92,9 +90,7 @@ export class OriginService extends APIService { * @param origin The Origin to create. * @returns The created Origin. */ - public async createOrigin( - origin: RequestOrigin - ): Promise { + public async createOrigin(origin: RequestOrigin): Promise { return this.post("origins", origin).toPromise(); } 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 3ee7189db3..7d0f9c00bf 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 @@ -16,7 +16,7 @@
- + ID @@ -27,7 +27,7 @@ Cache Group - {{cachegroup.name}} + {{cachegroup.name}} @@ -35,12 +35,12 @@ - + Last Updated - +
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 index 278140921a..0226b6f16a 100644 --- 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 @@ -58,8 +58,8 @@ describe("AsnDetailComponent", () => { await fixture.whenStable(); expect(paramMap).toHaveBeenCalled(); expect(component.asn).not.toBeNull(); - expect(component.asn.asn).toBe(1); - expect(component.new).toBeTrue(); + expect(component.asn.asn).toBe(0); + expect(component.isNew).toBeTrue(); }); it("existing asn", async () => { @@ -72,6 +72,6 @@ describe("AsnDetailComponent", () => { expect(paramMap).toHaveBeenCalled(); expect(component.asn).not.toBeNull(); expect(component.asn.asn).toBe(0); - expect(component.new).toBeFalse(); + expect(component.isNew).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 index cc80a543f7..905ab4f523 100644 --- 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 @@ -11,10 +11,10 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponseASN, ResponseCacheGroup } from "trafficops-types"; import { CacheGroupService } from "src/app/api"; @@ -31,16 +31,16 @@ import { NavigationService } from "src/app/shared/navigation/navigation.service" templateUrl: "./asn-detail.component.html" }) export class ASNDetailComponent implements OnInit { - public new = false; + public isNew = false; public asn!: ResponseASN; - public cachegroups!: Array; + public cacheGroups = new Array(); constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly cacheGroupService: CacheGroupService, - private readonly location: Location, private readonly dialog: MatDialog, - private readonly header: NavigationService, + private readonly navSvc: NavigationService, private readonly log: LoggingService, ) { } @@ -49,21 +49,28 @@ export class ASNDetailComponent implements OnInit { * Angular lifecycle hook where data is initialized. */ public async ngOnInit(): Promise { - this.cachegroups = await this.cacheGroupService.getCacheGroups(); + this.cacheGroupService.getCacheGroups().then( + cgs => { + this.cacheGroups = cgs; + } + ); + const ID = this.route.snapshot.paramMap.get("id"); if (ID === null) { this.log.error("missing required route parameter 'id'"); return; } - if (ID === "new") { - this.header.headerTitle.next("New ASN"); - this.new = true; + this.isNew = ID === "new"; + + if (this.isNew) { + this.setTitle(); + this.isNew = true; this.asn = { - asn: 1, - cachegroup: "test", - cachegroupId: 1, - id: 1, + asn: 0, + cachegroup: "", + cachegroupId: 0, + id: 0, lastUpdated: new Date() }; return; @@ -75,17 +82,31 @@ export class ASNDetailComponent implements OnInit { } this.asn = await this.cacheGroupService.getASNs(numID); - this.header.headerTitle.next(`ASN: ${this.asn.asn}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current ASN state. + * + * @private + */ + private setTitle(): void { + const title = this.isNew ? "New ASN" : `ASN: ${this.asn.asn}`; + this.navSvc.headerTitle.next(title); } /** * Deletes the current ASN. */ public async deleteAsn(): Promise { - if (this.new) { + if (this.isNew) { this.log.error("Unable to delete new ASN"); return; } + if(!this.asn.asn) { + this.log.error("Missing ASN number"); + 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"} @@ -93,7 +114,7 @@ export class ASNDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.cacheGroupService.deleteASN(this.asn.id); - this.location.back(); + this.router.navigate(["core/asns"]); } }); } @@ -106,12 +127,14 @@ export class ASNDetailComponent implements OnInit { public async submit(e: Event): Promise { e.preventDefault(); e.stopPropagation(); - if(this.new) { + if(this.isNew) { this.asn = await this.cacheGroupService.createASN(this.asn); - this.new = false; + this.isNew = false; + await this.router.navigate(["core/asns", this.asn.id]); } else { this.asn = await this.cacheGroupService.updateASN(this.asn); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.ts b/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.ts index 1ab0c37145..a52d290c34 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/asns/table/asns-table.component.ts @@ -44,7 +44,7 @@ export class ASNsTableComponent implements OnInit { constructor( private readonly route: ActivatedRoute, - private readonly headerSvc: NavigationService, + private readonly navSvc: NavigationService, private readonly api: CacheGroupService, private readonly dialog: MatDialog, public readonly auth: CurrentUserService, @@ -52,7 +52,7 @@ export class ASNsTableComponent implements OnInit { ) { this.fuzzySubject = new BehaviorSubject(""); this.asns = this.api.getASNs(); - this.headerSvc.headerTitle.next("ASNs"); + this.navSvc.headerTitle.next("ASNs"); } /** Initializes table data, loading it from Traffic Ops. */ diff --git a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.spec.ts b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.spec.ts index a8a1d14546..c7f0074512 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.spec.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.spec.ts @@ -44,18 +44,11 @@ describe("CacheGroupDetailsComponent", () => { imports: [ APITestingModule, RouterTestingModule.withRoutes([ - { - component: CacheGroupDetailsComponent, - path: "" - }, - { - component: CacheGroupDetailsComponent, - path: "cache-groups/:id" - } + {component: CacheGroupDetailsComponent, path: "core/cache-groups/:id"}, + {component: CacheGroupDetailsComponent, path: "core/cache-groups"} ]), MatDialogModule, NoopAnimationsModule, - ], providers: [ { provide: NavigationService, useValue: navSvc } ] }).compileComponents(); diff --git a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.ts b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.ts index 7842b7b810..cf6ff4bb29 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/cache-group-details/cache-group-details.component.ts @@ -12,15 +12,14 @@ * limitations under the License. */ -import { Location } from "@angular/common"; -import { Component, type OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormControl } from "@angular/forms"; import { MatDialog } from "@angular/material/dialog"; -import { ActivatedRoute } from "@angular/router"; -import { LocalizationMethod, localizationMethodToString, TypeFromResponse, type ResponseCacheGroup } from "trafficops-types"; +import { ActivatedRoute, Router } from "@angular/router"; +import { LocalizationMethod, localizationMethodToString, TypeFromResponse, ResponseCacheGroup } from "trafficops-types"; import { CacheGroupService, TypeService } from "src/app/api"; -import { DecisionDialogComponent, type DecisionDialogData } from "src/app/shared/dialogs/decision-dialog/decision-dialog.component"; +import { DecisionDialogComponent, DecisionDialogData } from "src/app/shared/dialogs/decision-dialog/decision-dialog.component"; import { LoggingService } from "src/app/shared/logging.service"; import { NavigationService } from "src/app/shared/navigation/navigation.service"; @@ -69,9 +68,9 @@ export class CacheGroupDetailsComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly api: CacheGroupService, private readonly typesAPI: TypeService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -182,7 +181,7 @@ export class CacheGroupDetailsComponent implements OnInit { ref.afterClosed().subscribe(result => { if (result) { this.api.deleteCacheGroup(this.cacheGroup); - this.location.replaceState("core/cache-groups"); + this.router.navigate(["core/cache-groups"]); } }); } @@ -208,6 +207,7 @@ export class CacheGroupDetailsComponent implements OnInit { if (this.new) { this.cacheGroup = await this.api.createCacheGroup(this.cacheGroup); this.new = false; + await this.router.navigate(["core/cache-groups", this.cacheGroup.id]); } else { this.cacheGroup = await this.api.updateCacheGroup(this.cacheGroup); } diff --git a/experimental/traffic-portal/src/app/core/cache-groups/coordinates/detail/coordinate-detail.component.ts b/experimental/traffic-portal/src/app/core/cache-groups/coordinates/detail/coordinate-detail.component.ts index 91b46961a4..f52e371087 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/coordinates/detail/coordinate-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/coordinates/detail/coordinate-detail.component.ts @@ -12,10 +12,9 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponseCoordinate } from "trafficops-types"; import { CacheGroupService } from "src/app/api"; @@ -37,8 +36,8 @@ export class CoordinateDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly cacheGroupService: CacheGroupService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -54,8 +53,10 @@ export class CoordinateDetailComponent implements OnInit { return; } - if (ID === "new") { - this.navSvc.headerTitle.next("New Coordinate"); + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.coordinate = { id: -1, @@ -73,7 +74,17 @@ export class CoordinateDetailComponent implements OnInit { } this.coordinate = await this.cacheGroupService.getCoordinates(numID); - this.navSvc.headerTitle.next(`Coordinate: ${this.coordinate.name}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Coordinate state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Coordinate" : `Coordinate: ${this.coordinate.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -91,7 +102,7 @@ export class CoordinateDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.cacheGroupService.deleteCoordinate(this.coordinate.id); - this.location.back(); + this.router.navigate(["core/coordinates"]); } }); } @@ -107,9 +118,11 @@ export class CoordinateDetailComponent implements OnInit { if(this.new) { this.coordinate = await this.cacheGroupService.createCoordinate(this.coordinate); this.new = false; + await this.router.navigate(["core/coordinates", this.coordinate.id]); } else { this.coordinate = await this.cacheGroupService.updateCoordinate(this.coordinate); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/cache-groups/divisions/detail/division-detail.component.ts b/experimental/traffic-portal/src/app/core/cache-groups/divisions/detail/division-detail.component.ts index 4a9081e598..80ce54203e 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/divisions/detail/division-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/divisions/detail/division-detail.component.ts @@ -11,10 +11,10 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponseDivision } from "trafficops-types"; import { CacheGroupService } from "src/app/api"; @@ -35,8 +35,8 @@ export class DivisionDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly cacheGroupService: CacheGroupService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -52,8 +52,10 @@ export class DivisionDetailComponent implements OnInit { return; } - if (ID === "new") { - this.navSvc.headerTitle.next("New Division"); + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.division = { id: -1, @@ -69,7 +71,17 @@ export class DivisionDetailComponent implements OnInit { } this.division = await this.cacheGroupService.getDivisions(numID); - this.navSvc.headerTitle.next(`Division: ${this.division.name}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Division state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Division" : `Division: ${this.division.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -87,7 +99,7 @@ export class DivisionDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.cacheGroupService.deleteDivision(this.division.id); - this.location.back(); + this.router.navigate(["core/divisions"]); } }); } @@ -103,9 +115,11 @@ export class DivisionDetailComponent implements OnInit { if(this.new) { this.division = await this.cacheGroupService.createDivision(this.division); this.new = false; + await this.router.navigate(["core/divisions", this.division.id]); } else { this.division = await this.cacheGroupService.updateDivision(this.division); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/cache-groups/regions/detail/region-detail.component.ts b/experimental/traffic-portal/src/app/core/cache-groups/regions/detail/region-detail.component.ts index 042f7faddf..4a6323b297 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/regions/detail/region-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/regions/detail/region-detail.component.ts @@ -11,10 +11,10 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponseDivision, ResponseRegion } from "trafficops-types"; import { CacheGroupService } from "src/app/api"; @@ -37,10 +37,10 @@ export class RegionDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly cacheGroupService: CacheGroupService, - private readonly location: Location, private readonly dialog: MatDialog, - private readonly header: NavigationService, + private readonly navSvc: NavigationService, private readonly log: LoggingService, ) { } @@ -56,8 +56,10 @@ export class RegionDetailComponent implements OnInit { return; } - if (ID === "new") { - this.header.headerTitle.next("New Region"); + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.region = { division: -1, @@ -75,7 +77,17 @@ export class RegionDetailComponent implements OnInit { } this.region = await this.cacheGroupService.getRegions(numID); - this.header.headerTitle.next(`Region: ${this.region.name}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Region state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Region" : `Region: ${this.region.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -93,7 +105,7 @@ export class RegionDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.cacheGroupService.deleteRegion(this.region.id); - this.location.back(); + this.router.navigate(["core/regions"]); } }); } @@ -109,9 +121,11 @@ export class RegionDetailComponent implements OnInit { if(this.new) { this.region = await this.cacheGroupService.createRegion(this.region); this.new = false; + await this.router.navigate(["core/regions", this.region.id]); } else { this.region = await this.cacheGroupService.updateRegion(this.region); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.ts b/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.ts index e4e966f89d..005d899f1c 100644 --- a/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.ts +++ b/experimental/traffic-portal/src/app/core/cache-groups/regions/table/regions-table.component.ts @@ -12,10 +12,10 @@ * limitations under the License. */ -import { Component, type OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormControl } from "@angular/forms"; import { MatDialog } from "@angular/material/dialog"; -import { ActivatedRoute, type Params } from "@angular/router"; +import { ActivatedRoute, Params } from "@angular/router"; import { BehaviorSubject } from "rxjs"; import type { Region, ResponseRegion } from "trafficops-types"; @@ -44,7 +44,7 @@ export class RegionsTableComponent implements OnInit { constructor( private readonly route: ActivatedRoute, - private readonly headerSvc: NavigationService, + private readonly navSvc: NavigationService, private readonly api: CacheGroupService, private readonly dialog: MatDialog, public readonly auth: CurrentUserService, @@ -52,7 +52,7 @@ export class RegionsTableComponent implements OnInit { ) { this.fuzzySubject = new BehaviorSubject(""); this.regions = this.api.getRegions(); - this.headerSvc.headerTitle.next("Regions"); + this.navSvc.headerTitle.next("Regions"); } /** Initializes table data, loading it from Traffic Ops. */ diff --git a/experimental/traffic-portal/src/app/core/cdns/cdn-detail/cdn-detail.component.ts b/experimental/traffic-portal/src/app/core/cdns/cdn-detail/cdn-detail.component.ts index 53fe744732..cc09162c98 100644 --- a/experimental/traffic-portal/src/app/core/cdns/cdn-detail/cdn-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/cdns/cdn-detail/cdn-detail.component.ts @@ -12,10 +12,9 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponseCDN } from "trafficops-types"; import { CDNService } from "src/app/api"; @@ -48,8 +47,8 @@ export class CDNDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly api: CDNService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -84,6 +83,7 @@ export class CDNDetailComponent implements OnInit { return; } this.cdn = this.cdns.splice(index, 1)[0]; + this.setTitle(); } /** @@ -116,7 +116,7 @@ export class CDNDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if (result) { this.api.deleteCDN(this.cdn); - this.location.replaceState("core/cdns"); + this.router.navigate(["core/cdns"]); } }); } @@ -133,6 +133,7 @@ export class CDNDetailComponent implements OnInit { if (this.new) { this.cdn = await this.api.createCDN(this.cdn); this.new = false; + await this.router.navigate(["core/cdns", this.cdn.id]); } else { this.cdn = await this.api.updateCDN(this.cdn); } diff --git a/experimental/traffic-portal/src/app/core/cdns/cdn-table/cdn-table.component.ts b/experimental/traffic-portal/src/app/core/cdns/cdn-table/cdn-table.component.ts index 8bf05e0199..c64526b317 100644 --- a/experimental/traffic-portal/src/app/core/cdns/cdn-table/cdn-table.component.ts +++ b/experimental/traffic-portal/src/app/core/cdns/cdn-table/cdn-table.component.ts @@ -32,6 +32,7 @@ import type { DoubleClickLink } from "src/app/shared/generic-table/generic-table.component"; import { LoggingService } from "src/app/shared/logging.service"; +import { NavigationService } from "src/app/shared/navigation/navigation.service"; /** * CDNTableComponent is the controller for the "CDNs" table. @@ -163,12 +164,14 @@ export class CDNTableComponent implements OnInit { private readonly alerts: AlertService, private readonly api: CDNService, public readonly auth: CurrentUserService, + private readonly navSvc: NavigationService, private readonly dialog: MatDialog, private readonly route: ActivatedRoute, private readonly log: LoggingService, ) { this.fuzzySubject = new BehaviorSubject(""); this.cdns = this.api.getCDNs(); + this.navSvc.headerTitle.next("CDNs"); } /** Initializes table data, loading it from Traffic Ops. */ diff --git a/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.spec.ts b/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.spec.ts index 62501ed52b..f1c98e263f 100644 --- a/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.spec.ts +++ b/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.spec.ts @@ -44,7 +44,10 @@ describe("OriginDetailComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [OriginDetailComponent], - imports: [APITestingModule, RouterTestingModule, MatDialogModule, NoopAnimationsModule], + imports: [APITestingModule, RouterTestingModule.withRoutes( [ + {component: OriginDetailComponent, path: "core/origins/:id"}, + {component: OriginDetailComponent, path: "core/origins"}, + ]), MatDialogModule, NoopAnimationsModule], providers: [{provide: NavigationService, useValue: navSvc}], }).compileComponents(); diff --git a/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.ts b/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.ts index 1d9c64046a..c50724475b 100644 --- a/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/origins/detail/origin-detail.component.ts @@ -12,10 +12,9 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import type { RequestOrigin, RequestOriginResponse, @@ -57,8 +56,8 @@ export class OriginDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly originService: OriginService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -84,8 +83,11 @@ export class OriginDetailComponent implements OnInit { this.log.error("missing required route parameter 'id'"); return; } - if (ID === "new") { - this.navSvc.headerTitle.next("New Origin"); + + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.origin = { cachegroup: null, @@ -116,7 +118,17 @@ export class OriginDetailComponent implements OnInit { return; } this.origin = await this.originService.getOrigins(numID); - this.navSvc.headerTitle.next(`Origin: ${this.origin.name}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Origin state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Origin" : `Origin: ${this.origin.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -136,7 +148,7 @@ export class OriginDetailComponent implements OnInit { ref.afterClosed().subscribe((result) => { if (result) { this.originService.deleteOrigin(this.origin); - this.location.back(); + this.router.navigate(["core/origins"]); } }); } @@ -189,8 +201,10 @@ export class OriginDetailComponent implements OnInit { this.origin = await this.originService.createOrigin(requestOrigin); this.new = false; + await this.router.navigate(["core/origins", this.origin.id]); } else { this.origin = await this.originService.updateOrigin(this.origin); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/origins/table/origins-table.component.ts b/experimental/traffic-portal/src/app/core/origins/table/origins-table.component.ts index 984879aa6a..06653b9dce 100644 --- a/experimental/traffic-portal/src/app/core/origins/table/origins-table.component.ts +++ b/experimental/traffic-portal/src/app/core/origins/table/origins-table.component.ts @@ -12,7 +12,7 @@ * limitations under the License. */ -import { Component, type OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import { FormControl } from "@angular/forms"; import { MatDialog } from "@angular/material/dialog"; import { ActivatedRoute } from "@angular/router"; diff --git a/experimental/traffic-portal/src/app/core/parameters/detail/parameter-detail.component.ts b/experimental/traffic-portal/src/app/core/parameters/detail/parameter-detail.component.ts index a78d4e11a8..4c00e04a1f 100644 --- a/experimental/traffic-portal/src/app/core/parameters/detail/parameter-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/parameters/detail/parameter-detail.component.ts @@ -12,10 +12,9 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponseParameter } from "trafficops-types"; import { ProfileService } from "src/app/api"; @@ -41,8 +40,8 @@ export class ParameterDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly profileService: ProfileService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -58,8 +57,10 @@ export class ParameterDetailComponent implements OnInit { return; } - if (ID === "new") { - this.navSvc.headerTitle.next("New Parameter"); + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.parameter = { configFile: "", @@ -80,7 +81,17 @@ export class ParameterDetailComponent implements OnInit { } this.parameter = await this.profileService.getParameters(numID); - this.navSvc.headerTitle.next(`Parameter: ${this.parameter.name} (${this.parameter.id})`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Parameter state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Parameter" : `Parameter: ${this.parameter.name} (${this.parameter.id})`; + this.navSvc.headerTitle.next(title); } /** @@ -98,7 +109,7 @@ export class ParameterDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.profileService.deleteParameter(this.parameter.id); - this.location.back(); + this.router.navigate(["core/parameters"]); } }); } @@ -114,8 +125,10 @@ export class ParameterDetailComponent implements OnInit { if(this.new) { this.parameter = await this.profileService.createParameter(this.parameter); this.new = false; + await this.router.navigate(["core/parameters", this.parameter.id]); } else { this.parameter = await this.profileService.updateParameter(this.parameter); } + this.setTitle(); } } 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 index cfc4381399..413fb08496 100644 --- 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 @@ -84,7 +84,7 @@ export class ProfileDetailComponent implements OnInit { 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.setTitle(); } this.loading = false; } else { @@ -104,6 +104,16 @@ export class ProfileDetailComponent implements OnInit { } } + /** + * Sets the headerTitle based on current Profile state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Profile" : `Profile: ${this.profile.name}`; + this.navSvc.headerTitle.next(title); + } + /** * Submits new/updated profile. * @@ -115,9 +125,11 @@ export class ProfileDetailComponent implements OnInit { if(this.new) { this.profile = await this.api.createProfile(this.profile); this.new = false; + await this.router.navigate(["core/profiles", this.profile.id]); } else { this.profile = await this.api.updateProfile(this.profile); } + this.setTitle(); } /** diff --git a/experimental/traffic-portal/src/app/core/servers/capabilities/capabilities.component.ts b/experimental/traffic-portal/src/app/core/servers/capabilities/capabilities.component.ts index 88a0e30aa5..ca724f5f88 100644 --- a/experimental/traffic-portal/src/app/core/servers/capabilities/capabilities.component.ts +++ b/experimental/traffic-portal/src/app/core/servers/capabilities/capabilities.component.ts @@ -11,7 +11,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Component, type OnInit } from "@angular/core"; + +import { Component, OnInit } from "@angular/core"; import { FormControl } from "@angular/forms"; import { MatDialog } from "@angular/material/dialog"; import { ActivatedRoute } from "@angular/router"; diff --git a/experimental/traffic-portal/src/app/core/servers/capabilities/capability-details/capability-details.component.ts b/experimental/traffic-portal/src/app/core/servers/capabilities/capability-details/capability-details.component.ts index 0d8cd1a912..a57c97067e 100644 --- a/experimental/traffic-portal/src/app/core/servers/capabilities/capability-details/capability-details.component.ts +++ b/experimental/traffic-portal/src/app/core/servers/capabilities/capability-details/capability-details.component.ts @@ -11,7 +11,7 @@ * 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, Router } from "@angular/router"; @@ -46,7 +46,6 @@ export class CapabilityDetailsComponent implements OnInit { private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly api: ServerService, - private readonly location: Location ) {} /** @@ -93,7 +92,7 @@ export class CapabilityDetailsComponent implements OnInit { const result = await ref.afterClosed().toPromise(); if(result) { await this.api.deleteCapability(this.capability); - this.location.back(); + await this.router.navigate(["core/capabilities"]); } } @@ -108,10 +107,11 @@ export class CapabilityDetailsComponent implements OnInit { if(this.new) { this.capability = await this.api.createCapability(this.capability); this.new = false; + this.setHeader("New Capability"); } else { this.capability = await this.api.updateCapability(this.name, this.capability); + this.navSvc.headerTitle.next(`Capability: ${this.capability.name}`); } this.router.navigate([`/core/capabilities/${this.capability.name}`], {replaceUrl: true}); - this.setHeader(this.name); } } diff --git a/experimental/traffic-portal/src/app/core/servers/phys-loc/detail/phys-loc-detail.component.ts b/experimental/traffic-portal/src/app/core/servers/phys-loc/detail/phys-loc-detail.component.ts index 51b9461615..557e3778c6 100644 --- a/experimental/traffic-portal/src/app/core/servers/phys-loc/detail/phys-loc-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/servers/phys-loc/detail/phys-loc-detail.component.ts @@ -11,10 +11,10 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { ResponsePhysicalLocation, ResponseRegion } from "trafficops-types"; import { CacheGroupService, PhysicalLocationService } from "src/app/api"; @@ -38,8 +38,8 @@ export class PhysLocDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly cacheGroupService: CacheGroupService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly physLocService: PhysicalLocationService, @@ -57,8 +57,10 @@ export class PhysLocDetailComponent implements OnInit { return; } - if (ID === "new") { - this.navSvc.headerTitle.next("New Physical Location"); + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.physLocation = { address: "", @@ -85,7 +87,17 @@ export class PhysLocDetailComponent implements OnInit { } this.physLocation = await this.physLocService.getPhysicalLocations(numID); - this.navSvc.headerTitle.next(`Physical Location: ${this.physLocation.name}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Physical Location state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Physical Location" : `Physical Location: ${this.physLocation.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -103,7 +115,7 @@ export class PhysLocDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.physLocService.deletePhysicalLocation(this.physLocation.id); - this.location.back(); + this.router.navigate(["core/phys-locs"]); } }); } @@ -119,8 +131,10 @@ export class PhysLocDetailComponent implements OnInit { if(this.new) { this.physLocation = await this.physLocService.createPhysicalLocation(this.physLocation); this.new = false; + await this.router.navigate(["core/phys-locs", this.physLocation.id]); } else { this.physLocation = await this.physLocService.updatePhysicalLocation(this.physLocation); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.ts b/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.ts index 9b1f25b139..b58fe73161 100644 --- a/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.ts +++ b/experimental/traffic-portal/src/app/core/servers/server-details/server-details.component.ts @@ -176,7 +176,7 @@ export class ServerDetailsComponent implements OnInit { this.serverService.getServers(Number(ID)).then( s => { this.server = s; - this.updateTitlebar(); + this.updateTitleBar(); } ).catch( e => { @@ -231,7 +231,7 @@ export class ServerDetailsComponent implements OnInit { updPending: false, xmppId: "" }; - this.updateTitlebar(); + this.updateTitleBar(); } } @@ -240,7 +240,7 @@ export class ServerDetailsComponent implements OnInit { * * @private */ - private updateTitlebar(): void { + private updateTitleBar(): void { if (this.isNew) { this.navSvc.headerTitle.next("New Server"); } else { @@ -265,6 +265,7 @@ export class ServerDetailsComponent implements OnInit { this.isNew = false; this.server = s; this.router.navigate(["server", s.id]); + this.updateTitleBar(); }, err => { this.log.error("failed to create server:", err); @@ -274,7 +275,7 @@ export class ServerDetailsComponent implements OnInit { this.serverService.updateServer(this.server).then( responseServer => { this.server = responseServer; - this.updateTitlebar(); + this.updateTitleBar(); }, err => { this.log.error(`failed to update server: ${err}`); diff --git a/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.ts b/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.ts index d1bea8b259..2f08850c81 100644 --- a/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.ts +++ b/experimental/traffic-portal/src/app/core/servers/servers-table/servers-table.component.ts @@ -12,13 +12,13 @@ * limitations under the License. */ -import { Component , type OnInit} from "@angular/core"; +import { Component , OnInit} from "@angular/core"; import { FormControl } from "@angular/forms"; import { MatDialog } from "@angular/material/dialog"; import { ActivatedRoute } from "@angular/router"; import type { ITooltipParams } from "ag-grid-community"; import { BehaviorSubject } from "rxjs"; -import { ResponseCDN, type ResponseServer, serviceAddresses } from "trafficops-types"; +import { ResponseCDN, ResponseServer, serviceAddresses } from "trafficops-types"; import { CDNService, ServerService } from "src/app/api"; import { UpdateStatusComponent } from "src/app/core/servers/update-status/update-status.component"; @@ -397,6 +397,7 @@ export class ServersTableComponent implements OnInit { switch(action) { case "dequeue": data.title = "Clear Server Updates"; + break; case "queue": const ref = this.dialog.open, number | false>( CollectionChoiceDialogComponent, diff --git a/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts b/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts index d071120d11..7f389111d6 100644 --- a/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts +++ b/experimental/traffic-portal/src/app/core/statuses/status-details/status-details.component.ts @@ -12,11 +12,10 @@ * limitations under the License. */ -import { Location } from "@angular/common"; import { Component } from "@angular/core"; import { FormControl, FormGroup } from "@angular/forms"; import { MatDialog } from "@angular/material/dialog"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { RequestStatus, ResponseStatus } from "trafficops-types"; import { ServerService } from "src/app/api"; @@ -52,9 +51,9 @@ export class StatusDetailsComponent { constructor( private readonly api: ServerService, private readonly route: ActivatedRoute, + private readonly router: Router, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, - private readonly location: Location, ) { // Getting id from the route const id = this.route.snapshot.paramMap.get("id"); @@ -82,7 +81,7 @@ export class StatusDetailsComponent { this.statusDetails = await this.api.getStatuses(Number(id)); // Set page title with status Name - this.navSvc.headerTitle.next(`Status ${this.statusDetails.name}`); + this.setTitle(); // Patch the form with existing data we got from service requested above. this.statusDetailsForm.setValue({ @@ -93,6 +92,16 @@ export class StatusDetailsComponent { this.loading = false; } + /** + * Sets the headerTitle based on current Status state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Status" : `Status: ${this.statusDetails.name}`; + this.navSvc.headerTitle.next(title); + } + /** * On submitting the form we check for whether we are performing Create or Edit * @@ -109,6 +118,8 @@ export class StatusDetailsComponent { name: this.statusDetailsForm.controls.name.value }; this.statusDetails = await this.api.createStatus(newData); + this.new = false; + await this.router.navigate(["core/statuses", this.statusDetails.id]); } else { const editData: ResponseStatus = { description: this.statusDetailsForm.controls.description.value, @@ -118,6 +129,7 @@ export class StatusDetailsComponent { }; this.statusDetails = await this.api.updateStatusDetail(editData); } + this.setTitle(); } } @@ -135,7 +147,7 @@ export class StatusDetailsComponent { ref.afterClosed().subscribe(result => { if (result) { this.api.deleteStatus(this.statusDetails.id); - this.location.back(); + this.router.navigate(["core/statuses"]); } }); } diff --git a/experimental/traffic-portal/src/app/core/topologies/topology-details/topology-details.component.ts b/experimental/traffic-portal/src/app/core/topologies/topology-details/topology-details.component.ts index de41c41781..8cf4e9a25f 100644 --- a/experimental/traffic-portal/src/app/core/topologies/topology-details/topology-details.component.ts +++ b/experimental/traffic-portal/src/app/core/topologies/topology-details/topology-details.component.ts @@ -11,12 +11,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { NestedTreeControl } from "@angular/cdk/tree"; -import { Location } from "@angular/common"; import { Component, OnInit } from "@angular/core"; import { MatDialog } from "@angular/material/dialog"; import { MatTreeNestedDataSource } from "@angular/material/tree"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import { ResponseTopology } from "trafficops-types"; import { TopologyService, TopTreeNode } from "src/app/api"; @@ -56,8 +56,8 @@ export class TopologyDetailsComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly api: TopologyService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -130,7 +130,7 @@ export class TopologyDetailsComponent implements OnInit { ref.afterClosed().subscribe(result => { if (result) { this.api.deleteTopology(this.topology); - this.location.replaceState("core/topologies"); + this.router.navigate(["core/topologies"]); } }); } @@ -149,6 +149,7 @@ export class TopologyDetailsComponent implements OnInit { if (this.new) { this.topology = await this.api.createTopology(this.topology); this.new = false; + await this.router.navigate(["core/topologies", this.topology.name]); } else { this.topology = await this.api.updateTopology(this.topology, this.oldName); } diff --git a/experimental/traffic-portal/src/app/core/types/detail/type-detail.component.ts b/experimental/traffic-portal/src/app/core/types/detail/type-detail.component.ts index ef1ec52507..7c52a8fe92 100644 --- a/experimental/traffic-portal/src/app/core/types/detail/type-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/types/detail/type-detail.component.ts @@ -12,10 +12,9 @@ * 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 { ActivatedRoute, Router } from "@angular/router"; import { TypeFromResponse } from "trafficops-types"; import { TypeService } from "src/app/api"; @@ -37,8 +36,8 @@ export class TypeDetailComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly typeService: TypeService, - private readonly location: Location, private readonly dialog: MatDialog, private readonly navSvc: NavigationService, private readonly log: LoggingService, @@ -54,8 +53,10 @@ export class TypeDetailComponent implements OnInit { return; } - if (ID === "new") { - this.navSvc.headerTitle.next("New Type"); + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.type = { description: "", @@ -74,7 +75,17 @@ export class TypeDetailComponent implements OnInit { } this.type = await this.typeService.getTypes(numID); - this.navSvc.headerTitle.next(`Type: ${this.type.name}`); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Type state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Type" : `Type: ${this.type.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -92,7 +103,7 @@ export class TypeDetailComponent implements OnInit { ref.afterClosed().subscribe(result => { if(result) { this.typeService.deleteType(this.type.id); - this.location.back(); + this.router.navigate(["core/types"]); } }); } @@ -108,9 +119,11 @@ export class TypeDetailComponent implements OnInit { if(this.new) { this.type = await this.typeService.createType(this.type); this.new = false; + await this.router.navigate(["core/types", this.type.id]); } else { this.type = await this.typeService.updateType(this.type); } + this.setTitle(); } } diff --git a/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts index c6203eeccf..e5179474d9 100644 --- a/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts +++ b/experimental/traffic-portal/src/app/core/users/roles/detail/role-detail.component.ts @@ -11,7 +11,7 @@ * 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, Router } from "@angular/router"; @@ -44,9 +44,8 @@ export class RoleDetailComponent implements OnInit { private readonly route: ActivatedRoute, private readonly router: Router, private readonly userService: UserService, - private readonly location: Location, private readonly dialog: MatDialog, - private readonly header: NavigationService, + private readonly navSvc: NavigationService, private readonly log: LoggingService, ) { } @@ -56,7 +55,7 @@ export class RoleDetailComponent implements OnInit { public async ngOnInit(): Promise { const role = this.route.snapshot.paramMap.get("name"); if (role === null) { - this.header.headerTitle.next("New Role"); + this.setHeader("New Role"); this.new = true; this.role = { description: "", @@ -69,7 +68,7 @@ export class RoleDetailComponent implements OnInit { this.role = await this.userService.getRoles(role); this.name = this.role.name; this.permissions = this.role.permissions?.join("\n")??""; - this.header.headerTitle.next(`Role: ${this.name}`); + this.navSvc.headerTitle.next(`Role: ${this.name}`); } /** @@ -80,7 +79,7 @@ export class RoleDetailComponent implements OnInit { */ private setHeader(name: string): void { this.name = name; - this.header.headerTitle.next(`Role: ${name}`); + this.navSvc.headerTitle.next(`Role: ${name}`); } /** @@ -98,7 +97,7 @@ export class RoleDetailComponent implements OnInit { const result = await ref.afterClosed().toPromise(); if(result) { await this.userService.deleteRole(this.role); - this.location.back(); + await this.router.navigate(["core/roles"]); } } @@ -123,10 +122,9 @@ export class RoleDetailComponent implements OnInit { this.new = false; } else { this.role = await this.userService.updateRole(this.name, this.role); + this.navSvc.headerTitle.next(`Role: ${this.role.name}`); } - this.router.navigate([`/core/roles/${this.role.name}`], {replaceUrl: true}); - this.setHeader(this.name); - + await this.router.navigate(["core/roles", this.role.name], {replaceUrl: true}); } } diff --git a/experimental/traffic-portal/src/app/core/users/roles/table/roles-table.component.ts b/experimental/traffic-portal/src/app/core/users/roles/table/roles-table.component.ts index dfc9228aa5..faa7d3937c 100644 --- a/experimental/traffic-portal/src/app/core/users/roles/table/roles-table.component.ts +++ b/experimental/traffic-portal/src/app/core/users/roles/table/roles-table.component.ts @@ -40,7 +40,7 @@ export class RolesTableComponent implements OnInit { constructor( private readonly route: ActivatedRoute, - private readonly headerSvc: NavigationService, + private readonly navSvc: NavigationService, private readonly api: UserService, private readonly dialog: MatDialog, public readonly auth: CurrentUserService, @@ -48,7 +48,7 @@ export class RolesTableComponent implements OnInit { ) { this.fuzzySubject = new BehaviorSubject(""); this.roles = this.api.getRoles(); - this.headerSvc.headerTitle.next("Roles"); + this.navSvc.headerTitle.next("Roles"); } /** Initializes table data, loading it from Traffic Ops. */ diff --git a/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.spec.ts b/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.spec.ts index 33c094895b..7396970c5b 100644 --- a/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.spec.ts +++ b/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.spec.ts @@ -12,6 +12,7 @@ * 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"; @@ -28,7 +29,7 @@ describe("TenantDetailsComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ TenantDetailsComponent ], - imports: [ APITestingModule, RouterTestingModule ], + imports: [ APITestingModule, MatDialogModule, RouterTestingModule ], }) .compileComponents(); diff --git a/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.ts b/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.ts index 43d9cb8e0b..763bac912e 100644 --- a/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.ts +++ b/experimental/traffic-portal/src/app/core/users/tenants/tenant-details/tenant-details.component.ts @@ -11,14 +11,17 @@ * 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 { ActivatedRoute } from "@angular/router"; +import { MatDialog } from "@angular/material/dialog"; +import { ActivatedRoute, Router } from "@angular/router"; import { RequestTenant, ResponseTenant, Tenant } from "trafficops-types"; import { UserService } from "src/app/api"; import { TreeData } from "src/app/models"; +import { DecisionDialogComponent } from "src/app/shared/dialogs/decision-dialog/decision-dialog.component"; import { LoggingService } from "src/app/shared/logging.service"; +import { NavigationService } from "src/app/shared/navigation/navigation.service"; /** * TenantsDetailsComponent is the controller for the tenant add/edit form. @@ -36,8 +39,10 @@ export class TenantDetailsComponent implements OnInit { constructor( private readonly route: ActivatedRoute, + private readonly router: Router, private readonly userService: UserService, - private readonly location: Location, + private readonly dialog: MatDialog, + private readonly navSvc: NavigationService, private readonly log: LoggingService, ) { this.displayTenant = { @@ -114,7 +119,10 @@ export class TenantDetailsComponent implements OnInit { this.tenants = await this.userService.getTenants(); this.constructTreeData(); - if (ID === "new") { + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.tenant = { active: true, @@ -134,6 +142,17 @@ export class TenantDetailsComponent implements OnInit { } this.tenant = tenant; this.disabled = this.isRoot(); + this.setTitle(); + } + + /** + * Sets the headerTitle based on current Tenant state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New Tenant" : `Tenant: ${this.tenant.name}`; + this.navSvc.headerTitle.next(title); } /** @@ -150,9 +169,11 @@ export class TenantDetailsComponent implements OnInit { if (this.new) { this.tenant = await this.userService.createTenant(this.tenant as RequestTenant); this.new = false; + await this.router.navigate(["core/tenants", (this.tenant as ResponseTenant).id]); } else { this.tenant = await this.userService.updateTenant(this.tenant as ResponseTenant); } + this.setTitle(); } /** @@ -163,8 +184,16 @@ export class TenantDetailsComponent implements OnInit { this.log.error("Unable to delete new tenant"); return; } - await this.userService.deleteTenant((this.tenant as ResponseTenant).id); - this.location.back(); + const ref = this.dialog.open(DecisionDialogComponent, { + data: {message: `Are you sure you want to delete tenantn ${this.tenant.name}`, + title: "Confirm Delete"} + }); + ref.afterClosed().subscribe(result => { + if (result) { + this.userService.deleteTenant((this.tenant as ResponseTenant).id); + this.router.navigate(["core/tenants"]); + } + }); } /** diff --git a/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.spec.ts b/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.spec.ts index 6c57059963..836e956b7c 100644 --- a/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.spec.ts +++ b/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.spec.ts @@ -31,7 +31,10 @@ describe("UserDetailsComponent", () => { beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ UserDetailsComponent ], - imports: [ APITestingModule, RouterTestingModule ], + imports: [ APITestingModule, RouterTestingModule.withRoutes( [ + {component: UserDetailsComponent, path: "core/users"}, + {component: UserDetailsComponent, path: "core/users/:id"}, + ])], providers: [{provide: CurrentUserService, useClass: CurrentUserTestingService}] }).compileComponents(); fixture = TestBed.createComponent(UserDetailsComponent); diff --git a/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.ts b/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.ts index 21b14cff69..08b33bec2b 100644 --- a/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.ts +++ b/experimental/traffic-portal/src/app/core/users/user-details/user-details.component.ts @@ -12,14 +12,15 @@ * limitations under the License. */ -import { Component, type OnInit } from "@angular/core"; +import { Component, OnInit } from "@angular/core"; import type { MatSelectChange } from "@angular/material/select"; -import { ActivatedRoute } from "@angular/router"; +import { ActivatedRoute, Router } from "@angular/router"; import type { PostRequestUser, ResponseRole, ResponseTenant, ResponseUser, User } from "trafficops-types"; import { UserService } from "src/app/api"; import { CurrentUserService } from "src/app/shared/current-user/current-user.service"; import { LoggingService } from "src/app/shared/logging.service"; +import { NavigationService } from "src/app/shared/navigation/navigation.service"; /** * UserDetailsComponent is the controller for the page for viewing/editing a @@ -40,8 +41,10 @@ export class UserDetailsComponent implements OnInit { constructor( private readonly userService: UserService, private readonly route: ActivatedRoute, + private readonly router: Router, private readonly currentUserService: CurrentUserService, - private readonly log: LoggingService + private readonly log: LoggingService, + private readonly navSvc: NavigationService, ) { } /** Angular lifecycle hook */ @@ -56,7 +59,10 @@ export class UserDetailsComponent implements OnInit { return; } await rolesAndTenants; - if (ID === "new") { + this.new = ID === "new"; + + if (this.new) { + this.setTitle(); this.new = true; this.user = { confirmLocalPasswd: "", @@ -75,6 +81,7 @@ export class UserDetailsComponent implements OnInit { return; } this.user = await this.userService.getUsers(numID); + this.setTitle(); } /** @@ -90,6 +97,16 @@ export class UserDetailsComponent implements OnInit { return this.new; } + /** + * Sets the headerTitle based on current User state. + * + * @private + */ + private setTitle(): void { + const title = this.new ? "New User" : `User: ${this.user.username}`; + this.navSvc.headerTitle.next(title); + } + /** * Handler for the user edit form submission. * @@ -102,9 +119,10 @@ export class UserDetailsComponent implements OnInit { if (this.isNew(this.user)) { this.user = await this.userService.createUser(this.user); this.new = false; - return; + await this.router.navigate(["core/users", this.user.id]); } this.user = await this.userService.updateUser(this.user); + this.setTitle(); } /** diff --git a/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.ts b/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.ts index b84ef8bf7b..12ae4ef043 100644 --- a/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.ts +++ b/experimental/traffic-portal/src/app/shared/navigation/tp-header/tp-header.component.ts @@ -45,23 +45,23 @@ export class TpHeaderComponent implements OnInit { * Angular lifecycle hook */ public ngOnInit(): void { - this.headerSvc.headerTitle.subscribe(title => { + this.navSvc.headerTitle.subscribe(title => { this.title = title; }); - this.headerSvc.headerHidden.subscribe(hidden => { + this.navSvc.headerHidden.subscribe(hidden => { this.hidden = hidden; }); - this.headerSvc.horizontalNavsUpdated.subscribe(navs => { + this.navSvc.horizontalNavsUpdated.subscribe(navs => { this.horizNavs = navs; }); - this.headerSvc.verticalNavsUpdated.subscribe(navs => { + this.navSvc.verticalNavsUpdated.subscribe(navs => { this.vertNavs = navs; }); } constructor( public readonly themeSvc: ThemeManagerService, - private readonly headerSvc: NavigationService, + private readonly navSvc: NavigationService, private readonly log: LoggingService, ) { }