Skip to content

Commit

Permalink
mgr/dashboard: Role management from the UI
Browse files Browse the repository at this point in the history
Fixes: https://tracker.ceph.com/issues/24447

Signed-off-by: Ricardo Marques <rimarques@suse.com>
  • Loading branch information
ricardoasmarques committed Sep 5, 2018
1 parent 1ccceee commit 5d2b0eb
Show file tree
Hide file tree
Showing 31 changed files with 1,011 additions and 23 deletions.
34 changes: 28 additions & 6 deletions src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
Expand Up @@ -20,6 +20,8 @@ import { RgwDaemonListComponent } from './ceph/rgw/rgw-daemon-list/rgw-daemon-li
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 { 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';
Expand Down Expand Up @@ -177,16 +179,36 @@ const routes: Routes = [
}
]
},
// Administration
// Dashboard Settings
{
path: 'users',
path: 'user-management',
canActivate: [AuthGuardService],
canActivateChild: [AuthGuardService],
data: { breadcrumbs: 'Administration/Users' },
data: { breadcrumbs: 'User management', path: null },
children: [
{ path: '', component: UserListComponent },
{ path: 'add', component: UserFormComponent, data: { breadcrumbs: 'Add' } },
{ path: 'edit/:username', component: UserFormComponent, data: { breadcrumbs: 'Edit' } }
{
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' } }
]
}
]
},
// System
Expand Down
Expand Up @@ -8,8 +8,12 @@ import { BsDropdownModule, PopoverModule, TabsModule } from 'ngx-bootstrap';
import { SharedModule } from '../../shared/shared.module';
import { LoginComponent } from './login/login.component';
import { LogoutComponent } from './logout/logout.component';
import { RoleDetailsComponent } from './role-details/role-details.component';
import { RoleFormComponent } from './role-form/role-form.component';
import { RoleListComponent } from './role-list/role-list.component';
import { UserFormComponent } from './user-form/user-form.component';
import { UserListComponent } from './user-list/user-list.component';
import { UserTabsComponent } from './user-tabs/user-tabs.component';

@NgModule({
imports: [
Expand All @@ -22,7 +26,16 @@ import { UserListComponent } from './user-list/user-list.component';
TabsModule.forRoot(),
RouterModule
],
declarations: [LoginComponent, LogoutComponent, UserListComponent, UserFormComponent],
declarations: [
LoginComponent,
LogoutComponent,
RoleDetailsComponent,
RoleFormComponent,
RoleListComponent,
UserTabsComponent,
UserListComponent,
UserFormComponent
],
exports: [LogoutComponent]
})
export class AuthModule {}
@@ -0,0 +1,32 @@
<tabset *ngIf="selection?.hasSingleSelection">
<tab heading="Details" i18n-heading>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th></th>
<th class="text-center">Read</th>
<th class="text-center">Create</th>
<th class="text-center">Update</th>
<th class="text-center">Delete</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let scope of scopes">
<td i18n
class="bold col-sm-3">
{{ scope }}
</td>
<td class="col-sm-2 text-center"
*ngFor="let column of ['read', 'create', 'update', 'delete']">
<span *ngIf="selectedItem.scopes_permissions[scope] && selectedItem.scopes_permissions[scope].indexOf(column) !== -1">
<i class="fa fa-check-square-o" aria-hidden="true"></i>
</span>
<span *ngIf="!selectedItem.scopes_permissions[scope] || selectedItem.scopes_permissions[scope].indexOf(column) === -1">
<i class="fa fa-square-o" aria-hidden="true"></i>
</span>
</td>
</tr>
</tbody>
</table>
</tab>
</tabset>
@@ -0,0 +1,12 @@
@import '../../../../defaults';

thead {
background-color: $color-table-header-bg;
}

.fa {
font-size: large;
&.fa-square-o {
color: $color-light-gray;
}
}
@@ -0,0 +1,37 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { ToastModule } from 'ng2-toastr';
import { TabsModule } from 'ngx-bootstrap';

