feat(console): show instance detail (#4032)

feat: instance detail

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Max Peintner 2022-07-25 14:26:36 +02:00 committed by GitHub
parent 6bb3220186
commit 7c491381de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 171 additions and 71 deletions

View File

@ -48,6 +48,66 @@
</div>
</div>
<div class="info-row" *ngIf="instance">
<div class="info-wrapper">
<p class="info-row-title">{{ 'IAM.PAGES.STATE' | translate }}</p>
<p
*ngIf="instance && instance.state !== undefined"
class="state"
[ngClass]="{
active: instance.state === State.INSTANCE_STATE_RUNNING,
inactive: instance.state === State.INSTANCE_STATE_STOPPED || instance.state === State.INSTANCE_STATE_STOPPING
}"
>
{{ 'IAM.STATE.' + instance.state | translate }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'RESOURCEID' | translate }}</p>
<p *ngIf="instance && instance.id" class="info-row-desc">{{ instance.id }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'NAME' | translate }}</p>
<p *ngIf="instance && instance.name" class="info-row-desc">{{ instance.name }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'VERSION' | translate }}</p>
<p *ngIf="instance && instance.version" class="info-row-desc">{{ instance.version }}</p>
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'IAM.PAGES.DOMAINLIST' | translate }}</p>
<div class="copy-row" *ngFor="let domain of instance?.domainsList">
<button
[disabled]="copied === domain.domain"
[matTooltip]="(copied !== domain.domain ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="domain.domain"
(copiedValue)="copied = $event"
>
{{ domain.domain }}
</button>
</div>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
<p *ngIf="instance && instance.details && instance.details.creationDate" class="info-row-desc">
{{ instance.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
<p *ngIf="instance && instance.details && instance.details.changeDate" class="info-row-desc">
{{ instance.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
</p>
</div>
</div>
<div class="info-row" *ngIf="org">
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.STATE' | translate }}</p>

View File

@ -1,6 +1,7 @@
import { Component, Input } from '@angular/core';
import { App, AppState } from 'src/app/proto/generated/zitadel/app_pb';
import { IDP, IDPState } from 'src/app/proto/generated/zitadel/idp_pb';
import { InstanceDetail, State } from 'src/app/proto/generated/zitadel/instance_pb';
import { Org, OrgState } from 'src/app/proto/generated/zitadel/org_pb';
import { GrantedProject, Project, ProjectGrantState, ProjectState } from 'src/app/proto/generated/zitadel/project_pb';
import { User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
@ -13,12 +14,14 @@ import { User, UserState } from 'src/app/proto/generated/zitadel/user_pb';
export class InfoRowComponent {
@Input() public user!: User.AsObject;
@Input() public org!: Org.AsObject;
@Input() public instance!: InstanceDetail.AsObject;
@Input() public app!: App.AsObject;
@Input() public idp!: IDP.AsObject;
@Input() public project!: Project.AsObject;
@Input() public grantedProject!: GrantedProject.AsObject;
public UserState: any = UserState;
public State: any = State;
public OrgState: any = OrgState;
public AppState: any = AppState;
public IDPState: any = IDPState;

View File

@ -1,14 +1,14 @@
<div class="iam-top">
<div class="max-width-container">
<div class="iam-top-row">
<div>
<div class="iam-title-row">
<h1 class="iam-title">{{ 'IAM.TITLE' | translate }}</h1>
</div>
<p class="iam-sub cnsl-secondary-text">{{ 'IAM.DESCRIPTION' | translate }}</p>
</div>
<span class="fill-space"></span>
<cnsl-top-view
[hasBackButton]="false"
title="{{ 'IAM.TITLE' | translate }}"
sub="{{ 'IAM.DESCRIPTION' | translate }}"
[isActive]="instance?.state === State.STATE_RUNNING"
[isInactive]="instance?.state === State.STATE_STOPPED || instance?.state === State.STATE_STOPPING"
[hasContributors]="true"
stateTooltip="{{ 'INSTANCE.STATE.' + instance?.state | translate }}"
>
<cnsl-contributors
topContributors
[totalResult]="totalMemberResult"
[loading]="loading$ | async"
[membersSubject]="membersSubject"
@ -20,13 +20,14 @@
[disabled]="(['iam.member.write'] | hasRole | async) === false"
>
</cnsl-contributors>
</div>
</div>
</div>
<div class="max-width-container">
<h2 class="org-table-title">{{ 'ORG.LIST.TITLE' | translate }}</h2>
<p class="org-table-desc cnsl-secondary-text">{{ 'ORG.LIST.DESCRIPTION' | translate }}</p>
<cnsl-info-row topContent *ngIf="instance" [instance]="instance"></cnsl-info-row>
</cnsl-top-view>
<div class="max-width-container">
<h2 class="instance-table-title">{{ 'ORG.LIST.TITLE' | translate }}</h2>
<p class="instance-table-desc cnsl-secondary-text">{{ 'ORG.LIST.DESCRIPTION' | translate }}</p>
<cnsl-org-table></cnsl-org-table>

View File

@ -4,55 +4,15 @@
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background);
.iam-top {
border-bottom: 1px solid map-get($foreground, divider);
margin: 0 -2rem;
padding: 2rem 2rem 1rem 2rem;
background: map-get($background, metadata-section);
@media only screen and (max-width: 500px) {
margin: 0 -1rem;
}
.iam-top-row {
display: flex;
align-items: center;
padding-bottom: 1rem;
.iam-title-row {
display: flex;
align-items: center;
.iam-title {
margin: 0;
margin-right: 0.5rem;
}
}
.iam-sub {
margin: 1rem 0 0 0;
font-size: 14px;
}
.iam-top-desc {
font-size: 14px;
}
.fill-space {
flex: 1;
}
}
}
}
.org-table-title {
.instance-table-title {
font-size: 1.2rem;
letter-spacing: 0.05em;
text-transform: uppercase;
margin-top: 2rem;
}
.org-table-desc {
.instance-table-desc {
font-size: 14px;
}

View File

@ -5,6 +5,7 @@ import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { InstanceDetail, State } from 'src/app/proto/generated/zitadel/instance_pb';
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
@ -17,12 +18,13 @@ import { ToastService } from 'src/app/services/toast.service';
styleUrls: ['./instance.component.scss'],
})
export class InstanceComponent {
public instance!: InstanceDetail.AsObject;
public PolicyComponentServiceType: any = PolicyComponentServiceType;
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
public totalMemberResult: number = 0;
public membersSubject: BehaviorSubject<Member.AsObject[]> = new BehaviorSubject<Member.AsObject[]>([]);
public State: any = State;
constructor(
public adminService: AdminService,
private dialog: MatDialog,
@ -39,6 +41,17 @@ export class InstanceComponent {
});
breadcrumbService.setBreadcrumb([instanceBread]);
this.adminService
.getMyInstance()
.then((instanceResp) => {
if (instanceResp.instance) {
this.instance = instanceResp.instance;
}
})
.catch((error) => {
this.toast.showError(error);
});
}
public loadMembers(): void {

View File

@ -16,12 +16,14 @@ import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { CardModule } from 'src/app/modules/card/card.module';
import { ChangesModule } from 'src/app/modules/changes/changes.module';
import { ContributorsModule } from 'src/app/modules/contributors/contributors.module';
import { InfoRowModule } from 'src/app/modules/info-row/info-row.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { OrgTableModule } from 'src/app/modules/org-table/org-table.module';
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
import { SettingsGridModule } from 'src/app/modules/settings-grid/settings-grid.module';
import { SharedModule } from 'src/app/modules/shared/shared.module';
import { TopViewModule } from 'src/app/modules/top-view/top-view.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
@ -43,7 +45,9 @@ import { InstanceComponent } from './instance.component';
MatCheckboxModule,
MetaLayoutModule,
MatIconModule,
TopViewModule,
MatTableModule,
InfoRowModule,
InputModule,
MatSortModule,
MatTooltipModule,

View File

@ -73,6 +73,8 @@ import {
GetLoginPolicyResponse,
GetLogNotificationProviderRequest,
GetLogNotificationProviderResponse,
GetMyInstanceRequest,
GetMyInstanceResponse,
GetOIDCSettingsRequest,
GetOIDCSettingsResponse,
GetPasswordAgePolicyRequest,
@ -404,6 +406,11 @@ export class AdminService {
return this.grpcService.admin.removeFailedEvent(req, null).then((resp) => resp.toObject());
}
public getMyInstance(): Promise<GetMyInstanceResponse.AsObject> {
const req = new GetMyInstanceRequest();
return this.grpcService.admin.getMyInstance(req, null).then((resp) => resp.toObject());
}
public getPrivacyPolicy(): Promise<GetPrivacyPolicyResponse.AsObject> {
const req = new GetPrivacyPolicyRequest();
return this.grpcService.admin.getPrivacyPolicy(req, null).then((resp) => resp.toObject());

View File

@ -194,6 +194,8 @@
}
},
"RESOURCEID": "Ressourcen-ID",
"NAME": "Name",
"VERSION": "Version",
"TABLE": {
"NOROWS": "Keine Daten"
},
@ -707,6 +709,17 @@
"TITLE": "Manager",
"DESCRIPTION": "Diese Manager können instanzweite Einstellungen vornehmen."
},
"PAGES": {
"STATE": "Status",
"DOMAINLIST": "Domains"
},
"STATE": {
"0": "Unspezifisch",
"1": "Wird erstellt",
"2": "Aktiv",
"3": "Wird gestoppt",
"4": "Gestoppt"
},
"VIEWS": {
"TITLE": "Views",
"DESCRIPTION": "Diese Ansicht zeigt die Anzeigen von ZITADEL. Diese können bei Bedarf zurückgesetzt werden.",

View File

@ -194,6 +194,8 @@
}
},
"RESOURCEID": "Resource Id",
"NAME": "Name",
"VERSION": "Version",
"TABLE": {
"NOROWS": "No data"
},
@ -707,6 +709,17 @@
"TITLE": "Managers",
"DESCRIPTION": "These Managers are allowed to make changes in your instance."
},
"PAGES": {
"STATE": "Status",
"DOMAINLIST": "Domains"
},
"STATE": {
"0": "Unspecified",
"1": "Creating",
"2": "Running",
"3": "Stopping",
"4": "Stopped"
},
"VIEWS": {
"TITLE": "Views",
"DESCRIPTION": "This card shows your ZITADEL views.",

View File

@ -194,6 +194,8 @@
}
},
"RESOURCEID": "Id de la ressource",
"NAME": "Name",
"VERSION": "Version",
"TABLE": {
"NOROWS": "Pas de données"
},
@ -707,6 +709,17 @@
"TITLE": "Managers",
"DESCRIPTION": "Ces gestionnaires sont autorisés à effectuer des changements dans votre instance."
},
"PAGES": {
"STATE": "Statut",
"DOMAINLIST": "Domaines"
},
"STATE": {
"0": "Inconnu",
"1": "Créer",
"2": "Actif",
"3": "Arrêt",
"4": "Arrêté"
},
"VIEWS": {
"TITLE": "Vues",
"DESCRIPTION": "Cette carte montre vos vues ZITADEL.",

View File

@ -194,6 +194,8 @@
}
},
"RESOURCEID": "Resource ID",
"NAME": "Name",
"VERSION": "Versione",
"TABLE": {
"NOROWS": "Nessun dato"
},
@ -707,6 +709,17 @@
"TITLE": "Manager",
"DESCRIPTION": "Questi manager possono modificare le impostazioni dell'istanza."
},
"PAGES": {
"STATE": "Stato",
"DOMAINLIST": "Domini"
},
"STATE": {
"0": "Non specifico",
"1": "In fase di creazione",
"2": "Attiva",
"3": "In fase di arresto",
"4": "Arrestata"
},
"VIEWS": {
"TITLE": "Views",
"DESCRIPTION": "Questa carta mostra i tuoi view ZITADEL.",