diff --git a/mlflow_oidc_auth/views.py b/mlflow_oidc_auth/views.py
index 6c6c46f..9e78d1e 100644
--- a/mlflow_oidc_auth/views.py
+++ b/mlflow_oidc_auth/views.py
@@ -367,7 +367,16 @@ def create_access_token():
def get_current_user():
user = store.get_user(_get_username())
- return jsonify(user.to_json())
+ user_json = user.to_json()
+ user_json["experiment_permissions"] = [
+ {
+ "name": mlflow_client.get_experiment(permission.experiment_id).name,
+ "id": permission.experiment_id,
+ "permission": permission.permission
+ }
+ for permission in user.experiment_permissions
+ ]
+ return jsonify(user_json)
def update_username_password():
diff --git a/web-ui/package.json b/web-ui/package.json
index 0112a42..3a203b8 100644
--- a/web-ui/package.json
+++ b/web-ui/package.json
@@ -1,6 +1,7 @@
{
"name": "mlflow-oidc-auth-front",
"version": "0.0.0",
+ "license":"Apache-2.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
diff --git a/web-ui/src/app/app.component.ts b/web-ui/src/app/app.component.ts
index 618ff50..eaa5f12 100644
--- a/web-ui/src/app/app.component.ts
+++ b/web-ui/src/app/app.component.ts
@@ -9,7 +9,7 @@ import { AuthService, DataService } from './shared/services';
export class AppComponent implements OnInit {
title = 'mlflow-oidc-auth-front';
- name: string = 'Alex';
+ name: string = '';
constructor(
private readonly dataService: DataService,
@@ -18,8 +18,10 @@ export class AppComponent implements OnInit {
}
ngOnInit(): void {
- this.dataService.getCurrentUser().subscribe(({ username }) => {
- this.authService.setUser(username);
- });
+ this.dataService.getCurrentUser()
+ .subscribe((userInfo) => {
+ this.authService.setUserInfo(userInfo);
+ this.name = userInfo.display_name;
+ });
}
}
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 4cef41d..5b6cfa8 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,7 +1,7 @@
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 4053d45..4833aa4 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,7 +1,13 @@
import { Component, OnInit } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
-import { EditPermissionsModalComponent, GrantPermissionModalComponent } from '../../../../../shared/components';
+import {
+ EditPermissionsModalComponent,
+ GrantPermissionModalComponent,
+ GrantPermissionModalData,
+} from '../../../../../shared/components';
+import { DataService } from '../../../../../shared/services';
+import { filter, switchMap } from 'rxjs';
+import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'ml-user-permission-details',
@@ -9,6 +15,7 @@ import { EditPermissionsModalComponent, GrantPermissionModalComponent } from '..
styleUrls: ['./user-permission-details.component.scss'],
})
export class UserPermissionDetailsComponent implements OnInit {
+ userId: string | null = null;
modelColumnConfig = [
{
title: 'Modal name',
@@ -55,17 +62,18 @@ export class UserPermissionDetailsComponent implements OnInit {
constructor(
- private router: Router,
- private route: ActivatedRoute,
- public dialog: MatDialog,
+ private readonly dialog: MatDialog,
+ private readonly dataService: DataService,
+ private readonly route: ActivatedRoute,
) {
}
ngOnInit(): void {
+ this.userId = this.route.snapshot.paramMap.get('id');
}
- handleUserEditForModel($event: any) {
+ handleUserEditForModel() {
this.dialog
.open(EditPermissionsModalComponent)
.afterClosed()
@@ -74,7 +82,7 @@ export class UserPermissionDetailsComponent implements OnInit {
})
}
- handleUserEditForExperiment($event: any) {
+ handleUserEditForExperiment() {
this.dialog
.open(EditPermissionsModalComponent)
.afterClosed()
@@ -88,4 +96,50 @@ export class UserPermissionDetailsComponent implements OnInit {
.afterClosed()
.subscribe(console.log);
}
+
+ addModelPermissionToUser() {
+ this.dataService.getAllModels()
+ .pipe(
+ switchMap(({ models }) => this.dialog.open(GrantPermissionModalComponent, {
+ data: {
+ type: 'model',
+ entities: models,
+ userName: this.userId ? this.userId : '',
+ }
+ })
+ .afterClosed()),
+ filter(Boolean),
+ )
+ .subscribe((data) => {
+ const { entity, permission, user } = data;
+ this.dataService.createModelPermission({
+ user_name: user,
+ model_name: entity,
+ new_permission: permission,
+ }).subscribe();
+ });
+ }
+
+ addExperimentPermissionToUser() {
+ this.dataService.getAllExperiments()
+ .pipe(
+ switchMap(({ experiments }) => this.dialog.open(GrantPermissionModalComponent, {
+ data: {
+ type: 'experiment',
+ entities: experiments,
+ userName: this.userId ? this.userId : '',
+ }
+ })
+ .afterClosed()),
+ filter(Boolean),
+ )
+ .subscribe((data) => {
+ const { entity, permission, user } = data;
+ this.dataService.createExperimentPermission({
+ user_name: user,
+ experiment_name: entity,
+ new_permission: permission,
+ }).subscribe();
+ });
+ }
}
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 f5f9d02..08b5641 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
@@ -8,13 +8,26 @@
-
-
+
Experiments
+
+ Models
+
-
+
+
+
+ You have no experiments
+
+
+
+
+
+ You have no models
+
+
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 9e2eed8..98e150b 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
@@ -6,6 +6,7 @@ import {
} from '../../../../shared/components';
import { finalize, forkJoin, switchMap } from 'rxjs';
import { AuthService, DataService } from '../../../../shared/services';
+import { ExperimentModel, ModelModel, UserResponseModel } from '../../../../shared/interfaces/data.interfaces';
@Component({
selector: 'ml-home-page',
@@ -13,7 +14,7 @@ import { AuthService, DataService } from '../../../../shared/services';
styleUrls: ['./home-page.component.scss'],
})
export class HomePageComponent implements OnInit {
- currentUser?: string;
+ currentUserInfo: UserResponseModel | null = null;
loading = false;
experimentsColumnConfig = [
{
@@ -22,11 +23,9 @@ export class HomePageComponent implements OnInit {
},
{
title: 'Permissions',
- key: 'permissions',
+ key: 'permission',
},
];
- experimentsDataSource = [];
-
modelsColumnConfig = [
{
title: 'Model name',
@@ -34,10 +33,12 @@ export class HomePageComponent implements OnInit {
},
{
title: 'Permissions',
- key: 'permissions',
+ key: 'permission',
},
];
- modelsDataSource = [];
+ experimentsDataSource: ExperimentModel[] = [];
+
+ modelsDataSource: ModelModel[] = [];
constructor(
private readonly dialog: MatDialog,
@@ -47,21 +48,15 @@ export class HomePageComponent implements OnInit {
}
ngOnInit(): void {
- this.currentUser = this.authService.getUser();
+ this.currentUserInfo = this.authService.getUserInfo();
- if (this.currentUser) {
- this.loading = true;
- forkJoin([
- this.dataService.getExperimentsForUser(this.currentUser),
- this.dataService.getModelsForUser(this.currentUser),
- ])
- .pipe(
- finalize(() => this.loading = false),
- )
- .subscribe(([experiments, models]) => {
- this.experimentsDataSource = experiments;
- this.modelsDataSource = models;
- });
+ if (this.currentUserInfo) {
+ const { username } = this.currentUserInfo;
+
+ if (username) {
+ this.experimentsDataSource = this.currentUserInfo.experiment_permissions;
+ this.modelsDataSource = this.currentUserInfo.registered_model_permissions;
+ }
}
}
diff --git a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html b/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html
index 4b43779..804b99a 100644
--- a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html
+++ b/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.html
@@ -1,22 +1,26 @@
-
"User name" permissions for model/experiment "name"
-
- Permissions
-
- Exp
-
-
-
- Permissions
-
-
+Add {{data.type}} permissions for {{data.userName}}
+
+
+
+ {{data.type | titlecase}}
+
+
+
+ Permissions
+
+
+
+
-
-
+
+
diff --git a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts b/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts
index 88f596e..de52887 100644
--- a/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts
+++ b/web-ui/src/app/shared/components/grant-permissoin-modal/grant-permission-modal.component.ts
@@ -1,4 +1,12 @@
-import { Component, OnInit } from '@angular/core';
+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[];
+}
@Component({
selector: 'ml-grant-permission-modal',
@@ -6,13 +14,20 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./grant-permission-modal.component.scss']
})
export class GrantPermissionModalComponent implements OnInit {
+ form!: FormGroup;
- constructor() { }
-
- ngOnInit(): void {
+ constructor(
+ @Inject(MAT_DIALOG_DATA) public data: GrantPermissionModalData,
+ private readonly fb: FormBuilder,
+ ) {
}
- onNoClick() {
-
+ ngOnInit(): void {
+ this.form = this.fb.group({
+ user: this.data.userName,
+ type: this.data.type,
+ permission: [null, Validators.required],
+ entity: [null, Validators.required],
+ })
}
}
diff --git a/web-ui/src/app/shared/interfaces/data.interfaces.ts b/web-ui/src/app/shared/interfaces/data.interfaces.ts
new file mode 100644
index 0000000..0e9ca70
--- /dev/null
+++ b/web-ui/src/app/shared/interfaces/data.interfaces.ts
@@ -0,0 +1,38 @@
+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/services/auth.service.ts b/web-ui/src/app/shared/services/auth.service.ts
index 8a1caf8..23e6a0a 100644
--- a/web-ui/src/app/shared/services/auth.service.ts
+++ b/web-ui/src/app/shared/services/auth.service.ts
@@ -1,19 +1,20 @@
import { Injectable } from '@angular/core';
+import { UserResponseModel } from '../interfaces/data.interfaces';
@Injectable({
providedIn: 'root',
})
export class AuthService {
- private user?: string;
+ private user: UserResponseModel | null = null;
constructor() {
}
- getUser() {
- return this.user;
+ getUserInfo(): UserResponseModel | null {
+ return this.user ? this.user : null;
}
- setUser(user: string) {
+ setUserInfo(user: UserResponseModel) {
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
index d568142..b39c7c9 100644
--- a/web-ui/src/app/shared/services/data.service.ts
+++ b/web-ui/src/app/shared/services/data.service.ts
@@ -1,5 +1,11 @@
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',
@@ -12,7 +18,7 @@ export class DataService {
}
getCurrentUser() {
- return this.http.get<{ username: string }>('/api/2.0/mlflow/users/current');
+ return this.http.get('/api/2.0/mlflow/users/current');
}
getAccessKey() {
@@ -32,10 +38,21 @@ export class DataService {
}
getExperimentsForUser(userName: string) {
- return this.http.get<[]>(`/api/2.0/mlflow/users/${userName}/experiments`);
+ 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/shared.module.ts b/web-ui/src/app/shared/shared.module.ts
index e8c761e..d7d94f6 100644
--- a/web-ui/src/app/shared/shared.module.ts
+++ b/web-ui/src/app/shared/shared.module.ts
@@ -9,7 +9,7 @@ import {
TableComponent,
} from './components';
import { MaterialModule } from './material/material.module';
-import { FormsModule } from '@angular/forms';
+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';
@@ -46,6 +46,7 @@ const SHARED_PIPES = [
NgbModule,
RouterLinkWithHref,
HttpClientModule,
+ ReactiveFormsModule,
],
})
export class SharedModule { }