import { SharedModule } from '../../../shared/shared.module';
import { RoleDetailsComponent } from './role-details.component';

describe('RoleDetailsComponent', () => {
let component: RoleDetailsComponent;
let fixture: ComponentFixture<RoleDetailsComponent>;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
SharedModule,
ToastModule.forRoot(),
TabsModule.forRoot(),
RouterTestingModule,
HttpClientTestingModule
],
declarations: [RoleDetailsComponent]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(RoleDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,24 @@
import { Component, Input, OnChanges } from '@angular/core';

import { CdTableSelection } from '../../../shared/models/cd-table-selection';

@Component({
selector: 'cd-role-details',
templateUrl: './role-details.component.html',
styleUrls: ['./role-details.component.scss']
})
export class RoleDetailsComponent implements OnChanges {
@Input()
selection: CdTableSelection;
@Input()
scopes: Array<string>;
selectedItem: any;

constructor() {}

ngOnChanges() {
if (this.selection.hasSelection) {
this.selectedItem = this.selection.first();
}
}
}
@@ -0,0 +1,3 @@
export enum RoleFormMode {
editing = 'editing'
}
@@ -0,0 +1,120 @@
<div class="col-sm-12 col-lg-6">
<form name="roleForm"
class="form-horizontal"
#formDir="ngForm"
[formGroup]="roleForm"
novalidate>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span i18n>{mode, select, editing {Edit} other {Add}}</span> Role
</h3>
</div>
<div class="panel-body">

<!-- Name -->
<div class="form-group"
[ngClass]="{'has-error': roleForm.showError('name', formDir)}">
<label i18n
class="control-label col-sm-3"
for="name">Name
<span class="required"
*ngIf="mode !== roleFormMode.editing"></span>
</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
i18n-placeholder
placeholder="Name..."
id="name"
name="name"
formControlName="name"
autofocus>
<span i18n
class="help-block"
*ngIf="roleForm.showError('name', formDir, 'required')">
This field is required.
</span>
<span i18n
class="help-block"
*ngIf="roleForm.showError('name', formDir, 'notUnique')">
The chosen name is already in use.
</span>
</div>
</div>

<!-- Description -->
<div class="form-group"
[ngClass]="{'has-error': roleForm.showError('description', formDir)}">
<label i18n
class="control-label col-sm-3"
for="name">Description
</label>
<div class="col-sm-9">
<input class="form-control"
type="text"
i18n-placeholder
placeholder="Description..."
id="description"
name="description"
formControlName="description">
</div>
</div>

<!-- Permissions -->
<div class="form-group">
<label i18n
class="control-label col-sm-3"
for="name">Permissions
</label>
<div class="col-sm-9">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th></th>
<th class="text-center">Read</th>
<th class="text-center">Create</th>
<th class="text-center">Update</th>
<th class="text-center">Delete</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let scope of scopes">
<td i18n
class="bold col-sm-3">
{{ scope }}
</td>
<td class="col-sm-2 text-center clickable"
*ngFor="let column of ['read', 'create', 'update', 'delete']">
<div class="checkbox checkbox-primary">
<input type="checkbox"
[checked]="roleForm.getValue('scopes_permissions')[scope] && roleForm.getValue('scopes_permissions')[scope].indexOf(column) !== -1"
(change)="hadlePermissionClick(scope, column)">
<label></label>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>

</div>
<div class="panel-footer">
<div class="button-group text-right">
<cd-submit-button [form]="formDir"
type="button"
(submitAction)="submit()">
<span i18n>{mode, select, editing {Update} other {Create}}</span> Role
</cd-submit-button>
<button i18n
type="button"
class="btn btn-sm btn-default"
routerLink="/user-management/users/roles">
Back
</button>
</div>
</div>
</div>
</form>
</div>
@@ -0,0 +1,5 @@
@import '../../../../defaults';

thead {
background-color: $color-table-header-bg;
}

0 comments on commit 5d2b0eb

Please sign in to comment.