mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-15 18:12:16 +00:00
feat(console): angular 10, iam settings, timestamp to date pipe, org iam indicator, general gui fixes and i18n (#270)
* prettier member dialog, iam indicator * changes i18n * fix timestamp conversion, timestamp to date pipe * rm create, update iam policy * add iam policy * add iam section, members component * add iam contributors * gen admin protos * iam member search * update angular * update cdk material * add module for iam members * add iam roles to member dialog * home shortcuts * project view, i18n * lint
This commit is contained in:
@@ -16,11 +16,11 @@
|
||||
<div class="content">
|
||||
<mat-button-toggle-group formControlName="state" class="toggle" (change)="changeState($event)">
|
||||
<mat-button-toggle [value]="AppState.APPSTATE_INACTIVE" matTooltip="Deactivate Org">
|
||||
<i class="las la-toggle-off"></i>
|
||||
<!-- <i class="las la-toggle-off"></i> -->
|
||||
{{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_INACTIVE | translate}}
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="AppState.APPSTATE_ACTIVE" matTooltip="Activate Org">
|
||||
<i class="las la-toggle-on"></i>
|
||||
<!-- <i class="las la-toggle-on"></i> -->
|
||||
{{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_ACTIVE | translate}}
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
|
||||
@@ -8,9 +8,25 @@
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<ng-template appHasRole [appHasRole]="['iam.write']">
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>
|
||||
<i class="icon las la-gem"></i>
|
||||
{{'HOME.IAM'| translate}}</h2>
|
||||
<p>{{'HOME.IAM_DESC'| translate}}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button [routerLink]="['/iam']">{{'HOME.IAM_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>{{'HOME.SECURITYANDPRIVACY'| translate}}</h2>
|
||||
<h2> <i class="icon las la-user-circle"></i>
|
||||
{{'HOME.SECURITYANDPRIVACY'| translate}}</h2>
|
||||
<p>{{'HOME.SECURITYANDPRIVACY_DESC'| translate}}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
@@ -19,35 +35,50 @@
|
||||
[routerLink]="['/users/me']">{{'HOME.SECURITYANDPRIVACY_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>{{'HOME.PROJECTS'| translate}}</h2>
|
||||
<p>{{'HOME.PROJECTS_DESC'| translate}}</p>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['project.read']">
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>
|
||||
<i class="icon las la-layer-group"></i>
|
||||
{{'HOME.PROJECTS'| translate}}</h2>
|
||||
<p>{{'HOME.PROJECTS_DESC'| translate}}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button [routerLink]="['/projects']">{{'HOME.PROJECTS_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button [routerLink]="['/users/me']">{{'HOME.PROJECTS_BUTTON' | translate}}</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['org.read']">
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2> <i class="icon las la-archway"></i>
|
||||
{{'HOME.PROTECTION'| translate}}</h2>
|
||||
<p>{{'HOME.PROTECTION_DESC'| translate}}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button
|
||||
[routerLink]="['/users/me']">{{'HOME.PROTECTION_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>{{'HOME.PROTECTION'| translate}}</h2>
|
||||
<p>{{'HOME.PROTECTION_DESC'| translate}}</p>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.read']">
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>
|
||||
<i class="las la-crosshairs"></i>
|
||||
{{'HOME.USERS'| translate}}</h2>
|
||||
<p>{{'HOME.USERS_DESC'| translate}}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button [routerLink]="['/users/me']">{{'HOME.USERS_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button [routerLink]="['/users/me']">{{'HOME.PROTECTION_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<div class="top">
|
||||
<h2>{{'HOME.USERS'| translate}}</h2>
|
||||
<p>{{'HOME.USERS_DESC'| translate}}</p>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<div class="footer">
|
||||
<a color="accent" mat-button [routerLink]="['/users/me']">{{'HOME.USERS_BUTTON' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
@@ -66,26 +66,42 @@
|
||||
|
||||
.item {
|
||||
flex: 1 1 45%;
|
||||
box-sizing: border-box;
|
||||
// box-sizing: border-box;
|
||||
margin: 1rem;
|
||||
border: 1px solid #ffffff20;
|
||||
border-radius: .5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: column;
|
||||
transition: border-color .1s;
|
||||
|
||||
&:hover {
|
||||
i {
|
||||
color: #fe11e280;
|
||||
}
|
||||
// border-width: 2px;
|
||||
border-color: #fe11e270;
|
||||
}
|
||||
|
||||
.top {
|
||||
padding: 1rem 2rem;
|
||||
h2 {
|
||||
display: block;
|
||||
margin-top: .5rem;
|
||||
font-family: 'Rubik';
|
||||
font-family: 'Rubik';
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
p {
|
||||
display: block;
|
||||
color: #81868a;
|
||||
font-size: .9rem;
|
||||
}
|
||||
}
|
||||
|
||||
i{
|
||||
font-size: 2.5rem;
|
||||
margin-right: 1rem;
|
||||
transition: color .1s;
|
||||
}
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
@@ -93,22 +109,11 @@
|
||||
}
|
||||
|
||||
.footer {
|
||||
// position: absolute;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
height: 60px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
border-top: 1px solid #ffffff20;
|
||||
|
||||
a {
|
||||
// text-decoration: none;
|
||||
// color: #e8eaed;
|
||||
// font-size: .8rem;
|
||||
// font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { HttpLoaderFactory } from 'src/app/app.module';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
|
||||
import { HomeRoutingModule } from './home-routing.module';
|
||||
import { HomeComponent } from './home.component';
|
||||
@@ -12,19 +13,20 @@ import { HomeComponent } from './home.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [HomeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
HomeRoutingModule,
|
||||
MatButtonModule,
|
||||
TranslateModule.forChild({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient],
|
||||
},
|
||||
}),
|
||||
],
|
||||
declarations: [HomeComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatIconModule,
|
||||
HasRoleModule,
|
||||
HomeRoutingModule,
|
||||
MatButtonModule,
|
||||
TranslateModule.forChild({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useFactory: HttpLoaderFactory,
|
||||
deps: [HttpClient],
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class HomeModule { }
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<div class="groups">
|
||||
<span class="header">{{ 'IAM.MEMBER.TITLE' | translate }}</span>
|
||||
<span class="sub-header">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</span>
|
||||
<div class="people">
|
||||
<div class="img-list">
|
||||
<ng-container *ngIf="totalResult < 10; else compact">
|
||||
<ng-container *ngFor="let member of membersSubject | async">
|
||||
<div (click)="showDetail()" class="avatar-circle"
|
||||
matTooltip="{{ member.email }} | {{member.rolesList?.join(' ')}}">
|
||||
<i class="avatar las la-user-circle"></i>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
<ng-template #compact>
|
||||
<div (click)="showDetail()" class="avatar-circle" matTooltip="Click to show detail">
|
||||
<span>{{totalResult}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<button class="add-img" (click)="openAddMember()" mat-icon-button aria-label="Edit contributors">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,72 @@
|
||||
.groups {
|
||||
padding-top: 1rem;
|
||||
|
||||
.header {
|
||||
display: block;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sub-header {
|
||||
font-size: .8rem;
|
||||
color: #81868a;
|
||||
}
|
||||
|
||||
.people {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.owner {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.img-list {
|
||||
width: 100%;
|
||||
margin-top: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.avatar-img, .avatar-circle {
|
||||
float: left;
|
||||
margin: 0 8px 0 -15px;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 5px 5px rgba(0, 0, 0, 0.5), 0 3px 6px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.add-img {
|
||||
float: left;
|
||||
margin: 0 8px 0 -15px;
|
||||
}
|
||||
|
||||
.avatar-img {
|
||||
&:before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-circle {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: indianred;
|
||||
}
|
||||
|
||||
.margin-neg {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { IamContributorsComponent } from './iam-contributors.component';
|
||||
|
||||
describe('OrgContributorsComponent', () => {
|
||||
let component: IamContributorsComponent;
|
||||
let fixture: ComponentFixture<IamContributorsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IamContributorsComponent],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IamContributorsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should compile', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { BehaviorSubject, from, Observable, of } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { OrgMember, OrgMemberView, OrgState, User } from 'src/app/proto/generated/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import {
|
||||
CreationType,
|
||||
MemberCreateDialogComponent,
|
||||
} from '../../../modules/add-member-dialog/member-create-dialog.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-iam-contributors',
|
||||
templateUrl: './iam-contributors.component.html',
|
||||
styleUrls: ['./iam-contributors.component.scss'],
|
||||
})
|
||||
export class IamContributorsComponent implements OnInit {
|
||||
@Input() public disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
@ViewChild(MatTable) public table!: MatTable<OrgMember.AsObject>;
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
|
||||
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public totalResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<OrgMemberView.AsObject[]>
|
||||
= new BehaviorSubject<OrgMemberView.AsObject[]>([]);
|
||||
|
||||
public OrgState: any = OrgState;
|
||||
constructor(private adminService: AdminService, private dialog: MatDialog,
|
||||
private toast: ToastService,
|
||||
private router: Router) { }
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.loadMembers(0, 25, 'asc');
|
||||
}
|
||||
|
||||
public loadMembers(pageIndex: number, pageSize: number, sortDirection?: string): void {
|
||||
const offset = pageIndex * pageSize;
|
||||
|
||||
this.loadingSubject.next(true);
|
||||
from(this.adminService.SearchIamMembers(pageSize, offset)).pipe(
|
||||
map(resp => {
|
||||
this.totalResult = resp.toObject().totalResult;
|
||||
return resp.toObject().resultList;
|
||||
}),
|
||||
catchError(() => of([])),
|
||||
finalize(() => this.loadingSubject.next(false)),
|
||||
).subscribe(members => {
|
||||
console.log(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.showError('members added');
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public showDetail(): void {
|
||||
this.router.navigate(['iam/members']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
|
||||
import { MemberCreateDialogModule } from '../../../modules/add-member-dialog/member-create-dialog.module';
|
||||
import { IamContributorsComponent } from './iam-contributors.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [IamContributorsComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
MemberCreateDialogModule,
|
||||
HasRoleModule,
|
||||
MatButtonModule,
|
||||
MatDialogModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatIconModule,
|
||||
RouterModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatCheckboxModule,
|
||||
MatTooltipModule,
|
||||
TranslateModule,
|
||||
],
|
||||
exports: [
|
||||
IamContributorsComponent,
|
||||
],
|
||||
})
|
||||
export class IamContributorsModule { }
|
||||
@@ -0,0 +1,64 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { BehaviorSubject, from, Observable, of } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { IamMemberSearchResponse } from 'src/app/proto/generated/admin_pb';
|
||||
import { ProjectMember } from 'src/app/proto/generated/management_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 IamMembersDataSource extends DataSource<ProjectMember.AsObject> {
|
||||
public totalResult: number = 0;
|
||||
public membersSubject: BehaviorSubject<ProjectMember.AsObject[]> = new BehaviorSubject<ProjectMember.AsObject[]>([]);
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
constructor(private adminService: AdminService) {
|
||||
super();
|
||||
}
|
||||
|
||||
public loadMembers(
|
||||
pageIndex: number, pageSize: number, grantId?: string, sortDirection?: string): void {
|
||||
const offset = pageIndex * pageSize;
|
||||
|
||||
this.loadingSubject.next(true);
|
||||
// TODO
|
||||
const promise: Promise<IamMemberSearchResponse> =
|
||||
this.adminService.SearchIamMembers(pageSize, offset);
|
||||
if (promise) {
|
||||
from(promise).pipe(
|
||||
map(resp => {
|
||||
this.totalResult = resp.toObject().totalResult;
|
||||
console.log(this.totalResult);
|
||||
return resp.toObject().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<ProjectMember.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 { IamMembersComponent } from './iam-members.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: IamMembersComponent,
|
||||
data: { animation: 'AddPage' },
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class IamMembersRoutingModule { }
|
||||
@@ -0,0 +1,95 @@
|
||||
<div class="max-width-container">
|
||||
<div class="container">
|
||||
<div class="head">
|
||||
<h1>{{ 'IAM.MEMBER.TITLE' | translate }}</h1>
|
||||
<p class="desc">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<div class="table-header-row" *ngIf="org">
|
||||
<div class="col">
|
||||
<ng-container *ngIf="!selection.hasValue()">
|
||||
<span class="desc">{{'ORG_DETAIL.TABLE.TOTAL' | translate}}</span>
|
||||
<span class="count">{{dataSource?.membersSubject.value.length}}</span>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="selection.hasValue()">
|
||||
<span class="desc">{{'ORG_DETAIL.TABLE.SELECTION' | translate}}</span>
|
||||
<span class="count">{{selection?.selected?.length}}</span>
|
||||
</ng-container>
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<ng-template appHasRole [appHasRole]="['org.member.delete:'+org.id,'org.member.delete']">
|
||||
<button (click)="removeProjectMemberSelection()" matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}"
|
||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template appHasRole [appHasRole]="['org.member.write:'+org.id,'org.member.write']">
|
||||
<a color="primary" [disabled]="disabled" class="add-button" (click)="openAddMember()" color="primary"
|
||||
mat-raised-button>
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="dataSource?.loading$ | async">
|
||||
<mat-spinner diameter="50"></mat-spinner>
|
||||
</div>
|
||||
<table mat-table class="background-style full-width-table" aria-label="Elements" [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<th class="selection" mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? masterToggle() : null"
|
||||
[checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</th>
|
||||
<td class="selection" mat-cell *matCellDef="let row">
|
||||
<mat-checkbox (click)="$event.stopPropagation()"
|
||||
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
|
||||
</mat-checkbox>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="firstname">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
||||
{{member.firstName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="lastname">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
||||
{{member.lastName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="username">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
||||
{{member.userName}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="email">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
||||
{{member.email}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="roles">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
|
||||
<span class="role app-label" *ngFor="let role of member.rolesList; index as i">
|
||||
{{ 'ROLES.'+role | translate }}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;">
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator class="background-style" #paginator [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
106
console/src/app/pages/iam/iam-members/iam-members.component.scss
Normal file
106
console/src/app/pages/iam/iam-members/iam-members.component.scss
Normal file
@@ -0,0 +1,106 @@
|
||||
.container {
|
||||
padding-bottom: 3rem;
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #ffffff20;
|
||||
margin-bottom: 2rem;
|
||||
flex-wrap: wrap;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.desc {
|
||||
width: 100%;
|
||||
display: block;
|
||||
font-size: .9rem;
|
||||
color: #81868a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.desc {
|
||||
font-size: .8rem;
|
||||
color: #81868a;
|
||||
}
|
||||
.count {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
table, mat-paginator {
|
||||
width: 100%;
|
||||
|
||||
td, th {
|
||||
padding: .5rem;
|
||||
&:first-child {
|
||||
padding-left: 0;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.data-row {
|
||||
&:hover {
|
||||
background-color: #ffffff05;
|
||||
}
|
||||
}
|
||||
|
||||
.selection {
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.role {
|
||||
display: inline-block;
|
||||
margin: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pointer {
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { ProjectMembersComponent } from './project-members.component';
|
||||
|
||||
describe('ProjectMembersComponent', () => {
|
||||
let component: ProjectMembersComponent;
|
||||
let fixture: ComponentFixture<ProjectMembersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProjectMembersComponent],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectMembersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should compile', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
109
console/src/app/pages/iam/iam-members/iam-members.component.ts
Normal file
109
console/src/app/pages/iam/iam-members/iam-members.component.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
import { Org, ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { IamMembersDataSource } from './iam-members-datasource';
|
||||
|
||||
@Component({
|
||||
selector: 'app-iam-members',
|
||||
templateUrl: './iam-members.component.html',
|
||||
styleUrls: ['./iam-members.component.scss'],
|
||||
})
|
||||
export class IamMembersComponent implements AfterViewInit {
|
||||
public org!: Org.AsObject;
|
||||
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
|
||||
public disabled: boolean = false;
|
||||
@ViewChild(MatPaginator) public paginator!: MatPaginator;
|
||||
@ViewChild(MatTable) public table!: MatTable<ProjectMember.AsObject>;
|
||||
public dataSource!: IamMembersDataSource;
|
||||
public selection: SelectionModel<ProjectMember.AsObject> = new SelectionModel<ProjectMember.AsObject>(true, []);
|
||||
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
|
||||
|
||||
constructor(private adminService: AdminService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService) {
|
||||
|
||||
this.dataSource = new IamMembersDataSource(this.adminService);
|
||||
this.dataSource.loadMembers(0, 25, 'asc');
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
this.paginator.page
|
||||
.pipe(
|
||||
tap(() => this.loadMembersPage()),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private loadMembersPage(): void {
|
||||
this.dataSource.loadMembers(
|
||||
this.paginator.pageIndex,
|
||||
this.paginator.pageSize,
|
||||
);
|
||||
}
|
||||
|
||||
public removeProjectMemberSelection(): void {
|
||||
Promise.all(this.selection.selected.map(member => {
|
||||
return this.adminService.RemoveIamMember(member.userId).then(() => {
|
||||
this.toast.showInfo('Removed successfully');
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
public removeMember(member: ProjectMember.AsObject): void {
|
||||
this.adminService.RemoveIamMember(member.userId).then(() => {
|
||||
this.toast.showInfo('Member removed successfully');
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
public isAllSelected(): boolean {
|
||||
const numSelected = this.selection.selected.length;
|
||||
const numRows = this.dataSource.membersSubject.value.length;
|
||||
return numSelected === numRows;
|
||||
}
|
||||
|
||||
public masterToggle(): void {
|
||||
this.isAllSelected() ?
|
||||
this.selection.clear() :
|
||||
this.dataSource.membersSubject.value.forEach(row => this.selection.select(row));
|
||||
}
|
||||
|
||||
public openAddMember(): void {
|
||||
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
|
||||
data: {
|
||||
creationType: CreationType.ORG,
|
||||
},
|
||||
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.showError('members added');
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
40
console/src/app/pages/iam/iam-members/iam-members.module.ts
Normal file
40
console/src/app/pages/iam/iam-members/iam-members.module.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
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 { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
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 { IamMembersRoutingModule } from './iam-members-routing.module';
|
||||
import { IamMembersComponent } from './iam-members.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [IamMembersComponent],
|
||||
imports: [
|
||||
IamMembersRoutingModule,
|
||||
CommonModule,
|
||||
MatAutocompleteModule,
|
||||
MatChipsModule,
|
||||
MatButtonModule,
|
||||
MatCheckboxModule,
|
||||
MatIconModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatSortModule,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
],
|
||||
})
|
||||
export class IamMembersModule { }
|
||||
21
console/src/app/pages/iam/iam-routing.module.ts
Normal file
21
console/src/app/pages/iam/iam-routing.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { IamComponent } from './iam.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: IamComponent,
|
||||
},
|
||||
{
|
||||
path: 'members',
|
||||
loadChildren: () => import('./iam-members/iam-members.module').then(m => m.IamMembersModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class IamRoutingModule { }
|
||||
25
console/src/app/pages/iam/iam.component.html
Normal file
25
console/src/app/pages/iam/iam.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<app-meta-layout>
|
||||
<div class="enlarged-container">
|
||||
<h1>{{'IAM.DETAIL.TITLE' | translate}}</h1>
|
||||
<p class="sub">{{'IAM.DETAIL.DESCRIPTION' | translate}}
|
||||
</p>
|
||||
<app-card title="{{ 'ORG.DOMAINS.TITLE' | translate }}"
|
||||
description="{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}">
|
||||
</app-card>
|
||||
</div>
|
||||
|
||||
<metainfo class="side">
|
||||
<!-- <div class="details">
|
||||
|
||||
</div> -->
|
||||
|
||||
<!-- <mat-tab-group mat-stretch-tabs class="tab-group" disablePagination="true">
|
||||
<mat-tab label="Details"> -->
|
||||
<app-iam-contributors>
|
||||
</app-iam-contributors>
|
||||
<!-- </mat-tab>
|
||||
<mat-tab label="{{ 'CHANGES.ORG.TITLE' | translate }}" class="flex-col">
|
||||
</mat-tab>
|
||||
</mat-tab-group> -->
|
||||
</metainfo>
|
||||
</app-meta-layout>
|
||||
151
console/src/app/pages/iam/iam.component.scss
Normal file
151
console/src/app/pages/iam/iam.component.scss
Normal file
@@ -0,0 +1,151 @@
|
||||
h1 {
|
||||
font-family: ailerons;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.sub {
|
||||
color: #81868a;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.state-label {
|
||||
font-size: .9rem;
|
||||
color: #81868a;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -.5rem;
|
||||
|
||||
mat-form-field {
|
||||
flex: 1 1 33%;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.table-header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.desc {
|
||||
font-size: .8rem;
|
||||
color: #81868a;
|
||||
}
|
||||
.count {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.add-button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.domain {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .5rem 0;
|
||||
flex-wrap: wrap;
|
||||
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.verified, .primary{
|
||||
color: #5282c1;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.new-desc {
|
||||
font-size: 14px;
|
||||
color: #818a8a;
|
||||
}
|
||||
|
||||
.new-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
||||
mat-form-field {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.side {
|
||||
.details {
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid #81868a40;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
button {
|
||||
display: none;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
button {
|
||||
display: inline-block;
|
||||
visibility: visible;
|
||||
mat-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.first {
|
||||
flex: 1;
|
||||
font-size: 0.8rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.second {
|
||||
font-size: 0.8rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
margin-left: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
a {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.side-section {
|
||||
color: #81868a;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
console/src/app/pages/iam/iam.component.spec.ts
Normal file
25
console/src/app/pages/iam/iam.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { IamComponent } from './iam.component';
|
||||
|
||||
describe('IamComponent', () => {
|
||||
let component: IamComponent;
|
||||
let fixture: ComponentFixture<IamComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IamComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IamComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
17
console/src/app/pages/iam/iam.component.ts
Normal file
17
console/src/app/pages/iam/iam.component.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-iam',
|
||||
templateUrl: './iam.component.html',
|
||||
styleUrls: ['./iam.component.scss'],
|
||||
})
|
||||
export class IamComponent implements OnInit {
|
||||
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
}
|
||||
56
console/src/app/pages/iam/iam.module.ts
Normal file
56
console/src/app/pages/iam/iam.module.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule, NO_ERRORS_SCHEMA } 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 { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatPaginatorModule } from '@angular/material/paginator';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { ChangesModule } from 'src/app/modules/changes/changes.module';
|
||||
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
|
||||
|
||||
import { IamContributorsModule } from './iam-contributors/iam-contributors.module';
|
||||
import { IamRoutingModule } from './iam-routing.module';
|
||||
import { IamComponent } from './iam.component';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [IamComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IamRoutingModule,
|
||||
ChangesModule,
|
||||
MatAutocompleteModule,
|
||||
MatChipsModule,
|
||||
MatButtonModule,
|
||||
HasRoleModule,
|
||||
MatCheckboxModule,
|
||||
MetaLayoutModule,
|
||||
MatIconModule,
|
||||
MatTabsModule,
|
||||
MatTableModule,
|
||||
MatPaginatorModule,
|
||||
MatFormFieldModule,
|
||||
MatSortModule,
|
||||
MatTooltipModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
MatDialogModule,
|
||||
IamContributorsModule,
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA],
|
||||
})
|
||||
export class IamModule { }
|
||||
@@ -10,9 +10,9 @@
|
||||
<i matTooltip="verified" *ngIf="domain.verified" class="verified las la-check-circle"></i>
|
||||
<i matTooltip="primary" *ngIf="domain.primary" class="primary las la-chess-queen"></i>
|
||||
<span class="fill-space"></span>
|
||||
<button disabled mat-icon-button
|
||||
<!-- <button disabled mat-icon-button
|
||||
matTooltip="download /.well-known/caos-developer-domain-association.txt and deploy it on your domain. Then verify"><i
|
||||
class="las la-file-download"></i></button>
|
||||
class="las la-file-download"></i></button> -->
|
||||
<button matTooltip="Remove domain" color="warn" mat-icon-button (click)="removeDomain(domain.domain)"><i
|
||||
class="las la-trash"></i></button>
|
||||
</div>
|
||||
@@ -28,10 +28,10 @@
|
||||
<mat-icon>check</mat-icon>
|
||||
</button>
|
||||
|
||||
<button disabled mat-icon-button
|
||||
<!-- <button disabled mat-icon-button
|
||||
matTooltip="download /.well-known/caos-developer-domain-association.txt and deploy it on your domain. Then verify"><i
|
||||
class="las la-file-download"></i></button>
|
||||
<button disabled mat-icon-button matTooltip="Verify"><i class=" las la-check-circle"></i></button>
|
||||
class="las la-file-download"></i></button> -->
|
||||
<!-- <button disabled mat-icon-button matTooltip="Verify"><i class=" las la-check-circle"></i></button> -->
|
||||
</div>
|
||||
</app-card>
|
||||
|
||||
|
||||
@@ -76,6 +76,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
||||
public saveNewOrgDomain(): void {
|
||||
this.orgService.AddMyOrgDomain(this.newDomain).then(domain => {
|
||||
this.domains.push(domain.toObject());
|
||||
this.newDomain = '';
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,14 +14,7 @@
|
||||
<div matTooltip="{{'ORG.PAGES.SELECTORGTOOLTIP' | translate}}" class="item card"
|
||||
*ngFor="let org of orgList; index as i" (click)="selectOrg(org, $event)"
|
||||
[ngClass]="{ selected: selection.isSelected(org),active: activeOrg?.id === org?.id }">
|
||||
<!-- <mat-icon matTooltip="select org" (click)="selection.toggle(org)" class="selection-icon">
|
||||
check_circle</mat-icon> -->
|
||||
|
||||
<div class="text-part">
|
||||
<!-- <span *ngIf="org?.changeDate" class="top">last modified on
|
||||
{{
|
||||
dateFromTimestamp(org.changeDate) | date: 'EEE dd. MMM, HH:mm'
|
||||
}}</span> -->
|
||||
<span class="description">{{org.id}}</span>
|
||||
|
||||
<span class="name" *ngIf="org.name">{{ org.name }}</span>
|
||||
@@ -39,9 +32,6 @@
|
||||
<button (click)="routeToOrg(org)" mat-menu-item>
|
||||
{{'ACTIONS.VIEW' | translate}}
|
||||
</button>
|
||||
<!-- <button (click)="selection.toggle(org)" mat-menu-item>
|
||||
{{'ACTIONS.INFO' | translate}}
|
||||
</button> -->
|
||||
</ng-template>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { Org } from 'src/app/proto/generated/auth_pb';
|
||||
import { AuthUserService } from 'src/app/services/auth-user.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
@@ -49,9 +48,4 @@ export class OrgGridComponent {
|
||||
public routeToOrg(item: Org.AsObject): void {
|
||||
this.router.navigate(['/orgs', item.id]);
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<ng-template appHasRole [appHasRole]="['org.member.delete:'+org.id,'org.member.delete']">
|
||||
<button (click)="removeProjectMemberSelection()"
|
||||
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="icon-button" mat-icon-button
|
||||
*ngIf="selection.hasValue()">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
*ngIf="selection.hasValue()" color="warn">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template appHasRole [appHasRole]="['org.member.write:'+org.id,'org.member.write']">
|
||||
|
||||
@@ -24,6 +24,7 @@ const routes: Routes = [
|
||||
action: PolicyComponentAction.CREATE,
|
||||
},
|
||||
},
|
||||
/// TODO: add roleguard for iam policy
|
||||
{
|
||||
path: 'policy/:policytype',
|
||||
component: PasswordPolicyComponent,
|
||||
|
||||
@@ -123,6 +123,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content" *ngIf="policyType === PolicyComponentType?.IAM_POLICY">
|
||||
<mat-form-field class="description-formfield" appearance="outline">
|
||||
<mat-label>{{ 'ORG.POLICY.DATA.DESCRIPTION' | translate }}</mat-label>
|
||||
<input matInput name="description" ngDefaultControl [(ngModel)]="iamData.description"
|
||||
required />
|
||||
</mat-form-field>
|
||||
<div class="row">
|
||||
<span class="left-desc">{{'ORG.POLICY.DATA.USERLOGINMUSTBEDOMAIN' | translate}}</span>
|
||||
<span class="fill-space"></span>
|
||||
<mat-slide-toggle name="hasNumber" ngDefaultControl [(ngModel)]="iamData.userLoginMustBeDomain">
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="btn-container">
|
||||
<button (click)="savePolicy()" color="accent" type="submit"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
|
||||
@@ -3,8 +3,15 @@ import { FormGroup } from '@angular/forms';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { PasswordAgePolicy, PasswordComplexityPolicy, PasswordLockoutPolicy } from 'src/app/proto/generated/management_pb';
|
||||
import {
|
||||
OrgIamPolicy,
|
||||
PasswordAgePolicy,
|
||||
PasswordComplexityPolicy,
|
||||
PasswordLockoutPolicy,
|
||||
} from 'src/app/proto/generated/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { OrgService } from 'src/app/services/org.service';
|
||||
import { StorageService } from 'src/app/services/storage.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
export enum PolicyComponentAction {
|
||||
@@ -16,6 +23,7 @@ export enum PolicyComponentType {
|
||||
LOCKOUT = 'lockout',
|
||||
AGE = 'age',
|
||||
COMPLEXITY = 'complexity',
|
||||
IAM_POLICY = 'iam_policy',
|
||||
}
|
||||
|
||||
@Component({
|
||||
@@ -24,13 +32,15 @@ export enum PolicyComponentType {
|
||||
styleUrls: ['./password-policy.component.scss'],
|
||||
})
|
||||
export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
public orgId: string = '';
|
||||
titleSub: BehaviorSubject<string> = new BehaviorSubject('');
|
||||
descSub: BehaviorSubject<string> = new BehaviorSubject('');
|
||||
|
||||
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
|
||||
|
||||
policyData!: PasswordLockoutPolicy.AsObject | PasswordAgePolicy.AsObject | PasswordComplexityPolicy.AsObject;
|
||||
policyData!: PasswordLockoutPolicy.AsObject |
|
||||
PasswordAgePolicy.AsObject |
|
||||
PasswordComplexityPolicy.AsObject |
|
||||
OrgIamPolicy.AsObject;
|
||||
policyType: PolicyComponentType = PolicyComponentType.COMPLEXITY;
|
||||
|
||||
public PolicyComponentType: any = PolicyComponentType;
|
||||
@@ -60,19 +70,25 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
maxAgeDays: 90,
|
||||
};
|
||||
|
||||
public iamData: any = {
|
||||
description: '',
|
||||
userLoginMustBeDomain: false,
|
||||
};
|
||||
|
||||
private sub: Subscription = new Subscription();
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private adminService: AdminService,
|
||||
private orgService: OrgService,
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
private sessionStorage: StorageService,
|
||||
) {
|
||||
this.sub = this.route.data.pipe(switchMap(data => {
|
||||
this.componentAction = data.action;
|
||||
return this.route.params;
|
||||
})).subscribe(params => {
|
||||
this.orgId = params.id;
|
||||
this.policyType = params.policytype;
|
||||
|
||||
switch (params.policytype) {
|
||||
@@ -88,6 +104,10 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.titleSub.next('ORG.POLICY.PWD_COMPLEXITY.TITLECREATE');
|
||||
this.descSub.next('ORG.POLICY.PWD_COMPLEXITY.DESCRIPTIONCREATE');
|
||||
break;
|
||||
case PolicyComponentType.IAM_POLICY:
|
||||
this.titleSub.next('ORG.POLICY.IAM_POLICY.TITLECREATE');
|
||||
this.descSub.next('ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE');
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.componentAction === PolicyComponentAction.MODIFY) {
|
||||
@@ -102,6 +122,10 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
case PolicyComponentType.COMPLEXITY:
|
||||
this.complexityData = data.toObject();
|
||||
break;
|
||||
case PolicyComponentType.IAM_POLICY:
|
||||
this.iamData = data.toObject();
|
||||
console.log(this.iamData);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -129,6 +153,10 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.titleSub.next('ORG.POLICY.PWD_COMPLEXITY.TITLE');
|
||||
this.descSub.next('ORG.POLICY.PWD_COMPLEXITY.DESCRIPTION');
|
||||
return this.orgService.GetPasswordComplexityPolicy();
|
||||
case PolicyComponentType.IAM_POLICY:
|
||||
this.titleSub.next('ORG.POLICY.IAM_POLICY.TITLECREATE');
|
||||
this.descSub.next('ORG.POLICY.IAM_POLICY.DESCRIPTIONCREATE');
|
||||
return this.orgService.GetMyOrgIamPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +217,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.lockoutData.maxAttempts,
|
||||
this.lockoutData.showLockOutFailures,
|
||||
).then(() => {
|
||||
this.router.navigate(['orgs', this.orgId]);
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
@@ -201,7 +229,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.ageData.maxAgeDays,
|
||||
this.ageData.expireWarnDays,
|
||||
).then(() => {
|
||||
this.router.navigate(['orgs', this.orgId]);
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
@@ -217,11 +245,27 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.complexityData.hasSymbol,
|
||||
this.complexityData.minLength,
|
||||
).then(() => {
|
||||
this.router.navigate(['orgs', this.orgId]);
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
break;
|
||||
|
||||
case PolicyComponentType.IAM_POLICY:
|
||||
console.log(this.complexityData);
|
||||
const orgId = this.sessionStorage.getItem('organization');
|
||||
if (orgId) {
|
||||
this.adminService.CreateOrgIamPolicy(
|
||||
orgId,
|
||||
this.complexityData.description,
|
||||
this.complexityData.userLoginMustBeDomain,
|
||||
).then(() => {
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (this.componentAction === PolicyComponentAction.MODIFY) {
|
||||
switch (this.policyType) {
|
||||
@@ -231,7 +275,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.lockoutData.maxAttempts,
|
||||
this.lockoutData.showLockOutFailures,
|
||||
).then(() => {
|
||||
this.router.navigate(['orgs', this.orgId]);
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
@@ -243,7 +287,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.ageData.maxAgeDays,
|
||||
this.ageData.expireWarnDays,
|
||||
).then(() => {
|
||||
this.router.navigate(['orgs', this.orgId]);
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
@@ -259,11 +303,27 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
|
||||
this.complexityData.hasSymbol,
|
||||
this.complexityData.minLength,
|
||||
).then(() => {
|
||||
this.router.navigate(['orgs', this.orgId]);
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
break;
|
||||
|
||||
case PolicyComponentType.IAM_POLICY:
|
||||
console.log(this.complexityData);
|
||||
const orgId = this.sessionStorage.getItem('organization');
|
||||
if (orgId) {
|
||||
this.adminService.UpdateOrgIamPolicy(
|
||||
orgId,
|
||||
this.complexityData.description,
|
||||
this.complexityData.userLoginMustBeDomain,
|
||||
).then(() => {
|
||||
this.router.navigate(['org']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error.message);
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<ng-template appHasRole [appHasRole]="['policy.delete']">
|
||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" (click)="deletePolicy(PolicyComponentType.AGE)"
|
||||
mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -46,12 +46,11 @@
|
||||
<div class="title">
|
||||
<span>{{'ORG.POLICY.PWD_COMPLEXITY.TITLE' | translate}}</span>
|
||||
<button mat-icon-button disabled>
|
||||
<mat-icon class="icon" *ngIf="complexityPolicy">
|
||||
check_circle</mat-icon>
|
||||
<i *ngIf="complexityPolicy" class="icon las la-check-circle"></i>
|
||||
</button>
|
||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}"
|
||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn"
|
||||
(click)="deletePolicy(PolicyComponentType.COMPLEXITY)" mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +80,7 @@
|
||||
</button>
|
||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" (click)="deletePolicy(PolicyComponentType.LOCKOUT)"
|
||||
mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -99,4 +98,39 @@
|
||||
mat-raised-button>{{'ORG.POLICY.BTN_EDIT' | translate}}</button>
|
||||
</div>
|
||||
</div> -->
|
||||
<ng-template appHasRole [appHasRole]="['iam.policy.read']">
|
||||
<div class="p-item card">
|
||||
<div class="avatar"><i class="icon las la-gem"></i>
|
||||
</div>
|
||||
<div class="title">
|
||||
<span>{{'ORG.POLICY.IAM_POLICY.TITLE' | translate}}</span>
|
||||
<button mat-icon-button disabled>
|
||||
<i *ngIf="iamPolicy" class="icon las la-check-circle"></i>
|
||||
</button>
|
||||
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
||||
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn"
|
||||
(click)="deletePolicy(PolicyComponentType.IAM_POLICY)" mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<p *ngIf="iamPolicy?.description; else showDescIAM" class="desc">
|
||||
{{ iamPolicy.description }}</p>
|
||||
<ng-template #showDescIAM>
|
||||
<p class="desc">
|
||||
{{'ORG.POLICY.IAM_POLICY.DESCRIPTION' | translate}}</p>
|
||||
</ng-template>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<div class="btn-wrapper">
|
||||
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
|
||||
<button [disabled]="iamPolicy" [routerLink]="[ 'policy', PolicyComponentType.IAM_POLICY,'create' ]"
|
||||
color="primary" mat-raised-button>{{'ORG.POLICY.BTN_INSTALL' | translate}}</button>
|
||||
<button [disabled]="!iamPolicy" [routerLink]="[ 'policy', PolicyComponentType.IAM_POLICY ]"
|
||||
mat-raised-button>{{'ORG.POLICY.BTN_EDIT' | translate}}</button>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -30,7 +30,7 @@ h1 {
|
||||
justify-content: center;
|
||||
margin-bottom: .5rem;
|
||||
|
||||
mat-icon {
|
||||
mat-icon, i {
|
||||
font-size: 2.5rem;
|
||||
height: 2.5rem;
|
||||
line-height: 2.5rem;
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import {
|
||||
OrgIamPolicy,
|
||||
PasswordAgePolicy,
|
||||
PasswordComplexityPolicy,
|
||||
PasswordLockoutPolicy,
|
||||
PolicyState,
|
||||
} from 'src/app/proto/generated/management_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { AuthUserService } from 'src/app/services/auth-user.service';
|
||||
import { OrgService } from 'src/app/services/org.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@@ -20,11 +22,17 @@ export class PolicyGridComponent implements OnInit {
|
||||
public lockoutPolicy!: PasswordLockoutPolicy.AsObject;
|
||||
public agePolicy!: PasswordAgePolicy.AsObject;
|
||||
public complexityPolicy!: PasswordComplexityPolicy.AsObject;
|
||||
public iamPolicy!: OrgIamPolicy.AsObject;
|
||||
|
||||
public PolicyState: any = PolicyState;
|
||||
public PolicyComponentType: any = PolicyComponentType;
|
||||
|
||||
constructor(private orgService: OrgService, public authUserService: AuthUserService, private toast: ToastService) {
|
||||
constructor(
|
||||
private orgService: OrgService,
|
||||
private adminService: AdminService,
|
||||
public authUserService: AuthUserService,
|
||||
private toast: ToastService,
|
||||
) {
|
||||
this.getData();
|
||||
}
|
||||
|
||||
@@ -36,6 +44,8 @@ export class PolicyGridComponent implements OnInit {
|
||||
// this.orgService.GetPasswordAgePolicy().then(data => this.agePolicy = data.toObject()).catch(error => { });
|
||||
this.orgService.GetPasswordComplexityPolicy().then(data => this.complexityPolicy = data.toObject())
|
||||
.catch(error => { });
|
||||
this.orgService.GetMyOrgIamPolicy().then(data => this.iamPolicy = data.toObject())
|
||||
.catch(error => { });
|
||||
}
|
||||
|
||||
public deletePolicy(type: PolicyComponentType): void {
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="card org" *ngIf="org">
|
||||
<p>{{org?.name}}</p>
|
||||
<span *ngIf="org.creationDate">created:
|
||||
{{dateFromTimestamp(org.creationDate) | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
{{org.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { Org, ProjectRole } from 'src/app/proto/generated/management_pb';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
@@ -76,12 +75,6 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
|
||||
public selectRoles(roles: ProjectRole.AsObject[]): void {
|
||||
this.rolesKeyList = roles.map(role => role.key);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { PipesModule } from 'src/app/pipes/pipes.module';
|
||||
|
||||
import { ProjectRolesModule } from '../../modules/project-roles/project-roles.module';
|
||||
import { ProjectGrantCreateRoutingModule } from './project-grant-create-routing.module';
|
||||
@@ -32,6 +33,7 @@ import { ProjectGrantCreateComponent } from './project-grant-create.component';
|
||||
ProjectRolesModule,
|
||||
MatIconModule,
|
||||
MatTooltipModule,
|
||||
PipesModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
|
||||
@@ -29,8 +29,9 @@
|
||||
<mat-label>{{ 'PROJECT.ROLE.GROUP' | translate }}</mat-label>
|
||||
<input matInput formControlName="group" />
|
||||
</mat-form-field>
|
||||
<button mat-icon-button (click)="removeEntry(i)" matTooltip="{{ 'ACTIONS.REMOVE' | translate }}">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<button mat-icon-button (click)="removeEntry(i)" color="warn"
|
||||
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-container>
|
||||
</div>
|
||||
|
||||
@@ -24,17 +24,17 @@
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.changeDate" class="top">last modified on
|
||||
{{
|
||||
dateFromTimestamp(item.changeDate) | date: 'EEE dd. MMM, HH:mm'
|
||||
item.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.projectName">{{ item.projectName }}</span>
|
||||
<span class="description" *ngIf="item.state">{{'PROJECT.STATE.'+item.state | translate}}</span>
|
||||
<span class="description" *ngIf="item.grantedOrgName">{{item.grantedOrgName}}</span>
|
||||
<!-- <span class="description" *ngIf="item.state">{{'PROJECT.STATE.'+item.state | translate}}</span> -->
|
||||
<span *ngIf="item.changeDate" class="created">created on
|
||||
{{
|
||||
dateFromTimestamp(item.creationDate) | date: 'EEE dd. MMM, HH:mm'
|
||||
item.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="fill-space"></span>
|
||||
<div class="icons">
|
||||
<mat-icon class="icon">apps</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button [matMenuTriggerFor]="editMenu" class="edit-button" mat-icon-button>
|
||||
@@ -53,4 +53,4 @@
|
||||
</mat-menu>
|
||||
</div>
|
||||
<p class="n-items" *ngIf="!loading && items.length === 0">{{'PROJECT.PAGES.NOITEMS' | translate}}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,7 +2,6 @@ import { animate, animateChild, query, stagger, style, transition, trigger } fro
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { ProjectGrantView, ProjectState, ProjectType, ProjectView } from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectService } from 'src/app/services/project.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@@ -57,9 +56,4 @@ export class GrantedProjectGridComponent {
|
||||
public addItem(): void {
|
||||
this.newClicked.emit(true);
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.creationDate">{{dateFromTimestamp(project.creationDate) | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.changeDate">{{dateFromTimestamp(project.changeDate) | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PageEvent } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { ProjectGrantView } from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectService } from 'src/app/services/project.service';
|
||||
@@ -99,11 +98,6 @@ export class GrantedProjectListComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
|
||||
public reactivateSelectedProjects(): void {
|
||||
const promises = this.selection.selected.map(project => {
|
||||
this.projectService.ReactivateProject(project.id);
|
||||
|
||||
@@ -25,17 +25,17 @@
|
||||
<div class="text-part">
|
||||
<span *ngIf="item.changeDate" class="top">last modified on
|
||||
{{
|
||||
dateFromTimestamp(item.changeDate) | date: 'EEE dd. MMM, HH:mm'
|
||||
item.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="name" *ngIf="item.name">{{ item.name }}</span>
|
||||
<span class="description" *ngIf="item.state">{{'PROJECT.STATE.'+item.state | translate}}</span>
|
||||
<!-- <span class="description" *ngIf="item.state">{{'PROJECT.STATE.'+item.state | translate}}</span> -->
|
||||
|
||||
<span *ngIf="item.changeDate" class="created">created on
|
||||
{{
|
||||
dateFromTimestamp(item.creationDate) | date: 'EEE dd. MMM, HH:mm'
|
||||
item.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'
|
||||
}}</span>
|
||||
<span class="fill-space"></span>
|
||||
<div class="icons">
|
||||
<mat-icon class="icon">apps</mat-icon>
|
||||
</div>
|
||||
</div>
|
||||
<button [matMenuTriggerFor]="editMenu" class="edit-button" mat-icon-button>
|
||||
@@ -56,8 +56,10 @@
|
||||
|
||||
<p class="n-items" *ngIf="!loading && items.length === 0">{{'PROJECT.PAGES.NOITEMS' | translate}}</p>
|
||||
|
||||
<div class="add-project-button card" (click)="addItem()">
|
||||
<mat-icon class="icon">add</mat-icon>
|
||||
<span>Add new project</span>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template appHasRole [appHasRole]="['project.write']">
|
||||
<div class="add-project-button card" (click)="addItem()">
|
||||
<mat-icon class="icon">add</mat-icon>
|
||||
<span>Add new project</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
@@ -2,7 +2,6 @@ import { animate, animateChild, query, stagger, style, transition, trigger } fro
|
||||
import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { ProjectState, ProjectType, ProjectView } from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectService } from 'src/app/services/project.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
@@ -58,11 +57,6 @@ export class OwnedProjectGridComponent {
|
||||
this.newClicked.emit(true);
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
|
||||
public reactivateProjects(selected: ProjectView.AsObject[]): void {
|
||||
Promise.all([selected.map(proj => {
|
||||
return this.projectService.ReactivateProject(proj.projectId);
|
||||
|
||||
@@ -30,9 +30,11 @@
|
||||
<mat-icon svgIcon="mdi_light_on"></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<a class="add-button" [routerLink]="[ '/projects', 'create']" color="primary" mat-raised-button>
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||
</a>
|
||||
<ng-template appHasRole [appHasRole]="['project.write']">
|
||||
<a class="add-button" [routerLink]="[ '/projects', 'create']" color="primary" mat-raised-button>
|
||||
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
|
||||
</a>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<div class="spinner-container" *ngIf="(loading$ | async) || (loading$ | async)">
|
||||
@@ -69,7 +71,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CREATIONDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.creationDate">{{dateFromTimestamp(project.creationDate) | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.creationDate">{{project.creationDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -78,7 +80,7 @@
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.TABLE.CHANGEDATE' | translate }} </th>
|
||||
<td mat-cell *matCellDef="let project">
|
||||
<span
|
||||
*ngIf="project.changeDate">{{dateFromTimestamp(project.changeDate) | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
*ngIf="project.changeDate">{{project.changeDate | timestampToDate | date: 'EEE dd. MMM, HH:mm'}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
@@ -90,4 +92,4 @@
|
||||
<mat-paginator class="background-style" [length]="totalResult" [pageSize]="10" [pageSizeOptions]="[5, 10, 20]"
|
||||
(page)="changePage($event)"></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -5,7 +5,6 @@ import { PageEvent } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { ProjectView } from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectService } from 'src/app/services/project.service';
|
||||
@@ -103,11 +102,6 @@ export class OwnedProjectListComponent implements OnInit, OnDestroy {
|
||||
this.ownedProjectList = [];
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
|
||||
public reactivateSelectedProjects(): void {
|
||||
const promises = this.selection.selected.map(project => {
|
||||
this.projectService.ReactivateProject(project.projectId);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<span class="fill-space"></span>
|
||||
<!-- <button [disabled]="disabled" matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="icon-button"
|
||||
mat-icon-button *ngIf="selection.hasValue()">
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<i class="las la-trash"></i>
|
||||
</button> -->
|
||||
<ng-template appHasRole [appHasRole]="['project.app.write']">
|
||||
<a [disabled]="disabled" class="add-button" [routerLink]="[ '/projects', projectId, 'apps', 'create']"
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<button (click)="removeProjectMemberSelection()" matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}"
|
||||
class="icon-button" mat-icon-button *ngIf="selection.hasValue()">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
class="icon-button" color="warn" mat-icon-button *ngIf="selection.hasValue()">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
<a color="primary" [disabled]="disabled" class="add-button" (click)="openAddMember()" color="primary"
|
||||
mat-raised-button>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<span class="fill-space"></span>
|
||||
<ng-template appHasRole [appHasRole]="['project.grant.member.delete:'+projectId, 'project.grant.member.delete']">
|
||||
<button [disabled]="disabled" matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="icon-button"
|
||||
mat-icon-button *ngIf="selection.hasValue()">
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
mat-icon-button *ngIf="selection.hasValue()" color="warn">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template appHasRole [appHasRole]="['project.grant.member.write:'+projectId,'project.grant.member.write']">
|
||||
@@ -59,13 +59,13 @@
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CREATIONDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
{{dateFromTimestamp(grant.creationDate) | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.creationDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
{{dateFromTimestamp(grant.changeDate) | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.changeDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/cor
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ProjectGrant, ProjectMemberView } from 'src/app/proto/generated/management_pb';
|
||||
import { ProjectService } from 'src/app/services/project.service';
|
||||
@@ -131,9 +130,4 @@ export class ProjectGrantsComponent implements OnInit, AfterViewInit {
|
||||
this.toast.showInfo(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@
|
||||
<span class="fill-space"></span>
|
||||
<ng-template appHasRole
|
||||
[appHasRole]="['project.member.delete:' + project.projectId, 'project.member.delete']">
|
||||
<button (click)="removeProjectMemberSelection()"
|
||||
<button (click)="removeProjectMemberSelection()" color="warn"
|
||||
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" class="icon-button" mat-icon-button
|
||||
*ngIf="selection.hasValue()">
|
||||
<mat-icon>remove_circle</mat-icon>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template appHasRole
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
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 { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component';
|
||||
import { OwnedProjectDetailComponent } from './owned-project-detail/owned-project-detail.component';
|
||||
@@ -14,6 +16,10 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'create',
|
||||
loadChildren: () => import('../project-create/project-create.module').then(m => m.ProjectCreateModule),
|
||||
canActivate: [AuthGuard, RoleGuard],
|
||||
data: {
|
||||
roles: ['project.write'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: ':id/grant/:grantId',
|
||||
|
||||
@@ -25,6 +25,7 @@ import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
|
||||
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
|
||||
import { PipesModule } from 'src/app/pipes/pipes.module';
|
||||
|
||||
import { ChangesModule } from '../../modules/changes/changes.module';
|
||||
import { ProjectRolesModule } from '../../modules/project-roles/project-roles.module';
|
||||
@@ -95,6 +96,7 @@ import { ProjectsComponent } from './projects.component';
|
||||
CardModule,
|
||||
MatTooltipModule,
|
||||
MatSortModule,
|
||||
PipesModule,
|
||||
OrgContributorsModule,
|
||||
TranslateModule.forChild({
|
||||
loader: {
|
||||
|
||||
@@ -167,8 +167,8 @@
|
||||
<button (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="phone?.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<button color="warn" *ngIf="phone?.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
<div class="row" *ngFor="let mfa of mfaSubject | async">
|
||||
<span>{{'USER.MFA.TYPE.'+ mfa.type | translate}}</span>
|
||||
<span>{{'USER.MFA.STATE.'+ mfa.state | translate}}</span>
|
||||
<button mat-icon-button (click)="deleteMFA(mfa.type)" matTooltip="{{'ACTIONS.DELETE' | translate}}">
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<button mat-icon-button (click)="deleteMFA(mfa.type)" color="warn"
|
||||
matTooltip="{{'ACTIONS.DELETE' | translate}}">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="row" *ngIf="error">{{error}}</p>
|
||||
|
||||
@@ -153,8 +153,8 @@
|
||||
<button (click)="phoneEditState = true" mat-icon-button>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button *ngIf="user?.phone" (click)="deletePhone()" mat-icon-button>
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<button *ngIf="user?.phone" (click)="deletePhone()" color="warn" mat-icon-button>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
</div>
|
||||
<span class="fill-space"></span>
|
||||
<ng-template appHasRole [appHasRole]="['user.grant.delete:'+userId,'user.grant.delete']">
|
||||
<button matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button
|
||||
<button color="warn" matTooltip="{{'GRANTS.DELETE' | translate}}" class="icon-button" mat-icon-button
|
||||
*ngIf="selection.hasValue()">
|
||||
<mat-icon>delete_outline</mat-icon>
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template appHasRole [appHasRole]="['user.grant.write:'+userId,'user.grant.write']">
|
||||
@@ -59,13 +59,13 @@
|
||||
<ng-container matColumnDef="creationDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CREATIONDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
{{dateFromTimestamp(grant.creationDate) | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.creationDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="changeDate">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.GRANT.CHANGEDATE' | translate }} </th>
|
||||
<td class="pointer" mat-cell *matCellDef="let grant">
|
||||
{{dateFromTimestamp(grant.changeDate) | date: 'dd. MMM, HH:mm' }} </td>
|
||||
{{grant.changeDate | timestampToDate | date: 'dd. MMM, HH:mm' }} </td>
|
||||
|
||||
</ng-container>
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import { SelectionModel } from '@angular/cdk/collections';
|
||||
import { AfterViewInit, Component, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTable } from '@angular/material/table';
|
||||
import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { ProjectGrant, UserGrant } from 'src/app/proto/generated/management_pb';
|
||||
import { MgmtUserService } from 'src/app/services/mgmt-user.service';
|
||||
@@ -59,9 +58,4 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
||||
this.selection.clear() :
|
||||
this.dataSource.grantsSubject.value.forEach(row => this.selection.select(row));
|
||||
}
|
||||
|
||||
public dateFromTimestamp(date: Timestamp.AsObject): any {
|
||||
const ts: Date = new Date(date.seconds * 1000 + date.nanos / 1000);
|
||||
return ts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { PipesModule } from 'src/app/pipes/pipes.module';
|
||||
|
||||
import { UserGrantsComponent } from './user-grants.component';
|
||||
|
||||
@@ -31,6 +32,7 @@ import { UserGrantsComponent } from './user-grants.component';
|
||||
MatCheckboxModule,
|
||||
MatTooltipModule,
|
||||
TranslateModule,
|
||||
PipesModule,
|
||||
],
|
||||
exports: [
|
||||
UserGrantsComponent,
|
||||
|
||||
Reference in New Issue
Block a user