From 2db3b6fd39d978a29a2ef4998d360dbf5bcfae52 Mon Sep 17 00:00:00 2001 From: Ernesto Puerta Date: Tue, 26 Mar 2019 19:01:01 +0100 Subject: [PATCH] mgr/dashboard: unify button/URL actions naming - Mappings (actually an Enum) created for actions (buttons and other UI elements) and URLs: ActionLabels and URLVerbs. - An alternative would be to fix/improve the current i18n-polyfill, which only works with literal strings (not even with 'const enums' which become literals after Typescript transpiling). - Additionally having a predefined file with some strings to translate (actions, verbs, etc) could improve on the 1st of the 2-stage i18n process (as extraction tool has a lot of limitations). - A corresponding ActionLabelsI18n service with translated labels (it's a service as I haven't found the way to either translate no-const strings (ngx-translate/AST parser failure) or get a static translator). - This services could/should be extended to cover all strings that are defined in static/globally scoped objects before any I18n provider has been initialized. - Breadcrumbs are not translated (neither were they before this change). This part remains untackled: using 'proxy' static objects and performing live translation could deal with the issue. - New URLBuilder service created (following a established pattern in the Java/.NET world) . This should avoid the need of messing with literal URLs and string composition/parsing, and while the front-end is not meant to be consumed by anyone, Angular does not provide any other way for the app to navigate between components, so the URLs are a de-facto interface contract. Unlike this approach is not flawless, it's easier to enforce, while issues coming from free-from strings are really hard to catch. - This could be further improved by using a router registry/dynamic routing. Most of the routes are trivial. - As a side effect of these changes, routing module has been refactored and some routes moved to their specific modules (pool, rbd, rgw), via loadChildren and routes.forChild() magic. Now the above mentioned components are lazy-loaded/pre-loaded (it means right after the main code is loaded). This should also decrease the loading time (though probably this is not biggest time eater here). - As now modules can be loaded multiple times, not only from App module by means of lazy loading, but also from other ones (as PoolModule loads BlockModule to get QoS widgets in Pool windows), now lazy loaded modules include 2 NgModules (one with imports: RouterModule.forChild(routes), meant for lazy-loading, and another without routes). - Caveat: Some parts might not be (fully) translated (NFS, iSCSI, mirroring), as there's been ongoing work on them and it's hard to keep up with the new code. These changes will be a waste of time if the new code does not take benefit from/adheres to it, so I'm still figuring out how to spread this (nothing really fancy to demo). Maybe adding some checks/harnessing to enforce the new naming convention (ideas greatly welcome here). Fixes: http://tracker.ceph.com/issues/37337 Signed-off-by: Ernesto Puerta (cherry picked from commit 33466883948a4155ee2f7716effe1d1f5aa00cc6) Signed-off-by: Ernesto Puerta --- .../frontend/src/app/app-routing.module.ts | 160 +- .../src/app/ceph/block/block.module.ts | 84 +- .../iscsi-target-form.component.html | 13 +- .../iscsi-target-form.component.ts | 11 +- .../block/rbd-form/rbd-form.component.html | 19 +- .../ceph/block/rbd-form/rbd-form.component.ts | 12 +- .../block/rbd-list/rbd-list.component.spec.ts | 5 +- .../ceph/block/rbd-list/rbd-list.component.ts | 23 +- .../frontend/src/app/ceph/ceph.module.ts | 6 - .../erasure-code-profile-form.component.html | 16 +- .../erasure-code-profile-form.component.ts | 10 +- .../pool-details.component.spec.ts | 3 +- .../pool/pool-form/pool-form.component.html | 15 +- .../pool/pool-form/pool-form.component.ts | 14 +- .../pool/pool-list/pool-list.component.ts | 28 +- .../frontend/src/app/ceph/pool/pool.module.ts | 22 +- .../rgw-bucket-form.component.html | 13 +- .../rgw-bucket-form.component.ts | 12 +- .../rgw-bucket-list.component.spec.ts | 11 +- .../rgw-bucket-list.component.ts | 21 +- .../rgw-user-capability-modal.component.html | 17 +- .../rgw-user-capability-modal.component.ts | 13 +- .../rgw-user-form.component.html | 19 +- .../rgw-user-form/rgw-user-form.component.ts | 18 +- .../rgw-user-list.component.spec.ts | 11 +- .../rgw-user-list/rgw-user-list.component.ts | 21 +- .../rgw-user-s3-key-modal.component.html | 18 +- .../rgw-user-s3-key-modal.component.ts | 13 +- .../rgw-user-subuser-modal.component.html | 17 +- .../rgw-user-subuser-modal.component.ts | 13 +- .../rgw-user-swift-key-modal.component.html | 9 +- ...rgw-user-swift-key-modal.component.spec.ts | 9 +- .../rgw-user-swift-key-modal.component.ts | 14 +- .../frontend/src/app/ceph/rgw/rgw.module.ts | 62 +- .../frontend/src/app/core/auth/auth.module.ts | 46 +- .../auth/role-form/role-form.component.html | 12 +- .../auth/role-form/role-form.component.ts | 11 +- .../role-list/role-list.component.spec.ts | 11 +- .../auth/role-list/role-list.component.ts | 21 +- .../auth/user-form/user-form.component.html | 14 +- .../auth/user-form/user-form.component.ts | 11 +- .../user-list/user-list.component.spec.ts | 9 +- .../auth/user-list/user-list.component.ts | 21 +- .../frontend/src/app/core/core.module.ts | 3 +- .../back-button/back-button.component.ts | 6 +- .../src/app/shared/constants/app.constants.ts | 97 + .../services/url-builder.service.spec.ts | 32 + .../shared/services/url-builder.service.ts | 40 + .../frontend/src/locale/messages.xlf | 5033 +++++++++-------- 49 files changed, 3267 insertions(+), 2852 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/url-builder.service.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/shared/services/url-builder.service.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index 89b4e7dd1956d..944847ff681d7 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -1,14 +1,8 @@ import { NgModule } from '@angular/core'; -import { ActivatedRouteSnapshot, RouterModule, Routes } from '@angular/router'; +import { ActivatedRouteSnapshot, PreloadAllModules, RouterModule, Routes } from '@angular/router'; import * as _ from 'lodash'; -import { IscsiTargetFormComponent } from './ceph/block/iscsi-target-form/iscsi-target-form.component'; -import { IscsiTargetListComponent } from './ceph/block/iscsi-target-list/iscsi-target-list.component'; -import { IscsiComponent } from './ceph/block/iscsi/iscsi.component'; -import { OverviewComponent as RbdMirroringComponent } from './ceph/block/mirroring/overview/overview.component'; -import { RbdFormComponent } from './ceph/block/rbd-form/rbd-form.component'; -import { RbdImagesComponent } from './ceph/block/rbd-images/rbd-images.component'; import { CephfsListComponent } from './ceph/cephfs/cephfs-list/cephfs-list.component'; import { ConfigurationFormComponent } from './ceph/cluster/configuration/configuration-form/configuration-form.component'; import { ConfigurationComponent } from './ceph/cluster/configuration/configuration.component'; @@ -25,20 +19,8 @@ import { Nfs501Component } from './ceph/nfs/nfs-501/nfs-501.component'; import { NfsFormComponent } from './ceph/nfs/nfs-form/nfs-form.component'; import { NfsListComponent } from './ceph/nfs/nfs-list/nfs-list.component'; import { PerformanceCounterComponent } from './ceph/performance-counter/performance-counter/performance-counter.component'; -import { PoolFormComponent } from './ceph/pool/pool-form/pool-form.component'; -import { PoolListComponent } from './ceph/pool/pool-list/pool-list.component'; -import { Rgw501Component } from './ceph/rgw/rgw-501/rgw-501.component'; -import { RgwBucketFormComponent } from './ceph/rgw/rgw-bucket-form/rgw-bucket-form.component'; -import { RgwBucketListComponent } from './ceph/rgw/rgw-bucket-list/rgw-bucket-list.component'; -import { RgwDaemonListComponent } from './ceph/rgw/rgw-daemon-list/rgw-daemon-list.component'; -import { RgwUserFormComponent } from './ceph/rgw/rgw-user-form/rgw-user-form.component'; -import { RgwUserListComponent } from './ceph/rgw/rgw-user-list/rgw-user-list.component'; import { LoginComponent } from './core/auth/login/login.component'; -import { RoleFormComponent } from './core/auth/role-form/role-form.component'; -import { RoleListComponent } from './core/auth/role-list/role-list.component'; import { SsoNotFoundComponent } from './core/auth/sso/sso-not-found/sso-not-found.component'; -import { UserFormComponent } from './core/auth/user-form/user-form.component'; -import { UserListComponent } from './core/auth/user-list/user-list.component'; import { ForbiddenComponent } from './core/forbidden/forbidden.component'; import { NotFoundComponent } from './core/not-found/not-found.component'; import { BreadcrumbsResolver, IBreadcrumb } from './shared/models/breadcrumbs'; @@ -98,12 +80,7 @@ const routes: Routes = [ canActivate: [AuthGuardService], canActivateChild: [AuthGuardService], data: { breadcrumbs: 'Cluster/OSDs' }, - children: [ - { - path: '', - component: OsdListComponent - } - ] + children: [{ path: '', component: OsdListComponent }] }, { path: 'configuration', @@ -169,11 +146,7 @@ const routes: Routes = [ canActivate: [AuthGuardService], canActivateChild: [AuthGuardService], data: { breadcrumbs: 'Pools' }, - children: [ - { path: '', component: PoolListComponent }, - { path: 'add', component: PoolFormComponent, data: { breadcrumbs: 'Add' } }, - { path: 'edit/:name', component: PoolFormComponent, data: { breadcrumbs: 'Edit' } } - ] + loadChildren: './ceph/pool/pool.module#RoutedPoolModule' }, // Block { @@ -181,71 +154,7 @@ const routes: Routes = [ canActivateChild: [AuthGuardService], canActivate: [AuthGuardService], data: { breadcrumbs: true, text: 'Block', path: null }, - children: [ - { - path: '', - redirectTo: 'rbd', - pathMatch: 'full' - }, - { - path: 'rbd', - canActivate: [FeatureTogglesGuardService], - data: { breadcrumbs: 'Images' }, - children: [ - { path: '', component: RbdImagesComponent }, - { path: 'add', component: RbdFormComponent, data: { breadcrumbs: 'Add' } }, - { path: 'edit/:pool/:name', component: RbdFormComponent, data: { breadcrumbs: 'Edit' } }, - { - path: 'clone/:pool/:name/:snap', - component: RbdFormComponent, - data: { breadcrumbs: 'Clone' } - }, - { path: 'copy/:pool/:name', component: RbdFormComponent, data: { breadcrumbs: 'Copy' } }, - { - path: 'copy/:pool/:name/:snap', - component: RbdFormComponent, - data: { breadcrumbs: 'Copy' } - } - ] - }, - { - path: 'mirroring', - component: RbdMirroringComponent, - canActivate: [FeatureTogglesGuardService], - data: { breadcrumbs: 'Mirroring' } - }, - // iSCSI - { - path: 'iscsi', - canActivate: [FeatureTogglesGuardService], - data: { breadcrumbs: 'iSCSI' }, - children: [ - { - path: '', - redirectTo: 'overview', - pathMatch: 'full' - }, - { - path: 'overview', - data: { breadcrumbs: 'Overview' }, - children: [{ path: '', component: IscsiComponent }] - }, - { - path: 'targets', - data: { breadcrumbs: 'Targets' }, - children: [ - { path: '', component: IscsiTargetListComponent }, - { path: 'add', component: IscsiTargetFormComponent, data: { breadcrumbs: 'Add' } }, - { - path: 'edit/:target_iqn', - component: IscsiTargetFormComponent, - data: { breadcrumbs: 'Edit' } - } - ] - } - ] - } - ] + loadChildren: './ceph/block/block.module#RoutedBlockModule' }, // Filesystems { @@ -255,12 +164,6 @@ const routes: Routes = [ data: { breadcrumbs: 'Filesystems' } }, // Object Gateway - { - path: 'rgw/501/:message', - component: Rgw501Component, - canActivate: [AuthGuardService], - data: { breadcrumbs: 'Object Gateway' } - }, { path: 'rgw', canActivateChild: [FeatureTogglesGuardService, ModuleStatusGuardService, AuthGuardService], @@ -273,27 +176,7 @@ const routes: Routes = [ text: 'Object Gateway', path: null }, - children: [ - { path: 'daemon', component: RgwDaemonListComponent, data: { breadcrumbs: 'Daemons' } }, - { - path: 'user', - data: { breadcrumbs: 'Users' }, - children: [ - { path: '', component: RgwUserListComponent }, - { path: 'add', component: RgwUserFormComponent, data: { breadcrumbs: 'Add' } }, - { path: 'edit/:uid', component: RgwUserFormComponent, data: { breadcrumbs: 'Edit' } } - ] - }, - { - path: 'bucket', - data: { breadcrumbs: 'Buckets' }, - children: [ - { path: '', component: RgwBucketListComponent }, - { path: 'add', component: RgwBucketFormComponent, data: { breadcrumbs: 'Add' } }, - { path: 'edit/:bid', component: RgwBucketFormComponent, data: { breadcrumbs: 'Edit' } } - ] - } - ] + loadChildren: './ceph/rgw/rgw.module#RoutedRgwModule' }, // Dashboard Settings { @@ -301,31 +184,7 @@ const routes: Routes = [ canActivate: [AuthGuardService], canActivateChild: [AuthGuardService], data: { breadcrumbs: 'User management', path: null }, - children: [ - { - path: '', - redirectTo: 'users', - pathMatch: 'full' - }, - { - path: 'users', - data: { breadcrumbs: 'Users' }, - children: [ - { path: '', component: UserListComponent }, - { path: 'add', component: UserFormComponent, data: { breadcrumbs: 'Add' } }, - { path: 'edit/:username', component: UserFormComponent, data: { breadcrumbs: 'Edit' } } - ] - }, - { - path: 'roles', - data: { breadcrumbs: 'Roles' }, - children: [ - { path: '', component: RoleListComponent }, - { path: 'add', component: RoleFormComponent, data: { breadcrumbs: 'Add' } }, - { path: 'edit/:name', component: RoleFormComponent, data: { breadcrumbs: 'Edit' } } - ] - } - ] + loadChildren: './core/auth/auth.module#RoutedAuthModule' }, // NFS { @@ -366,7 +225,12 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes, { useHash: true })], + imports: [ + RouterModule.forRoot(routes, { + useHash: true, + preloadingStrategy: PreloadAllModules + }) + ], exports: [RouterModule], providers: [StartCaseBreadcrumbsResolver, PerformanceCounterBreadcrumbsResolver] }) diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts index 4c706d4ae6292..b4322d2d0fdf4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/block.module.ts @@ -1,7 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { TreeModule } from 'ng2-tree'; import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; @@ -11,6 +11,8 @@ import { ProgressbarModule } from 'ngx-bootstrap/progressbar'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants'; +import { FeatureTogglesGuardService } from '../../shared/services/feature-toggles-guard.service'; import { SharedModule } from '../../shared/shared.module'; import { IscsiTabsComponent } from './iscsi-tabs/iscsi-tabs.component'; import { IscsiTargetDetailsComponent } from './iscsi-target-details/iscsi-target-details.component'; @@ -21,6 +23,7 @@ import { IscsiTargetIqnSettingsModalComponent } from './iscsi-target-iqn-setting import { IscsiTargetListComponent } from './iscsi-target-list/iscsi-target-list.component'; import { IscsiComponent } from './iscsi/iscsi.component'; import { MirroringModule } from './mirroring/mirroring.module'; +import { OverviewComponent as RbdMirroringComponent } from './mirroring/overview/overview.component'; import { RbdConfigurationFormComponent } from './rbd-configuration-form/rbd-configuration-form.component'; import { RbdConfigurationListComponent } from './rbd-configuration-list/rbd-configuration-list.component'; import { RbdDetailsComponent } from './rbd-details/rbd-details.component'; @@ -86,3 +89,82 @@ import { RbdTrashRestoreModalComponent } from './rbd-trash-restore-modal/rbd-tra exports: [RbdConfigurationListComponent, RbdConfigurationFormComponent] }) export class BlockModule {} + +/* The following breakdown is needed to allow importing block.module without + the routes (e.g.: this module is imported by pool.module for RBD QoS + components) +*/ +const routes: Routes = [ + { path: '', redirectTo: 'rbd', pathMatch: 'full' }, + { + path: 'rbd', + canActivate: [FeatureTogglesGuardService], + data: { breadcrumbs: 'Images' }, + children: [ + { path: '', component: RbdImagesComponent }, + { + path: URLVerbs.CREATE, + component: RbdFormComponent, + data: { breadcrumbs: ActionLabels.CREATE } + }, + { + path: `${URLVerbs.EDIT}/:pool/:name`, + component: RbdFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } + }, + { + path: `${URLVerbs.CLONE}/:pool/:name/:snap`, + component: RbdFormComponent, + data: { breadcrumbs: ActionLabels.CLONE } + }, + { + path: `${URLVerbs.COPY}/:pool/:name`, + component: RbdFormComponent, + data: { breadcrumbs: ActionLabels.COPY } + }, + { + path: `${URLVerbs.COPY}/:pool/:name/:snap`, + component: RbdFormComponent, + data: { breadcrumbs: ActionLabels.COPY } + } + ] + }, + { + path: 'mirroring', + component: RbdMirroringComponent, + canActivate: [FeatureTogglesGuardService], + data: { breadcrumbs: 'Mirroring' } + }, + // iSCSI + { + path: 'iscsi', + canActivate: [FeatureTogglesGuardService], + data: { breadcrumbs: 'iSCSI' }, + children: [ + { path: '', redirectTo: 'overview', pathMatch: 'full' }, + { path: 'overview', component: IscsiComponent, data: { breadcrumbs: 'Overview' } }, + { + path: 'targets', + data: { breadcrumbs: 'Targets' }, + children: [ + { path: '', component: IscsiTargetListComponent }, + { + path: URLVerbs.ADD, + component: IscsiTargetFormComponent, + data: { breadcrumbs: ActionLabels.ADD } + }, + { + path: `${URLVerbs.EDIT}/:target_iqn`, + component: IscsiTargetFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } + } + ] + } + ] + } +]; + +@NgModule({ + imports: [BlockModule, RouterModule.forChild(routes)] +}) +export class RoutedBlockModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html index ef0696a6ab104..53e83cc8d8345 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.html @@ -7,8 +7,8 @@ *ngIf="targetForm">
-

