feat(console): pinned org list, project grant detail view, state change, i18n, domain dialog, policy refactor, theme fixes, refactor user and projects (#449)

* pinned organisations

* project grant detail, state update, user-list pad

* rm entry components

* members nav, i18n, disable actions on non active

* add org domain dialog

* mv password policy rm to detail view

* prefix pinned orgs for userid, fix collapsed pad

* fix app back navigation

* rem pwd required validator

* fix org item overflow

* routing

* move users modules to users page

* reorganize projects

* remove child init of translate

* hide same preferred loginname
This commit is contained in:
Max Peintner 2020-07-13 13:38:49 +02:00 committed by GitHub
parent d8eef34a37
commit b628baeb1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
193 changed files with 967 additions and 500 deletions

View File

@ -12,7 +12,8 @@ const routes: Routes = [
}, },
{ {
path: 'granted-projects', path: 'granted-projects',
loadChildren: () => import('./pages/granted-projects/granted-projects.module').then(m => m.GrantedProjectsModule), loadChildren: () => import('./pages/projects/granted-projects/granted-projects.module')
.then(m => m.GrantedProjectsModule),
canActivate: [AuthGuard, RoleGuard], canActivate: [AuthGuard, RoleGuard],
data: { data: {
roles: ['project.read'], roles: ['project.read'],
@ -20,20 +21,16 @@ const routes: Routes = [
}, },
{ {
path: 'projects', path: 'projects',
loadChildren: () => import('./pages/owned-projects/owned-projects.module').then(m => m.OwnedProjectsModule), loadChildren: () => import('./pages/projects/owned-projects/owned-projects.module')
.then(m => m.OwnedProjectsModule),
canActivate: [AuthGuard, RoleGuard], canActivate: [AuthGuard, RoleGuard],
data: { data: {
roles: ['project.read'], roles: ['project.read'],
}, },
}, },
{
path: 'user',
loadChildren: () => import('./pages/user-detail/user-detail.module').then(m => m.UserDetailModule),
canActivate: [AuthGuard],
},
{ {
path: 'users', path: 'users',
loadChildren: () => import('./pages/user-list/user-list.module').then(m => m.UserListModule), loadChildren: () => import('./pages/users/users.module').then(m => m.UsersModule),
canActivate: [AuthGuard, RoleGuard], canActivate: [AuthGuard, RoleGuard],
data: { data: {
roles: ['user.read'], roles: ['user.read'],
@ -55,7 +52,6 @@ const routes: Routes = [
roles: ['org.read'], roles: ['org.read'],
}, },
}, },
{ {
path: 'grant-create/project/:projectid/grant/:grantid', path: 'grant-create/project/:projectid/grant/:grantid',
loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module') loadChildren: () => import('src/app/pages/user-grant-create/user-grant-create.module')

View File

@ -54,7 +54,7 @@
<div class="list"> <div class="list">
<ng-container *ngIf="authService.authenticationChanged | async"> <ng-container *ngIf="authService.authenticationChanged | async">
<a class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }" <a class="nav-item" [routerLinkActive]="['active']" [routerLinkActiveOptions]="{ exact: true }"
[routerLink]="['/user/me']"> [routerLink]="['/users/me']">
<i class="icon las la-user-circle"></i> <i class="icon las la-user-circle"></i>
<span class="label">{{ 'MENU.PERSONAL_INFO' | translate }}</span> <span class="label">{{ 'MENU.PERSONAL_INFO' | translate }}</span>
</a> </a>
@ -104,7 +104,7 @@
<div class="line"></div> <div class="line"></div>
</div> </div>
<a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/users']" <a class="nav-item" [routerLinkActive]="['active']" [routerLink]="[ '/users/all']"
[routerLinkActiveOptions]="{ exact: true }"> [routerLinkActiveOptions]="{ exact: true }">
<i class="icon las la-users"></i> <i class="icon las la-users"></i>
<span class="label">{{ 'MENU.USER' | translate }}</span> <span class="label">{{ 'MENU.USER' | translate }}</span>

View File

@ -20,7 +20,7 @@ export class AccountsCardComponent implements OnInit {
constructor(public authService: AuthService, private router: Router, private userService: AuthUserService) { constructor(public authService: AuthService, private router: Router, private userService: AuthUserService) {
this.userService.getMyUserSessions().then(sessions => { this.userService.getMyUserSessions().then(sessions => {
this.users = sessions.toObject().userSessionsList; this.users = sessions.toObject().userSessionsList;
const index = this.users.findIndex(user => user.userName === this.profile.userName); const index = this.users.findIndex(user => user.loginName === this.profile.preferredLoginName);
this.users.splice(index, 1); this.users.splice(index, 1);
this.loadingUsers = false; this.loadingUsers = false;
@ -34,7 +34,7 @@ export class AccountsCardComponent implements OnInit {
} }
public editUserProfile(): void { public editUserProfile(): void {
this.router.navigate(['user/me']); this.router.navigate(['users/me']);
this.close.emit(); this.close.emit();
} }

View File

@ -28,8 +28,5 @@ import { MemberCreateDialogComponent } from './member-create-dialog.component';
SearchRolesAutocompleteModule, SearchRolesAutocompleteModule,
OrgMemberRolesAutocompleteModule, OrgMemberRolesAutocompleteModule,
], ],
entryComponents: [
MemberCreateDialogComponent,
],
}) })
export class MemberCreateDialogModule { } export class MemberCreateDialogModule { }

View File

@ -17,11 +17,11 @@
.meta { .meta {
position: relative; position: relative;
flex: 1 0 300px; flex: 1 0 300px;
padding: 1rem;
@media only screen and (min-width: 1500px) { @media only screen and (min-width: 1500px) {
flex-basis: 400px; flex-basis: 400px;
} }
padding: 1rem;
.meta-content { .meta-content {
max-height: calc(100vh - 60px); max-height: calc(100vh - 60px);
@ -32,6 +32,7 @@
&.hidden { &.hidden {
flex: 0 0 0 !important; flex: 0 0 0 !important;
width: 0; width: 0;
padding: 1px;
.hide { .hide {
transform: rotate(180deg); transform: rotate(180deg);

View File

@ -29,6 +29,8 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
@Input() allowCreate: boolean = false; @Input() allowCreate: boolean = false;
@Input() allowDelete: boolean = false; @Input() allowDelete: boolean = false;
@Input() public disabled: boolean = false;
@Input() userId: string = ''; @Input() userId: string = '';
@Input() projectId: string = ''; @Input() projectId: string = '';

View File

@ -14,8 +14,5 @@ import { WarnDialogComponent } from './warn-dialog.component';
TranslateModule, TranslateModule,
MatButtonModule, MatButtonModule,
], ],
entryComponents: [
WarnDialogComponent,
],
}) })
export class WarnDialogModule { } export class WarnDialogModule { }

View File

@ -32,7 +32,7 @@
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="footer"> <div class="footer">
<a color="accent" mat-button <a color="accent" mat-button
[routerLink]="['/user/me']">{{'HOME.SECURITYANDPRIVACY_BUTTON' | translate}}</a> [routerLink]="['/users/me']">{{'HOME.SECURITYANDPRIVACY_BUTTON' | translate}}</a>
</div> </div>
</div> </div>
@ -75,7 +75,7 @@
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="footer"> <div class="footer">
<a color="accent" mat-button [routerLink]="['/user/me']">{{'HOME.USERS_BUTTON' | translate}}</a> <a color="accent" mat-button [routerLink]="['/users/me']">{{'HOME.USERS_BUTTON' | translate}}</a>
</div> </div>
</div> </div>
</ng-template> </ng-template>

View File

@ -1,10 +1,8 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HttpLoaderFactory } from 'src/app/app.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { HomeRoutingModule } from './home-routing.module'; import { HomeRoutingModule } from './home-routing.module';
@ -20,13 +18,7 @@ import { HomeComponent } from './home.component';
HasRoleModule, HasRoleModule,
HomeRoutingModule, HomeRoutingModule,
MatButtonModule, MatButtonModule,
TranslateModule.forChild({ TranslateModule,
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
], ],
}) })
export class HomeModule { } export class HomeModule { }

View File

@ -1,96 +1,105 @@
<div class="max-width-container"> <div class="max-width-container">
<div class="container"> <div class="container">
<div class="head"> <div class="left">
<h1>{{ 'IAM.MEMBER.TITLE' | translate }}</h1> <a [routerLink]="[ '/iam']" mat-icon-button>
<p class="desc">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</p> <mat-icon class="icon">arrow_back</mat-icon>
</a>
</div> </div>
<div class="right">
<div class="table-header-row"> <div class="head">
<div class="col"> <h1>{{ 'IAM.MEMBER.TITLE' | translate }}</h1>
<ng-container *ngIf="!selection.hasValue()"> <p class="desc">{{ 'IAM.MEMBER.DESCRIPTION' | translate }}</p>
<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> </div>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['iam.member.delete']">
<button color="warn" (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']">
<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="table-header-row">
<div class="spinner-container" *ngIf="dataSource?.loading$ | async"> <div class="col">
<mat-spinner diameter="50"></mat-spinner> <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]="['iam.member.delete']">
<button color="warn" (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']">
<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>
<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 color="primary" (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 color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="firstname"> <div class="table-wrapper">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th> <div class="spinner-container" *ngIf="dataSource?.loading$ | async">
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member"> <mat-spinner diameter="50"></mat-spinner>
{{member.firstName}} </td> </div>
</ng-container> <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 color="primary" (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 color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)">
</mat-checkbox>
</td>
</ng-container>
<ng-container matColumnDef="lastname"> <ng-container matColumnDef="firstname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.FIRSTNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member"> <td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.lastName}} </td> {{member.firstName}} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="username"> <ng-container matColumnDef="lastname">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.LASTNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member"> <td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.userName}} </td> {{member.lastName}} </td>
</ng-container> </ng-container>
<ng-container matColumnDef="email"> <ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.USERNAME' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member"> <td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
{{member.email}} {{member.userName}} </td>
</td> </ng-container>
</ng-container>
<ng-container matColumnDef="roles"> <ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th> <th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.EMAIL' | translate }} </th>
<td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member"> <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"> {{member.email}}
{{ 'ROLES.'+role | translate }}</span> </td>
</td> </ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <ng-container matColumnDef="roles">
<tr class="data-row" mat-row *matRowDef="let row; columns: displayedColumns;"> <th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th>
</tr> <td class="pointer" [routerLink]="['/user', member.userId]" mat-cell *matCellDef="let member">
</table> <span class="role app-label" *ngFor="let role of member.rolesList; index as i">
{{ 'ROLES.'+role | translate }}</span>
</td>
</ng-container>
<mat-paginator class="background-style" #paginator [pageSize]="50" [pageSizeOptions]="[25, 50, 100, 250]"> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
</mat-paginator> <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>
</div> </div>
</div> </div>

View File

@ -1,26 +1,43 @@
.container { .container {
display: flex;
padding-bottom: 3rem; padding-bottom: 3rem;
.head { .left {
width: 100px;
display: flex; display: flex;
align-items: center; padding: 1rem;
border-bottom: 1px solid #ffffff20; justify-content: center;
margin-bottom: 2rem;
flex-wrap: wrap;
a { a {
display: block; margin-top: .2rem;
} }
}
h1 {
font-size: 1.2rem; .right {
} flex: 1;
padding-top: 1rem;
.desc {
width: 100%; .head {
display: block; display: flex;
font-size: .9rem; align-items: center;
color: #8795a1; 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: #8795a1;
}
} }
} }
} }

View File

@ -3,14 +3,13 @@ import { Location } from '@angular/common';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from 'src/app/pages/validators';
import { CreateOrgRequest, CreateUserRequest, Gender, OrgSetUpResponse } from 'src/app/proto/generated/admin_pb'; import { CreateOrgRequest, CreateUserRequest, Gender, OrgSetUpResponse } from 'src/app/proto/generated/admin_pb';
import { PasswordComplexityPolicy } from 'src/app/proto/generated/auth_pb'; import { PasswordComplexityPolicy } from 'src/app/proto/generated/auth_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { AuthUserService } from 'src/app/services/auth-user.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from '../../user-detail/validators';
function passwordConfirmValidator(c: AbstractControl): any { function passwordConfirmValidator(c: AbstractControl): any {
if (!c.parent || !c) { if (!c.parent || !c) {
return; return;
@ -64,7 +63,7 @@ export class OrgCreateComponent {
private fb: FormBuilder, private fb: FormBuilder,
private authUserService: AuthUserService, private authUserService: AuthUserService,
) { ) {
const validators: Validators[] = [Validators.required]; const validators: Validators[] = [];
this.orgForm = this.fb.group({ this.orgForm = this.fb.group({
name: ['', [Validators.required]], name: ['', [Validators.required]],

View File

@ -1,5 +1,4 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -7,8 +6,7 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HttpLoaderFactory } from 'src/app/app.module';
import { PipesModule } from 'src/app/pipes/pipes.module'; import { PipesModule } from 'src/app/pipes/pipes.module';
import { OrgCreateRoutingModule } from './org-create-routing.module'; import { OrgCreateRoutingModule } from './org-create-routing.module';
@ -27,13 +25,7 @@ import { OrgCreateComponent } from './org-create.component';
MatIconModule, MatIconModule,
MatSelectModule, MatSelectModule,
PipesModule, PipesModule,
TranslateModule.forChild({ TranslateModule,
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
], ],
}) })
export class OrgCreateModule { } export class OrgCreateModule { }

View File

@ -0,0 +1,18 @@
<span class="title" mat-dialog-title>{{'ORG.DOMAINS.ADD.TITLE' | translate}}</span>
<div mat-dialog-content>
<p class="desc"> {{'ORG.DOMAINS.ADD.DESCRIPTION' | translate}}</p>
<mat-form-field label="Access Code" required="true">
<mat-label>Code</mat-label>
<input matInput [(ngModel)]="newdomain" />
</mat-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-button (click)="closeDialog()">
{{'ACTIONS.CANCEL' | translate}}
</button>
<button color="warn" mat-raised-button class="ok-button" [disabled]="!newdomain" (click)="closeDialogWithSuccess()">
{{'ACTIONS.ADD' | translate}}
</button>
</div>

View File

@ -0,0 +1,26 @@
.title {
font-size: 1.2rem;
margin-top: 0;
}
.desc {
color: #8795a1;
font-size: .9rem;
}
mat-form-field {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AddDomainDialogComponent } from './add-domain-dialog.component';
describe('WarnDialogComponent', () => {
let component: AddDomainDialogComponent;
let fixture: ComponentFixture<AddDomainDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AddDomainDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AddDomainDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,26 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-add-domain-dialog',
templateUrl: './add-domain-dialog.component.html',
styleUrls: ['./add-domain-dialog.component.scss'],
})
export class AddDomainDialogComponent implements OnInit {
public newdomain: string = '';
constructor(
public dialogRef: MatDialogRef<AddDomainDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
ngOnInit(): void {
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close(this.newdomain);
}
}

View File

@ -0,0 +1,22 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { TranslateModule } from '@ngx-translate/core';
import { AddDomainDialogComponent } from './add-domain-dialog.component';
@NgModule({
declarations: [AddDomainDialogComponent],
imports: [
CommonModule,
TranslateModule,
MatButtonModule,
MatFormFieldModule,
MatInputModule,
FormsModule,
],
})
export class AddDomainDialogModule { }

View File

@ -15,16 +15,8 @@
</div> </div>
<p class="new-desc">{{'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate}}</p> <p class="new-desc">{{'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate}}</p>
<div class="new-row"> <button matTooltip="Add domain" mat-raised-button color="primary"
<mat-form-field appearance="outline"> (click)="addNewDomain()">{{'ORG.DOMAINS.NEW' | translate}} </button>
<mat-label>new domain</mat-label>
<input matInput [(ngModel)]="newDomain" />
</mat-form-field>
<button matTooltip="Add domain" mat-icon-button color="primary" (click)="saveNewOrgDomain()">
<mat-icon>check</mat-icon>
</button>
</div>
</app-card> </app-card>
<ng-template appHasRole [appHasRole]="['policy.read']"> <ng-template appHasRole [appHasRole]="['policy.read']">

View File

@ -81,16 +81,6 @@ h1 {
color: #818a8a; color: #818a8a;
} }
.new-row {
display: flex;
flex-wrap: wrap;
align-items: center;
mat-form-field {
flex: 1;
}
}
.side { .side {
.details { .details {
margin-bottom: 1rem; margin-bottom: 1rem;

View File

@ -11,6 +11,8 @@ import { Org, OrgDomainView, OrgMember, OrgMemberSearchResponse, OrgState } from
import { OrgService } from 'src/app/services/org.service'; import { OrgService } from 'src/app/services/org.service';
import { ToastService } from 'src/app/services/toast.service'; import { ToastService } from 'src/app/services/toast.service';
import { AddDomainDialogComponent } from './add-domain-dialog/add-domain-dialog.component';
@Component({ @Component({
selector: 'app-org-detail', selector: 'app-org-detail',
@ -31,7 +33,6 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
public domains: OrgDomainView.AsObject[] = []; public domains: OrgDomainView.AsObject[] = [];
public primaryDomain: string = ''; public primaryDomain: string = '';
public newDomain: string = '';
constructor( constructor(
private dialog: MatDialog, private dialog: MatDialog,
@ -64,23 +65,32 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
public changeState(event: MatButtonToggleChange | any): void { public changeState(event: MatButtonToggleChange | any): void {
if (event.value === OrgState.ORGSTATE_ACTIVE) { if (event.value === OrgState.ORGSTATE_ACTIVE) {
this.orgService.ReactivateMyOrg().then(() => { this.orgService.ReactivateMyOrg().then(() => {
this.toast.showInfo('Reactivated Org'); this.toast.showInfo('ORG.TOAST.REACTIVATED', true);
}).catch((error) => { }).catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} else if (event.value === OrgState.ORGSTATE_INACTIVE) { } else if (event.value === OrgState.ORGSTATE_INACTIVE) {
this.orgService.DeactivateMyOrg().then(() => { this.orgService.DeactivateMyOrg().then(() => {
this.toast.showInfo('Deactivated Org'); this.toast.showInfo('ORG.TOAST.DEACTIVATED', true);
}).catch((error) => { }).catch((error) => {
this.toast.showError(error); this.toast.showError(error);
}); });
} }
} }
public saveNewOrgDomain(): void { public addNewDomain(): void {
this.orgService.AddMyOrgDomain(this.newDomain).then(domain => { const dialogRef = this.dialog.open(AddDomainDialogComponent, {
this.domains.push(domain.toObject()); data: {},
this.newDomain = ''; width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.orgService.AddMyOrgDomain(resp).then(domain => {
this.domains.push(domain.toObject());
this.toast.showInfo('ORG.TOAST.DOMAINADDED', true);
});
}
}); });
} }
@ -98,7 +108,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
dialogRef.afterClosed().subscribe(resp => { dialogRef.afterClosed().subscribe(resp => {
if (resp) { if (resp) {
this.orgService.RemoveMyOrgDomain(domain).then(() => { this.orgService.RemoveMyOrgDomain(domain).then(() => {
this.toast.showInfo('Removed'); this.toast.showInfo('ORG.TOAST.DOMAINREMOVED', true);
const index = this.domains.findIndex(d => d.domain === domain); const index = this.domains.findIndex(d => d.domain === domain);
if (index > -1) { if (index > -1) {
this.domains.splice(index, 1); this.domains.splice(index, 1);

View File

@ -11,9 +11,11 @@
</div> </div>
<div class="container"> <div class="container">
<p class="n-items" *ngIf="!loading && selection.selected.length > 0">{{'PROJECT.PAGES.PINNED' | translate}}</p>
<div matTooltip="{{'ORG.PAGES.SELECTORGTOOLTIP' | translate}}" class="item card" <div matTooltip="{{'ORG.PAGES.SELECTORGTOOLTIP' | translate}}" class="item card"
*ngFor="let org of orgList; index as i" (click)="selectOrg(org, $event)" *ngFor="let org of selection.selected; index as i" (click)="selectOrg(org, $event)"
[ngClass]="{ selected: selection.isSelected(org),active: activeOrg?.id === org?.id }"> [ngClass]="{ active: activeOrg?.id === org?.id }">
<div class="text-part"> <div class="text-part">
<span class="description">{{org.id}}</span> <span class="description">{{org.id}}</span>
@ -23,17 +25,31 @@
<div class="icons"> <div class="icons">
</div> </div>
</div> </div>
<button [matMenuTriggerFor]="editMenu" class="edit-button" mat-icon-button> <button [ngClass]="{ selected: selection.isSelected(org)}" (click)="selection.toggle(org)"
<mat-icon>more_vert</mat-icon> class="edit-button" mat-icon-button>
<mat-icon>push_pin_outline</mat-icon>
</button> </button>
</div>
</div>
<div class="container">
<p class="n-items" *ngIf="!loading && notPinned.length > 0">{{'PROJECT.PAGES.ALL' | translate}}</p>
<mat-menu #editMenu="matMenu"> <div matTooltip="{{'ORG.PAGES.SELECTORGTOOLTIP' | translate}}" class="item card"
<ng-template matMenuContent> *ngFor="let org of notPinned; index as i" (click)="selectOrg(org, $event)"
<button (click)="routeToOrg(org)" mat-menu-item> [ngClass]="{ active: activeOrg?.id === org?.id }">
{{'ACTIONS.VIEW' | translate}} <div class="text-part">
</button> <span class="description">{{org.id}}</span>
</ng-template>
</mat-menu> <span class="name" *ngIf="org.name">{{ org.name }}</span>
<span class="name" *ngIf="!org.name">No Name</span>
<span class="fill-space"></span>
<div class="icons">
</div>
</div>
<button [ngClass]="{ selected: selection.isSelected(org)}" (click)="selection.toggle(org)"
class="edit-button" mat-icon-button>
<mat-icon>push_pin_outline</mat-icon>
</button>
</div> </div>
<ng-template appHasRole [appHasRole]="['iam.write']"> <ng-template appHasRole [appHasRole]="['iam.write']">

View File

@ -35,9 +35,10 @@ h1 {
position: relative; position: relative;
z-index: 100; z-index: 100;
margin: 1rem; margin: 1rem;
flex-basis: 250px; flex-basis: 230px;
display: flex; display: flex;
text-decoration: none; text-decoration: none;
overflow: hidden;
cursor: pointer; cursor: pointer;
padding-top: 0; padding-top: 0;
padding-right: 0; padding-right: 0;
@ -45,14 +46,14 @@ h1 {
padding-left: 1rem; padding-left: 1rem;
border-radius: 0.5rem; border-radius: 0.5rem;
box-sizing: border-box; box-sizing: border-box;
min-height: 166px; min-height: 130px;
* { * {
box-sizing: border-box; box-sizing: border-box;
} }
&.active { &.active {
border: 2px solid #db4c69; border: 2px solid #38649d;
} }
.selection-icon { .selection-icon {
@ -151,16 +152,28 @@ h1 {
} }
.edit-button { .edit-button {
opacity: 0;
user-select: none;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
right: 0; right: 0;
margin: 0; margin: 0;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
color: #8795a1;
&:hover {
opacity: 1;
color: white;
}
&.selected {
opacity: 1;
}
} }
&:hover { &:hover {
.selection-icon { .edit-button {
opacity: 1; opacity: 1;
} }
@ -180,7 +193,7 @@ h1 {
} }
} }
.selection-icon { .edit-button {
opacity: 1; opacity: 1;
} }
} }
@ -188,13 +201,13 @@ h1 {
.add-org-button { .add-org-button {
z-index: 100; z-index: 100;
flex-basis: 250px; flex-basis: 230px;
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
min-height: 166px; min-height: 130px;
border-radius: 0.5rem; border-radius: 0.5rem;
margin: 1rem; margin: 1rem;
box-sizing: border-box; box-sizing: border-box;
@ -220,4 +233,11 @@ h1 {
} }
} }
} }
} }
.n-items {
padding: 0 1rem;
font-size: .8rem;
color: #8795a1;
flex-basis: 100%;
}

View File

@ -1,6 +1,8 @@
import { SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Observable, of } from 'rxjs';
import { switchMap, take } from 'rxjs/operators';
import { Org } from 'src/app/proto/generated/auth_pb'; import { Org } from 'src/app/proto/generated/auth_pb';
import { AuthUserService } from 'src/app/services/auth-user.service'; import { AuthUserService } from 'src/app/services/auth-user.service';
import { AuthService } from 'src/app/services/auth.service'; import { AuthService } from 'src/app/services/auth.service';
@ -17,23 +19,87 @@ export class OrgGridComponent {
public selection: SelectionModel<Org.AsObject> = new SelectionModel<Org.AsObject>(true, []); public selection: SelectionModel<Org.AsObject> = new SelectionModel<Org.AsObject>(true, []);
public selectedIndex: number = -1; public selectedIndex: number = -1;
public loading: boolean = false;
public notPinned: Array<Org.AsObject> = [];
constructor( constructor(
public authService: AuthService, public authService: AuthService,
private userService: AuthUserService, private userService: AuthUserService,
private toast: ToastService, private toast: ToastService,
private router: Router, private router: Router,
) { ) {
this.loading = true;
this.getData(10, 0); this.getData(10, 0);
this.authService.GetActiveOrg().then(org => this.activeOrg = org); this.authService.GetActiveOrg().then(org => this.activeOrg = org);
this.selection.changed.subscribe(selection => {
this.setPrefixedItem('pinned-orgs', JSON.stringify(
this.selection.selected.map(item => item.id),
)).pipe(take(1)).subscribe(() => {
selection.added.forEach(element => {
const index = this.notPinned.findIndex(item => item.id === element.id);
this.notPinned.splice(index, 1);
});
this.notPinned.push(...selection.removed);
});
});
}
public reorganizeItems(): void {
this.getPrefixedItem('pinned-orgs').pipe(take(1)).subscribe(storageEntry => {
if (storageEntry) {
const array: string[] = JSON.parse(storageEntry);
const toSelect: Org.AsObject[] = this.orgList.filter((item, index) => {
if (array.includes(item.id)) {
// this.notPinned.splice(index, 1);
return true;
}
});
this.selection.select(...toSelect);
const toNotPinned: Org.AsObject[] = this.orgList.filter((item, index) => {
if (!array.includes(item.id)) {
return true;
}
});
this.notPinned = toNotPinned;
}
});
}
private getPrefixedItem(key: string): Observable<string | null> {
return this.authService.user.pipe(
take(1),
switchMap(user => {
return of(localStorage.getItem(`${user.id}:${key}`));
}),
);
}
private setPrefixedItem(key: string, value: any): Observable<void> {
return this.authService.user.pipe(
take(1),
switchMap(user => {
return of(localStorage.setItem(`${user.id}:${key}`, value));
}),
);
} }
private getData(limit: number, offset: number): void { private getData(limit: number, offset: number): void {
this.userService.SearchMyProjectOrgs(limit, offset).then(res => { this.userService.SearchMyProjectOrgs(limit, offset).then(res => {
this.orgList = res.toObject().resultList; this.orgList = res.toObject().resultList;
console.log(this.orgList);
this.notPinned = Object.assign([], this.orgList);
this.reorganizeItems();
this.loading = false;
}).catch(error => { }).catch(error => {
console.error(error); console.error(error);
this.toast.showError(error); this.toast.showError(error);
this.loading = false;
}); });
} }

View File

@ -1,5 +1,4 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -11,8 +10,7 @@ import { MatInputModule } from '@angular/material/input';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HttpLoaderFactory } from 'src/app/app.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module'; import { CardModule } from 'src/app/modules/card/card.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module'; import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
@ -20,6 +18,7 @@ import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module
import { ChangesModule } from '../../modules/changes/changes.module'; import { ChangesModule } from '../../modules/changes/changes.module';
import { OrgContributorsModule } from './org-contributors/org-contributors.module'; import { OrgContributorsModule } from './org-contributors/org-contributors.module';
import { AddDomainDialogModule } from './org-detail/add-domain-dialog/add-domain-dialog.module';
import { OrgDetailComponent } from './org-detail/org-detail.component'; import { OrgDetailComponent } from './org-detail/org-detail.component';
import { OrgGridComponent } from './org-grid/org-grid.component'; import { OrgGridComponent } from './org-grid/org-grid.component';
import { OrgsRoutingModule } from './orgs-routing.module'; import { OrgsRoutingModule } from './orgs-routing.module';
@ -48,13 +47,8 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
WarnDialogModule, WarnDialogModule,
MatMenuModule, MatMenuModule,
ChangesModule, ChangesModule,
TranslateModule.forChild({ AddDomainDialogModule,
loader: { TranslateModule,
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
], ],
exports: [], exports: [],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],

View File

@ -10,6 +10,14 @@
<h1 *ngIf="(titleSub | async) || '' as titletrans">{{ titletrans | translate }}</h1> <h1 *ngIf="(titleSub | async) || '' as titletrans">{{ titletrans | translate }}</h1>
<p class="desc" *ngIf="(descSub | async) || '' as desctrans">{{ desctrans | translate }}</p> <p class="desc" *ngIf="(descSub | async) || '' as desctrans">{{ desctrans | translate }}</p>
<span class="fill-space"></span>
<ng-template appHasRole [appHasRole]="['iam.policy.write']">
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn" (click)="deletePolicy()"
mat-stroked-button>
{{'ORG.POLICY.DELETE' | translate}}
</button>
</ng-template>
</div> </div>
<div> <div>

View File

@ -29,6 +29,10 @@
h1 { h1 {
font-size: 1.2rem; font-size: 1.2rem;
} }
.fill-space {
flex: 1;
}
.desc { .desc {
width: 100%; width: 100%;
@ -36,6 +40,11 @@
font-size: .9rem; font-size: .9rem;
color: #8795a1; color: #8795a1;
} }
button {
border-radius: .5rem;
margin-bottom: .5rem;
}
} }
.content { .content {

View File

@ -3,12 +3,6 @@ import { FormGroup } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, Subscription } from 'rxjs'; import { BehaviorSubject, Subscription } from 'rxjs';
import { switchMap } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import {
OrgIamPolicy,
PasswordAgePolicy,
PasswordComplexityPolicy,
PasswordLockoutPolicy,
} from 'src/app/proto/generated/management_pb';
import { AdminService } from 'src/app/services/admin.service'; import { AdminService } from 'src/app/services/admin.service';
import { OrgService } from 'src/app/services/org.service'; import { OrgService } from 'src/app/services/org.service';
import { StorageService } from 'src/app/services/storage.service'; import { StorageService } from 'src/app/services/storage.service';
@ -37,11 +31,7 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
componentAction: PolicyComponentAction = PolicyComponentAction.CREATE; componentAction: PolicyComponentAction = PolicyComponentAction.CREATE;
policyData!: PasswordLockoutPolicy.AsObject | public policyType: PolicyComponentType = PolicyComponentType.COMPLEXITY;
PasswordAgePolicy.AsObject |
PasswordComplexityPolicy.AsObject |
OrgIamPolicy.AsObject;
policyType: PolicyComponentType = PolicyComponentType.COMPLEXITY;
public PolicyComponentType: any = PolicyComponentType; public PolicyComponentType: any = PolicyComponentType;
public PolicyComponentAction: any = PolicyComponentAction; public PolicyComponentAction: any = PolicyComponentAction;
@ -159,6 +149,32 @@ export class PasswordPolicyComponent implements OnInit, OnDestroy {
} }
} }
public deletePolicy(): void {
switch (this.policyType) {
case PolicyComponentType.LOCKOUT:
this.orgService.DeletePasswordLockoutPolicy(this.lockoutData.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
break;
case PolicyComponentType.AGE:
this.orgService.DeletePasswordAgePolicy(this.ageData.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
break;
case PolicyComponentType.COMPLEXITY:
this.orgService.DeletePasswordComplexityPolicy(this.complexityData.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
public incrementLength(): void { public incrementLength(): void {
if (this.complexityData?.minLength !== undefined) { if (this.complexityData?.minLength !== undefined) {
this.complexityData.minLength++; this.complexityData.minLength++;

View File

@ -1,5 +1,4 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -7,8 +6,9 @@ import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { MatTooltipModule } from '@angular/material/tooltip';
import { HttpLoaderFactory } from 'src/app/app.module'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { PasswordPolicyRoutingModule } from './password-policy-routing.module'; import { PasswordPolicyRoutingModule } from './password-policy-routing.module';
import { PasswordPolicyComponent } from './password-policy.component'; import { PasswordPolicyComponent } from './password-policy.component';
@ -24,13 +24,9 @@ import { PasswordPolicyComponent } from './password-policy.component';
MatButtonModule, MatButtonModule,
MatSlideToggleModule, MatSlideToggleModule,
MatIconModule, MatIconModule,
TranslateModule.forChild({ HasRoleModule,
loader: { MatTooltipModule,
provide: TranslateLoader, TranslateModule,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
], ],
}) })
export class PasswordPolicyModule { } export class PasswordPolicyModule { }

View File

@ -48,10 +48,6 @@
<button mat-icon-button disabled> <button mat-icon-button disabled>
<i *ngIf="complexityPolicy" class="icon las la-check-circle"></i> <i *ngIf="complexityPolicy" class="icon las la-check-circle"></i>
</button> </button>
<button matTooltip="{{'ORG.POLICY.DELETE' | translate}}" color="warn"
(click)="deletePolicy(PolicyComponentType.COMPLEXITY)" mat-icon-button>
<i class="las la-trash"></i>
</button>
</div> </div>
<p *ngIf="complexityPolicy?.description; else showDescComplexity" class="desc"> <p *ngIf="complexityPolicy?.description; else showDescComplexity" class="desc">
@ -107,12 +103,6 @@
<button mat-icon-button disabled> <button mat-icon-button disabled>
<i *ngIf="iamPolicy" class="icon las la-check-circle"></i> <i *ngIf="iamPolicy" class="icon las la-check-circle"></i>
</button> </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> </div>
<p *ngIf="iamPolicy?.description; else showDescIAM" class="desc"> <p *ngIf="iamPolicy?.description; else showDescIAM" class="desc">

View File

@ -47,30 +47,4 @@ export class PolicyGridComponent implements OnInit {
this.orgService.GetMyOrgIamPolicy().then(data => this.iamPolicy = data.toObject()) this.orgService.GetMyOrgIamPolicy().then(data => this.iamPolicy = data.toObject())
.catch(error => { }); .catch(error => { });
} }
public deletePolicy(type: PolicyComponentType): void {
switch (type) {
case PolicyComponentType.LOCKOUT:
this.orgService.DeletePasswordLockoutPolicy(this.lockoutPolicy.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
break;
case PolicyComponentType.AGE:
this.orgService.DeletePasswordAgePolicy(this.agePolicy.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
break;
case PolicyComponentType.COMPLEXITY:
this.orgService.DeletePasswordLockoutPolicy(this.lockoutPolicy.id).then(() => {
this.toast.showInfo('Successfully deleted');
}).catch(error => {
this.toast.showError(error);
});
break;
}
}
} }

View File

@ -1,19 +0,0 @@
<div class="max-width-container">
<div class="container">
<div class="left">
<a *ngIf="projectid" [routerLink]="[ '/projects', projectid]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
</div>
<div class="right">
<div class="head">
<h1>{{ 'PROJECT.GRANT.DETAIL.TITLE' | translate }}</h1>
<p class="desc">{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}</p>
</div>
<app-project-grant-members *ngIf="this.projectid && this.grantid" [disabled]="isZitadel"
[projectId]="projectid" [grantId]="grantid" [type]="projectType">
</app-project-grant-members>
</div>
</div>
</div>

View File

@ -1,43 +0,0 @@
.container {
display: flex;
padding-bottom: 3rem;
.left {
width: 100px;
display: flex;
padding: 1rem;
justify-content: center;
a {
margin-top: .2rem;
}
}
.right {
flex: 1;
padding-top: 1rem;
.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: #8795a1;
}
}
}
}

View File

@ -1,32 +0,0 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ProjectType } from 'src/app/proto/generated/management_pb';
import { OrgService } from 'src/app/services/org.service';
@Component({
selector: 'app-project-grant-detail',
templateUrl: './project-grant-detail.component.html',
styleUrls: ['./project-grant-detail.component.scss'],
})
export class ProjectGrantDetailComponent {
public projectid: string = '';
public grantid: string = '';
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public disabled: boolean = false;
public isZitadel: boolean = false;
constructor(
private orgService: OrgService,
private route: ActivatedRoute) {
this.route.params.subscribe(params => {
this.projectid = params.projectid;
this.grantid = params.grantid;
this.orgService.GetIam().then(iam => {
this.isZitadel = iam.toObject().iamProjectId === this.projectid;
});
});
}
}

View File

@ -1,6 +1,6 @@
<div class="max-width-container"> <div class="max-width-container">
<div class="head"> <div class="head">
<a (click)="navigateBack()" mat-icon-button> <a [routerLink]="['/projects', projectId]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon> <mat-icon class="icon">arrow_back</mat-icon>
</a> </a>
<h1>{{ 'APP.PAGES.TITLE' | translate }} {{app?.name}}</h1> <h1>{{ 'APP.PAGES.TITLE' | translate }} {{app?.name}}</h1>
@ -16,11 +16,9 @@
<div class="content"> <div class="content">
<mat-button-toggle-group formControlName="state" class="toggle" (change)="changeState($event)"> <mat-button-toggle-group formControlName="state" class="toggle" (change)="changeState($event)">
<mat-button-toggle [value]="AppState.APPSTATE_INACTIVE" matTooltip="Deactivate Org"> <mat-button-toggle [value]="AppState.APPSTATE_INACTIVE" matTooltip="Deactivate Org">
<!-- <i class="las la-toggle-off"></i> -->
{{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_INACTIVE | translate}} {{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_INACTIVE | translate}}
</mat-button-toggle> </mat-button-toggle>
<mat-button-toggle [value]="AppState.APPSTATE_ACTIVE" matTooltip="Activate Org"> <mat-button-toggle [value]="AppState.APPSTATE_ACTIVE" matTooltip="Activate Org">
<!-- <i class="las la-toggle-on"></i> -->
{{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_ACTIVE | translate}} {{'APP.PAGES.DETAIL.STATE.'+AppState.APPSTATE_ACTIVE | translate}}
</mat-button-toggle> </mat-button-toggle>
</mat-button-toggle-group> </mat-button-toggle-group>

View File

@ -1,5 +1,4 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -15,8 +14,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HttpLoaderFactory } from 'src/app/app.module';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module'; import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module'; import { CardModule } from 'src/app/modules/card/card.module';
@ -52,16 +50,7 @@ import { AppsRoutingModule } from './apps-routing.module';
MatCheckboxModule, MatCheckboxModule,
CardModule, CardModule,
MatTooltipModule, MatTooltipModule,
TranslateModule.forChild({ TranslateModule,
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
],
entryComponents: [
AppSecretDialogComponent,
], ],
exports: [TranslateModule], exports: [TranslateModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],

View File

@ -26,7 +26,8 @@ const routes: Routes = [
data: { data: {
type: ProjectType.PROJECTTYPE_GRANTED, type: ProjectType.PROJECTTYPE_GRANTED,
}, },
loadChildren: () => import('../../modules/project-members/project-members.module').then(m => m.ProjectMembersModule), loadChildren: () => import('src/app/modules/project-members/project-members.module')
.then(m => m.ProjectMembersModule),
}, },
{ {
path: ':id/grant/:grantId', path: ':id/grant/:grantId',

View File

@ -1,5 +1,4 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -15,17 +14,16 @@ import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; 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 { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { ProjectContributorsModule } from 'src/app/modules/project-contributors/project-contributors.module';
import { ProjectRolesModule } from 'src/app/modules/project-roles/project-roles.module';
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module'; import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
import { PipesModule } from 'src/app/pipes/pipes.module';
import { HttpLoaderFactory } from '../../app.module';
import { HasRoleModule } from '../../directives/has-role/has-role.module';
import { CardModule } from '../../modules/card/card.module';
import { ChangesModule } from '../../modules/changes/changes.module';
import { MetaLayoutModule } from '../../modules/meta-layout/meta-layout.module';
import { ProjectContributorsModule } from '../../modules/project-contributors/project-contributors.module';
import { ProjectRolesModule } from '../../modules/project-roles/project-roles.module';
import { PipesModule } from '../../pipes/pipes.module';
import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component'; import { GrantedProjectDetailComponent } from './granted-project-detail/granted-project-detail.component';
import { GrantedProjectGridComponent } from './granted-project-grid/granted-project-grid.component'; import { GrantedProjectGridComponent } from './granted-project-grid/granted-project-grid.component';
import { GrantedProjectListComponent } from './granted-project-list/granted-project-list.component'; import { GrantedProjectListComponent } from './granted-project-list/granted-project-list.component';
@ -66,13 +64,7 @@ import { GrantedProjectsComponent } from './granted-projects.component';
MatTooltipModule, MatTooltipModule,
MatSortModule, MatSortModule,
PipesModule, PipesModule,
TranslateModule.forChild({ TranslateModule,
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}) })

View File

@ -82,8 +82,9 @@
<app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}" <app-card *ngIf="project?.projectId" title="{{ 'GRANTS.PROJECT.TITLE' | translate }}"
description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}"> description="{{'GRANTS.PROJECT.DESCRIPTION' | translate }}">
<app-user-grants [context]="userGrantContext" [projectId]="projectId" <app-user-grants [context]="userGrantContext" [projectId]="projectId"
[allowCreate]="['user.grant.write'] | hasRole" [disabled]="project?.state !== ProjectState.PROJECTSTATE_ACTIVE"
[allowDelete]="['user.grant.delete'] | hasRole"> [allowCreate]="project?.state == ProjectState.PROJECTSTATE_ACTIVE && (['user.grant.write'] | hasRole)"
[allowDelete]="project?.state == ProjectState.PROJECTSTATE_ACTIVE && (['user.grant.delete'] | hasRole)">
</app-user-grants> </app-user-grants>
</app-card> </app-card>
</ng-template> </ng-template>

View File

@ -1,12 +1,14 @@
import { SelectionModel } from '@angular/cdk/collections'; import { SelectionModel } from '@angular/cdk/collections';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, OnDestroy, OnInit } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { ActivatedRoute, Params } from '@angular/router'; import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource'; import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { import {
Application, Application,
ApplicationSearchResponse, ApplicationSearchResponse,
@ -69,6 +71,7 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
private projectService: ProjectService, private projectService: ProjectService,
private _location: Location, private _location: Location,
private orgService: OrgService, private orgService: OrgService,
private dialog: MatDialog,
) { ) {
} }
@ -99,16 +102,45 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
public changeState(newState: ProjectState): void { public changeState(newState: ProjectState): void {
if (newState === ProjectState.PROJECTSTATE_ACTIVE) { if (newState === ProjectState.PROJECTSTATE_ACTIVE) {
this.projectService.ReactivateProject(this.projectId).then(() => { const dialogRef = this.dialog.open(WarnDialogComponent, {
this.toast.showInfo('Reactivated Project'); data: {
}).catch(error => { confirmKey: 'ACTIONS.REACTIVATE',
this.toast.showError(error); cancelKey: 'ACTIONS.CANCEL',
titleKey: 'PROJECT.PAGES.DIALOG.REACTIVATE.TITLE',
descriptionKey: 'PROJECT.PAGES.DIALOG.REACTIVATE.DESCRIPTION',
},
width: '400px',
}); });
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.projectService.ReactivateProject(this.projectId).then(() => {
this.toast.showInfo('Reactivated Project');
this.project.state = ProjectState.PROJECTSTATE_ACTIVE;
}).catch(error => {
this.toast.showError(error);
});
}
});
} else if (newState === ProjectState.PROJECTSTATE_INACTIVE) { } else if (newState === ProjectState.PROJECTSTATE_INACTIVE) {
this.projectService.DeactivateProject(this.projectId).then(() => { const dialogRef = this.dialog.open(WarnDialogComponent, {
this.toast.showInfo('Deactivated Project'); data: {
}).catch(error => { confirmKey: 'ACTIONS.DEACTIVATE',
this.toast.showError(error); cancelKey: 'ACTIONS.CANCEL',
titleKey: 'PROJECT.PAGES.DIALOG.DEACTIVATE.TITLE',
descriptionKey: 'PROJECT.PAGES.DIALOG.DEACTIVATE.DESCRIPTION',
},
width: '400px',
});
dialogRef.afterClosed().subscribe(resp => {
if (resp) {
this.projectService.DeactivateProject(this.projectId).then(() => {
this.toast.showInfo('Deactivated Project');
this.project.state = ProjectState.PROJECTSTATE_INACTIVE;
}).catch(error => {
this.toast.showError(error);
});
}
}); });
} }
} }

View File

@ -38,7 +38,6 @@ export class OwnedProjectGridComponent implements OnChanges {
@Input() loading: boolean = false; @Input() loading: boolean = false;
public selection: SelectionModel<ProjectView.AsObject> = new SelectionModel<ProjectView.AsObject>(true, []); public selection: SelectionModel<ProjectView.AsObject> = new SelectionModel<ProjectView.AsObject>(true, []);
public selectedIndex: number = -1;
public showNewProject: boolean = false; public showNewProject: boolean = false;
public ProjectState: any = ProjectState; public ProjectState: any = ProjectState;

View File

@ -31,12 +31,13 @@ const routes: Routes = [
data: { data: {
type: ProjectType.PROJECTTYPE_OWNED, type: ProjectType.PROJECTTYPE_OWNED,
}, },
loadChildren: () => import('../../modules/project-members/project-members.module').then(m => m.ProjectMembersModule), loadChildren: () => import('src/app/modules/project-members/project-members.module')
.then(m => m.ProjectMembersModule),
}, },
{ {
path: ':projectid/apps', path: ':projectid/apps',
data: { animation: 'AddPage' }, data: { animation: 'AddPage' },
loadChildren: () => import('../apps/apps.module').then(m => m.AppsModule), loadChildren: () => import('src/app/pages/projects/apps/apps.module').then(m => m.AppsModule),
}, },
{ {
path: ':projectid/roles/create', path: ':projectid/roles/create',

View File

@ -1,5 +1,4 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core'; import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -16,19 +15,18 @@ import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { AvatarModule } from 'src/app/modules/avatar/avatar.module'; import { AvatarModule } from 'src/app/modules/avatar/avatar.module';
import { CardModule } from 'src/app/modules/card/card.module';
import { ChangesModule } from 'src/app/modules/changes/changes.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { ProjectContributorsModule } from 'src/app/modules/project-contributors/project-contributors.module';
import { ProjectRolesModule } from 'src/app/modules/project-roles/project-roles.module'; import { ProjectRolesModule } from 'src/app/modules/project-roles/project-roles.module';
import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module'; import { UserGrantsModule } from 'src/app/modules/user-grants/user-grants.module';
import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module';
import { PipesModule } from 'src/app/pipes/pipes.module';
import { HttpLoaderFactory } from '../../app.module';
import { HasRoleModule } from '../../directives/has-role/has-role.module';
import { CardModule } from '../../modules/card/card.module';
import { ChangesModule } from '../../modules/changes/changes.module';
import { MetaLayoutModule } from '../../modules/meta-layout/meta-layout.module';
import { ProjectContributorsModule } from '../../modules/project-contributors/project-contributors.module';
import { PipesModule } from '../../pipes/pipes.module';
import { UserListModule } from '../user-list/user-list.module';
import { OwnedProjectDetailComponent } from './owned-project-detail/owned-project-detail.component'; import { OwnedProjectDetailComponent } from './owned-project-detail/owned-project-detail.component';
import { OwnedProjectGridComponent } from './owned-project-grid/owned-project-grid.component'; import { OwnedProjectGridComponent } from './owned-project-grid/owned-project-grid.component';
import { OwnedProjectListComponent } from './owned-project-list/owned-project-list.component'; import { OwnedProjectListComponent } from './owned-project-list/owned-project-list.component';
@ -64,10 +62,10 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
ChangesModule, ChangesModule,
UserListModule,
MatChipsModule, MatChipsModule,
MatIconModule, MatIconModule,
MatButtonModule, MatButtonModule,
WarnDialogModule,
MatProgressSpinnerModule, MatProgressSpinnerModule,
MetaLayoutModule, MetaLayoutModule,
MatProgressBarModule, MatProgressBarModule,
@ -79,13 +77,7 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
MatTooltipModule, MatTooltipModule,
MatSortModule, MatSortModule,
PipesModule, PipesModule,
TranslateModule.forChild({ TranslateModule,
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient],
},
}),
], ],
schemas: [NO_ERRORS_SCHEMA], schemas: [NO_ERRORS_SCHEMA],
}) })

View File

@ -0,0 +1,58 @@
<div class="max-width-container">
<div class="container">
<div class="left">
<a *ngIf="projectid" [routerLink]="[ '/projects', projectid]" mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
</div>
<div class="right">
<div class="head">
<h1>{{ 'PROJECT.GRANT.DETAIL.TITLE' | translate }}</h1>
<p class="desc">{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}</p>
</div>
<div class="master-row">
<div class="left-col">
<div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.PROJECTNAME' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.projectName}}</span>
</div>
<div class="row">
<span class="first">{{'PROJECT.GRANT.DETAIL.RESOURCEOWNER' | translate}}</span>
<span class="fill-space"></span>
<span>{{grant?.resourceOwnerName}}</span>
</div>
</div>
<span class="fill-space"></span>
<div>
<button mat-stroked-button color="accent"
*ngIf="grant?.state === ProjectGrantState.PROJECTGRANTSTATE_ACTIVE" class="state-button"
(click)="changeState(ProjectGrantState.PROJECTGRANTSTATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' | translate}}</button>
<button mat-stroked-button color="accent"
*ngIf="grant?.state === ProjectGrantState.PROJECTGRANTSTATE_INACTIVE" class="state-button"
(click)="changeState(ProjectGrantState.PROJECTGRANTSTATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' | translate}}</button>
</div>
</div>
<mat-form-field class="form-field" appearance="outline" *ngIf="grant && grant.roleKeysList">
<mat-label>{{ 'PROJECT.GRANT.ROLENAMESLIST' | translate }}</mat-label>
<mat-select [(ngModel)]="grant.roleKeysList" multiple (selectionChange)="updateRoles($event)">
<mat-option *ngFor="let role of memberRoleOptions" [value]="role.key">
{{role.key}}
</mat-option>
</mat-select>
</mat-form-field>
<div class="divider"></div>
<h1>{{ 'PROJECT.GRANT.DETAIL.MEMBERTITLE' | translate }}</h1>
<p class="desc">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p>
<app-project-grant-members *ngIf="this.projectid && this.grantid" [disabled]="isZitadel"
[projectId]="projectid" [grantId]="grantid" [type]="projectType">
</app-project-grant-members>
</div>
</div>
</div>

View File

@ -0,0 +1,92 @@
.container {
display: flex;
padding-bottom: 3rem;
h1 {
font-size: 1.2rem;
}
.desc {
display: block;
font-size: .9rem;
color: #8795a1;
}
.left {
width: 100px;
display: flex;
padding: 1rem;
justify-content: center;
a {
margin-top: .2rem;
}
}
.right {
flex: 1;
padding-top: 1rem;
.head {
display: flex;
align-items: center;
border-bottom: 1px solid #ffffff20;
margin-bottom: 2rem;
flex-wrap: wrap;
a {
display: block;
}
h1 {
margin-top: 0.2rem;
font-size: 2rem;
}
}
.master-row {
display: flex;
flex-wrap: wrap;
width: 100%;
.left-col {
flex: 1;
display: flex;
flex-direction: column;
margin-bottom: 2rem;
.row {
max-width: 400px;
display: flex;
align-items: center;
padding: .5rem 0;
span {
font-size: .9rem;
}
.first {
color: #8795a1;
}
}
}
.fill-space {
flex: 1;
}
.state-button {
border-radius: .5rem;
}
}
mat-form-field {
width: 100%;
}
.divider {
height: 1px;
background-color: #ffffff20;
}
}
}

View File

@ -0,0 +1,88 @@
import { Component } from '@angular/core';
import { MatSelectChange } from '@angular/material/select';
import { ActivatedRoute } from '@angular/router';
import {
ProjectGrant,
ProjectGrantState,
ProjectGrantView,
ProjectRoleView,
ProjectType,
} from 'src/app/proto/generated/management_pb';
import { OrgService } from 'src/app/services/org.service';
import { ProjectService } from 'src/app/services/project.service';
import { ToastService } from 'src/app/services/toast.service';
@Component({
selector: 'app-project-grant-detail',
templateUrl: './project-grant-detail.component.html',
styleUrls: ['./project-grant-detail.component.scss'],
})
export class ProjectGrantDetailComponent {
public grant!: ProjectGrantView.AsObject;
public projectid: string = '';
public grantid: string = '';
public projectType: ProjectType = ProjectType.PROJECTTYPE_OWNED;
public disabled: boolean = false;
public isZitadel: boolean = false;
ProjectGrantState: any = ProjectGrantState;
public memberRoleOptions: ProjectRoleView.AsObject[] = [];
constructor(
private orgService: OrgService,
private projectService: ProjectService,
private route: ActivatedRoute,
private toast: ToastService,
) {
this.route.params.subscribe(params => {
this.projectid = params.projectid;
this.grantid = params.grantid;
this.orgService.GetIam().then(iam => {
this.isZitadel = iam.toObject().iamProjectId === this.projectid;
});
this.getRoleOptions(params.projectid);
this.projectService.ProjectGrantByID(this.grantid, this.projectid).then((grant) => {
this.grant = grant.toObject();
});
});
}
public changeState(newState: ProjectGrantState): void {
if (newState === ProjectGrantState.PROJECTGRANTSTATE_ACTIVE) {
this.projectService.ReactivateProjectGrant(this.grantid, this.projectid).then(() => {
this.toast.showInfo('PROJECT.TOAST.REACTIVATED', true);
this.grant.state = newState;
}).catch(error => {
this.toast.showError(error);
});
} else if (newState === ProjectGrantState.PROJECTGRANTSTATE_INACTIVE) {
this.projectService.DeactivateProjectGrant(this.grantid, this.projectid).then(() => {
this.toast.showInfo('PROJECT.TOAST.DEACTIVATED', true);
this.grant.state = newState;
}).catch(error => {
this.toast.showError(error);
});
}
}
public getRoleOptions(projectId: string): void {
this.projectService.SearchProjectRoles(projectId, 100, 0).then(resp => {
this.memberRoleOptions = resp.toObject().resultList;
console.log(resp.toObject());
});
}
updateRoles(selectionChange: MatSelectChange): void {
this.projectService.UpdateProjectGrant(this.grant.id, this.grant.projectId, selectionChange.value)
.then((newgrant: ProjectGrant) => {
this.toast.showInfo('Grant updated!');
}).catch(error => {
this.toast.showError(error);
});
}
}

View File

@ -5,9 +5,11 @@ import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips'; import { MatChipsModule } from '@angular/material/chips';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatPaginatorModule } from '@angular/material/paginator'; import { MatPaginatorModule } from '@angular/material/paginator';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { MatSortModule } from '@angular/material/sort'; import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table'; import { MatTableModule } from '@angular/material/table';
import { MatTooltipModule } from '@angular/material/tooltip'; import { MatTooltipModule } from '@angular/material/tooltip';
@ -33,6 +35,8 @@ import { ProjectGrantMembersModule } from './project-grant-members/project-grant
MatIconModule, MatIconModule,
MatTableModule, MatTableModule,
MatPaginatorModule, MatPaginatorModule,
MatFormFieldModule,
MatSelectModule,
MatSortModule, MatSortModule,
MatTooltipModule, MatTooltipModule,
ReactiveFormsModule, ReactiveFormsModule,

Some files were not shown because too many files have changed in this diff Show More