return isinstance(o, tuple) and hasattr(o, '_asdict') and hasattr(o, '_fields')
+class SerializableClass:
+ def __iter__(self):
+ for attr in self.__dict__:
+ if not attr.startswith("__"):
+ yield attr, getattr(self, attr)
+
+ def __contains__(self, value):
+ return value in self.__dict__
+
+ def __len__(self):
+ return len(self.__dict__)
+
+
def serialize(o, expected_type=None):
# pylint: disable=R1705,W1116
- print(o, expected_type)
if isnamedtuple(o):
hints = get_type_hints(o)
return {k: serialize(v, hints[k]) for k, v in zip(o._fields, o)}
# NOTE: we could add a metadata value in a list to indentify tuples and,
# sets if we wanted but for now let's go for lists.
return [serialize(i) for i in o]
+ elif isinstance(o, SerializableClass):
+ return {serialize(k): serialize(v) for k, v in o}
elif isinstance(o, (Iterator, Generator)):
return [serialize(i) for i in o]
elif expected_type and isclass(expected_type) and issubclass(expected_type, SecretStr):
cellTemplate: str = ''
isHidden: bool = False
filterable: bool = True
+ flexGrow: int = 1
class TableAction(NamedTuple):
routerLink: str # redirect to...
-class TableComponent(NamedTuple):
- columns: List[TableColumn] = []
- columnMode: str = 'flex'
- toolHeader: bool = True
+class TableComponent(SerializableClass):
+ def __init__(self) -> None:
+ self.columns: List[TableColumn] = []
+ self.columnMode: str = 'flex'
+ self.toolHeader: bool = True
class Icon(Enum):
return container_schema
-class CRUDMeta(NamedTuple):
- table: TableComponent = TableComponent()
- permissions: List[str] = []
- actions: List[Dict[str, Any]] = []
- forms: List[Dict[str, Any]] = []
+class CRUDMeta(SerializableClass):
+ def __init__(self):
+ self.table = TableComponent()
+ self.permissions = []
+ self.actions = []
+ self.forms = []
class CRUDCollectionMethod(NamedTuple):
doc: EndpointDoc
-class CRUDEndpoint(NamedTuple):
- router: APIRouter
- doc: APIDoc
- set_column: Optional[Dict[str, Dict[str, str]]] = None
- actions: List[TableAction] = []
- permissions: List[str] = []
- forms: List[Form] = []
- meta: CRUDMeta = CRUDMeta()
- get_all: Optional[CRUDCollectionMethod] = None
- create: Optional[CRUDCollectionMethod] = None
-
+class CRUDEndpoint:
# for testing purposes
CRUDClass: Optional[RESTController] = None
CRUDClassMetadata: Optional[RESTController] = None
- # ---------------------
- def __call__(self, cls: NamedTuple):
+ # pylint: disable=R0902
+ def __init__(self, router: APIRouter, doc: APIDoc,
+ set_column: Optional[Dict[str, Dict[str, str]]] = None,
+ actions: Optional[List[TableAction]] = None,
+ permissions: Optional[List[str]] = None, forms: Optional[List[Form]] = None,
+ meta: CRUDMeta = CRUDMeta(), get_all: Optional[CRUDCollectionMethod] = None,
+ create: Optional[CRUDCollectionMethod] = None):
+ self.router = router
+ self.doc = doc
+ self.set_column = set_column
+ if actions:
+ self.actions = actions
+ else:
+ self.actions = []
+
+ if forms:
+ self.forms = forms
+ else:
+ self.forms = []
+ self.meta = meta
+ self.get_all = get_all
+ self.create = create
+ if permissions:
+ self.permissions = permissions
+ else:
+ self.permissions = []
+
+ def __call__(self, cls: Any):
self.create_crud_class(cls)
self.meta.table.columns.extend(TableColumn(prop=field) for field in cls._fields)
cls.CRUDClass = CRUDClass
def create_meta_class(self, cls):
- outer_self: CRUDEndpoint = self
-
- @UIRouter(self.router.path, self.router.security_scope)
- class CRUDClassMetadata(RESTController):
- def list(self):
- self.update_columns()
- self.generate_actions()
- self.generate_forms()
- self.set_permissions()
- return serialize(outer_self.meta)
-
- def update_columns(self):
- if outer_self.set_column:
- for i, column in enumerate(outer_self.meta.table.columns):
- if column.prop in dict(outer_self.set_column):
- new_template = outer_self.set_column[column.prop]["cellTemplate"]
- new_column = TableColumn(column.prop,
- new_template,
- column.isHidden,
- column.filterable)
- outer_self.meta.table.columns[i] = new_column
-
- def generate_actions(self):
- outer_self.meta.actions.clear()
-
- for action in outer_self.actions:
- outer_self.meta.actions.append(action._asdict())
-
- def generate_forms(self):
- outer_self.meta.forms.clear()
-
- for form in outer_self.forms:
- outer_self.meta.forms.append(form.to_dict())
-
- def set_permissions(self):
- if outer_self.permissions:
- outer_self.meta.permissions.extend(outer_self.permissions)
-
- cls.CRUDClassMetadata = CRUDClassMetadata
+ def _list(self):
+ self.update_columns()
+ self.generate_actions()
+ self.generate_forms()
+ self.set_permissions()
+ return serialize(self.__class__.outer_self.meta)
+
+ def update_columns(self):
+ if self.__class__.outer_self.set_column:
+ for i, column in enumerate(self.__class__.outer_self.meta.table.columns):
+ if column.prop in dict(self.__class__.outer_self.set_column):
+ prop = self.__class__.outer_self.set_column[column.prop]
+ new_template = ""
+ if "cellTemplate" in prop:
+ new_template = prop["cellTemplate"]
+ hidden = prop['isHidden'] if 'isHidden' in prop else False
+ flex_grow = prop['flexGrow'] if 'flexGrow' in prop else column.flexGrow
+ new_column = TableColumn(column.prop,
+ new_template,
+ hidden,
+ column.filterable,
+ flex_grow)
+ self.__class__.outer_self.meta.table.columns[i] = new_column
+
+ def generate_actions(self):
+ self.__class__.outer_self.meta.actions.clear()
+
+ for action in self.__class__.outer_self.actions:
+ self.__class__.outer_self.meta.actions.append(action._asdict())
+
+ def generate_forms(self):
+ self.__class__.outer_self.meta.forms.clear()
+
+ for form in self.__class__.outer_self.forms:
+ self.__class__.outer_self.meta.forms.append(form.to_dict())
+
+ def set_permissions(self):
+ if self.__class__.outer_self.permissions:
+ self.outer_self.meta.permissions.extend(self.__class__.outer_self.permissions)
+ class_name = self.router.path.replace('/', '')
+ meta_class = type(f'{class_name}_CRUDClassMetadata',
+ (RESTController,),
+ {
+ 'list': _list,
+ 'update_columns': update_columns,
+ 'generate_actions': generate_actions,
+ 'generate_forms': generate_forms,
+ 'set_permissions': set_permissions,
+ 'outer_self': self,
+ })
+ UIRouter(self.router.path, self.router.security_scope)(meta_class)
+ cls.CRUDClassMetadata = meta_class
from ..security import Scope
from ..services.ceph_service import CephService, SendCommandError
from . import APIDoc, APIRouter, CRUDCollectionMethod, CRUDEndpoint, EndpointDoc, SecretStr
-from ._crud import ArrayHorizontalContainer, Form, FormField, Icon, TableAction, VerticalContainer
+from ._crud import ArrayHorizontalContainer, CRUDMeta, Form, FormField, Icon, \
+ TableAction, VerticalContainer
logger = logging.getLogger("controllers.ceph_users")
create=CRUDCollectionMethod(
func=CephUserEndpoints.user_create,
doc=EndpointDoc("Create Ceph User")
- )
+ ),
+ meta=CRUDMeta()
)
class CephUser(NamedTuple):
entity: str
import json
import logging
+from typing import Any, Dict, List, NamedTuple, Optional, Union
import cherrypy
from ..services.ceph_service import CephService
from ..services.rgw_client import NoRgwDaemonsException, RgwClient
from ..tools import json_str_to_object, str_to_bool
-from . import APIDoc, APIRouter, BaseController, Endpoint, EndpointDoc, \
- ReadPermission, RESTController, UIRouter, allow_empty_body
+from . import APIDoc, APIRouter, BaseController, CRUDCollectionMethod, \
+ CRUDEndpoint, Endpoint, EndpointDoc, ReadPermission, RESTController, \
+ UIRouter, allow_empty_body
+from ._crud import CRUDMeta
from ._version import APIVersion
-try:
- from typing import Any, Dict, List, Optional, Union
-except ImportError: # pragma: no cover
- pass # Just for type checking
-
logger = logging.getLogger("controllers.rgw")
RGW_SCHEMA = {
'subuser': subuser,
'purge-keys': purge_keys
}, json_response=False)
+
+
+class RGWRoleEndpoints:
+ @staticmethod
+ def role_list(_):
+ rgw_client = RgwClient.admin_instance()
+ roles = rgw_client.list_roles()
+ return roles
+
+
+@CRUDEndpoint(
+ router=APIRouter('/rgw/user/roles', Scope.RGW),
+ doc=APIDoc("List of RGW roles", "RGW"),
+ actions=[],
+ permissions=[Scope.CONFIG_OPT],
+ get_all=CRUDCollectionMethod(
+ func=RGWRoleEndpoints.role_list,
+ doc=EndpointDoc("List RGW roles")
+ ),
+ set_column={
+ "CreateDate": {'cellTemplate': 'date'},
+ "MaxSessionDuration": {'cellTemplate': 'duration'},
+ "AssumeRolePolicyDocument": {'isHidden': True}
+ },
+ meta=CRUDMeta()
+)
+class RgwUserRole(NamedTuple):
+ RoleId: int
+ RoleName: str
+ Path: str
+ Arn: str
+ CreateDate: str
+ MaxSessionDuration: int
+ AssumeRolePolicyDocument: str
+<cd-rgw-user-tabs></cd-rgw-user-tabs>
+
<cd-table #table
[autoReload]="false"
[data]="users"
--- /dev/null
+<ul class="nav nav-tabs">
+ <li class="nav-item">
+ <a class="nav-link"
+ routerLink="/rgw/user"
+ routerLinkActive="active"
+ ariaCurrentWhenActive="page"
+ [routerLinkActiveOptions]="{exact: true}"
+ i18n>Users</a>
+ </li>
+ <li class="nav-item">
+ <a class="nav-link"
+ routerLink="/rgw/user/roles"
+ routerLinkActive="active"
+ ariaCurrentWhenActive="page"
+ [routerLinkActiveOptions]="{exact: true}"
+ i18n>Roles</a>
+ </li>
+</ul>
--- /dev/null
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { RgwUserTabsComponent } from './rgw-user-tabs.component';
+
+describe('RgwUserTabsComponent', () => {
+ let component: RgwUserTabsComponent;
+ let fixture: ComponentFixture<RgwUserTabsComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [RgwUserTabsComponent]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RgwUserTabsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'cd-rgw-user-tabs',
+ templateUrl: './rgw-user-tabs.component.html',
+ styleUrls: ['./rgw-user-tabs.component.scss']
+})
+export class RgwUserTabsComponent {}
import { NgxPipeFunctionModule } from 'ngx-pipe-function';
import { ActionLabels, URLVerbs } from '~/app/shared/constants/app.constants';
+import { CRUDTableComponent } from '~/app/shared/datatable/crud-table/crud-table.component';
import { SharedModule } from '~/app/shared/shared.module';
import { PerformanceCounterModule } from '../performance-counter/performance-counter.module';
import { RgwBucketDetailsComponent } from './rgw-bucket-details/rgw-bucket-details.component';
import { RgwUserS3KeyModalComponent } from './rgw-user-s3-key-modal/rgw-user-s3-key-modal.component';
import { RgwUserSubuserModalComponent } from './rgw-user-subuser-modal/rgw-user-subuser-modal.component';
import { RgwUserSwiftKeyModalComponent } from './rgw-user-swift-key-modal/rgw-user-swift-key-modal.component';
+import { RgwUserTabsComponent } from './rgw-user-tabs/rgw-user-tabs.component';
@NgModule({
imports: [
RgwUserS3KeyModalComponent,
RgwUserCapabilityModalComponent,
RgwUserSubuserModalComponent,
- RgwConfigModalComponent
+ RgwConfigModalComponent,
+ RgwUserTabsComponent
]
})
export class RgwModule {}
path: `${URLVerbs.EDIT}/:uid`,
component: RgwUserFormComponent,
data: { breadcrumbs: ActionLabels.EDIT }
+ },
+ {
+ path: 'roles',
+ component: CRUDTableComponent,
+ data: {
+ breadcrumbs: 'Roles',
+ resource: 'api.rgw.user.roles@1.0',
+ tabs: [
+ {
+ name: 'Users',
+ url: '/rgw/user'
+ },
+ {
+ name: 'Roles',
+ url: '/rgw/user/roles'
+ }
+ ]
+ }
}
]
},
+<ul class="nav nav-tabs"
+ *ngIf="tabs">
+ <li class="nav-item"
+ *ngFor="let tab of tabs; keyvalue">
+ <a class="nav-link"
+ [routerLink]="tab.url"
+ routerLinkActive="active"
+ ariaCurrentWhenActive="page"
+ [routerLinkActiveOptions]="{exact: true}"
+ i18n>{{tab.name}}</a>
+ </li>
+</ul>
+
<ng-container *ngIf="meta">
<cd-table
[data]="data$ | async"
<ng-container *ngIf="!isLast"> </ng-container>
</span>
</ng-template>
+
+<ng-template #dateTpl
+ let-value="value">
+ <span>{{ value | cdDate }}</span>
+</ng-template>
+
+<ng-template #durationTpl
+ let-value="value">
+ <span>{{ value | duration }}</span>
+</ng-template>
export class CRUDTableComponent implements OnInit {
@ViewChild('badgeDictTpl')
public badgeDictTpl: TemplateRef<any>;
+ @ViewChild('dateTpl')
+ public dateTpl: TemplateRef<any>;
+ @ViewChild('durationTpl')
+ public durationTpl: TemplateRef<any>;
data$: Observable<any>;
meta$: Observable<CrudMetadata>;
permissions: Permissions;
permission: Permission;
selection = new CdTableSelection();
+ tabs = {};
constructor(
private authStorageService: AuthStorageService,
*/
this.activatedRoute.data.subscribe((data: any) => {
const resource: string = data.resource;
+ this.tabs = data.tabs;
this.dataGatewayService
.list(`ui-${resource}`)
- .subscribe((response: any) => this.processMeta(response));
+ .subscribe((response: CrudMetadata) => this.processMeta(response));
this.data$ = this.timerService.get(() => this.dataGatewayService.list(resource));
});
}
''
);
this.permission = this.permissions[toCamelCase(meta.permissions[0])];
- this.meta = meta;
const templates = {
- badgeDict: this.badgeDictTpl
+ badgeDict: this.badgeDictTpl,
+ date: this.dateTpl,
+ duration: this.durationTpl
};
- this.meta.table.columns.forEach((element, index) => {
+ meta.table.columns.forEach((element, index) => {
if (element['cellTemplate'] !== undefined) {
- this.meta.table.columns[index]['cellTemplate'] =
- templates[element['cellTemplate'] as string];
+ meta.table.columns[index]['cellTemplate'] = templates[element['cellTemplate'] as string];
}
});
+ // isHidden flag does not work as expected somehow so the best ways to enforce isHidden is
+ // to filter the columns manually instead of letting isHidden flag inside table.component to
+ // work.
+ meta.table.columns = meta.table.columns.filter((col: any) => {
+ return !col['isHidden'];
+ });
+ this.meta = meta;
}
}
- jwt: []
tags:
- RgwUser
+ /api/rgw/user/roles:
+ get:
+ parameters: []
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ summary: List RGW roles
+ tags:
+ - RGW
/api/rgw/user/{uid}:
delete:
parameters:
name: Prometheus
- description: Prometheus Notifications Management API
name: PrometheusNotifications
+- description: List of RGW roles
+ name: RGW
- description: RBD Management API
name: Rbd
- description: RBD Mirroring Management API
except RequestException as e:
raise DashboardException(msg=str(e), component='rgw')
+ def list_roles(self) -> List[Dict[str, Any]]:
+ rgw_list_roles_command = ['role', 'list']
+ code, roles, err = mgr.send_rgwadmin_command(rgw_list_roles_command)
+ if code < 0:
+ logger.warning('Error listing roles with code %d: %s', code, err)
+ return []
+
+ return roles
+
def perform_validations(self, retention_period_days, retention_period_years, mode):
try:
retention_period_days = int(retention_period_days) if retention_period_days else 0