mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:37:23 +00:00
feat(console): domain verification, create org, regexp route guards, change org, iam member (#573)
* verification dialog, service * i18n, verification dialog * file saver * savefile * verify trigger * delete project, i18n * org create dialog * org-create-self * stylelint * fix signout redirect * rm unused dialog component, import * project i18n de * regexp roles * use regex to check permissions * border radius * change validation flow * update org member * iam member change * lint * rm unused css * Update console/src/assets/i18n/de.json Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * Update console/src/assets/i18n/de.json Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com> * change guard Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
This commit is contained in:
parent
29831111ae
commit
65058ed17c
10
console/package-lock.json
generated
10
console/package-lock.json
generated
@ -2181,6 +2181,11 @@
|
||||
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/file-saver": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.1.tgz",
|
||||
"integrity": "sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw=="
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
||||
@ -6353,6 +6358,11 @@
|
||||
"schema-utils": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"file-saver": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.2.tgz",
|
||||
"integrity": "sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw=="
|
||||
},
|
||||
"file-uri-to-path": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||
|
@ -24,11 +24,13 @@
|
||||
"@angular/service-worker": "~10.0.2",
|
||||
"@ngx-translate/core": "^13.0.0",
|
||||
"@ngx-translate/http-loader": "^6.0.0",
|
||||
"@types/file-saver": "^2.0.1",
|
||||
"@types/google-protobuf": "^3.7.2",
|
||||
"@types/uuid": "^8.0.1",
|
||||
"angularx-qrcode": "^10.0.6",
|
||||
"angular-oauth2-oidc": "^10.0.3",
|
||||
"cors": "^2.8.5",
|
||||
"file-saver": "^2.0.2",
|
||||
"google-proto-files": "^2.2.0",
|
||||
"google-protobuf": "^3.12.4",
|
||||
"grpc": "^1.24.3",
|
||||
|
@ -28,7 +28,7 @@
|
||||
{{temporg?.name ? temporg.name : 'NO NAME'}}
|
||||
</button>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['iam.write']">
|
||||
<ng-template appHasRole [appHasRole]="['(org.create)?(iam.write)?']">
|
||||
<button mat-menu-item [routerLink]="[ '/org/create' ]">
|
||||
<mat-icon class="avatar">add</mat-icon>
|
||||
{{'MENU.NEWORG' | translate}}
|
||||
@ -79,7 +79,7 @@
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['project.read']">
|
||||
<ng-template appHasRole [appHasRole]="['project.read(:[0-9]*)?']">
|
||||
<div @navitem class="divider">
|
||||
<div class="line"></div>
|
||||
<span>{{'MENU.PROJECTSSECTION' | translate}}</span>
|
||||
@ -108,7 +108,7 @@
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['user.read']">
|
||||
<ng-template appHasRole [appHasRole]="['user.read(:[0-9]*)?']">
|
||||
<div @navitem class="divider">
|
||||
<div class="line"></div>
|
||||
<span class="label">
|
||||
|
@ -20,6 +20,7 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
|
||||
import { AuthConfig, OAuthModule, OAuthStorage } from 'angular-oauth2-oidc';
|
||||
import { QuicklinkModule } from 'ngx-quicklink';
|
||||
import { RegExpPipeModule } from 'src/app/pipes/regexp-pipe.module';
|
||||
|
||||
import { environment } from '../environments/environment';
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
@ -109,6 +110,7 @@ const authConfig: AuthConfig = {
|
||||
AvatarModule,
|
||||
WarnDialogModule,
|
||||
MatDialogModule,
|
||||
RegExpPipeModule,
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||
],
|
||||
providers: [
|
||||
|
@ -8,7 +8,7 @@ import { AuthService } from 'src/app/services/auth.service';
|
||||
|
||||
export class HasRoleDirective {
|
||||
private hasView: boolean = false;
|
||||
@Input() public set appHasRole(roles: string[]) {
|
||||
@Input() public set appHasRole(roles: string[] | RegExp[]) {
|
||||
if (roles && roles.length > 0) {
|
||||
this.authService.isAllowed(roles).subscribe(isAllowed => {
|
||||
if (isAllowed && !this.hasView) {
|
||||
|
@ -15,6 +15,6 @@ export class RoleGuard implements CanActivate {
|
||||
route: ActivatedRouteSnapshot,
|
||||
state: RouterStateSnapshot,
|
||||
): Observable<boolean> {
|
||||
return this.authService.isAllowed(route.data['roles'], true);
|
||||
return this.authService.isAllowed(route.data['roles']);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
<app-detail-layout [backRouterLink]="[ '/projects', project.projectId]"
|
||||
<app-detail-layout *ngIf="project" [backRouterLink]="[ '/projects', project?.projectId]"
|
||||
title="{{projectName}} {{ 'PROJECT.MEMBER.TITLE' | translate }}"
|
||||
description="{{ 'PROJECT.MEMBER.DESCRIPTION' | translate }}">
|
||||
<app-refresh-table *ngIf="project" (refreshed)="changePage()" [dataSize]="dataSource.totalResult"
|
||||
|
@ -73,9 +73,17 @@
|
||||
|
||||
<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 class="pointer" mat-cell *matCellDef="let member">
|
||||
<mat-form-field class="form-field" appearance="outline">
|
||||
<mat-label>{{ 'PROJECT.GRANT.TITLE' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="member.rolesList" multiple
|
||||
[disabled]="(['org.member.write'] | hasRole | async) == false"
|
||||
(selectionChange)="updateRoles(member, $event)">
|
||||
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
|
||||
{{ 'ROLES.'+role | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -73,11 +73,6 @@
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.role {
|
||||
display: inline-block;
|
||||
margin: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,15 +4,15 @@ 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';
|
||||
import { IamMembersComponent } from './iam-members.component';
|
||||
|
||||
describe('ProjectMembersComponent', () => {
|
||||
let component: ProjectMembersComponent;
|
||||
let fixture: ComponentFixture<ProjectMembersComponent>;
|
||||
describe('IamMembersComponent', () => {
|
||||
let component: IamMembersComponent;
|
||||
let fixture: ComponentFixture<IamMembersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProjectMembersComponent],
|
||||
declarations: [IamMembersComponent],
|
||||
imports: [
|
||||
NoopAnimationsModule,
|
||||
MatPaginatorModule,
|
||||
@ -23,7 +23,7 @@ describe('ProjectMembersComponent', () => {
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProjectMembersComponent);
|
||||
fixture = TestBed.createComponent(IamMembersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@ -2,10 +2,11 @@ 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 { MatSelectChange } from '@angular/material/select';
|
||||
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 { IamMemberView } from 'src/app/proto/generated/admin_pb';
|
||||
import { IamMember, IamMemberView } from 'src/app/proto/generated/admin_pb';
|
||||
import { 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';
|
||||
@ -25,6 +26,7 @@ export class IamMembersComponent implements AfterViewInit {
|
||||
public dataSource!: IamMembersDataSource;
|
||||
public selection: SelectionModel<IamMemberView.AsObject> = new SelectionModel<IamMemberView.AsObject>(true, []);
|
||||
|
||||
public memberRoleOptions: string[] = [];
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
|
||||
|
||||
@ -34,6 +36,7 @@ export class IamMembersComponent implements AfterViewInit {
|
||||
|
||||
this.dataSource = new IamMembersDataSource(this.adminService);
|
||||
this.dataSource.loadMembers(0, 25);
|
||||
this.getRoleOptions();
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
@ -51,6 +54,25 @@ export class IamMembersComponent implements AfterViewInit {
|
||||
);
|
||||
}
|
||||
|
||||
public getRoleOptions(): void {
|
||||
this.adminService.GetIamMemberRoles().then(resp => {
|
||||
this.memberRoleOptions = resp.toObject().rolesList;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
updateRoles(member: IamMemberView.AsObject, selectionChange: MatSelectChange): void {
|
||||
console.log(member.userId, selectionChange.value);
|
||||
this.adminService.ChangeIamMember(member.userId, selectionChange.value)
|
||||
.then((newmember: IamMember) => {
|
||||
this.toast.showInfo('ORG.TOAST.MEMBERCHANGED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public removeProjectMemberSelection(): void {
|
||||
Promise.all(this.selection.selected.map(member => {
|
||||
return this.adminService.RemoveIamMember(member.userId).then(() => {
|
||||
|
@ -5,15 +5,18 @@ 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 { 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 { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
|
||||
import { IamMembersRoutingModule } from './iam-members-routing.module';
|
||||
import { IamMembersComponent } from './iam-members.component';
|
||||
@ -39,6 +42,9 @@ import { IamMembersComponent } from './iam-members.component';
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
HasRolePipeModule,
|
||||
],
|
||||
})
|
||||
export class IamMembersModule { }
|
||||
|
@ -8,6 +8,12 @@
|
||||
{{ createSteps }}</span>
|
||||
</div>
|
||||
|
||||
<ng-template appHasRole [appHasRole]="['iam.write']">
|
||||
<mat-slide-toggle class="example-margin" color="primary" (change)="changeSelf($event)" [(ngModel)]="forSelf">
|
||||
Use your personal account as organisation owner
|
||||
</mat-slide-toggle>
|
||||
|
||||
<ng-container *ngIf="!forSelf">
|
||||
<ng-container *ngIf="currentCreateStep == 1">
|
||||
<h1>{{'ORG.PAGES.ORGDETAIL_TITLE' | translate}}</h1>
|
||||
<form [formGroup]="orgForm" (ngSubmit)="next()">
|
||||
@ -30,21 +36,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- <div *ngIf="name?.touched" @openClose>
|
||||
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate }}</p>
|
||||
|
||||
<p>{{domain?.value}}/.well-known/caos-developer-domain-association.txt</p>
|
||||
|
||||
<div class="btn-container">
|
||||
<button color="primary" type="submit"
|
||||
mat-stroked-button>{{ 'ORG.PAGES.DOWNLOAD_FILE' | translate }}</button>
|
||||
|
||||
<button color="primary" type="submit" mat-raised-button>{{ 'ACTIONS.VERIFY' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION_SKIP' | translate }}</p>
|
||||
</div> -->
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="currentCreateStep == createSteps">
|
||||
@ -109,14 +100,15 @@
|
||||
<mat-option *ngFor="let language of languages" [value]="language">
|
||||
{{ 'LANGUAGES.'+language | translate }}
|
||||
</mat-option>
|
||||
<mat-error *ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
|
||||
<mat-error
|
||||
*ngIf="preferredLanguage?.invalid && preferredLanguage?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
</mat-error>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-checkbox class="checkbox" [(ngModel)]="usePassword" [ngModelOptions]="{standalone: true}"
|
||||
(change)="initPwdValidators()">
|
||||
<mat-checkbox class="checkbox" [(ngModel)]="usePassword"
|
||||
[ngModelOptions]="{standalone: true}" (change)="initPwdValidators()">
|
||||
{{'ORG.PAGES.USEPASSWORD' | translate}}</mat-checkbox>
|
||||
|
||||
<ng-container *ngIf="usePassword && pwdForm">
|
||||
@ -129,8 +121,8 @@
|
||||
<form [formGroup]="pwdForm" class="form">
|
||||
<mat-form-field class="formfield" *ngIf="password" appearance="outline">
|
||||
<mat-label>{{ 'USER.PASSWORD.NEW' | translate }}</mat-label>
|
||||
<input autocomplete="off" name="firstpassword" matInput formControlName="password"
|
||||
type="password" />
|
||||
<input autocomplete="off" name="firstpassword" matInput
|
||||
formControlName="password" type="password" />
|
||||
|
||||
<mat-error *ngIf="password?.errors?.required">
|
||||
{{ 'USER.VALIDATION.REQUIRED' | translate }}
|
||||
@ -163,4 +155,29 @@
|
||||
</form>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-template>
|
||||
<ng-template appHasRole [appHasRole]="['org.create']">
|
||||
<div *ngIf="forSelf">
|
||||
<ng-container *ngIf="currentCreateStep == 1">
|
||||
<h1>{{'ORG.PAGES.ORGDETAIL_TITLE' | translate}}</h1>
|
||||
<form [formGroup]="orgForm" (ngSubmit)="createOrgForSelf()">
|
||||
<div class="content">
|
||||
<mat-form-field class="formfield" appearance="outline">
|
||||
<mat-label>{{ 'ORG_DETAIL.DETAIL.NAME' | translate }}</mat-label>
|
||||
<input matInput formControlName="name" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="btn-container">
|
||||
<span class="fill-space"></span>
|
||||
<button [disabled]="orgForm.invalid" color="primary" mat-raised-button class="big-button"
|
||||
cdkFocusInitial type="submit">
|
||||
{{'CREATE' | translate}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
@ -2,11 +2,14 @@ import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { Location } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { MatSlideToggleChange } from '@angular/material/slide-toggle';
|
||||
import { Router } from '@angular/router';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from 'src/app/pages/validators';
|
||||
import { CreateOrgRequest, CreateUserRequest, Gender, OrgSetUpResponse } from 'src/app/proto/generated/admin_pb';
|
||||
import { PasswordComplexityPolicy } from 'src/app/proto/generated/auth_pb';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { AuthService } from 'src/app/services/auth.service';
|
||||
import { OrgService } from 'src/app/services/org.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@ -56,6 +59,8 @@ export class OrgCreateComponent {
|
||||
|
||||
public policy!: PasswordComplexityPolicy.AsObject;
|
||||
public usePassword: boolean = false;
|
||||
|
||||
public forSelf: boolean = true;
|
||||
constructor(
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
@ -63,8 +68,13 @@ export class OrgCreateComponent {
|
||||
private _location: Location,
|
||||
private fb: FormBuilder,
|
||||
private orgService: OrgService,
|
||||
private authService: AuthService,
|
||||
) {
|
||||
const validators: Validators[] = [];
|
||||
this.authService.isAllowed(['iam.write']).pipe(take(1)).subscribe((allowed) => {
|
||||
if (allowed) {
|
||||
this.forSelf = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.orgForm = this.fb.group({
|
||||
name: ['', [Validators.required]],
|
||||
@ -165,6 +175,36 @@ export class OrgCreateComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public changeSelf(change: MatSlideToggleChange): void {
|
||||
console.log(change.checked);
|
||||
|
||||
if (change.checked) {
|
||||
this.createSteps = 1;
|
||||
|
||||
this.orgForm = this.fb.group({
|
||||
name: ['', [Validators.required]],
|
||||
});
|
||||
} else {
|
||||
this.createSteps = 2;
|
||||
|
||||
this.orgForm = this.fb.group({
|
||||
name: ['', [Validators.required]],
|
||||
domain: [''],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public createOrgForSelf(): void {
|
||||
console.log('create for self');
|
||||
if (this.name && this.name.value) {
|
||||
this.orgService.CreateOrg(this.name.value).then((org) => {
|
||||
this.router.navigate(['orgs', org.toObject().id]);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public get name(): AbstractControl | null {
|
||||
return this.orgForm.get('name');
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { PasswordComplexityViewModule } from 'src/app/modules/password-complexity-view/password-complexity-view.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
|
||||
@ -28,8 +30,10 @@ import { OrgCreateComponent } from './org-create.component';
|
||||
MatSelectModule,
|
||||
HasRolePipeModule,
|
||||
TranslateModule,
|
||||
HasRoleModule,
|
||||
MatCheckboxModule,
|
||||
PasswordComplexityViewModule,
|
||||
MatSlideToggleModule,
|
||||
],
|
||||
})
|
||||
export class OrgCreateModule { }
|
||||
|
@ -0,0 +1,47 @@
|
||||
<span class="title" mat-dialog-title>{{domain.domain}} {{'ORG.PAGES.ORGDOMAIN_TITLE' | translate}} </span>
|
||||
<div mat-dialog-content>
|
||||
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate }}</p>
|
||||
|
||||
<p class="desc warn">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION_VALIDATION_DESC' | translate }}</p>
|
||||
|
||||
|
||||
<div class="btn-container">
|
||||
<button color="primary" type="submit" mat-raised-button
|
||||
(click)="validate()">{{ 'ACTIONS.VERIFY' | translate }}</button>
|
||||
</div>
|
||||
|
||||
<p>{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION_NEWTOKEN_TITLE' | translate }}</p>
|
||||
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN_VERIFICATION_NEWTOKEN_DESC' | translate }}</p>
|
||||
|
||||
<div class="btn-container" *ngIf="!(http || dns)">
|
||||
<button color="primary" mat-raised-button (click)="loadHttpToken()">HTTP</button>
|
||||
<button color="primary" mat-raised-button (click)="loadDnsToken()">DNS</button>
|
||||
</div>
|
||||
<div *ngIf="http">
|
||||
<p>HTTP TOKEN</p>
|
||||
<p class="entry">{{http?.url}}.txt</p>
|
||||
|
||||
<div class="btn-container">
|
||||
<button mat-stroked-button (click)="saveFile()"
|
||||
color="primary">{{ 'ORG.PAGES.DOWNLOAD_FILE' | translate }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="dns">
|
||||
<p>DNS TOKEN</p>
|
||||
<div class="line" *ngIf="dns?.token">
|
||||
<p class="entry">{{dns?.token}}</p>
|
||||
<button color="primary" [disabled]="copied == data.clientSecret" matTooltip="copy to clipboard"
|
||||
appCopyToClipboard [valueToCopy]="dns.token" (copiedValue)="copied = $event" mat-icon-button>
|
||||
<i *ngIf="copied != dns.token" class="las la-clipboard"></i>
|
||||
<i *ngIf="copied == dns.token" class="las la-clipboard-check"></i>
|
||||
</button>
|
||||
</div>
|
||||
<p class="entry">{{dns?.url}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions class="action">
|
||||
<button mat-button class="ok-button" (click)="closeDialog()">
|
||||
{{'ACTIONS.CLOSE' | translate}}
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,41 @@
|
||||
.btn-container {
|
||||
display: flex;
|
||||
margin: -.5rem;
|
||||
|
||||
button {
|
||||
margin: 1rem .5rem;
|
||||
border-radius: .5rem;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
color: #8795a1;
|
||||
font-size: .9rem;
|
||||
|
||||
&.warn {
|
||||
color: rgb(201, 51, 71);
|
||||
}
|
||||
}
|
||||
|
||||
.entry {
|
||||
margin: .5rem 0;
|
||||
}
|
||||
|
||||
.line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.action {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
.ok-button {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { DomainVerificationComponent } from './domain-verification.component';
|
||||
|
||||
describe('DomainVerificationComponent', () => {
|
||||
let component: DomainVerificationComponent;
|
||||
let fixture: ComponentFixture<DomainVerificationComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DomainVerificationComponent],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DomainVerificationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,59 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { saveAs } from 'file-saver';
|
||||
import { OrgDomainValidationResponse, OrgDomainValidationType, OrgDomainView } from 'src/app/proto/generated/management_pb';
|
||||
import { OrgService } from 'src/app/services/org.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-domain-verification',
|
||||
templateUrl: './domain-verification.component.html',
|
||||
styleUrls: ['./domain-verification.component.scss'],
|
||||
})
|
||||
export class DomainVerificationComponent {
|
||||
public domain!: OrgDomainView.AsObject;
|
||||
|
||||
public OrgDomainValidationType: any = OrgDomainValidationType;
|
||||
|
||||
public http!: OrgDomainValidationResponse.AsObject;
|
||||
public dns!: OrgDomainValidationResponse.AsObject;
|
||||
public copied: string = '';
|
||||
|
||||
constructor(
|
||||
private toast: ToastService,
|
||||
public dialogRef: MatDialogRef<DomainVerificationComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private orgService: OrgService,
|
||||
) {
|
||||
this.domain = data.domain;
|
||||
}
|
||||
|
||||
async loadHttpToken(): Promise<void> {
|
||||
this.http = (await this.orgService.GenerateMyOrgDomainValidation(
|
||||
this.domain.domain,
|
||||
OrgDomainValidationType.ORGDOMAINVALIDATIONTYPE_HTTP)).toObject();
|
||||
}
|
||||
|
||||
async loadDnsToken(): Promise<void> {
|
||||
this.dns = (await this.orgService.GenerateMyOrgDomainValidation(
|
||||
this.domain.domain,
|
||||
OrgDomainValidationType.ORGDOMAINVALIDATIONTYPE_DNS)).toObject();
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
public validate(): void {
|
||||
this.orgService.ValidateMyOrgDomain(this.domain.domain).then(() => {
|
||||
this.dialogRef.close(false);
|
||||
}).catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public saveFile(): void {
|
||||
const blob = new Blob([this.http.token], { type: 'text/plain;charset=utf-8' });
|
||||
saveAs(blob, this.http.token + '.txt');
|
||||
}
|
||||
}
|
@ -5,16 +5,17 @@
|
||||
<app-card title="{{ 'ORG.DOMAINS.TITLE' | translate }}"
|
||||
description="{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}">
|
||||
<div *ngFor="let domain of domains" class="domain">
|
||||
<span class="title">{{domain.domain}}</span>
|
||||
<span (click)="verifyDomain(domain)" class="title">{{domain.domain}}</span>
|
||||
<i matTooltip="verified" *ngIf="domain.verified" class="verified las la-check-circle"></i>
|
||||
<i matTooltip="primary" *ngIf="domain.primary" class="primary las la-star"></i>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
<button matTooltip="Remove domain" color="warn" mat-icon-button (click)="removeDomain(domain.domain)"><i
|
||||
class="las la-trash"></i></button>
|
||||
</div>
|
||||
|
||||
<p class="new-desc">{{'ORG.PAGES.ORGDOMAIN_VERIFICATION' | translate}}</p>
|
||||
<button matTooltip="Add domain" mat-raised-button color="primary"
|
||||
<button class="add-button" matTooltip="Add domain" mat-raised-button color="primary"
|
||||
(click)="addNewDomain()">{{'ORG.DOMAINS.NEW' | translate}} </button>
|
||||
</app-card>
|
||||
|
||||
|
@ -16,6 +16,11 @@
|
||||
.title {
|
||||
font-size: 16px;
|
||||
margin-right: 1rem;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.verified,
|
||||
@ -24,11 +29,20 @@
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.verify-btn {
|
||||
border-radius: .5rem;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.add-button {
|
||||
border-radius: .5rem;
|
||||
}
|
||||
|
||||
.new-desc {
|
||||
font-size: 14px;
|
||||
color: #818a8a;
|
||||
|
@ -23,6 +23,7 @@ import { OrgService } from 'src/app/services/org.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { AddDomainDialogComponent } from './add-domain-dialog/add-domain-dialog.component';
|
||||
import { DomainVerificationComponent } from './domain-verification/domain-verification.component';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -182,4 +183,18 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
||||
this.router.navigate(['org/members']);
|
||||
}
|
||||
}
|
||||
|
||||
public verifyDomain(domain: OrgDomainView.AsObject): void {
|
||||
const dialogRef = this.dialog.open(DomainVerificationComponent, {
|
||||
data: {
|
||||
domain: domain,
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
console.log(resp);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -73,9 +73,17 @@
|
||||
|
||||
<ng-container matColumnDef="roles">
|
||||
<th mat-header-cell *matHeaderCellDef> {{ 'PROJECT.MEMBER.ROLES' | translate }} </th>
|
||||
<td class="pointer" [routerLink]="['/users', 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 class="pointer" mat-cell *matCellDef="let member">
|
||||
<mat-form-field class="form-field" appearance="outline">
|
||||
<mat-label>{{ 'PROJECT.GRANT.TITLE' | translate }}</mat-label>
|
||||
<mat-select [(ngModel)]="member.rolesList" multiple
|
||||
[disabled]="(['org.member.write'] | hasRole | async) == false"
|
||||
(selectionChange)="updateRoles(member, $event)">
|
||||
<mat-option *ngFor="let role of memberRoleOptions" [value]="role">
|
||||
{{ 'ROLES.'+role | translate }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
|
@ -73,11 +73,6 @@
|
||||
width: 50px;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.role {
|
||||
display: inline-block;
|
||||
margin: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,11 @@ 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 { MatSelectChange } from '@angular/material/select';
|
||||
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, OrgMemberView, ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb';
|
||||
import { Org, OrgMember, OrgMemberView, ProjectMember, ProjectType, User } from 'src/app/proto/generated/management_pb';
|
||||
import { OrgService } from 'src/app/services/org.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@ -25,17 +26,23 @@ export class OrgMembersComponent implements AfterViewInit {
|
||||
public dataSource!: OrgMembersDataSource;
|
||||
public selection: SelectionModel<OrgMemberView.AsObject> = new SelectionModel<OrgMemberView.AsObject>(true, []);
|
||||
|
||||
public memberRoleOptions: string[] = [];
|
||||
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
public displayedColumns: string[] = ['select', 'firstname', 'lastname', 'username', 'email', 'roles'];
|
||||
|
||||
constructor(private orgService: OrgService,
|
||||
constructor(
|
||||
private orgService: OrgService,
|
||||
private dialog: MatDialog,
|
||||
private toast: ToastService) {
|
||||
private toast: ToastService,
|
||||
) {
|
||||
this.orgService.GetMyOrg().then(org => {
|
||||
this.org = org.toObject();
|
||||
this.dataSource = new OrgMembersDataSource(this.orgService);
|
||||
this.dataSource.loadMembers(0, 25);
|
||||
});
|
||||
|
||||
this.getRoleOptions();
|
||||
}
|
||||
|
||||
public ngAfterViewInit(): void {
|
||||
@ -44,7 +51,24 @@ export class OrgMembersComponent implements AfterViewInit {
|
||||
tap(() => this.loadMembersPage()),
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
public getRoleOptions(): void {
|
||||
this.orgService.GetOrgMemberRoles().then(resp => {
|
||||
this.memberRoleOptions = resp.toObject().rolesList;
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
updateRoles(member: OrgMemberView.AsObject, selectionChange: MatSelectChange): void {
|
||||
console.log(member.userId, selectionChange.value);
|
||||
this.orgService.ChangeMyOrgMember(member.userId, selectionChange.value)
|
||||
.then((newmember: OrgMember) => {
|
||||
this.toast.showInfo('ORG.TOAST.MEMBERCHANGED', true);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
private loadMembersPage(): void {
|
||||
|
@ -5,15 +5,18 @@ 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 { 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 { MatSelectModule } from '@angular/material/select';
|
||||
import { MatSortModule } from '@angular/material/sort';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
|
||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe.module';
|
||||
|
||||
import { OrgMembersRoutingModule } from './org-members-routing.module';
|
||||
import { OrgMembersComponent } from './org-members.component';
|
||||
@ -39,6 +42,9 @@ import { OrgMembersComponent } from './org-members.component';
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
DetailLayoutModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
HasRolePipeModule,
|
||||
],
|
||||
})
|
||||
export class OrgMembersModule { }
|
||||
|
@ -13,7 +13,7 @@ const routes: Routes = [
|
||||
component: OrgCreateComponent,
|
||||
canActivate: [RoleGuard],
|
||||
data: {
|
||||
roles: ['iam.write'],
|
||||
roles: ['(org.create)?(iam.write)?'],
|
||||
},
|
||||
loadChildren: () => import('./org-create/org-create.module').then(m => m.OrgCreateModule),
|
||||
},
|
||||
|
@ -11,6 +11,7 @@ import { MatMenuModule } from '@angular/material/menu';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CopyToClipboardModule } from 'src/app/directives/copy-to-clipboard/copy-to-clipboard.module';
|
||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||
import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
|
||||
import { CardModule } from 'src/app/modules/card/card.module';
|
||||
@ -21,13 +22,14 @@ import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module
|
||||
|
||||
import { ChangesModule } from '../../modules/changes/changes.module';
|
||||
import { AddDomainDialogModule } from './org-detail/add-domain-dialog/add-domain-dialog.module';
|
||||
import { DomainVerificationComponent } from './org-detail/domain-verification/domain-verification.component';
|
||||
import { OrgDetailComponent } from './org-detail/org-detail.component';
|
||||
import { OrgGridComponent } from './org-grid/org-grid.component';
|
||||
import { OrgsRoutingModule } from './orgs-routing.module';
|
||||
import { PolicyGridComponent } from './policy-grid/policy-grid.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [OrgDetailComponent, OrgGridComponent, PolicyGridComponent],
|
||||
declarations: [OrgDetailComponent, OrgGridComponent, PolicyGridComponent, DomainVerificationComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
OrgsRoutingModule,
|
||||
@ -53,6 +55,7 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
|
||||
TranslateModule,
|
||||
SharedModule,
|
||||
ContributorsModule,
|
||||
CopyToClipboardModule,
|
||||
],
|
||||
})
|
||||
export class OrgsModule { }
|
||||
|
@ -32,7 +32,8 @@
|
||||
<p class="docs-line" *ngIf="docs?.issuer">Issuer: {{docs.issuer}}</p>
|
||||
</div>
|
||||
<div class="btn-container">
|
||||
<button type="submit" color="primary" [disabled]="appNameForm.invalid || name?.disabled"
|
||||
<button class="submit-button" type="submit" color="primary"
|
||||
[disabled]="appNameForm.invalid || name?.disabled"
|
||||
mat-raised-button>{{ 'ACTIONS.SAVE' | translate }}</button>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -6,12 +6,18 @@
|
||||
</a>
|
||||
<h1>{{ 'PROJECT.PAGES.TITLE' | translate }} {{project?.name}}</h1>
|
||||
<ng-template appHasRole [appHasRole]="['project.write:'+projectId, 'project.write']">
|
||||
<button mat-icon-button (click)="editstate = !editstate" aria-label="Edit project name"
|
||||
*ngIf="isZitadel === false">
|
||||
<button matTooltip="{{'ACTIONS.EDIT' | translate}}" mat-icon-button (click)="editstate = !editstate"
|
||||
aria-label="Edit project name" *ngIf="isZitadel === false">
|
||||
<mat-icon *ngIf="!editstate">edit</mat-icon>
|
||||
<mat-icon *ngIf="editstate">close</mat-icon>
|
||||
</button>
|
||||
</ng-template>
|
||||
<ng-template appHasRole [appHasRole]="['project.delete:'+projectId, 'project.delete']">
|
||||
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" color="warn" mat-icon-button
|
||||
(click)="deleteProject()" aria-label="Edit project name" *ngIf="isZitadel === false">
|
||||
<i class="las la-trash"></i>
|
||||
</button>
|
||||
</ng-template>
|
||||
|
||||
<span class="fill-space"></span>
|
||||
|
||||
|
@ -166,6 +166,28 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
public deleteProject(): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
cancelKey: 'ACTIONS.CANCEL',
|
||||
titleKey: 'PROJECT.PAGES.DIALOG.DELETE.TITLE',
|
||||
descriptionKey: 'PROJECT.PAGES.DIALOG.DELETE.DESCRIPTION',
|
||||
},
|
||||
width: '400px',
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(resp => {
|
||||
if (resp) {
|
||||
this.projectService.RemoveProject(this.projectId).then(() => {
|
||||
this.toast.showInfo('PROJECT.TOAST.DELETED', true);
|
||||
this.router.navigate(['/projects']);
|
||||
}).catch(error => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public saveProject(): void {
|
||||
this.projectService.UpdateProject(this.project.projectId, this.project.name).then(() => {
|
||||
this.toast.showInfo('PROJECT.TOAST.UPDATED', true);
|
||||
|
@ -11,6 +11,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
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 { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
|
||||
@ -50,6 +51,7 @@ import { ProjectGrantsComponent } from './project-grants/project-grants.componen
|
||||
MatIconModule,
|
||||
ContributorsModule,
|
||||
WarnDialogModule,
|
||||
MatTooltipModule,
|
||||
ProjectRolesModule,
|
||||
HasRolePipeModule,
|
||||
UserGrantsModule,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FormControl } from '@angular/forms';
|
||||
|
||||
export function symbolValidator(c: FormControl): any {
|
||||
const REGEXP = /[^a-z0-9]/gi;
|
||||
const REGEXP: RegExp = /[^a-z0-9]/gi;
|
||||
|
||||
return REGEXP.test(c.value) ? null : {
|
||||
invalid: true,
|
||||
|
@ -7,10 +7,9 @@ import { AuthService } from '../services/auth.service';
|
||||
name: 'hasRole',
|
||||
})
|
||||
export class HasRolePipe implements PipeTransform {
|
||||
|
||||
constructor(private authService: AuthService) { }
|
||||
|
||||
public transform(values: string[], each: boolean = false): Observable<boolean> {
|
||||
return this.authService.isAllowed(values, each);
|
||||
public transform(values: string[]): Observable<boolean> {
|
||||
return this.authService.isAllowed(values);
|
||||
}
|
||||
}
|
||||
|
18
console/src/app/pipes/regexp-pipe.module.ts
Normal file
18
console/src/app/pipes/regexp-pipe.module.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
|
||||
import { RegexpPipe } from './regexp.pipe';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
RegexpPipe,
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
],
|
||||
exports: [
|
||||
RegexpPipe,
|
||||
],
|
||||
})
|
||||
export class RegExpPipeModule { }
|
10
console/src/app/pipes/regexp.pipe.ts
Normal file
10
console/src/app/pipes/regexp.pipe.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'regexp',
|
||||
})
|
||||
export class RegexpPipe implements PipeTransform {
|
||||
public transform(value: string): RegExp {
|
||||
return new RegExp(value);
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ import { Metadata } from 'grpc-web';
|
||||
import { AdminServicePromiseClient } from '../proto/generated/admin_grpc_web_pb';
|
||||
import {
|
||||
AddIamMemberRequest,
|
||||
ChangeIamMemberRequest,
|
||||
CreateOrgRequest,
|
||||
CreateUserRequest,
|
||||
IamMember,
|
||||
@ -134,6 +135,20 @@ export class AdminService {
|
||||
);
|
||||
}
|
||||
|
||||
public async ChangeIamMember(
|
||||
userId: string,
|
||||
rolesList: string[],
|
||||
): Promise<IamMember> {
|
||||
const req = new ChangeIamMemberRequest();
|
||||
req.setUserId(userId);
|
||||
req.setRolesList(rolesList);
|
||||
|
||||
return await this.request(
|
||||
c => c.changeIamMember,
|
||||
req,
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
public async GetOrgIamPolicy(orgId: string): Promise<OrgIamPolicy> {
|
||||
const req = new OrgIamPolicyID();
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthConfig, OAuthService } from 'angular-oauth2-oidc';
|
||||
import { BehaviorSubject, from, merge, Observable, of, Subject } from 'rxjs';
|
||||
import { catchError, filter, finalize, first, map, mergeMap, switchMap, take, timeout } from 'rxjs/operators';
|
||||
@ -31,7 +30,6 @@ export class AuthService {
|
||||
private userService: AuthUserService,
|
||||
private storage: StorageService,
|
||||
private statehandler: StatehandlerService,
|
||||
private router: Router,
|
||||
) {
|
||||
this.user = merge(
|
||||
of(this.oauthService.getAccessToken()).pipe(
|
||||
@ -66,24 +64,27 @@ export class AuthService {
|
||||
first(),
|
||||
switchMap(() => from(this.userService.GetMyzitadelPermissions())),
|
||||
map(rolesResp => rolesResp.toObject().permissionsList),
|
||||
).subscribe(roles => this.zitadelPermissions.next(roles));
|
||||
).subscribe(roles => {
|
||||
console.log(roles);
|
||||
this.zitadelPermissions.next(roles);
|
||||
});
|
||||
}
|
||||
|
||||
public isAllowed(roles: string[], each: boolean = false): Observable<boolean> {
|
||||
public isAllowed(roles: string[] | RegExp[]): Observable<boolean> {
|
||||
if (roles && roles.length > 0) {
|
||||
return this.zitadelPermissions.pipe(switchMap(zroles => {
|
||||
return of(this.hasRoles(zroles, roles, each));
|
||||
return of(this.hasRoles(zroles, roles));
|
||||
}));
|
||||
} else {
|
||||
return of(false);
|
||||
}
|
||||
}
|
||||
|
||||
public hasRoles(userRoles: string[], requestedRoles: string[], each: boolean = false): boolean {
|
||||
return each ?
|
||||
requestedRoles.every(role => userRoles.includes(role)) :
|
||||
requestedRoles.findIndex(role => {
|
||||
return userRoles.findIndex(i => i.includes(role)) > -1;
|
||||
public hasRoles(userRoles: string[], requestedRoles: string[] | RegExp[]): boolean {
|
||||
return requestedRoles.findIndex((regexp: any) => {
|
||||
return userRoles.findIndex(role => {
|
||||
return (new RegExp(regexp)).test(role);
|
||||
}) > -1;
|
||||
}) > -1;
|
||||
}
|
||||
|
||||
@ -129,7 +130,6 @@ export class AuthService {
|
||||
this.oauthService.logOut();
|
||||
this._authenticated = false;
|
||||
this._authenticationChanged.next(false);
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
|
||||
public get activeOrgChanged(): Observable<Org.AsObject> {
|
||||
|
@ -6,15 +6,21 @@ import { ManagementServicePromiseClient } from '../proto/generated/management_gr
|
||||
import {
|
||||
AddOrgDomainRequest,
|
||||
AddOrgMemberRequest,
|
||||
ChangeOrgMemberRequest,
|
||||
Domain,
|
||||
Iam,
|
||||
Org,
|
||||
OrgCreateRequest,
|
||||
OrgDomain,
|
||||
OrgDomainSearchQuery,
|
||||
OrgDomainSearchRequest,
|
||||
OrgDomainSearchResponse,
|
||||
OrgDomainValidationRequest,
|
||||
OrgDomainValidationResponse,
|
||||
OrgDomainValidationType,
|
||||
OrgIamPolicy,
|
||||
OrgID,
|
||||
OrgMember,
|
||||
OrgMemberRoles,
|
||||
OrgMemberSearchRequest,
|
||||
OrgMemberSearchResponse,
|
||||
@ -34,6 +40,7 @@ import {
|
||||
ProjectGrantCreate,
|
||||
RemoveOrgDomainRequest,
|
||||
RemoveOrgMemberRequest,
|
||||
ValidateOrgDomainRequest,
|
||||
} from '../proto/generated/management_pb';
|
||||
import { GrpcBackendService } from './grpc-backend.service';
|
||||
import { GrpcService, RequestFactory, ResponseMapper } from './grpc.service';
|
||||
@ -121,6 +128,31 @@ export class OrgService {
|
||||
);
|
||||
}
|
||||
|
||||
public async GenerateMyOrgDomainValidation(domain: string, type: OrgDomainValidationType):
|
||||
Promise<OrgDomainValidationResponse> {
|
||||
const req: OrgDomainValidationRequest = new OrgDomainValidationRequest();
|
||||
req.setDomain(domain);
|
||||
req.setType(type);
|
||||
|
||||
return await this.request(
|
||||
c => c.generateMyOrgDomainValidation,
|
||||
req,
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
public async ValidateMyOrgDomain(domain: string):
|
||||
Promise<Empty> {
|
||||
const req: ValidateOrgDomainRequest = new ValidateOrgDomainRequest();
|
||||
req.setDomain(domain);
|
||||
|
||||
return await this.request(
|
||||
c => c.validateMyOrgDomain,
|
||||
req,
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
public async SearchMyOrgMembers(limit: number, offset: number): Promise<OrgMemberSearchResponse> {
|
||||
const req = new OrgMemberSearchRequest();
|
||||
req.setLimit(limit);
|
||||
@ -142,6 +174,16 @@ export class OrgService {
|
||||
);
|
||||
}
|
||||
|
||||
public async CreateOrg(name: string): Promise<Org> {
|
||||
const req = new OrgCreateRequest();
|
||||
req.setName(name);
|
||||
return await this.request(
|
||||
c => c.createOrg,
|
||||
req,
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
public async AddMyOrgMember(userId: string, rolesList: string[]): Promise<Empty> {
|
||||
const req = new AddOrgMemberRequest();
|
||||
req.setUserId(userId);
|
||||
@ -155,6 +197,18 @@ export class OrgService {
|
||||
);
|
||||
}
|
||||
|
||||
public async ChangeMyOrgMember(userId: string, rolesList: string[]): Promise<OrgMember> {
|
||||
const req = new ChangeOrgMemberRequest();
|
||||
req.setUserId(userId);
|
||||
req.setRolesList(rolesList);
|
||||
return await this.request(
|
||||
c => c.changeMyOrgMember,
|
||||
req,
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public async RemoveMyOrgMember(userId: string): Promise<Empty> {
|
||||
const req = new RemoveOrgMemberRequest();
|
||||
req.setUserId(userId);
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
ApplicationSearchRequest,
|
||||
ApplicationSearchResponse,
|
||||
ApplicationUpdate,
|
||||
ApplicationView,
|
||||
GrantedProjectSearchRequest,
|
||||
OIDCApplicationCreate,
|
||||
OIDCConfig,
|
||||
@ -488,7 +489,7 @@ export class ProjectService {
|
||||
);
|
||||
}
|
||||
|
||||
public async GetApplicationById(projectId: string, applicationId: string): Promise<Application> {
|
||||
public async GetApplicationById(projectId: string, applicationId: string): Promise<ApplicationView> {
|
||||
const req = new ApplicationID();
|
||||
req.setProjectId(projectId);
|
||||
req.setId(applicationId);
|
||||
@ -519,6 +520,16 @@ export class ProjectService {
|
||||
);
|
||||
}
|
||||
|
||||
public async RemoveProject(id: string): Promise<Empty> {
|
||||
const req = new ProjectID();
|
||||
req.setId(id);
|
||||
return await this.request(
|
||||
c => c.removeProject,
|
||||
req,
|
||||
f => f,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public async DeactivateProjectGrant(id: string, projectId: string): Promise<ProjectGrant> {
|
||||
const req = new ProjectGrantID();
|
||||
|
@ -56,7 +56,8 @@
|
||||
"REACTIVATE":"Aktivieren",
|
||||
"DEACTIVATE":"Deaktivieren",
|
||||
"REFRESH":"Aktualisieren",
|
||||
"LOGIN":"Login"
|
||||
"LOGIN":"Login",
|
||||
"EDIT":"Bearbeiten"
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Bitte fülle alle benötigten Felder aus!",
|
||||
@ -265,9 +266,12 @@
|
||||
"CREATE":"Organisation erstellen",
|
||||
"ORGDETAIL_TITLE":"Gib den Namen und die Domain für die neue Organisation ein.",
|
||||
"ORGDOMAIN_TITLE":"Organisations Domain Verifikation",
|
||||
"ORGDOMAIN_VERIFICATION":"Stelle deine Domain bereit und überprüfe deren Besitz indem du eine Bestätigungsdatei herunterladen und unter der unten angegebenen URL hochladen. Klicken zum Abschluss auf die Schaltfläche, um diese zu überprüfen.",
|
||||
"ORGDOMAIN_VERIFICATION":"Überprüfen Sie den Besitz ihrer Domain indem Sie eine Bestätigungsdatei herunterladen und unter der angegebenen URL hochladen oder Sie sie mit einem DNS Eintrag verifizieren.",
|
||||
"ORGDOMAIN_VERIFICATION_SKIP":"Du kannst die Überprüfung vorerst überspringen und deine Organisation weiter erstellen. Um deine Organisation jedoch verwenden zu können, muss dieser Schritt abgeschlossen sein!",
|
||||
"ORGDETAILUSER_TITLE":"Organisationsbesitzer hinzufügen",
|
||||
"ORGDOMAIN_VERIFICATION_VALIDATION_DESC":"Die Tokens werden regelmäßig überprüft, um sicherzustellen, dass sie weiterhin Besitzer der Domain sind.",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_TITLE":"Neues Token anfordern",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_DESC":"Wenn Sie ein neues Token anfordern wollen, klicken Sie auf die gewünschte Methode. Wenn Sie ein vorhandenes Token validieren möchten Klicken Sie auf Validieren.",
|
||||
"DOWNLOAD_FILE":"Datei download",
|
||||
"SELECTORGTOOLTIP":"Wähle diese Organisation",
|
||||
"PRIMARYDOMAIN":"Primäre Domain",
|
||||
@ -350,7 +354,8 @@
|
||||
"DOMAINADDED":"Domain hinzugefügt!",
|
||||
"DOMAINREMOVED":"Domain entfernt!",
|
||||
"MEMBERADDED":"Manager hinzugefügt!",
|
||||
"MEMBERREMOVED":"Manager entfernt!"
|
||||
"MEMBERREMOVED":"Manager entfernt!",
|
||||
"MEMBERCHANGED":"Manager verändert!"
|
||||
}
|
||||
},
|
||||
"ORG_DETAIL": {
|
||||
@ -408,11 +413,15 @@
|
||||
"DIALOG": {
|
||||
"REACTIVATE": {
|
||||
"TITLE":"Projekt reaktivieren",
|
||||
"DESCRIPTION":"Wollen Sie das Project wirklich reaktivieren?"
|
||||
"DESCRIPTION":"Wollen Sie das Projekt wirklich reaktivieren?"
|
||||
},
|
||||
"DEACTIVATE": {
|
||||
"TITLE":"Projekt deaktivieren",
|
||||
"DESCRIPTION":"Wollen Sie das Project wirklich deaktivieren?"
|
||||
"DESCRIPTION":"Wollen Sie das Projekt wirklich deaktivieren?"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE":"Projekt löschen",
|
||||
"DESCRIPTION":"Wollen Sie das Projekt wirklich löschen?"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -479,7 +488,7 @@
|
||||
"ROLENAMESLIST": "Rollen",
|
||||
"NOROLES":"Keine Rollen",
|
||||
"TOAST":{
|
||||
"PROJECTGRANTUSERGRANTADDED":"Project Berechtigung erstellt!",
|
||||
"PROJECTGRANTUSERGRANTADDED":"Projekt Berechtigung erstellt!",
|
||||
"PROJECTGRANTADDED":"Projekt Berechtigung erstellt",
|
||||
"PROJECTGRANTCHANGED":"Projekt Berechtigung geändert!",
|
||||
"PROJECTGRANTMEMBERADDED":"Berechtigungsmanager hinzugefügt!",
|
||||
@ -533,7 +542,8 @@
|
||||
"REACTIVATED":"Reaktiviert!",
|
||||
"DEACTIVATED":"Deaktiviert!",
|
||||
"UPDATED":"Projekt gespeichert!",
|
||||
"GRANTUPDATED":"Berechtigung verändert!"
|
||||
"GRANTUPDATED":"Berechtigung verändert!",
|
||||
"DELETED":"Projekt gelöscht!"
|
||||
}
|
||||
},
|
||||
"APP": {
|
||||
@ -624,44 +634,44 @@
|
||||
"ROLES": {
|
||||
"ORG_OWNER": "Org. Owner",
|
||||
"ORG_MEMBER_VIEWER": "Org. Member Viewer",
|
||||
"ORG_PROJECT_ROLE_VIEWER": "Org. Project Role Viewer",
|
||||
"ORG_PROJECT_ROLE_VIEWER": "Org. Projekt Role Viewer",
|
||||
"ORG_EDITOR":"Org. Editor",
|
||||
"ORG_VIEWER":"Org. Viewer",
|
||||
"ORG_MEMBER_EDITOR":"Org.. Member Editor",
|
||||
"ORG_PROJECT_CREATOR":"Org.. Project Creator",
|
||||
"ORG_PROJECT_EDITOR":"Org.. Project Editor",
|
||||
"ORG_PROJECT_VIEWER":"Org.. Project Viewer",
|
||||
"ORG_PROJECT_MEMBER_EDITOR":"Org.. Project Member Editor",
|
||||
"ORG_PROJECT_MEMBER_VIEWER":"Org.. Project Member Viewer",
|
||||
"ORG_PROJECT_ROLE_EDITOR":"Org.. Project Role Editor",
|
||||
"ORG_PROJECT_APP_EDITOR":"Org. Project App Editor",
|
||||
"ORG_PROJECT_APP_VIEWER":"Org. Project App Viewer",
|
||||
"ORG_PROJECT_GRANT_EDITOR":"Org. Project Grant Editor" ,
|
||||
"ORG_PROJECT_GRANT_VIEWER":"Org.Project Grant Viewer",
|
||||
"ORG_PROJECT_GRANT_MEMBER_EDITOR":"Org.Project Grant Member Editor",
|
||||
"ORG_PROJECT_GRANT_MEMBER_VIEWER":"Org.Project Grant Member Viewer",
|
||||
"ORG_PROJECT_CREATOR":"Org.. Projekt Creator",
|
||||
"ORG_PROJECT_EDITOR":"Org.. Projekt Editor",
|
||||
"ORG_PROJECT_VIEWER":"Org.. Projekt Viewer",
|
||||
"ORG_PROJECT_MEMBER_EDITOR":"Org.. Projekt Member Editor",
|
||||
"ORG_PROJECT_MEMBER_VIEWER":"Org.. Projekt Member Viewer",
|
||||
"ORG_PROJECT_ROLE_EDITOR":"Org.. Projekt Role Editor",
|
||||
"ORG_PROJECT_APP_EDITOR":"Org. Projekt App Editor",
|
||||
"ORG_PROJECT_APP_VIEWER":"Org. Projekt App Viewer",
|
||||
"ORG_PROJECT_GRANT_EDITOR":"Org. Projekt Grant Editor" ,
|
||||
"ORG_PROJECT_GRANT_VIEWER":"Org.Projekt Grant Viewer",
|
||||
"ORG_PROJECT_GRANT_MEMBER_EDITOR":"Org.Projekt Grant Member Editor",
|
||||
"ORG_PROJECT_GRANT_MEMBER_VIEWER":"Org.Projekt Grant Member Viewer",
|
||||
"ORG_USER_EDITOR":"Org.User Editor",
|
||||
"ORG_USER_VIEWER":"Org. User Viewer",
|
||||
"ORG_USER_GRANT_EDITOR":"Org. User Grant Editor",
|
||||
"ORG_USER_GRANT_VIEWER":"Org. User Grant Viewer",
|
||||
"ORG_POLICY_EDITOR":"Org. Policy Editor",
|
||||
"ORG_POLICY_VIEWER":"Org. Policy Viewer",
|
||||
"PROJECT_OWNER":"Project Owner",
|
||||
"PROJECT_OWNER_VIEWER":"Project Owner Viewer",
|
||||
"PROJECT_MEMBER_EDITOR":"Project Member Editor",
|
||||
"PROJECT_APP_EDITOR":"Project App Editor",
|
||||
"PROJECT_APP_VIEWER":"Project App Viewer",
|
||||
"PROJECT_USER_GRANT_EDITOR":"Project User Grant Editor",
|
||||
"PROJECT_USER_GRANT_VIEWER":"Project User Grant Viewer",
|
||||
"PROJECT_ROLE_EDITOR": "Project Role Editor",
|
||||
"PROJECT_MEMBER_VIEWER": "Project Member Viewer",
|
||||
"PROJECT_GRANT_EDITOR":"Project Grant Editor",
|
||||
"PROJECT_GRANT_VIEWER":"Project Grant Viewer",
|
||||
"PROJECT_GRANT_MEMBER_EDITOR":"Project Grant Member Editor",
|
||||
"PROJECT_GRANT_MEMBER_VIEWER":"Project Grant Member Viewer",
|
||||
"PROJECT_GRANT_OWNER":"Project Grant Owner",
|
||||
"PROJECT_GRANT_USER_GRANT_EDITOR":"Project Grant User Editor",
|
||||
"PROJECT_GRANT_USER_GRANT_VIEWER":"Project Grant User Grant Viewer"
|
||||
"PROJECT_OWNER":"Projekt Besitzer",
|
||||
"PROJECT_OWNER_VIEWER":"Projekt Besitzer Viewer",
|
||||
"PROJECT_MEMBER_EDITOR":"Projekt Manager Editor",
|
||||
"PROJECT_APP_EDITOR":"Projekt App Editor",
|
||||
"PROJECT_APP_VIEWER":"Projekt App Viewer",
|
||||
"PROJECT_USER_GRANT_EDITOR":"Projekt User Grant Editor",
|
||||
"PROJECT_USER_GRANT_VIEWER":"Projekt User Grant Viewer",
|
||||
"PROJECT_ROLE_EDITOR": "Projekt Role Editor",
|
||||
"PROJECT_MEMBER_VIEWER": "Projekt Member Viewer",
|
||||
"PROJECT_GRANT_EDITOR":"Projekt Grant Editor",
|
||||
"PROJECT_GRANT_VIEWER":"Projekt Grant Viewer",
|
||||
"PROJECT_GRANT_MEMBER_EDITOR":"Projekt Grant Member Editor",
|
||||
"PROJECT_GRANT_MEMBER_VIEWER":"Projekt Grant Member Viewer",
|
||||
"PROJECT_GRANT_OWNER":"Projekt Grant Owner",
|
||||
"PROJECT_GRANT_USER_GRANT_EDITOR":"Projekt Grant User Editor",
|
||||
"PROJECT_GRANT_USER_GRANT_VIEWER":"Projekt Grant User Grant Viewer"
|
||||
},
|
||||
"GRANTS": {
|
||||
"DELETE":"Grant löschen",
|
||||
|
@ -56,7 +56,8 @@
|
||||
"REACTIVATE":"Reactivate",
|
||||
"DEACTIVATE":"Deactivate",
|
||||
"REFRESH":"Refresh",
|
||||
"LOGIN":"Login"
|
||||
"LOGIN":"Login",
|
||||
"EDIT":"Edit"
|
||||
},
|
||||
"ERRORS": {
|
||||
"REQUIRED": "Some required fields are missing!",
|
||||
@ -265,9 +266,12 @@
|
||||
"CREATE":"Create organisation",
|
||||
"ORGDETAIL_TITLE":"Enter the name and domain of your new organisation.",
|
||||
"ORGDOMAIN_TITLE":"Organisation domain ownership verification",
|
||||
"ORGDOMAIN_VERIFICATION":"Provide your web domain and verify their ownership. You need to download a verification file and upload it at the provided URL listed below. To complete, click the button to verify.",
|
||||
"ORGDOMAIN_VERIFICATION":"Verify ownership of your domain. You need to download a verification file and upload it at the provided URL listed below or place a TXT Record at the provided Url. To complete, click the button to verify.",
|
||||
"ORGDOMAIN_VERIFICATION_SKIP":"You can skip verification for now and continue to create your organisation, but in order to use your organisation this step has to be completed!",
|
||||
"ORGDETAILUSER_TITLE":"Configure Organisation Owner",
|
||||
"ORGDOMAIN_VERIFICATION_VALIDATION_DESC":"The tokens are checked regularly to ensure you are still owner of the domain.",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_TITLE":"Request new token",
|
||||
"ORGDOMAIN_VERIFICATION_NEWTOKEN_DESC":"If you want to request a new token, select you preferred method, if you want to validate a persisting token, click on the button above.",
|
||||
"DOWNLOAD_FILE":"Download file",
|
||||
"SELECTORGTOOLTIP":"Select this organisation",
|
||||
"PRIMARYDOMAIN":"Primary Domain",
|
||||
@ -350,7 +354,8 @@
|
||||
"DOMAINADDED":"Added domain!",
|
||||
"DOMAINREMOVED":"Removed domain!",
|
||||
"MEMBERADDED":"Manager added!",
|
||||
"MEMBERREMOVED":"Manager removed!"
|
||||
"MEMBERREMOVED":"Manager removed!",
|
||||
"MEMBERCHANGED":"Manager changed!"
|
||||
}
|
||||
},
|
||||
"ORG_DETAIL": {
|
||||
@ -413,6 +418,10 @@
|
||||
"DEACTIVATE": {
|
||||
"TITLE":"Deactivate project",
|
||||
"DESCRIPTION":"Do you really want to deactivate your project?"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE":"Projekt löschen",
|
||||
"DESCRIPTION":"Wollen Sie das Project wirklich löschen?"
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -533,7 +542,8 @@
|
||||
"REACTIVATED":"Reactivated!",
|
||||
"DEACTIVATED":"Deactivated!",
|
||||
"UPDATED":"Project changed!",
|
||||
"GRANTUPDATED":"Grant changed!"
|
||||
"GRANTUPDATED":"Grant changed!",
|
||||
"DELETED":"Deleted Project!"
|
||||
}
|
||||
},
|
||||
"APP": {
|
||||
|
Loading…
x
Reference in New Issue
Block a user