From 9ce176eff71d2241256e6a08a07bdb034a7b0de2 Mon Sep 17 00:00:00 2001 From: Alexander Kharkevich Date: Mon, 8 Apr 2024 10:35:11 -0400 Subject: [PATCH] feat: ui integration (#8) * feat: integration with backendd * chore: refactoring * chore: refactoring * fix: allow use experiment_name and experiment_id simultaneously * chore: refactoring * chore: refactoring * fix: fix CRUD * fix: fix build script --------- Co-authored-by: Aleksei Moiseev --- mlflow_oidc_auth/views.py | 36 ++-- web-ui/package.json | 2 +- web-ui/src/app/app-routing.module.ts | 2 +- web-ui/src/app/app.component.html | 19 ++- web-ui/src/app/app.component.scss | 3 + web-ui/src/app/app.component.ts | 19 ++- web-ui/src/app/app.module.ts | 5 + web-ui/src/app/core/configs/api-urls.ts | 18 ++ web-ui/src/app/core/configs/core.ts | 9 + web-ui/src/app/core/configs/permissions.ts | 20 +++ .../admin-page/admin-page-routing.module.ts | 18 +- .../features/admin-page/admin-page.module.ts | 28 ++-- .../admin-page/admin-page.component.html | 1 - .../admin-page/admin-page.component.ts | 31 +--- ...periment-permission-details.component.html | 6 +- ...experiment-permission-details.component.ts | 119 +++++++++---- .../experiment-permission-details.config.ts | 16 ++ .../model-permision-details.component.ts | 54 ------ .../model-permission-details.component.html} | 7 +- .../model-permission-details.component.scss} | 0 ...odel-permission-details.component.spec.ts} | 10 +- .../model-permission-details.component.ts | 118 +++++++++++++ .../model-permission-details.config.ts | 17 ++ .../user-permission-details.component.html | 65 ++++---- .../user-permission-details.component.ts | 156 ++++++------------ .../user-permission-details.config.ts | 9 + .../features/admin-page/components/index.ts | 10 ++ .../experiment-permissions.component.html | 5 +- .../experiment-permissions.component.ts | 43 +++-- .../experiment-permissions.config.ts | 10 ++ .../model-permissions.component.html | 4 +- .../model-permissions.component.ts | 44 +++-- .../model-permissions.config.ts | 11 ++ .../permissions/permissions.component.ts | 31 +--- .../user-permissions.component.html | 5 +- .../user-permissions.component.ts | 47 ++++-- .../user-permissions.config.ts | 13 ++ .../home-page/home-page.component.html | 38 +++-- .../home-page/home-page.component.scss | 24 ++- .../home-page/home-page.component.ts | 72 +++----- .../components/home-page/home-page.config.ts | 9 + .../features/home-page/home-page.module.ts | 2 - .../action-table/action-table.component.html | 15 -- .../action-table.component.spec.ts | 23 --- .../action-table/action-table.component.ts | 15 -- .../edit-permissions-modal.component.html | 16 -- .../components/header/header.component.html | 5 +- web-ui/src/app/shared/components/index.ts | 10 +- .../access-key-modal.component.html | 0 .../access-key-modal.component.scss | 0 .../access-key-modal.component.spec.ts | 0 .../access-key-modal.component.ts | 5 +- .../access-key-modal.interface.ts | 3 + .../confirm-modal.component.html | 8 + .../confirm-modal.component.scss} | 0 .../confirm-modal.component.spec.ts} | 12 +- .../confirm-modal/confirm-modal.component.ts | 10 ++ .../edit-permissions-modal.component.html | 13 ++ .../edit-permissions-modal.component.scss | 0 .../edit-permissions-modal.component.spec.ts | 0 .../edit-permissions-modal.component.ts | 20 ++- .../edit-permissions-modal.interface.ts | 9 + .../grant-permission-modal.component.html | 8 +- .../grant-permission-modal.component.scss | 0 .../grant-permission-modal.component.spec.ts | 0 .../grant-permission-modal.component.ts | 16 +- .../grant-permission-modal.config.ts} | 0 .../grant-permission-modal.inteface.ts | 7 + .../grant-user-permissions.component.html | 23 +++ .../grant-user-permissions.component.scss | 0 .../grant-user-permissions.component.spec.ts | 23 +++ .../grant-user-permissions.component.ts | 35 ++++ .../components/table/table.component.html | 25 ++- .../components/table/table.component.spec.ts | 6 +- .../components/table/table.component.ts | 45 ++--- .../shared/components/table/table.config.ts | 40 +++++ .../components/table/table.interface.ts | 17 ++ .../error-handler.interceptor.spec.ts | 16 ++ .../interceptors/error-handler.interceptor.ts | 27 +++ .../app/shared/interfaces/data.interfaces.ts | 38 ----- .../interfaces/experiments-data.interface.ts | 24 +++ .../interfaces/models-data.interface.ts | 25 +++ .../interfaces/permission-data.interface.ts | 12 ++ .../shared/interfaces/user-data.interface.ts | 28 ++++ .../app/shared/material/material.module.ts | 2 + .../shared/pipes/table-search.pipe.spec.ts | 8 - .../src/app/shared/pipes/table-search.pipe.ts | 13 -- .../src/app/shared/services/auth.service.ts | 10 +- .../src/app/shared/services/data.service.ts | 58 ------- .../services/data/experiments-data.service.ts | 39 +++++ .../services/data/models-data.service.ts | 35 ++++ .../services/data/permission-data.service.ts | 44 +++++ .../shared/services/data/user-data.service.ts | 26 +++ web-ui/src/app/shared/services/index.ts | 6 +- .../services/{ => specs}/auth.service.spec.ts | 2 +- .../specs/experiments-data.service.spec.ts | 16 ++ .../specs/models-data.service.spec.ts | 16 ++ .../specs/permission-data.service.spec.ts | 16 ++ .../services/specs/snack-bar.service.spec.ts | 16 ++ .../user-data.service.spec.ts} | 8 +- .../services/utility/snack-bar.service.ts | 20 +++ web-ui/src/app/shared/shared.module.ts | 16 +- 102 files changed, 1332 insertions(+), 754 deletions(-) create mode 100644 web-ui/src/app/core/configs/api-urls.ts create mode 100644 web-ui/src/app/core/configs/core.ts create mode 100644 web-ui/src/app/core/configs/permissions.ts create mode 100644 web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.config.ts delete mode 100644 web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.ts rename web-ui/src/app/features/admin-page/components/details/{model-permision-details/model-permision-details.component.html => model-permission-details/model-permission-details.component.html} (65%) rename web-ui/src/app/features/admin-page/components/{admin-page/admin-page.component.scss => details/model-permission-details/model-permission-details.component.scss} (100%) rename web-ui/src/app/features/admin-page/components/details/{model-permision-details/model-permision-details.component.spec.ts => model-permission-details/model-permission-details.component.spec.ts} (53%) create mode 100644 web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.ts create mode 100644 web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.config.ts create mode 100644 web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.config.ts create mode 100644 web-ui/src/app/features/admin-page/components/index.ts create mode 100644 web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.config.ts create mode 100644 web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.config.ts create mode 100644 web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.config.ts create mode 100644 web-ui/src/app/features/home-page/components/home-page/home-page.config.ts delete mode 100644 web-ui/src/app/shared/components/action-table/action-table.component.html delete mode 100644 web-ui/src/app/shared/components/action-table/action-table.component.spec.ts delete mode 100644 web-ui/src/app/shared/components/action-table/action-table.component.ts delete mode 100644 web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.html rename web-ui/src/app/shared/components/{ => modals}/access-key-modal/access-key-modal.component.html (100%) rename web-ui/src/app/shared/components/{ => modals}/access-key-modal/access-key-modal.component.scss (100%) rename web-ui/src/app/shared/components/{ => modals}/access-key-modal/access-key-modal.component.spec.ts (100%) rename web-ui/src/app/shared/components/{ => modals}/access-key-modal/access-key-modal.component.ts (90%) create mode 100644 web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.interface.ts create mode 100644 web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.html rename web-ui/src/app/{features/admin-page/components/details/model-permision-details/model-permision-details.component.scss => shared/components/modals/confirm-modal/confirm-modal.component.scss} (100%) rename web-ui/src/app/{features/admin-page/components/admin-page/admin-page.component.spec.ts => shared/components/modals/confirm-modal/confirm-modal.component.spec.ts} (51%) create mode 100644 web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.ts create mode 100644 web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.html rename web-ui/src/app/shared/components/{ => modals}/edit-permissions-modal/edit-permissions-modal.component.scss (100%) rename web-ui/src/app/shared/components/{ => modals}/edit-permissions-modal/edit-permissions-modal.component.spec.ts (100%) rename web-ui/src/app/shared/components/{ => modals}/edit-permissions-modal/edit-permissions-modal.component.ts (52%) create mode 100644 web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.interface.ts rename web-ui/src/app/shared/components/{ => modals}/grant-permissoin-modal/grant-permission-modal.component.html (73%) rename web-ui/src/app/shared/components/{ => modals}/grant-permissoin-modal/grant-permission-modal.component.scss (100%) rename web-ui/src/app/shared/components/{ => modals}/grant-permissoin-modal/grant-permission-modal.component.spec.ts (100%) rename web-ui/src/app/shared/components/{ => modals}/grant-permissoin-modal/grant-permission-modal.component.ts (65%) rename web-ui/src/app/shared/components/{action-table/action-table.component.scss => modals/grant-permissoin-modal/grant-permission-modal.config.ts} (100%) create mode 100644 web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.inteface.ts create mode 100644 web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.html create mode 100644 web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.scss create mode 100644 web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.spec.ts create mode 100644 web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.ts create mode 100644 web-ui/src/app/shared/components/table/table.config.ts create mode 100644 web-ui/src/app/shared/components/table/table.interface.ts create mode 100644 web-ui/src/app/shared/interceptors/error-handler.interceptor.spec.ts create mode 100644 web-ui/src/app/shared/interceptors/error-handler.interceptor.ts delete mode 100644 web-ui/src/app/shared/interfaces/data.interfaces.ts create mode 100644 web-ui/src/app/shared/interfaces/experiments-data.interface.ts create mode 100644 web-ui/src/app/shared/interfaces/models-data.interface.ts create mode 100644 web-ui/src/app/shared/interfaces/permission-data.interface.ts create mode 100644 web-ui/src/app/shared/interfaces/user-data.interface.ts delete mode 100644 web-ui/src/app/shared/pipes/table-search.pipe.spec.ts delete mode 100644 web-ui/src/app/shared/pipes/table-search.pipe.ts delete mode 100644 web-ui/src/app/shared/services/data.service.ts create mode 100644 web-ui/src/app/shared/services/data/experiments-data.service.ts create mode 100644 web-ui/src/app/shared/services/data/models-data.service.ts create mode 100644 web-ui/src/app/shared/services/data/permission-data.service.ts create mode 100644 web-ui/src/app/shared/services/data/user-data.service.ts rename web-ui/src/app/shared/services/{ => specs}/auth.service.spec.ts (86%) create mode 100644 web-ui/src/app/shared/services/specs/experiments-data.service.spec.ts create mode 100644 web-ui/src/app/shared/services/specs/models-data.service.spec.ts create mode 100644 web-ui/src/app/shared/services/specs/permission-data.service.spec.ts create mode 100644 web-ui/src/app/shared/services/specs/snack-bar.service.spec.ts rename web-ui/src/app/shared/services/{data.service.spec.ts => specs/user-data.service.spec.ts} (52%) create mode 100644 web-ui/src/app/shared/services/utility/snack-bar.service.ts diff --git a/mlflow_oidc_auth/views.py b/mlflow_oidc_auth/views.py index fcfb5f0..9418b8f 100644 --- a/mlflow_oidc_auth/views.py +++ b/mlflow_oidc_auth/views.py @@ -53,6 +53,12 @@ _logger = logging.getLogger(__name__) +def _get_experiment_id(request_data: dict) -> str: + experiment_id = request_data.get("experiment_id") + if "experiment_id" not in request_data: + experiment_id = mlflow_client.get_experiment_by_name(request_data.get("experiment_name")).experiment_id + return experiment_id + def _get_request_param(param: str) -> str: if request.method == "GET": args = request.args @@ -208,16 +214,12 @@ def make_basic_auth_response() -> Response: def create_experiment_permission(): request_data = request.get_json() - # Get the experiment - experiment = mlflow_client.get_experiment_by_name(request_data.get("experiment_name")) - - # # Update the experiment store.create_experiment_permission( - experiment.experiment_id, + _get_experiment_id(request_data), request_data.get("user_name"), request_data.get("new_permission"), ) - return "Experiment permission has been created." + return jsonify({"message": "Experiment permission has been created."}) # Experiment views @@ -517,29 +519,21 @@ def _password_generation(): def update_experiment_permission(): request_data = request.get_json() - # Get the experiment - experiment = mlflow_client.get_experiment_by_name(request_data.get("experiment_name")) - - # # Update the experiment store.update_experiment_permission( - experiment.experiment_id, + _get_experiment_id(request_data), request_data.get("user_name"), request_data.get("new_permission"), ) - return "Experiment permission has been changed." + return jsonify({"message": "Experiment permission has been changed."}) def delete_experiment_permission(): request_data = request.get_json() - # Get the experiment - experiment = mlflow_client.get_experiment_by_name(request_data.get("experiment_name")) - - # # Update the experiment store.delete_experiment_permission( - experiment.experiment_id, + _get_experiment_id(request_data), request_data.get("user_name"), ) - return "Experiment permission has been deleted." + return jsonify({"message": "Experiment permission has been deleted."}) def create_model_permission(): @@ -550,7 +544,7 @@ def create_model_permission(): request_data.get("user_name"), request_data.get("new_permission"), ) - return "Model permission has been created." + return jsonify({"message": "Model permission has been created."}) def get_model_permission(): @@ -571,7 +565,7 @@ def update_model_permission(): request_data.get("user_name"), request_data.get("new_permission"), ) - return "Model permission has been changed." + return jsonify({"message": "Model permission has been changed."}) def delete_model_permission(): @@ -581,4 +575,4 @@ def delete_model_permission(): request_data.get("model_name"), request_data.get("user_name"), ) - return "Model permission has been deleted." + return jsonify({"message": "Model permission has been deleted."}) diff --git a/web-ui/package.json b/web-ui/package.json index 3a203b8..aa4052d 100644 --- a/web-ui/package.json +++ b/web-ui/package.json @@ -8,7 +8,7 @@ "build": "ng build --base-href .", "clean": "rimraf ../mlflow_oidc_auth/ui", "release": "yarn clean && ng build --base-href .", - "watch": "ng build --watch --base-href . --configuration development", + "watch": "yarn clean && ng build --watch --base-href . --configuration development", "test": "ng test" }, "private": true, diff --git a/web-ui/src/app/app-routing.module.ts b/web-ui/src/app/app-routing.module.ts index a64c9d4..66a14f5 100644 --- a/web-ui/src/app/app-routing.module.ts +++ b/web-ui/src/app/app-routing.module.ts @@ -11,7 +11,7 @@ const routes: Routes = [ ]; @NgModule({ - imports: [RouterModule.forRoot(routes)], + imports: [RouterModule.forRoot(routes, { useHash: true })], exports: [RouterModule], }) export class AppRoutingModule {} diff --git a/web-ui/src/app/app.component.html b/web-ui/src/app/app.component.html index 53d8651..a78643b 100644 --- a/web-ui/src/app/app.component.html +++ b/web-ui/src/app/app.component.html @@ -1,4 +1,15 @@ -
- - -
+ + +
+ + +
+ +
+ + + +
+ +
+
diff --git a/web-ui/src/app/app.component.scss b/web-ui/src/app/app.component.scss index e69de29..f4e41f7 100644 --- a/web-ui/src/app/app.component.scss +++ b/web-ui/src/app/app.component.scss @@ -0,0 +1,3 @@ +.loader { + height: 100vh; +} diff --git a/web-ui/src/app/app.component.ts b/web-ui/src/app/app.component.ts index eaa5f12..dcabe39 100644 --- a/web-ui/src/app/app.component.ts +++ b/web-ui/src/app/app.component.ts @@ -1,5 +1,8 @@ import { Component, OnInit } from '@angular/core'; -import { AuthService, DataService } from './shared/services'; +import { AuthService } from './shared/services'; +import { UserDataService } from './shared/services'; +import { finalize } from 'rxjs'; +import { CurrentUserModel } from './shared/interfaces/user-data.interface'; @Component({ selector: 'app-root', @@ -8,20 +11,24 @@ import { AuthService, DataService } from './shared/services'; }) export class AppComponent implements OnInit { title = 'mlflow-oidc-auth-front'; - - name: string = ''; + loading = false; + user!: CurrentUserModel; constructor( - private readonly dataService: DataService, + private readonly userDataService: UserDataService, private readonly authService: AuthService, ) { } ngOnInit(): void { - this.dataService.getCurrentUser() + this.loading = false; + this.userDataService.getCurrentUser() + .pipe( + finalize(() => this.loading = false), + ) .subscribe((userInfo) => { this.authService.setUserInfo(userInfo); - this.name = userInfo.display_name; + this.user = userInfo; }); } } diff --git a/web-ui/src/app/app.module.ts b/web-ui/src/app/app.module.ts index bf56eb1..147fc93 100644 --- a/web-ui/src/app/app.module.ts +++ b/web-ui/src/app/app.module.ts @@ -5,6 +5,8 @@ import { AppRoutingModule } from './app-routing.module'; import { AppComponent } from './app.component'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { SharedModule } from './shared/shared.module'; +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { ErrorHandlerInterceptor } from './shared/interceptors/error-handler.interceptor'; @NgModule({ declarations: [ @@ -16,6 +18,9 @@ import { SharedModule } from './shared/shared.module'; BrowserAnimationsModule, SharedModule, ], + providers: [ + { provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerInterceptor, multi: true } + ], bootstrap: [AppComponent] }) export class AppModule { } diff --git a/web-ui/src/app/core/configs/api-urls.ts b/web-ui/src/app/core/configs/api-urls.ts new file mode 100644 index 0000000..3095378 --- /dev/null +++ b/web-ui/src/app/core/configs/api-urls.ts @@ -0,0 +1,18 @@ +export const API_URL = { + ALL_EXPERIMENTS: '/api/2.0/mlflow/experiments', + EXPERIMENTS_FOR_USER: '/api/2.0/mlflow/users/${userName}/experiments', + USERS_FOR_EXPERIMENT: '/api/2.0/mlflow/experiments/${experimentName}/users', + ALL_MODELS: '/api/2.0/mlflow/registered-models', + MODELS_FOR_USER: '/api/2.0/mlflow/users/${userName}/registered-models', + USERS_FOR_MODEL: '/api/2.0/mlflow/registered-models/${modelName}/users', + CREATE_EXPERIMENT_PERMISSION: '/api/2.0/mlflow/experiments/permissions/create', + CREATE_MODEL_PERMISSION: '/api/2.0/mlflow/registered-models/permissions/create', + UPDATE_EXPERIMENT_PERMISSION: '/api/2.0/mlflow/experiments/permissions/update', + UPDATE_MODEL_PERMISSION: '/api/2.0/mlflow/registered-models/permissions/update', + DELETE_EXPERIMENT_PERMISSION: '/api/2.0/mlflow/experiments/permissions/delete', + DELETE_MODEL_PERMISSION: '/api/2.0/mlflow/registered-models/permissions/delete', + + GET_ALL_USERS: '/api/2.0/mlflow/users', + GET_ACCESS_TOKEN: '/api/2.0/mlflow/users/access-token', + GET_CURRENT_USER: '/api/2.0/mlflow/users/current', +}; diff --git a/web-ui/src/app/core/configs/core.ts b/web-ui/src/app/core/configs/core.ts new file mode 100644 index 0000000..35dd936 --- /dev/null +++ b/web-ui/src/app/core/configs/core.ts @@ -0,0 +1,9 @@ +export const CORE_CONFIGS = { + SNACK_BAR_DURATION: 7_000, + DEBOUNCE_TIME: 300, +} + +export enum EntityEnum { + EXPERIMENT = 'experiment', + MODEL = 'model', +} diff --git a/web-ui/src/app/core/configs/permissions.ts b/web-ui/src/app/core/configs/permissions.ts new file mode 100644 index 0000000..284188c --- /dev/null +++ b/web-ui/src/app/core/configs/permissions.ts @@ -0,0 +1,20 @@ +export enum PermissionEnum { + EDIT = 'EDIT', + READ = 'READ', + MANAGE = 'MANAGE', +} + +export const PERMISSIONS = [ + { + value: PermissionEnum.EDIT, + title: 'Edit' + }, + { + value: PermissionEnum.READ, + title: 'Read' + }, + { + value: PermissionEnum.MANAGE, + title: 'Manage' + } +] diff --git a/web-ui/src/app/features/admin-page/admin-page-routing.module.ts b/web-ui/src/app/features/admin-page/admin-page-routing.module.ts index 0cf3762..a6ebb16 100644 --- a/web-ui/src/app/features/admin-page/admin-page-routing.module.ts +++ b/web-ui/src/app/features/admin-page/admin-page-routing.module.ts @@ -1,14 +1,12 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; -import { AdminPageComponent } from './components/admin-page/admin-page.component'; -import { PermissionsComponent } from './components/permissions/permissions.component'; import { - UserPermissionDetailsComponent -} from './components/details/user-permission-details/user-permission-details.component'; -import { - ExperimentPermissionsComponent -} from './components/permissions/experiment-permissions/experiment-permissions.component'; -import { ModelPermissionsComponent } from './components/permissions/model-permissions/model-permissions.component'; + AdminPageComponent, + ExperimentPermissionDetailsComponent, + ModelPermissionDetailsComponent, + PermissionsComponent, + UserPermissionDetailsComponent, +} from './components'; const routes: Routes = [ @@ -26,11 +24,11 @@ const routes: Routes = [ }, { path: 'experiment/:id', - component: ExperimentPermissionsComponent, + component: ExperimentPermissionDetailsComponent, }, { path: 'model/:id', - component: ModelPermissionsComponent, + component: ModelPermissionDetailsComponent, }, { path: '**', diff --git a/web-ui/src/app/features/admin-page/admin-page.module.ts b/web-ui/src/app/features/admin-page/admin-page.module.ts index 81d03b1..4eb06e2 100644 --- a/web-ui/src/app/features/admin-page/admin-page.module.ts +++ b/web-ui/src/app/features/admin-page/admin-page.module.ts @@ -1,22 +1,20 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; - -import { AdminPageRoutingModule } from './admin-page-routing.module'; -import { AdminPageComponent } from './components/admin-page/admin-page.component'; -import { SharedModule } from '../../shared/shared.module'; -import { UserPermissionsComponent } from './components/permissions/user-permissions/user-permissions.component'; -import { - ExperimentPermissionsComponent, -} from './components/permissions/experiment-permissions/experiment-permissions.component'; -import { ModelPermissionsComponent } from './components/permissions/model-permissions/model-permissions.component'; import { FormsModule } from '@angular/forms'; -import { UserPermissionDetailsComponent } from './components/details/user-permission-details/user-permission-details.component'; -import { PermissionsComponent } from './components/permissions/permissions.component'; +import { RouterModule } from '@angular/router'; + import { + AdminPageComponent, ExperimentPermissionDetailsComponent, -} from './components/details/experiment-permission-details/experiment-permission-details.component'; -import { ModelPermisionDetailsComponent } from './components/details/model-permision-details/model-permision-details.component'; -import { RouterModule } from '@angular/router'; + ExperimentPermissionsComponent, + ModelPermissionDetailsComponent, + ModelPermissionsComponent, + PermissionsComponent, + UserPermissionDetailsComponent, + UserPermissionsComponent, +} from './components'; +import { AdminPageRoutingModule } from './admin-page-routing.module'; +import { SharedModule } from '../../shared/shared.module'; @NgModule({ @@ -28,7 +26,7 @@ import { RouterModule } from '@angular/router'; UserPermissionDetailsComponent, PermissionsComponent, ExperimentPermissionDetailsComponent, - ModelPermisionDetailsComponent, + ModelPermissionDetailsComponent, ], imports: [ CommonModule, diff --git a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.html b/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.html index 0680b43..e69de29 100644 --- a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.html +++ b/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.html @@ -1 +0,0 @@ - diff --git a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.ts b/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.ts index e7c12ee..91bdba0 100644 --- a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.ts +++ b/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.ts @@ -1,32 +1,7 @@ -import { Component, OnInit } from '@angular/core'; -import { MatTabChangeEvent } from '@angular/material/tabs'; -import { ActivatedRoute, Router } from '@angular/router'; +import { Component } from '@angular/core'; @Component({ selector: 'ml-admin-page', - templateUrl: './admin-page.component.html', - styleUrls: ['./admin-page.component.scss'], + template: '', }) -export class AdminPageComponent implements OnInit { - - constructor( - private readonly router: Router, - private readonly route: ActivatedRoute, - ) { - } - - ngOnInit(): void { - } - - foo(event: MatTabChangeEvent) { - const mapping: { [key: number]: string } = { - 0: '/admin/user-permissions', - 1: '/admin/experiments-permissions', - 2: '/admin/models-permissions', - } - const route = mapping[event.index]; - if (route) { - void this.router.navigate([route], { relativeTo: this.route }); - } - } -} +export class AdminPageComponent {} diff --git a/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.html b/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.html index 4b9784f..e5719c1 100644 --- a/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.html +++ b/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.html @@ -1,7 +1,7 @@

Experiment Access

- @@ -10,7 +10,7 @@

Experiment Access

diff --git a/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.ts b/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.ts index fe93c37..c718423 100644 --- a/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.ts +++ b/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.component.ts @@ -1,6 +1,22 @@ import { Component, OnInit } from '@angular/core'; -import { EditPermissionsModalComponent } from '../../../../../shared/components'; import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute } from '@angular/router'; + +import { + EditPermissionsModalComponent, + GrantUserPermissionsComponent, + GrantUserPermissionsModel, +} from 'src/app/shared/components'; +import { ExperimentsDataService, PermissionDataService, UserDataService } from 'src/app/shared/services'; +import { TableActionEvent, TableActionModel } from 'src/app/shared/components/table/table.interface'; +import { filter, switchMap } from 'rxjs'; +import { TableActionEnum } from 'src/app/shared/components/table/table.config'; +import { EntityEnum } from 'src/app/core/configs/core'; +import { COLUMN_CONFIG, TABLE_ACTIONS } from './experiment-permission-details.config'; +import { + PermissionsDialogData, +} from 'src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.interface'; +import { PermissionEnum } from 'src/app/core/configs/permissions'; @Component({ selector: 'ml-experiment-permission-details', @@ -8,43 +24,90 @@ import { MatDialog } from '@angular/material/dialog'; styleUrls: ['./experiment-permission-details.component.scss'] }) export class ExperimentPermissionDetailsComponent implements OnInit { - userColumnConfig = [ - { - title: 'User name', - key: 'userName', - }, { - title: 'Permissions', - key: 'permissions', - }, - ]; - - userDataSource = [ - { - userName: 'Test 0', - permissions: 'admin', - }, - { - userName: 'Test 1', - permissions: 'admin', - }, - { - userName: 'Test 2', - permissions: 'admin', - }, - ]; + experimentId!: string; + userColumnConfig = COLUMN_CONFIG; + actions: TableActionModel[] = TABLE_ACTIONS; + userDataSource: { permission: string, username: string }[] = []; constructor( - private dialog: MatDialog, + private readonly dialog: MatDialog, + private readonly experimentDataService: ExperimentsDataService, + private readonly permissionDataService: PermissionDataService, + private readonly userDataService: UserDataService, + private readonly route: ActivatedRoute, ) { } ngOnInit(): void { + this.experimentId = this.route.snapshot.paramMap.get('id') ?? ''; + + this.experimentDataService + .getUsersForExperiment(this.experimentId) + .subscribe((users) => { + this.userDataSource = users; + }); } - handleUserEdit($event: any) { + + handleUserEdit(event: { permission: string; username: string }) { + const data: PermissionsDialogData = { + userName: event.username, + entityName: this.experimentId, + entityType: EntityEnum.EXPERIMENT, + permission: event.permission as PermissionEnum, + }; + this.dialog - .open(EditPermissionsModalComponent) + .open(EditPermissionsModalComponent, { data }) .afterClosed() + .pipe( + switchMap((data) => this.permissionDataService.updateExperimentPermission({ + user_name: event.username, + experiment_id: this.experimentId, + new_permission: data.permission, + })) + ) .subscribe((data) => { console.log(data) }) } + + handleActions($event: TableActionEvent<{ permission: string; username: string }>) { + const actionMapping: { [key: string]: any } = { + [TableActionEnum.EDIT]: this.handleUserEdit.bind(this), + [TableActionEnum.REVOKE]: this.revokePermissionForUser.bind(this), + } + + const selectedAction = actionMapping[$event.action.action]; + if (selectedAction) { + selectedAction($event.item); + } + } + + revokePermissionForUser(item: any) { + this.permissionDataService.deleteExperimentPermission( + { experiment_id: this.experimentId, user_name: item.username }) + .subscribe(console.log); + } + + addUser() { + this.userDataService.getAllUsers() + .pipe( + switchMap(({ users }) => this.dialog.open(GrantUserPermissionsComponent, + { data: { users } }) + .afterClosed()), + filter(Boolean), + switchMap(({ user, permission }) => this.permissionDataService.createExperimentPermission({ + experiment_id: this.experimentId, + new_permission: permission, + user_name: user, + })), + switchMap(() => this.loadUsersForExperiment(this.experimentId)), + ) + .subscribe((users) => { + this.userDataSource = users; + }); + } + + loadUsersForExperiment(experimentId: string) { + return this.experimentDataService.getUsersForExperiment(experimentId); + } } diff --git a/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.config.ts b/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.config.ts new file mode 100644 index 0000000..79fdf46 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/details/experiment-permission-details/experiment-permission-details.config.ts @@ -0,0 +1,16 @@ +import { TABLE_ACTION_CONFIG } from '../../../../../shared/components/table/table.config'; + +export const TABLE_ACTIONS = [ + TABLE_ACTION_CONFIG.EDIT, + TABLE_ACTION_CONFIG.REVOKE, +]; + +export const COLUMN_CONFIG = [ + { + title: 'User name', + key: 'username', + }, { + title: 'Permissions', + key: 'permission', + }, +]; diff --git a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.ts b/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.ts deleted file mode 100644 index 7ab9e90..0000000 --- a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { EditPermissionsModalComponent } from '../../../../../shared/components'; -import { MatDialog } from '@angular/material/dialog'; - -@Component({ - selector: 'ml-model-permision-details', - templateUrl: './model-permision-details.component.html', - styleUrls: ['./model-permision-details.component.scss'], -}) -export class ModelPermisionDetailsComponent implements OnInit { - - userColumnConfig = [ - { - title: 'User name', - key: 'userName', - }, - { - title: 'Permissions', - key: 'permissions', - }, - ]; - - userDataSource = [ - { - userName: 'Test 0', - permissions: 'admin', - }, - { - userName: 'Test 1', - permissions: 'admin', - }, - { - userName: 'Test 2', - permissions: 'admin', - }, - ]; - - constructor( - private dialog: MatDialog, - ) { - } - - ngOnInit(): void { - } - - handleUserEdit($event: any) { - this.dialog - .open(EditPermissionsModalComponent) - .afterClosed() - .subscribe((data) => { - console.log(data) - }) - } -} diff --git a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.html b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.html similarity index 65% rename from web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.html rename to web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.html index 4b9784f..697ad33 100644 --- a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.html +++ b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.html @@ -1,16 +1,17 @@

Experiment Access

-
diff --git a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.scss b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.scss similarity index 100% rename from web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.scss rename to web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.scss diff --git a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.spec.ts b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.spec.ts similarity index 53% rename from web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.spec.ts rename to web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.spec.ts index 6dfc8ca..2bf64af 100644 --- a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.spec.ts +++ b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.spec.ts @@ -1,18 +1,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { ModelPermisionDetailsComponent } from './model-permision-details.component'; +import { ModelPermissionDetailsComponent } from './model-permission-details.component'; describe('ModelPermisionDetailsComponent', () => { - let component: ModelPermisionDetailsComponent; - let fixture: ComponentFixture; + let component: ModelPermissionDetailsComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ ModelPermisionDetailsComponent ] + declarations: [ ModelPermissionDetailsComponent ] }) .compileComponents(); - fixture = TestBed.createComponent(ModelPermisionDetailsComponent); + fixture = TestBed.createComponent(ModelPermissionDetailsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.ts b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.ts new file mode 100644 index 0000000..3200738 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.component.ts @@ -0,0 +1,118 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { filter, switchMap, tap } from 'rxjs'; + +import { + EditPermissionsModalComponent, + GrantUserPermissionsComponent, + GrantUserPermissionsModel, +} from 'src/app//shared/components'; +import { MatDialog } from '@angular/material/dialog'; +import { TableActionEvent, TableActionModel } from 'src/app/shared/components/table/table.interface'; +import { ModelsDataService, PermissionDataService, SnackBarService, UserDataService } from 'src/app//shared/services'; +import { COLUMN_CONFIG, TABLE_ACTIONS } from './model-permission-details.config'; +import { TableActionEnum } from 'src/app/shared/components/table/table.config'; +import { + PermissionsDialogData, +} from 'src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.interface'; +import { EntityEnum } from 'src/app/core/configs/core'; + + +@Component({ + selector: 'ml-model-permission-details', + templateUrl: './model-permission-details.component.html', + styleUrls: ['./model-permission-details.component.scss'], +}) +export class ModelPermissionDetailsComponent implements OnInit { + modelId!: string; + userDataSource: any[] = []; + + userColumnConfig = COLUMN_CONFIG; + actions: TableActionModel[] = TABLE_ACTIONS; + + constructor( + private readonly dialog: MatDialog, + private readonly route: ActivatedRoute, + private readonly modelDataService: ModelsDataService, + private readonly permissionDataService: PermissionDataService, + private readonly userDataService: UserDataService, + private readonly snackService: SnackBarService, + ) { + } + + ngOnInit(): void { + this.modelId = this.route.snapshot.paramMap.get('id') ?? ''; + + this.loadUsersForModel(this.modelId).subscribe((users) => { + this.userDataSource = users; + }); + } + + revokePermissionForUser(item: any) { + this.permissionDataService.deleteModelPermission( + { model_name: this.modelId, user_name: item.username } + ) + .subscribe(console.log); + } + + editPermissionForUser({ username, permission }: any) { + const data: PermissionsDialogData = { + userName: username, + entityName: this.modelId, + entityType: EntityEnum.MODEL, + permission, + }; + + this.dialog + .open(EditPermissionsModalComponent, { data }) + .afterClosed() + .pipe( + filter(Boolean), + switchMap(({ permission }) => this.permissionDataService.updateModelPermission({ + model_name: this.modelId, + new_permission: permission, + user_name: username, + })), + tap(() => this.snackService.openSnackBar('Permission updated')), + switchMap(() => this.loadUsersForModel(this.modelId)), + ) + .subscribe((users) => { + this.userDataSource = users; + }); + } + + handleActions({ action, item }: TableActionEvent<{ model: string; id: string }>) { + const actionMapping: { [key: string]: any } = { + [TableActionEnum.REVOKE]: this.revokePermissionForUser.bind(this), + [TableActionEnum.EDIT]: this.editPermissionForUser.bind(this), + }; + + const selectedAction = actionMapping[action.action]; + if (selectedAction) { + selectedAction(item); + } + } + + loadUsersForModel(modelId: string) { + return this.modelDataService.getUsersForModel(modelId); + } + + addUser() { + this.userDataService.getAllUsers() + .pipe( + switchMap(({ users }) => this.dialog.open(GrantUserPermissionsComponent, + { data: { users } }) + .afterClosed()), + filter(Boolean), + switchMap(({ user, permission }) => this.permissionDataService.createModelPermission({ + model_name: this.modelId, + new_permission: permission, + user_name: user, + })), + switchMap(() => this.loadUsersForModel(this.modelId)), + ) + .subscribe((users) => { + this.userDataSource = users; + }); + } +} diff --git a/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.config.ts b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.config.ts new file mode 100644 index 0000000..71e8111 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/details/model-permission-details/model-permission-details.config.ts @@ -0,0 +1,17 @@ +import { TABLE_ACTION_CONFIG } from 'src/app/shared/components/table/table.config'; + +export const TABLE_ACTIONS = [ + TABLE_ACTION_CONFIG.EDIT, + TABLE_ACTION_CONFIG.REVOKE, +]; + +export const COLUMN_CONFIG = [ + { + title: 'User name', + key: 'username', + }, + { + title: 'Permissions', + key: 'permission', + }, +]; diff --git a/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.html b/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.html index 5b6cfa8..1ab94c2 100644 --- a/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.html +++ b/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.html @@ -1,33 +1,38 @@ -
-
-

Model Access

- -
+ + + +
+
+ +
- -
+ +
+ -
-
-

Experiment Access

- -
+ + +
+
+ +
- -
+ +
+ + + diff --git a/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.ts b/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.ts index 4833aa4..6295254 100644 --- a/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.ts +++ b/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.component.ts @@ -1,13 +1,15 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; +import { filter, forkJoin, switchMap } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; + +import { GrantPermissionModalComponent } from 'src/app/shared/components'; +import { ExperimentsDataService, ModelsDataService, PermissionDataService } from 'src/app/shared/services'; +import { EXPERIMENT_COLUMN_CONFIG, MODEL_COLUMN_CONFIG } from './user-permission-details.config'; +import { EntityEnum } from 'src/app/core/configs/core'; import { - EditPermissionsModalComponent, - GrantPermissionModalComponent, GrantPermissionModalData, -} from '../../../../../shared/components'; -import { DataService } from '../../../../../shared/services'; -import { filter, switchMap } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; +} from 'src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.inteface'; @Component({ selector: 'ml-user-permission-details', @@ -15,131 +17,77 @@ import { ActivatedRoute } from '@angular/router'; styleUrls: ['./user-permission-details.component.scss'], }) export class UserPermissionDetailsComponent implements OnInit { - userId: string | null = null; - modelColumnConfig = [ - { - title: 'Modal name', - key: 'modal', - }, { - title: 'Permissions', - key: 'permissions', - }, - ]; - modelDataSource = [ - { - modal: 'Model 1', - permissions: 'Read', - }, - { - modal: 'Model 2', - permissions: 'Write', - }, - { - modal: 'Model 3', - permissions: 'Read', - }, - ]; - - experimentColumnConfig = [ - { - title: 'Experiment name', - key: 'experiment', - }, { - title: 'Permissions', - key: 'permissions', - }, - ]; - experimentDataSource = [ - { - experiment: 'Experiment 1', - permissions: 'Read', - }, - { - experiment: 'Experiment 2', - permissions: 'Write', - }, - ] + userId: string = ''; + experimentsColumnConfig = EXPERIMENT_COLUMN_CONFIG; + modelsColumnConfig = MODEL_COLUMN_CONFIG; + experimentsDataSource: any[] = []; + modelsDataSource: any[] = []; constructor( private readonly dialog: MatDialog, - private readonly dataService: DataService, + private readonly expDataService: ExperimentsDataService, + private readonly modelDataService: ModelsDataService, + private readonly permissionDataService: PermissionDataService, private readonly route: ActivatedRoute, ) { } ngOnInit(): void { - this.userId = this.route.snapshot.paramMap.get('id'); - } - - - handleUserEditForModel() { - this.dialog - .open(EditPermissionsModalComponent) - .afterClosed() - .subscribe((data) => { - console.log(data) - }) - } + this.userId = this.route.snapshot.paramMap.get('id') ?? ''; - handleUserEditForExperiment() { - this.dialog - .open(EditPermissionsModalComponent) - .afterClosed() - .subscribe((data) => { - console.log(data) - }) - } + forkJoin([ + this.expDataService.getExperimentsForUser(this.userId), + this.modelDataService.getModelsForUser(this.userId), + ]) + .subscribe(([experiments, models]) => { + this.experimentsDataSource = experiments; + this.modelsDataSource = models; + }); - add() { - this.dialog.open(GrantPermissionModalComponent) - .afterClosed() - .subscribe(console.log); } addModelPermissionToUser() { - this.dataService.getAllModels() + this.modelDataService.getAllModels() .pipe( - switchMap(({ models }) => this.dialog.open(GrantPermissionModalComponent, { + switchMap((models) => this.dialog.open(GrantPermissionModalComponent, { data: { - type: 'model', - entities: models, - userName: this.userId ? this.userId : '', + entityType: EntityEnum.MODEL, + entities: models.map(({ name }) => name), + userName: this.userId, } - }) - .afterClosed()), + }).afterClosed() + ), filter(Boolean), - ) - .subscribe((data) => { - const { entity, permission, user } = data; - this.dataService.createModelPermission({ - user_name: user, + switchMap(({ entity, permission, user }) => this.permissionDataService.createModelPermission({ + user_name: this.userId, model_name: entity, new_permission: permission, - }).subscribe(); - }); + })), + ) + .subscribe(); } addExperimentPermissionToUser() { - this.dataService.getAllExperiments() + this.expDataService.getAllExperiments() .pipe( - switchMap(({ experiments }) => this.dialog.open(GrantPermissionModalComponent, { + switchMap((experiments) => this.dialog.open(GrantPermissionModalComponent, { data: { - type: 'experiment', - entities: experiments, - userName: this.userId ? this.userId : '', + entityType: EntityEnum.EXPERIMENT, + entities: experiments.map(({ name }) => name), + userName: this.userId, } - }) - .afterClosed()), + }).afterClosed() + ), filter(Boolean), + switchMap(({ entity, permission, user }) => { + return this.permissionDataService.createExperimentPermission({ + user_name: this.userId, + experiment_name: entity, + new_permission: permission, + }) + }), ) - .subscribe((data) => { - const { entity, permission, user } = data; - this.dataService.createExperimentPermission({ - user_name: user, - experiment_name: entity, - new_permission: permission, - }).subscribe(); - }); + .subscribe(); } } diff --git a/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.config.ts b/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.config.ts new file mode 100644 index 0000000..8b78df5 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/details/user-permission-details/user-permission-details.config.ts @@ -0,0 +1,9 @@ +export const MODEL_COLUMN_CONFIG = [ + { title: 'Model name', key: 'name' }, + { title: 'Permissions', key: 'permissions' }, +]; + +export const EXPERIMENT_COLUMN_CONFIG = [ + { title: 'Experiment Name', key: 'name' }, + { title: 'Permission', key: 'permissions' }, +]; diff --git a/web-ui/src/app/features/admin-page/components/index.ts b/web-ui/src/app/features/admin-page/components/index.ts new file mode 100644 index 0000000..8e3c2e0 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/index.ts @@ -0,0 +1,10 @@ +export * from './details/experiment-permission-details/experiment-permission-details.component' +export * from './details/model-permission-details/model-permission-details.component'; +export * from './details/user-permission-details/user-permission-details.component'; + +export * from './admin-page/admin-page.component'; + +export * from './permissions/permissions.component'; +export * from './permissions/experiment-permissions/experiment-permissions.component'; +export * from './permissions/model-permissions/model-permissions.component'; +export * from './permissions/user-permissions/user-permissions.component'; diff --git a/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.html b/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.html index c4f6f51..9559038 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.html +++ b/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.html @@ -1,7 +1,6 @@ diff --git a/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.ts b/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.ts index 804f95b..32ee126 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.ts +++ b/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.component.ts @@ -1,6 +1,12 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { DataService } from '../../../../../shared/services'; + +import { ExperimentsDataService } from 'src/app/shared/services'; +import { TableActionEvent, TableActionModel } from 'src/app/shared/components/table/table.interface'; +import { TableActionEnum } from 'src/app/shared/components/table/table.config'; +import { COLUMN_CONFIG, TABLE_ACTIONS } from './experiment-permissions.config'; +import { ExperimentModel } from 'src/app/shared/interfaces/experiments-data.interface'; + @Component({ selector: 'ml-experiment-permissions', @@ -8,28 +14,33 @@ import { DataService } from '../../../../../shared/services'; styleUrls: ['./experiment-permissions.component.scss'] }) export class ExperimentPermissionsComponent implements OnInit { - searchValue: string = ''; - columnConfig = [{ - title: 'Experiment Name', - key: 'experiment' - }]; - dataSource: { experiment: string, id: string }[] = []; + columnConfig = COLUMN_CONFIG; + dataSource: ExperimentModel[] = []; + actions: TableActionModel[] = TABLE_ACTIONS; constructor( - private router: Router, - private route: ActivatedRoute, - private dataService: DataService, + private readonly router: Router, + private readonly route: ActivatedRoute, + private readonly experimentDataService: ExperimentsDataService, ) { } ngOnInit(): void { - this.dataService.getAllExperiments() - .subscribe(({ experiments }) => { - this.dataSource = experiments.map((experiment) => ({ experiment, id: experiment })); - }) + this.experimentDataService.getAllExperiments() + .subscribe((experiments) => this.dataSource = experiments); + } + + handleActions(event: TableActionEvent) { + const actionMapping: { [key: string]: any } = { + [TableActionEnum.EDIT]: this.handleExperimentEdit.bind(this), + } + + const selectedAction = actionMapping[event.action.action]; + if (selectedAction) { + selectedAction(event.item); + } } - handleExperimentEdit($event: any) { - const { id } = $event; + handleExperimentEdit({ id }: ExperimentModel) { this.router.navigate(['../experiment/' + id], { relativeTo: this.route }) } } diff --git a/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.config.ts b/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.config.ts new file mode 100644 index 0000000..0412a9c --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/permissions/experiment-permissions/experiment-permissions.config.ts @@ -0,0 +1,10 @@ +import { TableActionEnum } from 'src/app/shared/components/table/table.config'; + +export const TABLE_ACTIONS = [ + { action: TableActionEnum.EDIT, icon: 'edit', name: 'Edit' }, +]; + +export const COLUMN_CONFIG = [{ + title: 'Experiment Name', + key: 'name' +}]; diff --git a/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.html b/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.html index 86225a0..735bd5e 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.html +++ b/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.html @@ -1,7 +1,7 @@ diff --git a/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.ts b/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.ts index 88639b7..9c448dc 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.ts +++ b/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.component.ts @@ -1,6 +1,11 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { DataService } from '../../../../../shared/services'; + +import { ModelsDataService } from 'src/app/shared/services'; +import { TableActionEvent, TableActionModel } from 'src/app/shared/components/table/table.interface'; +import { MODEL_COLUMN_CONFIG, MODEL_TABLE_ACTIONS } from './model-permissions.config'; +import { TableActionEnum } from 'src/app/shared/components/table/table.config'; +import { ModelModel } from 'src/app/shared/interfaces/models-data.interface'; @Component({ selector: 'ml-model-permissions', @@ -8,29 +13,36 @@ import { DataService } from '../../../../../shared/services'; styleUrls: ['./model-permissions.component.scss'], }) export class ModelPermissionsComponent implements OnInit { - searchValue: string = ''; - columnConfig = [{ - title: 'Model name', - key: 'model', - }]; - dataSource: { model: string, id: string }[] = []; + columnConfig = MODEL_COLUMN_CONFIG; + dataSource: ModelModel[] = []; + actions: TableActionModel[] = MODEL_TABLE_ACTIONS; constructor( - private route: ActivatedRoute, - private router: Router, - private dataService: DataService, + private readonly route: ActivatedRoute, + private readonly router: Router, + private readonly modelDataService: ModelsDataService, ) { } ngOnInit(): void { - this.dataService.getAllModels() - .subscribe(({ models }) => { - this.dataSource = models.map((model) => ({ model, id: model })); + this.modelDataService.getAllModels() + .subscribe((models) => { + this.dataSource = models; }) } - handleModelEdit($event: any) { - const { id } = $event; - this.router.navigate(['../model/' + id], { relativeTo: this.route }) + handleModelEdit({ name }: ModelModel) { + this.router.navigate(['../model/' + name], { relativeTo: this.route }) + } + + handleAction({ action, item }: TableActionEvent) { + const actionMapping: { [key: string]: any } = { + [TableActionEnum.EDIT]: this.handleModelEdit.bind(this), + }; + + const selectedAction = actionMapping[action.action]; + if (selectedAction) { + selectedAction(item); + } } } diff --git a/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.config.ts b/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.config.ts new file mode 100644 index 0000000..4e9d038 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/permissions/model-permissions/model-permissions.config.ts @@ -0,0 +1,11 @@ +import { TABLE_ACTION_CONFIG } from 'src/app/shared/components/table/table.config'; + +export const MODEL_COLUMN_CONFIG = [ + { + title: 'Model Name', + key: 'name', + }, +]; +export const MODEL_TABLE_ACTIONS = [ + TABLE_ACTION_CONFIG.EDIT, +]; diff --git a/web-ui/src/app/features/admin-page/components/permissions/permissions.component.ts b/web-ui/src/app/features/admin-page/components/permissions/permissions.component.ts index 4dcb3ba..8dd70c3 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/permissions.component.ts +++ b/web-ui/src/app/features/admin-page/components/permissions/permissions.component.ts @@ -1,35 +1,8 @@ -import { Component, OnInit } from '@angular/core'; -import { MatTabChangeEvent } from '@angular/material/tabs'; -import { ActivatedRoute, Router } from '@angular/router'; -import { UserPermissionDetailsComponent } from '../details/user-permission-details/user-permission-details.component'; -import { ExperimentPermissionsComponent } from './experiment-permissions/experiment-permissions.component'; -import { ModelPermissionsComponent } from './model-permissions/model-permissions.component'; -import { DataService } from '../../../../shared/services'; +import { Component } from '@angular/core'; @Component({ selector: 'ml-permissions', templateUrl: './permissions.component.html', styleUrls: ['./permissions.component.scss'] }) -export class PermissionsComponent implements OnInit { - - constructor( - private router: Router, - private route: ActivatedRoute, - ) { } - - ngOnInit(): void { - } - - handleTabSelection($event: MatTabChangeEvent) { - const mapping: { [key: number]: string } = { - 0: 'user', - 1: 'experiment', - 2: 'model', - } - const route = mapping[$event.index]; - if (route) { - setTimeout(() => void this.router.navigate([route], { relativeTo: this.route })) - } - } -} +export class PermissionsComponent {} diff --git a/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.html b/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.html index c51cab5..32188af 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.html +++ b/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.html @@ -1,7 +1,6 @@ diff --git a/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.ts b/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.ts index c4d71e8..048bc8b 100644 --- a/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.ts +++ b/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.component.ts @@ -1,6 +1,14 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { DataService } from '../../../../../shared/services'; + +import { UserDataService } from 'src/app/shared/services'; +import { TableActionEvent, TableActionModel } from 'src/app/shared/components/table/table.interface'; +import { TableActionEnum } from 'src/app/shared/components/table/table.config'; +import { USER_ACTIONS, USER_COLUMN_CONFIG } from './user-permissions.config'; + +interface UserModel { + user: string, +} @Component({ selector: 'ml-user-permissions', @@ -8,29 +16,34 @@ import { DataService } from '../../../../../shared/services'; styleUrls: ['./user-permissions.component.scss'], }) export class UserPermissionsComponent implements OnInit { - searchValue: string = ''; - columnConfig = [{ - title: 'User', - key: 'user', - }]; - dataSource: { user: string, id: string }[] = []; + columnConfig = USER_COLUMN_CONFIG; + actions: TableActionModel[] = USER_ACTIONS; + dataSource: UserModel[] = []; constructor( - private router: Router, - private route: ActivatedRoute, - private dataService: DataService, + private readonly router: Router, + private readonly route: ActivatedRoute, + private readonly userDataService: UserDataService, ) { } ngOnInit(): void { - this.dataService.getAllUsers() - .subscribe(({ users }) => { - this.dataSource = users.map((user) => ({ user, id: user })); - }) + this.userDataService.getAllUsers() + .subscribe(({ users }) => this.dataSource = users.map((user) => ({ user }))) + } + + handleItemAction({ action, item }: TableActionEvent) { + const actionHandlers: { [key: string]: (user: UserModel) => void } = { + [TableActionEnum.EDIT]: this.handleUserEdit.bind(this), + } + + const selectedAction = actionHandlers[action.action]; + if (selectedAction) { + selectedAction(item); + } } - handleUserEdit(event: any) { - const { id } = event; - this.router.navigate(['../user/' + id], { relativeTo: this.route }) + handleUserEdit({ user }: UserModel): void { + this.router.navigate(['../user/' + user], { relativeTo: this.route }) } } diff --git a/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.config.ts b/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.config.ts new file mode 100644 index 0000000..5347d27 --- /dev/null +++ b/web-ui/src/app/features/admin-page/components/permissions/user-permissions/user-permissions.config.ts @@ -0,0 +1,13 @@ +import { TABLE_ACTION_CONFIG } from 'src/app/shared/components/table/table.config'; +import { TableActionModel } from 'src/app/shared/components/table/table.interface'; + +export const USER_COLUMN_CONFIG = [ + { + title: 'User name', + key: 'user', + }, +]; + +export const USER_ACTIONS: TableActionModel[] = [ + TABLE_ACTION_CONFIG.EDIT +]; diff --git a/web-ui/src/app/features/home-page/components/home-page/home-page.component.html b/web-ui/src/app/features/home-page/components/home-page/home-page.component.html index 08b5641..f5b0555 100644 --- a/web-ui/src/app/features/home-page/components/home-page/home-page.component.html +++ b/web-ui/src/app/features/home-page/components/home-page/home-page.component.html @@ -1,4 +1,4 @@ -
+
@@ -7,18 +7,32 @@
-
-

Experiments

- -

Models

- -
+
- -
- -
-
+ + + +
+ + +
+
+ + +
+ + +
+
+ +
+
diff --git a/web-ui/src/app/features/home-page/components/home-page/home-page.component.scss b/web-ui/src/app/features/home-page/components/home-page/home-page.component.scss index b308c26..6cf650c 100644 --- a/web-ui/src/app/features/home-page/components/home-page/home-page.component.scss +++ b/web-ui/src/app/features/home-page/components/home-page/home-page.component.scss @@ -1,17 +1,13 @@ -::ng-deep { - .mat-column-name { - word-wrap: break-word !important; - white-space: unset !important; - flex: 0 0 50% !important; - width: 50% !important; - overflow-wrap: break-word; - word-wrap: break-word; +.mat-column-experiment_id, .mat-column-name { + white-space: unset !important; + width: 50% !important; + overflow-wrap: break-word; + word-wrap: break-word; - word-break: break-word; + word-break: break-word; - -ms-hyphens: auto; - -moz-hyphens: auto; - -webkit-hyphens: auto; - hyphens: auto; - } + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; } diff --git a/web-ui/src/app/features/home-page/components/home-page/home-page.component.ts b/web-ui/src/app/features/home-page/components/home-page/home-page.component.ts index 98e150b..a3769a8 100644 --- a/web-ui/src/app/features/home-page/components/home-page/home-page.component.ts +++ b/web-ui/src/app/features/home-page/components/home-page/home-page.component.ts @@ -1,12 +1,15 @@ import { Component, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; +import { AccessKeyModalComponent } from 'src/app/shared/components'; +import { AuthService } from 'src/app/shared/services'; +import { EXPERIMENTS_COLUMN_CONFIG, MODELS_COLUMN_CONFIG } from './home-page.config'; +import { AccessKeyDialogData } from 'src/app/shared/components/modals/access-key-modal/access-key-modal.interface'; +import { UserDataService } from 'src/app/shared/services/data/user-data.service'; import { - AccessKeyDialogData, - AccessKeyModalComponent, -} from '../../../../shared/components'; -import { finalize, forkJoin, switchMap } from 'rxjs'; -import { AuthService, DataService } from '../../../../shared/services'; -import { ExperimentModel, ModelModel, UserResponseModel } from '../../../../shared/interfaces/data.interfaces'; + CurrentUserModel, + ExperimentPermission, + RegisteredModelPermission, +} from 'src/app/shared/interfaces/user-data.interface'; @Component({ selector: 'ml-home-page', @@ -14,36 +17,16 @@ import { ExperimentModel, ModelModel, UserResponseModel } from '../../../../shar styleUrls: ['./home-page.component.scss'], }) export class HomePageComponent implements OnInit { - currentUserInfo: UserResponseModel | null = null; - loading = false; - experimentsColumnConfig = [ - { - title: 'Experiment name', - key: 'name', - }, - { - title: 'Permissions', - key: 'permission', - }, - ]; - modelsColumnConfig = [ - { - title: 'Model name', - key: 'name', - }, - { - title: 'Permissions', - key: 'permission', - }, - ]; - experimentsDataSource: ExperimentModel[] = []; - - modelsDataSource: ModelModel[] = []; + currentUserInfo: CurrentUserModel | null = null; + experimentsColumnConfig = EXPERIMENTS_COLUMN_CONFIG; + modelsColumnConfig = MODELS_COLUMN_CONFIG; + experimentsDataSource: ExperimentPermission[] = []; + modelsDataSource: RegisteredModelPermission[] = []; constructor( private readonly dialog: MatDialog, - private readonly dataService: DataService, private readonly authService: AuthService, + private readonly userDataService: UserDataService, ) { } @@ -51,25 +34,18 @@ export class HomePageComponent implements OnInit { this.currentUserInfo = this.authService.getUserInfo(); if (this.currentUserInfo) { - const { username } = this.currentUserInfo; - - if (username) { - this.experimentsDataSource = this.currentUserInfo.experiment_permissions; - this.modelsDataSource = this.currentUserInfo.registered_model_permissions; - } + const { experiment_permissions, registered_model_permissions } = this.currentUserInfo; + + this.modelsDataSource = registered_model_permissions; + this.experimentsDataSource = experiment_permissions; } } showAccessKeyModal() { - this.dataService.getAccessKey() - .pipe( - switchMap(({ token }) => this.dialog.open(AccessKeyModalComponent, { - data: { - token, - }, - }) - .afterClosed()), - ) - .subscribe(); + this.userDataService.getAccessKey() + .subscribe(({ token }) => { + const data = { token }; + this.dialog.open(AccessKeyModalComponent, { data }) + }); } } diff --git a/web-ui/src/app/features/home-page/components/home-page/home-page.config.ts b/web-ui/src/app/features/home-page/components/home-page/home-page.config.ts new file mode 100644 index 0000000..e12eee5 --- /dev/null +++ b/web-ui/src/app/features/home-page/components/home-page/home-page.config.ts @@ -0,0 +1,9 @@ +export const EXPERIMENTS_COLUMN_CONFIG = [ + { title: 'Experiment Name', key: 'name' }, + { title: 'Permission', key: 'permission' }, +]; + +export const MODELS_COLUMN_CONFIG = [ + { title: 'Model name', key: 'name' }, + { title: 'Permissions', key: 'permission' }, +]; diff --git a/web-ui/src/app/features/home-page/home-page.module.ts b/web-ui/src/app/features/home-page/home-page.module.ts index 7203eb1..fa17862 100644 --- a/web-ui/src/app/features/home-page/home-page.module.ts +++ b/web-ui/src/app/features/home-page/home-page.module.ts @@ -4,8 +4,6 @@ import { CommonModule } from '@angular/common'; import { HomePageRoutingModule } from './home-page-routing.module'; import { HomePageComponent } from './components/home-page/home-page.component'; import { SharedModule } from '../../shared/shared.module'; -import { RouterModule } from '@angular/router'; - @NgModule({ declarations: [ diff --git a/web-ui/src/app/shared/components/action-table/action-table.component.html b/web-ui/src/app/shared/components/action-table/action-table.component.html deleted file mode 100644 index 169c92a..0000000 --- a/web-ui/src/app/shared/components/action-table/action-table.component.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web-ui/src/app/shared/components/action-table/action-table.component.spec.ts b/web-ui/src/app/shared/components/action-table/action-table.component.spec.ts deleted file mode 100644 index 2c0928c..0000000 --- a/web-ui/src/app/shared/components/action-table/action-table.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ActionTableComponent } from './action-table.component'; - -describe('ActionTableComponent', () => { - let component: ActionTableComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ ActionTableComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ActionTableComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/web-ui/src/app/shared/components/action-table/action-table.component.ts b/web-ui/src/app/shared/components/action-table/action-table.component.ts deleted file mode 100644 index 4e50439..0000000 --- a/web-ui/src/app/shared/components/action-table/action-table.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'ml-action-table', - templateUrl: './action-table.component.html', - styleUrls: ['./action-table.component.scss'] -}) -export class ActionTableComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} diff --git a/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.html b/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.html deleted file mode 100644 index f33bea0..0000000 --- a/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.html +++ /dev/null @@ -1,16 +0,0 @@ -

"User name" permissions for model/experiment "name"

- -

Permissions

- - Permissions - - -
- - - - diff --git a/web-ui/src/app/shared/components/header/header.component.html b/web-ui/src/app/shared/components/header/header.component.html index af40973..90b8934 100644 --- a/web-ui/src/app/shared/components/header/header.component.html +++ b/web-ui/src/app/shared/components/header/header.component.html @@ -6,7 +6,10 @@ Home Admin panel - +
diff --git a/web-ui/src/app/shared/components/index.ts b/web-ui/src/app/shared/components/index.ts index 92b20e9..cc32ac8 100644 --- a/web-ui/src/app/shared/components/index.ts +++ b/web-ui/src/app/shared/components/index.ts @@ -1,6 +1,8 @@ export * from './table/table.component'; export * from './header/header.component'; -export * from './action-table/action-table.component'; -export * from './edit-permissions-modal/edit-permissions-modal.component'; -export * from './grant-permissoin-modal/grant-permission-modal.component'; -export * from './access-key-modal/access-key-modal.component'; + +export * from './modals/edit-permissions-modal/edit-permissions-modal.component'; +export * from './modals/grant-permissoin-modal/grant-permission-modal.component'; +export * from './modals/access-key-modal/access-key-modal.component'; +export * from './modals/grant-user-permissions/grant-user-permissions.component'; +export * from './modals/confirm-modal/confirm-modal.component'; diff --git a/web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.html b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.html similarity index 100% rename from web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.html rename to web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.html diff --git a/web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.scss b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.scss similarity index 100% rename from web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.scss rename to web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.scss diff --git a/web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.spec.ts b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.spec.ts similarity index 100% rename from web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.spec.ts rename to web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.spec.ts diff --git a/web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.ts b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.ts similarity index 90% rename from web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.ts rename to web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.ts index 6bf0a7c..e1658b2 100644 --- a/web-ui/src/app/shared/components/access-key-modal/access-key-modal.component.ts +++ b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.component.ts @@ -1,9 +1,6 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; - -export interface AccessKeyDialogData { - token: string; -} +import { AccessKeyDialogData } from './access-key-modal.interface'; @Component({ selector: 'ml-access-key-modal', diff --git a/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.interface.ts b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.interface.ts new file mode 100644 index 0000000..3c33358 --- /dev/null +++ b/web-ui/src/app/shared/components/modals/access-key-modal/access-key-modal.interface.ts @@ -0,0 +1,3 @@ +export interface AccessKeyDialogData { + token: string; +} diff --git a/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.html b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.html new file mode 100644 index 0000000..931166e --- /dev/null +++ b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.html @@ -0,0 +1,8 @@ +

Action confirmations

+ + Please confirm your action + + + + + diff --git a/web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.scss b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.scss similarity index 100% rename from web-ui/src/app/features/admin-page/components/details/model-permision-details/model-permision-details.component.scss rename to web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.scss diff --git a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.spec.ts b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.spec.ts similarity index 51% rename from web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.spec.ts rename to web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.spec.ts index 71a1d03..56f48a0 100644 --- a/web-ui/src/app/features/admin-page/components/admin-page/admin-page.component.spec.ts +++ b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.spec.ts @@ -1,18 +1,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { AdminPageComponent } from './admin-page.component'; +import { ConfirmModalComponent } from './confirm-modal.component'; -describe('AdminPageComponent', () => { - let component: AdminPageComponent; - let fixture: ComponentFixture; +describe('ConfirmModalComponent', () => { + let component: ConfirmModalComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ AdminPageComponent ] + declarations: [ ConfirmModalComponent ] }) .compileComponents(); - fixture = TestBed.createComponent(AdminPageComponent); + fixture = TestBed.createComponent(ConfirmModalComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.ts b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.ts new file mode 100644 index 0000000..72a1ed1 --- /dev/null +++ b/web-ui/src/app/shared/components/modals/confirm-modal/confirm-modal.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'ml-confirm-modal', + templateUrl: './confirm-modal.component.html', + styleUrls: ['./confirm-modal.component.scss'] +}) +export class ConfirmModalComponent { + +} diff --git a/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.html b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.html new file mode 100644 index 0000000..e66c3fa --- /dev/null +++ b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.html @@ -0,0 +1,13 @@ +

{{title}}

+ + + Permissions + + + + + + + diff --git a/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.scss b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.scss similarity index 100% rename from web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.scss rename to web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.scss diff --git a/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.spec.ts b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.spec.ts similarity index 100% rename from web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.spec.ts rename to web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.spec.ts diff --git a/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.ts b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.ts similarity index 52% rename from web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.ts rename to web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.ts index c63525a..de8157a 100644 --- a/web-ui/src/app/shared/components/edit-permissions-modal/edit-permissions-modal.component.ts +++ b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.component.ts @@ -1,25 +1,31 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { PERMISSIONS } from 'src/app/core/configs/permissions'; +import { PermissionsDialogData } from './edit-permissions-modal.interface'; -export interface PermissionsDialogData { - permissions: string[]; -} @Component({ selector: 'ml-edit-permissions-modal', templateUrl: './edit-permissions-modal.component.html', styleUrls: ['./edit-permissions-modal.component.scss'] }) export class EditPermissionsModalComponent implements OnInit { + permissions = PERMISSIONS; + title: string = ''; + + form!: FormGroup + constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: PermissionsDialogData, + private readonly fb: FormBuilder, ) {} - onNoClick(): void { - this.dialogRef.close(null); - } - ngOnInit(): void { + this.title = `Edit ${this.data.entityName} permissions for ${this.data.userName}`; + this.form = this.fb.group({ + permission: [this.data.permission, Validators.required], + }) } } diff --git a/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.interface.ts b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.interface.ts new file mode 100644 index 0000000..fb89afa --- /dev/null +++ b/web-ui/src/app/shared/components/modals/edit-permissions-modal/edit-permissions-modal.interface.ts @@ -0,0 +1,9 @@ +import { EntityEnum } from 'src/app/core/configs/core'; +import { PermissionEnum } from 'src/app/core/configs/permissions'; + +export interface PermissionsDialogData { + entityType: EntityEnum; + entityName: string; + userName: string; + permission: PermissionEnum; +} diff --git a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.html similarity index 73% rename from web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html rename to web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.html index 804b99a..59895d7 100644 --- a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html +++ b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.html @@ -1,8 +1,8 @@ -

Add {{data.type}} permissions for {{data.userName}}

+

{{title}}

- {{data.type | titlecase}} + {{data.entityType | titlecase}} - - - +
diff --git a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.scss b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.scss similarity index 100% rename from web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.scss rename to web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.scss diff --git a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.spec.ts b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.spec.ts similarity index 100% rename from web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.spec.ts rename to web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.spec.ts diff --git a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.ts similarity index 65% rename from web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts rename to web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.ts index de52887..cdf110c 100644 --- a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts +++ b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.component.ts @@ -1,12 +1,8 @@ import { Component, Inject, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA } from '@angular/material/dialog'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; - -export interface GrantPermissionModalData { - userName: string; - type: 'model' | 'experiment'; - entities: string[]; -} +import { PermissionEnum, PERMISSIONS } from 'src/app/core/configs/permissions'; +import { GrantPermissionModalData } from './grant-permission-modal.inteface'; @Component({ selector: 'ml-grant-permission-modal', @@ -16,6 +12,9 @@ export interface GrantPermissionModalData { export class GrantPermissionModalComponent implements OnInit { form!: FormGroup; + permissions = PERMISSIONS; + title: string = ''; + constructor( @Inject(MAT_DIALOG_DATA) public data: GrantPermissionModalData, private readonly fb: FormBuilder, @@ -23,10 +22,9 @@ export class GrantPermissionModalComponent implements OnInit { } ngOnInit(): void { + this.title = `Grant ${this.data.entityType} permissions for ${this.data.userName}`; this.form = this.fb.group({ - user: this.data.userName, - type: this.data.type, - permission: [null, Validators.required], + permission: [PermissionEnum.READ, Validators.required], entity: [null, Validators.required], }) } diff --git a/web-ui/src/app/shared/components/action-table/action-table.component.scss b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.config.ts similarity index 100% rename from web-ui/src/app/shared/components/action-table/action-table.component.scss rename to web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.config.ts diff --git a/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.inteface.ts b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.inteface.ts new file mode 100644 index 0000000..0731ae2 --- /dev/null +++ b/web-ui/src/app/shared/components/modals/grant-permissoin-modal/grant-permission-modal.inteface.ts @@ -0,0 +1,7 @@ +import { EntityEnum } from 'src/app/core/configs/core'; + +export interface GrantPermissionModalData { + userName: string; + entityType: EntityEnum; + entities: string[]; +} diff --git a/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.html b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.html new file mode 100644 index 0000000..f8ad3fd --- /dev/null +++ b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.html @@ -0,0 +1,23 @@ +

{{title}}

+ +
+ + Users + + + + Permissions + + +
+ +
+ + + + + diff --git a/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.scss b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.spec.ts b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.spec.ts new file mode 100644 index 0000000..4fa5cab --- /dev/null +++ b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GrantUserPermissionsComponent } from './grant-user-permissions.component'; + +describe('GrantUserPermissionsComponent', () => { + let component: GrantUserPermissionsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GrantUserPermissionsComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(GrantUserPermissionsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.ts b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.ts new file mode 100644 index 0000000..9355423 --- /dev/null +++ b/web-ui/src/app/shared/components/modals/grant-user-permissions/grant-user-permissions.component.ts @@ -0,0 +1,35 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { PermissionEnum, PERMISSIONS } from '../../../../core/configs/permissions'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; + + +export interface GrantUserPermissionsModel { + users: string[]; +} + +@Component({ + selector: 'ml-grant-user-permissions', + templateUrl: './grant-user-permissions.component.html', + styleUrls: ['./grant-user-permissions.component.scss'] +}) +export class GrantUserPermissionsComponent implements OnInit { + form!: FormGroup; + + permissions = PERMISSIONS; + title: string = ''; + + constructor( + @Inject(MAT_DIALOG_DATA) public data: GrantUserPermissionsModel, + private readonly fb: FormBuilder, + ) { + } + + ngOnInit(): void { + this.title = `Grant permissions`; + this.form = this.fb.group({ + user: [null, Validators.required], + permission: [PermissionEnum.READ, Validators.required], + }) + } +} diff --git a/web-ui/src/app/shared/components/table/table.component.html b/web-ui/src/app/shared/components/table/table.component.html index 0a0ab7a..481499c 100644 --- a/web-ui/src/app/shared/components/table/table.component.html +++ b/web-ui/src/app/shared/components/table/table.component.html @@ -1,10 +1,10 @@
Search - - + + + +
@@ -18,21 +18,20 @@ - + + Actions - - + + + diff --git a/web-ui/src/app/shared/components/table/table.component.spec.ts b/web-ui/src/app/shared/components/table/table.component.spec.ts index 645d452..4f5ddd5 100644 --- a/web-ui/src/app/shared/components/table/table.component.spec.ts +++ b/web-ui/src/app/shared/components/table/table.component.spec.ts @@ -3,8 +3,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TableComponent } from './table.component'; describe('TableComponent', () => { - let component: TableComponent; - let fixture: ComponentFixture; + let component: TableComponent; + let fixture: ComponentFixture>; beforeEach(async () => { await TestBed.configureTestingModule({ @@ -12,7 +12,7 @@ describe('TableComponent', () => { }) .compileComponents(); - fixture = TestBed.createComponent(TableComponent); + fixture = TestBed.createComponent(TableComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/web-ui/src/app/shared/components/table/table.component.ts b/web-ui/src/app/shared/components/table/table.component.ts index f77045e..04ae4d3 100644 --- a/web-ui/src/app/shared/components/table/table.component.ts +++ b/web-ui/src/app/shared/components/table/table.component.ts @@ -1,43 +1,50 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; +import { TableActionEvent, TableActionModel, TableColumnConfigModel } from './table.interface'; @Component({ selector: 'ml-table', templateUrl: './table.component.html', styleUrls: ['./table.component.scss'], }) -export class TableComponent implements OnInit { - @Input() columnConfig: { title: string, key: string }[] = []; +export class TableComponent implements OnInit, OnChanges { + @Input() columnConfig: TableColumnConfigModel[] = []; @Input() data: T[] = []; - @Input() isActionsActive = false; + @Input() actions: TableActionModel[] = []; - @Output() editEvent = new EventEmitter(); - @Output() deleteEvent = new EventEmitter(); + @Output() action = new EventEmitter>(); dataSource: MatTableDataSource = new MatTableDataSource(); - columns: string[] = []; - searchValue = ''; - constructor() { + serachValue: string = ''; + + readonly columnActionName = 'actions'; + + ngOnChanges(changes: SimpleChanges): void { + const data = changes['data'].currentValue; + if (data) { + this.dataSource = new MatTableDataSource(data); + } } ngOnInit(): void { - this.columns = this.columnConfig.map(c => c.key); + const columnKeys = this.columnConfig.map(config => config.key); + this.dataSource = new MatTableDataSource(this.data); - this.columns = ['actions', ...this.columns]; + this.columns = columnKeys; + this.actions.length + ? this.columns = [this.columnActionName, ...columnKeys] + : this.columns = columnKeys; } - edit(item: T) { - this.editEvent.emit(item); - } - delete(item: T) { - this.deleteEvent.emit(item); + filter(event: Event) { + const inputElement = event.target as HTMLInputElement; + this.dataSource.filter = inputElement.value.trim().toLowerCase(); } - filter(value: Event) { - const filterValue = (value.target as HTMLInputElement).value; - this.dataSource.filter = filterValue.trim().toLowerCase(); + handleActionClick(item: T, action: TableActionModel) { + this.action.emit({ item, action }); } } diff --git a/web-ui/src/app/shared/components/table/table.config.ts b/web-ui/src/app/shared/components/table/table.config.ts new file mode 100644 index 0000000..c543952 --- /dev/null +++ b/web-ui/src/app/shared/components/table/table.config.ts @@ -0,0 +1,40 @@ +import { TableActionModel } from './table.interface'; + +export enum TableActionEnum { + EDIT = 'EDIT', + DELETE = 'DELETE', + REVOKE = 'REVOKE', + ADD = 'ADD', + MANAGE = 'MANAGE', +} + +const ADD_ACTION: TableActionModel = { + name: 'Add', + icon: 'add', + action: TableActionEnum.ADD +}; + +const EDIT_ACTION = { + name: 'Edit', + icon: 'edit', + action: TableActionEnum.EDIT +}; + +const DELETE_ACTION = { + name: 'Delete', + icon: 'delete', + action: TableActionEnum.DELETE +}; + +const REVOKE_ACTION = { + name: 'Revoke', + icon: 'key_off', + action: TableActionEnum.REVOKE +}; + +export const TABLE_ACTION_CONFIG = { + ADD: ADD_ACTION, + EDIT: EDIT_ACTION, + DELETE: DELETE_ACTION, + REVOKE: REVOKE_ACTION +} diff --git a/web-ui/src/app/shared/components/table/table.interface.ts b/web-ui/src/app/shared/components/table/table.interface.ts new file mode 100644 index 0000000..990b6b5 --- /dev/null +++ b/web-ui/src/app/shared/components/table/table.interface.ts @@ -0,0 +1,17 @@ +import { TableActionEnum } from './table.config'; + +export interface TableActionModel { + icon: string; + name: string; + action: TableActionEnum; +} + +export interface TableActionEvent { + item: T; + action: TableActionModel; +} + +export interface TableColumnConfigModel { + title: string, + key: string +} diff --git a/web-ui/src/app/shared/interceptors/error-handler.interceptor.spec.ts b/web-ui/src/app/shared/interceptors/error-handler.interceptor.spec.ts new file mode 100644 index 0000000..aa99c94 --- /dev/null +++ b/web-ui/src/app/shared/interceptors/error-handler.interceptor.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ErrorHandlerInterceptor } from './error-handler.interceptor'; + +describe('ErrorHandlerInterceptor', () => { + beforeEach(() => TestBed.configureTestingModule({ + providers: [ + ErrorHandlerInterceptor + ] + })); + + it('should be created', () => { + const interceptor: ErrorHandlerInterceptor = TestBed.inject(ErrorHandlerInterceptor); + expect(interceptor).toBeTruthy(); + }); +}); diff --git a/web-ui/src/app/shared/interceptors/error-handler.interceptor.ts b/web-ui/src/app/shared/interceptors/error-handler.interceptor.ts new file mode 100644 index 0000000..03658f0 --- /dev/null +++ b/web-ui/src/app/shared/interceptors/error-handler.interceptor.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@angular/core'; +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { catchError, Observable, throwError } from 'rxjs'; +import { SnackBarService } from '../services'; + +@Injectable() +export class ErrorHandlerInterceptor implements HttpInterceptor { + + constructor( + private readonly snackBarService: SnackBarService, + ) { + } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request) + .pipe( + catchError((error: HttpErrorResponse) => { + let errorMessage = (error.error instanceof ErrorEvent) + ? `Error: ${error.error.message}` + : `Error Code: ${error.status}\nMessage: ${error.message}`; + + this.snackBarService.openSnackBar(errorMessage); + return throwError(errorMessage); + }), + ) + } +} diff --git a/web-ui/src/app/shared/interfaces/data.interfaces.ts b/web-ui/src/app/shared/interfaces/data.interfaces.ts deleted file mode 100644 index 0e9ca70..0000000 --- a/web-ui/src/app/shared/interfaces/data.interfaces.ts +++ /dev/null @@ -1,38 +0,0 @@ -export interface UserResponseModel { - display_name: string; - experiment_permissions: ExperimentModel[]; - id: number; - is_admin: boolean; - registered_model_permissions: ModelModel[]; - username: string; -} - -export interface ExperimentModel { - id: string; - name: string; - permission: string; -} - -export interface ModelModel { - name: string; - permission: string; -} - -export interface ExperimentsResponseModel { - experiments: { - id: string, - name: string, - permissions: string }[] -} - -export interface CreateExperimentPermissionRequestBodyModel { - experiment_name: string; - user_name: string; - new_permission: string; -} - -export interface CreateModelPermissionRequestBodyModel { - "model_name": string; - "user_name": string; - "new_permission": string; -} diff --git a/web-ui/src/app/shared/interfaces/experiments-data.interface.ts b/web-ui/src/app/shared/interfaces/experiments-data.interface.ts new file mode 100644 index 0000000..64fb581 --- /dev/null +++ b/web-ui/src/app/shared/interfaces/experiments-data.interface.ts @@ -0,0 +1,24 @@ +import { PermissionEnum } from '../../core/configs/permissions'; + +export interface ExperimentModel { + id: string; + name: string; + tags: Record; +} + + +export interface ExperimentsForUserModel { + experiments: ExperimentForUserModel[] +} + +interface ExperimentForUserModel { + id: string, + name: string, + permissions: string, +} + + +export interface UserPermissionModel { + permission: PermissionEnum; + username: string; +} diff --git a/web-ui/src/app/shared/interfaces/models-data.interface.ts b/web-ui/src/app/shared/interfaces/models-data.interface.ts new file mode 100644 index 0000000..1d3495a --- /dev/null +++ b/web-ui/src/app/shared/interfaces/models-data.interface.ts @@ -0,0 +1,25 @@ +import { PermissionEnum } from 'src/app/core/configs/permissions'; + +export interface ModelModel { + aliases: Record; + description: string; + latest_versions: any[]; + name: string; + tags: Record; +} + + +export interface ModelPermissionsModel { + models: ModelPermissionModel[]; +} + +export interface ModelPermissionModel { + name: string; + permissions: PermissionEnum; +} + +export interface ModelUserListModel { + permission: PermissionEnum; + username: string; +} + diff --git a/web-ui/src/app/shared/interfaces/permission-data.interface.ts b/web-ui/src/app/shared/interfaces/permission-data.interface.ts new file mode 100644 index 0000000..9a331f3 --- /dev/null +++ b/web-ui/src/app/shared/interfaces/permission-data.interface.ts @@ -0,0 +1,12 @@ +export interface CreateExperimentPermissionRequestBodyModel { + experiment_name?: string; + experiment_id?: string; + user_name: string; + new_permission: string; +} + +export interface CreateModelPermissionRequestBodyModel { + model_name: string; + user_name: string; + new_permission: string; +} diff --git a/web-ui/src/app/shared/interfaces/user-data.interface.ts b/web-ui/src/app/shared/interfaces/user-data.interface.ts new file mode 100644 index 0000000..470a486 --- /dev/null +++ b/web-ui/src/app/shared/interfaces/user-data.interface.ts @@ -0,0 +1,28 @@ +export interface CurrentUserModel { + display_name: string; + experiment_permissions: ExperimentPermission[]; + id: number; + is_admin: boolean; + registered_model_permissions: RegisteredModelPermission[]; + username: string; +} + +export interface ExperimentPermission { + id: string; + name: string; + permission: string; +} + +export interface RegisteredModelPermission { + name: string; + permission: string; + user_id: number; +} + +export interface TokenModel { + token: string; +} + +export interface AllUsersListModel { + users: string[]; +} diff --git a/web-ui/src/app/shared/material/material.module.ts b/web-ui/src/app/shared/material/material.module.ts index 59c7274..c457591 100644 --- a/web-ui/src/app/shared/material/material.module.ts +++ b/web-ui/src/app/shared/material/material.module.ts @@ -9,6 +9,7 @@ import { MatTabsModule } from '@angular/material/tabs'; import { MatMenuModule } from '@angular/material/menu'; import { MatDialogModule } from '@angular/material/dialog'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; const MATERIAL_MODULES = [ MatTableModule, @@ -20,6 +21,7 @@ const MATERIAL_MODULES = [ MatMenuModule, MatDialogModule, MatProgressSpinnerModule, + MatSnackBarModule, ]; @NgModule({ diff --git a/web-ui/src/app/shared/pipes/table-search.pipe.spec.ts b/web-ui/src/app/shared/pipes/table-search.pipe.spec.ts deleted file mode 100644 index 7e09adf..0000000 --- a/web-ui/src/app/shared/pipes/table-search.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { TableSearchPipe } from './table-search.pipe'; - -describe('TableSearchPipe', () => { - it('create an instance', () => { - const pipe = new TableSearchPipe(); - expect(pipe).toBeTruthy(); - }); -}); diff --git a/web-ui/src/app/shared/pipes/table-search.pipe.ts b/web-ui/src/app/shared/pipes/table-search.pipe.ts deleted file mode 100644 index 9e2ad53..0000000 --- a/web-ui/src/app/shared/pipes/table-search.pipe.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({ - name: 'tableSearch' -}) -export class TableSearchPipe implements PipeTransform { - - transform(value: any , ...args: unknown[]): [] { - console.log(value, args); - return []; - } - -} diff --git a/web-ui/src/app/shared/services/auth.service.ts b/web-ui/src/app/shared/services/auth.service.ts index 23e6a0a..867315b 100644 --- a/web-ui/src/app/shared/services/auth.service.ts +++ b/web-ui/src/app/shared/services/auth.service.ts @@ -1,20 +1,20 @@ import { Injectable } from '@angular/core'; -import { UserResponseModel } from '../interfaces/data.interfaces'; +import { CurrentUserModel } from '../interfaces/user-data.interface'; @Injectable({ providedIn: 'root', }) export class AuthService { - private user: UserResponseModel | null = null; + private user!: CurrentUserModel; constructor() { } - getUserInfo(): UserResponseModel | null { - return this.user ? this.user : null; + getUserInfo(): CurrentUserModel { + return this.user; } - setUserInfo(user: UserResponseModel) { + setUserInfo(user: CurrentUserModel) { this.user = user; } } diff --git a/web-ui/src/app/shared/services/data.service.ts b/web-ui/src/app/shared/services/data.service.ts deleted file mode 100644 index b39c7c9..0000000 --- a/web-ui/src/app/shared/services/data.service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { - CreateExperimentPermissionRequestBodyModel, CreateModelPermissionRequestBodyModel, - ExperimentsResponseModel, - UserResponseModel, -} from '../interfaces/data.interfaces'; -import { map } from 'rxjs' - -@Injectable({ - providedIn: 'root', -}) -export class DataService { - - constructor( - private readonly http: HttpClient, - ) { - } - - getCurrentUser() { - return this.http.get('/api/2.0/mlflow/users/current'); - } - - getAccessKey() { - return this.http.get<{ token: string }>('/api/2.0/mlflow/users/access-token', {}); - } - - getAllExperiments() { - return this.http.get<{experiments: string[]}>('/api/2.0/mlflow/experiments'); - } - - getAllModels() { - return this.http.get<{models: string[]}>('/api/2.0/mlflow/registered-models'); - } - - getAllUsers() { - return this.http.get<{users: string[]}>('/api/2.0/mlflow/users'); - } - - getExperimentsForUser(userName: string) { - return this.http.get(`/api/2.0/mlflow/users/${userName}/experiments`) - .pipe( - map(response => response.experiments), - ); - } - - getModelsForUser(userName: string) { - return this.http.get<[]>(`/api/2.0/mlflow/users/${userName}/registered-models`); - } - - createExperimentPermission(body: CreateExperimentPermissionRequestBodyModel) { - return this.http.post('/api/2.0/mlflow/experiments/permissions/create', body); - } - - createModelPermission(body: CreateModelPermissionRequestBodyModel) { - return this.http.post('/api/2.0/mlflow/registered-models/permissions/create', body); - } -} diff --git a/web-ui/src/app/shared/services/data/experiments-data.service.ts b/web-ui/src/app/shared/services/data/experiments-data.service.ts new file mode 100644 index 0000000..0c01213 --- /dev/null +++ b/web-ui/src/app/shared/services/data/experiments-data.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; + +import { HttpClient } from '@angular/common/http'; +import { map } from 'rxjs'; +import { + ExperimentModel, + ExperimentsForUserModel, + UserPermissionModel, +} from '../../interfaces/experiments-data.interface'; +import { API_URL } from 'src/app/core/configs/api-urls'; + +@Injectable({ + providedIn: 'root' +}) +export class ExperimentsDataService { + + constructor( + private readonly http: HttpClient, + ) { + } + + getAllExperiments() { + return this.http.get(API_URL.ALL_EXPERIMENTS); + } + + getExperimentsForUser(userName: string) { + console.log(userName); + const url = API_URL.EXPERIMENTS_FOR_USER.replace('${userName}', userName); + return this.http.get(url) + .pipe( + map(response => response.experiments), + ); + } + + getUsersForExperiment(experimentName: string) { + const url = API_URL.USERS_FOR_EXPERIMENT.replace('${experimentName}', experimentName); + return this.http.get(url); + } +} diff --git a/web-ui/src/app/shared/services/data/models-data.service.ts b/web-ui/src/app/shared/services/data/models-data.service.ts new file mode 100644 index 0000000..8aa5a87 --- /dev/null +++ b/web-ui/src/app/shared/services/data/models-data.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { map } from 'rxjs'; + +import { ModelModel, ModelPermissionsModel, ModelUserListModel } from '../../interfaces/models-data.interface'; +import { API_URL } from 'src/app/core/configs/api-urls'; + +@Injectable({ + providedIn: 'root' +}) +export class ModelsDataService { + + constructor( + private readonly http: HttpClient, + ) { + } + + getAllModels() { + return this.http.get(API_URL.ALL_MODELS); + } + + getModelsForUser(userName: string) { + const url = API_URL.MODELS_FOR_USER.replace('${userName}', userName); + return this.http.get(url) + .pipe( + map(response => response.models), + ); + } + + getUsersForModel(modelName: string) { + const url = API_URL.USERS_FOR_MODEL.replace('${modelName}', modelName); + return this.http.get(url); + } + +} diff --git a/web-ui/src/app/shared/services/data/permission-data.service.ts b/web-ui/src/app/shared/services/data/permission-data.service.ts new file mode 100644 index 0000000..9aac38c --- /dev/null +++ b/web-ui/src/app/shared/services/data/permission-data.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { + CreateExperimentPermissionRequestBodyModel, + CreateModelPermissionRequestBodyModel, +} from 'src/app/shared/interfaces/permission-data.interface'; +import { API_URL } from 'src/app/core/configs/api-urls'; + + +@Injectable({ + providedIn: 'root', +}) +export class PermissionDataService { + + constructor( + private readonly http: HttpClient, + ) { + } + + createExperimentPermission(body: CreateExperimentPermissionRequestBodyModel) { + return this.http.post(API_URL.CREATE_EXPERIMENT_PERMISSION, body, { responseType: 'text' }); + } + + updateExperimentPermission(body: { user_name: string, experiment_id: string, new_permission: string }) { + return this.http.post(API_URL.UPDATE_EXPERIMENT_PERMISSION, body, { responseType: 'text' }); + } + + deleteExperimentPermission(body: { experiment_id: string, user_name: string }) { + return this.http.post(API_URL.DELETE_EXPERIMENT_PERMISSION, body, { responseType: 'text' }); + } + + createModelPermission(body: CreateModelPermissionRequestBodyModel) { + return this.http.post(API_URL.CREATE_MODEL_PERMISSION, body); + } + + updateModelPermission(body: { user_name: string, model_name: string, new_permission: string }) { + return this.http.post(API_URL.UPDATE_MODEL_PERMISSION, body, { responseType: 'text' }); + } + + deleteModelPermission(body: { model_name: string, user_name: string }) { + return this.http.post(API_URL.DELETE_MODEL_PERMISSION, body, { responseType: 'text' }); + } + +} diff --git a/web-ui/src/app/shared/services/data/user-data.service.ts b/web-ui/src/app/shared/services/data/user-data.service.ts new file mode 100644 index 0000000..a3cd6f6 --- /dev/null +++ b/web-ui/src/app/shared/services/data/user-data.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { AllUsersListModel, CurrentUserModel, TokenModel } from '../../interfaces/user-data.interface'; +import { API_URL } from 'src/app/core/configs/api-urls'; + +@Injectable({ + providedIn: 'root' +}) +export class UserDataService { + + constructor( + private readonly http: HttpClient, + ) { } + + getCurrentUser() { + return this.http.get(API_URL.GET_CURRENT_USER); + } + + getAccessKey() { + return this.http.get(API_URL.GET_ACCESS_TOKEN); + } + + getAllUsers() { + return this.http.get(API_URL.GET_ALL_USERS); + } +} diff --git a/web-ui/src/app/shared/services/index.ts b/web-ui/src/app/shared/services/index.ts index 4f62eed..b5d0f90 100644 --- a/web-ui/src/app/shared/services/index.ts +++ b/web-ui/src/app/shared/services/index.ts @@ -1,2 +1,6 @@ -export * from './data.service'; +export * from './data/models-data.service'; +export * from './data/user-data.service'; +export * from './data/experiments-data.service'; +export * from './data/permission-data.service'; +export * from './utility/snack-bar.service'; export * from './auth.service'; diff --git a/web-ui/src/app/shared/services/auth.service.spec.ts b/web-ui/src/app/shared/services/specs/auth.service.spec.ts similarity index 86% rename from web-ui/src/app/shared/services/auth.service.spec.ts rename to web-ui/src/app/shared/services/specs/auth.service.spec.ts index f1251ca..c381260 100644 --- a/web-ui/src/app/shared/services/auth.service.spec.ts +++ b/web-ui/src/app/shared/services/specs/auth.service.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing'; -import { AuthService } from './auth.service'; +import { AuthService } from '../auth.service'; describe('AuthService', () => { let service: AuthService; diff --git a/web-ui/src/app/shared/services/specs/experiments-data.service.spec.ts b/web-ui/src/app/shared/services/specs/experiments-data.service.spec.ts new file mode 100644 index 0000000..7f521c8 --- /dev/null +++ b/web-ui/src/app/shared/services/specs/experiments-data.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ExperimentsDataService } from '../data/experiments-data.service'; + +describe('ExperimentsDataService', () => { + let service: ExperimentsDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ExperimentsDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/web-ui/src/app/shared/services/specs/models-data.service.spec.ts b/web-ui/src/app/shared/services/specs/models-data.service.spec.ts new file mode 100644 index 0000000..affb6a4 --- /dev/null +++ b/web-ui/src/app/shared/services/specs/models-data.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ModelsDataService } from '../data/models-data.service'; + +describe('ModelsDataService', () => { + let service: ModelsDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ModelsDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/web-ui/src/app/shared/services/specs/permission-data.service.spec.ts b/web-ui/src/app/shared/services/specs/permission-data.service.spec.ts new file mode 100644 index 0000000..a6ef892 --- /dev/null +++ b/web-ui/src/app/shared/services/specs/permission-data.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { PermissionDataService } from '../data/permission-data.service'; + +describe('PermissionDataService', () => { + let service: PermissionDataService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(PermissionDataService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/web-ui/src/app/shared/services/specs/snack-bar.service.spec.ts b/web-ui/src/app/shared/services/specs/snack-bar.service.spec.ts new file mode 100644 index 0000000..0d90d5c --- /dev/null +++ b/web-ui/src/app/shared/services/specs/snack-bar.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { SnackBarService } from '../utility/snack-bar.service'; + +describe('SnackBarService', () => { + let service: SnackBarService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(SnackBarService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/web-ui/src/app/shared/services/data.service.spec.ts b/web-ui/src/app/shared/services/specs/user-data.service.spec.ts similarity index 52% rename from web-ui/src/app/shared/services/data.service.spec.ts rename to web-ui/src/app/shared/services/specs/user-data.service.spec.ts index 38e8d9e..f2c9045 100644 --- a/web-ui/src/app/shared/services/data.service.spec.ts +++ b/web-ui/src/app/shared/services/specs/user-data.service.spec.ts @@ -1,13 +1,13 @@ import { TestBed } from '@angular/core/testing'; -import { DataService } from './data.service'; +import { UserDataService } from '../data/user-data.service'; -describe('DataService', () => { - let service: DataService; +describe('UserDataService', () => { + let service: UserDataService; beforeEach(() => { TestBed.configureTestingModule({}); - service = TestBed.inject(DataService); + service = TestBed.inject(UserDataService); }); it('should be created', () => { diff --git a/web-ui/src/app/shared/services/utility/snack-bar.service.ts b/web-ui/src/app/shared/services/utility/snack-bar.service.ts new file mode 100644 index 0000000..9a78045 --- /dev/null +++ b/web-ui/src/app/shared/services/utility/snack-bar.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { MatSnackBar } from '@angular/material/snack-bar'; +import { CORE_CONFIGS } from '../../../core/configs/core'; + +@Injectable({ + providedIn: 'root' +}) +export class SnackBarService { + + constructor( + private readonly snackBarService: MatSnackBar, + ) { } + + openSnackBar(message: string) { + return this.snackBarService.open(message, 'OK', { + duration: CORE_CONFIGS.SNACK_BAR_DURATION + }); + } + +} diff --git a/web-ui/src/app/shared/shared.module.ts b/web-ui/src/app/shared/shared.module.ts index d7d94f6..6374e1c 100644 --- a/web-ui/src/app/shared/shared.module.ts +++ b/web-ui/src/app/shared/shared.module.ts @@ -2,40 +2,35 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AccessKeyModalComponent, - ActionTableComponent, + ConfirmModalComponent, EditPermissionsModalComponent, GrantPermissionModalComponent, + GrantUserPermissionsComponent, HeaderComponent, TableComponent, } from './components'; import { MaterialModule } from './material/material.module'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; -import { TableSearchPipe } from './pipes/table-search.pipe'; -import { RouterLinkWithHref } from '@angular/router'; +import { RouterLink, RouterLinkWithHref } from '@angular/router'; import { HttpClientModule } from '@angular/common/http'; const SHARED_COMPONENTS = [ AccessKeyModalComponent, - ActionTableComponent, + ConfirmModalComponent, EditPermissionsModalComponent, GrantPermissionModalComponent, + GrantUserPermissionsComponent, HeaderComponent, TableComponent, ]; -const SHARED_PIPES = [ - TableSearchPipe -]; - @NgModule({ declarations: [ ...SHARED_COMPONENTS, - ...SHARED_PIPES, ], exports: [ ...SHARED_COMPONENTS, - ...SHARED_PIPES, MaterialModule, ], @@ -47,6 +42,7 @@ const SHARED_PIPES = [ RouterLinkWithHref, HttpClientModule, ReactiveFormsModule, + RouterLink, ], }) export class SharedModule { }