mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 02:54:20 +00:00
feat(console): replace wget with curl, user login prompt, warn dialog (#387)
* remove evil wget, add warn-dialog, login hint * i18n * reference ngsw.js * update clientid * fix warn dialog * lint * Update console/src/assets/i18n/de.json Co-authored-by: Florian Forster <florian@caos.ch> * Update console/src/assets/i18n/en.json Co-authored-by: Florian Forster <florian@caos.ch> Co-authored-by: Florian Forster <florian@caos.ch>
This commit is contained in:
parent
a5bfd085a1
commit
c621fdee41
@ -10,10 +10,14 @@ rm -rf $GEN_PATH
|
|||||||
echo "Create folders"
|
echo "Create folders"
|
||||||
mkdir -p $GEN_PATH
|
mkdir -p $GEN_PATH
|
||||||
|
|
||||||
|
targetcurl () {
|
||||||
|
mkdir -p $1 && cd $1 && { curl -O $2; cd -; }
|
||||||
|
}
|
||||||
|
|
||||||
echo "Download additional protofiles"
|
echo "Download additional protofiles"
|
||||||
wget -P tmp/validate https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/v0.4.0/validate/validate.proto
|
targetcurl tmp/validate https://raw.githubusercontent.com/envoyproxy/protoc-gen-validate/v0.4.0/validate/validate.proto
|
||||||
wget -P tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/annotations.proto
|
targetcurl tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/annotations.proto
|
||||||
wget -P tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/openapiv2.proto
|
targetcurl tmp/protoc-gen-swagger/options https://raw.githubusercontent.com/grpc-ecosystem/grpc-gateway/v1.14.6/protoc-gen-swagger/options/openapiv2.proto
|
||||||
|
|
||||||
echo "Generate grpc"
|
echo "Generate grpc"
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ export let authConfig = {
|
|||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
AvatarModule,
|
AvatarModule,
|
||||||
ServiceWorkerModule.register('ngsw-config.json', { enabled: environment.production }),
|
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ThemeService,
|
ThemeService,
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<button (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
|
<button (click)="editUserProfile()" mat-stroked-button>{{'USER.EDITACCOUNT' | translate}}</button>
|
||||||
<div class="l-accounts">
|
<div class="l-accounts">
|
||||||
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
|
<mat-progress-bar *ngIf="loadingUsers" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||||
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.userName)">
|
<a class="row" *ngFor="let user of users" (click)="selectAccount(user.loginName)">
|
||||||
<app-avatar *ngIf="user && user.displayName" class="small-avatar"
|
<app-avatar *ngIf="user && user.displayName" class="small-avatar"
|
||||||
[name]="user.displayName ? user.displayName : ''" [size]="32">
|
[name]="user.displayName ? user.displayName : ''" [size]="32">
|
||||||
</app-avatar>
|
</app-avatar>
|
||||||
@ -22,7 +22,7 @@
|
|||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<mat-icon>keyboard_arrow_right</mat-icon>
|
<mat-icon>keyboard_arrow_right</mat-icon>
|
||||||
</a>
|
</a>
|
||||||
<a class="row" (click)="selectAccount()">
|
<a class="row" (click)="selectNewAccount()">
|
||||||
<div class="icon-wrapper">
|
<div class="icon-wrapper">
|
||||||
<i class="las la-user-plus"></i>
|
<i class="las la-user-plus"></i>
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,18 +45,24 @@ export class AccountsCardComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectAccount(loginHint?: string, idToken?: string): void {
|
public selectAccount(loginName?: string): void {
|
||||||
const configWithPrompt: Partial<AuthConfig> = {
|
const configWithPrompt: Partial<AuthConfig> = {
|
||||||
customQueryParams: {
|
customQueryParams: {
|
||||||
prompt: 'select_account',
|
// prompt: 'select_account',
|
||||||
} as any,
|
} as any,
|
||||||
};
|
};
|
||||||
if (loginHint) {
|
if (loginName) {
|
||||||
(configWithPrompt as any).customQueryParams['login_hint'] = loginHint;
|
(configWithPrompt as any).customQueryParams['login_hint'] = loginName;
|
||||||
}
|
}
|
||||||
if (idToken) {
|
this.authService.authenticate(configWithPrompt);
|
||||||
(configWithPrompt as any).customQueryParams['id_token_hint'] = idToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public selectNewAccount(): void {
|
||||||
|
const configWithPrompt: Partial<AuthConfig> = {
|
||||||
|
customQueryParams: {
|
||||||
|
prompt: 'login',
|
||||||
|
} as any,
|
||||||
|
};
|
||||||
this.authService.authenticate(configWithPrompt);
|
this.authService.authenticate(configWithPrompt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,11 +39,10 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
|||||||
): void {
|
): void {
|
||||||
const offset = pageIndex * pageSize;
|
const offset = pageIndex * pageSize;
|
||||||
|
|
||||||
this.loadingSubject.next(true);
|
|
||||||
|
|
||||||
switch (context) {
|
switch (context) {
|
||||||
case UserGrantContext.USER:
|
case UserGrantContext.USER:
|
||||||
if (data && data.userId) {
|
if (data && data.userId) {
|
||||||
|
this.loadingSubject.next(true);
|
||||||
const userfilter = new UserGrantSearchQuery();
|
const userfilter = new UserGrantSearchQuery();
|
||||||
userfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID);
|
userfilter.setKey(UserGrantSearchKey.USERGRANTSEARCHKEY_USER_ID);
|
||||||
userfilter.setValue(data.userId);
|
userfilter.setValue(data.userId);
|
||||||
@ -59,12 +58,14 @@ export class UserGrantsDataSource extends DataSource<UserGrant.AsObject> {
|
|||||||
break;
|
break;
|
||||||
case UserGrantContext.OWNED_PROJECT:
|
case UserGrantContext.OWNED_PROJECT:
|
||||||
if (data && data.projectId) {
|
if (data && data.projectId) {
|
||||||
|
this.loadingSubject.next(true);
|
||||||
const promise1 = this.userService.SearchProjectUserGrants(data.projectId, 10, 0, queries);
|
const promise1 = this.userService.SearchProjectUserGrants(data.projectId, 10, 0, queries);
|
||||||
this.loadResponse(promise1);
|
this.loadResponse(promise1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UserGrantContext.GRANTED_PROJECT:
|
case UserGrantContext.GRANTED_PROJECT:
|
||||||
if (data && data.grantId) {
|
if (data && data.grantId) {
|
||||||
|
this.loadingSubject.next(true);
|
||||||
const promise2 = this.userService.SearchProjectGrantUserGrants(data.grantId, 10, 0, queries);
|
const promise2 = this.userService.SearchProjectGrantUserGrants(data.grantId, 10, 0, queries);
|
||||||
this.loadResponse(promise2);
|
this.loadResponse(promise2);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,17 @@ export class UserGrantsComponent implements OnInit, AfterViewInit {
|
|||||||
deleteGrantSelection(): void {
|
deleteGrantSelection(): void {
|
||||||
this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
|
this.userService.BulkRemoveUserGrant(this.selection.selected.map(grant => grant.id)).then(() => {
|
||||||
this.toast.showInfo('Grants deleted');
|
this.toast.showInfo('Grants deleted');
|
||||||
this.loadGrantsPage();
|
const data = this.dataSource.grantsSubject.getValue();
|
||||||
|
console.log(data);
|
||||||
|
this.selection.selected.forEach((item) => {
|
||||||
|
console.log(item);
|
||||||
|
const index = data.findIndex(i => i.id === item.id);
|
||||||
|
if (index > -1) {
|
||||||
|
data.splice(index, 1);
|
||||||
|
this.dataSource.grantsSubject.next(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.selection.clear();
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
this.toast.showError(error.message);
|
this.toast.showError(error.message);
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<span class="title" mat-dialog-title>{{data.titleKey | translate}}</span>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<p class="desc"> {{data.descriptionKey | translate}}</p>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions class="action">
|
||||||
|
<button mat-button (click)="closeDialog()">
|
||||||
|
{{data.cancelKey | translate}}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button color="warn" mat-raised-button class="ok-button" (click)="closeDialogWithSuccess()">
|
||||||
|
{{data.confirmKey | translate}}
|
||||||
|
</button>
|
||||||
|
</div>
|
@ -0,0 +1,22 @@
|
|||||||
|
.title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
color: #8795a1;
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.ok-button {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { WarnDialogComponent } from './warn-dialog.component';
|
||||||
|
|
||||||
|
describe('WarnDialogComponent', () => {
|
||||||
|
let component: WarnDialogComponent;
|
||||||
|
let fixture: ComponentFixture<WarnDialogComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [WarnDialogComponent],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(WarnDialogComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
26
console/src/app/modules/warn-dialog/warn-dialog.component.ts
Normal file
26
console/src/app/modules/warn-dialog/warn-dialog.component.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-warn-dialog',
|
||||||
|
templateUrl: './warn-dialog.component.html',
|
||||||
|
styleUrls: ['./warn-dialog.component.scss'],
|
||||||
|
})
|
||||||
|
export class WarnDialogComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<WarnDialogComponent>,
|
||||||
|
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialog(): void {
|
||||||
|
this.dialogRef.close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeDialogWithSuccess(): void {
|
||||||
|
this.dialogRef.close(true);
|
||||||
|
}
|
||||||
|
}
|
21
console/src/app/modules/warn-dialog/warn-dialog.module.ts
Normal file
21
console/src/app/modules/warn-dialog/warn-dialog.module.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { WarnDialogComponent } from './warn-dialog.component';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [WarnDialogComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule,
|
||||||
|
MatButtonModule,
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
WarnDialogComponent,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class WarnDialogModule { }
|
@ -1,10 +1,12 @@
|
|||||||
import { SelectionModel } from '@angular/cdk/collections';
|
import { SelectionModel } from '@angular/cdk/collections';
|
||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { MatButtonToggleChange } from '@angular/material/button-toggle';
|
import { MatButtonToggleChange } from '@angular/material/button-toggle';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatTableDataSource } from '@angular/material/table';
|
import { MatTableDataSource } from '@angular/material/table';
|
||||||
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 { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||||
import { Org, OrgDomainView, OrgMember, OrgMemberSearchResponse, OrgState } from 'src/app/proto/generated/management_pb';
|
import { Org, OrgDomainView, OrgMember, OrgMemberSearchResponse, OrgState } from 'src/app/proto/generated/management_pb';
|
||||||
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';
|
||||||
@ -32,6 +34,7 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
|||||||
public newDomain: string = '';
|
public newDomain: string = '';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private dialog: MatDialog,
|
||||||
public translate: TranslateService,
|
public translate: TranslateService,
|
||||||
private orgService: OrgService,
|
private orgService: OrgService,
|
||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
@ -82,6 +85,18 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public removeDomain(domain: string): void {
|
public removeDomain(domain: string): void {
|
||||||
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.DELETE',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'ORG.DOMAINS.DELETE.TITLE',
|
||||||
|
descriptionKey: 'ORG.DOMAINS.DELETE.DESCRIPTION',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(resp => {
|
||||||
|
if (resp) {
|
||||||
this.orgService.RemoveMyOrgDomain(domain).then(() => {
|
this.orgService.RemoveMyOrgDomain(domain).then(() => {
|
||||||
this.toast.showInfo('Removed');
|
this.toast.showInfo('Removed');
|
||||||
const index = this.domains.findIndex(d => d.domain === domain);
|
const index = this.domains.findIndex(d => d.domain === domain);
|
||||||
@ -92,4 +107,6 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
|||||||
this.toast.showError(error.message);
|
this.toast.showError(error.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ 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';
|
||||||
|
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';
|
||||||
@ -43,6 +44,8 @@ import { PolicyGridComponent } from './policy-grid/policy-grid.component';
|
|||||||
MetaLayoutModule,
|
MetaLayoutModule,
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
|
MatDialogModule,
|
||||||
|
WarnDialogModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
ChangesModule,
|
ChangesModule,
|
||||||
TranslateModule.forChild({
|
TranslateModule.forChild({
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
"mgmtServiceUrl": "https://api.zitadel.dev",
|
"mgmtServiceUrl": "https://api.zitadel.dev",
|
||||||
"adminServiceUrl":"https://api.zitadel.dev",
|
"adminServiceUrl":"https://api.zitadel.dev",
|
||||||
"issuer": "https://issuer.zitadel.dev",
|
"issuer": "https://issuer.zitadel.dev",
|
||||||
"clientid": "61542534056307917@zitadel"
|
"clientid": "63146698922323188@zitadel"
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,10 @@
|
|||||||
"PERSONAL_INFO": "Persönliche Informationen",
|
"PERSONAL_INFO": "Persönliche Informationen",
|
||||||
"IAM":"Administration",
|
"IAM":"Administration",
|
||||||
"ORGANIZATION": "Organisation",
|
"ORGANIZATION": "Organisation",
|
||||||
"PROJECTSSECTION":"Projekte",
|
"PROJECTSSECTION":"Projektsektion",
|
||||||
"PROJECT": "Projekte",
|
"PROJECT": "Projekte",
|
||||||
"GRANTEDPROJECT":"Berechtigte Projekte",
|
"GRANTEDPROJECT":"Berechtigte Projekte",
|
||||||
"USERSECTION":"Benutzer",
|
"USERSECTION":"Benutzersektion",
|
||||||
"USER": "Benutzer",
|
"USER": "Benutzer",
|
||||||
"LOGOUT": "alle Benutzer abmelden",
|
"LOGOUT": "alle Benutzer abmelden",
|
||||||
"NEWORG":"Neue Organisation",
|
"NEWORG":"Neue Organisation",
|
||||||
@ -230,7 +230,11 @@
|
|||||||
},
|
},
|
||||||
"DOMAINS": {
|
"DOMAINS": {
|
||||||
"TITLE":"Domains",
|
"TITLE":"Domains",
|
||||||
"DESCRIPTION":"Konfigurieren Sie Ihre Domains mit denen sich Ihre Nutzer einloggen können."
|
"DESCRIPTION":"Konfigurieren Sie die Domains mit denen sich Ihre Nutzer einloggen können.",
|
||||||
|
"DELETE": {
|
||||||
|
"TITLE":"Domain löschen?",
|
||||||
|
"DESCRIPTION":"Sie sind im Begriff eine Domain aus Ihrer Organisation zu löschen. Ihre Benutzer können diese nach dem Löschen nicht mehr für den Login nutzen."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"STATE": {
|
"STATE": {
|
||||||
"0": "nicht definiert",
|
"0": "nicht definiert",
|
||||||
|
@ -22,10 +22,10 @@
|
|||||||
"PERSONAL_INFO": "Personal Information",
|
"PERSONAL_INFO": "Personal Information",
|
||||||
"IAM":"Administration",
|
"IAM":"Administration",
|
||||||
"ORGANIZATION": "Organization",
|
"ORGANIZATION": "Organization",
|
||||||
"PROJECTSSECTION":"Projects",
|
"PROJECTSSECTION":"projects section",
|
||||||
"PROJECT": "Projects",
|
"PROJECT": "Projects",
|
||||||
"GRANTEDPROJECT":"Granted Projects",
|
"GRANTEDPROJECT":"Granted Projects",
|
||||||
"USERSECTION":"Benutzer",
|
"USERSECTION":"user section",
|
||||||
"USER": "Users",
|
"USER": "Users",
|
||||||
"LOGOUT": "Logout all users",
|
"LOGOUT": "Logout all users",
|
||||||
"NEWORG":"New Organization",
|
"NEWORG":"New Organization",
|
||||||
@ -231,7 +231,11 @@
|
|||||||
},
|
},
|
||||||
"DOMAINS": {
|
"DOMAINS": {
|
||||||
"TITLE":"Domains",
|
"TITLE":"Domains",
|
||||||
"DESCRIPTION":"Configure your domains on which your users will be registered."
|
"DESCRIPTION":"Configure your domains on which your users will be registered.",
|
||||||
|
"DELETE": {
|
||||||
|
"TITLE":"Delete domain?",
|
||||||
|
"DESCRIPTION":"You are about to delete one of your domains. Note that your users can no longer use this domain for their logins."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"STATE": {
|
"STATE": {
|
||||||
"0": "not defined",
|
"0": "not defined",
|
||||||
|
Loading…
Reference in New Issue
Block a user