{isEdit, select, 1 {Edit} other {Add}} target

+

{{ action | titlecase }} {{ resource | upperFirst }}

@@ -556,10 +556,11 @@
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts index bd68a3399775b..5737f4852197f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi-target-form/iscsi-target-form.component.ts @@ -11,6 +11,7 @@ import { IscsiService } from '../../../shared/api/iscsi.service'; import { RbdService } from '../../../shared/api/rbd.service'; import { SelectMessages } from '../../../shared/components/select/select-messages.model'; import { SelectOption } from '../../../shared/components/select/select-option.model'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; import { CdValidators } from '../../../shared/forms/cd-validators'; import { FinishedTask } from '../../../shared/models/finished-task'; @@ -76,6 +77,8 @@ export class IscsiTargetFormComponent implements OnInit { IQN_REGEX = /^iqn\.(19|20)\d\d-(0[1-9]|1[0-2])\.\D{2,3}(\.[A-Za-z0-9-]+)+(:[A-Za-z0-9-\.]+)*$/; USER_REGEX = /[\w\.:@_-]{8,64}/; PASSWORD_REGEX = /[\w@\-_\/]{12,16}/; + action: string; + resource: string; constructor( private iscsiService: IscsiService, @@ -84,8 +87,11 @@ export class IscsiTargetFormComponent implements OnInit { private router: Router, private route: ActivatedRoute, private i18n: I18n, - private taskWrapper: TaskWrapperService - ) {} + private taskWrapper: TaskWrapperService, + public actionLabels: ActionLabelsI18n + ) { + this.resource = this.i18n('target'); + } ngOnInit() { const promises: any[] = [ @@ -102,6 +108,7 @@ export class IscsiTargetFormComponent implements OnInit { promises.push(this.iscsiService.getTarget(this.target_iqn)); }); } + this.action = this.isEdit ? this.actionLabels.EDIT : this.actionLabels.CREATE; forkJoin(promises).subscribe((data: any[]) => { // iscsiService.listTargets diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html index 7caad726a4c25..b567ddd96eae1 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.html @@ -6,11 +6,8 @@ novalidate>
-

- {mode, select, editing {Edit} cloning {Clone} copying {Copy} other {Add}} - RBD -

+

{{ action | titlecase }} {{ resource | upperFirst }}

@@ -19,7 +16,7 @@ *ngIf="rbdForm.getValue('parent')"> + for="name">{{ action | titlecase }} from
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts index 3739d0eda62e7..8bda29553c729 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-form/rbd-form.component.ts @@ -8,6 +8,7 @@ import { Observable } from 'rxjs'; import { PoolService } from '../../../shared/api/pool.service'; import { RbdService } from '../../../shared/api/rbd.service'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; import { RbdConfigurationEntry, @@ -85,6 +86,8 @@ export class RbdFormComponent implements OnInit { '16 MiB', '32 MiB' ]; + action: string; + resource: string; constructor( private authStorageService: AuthStorageService, @@ -95,9 +98,11 @@ export class RbdFormComponent implements OnInit { private formatter: FormatterService, private taskWrapper: TaskWrapperService, private dimlessBinaryPipe: DimlessBinaryPipe, - private i18n: I18n + private i18n: I18n, + public actionLabels: ActionLabelsI18n ) { this.poolPermission = this.authStorageService.getPermissions().pool; + this.resource = this.i18n('RBD'); this.features = { 'deep-flatten': { desc: this.i18n('Deep flatten'), @@ -207,13 +212,18 @@ export class RbdFormComponent implements OnInit { ngOnInit() { if (this.router.url.startsWith('/block/rbd/edit')) { this.mode = this.rbdFormMode.editing; + this.action = this.actionLabels.EDIT; this.disableForEdit(); } else if (this.router.url.startsWith('/block/rbd/clone')) { this.mode = this.rbdFormMode.cloning; this.disableForClone(); + this.action = this.actionLabels.CLONE; } else if (this.router.url.startsWith('/block/rbd/copy')) { this.mode = this.rbdFormMode.copying; + this.action = this.actionLabels.COPY; this.disableForCopy(); + } else { + this.action = this.actionLabels.CREATE; } if ( this.mode === this.rbdFormMode.editing || diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts index 93cd64b26d47f..346a6bd650678 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.spec.ts @@ -17,6 +17,7 @@ import { PermissionHelper } from '../../../../testing/unit-test-helper'; import { RbdService } from '../../../shared/api/rbd.service'; +import { ActionLabels } from '../../../shared/constants/app.constants'; import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component'; import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; import { ExecutingTask } from '../../../shared/models/executing-task'; @@ -212,8 +213,8 @@ describe('RbdListComponent', () => { ); scenario = { fn: () => tableActions.getCurrentButton().name, - single: 'Edit', - empty: 'Add' + single: ActionLabels.EDIT, + empty: ActionLabels.CREATE }; }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts index 335798f9c3d82..0ba72c8ef6797 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/block/rbd-list/rbd-list.component.ts @@ -7,6 +7,7 @@ import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { RbdService } from '../../../shared/api/rbd.service'; import { ConfirmationModalComponent } from '../../../shared/components/confirmation-modal/confirmation-modal.component'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; import { CellTemplate } from '../../../shared/enum/cell-template.enum'; import { ViewCacheStatus } from '../../../shared/enum/view-cache-status.enum'; @@ -20,15 +21,21 @@ import { DimlessPipe } from '../../../shared/pipes/dimless.pipe'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { TaskListService } from '../../../shared/services/task-list.service'; import { TaskWrapperService } from '../../../shared/services/task-wrapper.service'; +import { URLBuilderService } from '../../../shared/services/url-builder.service'; import { RbdParentModel } from '../rbd-form/rbd-parent.model'; import { RbdTrashMoveModalComponent } from '../rbd-trash-move-modal/rbd-trash-move-modal.component'; import { RbdModel } from './rbd-model'; +const BASE_URL = 'block/rbd'; + @Component({ selector: 'cd-rbd-list', templateUrl: './rbd-list.component.html', styleUrls: ['./rbd-list.component.scss'], - providers: [TaskListService] + providers: [ + TaskListService, + { provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) } + ] }) export class RbdListComponent implements OnInit { @ViewChild(TableComponent) @@ -77,7 +84,9 @@ export class RbdListComponent implements OnInit { private modalService: BsModalService, private taskWrapper: TaskWrapperService, private taskListService: TaskListService, - private i18n: I18n + private i18n: I18n, + private urlBuilder: URLBuilderService, + public actionLabels: ActionLabelsI18n ) { this.permission = this.authStorageService.getPermissions().rbdImage; const getImageUri = () => @@ -88,21 +97,21 @@ export class RbdListComponent implements OnInit { const addAction: CdTableAction = { permission: 'create', icon: 'fa-plus', - routerLink: () => '/block/rbd/add', + routerLink: () => this.urlBuilder.getCreate(), canBePrimary: (selection: CdTableSelection) => !selection.hasSingleSelection, - name: this.i18n('Add') + name: this.actionLabels.CREATE }; const editAction: CdTableAction = { permission: 'update', icon: 'fa-pencil', - routerLink: () => `/block/rbd/edit/${getImageUri()}`, - name: this.i18n('Edit') + routerLink: () => this.urlBuilder.getEdit(getImageUri()), + name: this.actionLabels.EDIT }; const deleteAction: CdTableAction = { permission: 'delete', icon: 'fa-times', click: () => this.deleteRbdModal(), - name: this.i18n('Delete') + name: this.actionLabels.DELETE }; const copyAction: CdTableAction = { permission: 'create', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts index 1ba6085070ed9..47772304b505f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/ceph.module.ts @@ -2,24 +2,18 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; -import { BlockModule } from './block/block.module'; import { CephfsModule } from './cephfs/cephfs.module'; import { ClusterModule } from './cluster/cluster.module'; import { DashboardModule } from './dashboard/dashboard.module'; import { NfsModule } from './nfs/nfs.module'; import { PerformanceCounterModule } from './performance-counter/performance-counter.module'; -import { PoolModule } from './pool/pool.module'; -import { RgwModule } from './rgw/rgw.module'; @NgModule({ imports: [ CommonModule, ClusterModule, DashboardModule, - RgwModule, PerformanceCounterModule, - BlockModule, - PoolModule, CephfsModule, NfsModule, SharedModule diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html index 31f2eb94df014..4806bf21d062d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/pool/erasure-code-profile-form/erasure-code-profile-form.component.html @@ -1,6 +1,6 @@ @@ -307,7 +308,7 @@ class="btn btn-sm btn-default btn-label pull-right tc_addS3KeyButton" (click)="showS3KeyModal()"> - Add S3 key + {{ actionLabels.CREATE | titlecase }} {{ s3keyLabel | upperFirst }}
@@ -392,7 +393,7 @@ class="btn btn-sm btn-default btn-label pull-right tc_addCapButton" (click)="showCapabilityModal()"> - Add capability + {{ actionLabels.ADD | titlecase }} {{ capabilityLabel | upperFirst }}
@@ -587,11 +588,11 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts index a573dbdc1ca6c..3a2a0f0e8dfa6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts @@ -8,6 +8,7 @@ import { BsModalService } from 'ngx-bootstrap/modal'; import { forkJoin as observableForkJoin, Observable } from 'rxjs'; import { RgwUserService } from '../../../shared/api/rgw-user.service'; +import { ActionLabelsI18n, URLVerbs } from '../../../shared/constants/app.constants'; import { NotificationType } from '../../../shared/enum/notification-type.enum'; import { CdFormBuilder } from '../../../shared/forms/cd-form-builder'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; @@ -40,6 +41,12 @@ export class RgwUserFormComponent implements OnInit { swiftKeys: RgwUserSwiftKey[] = []; capabilities: RgwUserCapability[] = []; + action: string; + resource: string; + subuserLabel: string; + s3keyLabel: string; + capabilityLabel: string; + constructor( private formBuilder: CdFormBuilder, private route: ActivatedRoute, @@ -47,8 +54,13 @@ export class RgwUserFormComponent implements OnInit { private rgwUserService: RgwUserService, private bsModalService: BsModalService, private notificationService: NotificationService, - private i18n: I18n + private i18n: I18n, + public actionLabels: ActionLabelsI18n ) { + this.resource = this.i18n('user'); + this.subuserLabel = this.i18n('subuser'); + this.s3keyLabel = this.i18n('S3 Key'); + this.capabilityLabel = this.i18n('capability'); this.createForm(); this.listenToChanges(); } @@ -158,6 +170,8 @@ export class RgwUserFormComponent implements OnInit { } ngOnInit() { + this.editing = this.router.url.startsWith(`/rgw/user/${URLVerbs.EDIT}`); + this.action = this.editing ? this.actionLabels.EDIT : this.actionLabels.CREATE; // Process route parameters. this.route.params.subscribe((params: { uid: string }) => { if (!params.hasOwnProperty('uid')) { @@ -165,8 +179,6 @@ export class RgwUserFormComponent implements OnInit { } const uid = decodeURIComponent(params.uid); this.loading = true; - // Load the user data in 'edit' mode. - this.editing = true; // Load the user and quota information. const observables = []; observables.push(this.rgwUserService.get(uid)); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts index 8f05cab50aa31..46c3ee99f8bec 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.spec.ts @@ -11,6 +11,7 @@ import { i18nProviders, PermissionHelper } from '../../../../testing/unit-test-helper'; +import { ActionLabels } from '../../../shared/constants/app.constants'; import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component'; import { SharedModule } from '../../../shared/shared.module'; import { RgwUserListComponent } from './rgw-user-list.component'; @@ -52,8 +53,8 @@ describe('RgwUserListComponent', () => { ); scenario = { fn: () => tableActions.getCurrentButton().name, - single: 'Edit', - empty: 'Add' + single: ActionLabels.EDIT, + empty: ActionLabels.CREATE }; }); @@ -111,7 +112,7 @@ describe('RgwUserListComponent', () => { }); it(`shows always 'Edit' as main action`, () => { - scenario.empty = 'Edit'; + scenario.empty = ActionLabels.EDIT; permissionHelper.testScenarios(scenario); }); @@ -130,7 +131,7 @@ describe('RgwUserListComponent', () => { }); it(`shows always 'Add' as main action`, () => { - scenario.single = 'Add'; + scenario.single = ActionLabels.CREATE; permissionHelper.testScenarios(scenario); }); @@ -146,7 +147,7 @@ describe('RgwUserListComponent', () => { }); it(`shows always 'Edit' as main action`, () => { - scenario.empty = 'Edit'; + scenario.empty = ActionLabels.EDIT; permissionHelper.testScenarios(scenario); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts index a8975a1b9bdc8..7a6789f4b896e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts @@ -6,6 +6,7 @@ import { forkJoin as observableForkJoin, Observable, Subscriber } from 'rxjs'; import { RgwUserService } from '../../../shared/api/rgw-user.service'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { TableComponent } from '../../../shared/datatable/table/table.component'; import { CellTemplate } from '../../../shared/enum/cell-template.enum'; import { CdTableAction } from '../../../shared/models/cd-table-action'; @@ -14,11 +15,15 @@ import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-d import { CdTableSelection } from '../../../shared/models/cd-table-selection'; import { Permission } from '../../../shared/models/permissions'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; +import { URLBuilderService } from '../../../shared/services/url-builder.service'; + +const BASE_URL = 'rgw/user'; @Component({ selector: 'cd-rgw-user-list', templateUrl: './rgw-user-list.component.html', - styleUrls: ['./rgw-user-list.component.scss'] + styleUrls: ['./rgw-user-list.component.scss'], + providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) export class RgwUserListComponent { @ViewChild(TableComponent) @@ -34,7 +39,9 @@ export class RgwUserListComponent { private authStorageService: AuthStorageService, private rgwUserService: RgwUserService, private bsModalService: BsModalService, - private i18n: I18n + private i18n: I18n, + private urlBuilder: URLBuilderService, + public actionLabels: ActionLabelsI18n ) { this.permission = this.authStorageService.getPermissions().rgw; this.columns = [ @@ -71,20 +78,20 @@ export class RgwUserListComponent { const addAction: CdTableAction = { permission: 'create', icon: 'fa-plus', - routerLink: () => '/rgw/user/add', - name: this.i18n('Add') + routerLink: () => this.urlBuilder.getCreate(), + name: this.actionLabels.CREATE }; const editAction: CdTableAction = { permission: 'update', icon: 'fa-pencil', - routerLink: () => `/rgw/user/edit/${getUserUri()}`, - name: this.i18n('Edit') + routerLink: () => this.urlBuilder.getEdit(getUserUri()), + name: this.actionLabels.EDIT }; const deleteAction: CdTableAction = { permission: 'delete', icon: 'fa-times', click: () => this.deleteAction(), - name: this.i18n('Delete') + name: this.actionLabels.DELETE }; this.tableActions = [addAction, editAction, deleteAction]; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.html index c2b198d06ffc2..0805c5e871187 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-s3-key-modal/rgw-user-s3-key-modal.component.html @@ -1,6 +1,6 @@ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.spec.ts index cf950177470a2..8850fc71f93cf 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.spec.ts @@ -1,9 +1,12 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; +import { RouterTestingModule } from '@angular/router/testing'; +import { ToastModule } from 'ng2-toastr'; import { BsModalRef } from 'ngx-bootstrap/modal'; -import { configureTestBed } from '../../../../testing/unit-test-helper'; +import { configureTestBed, i18nProviders } from '../../../../testing/unit-test-helper'; +import { SharedModule } from '../../../shared/shared.module'; import { RgwUserSwiftKeyModalComponent } from './rgw-user-swift-key-modal.component'; describe('RgwUserSwiftKeyModalComponent', () => { @@ -12,8 +15,8 @@ describe('RgwUserSwiftKeyModalComponent', () => { configureTestBed({ declarations: [RgwUserSwiftKeyModalComponent], - imports: [FormsModule], - providers: [BsModalRef] + imports: [ToastModule.forRoot(), FormsModule, SharedModule, RouterTestingModule], + providers: [BsModalRef, i18nProviders] }); beforeEach(() => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.ts index 080c2b144e11d..ad10e070f7e6d 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-swift-key-modal/rgw-user-swift-key-modal.component.ts @@ -1,7 +1,10 @@ import { Component } from '@angular/core'; +import { I18n } from '@ngx-translate/i18n-polyfill'; import { BsModalRef } from 'ngx-bootstrap/modal'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; + @Component({ selector: 'cd-rgw-user-swift-key-modal', templateUrl: './rgw-user-swift-key-modal.component.html', @@ -10,8 +13,17 @@ import { BsModalRef } from 'ngx-bootstrap/modal'; export class RgwUserSwiftKeyModalComponent { user: string; secret_key: string; + resource: string; + action: string; - constructor(public bsModalRef: BsModalRef) {} + constructor( + public bsModalRef: BsModalRef, + private i18n: I18n, + public actionLabels: ActionLabelsI18n + ) { + this.resource = this.i18n('Swift Key'); + this.action = this.actionLabels.SHOW; + } /** * Set the values displayed in the dialog. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index 6090826588795..25ce79a373dfc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -1,6 +1,7 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { RouterModule, Routes } from '@angular/router'; import { AlertModule } from 'ngx-bootstrap/alert'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; @@ -8,7 +9,8 @@ import { ModalModule } from 'ngx-bootstrap/modal'; import { TabsModule } from 'ngx-bootstrap/tabs'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; -import { AppRoutingModule } from '../../app-routing.module'; +import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants'; +import { AuthGuardService } from '../../shared/services/auth-guard.service'; import { SharedModule } from '../../shared/shared.module'; import { PerformanceCounterModule } from '../performance-counter/performance-counter.module'; import { Rgw501Component } from './rgw-501/rgw-501.component'; @@ -38,7 +40,6 @@ import { RgwUserSwiftKeyModalComponent } from './rgw-user-swift-key-modal/rgw-us imports: [ CommonModule, SharedModule, - AppRoutingModule, FormsModule, ReactiveFormsModule, PerformanceCounterModule, @@ -46,7 +47,8 @@ import { RgwUserSwiftKeyModalComponent } from './rgw-user-swift-key-modal/rgw-us BsDropdownModule.forRoot(), TabsModule.forRoot(), TooltipModule.forRoot(), - ModalModule.forRoot() + ModalModule.forRoot(), + RouterModule ], exports: [ Rgw501Component, @@ -76,3 +78,57 @@ import { RgwUserSwiftKeyModalComponent } from './rgw-user-swift-key-modal/rgw-us ] }) export class RgwModule {} + +const routes: Routes = [ + { + path: '', + redirectTo: 'daemon', + pathMatch: 'full' + }, + { path: 'daemon', component: RgwDaemonListComponent, data: { breadcrumbs: 'Daemons' } }, + { + path: 'user', + data: { breadcrumbs: 'Users' }, + children: [ + { path: '', component: RgwUserListComponent }, + { + path: URLVerbs.CREATE, + component: RgwUserFormComponent, + data: { breadcrumbs: ActionLabels.CREATE } + }, + { + path: `${URLVerbs.EDIT}/:uid`, + component: RgwUserFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } + } + ] + }, + { + path: 'bucket', + data: { breadcrumbs: 'Buckets' }, + children: [ + { path: '', component: RgwBucketListComponent }, + { + path: URLVerbs.CREATE, + component: RgwBucketFormComponent, + data: { breadcrumbs: ActionLabels.CREATE } + }, + { + path: `${URLVerbs.EDIT}/:bid`, + component: RgwBucketFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } + } + ] + }, + { + path: '501/:message', + component: Rgw501Component, + canActivate: [AuthGuardService], + data: { breadcrumbs: 'Object Gateway' } + } +]; + +@NgModule({ + imports: [RgwModule, RouterModule.forChild(routes)] +}) +export class RoutedRgwModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts index 93aa4c887ac32..cdba46c2e2c83 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/auth.module.ts @@ -1,12 +1,13 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { PopoverModule } from 'ngx-bootstrap/popover'; import { TabsModule } from 'ngx-bootstrap/tabs'; +import { ActionLabels, URLVerbs } from '../../shared/constants/app.constants'; import { SharedModule } from '../../shared/shared.module'; import { LoginComponent } from './login/login.component'; import { RoleDetailsComponent } from './role-details/role-details.component'; @@ -40,3 +41,46 @@ import { UserTabsComponent } from './user-tabs/user-tabs.component'; ] }) export class AuthModule {} + +const routes: Routes = [ + { path: '', redirectTo: 'users', pathMatch: 'full' }, + { + path: 'users', + data: { breadcrumbs: 'Users' }, + children: [ + { path: '', component: UserListComponent }, + { + path: URLVerbs.CREATE, + component: UserFormComponent, + data: { breadcrumbs: ActionLabels.CREATE } + }, + { + path: `${URLVerbs.EDIT}/:username`, + component: UserFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } + } + ] + }, + { + path: 'roles', + data: { breadcrumbs: 'Roles' }, + children: [ + { path: '', component: RoleListComponent }, + { + path: URLVerbs.CREATE, + component: RoleFormComponent, + data: { breadcrumbs: ActionLabels.CREATE } + }, + { + path: `${URLVerbs.EDIT}/:name`, + component: RoleFormComponent, + data: { breadcrumbs: ActionLabels.EDIT } + } + ] + } +]; + +@NgModule({ + imports: [AuthModule, RouterModule.forChild(routes)] +}) +export class RoutedAuthModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.html index 236670cb76a6a..2f9eefa9dcbbc 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.html @@ -6,8 +6,8 @@ novalidate>
-

{mode, select, editing {Edit} other {Add}} Role

+

{{ action | titlecase }} {{ resource | upperFirst }}

@@ -75,10 +75,10 @@
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts index a1b02c7be2b63..29efb82c6df93 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-form/role-form.component.ts @@ -9,6 +9,7 @@ import { forkJoin as observableForkJoin } from 'rxjs'; import { RoleService } from '../../../shared/api/role.service'; import { ScopeService } from '../../../shared/api/scope.service'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { NotificationType } from '../../../shared/enum/notification-type.enum'; import { CdFormGroup } from '../../../shared/forms/cd-form-group'; import { CdValidators } from '../../../shared/forms/cd-validators'; @@ -42,14 +43,19 @@ export class RoleFormComponent implements OnInit { roleFormMode = RoleFormMode; mode: RoleFormMode; + action: string; + resource: string; + constructor( private route: ActivatedRoute, private router: Router, private roleService: RoleService, private scopeService: ScopeService, private notificationService: NotificationService, - private i18n: I18n + private i18n: I18n, + public actionLabels: ActionLabelsI18n ) { + this.resource = this.i18n('role'); this.createForm(); this.listenToChanges(); } @@ -109,6 +115,9 @@ export class RoleFormComponent implements OnInit { ]; if (this.router.url.startsWith('/user-management/roles/edit')) { this.mode = this.roleFormMode.editing; + this.action = this.actionLabels.EDIT; + } else { + this.action = this.actionLabels.CREATE; } if (this.mode === this.roleFormMode.editing) { this.initEdit(); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.spec.ts index 58c0c17a8ab19..30048cf9bf7a6 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.spec.ts @@ -11,6 +11,7 @@ import { i18nProviders, PermissionHelper } from '../../../../testing/unit-test-helper'; +import { ActionLabels } from '../../../shared/constants/app.constants'; import { TableActionsComponent } from '../../../shared/datatable/table-actions/table-actions.component'; import { SharedModule } from '../../../shared/shared.module'; import { RoleDetailsComponent } from '../role-details/role-details.component'; @@ -59,8 +60,8 @@ describe('RoleListComponent', () => { ); scenario = { fn: () => tableActions.getCurrentButton().name, - single: 'Edit', - empty: 'Add' + single: ActionLabels.EDIT, + empty: ActionLabels.CREATE }; }); @@ -118,7 +119,7 @@ describe('RoleListComponent', () => { }); it(`shows always 'Edit' as main action`, () => { - scenario.empty = 'Edit'; + scenario.empty = ActionLabels.EDIT; permissionHelper.testScenarios(scenario); }); @@ -137,7 +138,7 @@ describe('RoleListComponent', () => { }); it(`shows always 'Add' as main action`, () => { - scenario.single = 'Add'; + scenario.single = ActionLabels.CREATE; permissionHelper.testScenarios(scenario); }); @@ -153,7 +154,7 @@ describe('RoleListComponent', () => { }); it(`shows always 'Edit' as main action`, () => { - scenario.empty = 'Edit'; + scenario.empty = ActionLabels.EDIT; permissionHelper.testScenarios(scenario); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts index ba7c1e9cff252..49b9c2ac468c5 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/role-list/role-list.component.ts @@ -7,6 +7,7 @@ import { forkJoin } from 'rxjs'; import { RoleService } from '../../../shared/api/role.service'; import { ScopeService } from '../../../shared/api/scope.service'; import { CriticalConfirmationModalComponent } from '../../../shared/components/critical-confirmation-modal/critical-confirmation-modal.component'; +import { ActionLabelsI18n } from '../../../shared/constants/app.constants'; import { CellTemplate } from '../../../shared/enum/cell-template.enum'; import { NotificationType } from '../../../shared/enum/notification-type.enum'; import { CdTableAction } from '../../../shared/models/cd-table-action'; @@ -16,11 +17,15 @@ import { Permission } from '../../../shared/models/permissions'; import { EmptyPipe } from '../../../shared/pipes/empty.pipe'; import { AuthStorageService } from '../../../shared/services/auth-storage.service'; import { NotificationService } from '../../../shared/services/notification.service'; +import { URLBuilderService } from '../../../shared/services/url-builder.service'; + +const BASE_URL = 'user-management/roles'; @Component({ selector: 'cd-role-list', templateUrl: './role-list.component.html', - styleUrls: ['./role-list.component.scss'] + styleUrls: ['./role-list.component.scss'], + providers: [{ provide: URLBuilderService, useValue: new URLBuilderService(BASE_URL) }] }) export class RoleListComponent implements OnInit { permission: Permission; @@ -39,29 +44,31 @@ export class RoleListComponent implements OnInit { private authStorageService: AuthStorageService, private modalService: BsModalService, private notificationService: NotificationService, - private i18n: I18n + private i18n: I18n, + private urlBuilder: URLBuilderService, + public actionLabels: ActionLabelsI18n ) { this.permission = this.authStorageService.getPermissions().user; const addAction: CdTableAction = { permission: 'create', icon: 'fa-plus', - routerLink: () => '/user-management/roles/add', - name: this.i18n('Add') + routerLink: () => this.urlBuilder.getCreate(), + name: this.actionLabels.CREATE }; const editAction: CdTableAction = { permission: 'update', icon: 'fa-pencil', disable: () => !this.selection.hasSingleSelection || this.selection.first().system, routerLink: () => - this.selection.first() && `/user-management/roles/edit/${this.selection.first().name}`, - name: this.i18n('Edit') + this.selection.first() && this.urlBuilder.getEdit(this.selection.first().name), + name: this.actionLabels.EDIT }; const deleteAction: CdTableAction = { permission: 'delete', icon: 'fa-times', disable: () => !this.selection.hasSingleSelection || this.selection.first().system, click: () => this.deleteRoleModal(), - name: this.i18n('Delete') + name: this.actionLabels.DELETE }; this.tableActions = [addAction, editAction, deleteAction]; } diff --git a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html index f35c2324122d7..902e5f358dc95 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/core/auth/user-form/user-form.component.html @@ -6,8 +6,8 @@ novalidate>
-

{mode, select, editing {Edit} other {Add}} User

+

{{ action | titlecase }} {{ resource | upperFirst }}

@@ -47,6 +47,7 @@ placeholder="Password..." id="password" name="password" + autocomplete="new-password" formControlName="password">