mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-05 17:52:01 +00:00
feat: console flat navigation, settings (#3581)
* instance routing * instance naming * org list * rm isonsystem * breadcrumb type * routing * instance members * fragment refresh org * settings pages * settings list, sidenav grouping, i18n * org-settings, policy changes * lint * grid * rename grid * fallback to general * cleanup * general settings, remove cards * sidenav for settings, label policy * i18n * header, nav backbuild * general, project nav rehaul * login text background adapt * org nav anim * org, instance settings, fix policy layout, roles * i18n, active route for project * lint
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { BehaviorSubject, from, Observable, of } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
|
||||
/**
|
||||
* Data source for the ProjectMembers view. This class should
|
||||
* encapsulate all logic for fetching and manipulating the displayed data
|
||||
* (including sorting, pagination, and filtering).
|
||||
*/
|
||||
export class InstanceMembersDataSource extends DataSource<Member.AsObject> {
|
||||
public totalResult: number = 0;
|
||||
public viewTimestamp!: Timestamp.AsObject;
|
||||
public membersSubject: BehaviorSubject<Member.AsObject[]> = new BehaviorSubject<Member.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
constructor(private service: AdminService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public loadMembers(pageIndex: number, pageSize: number): void {
|
||||
const offset = pageIndex * pageSize;
|
||||
|
||||
this.loadingSubject.next(true);
|
||||
|
||||
from(this.service.listIAMMembers(pageSize, offset))
|
||||
.pipe(
|
||||
map((resp) => {
|
||||
this.totalResult = resp.details?.totalResult || 0;
|
||||
if (resp.details?.viewTimestamp) {
|
||||
this.viewTimestamp = resp.details?.viewTimestamp;
|
||||
}
|
||||
return resp.resultList;
|
||||
}),
|
||||
catchError(() => of([])),
|
||||
finalize(() => this.loadingSubject.next(false)),
|
||||
)
|
||||
.subscribe((members) => {
|
||||
this.membersSubject.next(members);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect this data source to the table. The table will only update when
|
||||
* the returned stream emits new items.
|
||||
* @returns A stream of the items to be rendered.
|
||||
*/
|
||||
public connect(): Observable<Member.AsObject[]> {
|
||||
return this.membersSubject.asObservable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the table is being destroyed. Use this function, to clean up
|
||||
* any open connections or free any held resources that were set up during connect.
|
||||
*/
|
||||
public disconnect(): void {
|
||||
this.membersSubject.complete();
|
||||
this.loadingSubject.complete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { InstanceMembersComponent } from './instance-members.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: InstanceMembersComponent,
|
||||
data: { animation: 'AddPage' },
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class IamMembersRoutingModule {}
|
||||
@@ -0,0 +1,32 @@
|
||||
<cnsl-detail-layout [hasBackButton]="true" title="{{ 'IAM.MEMBER.TITLE' | translate }}">
|
||||
<p class="subinfo" sub>
|
||||
<span class="cnsl-secondary-text">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</span>
|
||||
<a mat-icon-button href="https://docs.zitadel.ch/docs/manuals/admin-managers" target="_blank">
|
||||
<i class="las la-info-circle"></i>
|
||||
</a>
|
||||
</p>
|
||||
<cnsl-members-table [dataSource]="dataSource" [memberRoleOptions]="memberRoleOptions"
|
||||
(updateRoles)="updateRoles($event.member, $event.change)" [factoryLoadFunc]="changePageFactory"
|
||||
(changedSelection)="selection = $event" [refreshTrigger]="changePage"
|
||||
[canWrite]="['iam.member.write$'] | hasRole | async" [canDelete]="['iam.member.delete$'] | hasRole | async"
|
||||
(deleteMember)="removeMember($event)">
|
||||
|
||||
<ng-template cnslHasRole selectactions [hasRole]="['iam.member.delete']">
|
||||
<button color="warn" (click)="removeMemberSelection()" matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}"
|
||||
mat-raised-button>
|
||||
<i class="las la-trash"></i>
|
||||
<span>{{'ACTIONS.SELECTIONDELETE' | translate}}</span>
|
||||
<cnsl-action-keys [type]="ActionKeysType.DELETE" (actionTriggered)="removeMemberSelection()">
|
||||
</cnsl-action-keys>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template cnslHasRole writeactions [hasRole]="['iam.member.write']">
|
||||
<button color="primary" (click)="openAddMember()" class="cnsl-action-button" mat-raised-button>
|
||||
<mat-icon class="icon">add</mat-icon>
|
||||
<span>{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
<cnsl-action-keys (actionTriggered)="openAddMember()">
|
||||
</cnsl-action-keys>
|
||||
</button>
|
||||
</ng-template>
|
||||
</cnsl-members-table>
|
||||
</cnsl-detail-layout>
|
||||
@@ -0,0 +1,12 @@
|
||||
.subinfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
margin: -1.5rem 0 0 0;
|
||||
|
||||
i {
|
||||
font-size: 1.2rem;
|
||||
height: 1.2rem;
|
||||
line-height: 1.2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { InstanceMembersComponent } from './instance-members.component';
|
||||
|
||||
describe('IamMembersComponent', () => {
|
||||
let component: InstanceMembersComponent;
|
||||
let fixture: ComponentFixture<InstanceMembersComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [InstanceMembersComponent],
|
||||
imports: [NoopAnimationsModule, MatSortModule, MatTableModule],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceMembersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should compile', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
import { Component, EventEmitter } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { PageEvent } from '@angular/material/paginator';
|
||||
import { ActionKeysType } from 'src/app/modules/action-keys/action-keys.component';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
|
||||
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { InstanceMembersDataSource } from './instance-members-datasource';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-instance-members',
|
||||
templateUrl: './instance-members.component.html',
|
||||
styleUrls: ['./instance-members.component.scss'],
|
||||
})
|
||||
export class InstanceMembersComponent {
|
||||
public INITIALPAGESIZE: number = 25;
|
||||
public dataSource!: InstanceMembersDataSource;
|
||||
|
||||
public memberRoleOptions: string[] = [];
|
||||
public changePageFactory!: Function;
|
||||
public changePage: EventEmitter<void> = new EventEmitter();
|
||||
public selection: Array<Member.AsObject> = [];
|
||||
public ActionKeysType: any = ActionKeysType;
|
||||
|
||||
constructor(
|
||||
private adminService: AdminService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService,
|
||||
breadcrumbService: BreadcrumbService,
|
||||
) {
|
||||
const breadcrumbs = [
|
||||
new Breadcrumb({
|
||||
type: BreadcrumbType.INSTANCE,
|
||||
name: 'Instance',
|
||||
routerLink: ['/instance'],
|
||||
}),
|
||||
];
|
||||
breadcrumbService.setBreadcrumb(breadcrumbs);
|
||||
|
||||
this.dataSource = new InstanceMembersDataSource(this.adminService);
|
||||
this.dataSource.loadMembers(0, 25);
|
||||
this.getRoleOptions();
|
||||
|
||||
this.changePageFactory = (event?: PageEvent) => {
|
||||
return this.dataSource.loadMembers(event?.pageIndex ?? 0, event?.pageSize ?? this.INITIALPAGESIZE);
|
||||
};
|
||||
}
|
||||
|
||||
public getRoleOptions(): void {
|
||||
this.adminService
|
||||
.listIAMMemberRoles()
|
||||
.then((resp) => {
|
||||
this.memberRoleOptions = resp.rolesList;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
updateRoles(member: Member.AsObject, selectionChange: string[]): void {
|
||||
this.adminService
|
||||
.updateIAMMember(member.userId, selectionChange)
|
||||
.then(() => {
|
||||
this.toast.showInfo('ORG.TOAST.MEMBERCHANGED', true);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public removeMemberSelection(): void {
|
||||
Promise.all(
|
||||
this.selection.map((member) => {
|
||||
return this.adminService
|
||||
.removeIAMMember(member.userId)
|
||||
.then(() => {
|
||||
this.toast.showInfo('IAM.TOAST.MEMBERREMOVED', true);
|
||||
this.changePage.emit();
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public removeMember(member: Member.AsObject): void {
|
||||
this.adminService
|
||||
.removeIAMMember(member.userId)
|
||||
.then(() => {
|
||||
this.toast.showInfo('IAM.TOAST.MEMBERREMOVED', true);
|
||||
setTimeout(() => {
|
||||
this.changePage.emit();
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public openAddMember(): void {
|
||||
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
|
||||
data: {
|
||||
creationType: CreationType.IAM,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
const users: User.AsObject[] = resp.users;
|
||||
const roles: string[] = resp.roles;
|
||||
|
||||
if (users && users.length && roles && roles.length) {
|
||||
Promise.all(
|
||||
users.map((user) => {
|
||||
return this.adminService.addIAMMember(user.id, roles);
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
this.toast.showInfo('IAM.TOAST.MEMBERADDED', true);
|
||||
setTimeout(() => {
|
||||
this.changePage.emit();
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { ActionKeysModule } from 'src/app/modules/action-keys/action-keys.module';
|
||||
import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
|
||||
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||
import { MembersTableModule } from 'src/app/modules/members-table/members-table.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
|
||||
import { IamMembersRoutingModule } from './instance-members-routing.module';
|
||||
import { InstanceMembersComponent } from './instance-members.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [InstanceMembersComponent],
|
||||
imports: [
|
||||
IamMembersRoutingModule,
|
||||
DetailLayoutModule,
|
||||
CommonModule,
|
||||
HasRoleModule,
|
||||
MatButtonModule,
|
||||
ActionKeysModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
TranslateModule,
|
||||
MembersTableModule,
|
||||
HasRolePipeModule,
|
||||
MemberCreateDialogModule,
|
||||
],
|
||||
})
|
||||
export class InstanceMembersModule {}
|
||||
55
console/src/app/pages/instance/instance-routing.module.ts
Normal file
55
console/src/app/pages/instance/instance-routing.module.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from 'src/app/guards/auth.guard';
|
||||
import { RoleGuard } from 'src/app/guards/role.guard';
|
||||
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||
|
||||
import { InstanceComponent } from './instance.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: InstanceComponent,
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['iam.read'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'members',
|
||||
loadChildren: () => import('./instance-members/instance-members.module').then((m) => m.InstanceMembersModule),
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['iam.member.read'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'idp',
|
||||
children: [
|
||||
{
|
||||
path: 'create',
|
||||
loadChildren: () => import('src/app/modules/idp-create/idp-create.module').then((m) => m.IdpCreateModule),
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['iam.idp.write'],
|
||||
serviceType: PolicyComponentServiceType.ADMIN,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
loadChildren: () => import('src/app/modules/idp/idp.module').then((m) => m.IdpModule),
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['iam.idp.read'],
|
||||
serviceType: PolicyComponentServiceType.ADMIN,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class IamRoutingModule {}
|
||||
34
console/src/app/pages/instance/instance.component.html
Normal file
34
console/src/app/pages/instance/instance.component.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<div class="iam-top">
|
||||
<div class="max-width-container">
|
||||
<div class="iam-top-row">
|
||||
<div>
|
||||
<div class="iam-title-row">
|
||||
<h1 class="iam-title">{{ 'IAM.TITLE' | translate }}</h1>
|
||||
</div>
|
||||
<p class="iam-sub cnsl-secondary-text">{{ 'IAM.DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<cnsl-contributors
|
||||
[totalResult]="totalMemberResult"
|
||||
[loading]="loading$ | async"
|
||||
[membersSubject]="membersSubject"
|
||||
title="{{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||
description="{{ 'PROJECT.MEMBER.TITLEDESC' | translate }}"
|
||||
(addClicked)="openAddMember()"
|
||||
(showDetailClicked)="showDetail()"
|
||||
(refreshClicked)="loadMembers()"
|
||||
[disabled]="false"
|
||||
>
|
||||
</cnsl-contributors>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-width-container">
|
||||
<h2 class="org-table-title">{{ 'ORG.LIST.TITLE' | translate }}</h2>
|
||||
|
||||
<p class="org-table-desc cnsl-secondary-text">{{ 'ORG.LIST.DESCRIPTION' | translate }}</p>
|
||||
|
||||
<cnsl-org-table></cnsl-org-table>
|
||||
|
||||
<cnsl-settings-grid [type]="PolicyComponentServiceType.ADMIN"></cnsl-settings-grid>
|
||||
</div>
|
||||
58
console/src/app/pages/instance/instance.component.scss
Normal file
58
console/src/app/pages/instance/instance.component.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@mixin instance-detail-theme($theme) {
|
||||
$foreground: map-get($theme, foreground);
|
||||
$is-dark-theme: map-get($theme, is-dark);
|
||||
$background: map-get($theme, background);
|
||||
|
||||
.iam-top {
|
||||
border-bottom: 1px solid map-get($foreground, divider);
|
||||
margin: 0 -2rem;
|
||||
padding: 2rem 2rem 1rem 2rem;
|
||||
background: map-get($background, metadata-section);
|
||||
|
||||
@media only screen and (max-width: 500px) {
|
||||
margin: 0 -1rem;
|
||||
}
|
||||
|
||||
.iam-top-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
.iam-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.iam-title {
|
||||
margin: 0;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.iam-sub {
|
||||
margin: 1rem 0 0 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.iam-top-desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-table-title {
|
||||
font-size: 1.2rem;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.org-table-desc {
|
||||
font-size: 14px;
|
||||
}
|
||||
24
console/src/app/pages/instance/instance.component.spec.ts
Normal file
24
console/src/app/pages/instance/instance.component.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { InstanceComponent } from './instance.component';
|
||||
|
||||
describe('IamComponent', () => {
|
||||
let component: InstanceComponent;
|
||||
let fixture: ComponentFixture<InstanceComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [InstanceComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InstanceComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
103
console/src/app/pages/instance/instance.component.ts
Normal file
103
console/src/app/pages/instance/instance.component.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { BehaviorSubject, from, Observable, of } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
|
||||
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-instance',
|
||||
templateUrl: './instance.component.html',
|
||||
styleUrls: ['./instance.component.scss'],
|
||||
})
|
||||
export class InstanceComponent {
|
||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public totalMemberResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<Member.AsObject[]> = new BehaviorSubject<Member.AsObject[]>([]);
|
||||
|
||||
constructor(
|
||||
public adminService: AdminService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService,
|
||||
breadcrumbService: BreadcrumbService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.loadMembers();
|
||||
|
||||
const instanceBread = new Breadcrumb({
|
||||
type: BreadcrumbType.INSTANCE,
|
||||
name: 'Instance',
|
||||
routerLink: ['/instance'],
|
||||
});
|
||||
|
||||
breadcrumbService.setBreadcrumb([instanceBread]);
|
||||
}
|
||||
|
||||
public loadMembers(): void {
|
||||
this.loadingSubject.next(true);
|
||||
from(this.adminService.listIAMMembers(100, 0))
|
||||
.pipe(
|
||||
map((resp) => {
|
||||
if (resp.details?.totalResult) {
|
||||
this.totalMemberResult = resp.details.totalResult;
|
||||
} else {
|
||||
this.totalMemberResult = 0;
|
||||
}
|
||||
return resp.resultList;
|
||||
}),
|
||||
catchError(() => of([])),
|
||||
finalize(() => this.loadingSubject.next(false)),
|
||||
)
|
||||
.subscribe((members) => {
|
||||
this.membersSubject.next(members);
|
||||
});
|
||||
}
|
||||
|
||||
public openAddMember(): void {
|
||||
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
|
||||
data: {
|
||||
creationType: CreationType.IAM,
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
const users: User.AsObject[] = resp.users;
|
||||
const roles: string[] = resp.roles;
|
||||
|
||||
if (users && users.length && roles && roles.length) {
|
||||
Promise.all(
|
||||
users.map((user) => {
|
||||
return this.adminService.addIAMMember(user.id, roles);
|
||||
}),
|
||||
)
|
||||
.then(() => {
|
||||
this.toast.showInfo('IAM.TOAST.MEMBERADDED');
|
||||
setTimeout(() => {
|
||||
this.loadMembers();
|
||||
}, 1000);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
setTimeout(() => {
|
||||
this.loadMembers();
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public showDetail(): void {
|
||||
this.router.navigate(['/instance', 'members']);
|
||||
}
|
||||
}
|
||||
66
console/src/app/pages/instance/instance.module.ts
Normal file
66
console/src/app/pages/instance/instance.module.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatChipsModule } from '@angular/material/chips';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { ChangesModule } from 'src/app/modules/changes/changes.module';
|
||||
import { ContributorsModule } from 'src/app/modules/contributors/contributors.module';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
|
||||
import { OrgTableModule } from 'src/app/modules/org-table/org-table.module';
|
||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||
import { SettingsGridModule } from 'src/app/modules/settings-grid/settings-grid.module';
|
||||
import { SharedModule } from 'src/app/modules/shared/shared.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||
|
||||
import { IamRoutingModule } from './instance-routing.module';
|
||||
import { InstanceComponent } from './instance.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [InstanceComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IamRoutingModule,
|
||||
ChangesModule,
|
||||
CardModule,
|
||||
MatAutocompleteModule,
|
||||
MatChipsModule,
|
||||
MatButtonModule,
|
||||
HasRoleModule,
|
||||
MatCheckboxModule,
|
||||
MetaLayoutModule,
|
||||
MatIconModule,
|
||||
MatTableModule,
|
||||
InputModule,
|
||||
MatSortModule,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
OrgTableModule,
|
||||
MatDialogModule,
|
||||
ContributorsModule,
|
||||
LocalizedDatePipeModule,
|
||||
TimestampToDatePipeModule,
|
||||
SharedModule,
|
||||
RefreshTableModule,
|
||||
HasRolePipeModule,
|
||||
MatSortModule,
|
||||
SettingsGridModule,
|
||||
],
|
||||
})
|
||||
export class InstanceModule {}
|
||||
Reference in New Issue
Block a user