From: Ernesto Puerta Date: Tue, 26 Mar 2019 18:01:01 +0000 (+0100) Subject: mgr/dashboard: unify button/URL actions naming X-Git-Tag: v15.0.0~3^2~1 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=33466883948a4155ee2f7716effe1d1f5aa00cc6;p=ceph.git 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 --- 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 89b4e7dd1956..944847ff681d 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 4c706d4ae629..b4322d2d0fdf 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 ef0696a6ab10..53e83cc8d834 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 bd68a3399775..5737f4852197 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 7caad726a4c2..b567ddd96eae 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 3739d0eda62e..8bda29553c72 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 93cd64b26d47..346a6bd65067 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 335798f9c3d8..0ba72c8ef679 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 1ba6085070ed..47772304b505 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 31f2eb94df01..4806bf21d062 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 a573dbdc1ca6..3a2a0f0e8dfa 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 8f05cab50aa3..46c3ee99f8be 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 a8975a1b9bdc..7a6789f4b896 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 c2b198d06ffc..0805c5e87118 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 cf950177470a..8850fc71f93c 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 080c2b144e11..ad10e070f7e6 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 609082658879..25ce79a373df 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 93aa4c887ac3..cdba46c2e2c8 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 236670cb76a6..2f9eefa9dcbb 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 a1b02c7be2b6..29efb82c6df9 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 58c0c17a8ab1..30048cf9bf7a 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 ba7c1e9cff25..49b9c2ac468c 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 f35c2324122d..902e5f358dc9 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">