mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-08 19:17:40 +00:00
feat(console): onboarding flow (#5225)
Implements an onboarding UI for users
This commit is contained in:
parent
a7cc907ab7
commit
f8ddc844f8
@ -165,6 +165,11 @@ export class AppComponent implements OnDestroy {
|
|||||||
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/shield-alert.svg'),
|
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/shield-alert.svg'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.matIconRegistry.addSvgIcon(
|
||||||
|
'mdi_shield_check',
|
||||||
|
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/shield-check.svg'),
|
||||||
|
);
|
||||||
|
|
||||||
this.matIconRegistry.addSvgIcon(
|
this.matIconRegistry.addSvgIcon(
|
||||||
'mdi_arrow_expand',
|
'mdi_arrow_expand',
|
||||||
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/arrow-expand.svg'),
|
this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/arrow-expand.svg'),
|
||||||
|
@ -212,7 +212,46 @@
|
|||||||
|
|
||||||
<ng-template #shortcutKeyRef>
|
<ng-template #shortcutKeyRef>
|
||||||
<ng-container *ngIf="(isHandset$ | async) === false">
|
<ng-container *ngIf="(isHandset$ | async) === false">
|
||||||
|
<ng-template cnslHasRole [hasRole]="['iam.read']">
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
|
<ng-container *ngIf="!adminService.hideOnboarding && (adminService.progressAllDone | async) === false">
|
||||||
|
<button
|
||||||
|
cdkOverlayOrigin
|
||||||
|
#trigger="cdkOverlayOrigin"
|
||||||
|
matRipple
|
||||||
|
class="progress-bar"
|
||||||
|
(click)="showInstanceProgress = !showInstanceProgress"
|
||||||
|
>
|
||||||
|
<mat-progress-bar
|
||||||
|
class="progress"
|
||||||
|
mode="determinate"
|
||||||
|
[value]="adminService.progressPercentage | async"
|
||||||
|
></mat-progress-bar>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<ng-template
|
||||||
|
cdkConnectedOverlay
|
||||||
|
[cdkConnectedOverlayOrigin]="trigger"
|
||||||
|
[flexibleDimensions]="true"
|
||||||
|
[lockPosition]="true"
|
||||||
|
[cdkConnectedOverlayOffsetY]="10"
|
||||||
|
[cdkConnectedOverlayHasBackdrop]="true"
|
||||||
|
[cdkConnectedOverlayPositions]="positions"
|
||||||
|
cdkConnectedOverlayBackdropClass="transparent-backdrop"
|
||||||
|
[cdkConnectedOverlayOpen]="showInstanceProgress"
|
||||||
|
(backdropClick)="showInstanceProgress = false"
|
||||||
|
(detach)="showInstanceProgress = false"
|
||||||
|
>
|
||||||
|
<cnsl-onboarding-card
|
||||||
|
(dismissedCard)="dismissOnboarding()"
|
||||||
|
class="onboarding_card"
|
||||||
|
*ngIf="org && showInstanceProgress"
|
||||||
|
>
|
||||||
|
</cnsl-onboarding-card>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<div (click)="openHelp()" class="nav-shortcut-action-key" matTooltip="{{ 'MENU.OPENSHORTCUTSTOOLTIP' | translate }}">
|
<div (click)="openHelp()" class="nav-shortcut-action-key" matTooltip="{{ 'MENU.OPENSHORTCUTSTOOLTIP' | translate }}">
|
||||||
<div class="nav-key-overlay"></div>
|
<div class="nav-key-overlay"></div>
|
||||||
<span>?</span>
|
<span>?</span>
|
||||||
|
@ -181,8 +181,39 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
width: 100px;
|
||||||
|
min-width: 50px;
|
||||||
|
background: if($is-dark-theme, #ffffff20, #00000010);
|
||||||
|
border: none;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 50vw;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: if($is-dark-theme, #ffffff, $primary-color);
|
||||||
|
transition: background ease 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
border-radius: 50vw;
|
||||||
|
height: 8px;
|
||||||
|
|
||||||
|
.mat-progress-bar-buffer {
|
||||||
|
background-color: if($is-dark-theme, rgb(69, 91, 84), #cccccc) !important;
|
||||||
|
}
|
||||||
|
.mat-progress-bar-background {
|
||||||
|
fill: if($is-dark-theme, rgb(69, 91, 84), #cccccc) !important;
|
||||||
|
}
|
||||||
|
.mat-progress-bar-fill:after {
|
||||||
|
background-color: var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.nav-shortcut-action-key {
|
.nav-shortcut-action-key {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -213,3 +244,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.onboarding_card {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-shadow: 0 0 15px 0 rgb(0 0 0 / 10%);
|
||||||
|
border: 1px solid rgba(#8795a1, 0.2);
|
||||||
|
}
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { animate, keyframes, style, transition, trigger } from '@angular/animations';
|
import { animate, keyframes, style, transition, trigger } from '@angular/animations';
|
||||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
|
import { ConnectedPosition, ConnectionPositionPair } from '@angular/cdk/overlay';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { UntypedFormControl } from '@angular/forms';
|
import { UntypedFormControl } from '@angular/forms';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { BehaviorSubject, combineLatest, forkJoin, map, merge, Observable, Subject, switchMap, take } from 'rxjs';
|
import { BehaviorSubject, combineLatest, map, Observable, Subject, take, tap } from 'rxjs';
|
||||||
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
|
||||||
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
import { User } from 'src/app/proto/generated/zitadel/user_pb';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
import { AuthenticationService } from 'src/app/services/authentication.service';
|
import { AuthenticationService } from 'src/app/services/authentication.service';
|
||||||
import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
import { KeyboardShortcutsService } from 'src/app/services/keyboard-shortcuts/keyboard-shortcuts.service';
|
import { KeyboardShortcutsService } from 'src/app/services/keyboard-shortcuts/keyboard-shortcuts.service';
|
||||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||||
|
import { StorageLocation, StorageService } from 'src/app/services/storage.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cnsl-nav',
|
selector: 'cnsl-nav',
|
||||||
@ -73,6 +76,7 @@ export class NavComponent implements OnDestroy {
|
|||||||
|
|
||||||
@Input() public isDarkTheme: boolean = true;
|
@Input() public isDarkTheme: boolean = true;
|
||||||
@Input() public user!: User.AsObject;
|
@Input() public user!: User.AsObject;
|
||||||
|
public showInstanceProgress: boolean = false;
|
||||||
public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
|
public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
|
||||||
map((result) => {
|
map((result) => {
|
||||||
return result.matches;
|
return result.matches;
|
||||||
@ -83,14 +87,19 @@ export class NavComponent implements OnDestroy {
|
|||||||
public filterControl: UntypedFormControl = new UntypedFormControl('');
|
public filterControl: UntypedFormControl = new UntypedFormControl('');
|
||||||
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
||||||
public showAccount: boolean = false;
|
public showAccount: boolean = false;
|
||||||
public hideAdminWarn: boolean = true;
|
|
||||||
private destroy$: Subject<void> = new Subject();
|
private destroy$: Subject<void> = new Subject();
|
||||||
|
|
||||||
public BreadcrumbType: any = BreadcrumbType;
|
public BreadcrumbType: any = BreadcrumbType;
|
||||||
public customerPortalLink: string = '';
|
public customerPortalLink: string = '';
|
||||||
|
|
||||||
|
public positions: ConnectedPosition[] = [
|
||||||
|
new ConnectionPositionPair({ originX: 'start', originY: 'bottom' }, { overlayX: 'start', overlayY: 'top' }, 0, 10),
|
||||||
|
new ConnectionPositionPair({ originX: 'end', originY: 'bottom' }, { overlayX: 'end', overlayY: 'top' }, 0, 10),
|
||||||
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public authService: GrpcAuthService,
|
public authService: GrpcAuthService,
|
||||||
|
public adminService: AdminService,
|
||||||
public authenticationService: AuthenticationService,
|
public authenticationService: AuthenticationService,
|
||||||
public breadcrumbService: BreadcrumbService,
|
public breadcrumbService: BreadcrumbService,
|
||||||
public mgmtService: ManagementService,
|
public mgmtService: ManagementService,
|
||||||
@ -98,8 +107,8 @@ export class NavComponent implements OnDestroy {
|
|||||||
private breakpointObserver: BreakpointObserver,
|
private breakpointObserver: BreakpointObserver,
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
private shortcutService: KeyboardShortcutsService,
|
private shortcutService: KeyboardShortcutsService,
|
||||||
|
private storageService: StorageService,
|
||||||
) {
|
) {
|
||||||
this.hideAdminWarn = localStorage.getItem('hideAdministratorWarning') === 'true' ? true : false;
|
|
||||||
this.loadEnvironment();
|
this.loadEnvironment();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,16 +123,18 @@ export class NavComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public toggleAdminHide(): void {
|
|
||||||
this.hideAdminWarn = !this.hideAdminWarn;
|
|
||||||
localStorage.setItem('hideAdministratorWarning', this.hideAdminWarn.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.destroy$.next();
|
this.destroy$.next();
|
||||||
this.destroy$.complete();
|
this.destroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public dismissOnboarding(): void {
|
||||||
|
this.showInstanceProgress = false;
|
||||||
|
this.adminService.hideOnboarding = true;
|
||||||
|
this.storageService.setItem('onboarding-dismissed', 'true', StorageLocation.local);
|
||||||
|
this.adminService.progressAllDone.next(true);
|
||||||
|
}
|
||||||
|
|
||||||
public get isUserLinkActive(): boolean {
|
public get isUserLinkActive(): boolean {
|
||||||
const url = this.router.url;
|
const url = this.router.url;
|
||||||
return url.substring(0, 6) === '/users';
|
return url.substring(0, 6) === '/users';
|
||||||
|
@ -12,12 +12,15 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
|
||||||
|
import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar';
|
||||||
|
import OnboardingCardModule from '../onboarding-card/onboarding-card.module';
|
||||||
import { NavComponent } from './nav.component';
|
import { NavComponent } from './nav.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [NavComponent],
|
declarations: [NavComponent],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
OnboardingCardModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
@ -25,6 +28,7 @@ import { NavComponent } from './nav.component';
|
|||||||
RouterModule,
|
RouterModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
HasRolePipeModule,
|
HasRolePipeModule,
|
||||||
|
MatLegacyProgressBarModule,
|
||||||
HasRoleModule,
|
HasRoleModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
<div class="onboarding-card" cdkTrapFocus>
|
||||||
|
<div class="spinner-w">
|
||||||
|
<mat-spinner diameter="20" *ngIf="loading$ | async" color="accent"> </mat-spinner>
|
||||||
|
</div>
|
||||||
|
<div class="progress-header">
|
||||||
|
<h2>{{ 'ONBOARDING.CARD.TITLE' | translate }}</h2>
|
||||||
|
<p class="cnsl-secondary-text">
|
||||||
|
{{ 'ONBOARDING.CARD.DESCRIPTION' | translate }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="prog-desc cnsl-secondary-text">
|
||||||
|
{{ adminService.progressDone | async }} / {{ adminService.progressTotal | async }}
|
||||||
|
{{ 'ONBOARDING.COMPLETED' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="actions-list">
|
||||||
|
<ng-container *ngFor="let action of actions | async">
|
||||||
|
<a
|
||||||
|
[routerLink]="action[1].link"
|
||||||
|
[queryParams]="{ id: action[1].fragment }"
|
||||||
|
class="action-element"
|
||||||
|
[ngClass]="{ done: action[1].event !== undefined }"
|
||||||
|
>
|
||||||
|
<div class="state-circle">
|
||||||
|
<mat-icon *ngIf="action[1]?.event !== undefined" class="success-icon" matTooltip="{{ action[1].event | event }}"
|
||||||
|
>check_circle</mat-icon
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="name">{{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }}</span>
|
||||||
|
<mat-icon class="arrow-right">keyboard_arrow_right</mat-icon>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<div class="no-thanks-wrapper">
|
||||||
|
<a class="no-thanks-btn" (click)="dismiss()">{{ 'ONBOARDING.DISMISS' | translate }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
@ -0,0 +1,144 @@
|
|||||||
|
@mixin onboarding-card-theme($theme) {
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
$warn: map-get($theme, warn);
|
||||||
|
$background: map-get($theme, background);
|
||||||
|
$accent: map-get($theme, accent);
|
||||||
|
$primary-color: mat.get-color-from-palette($primary, 500);
|
||||||
|
|
||||||
|
$warn-color: mat.get-color-from-palette($warn, 500);
|
||||||
|
$accent-color: mat.get-color-from-palette($accent, 500);
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
$is-dark-theme: map-get($theme, is-dark);
|
||||||
|
$back: map-get($background, background);
|
||||||
|
|
||||||
|
.onboarding-card {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
z-index: 200;
|
||||||
|
position: relative;
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 280px;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
color: map-get($foreground, text);
|
||||||
|
background: map-get($background, cards);
|
||||||
|
|
||||||
|
.spinner-w {
|
||||||
|
top: 1rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-header {
|
||||||
|
padding: 1rem 1rem 0 1rem;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prog-desc {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 0 1rem 0;
|
||||||
|
|
||||||
|
.action-element {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.25rem 0.5rem 0.25rem 1rem;
|
||||||
|
text-decoration: none;
|
||||||
|
color: map-get($foreground, text);
|
||||||
|
|
||||||
|
.state-circle {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
border-radius: 50vw;
|
||||||
|
margin-right: 1rem;
|
||||||
|
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
|
||||||
|
box-shadow: 0 0 3px #0000001a;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
width: 1.2rem;
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warn-icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
width: 1.2rem;
|
||||||
|
color: map-get($background, alert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 14px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-space {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-right {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.done {
|
||||||
|
.state-circle i {
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
text-decoration: line-through;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #00000010;
|
||||||
|
|
||||||
|
.arrow-right {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-thanks-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 0 1rem 0.5rem 1rem;
|
||||||
|
|
||||||
|
.no-thanks-btn {
|
||||||
|
font-style: italic;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OnboardingCardComponent } from './onboarding-card.component';
|
||||||
|
|
||||||
|
describe('OnboardingCardComponent', () => {
|
||||||
|
let component: OnboardingCardComponent;
|
||||||
|
let fixture: ComponentFixture<OnboardingCardComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [OnboardingCardComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(OnboardingCardComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-onboarding-card',
|
||||||
|
templateUrl: './onboarding-card.component.html',
|
||||||
|
styleUrls: ['./onboarding-card.component.scss'],
|
||||||
|
})
|
||||||
|
export class OnboardingCardComponent implements OnInit {
|
||||||
|
public percentageChanged: EventEmitter<number> = new EventEmitter<number>();
|
||||||
|
public loading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
||||||
|
public actions = this.adminService.progressEvents;
|
||||||
|
@Output() public dismissedCard: EventEmitter<void> = new EventEmitter();
|
||||||
|
|
||||||
|
constructor(public adminService: AdminService) {}
|
||||||
|
|
||||||
|
public dismiss(): void {
|
||||||
|
this.dismissedCard.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.adminService.loadEvents.next(ONBOARDING_EVENTS);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
||||||
|
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
|
||||||
|
import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
|
import { ShortcutsModule } from 'src/app/modules/shortcuts/shortcuts.module';
|
||||||
|
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { EventPipeModule } from 'src/app/pipes/event-pipe/event-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';
|
||||||
|
import { OnboardingCardComponent } from './onboarding-card.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [OnboardingCardComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatIconModule,
|
||||||
|
TranslateModule,
|
||||||
|
RouterModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
EventPipeModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
],
|
||||||
|
exports: [OnboardingCardComponent],
|
||||||
|
})
|
||||||
|
export default class OnboardingCardModule {}
|
57
console/src/app/modules/onboarding/onboarding.component.html
Normal file
57
console/src/app/modules/onboarding/onboarding.component.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<div class="onboarding-header">
|
||||||
|
<h1 class="title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
|
||||||
|
|
||||||
|
<p class="desc cnsl-secondary-text">{{ 'ONBOARDING.DESCRIPTION' | translate }}</p>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!adminService.hideOnboarding && (adminService.progressAllDone | async) === false">
|
||||||
|
<div class="onboarding-progress-bar-wrapper">
|
||||||
|
<mat-progress-bar
|
||||||
|
class="progress"
|
||||||
|
mode="determinate"
|
||||||
|
[value]="adminService.progressPercentage | async"
|
||||||
|
></mat-progress-bar>
|
||||||
|
|
||||||
|
<div class="prog-desc cnsl-secondary-text">
|
||||||
|
{{ adminService.progressDone | async }} / {{ adminService.progressTotal | async }}
|
||||||
|
{{ 'ONBOARDING.COMPLETED' | translate }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<mat-spinner diameter="20" *ngIf="adminService.onboardingLoading | async"></mat-spinner>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="action-card-wrapper"
|
||||||
|
[ngClass]="{ alldone: adminService.hideOnboarding || (adminService.progressAllDone | async) }"
|
||||||
|
>
|
||||||
|
<ng-container *ngFor="let action of actions | async">
|
||||||
|
<a
|
||||||
|
[routerLink]="action[1].link"
|
||||||
|
[queryParams]="{ id: action[1].fragment }"
|
||||||
|
class="action-card card"
|
||||||
|
[ngClass]="{ done: action[1].event !== undefined }"
|
||||||
|
>
|
||||||
|
<div class="state-circle">
|
||||||
|
<mat-icon *ngIf="action[1]?.event !== undefined" matTooltip="{{ action[1].event | event }}" class="success-icon"
|
||||||
|
>check_circle</mat-icon
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-content">
|
||||||
|
<div class="text-block">
|
||||||
|
<span class="name">{{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }}</span>
|
||||||
|
<span class="cnsl-secondary-text description">{{
|
||||||
|
'ONBOARDING.EVENTS.' + action[0] + '.description' | translate
|
||||||
|
}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<div class="action-row">
|
||||||
|
<span>{{ 'ACTIONS.SETUP' | translate }}</span>
|
||||||
|
<mat-icon class="icon">keyboard_arrow_right</mat-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
174
console/src/app/modules/onboarding/onboarding.component.scss
Normal file
174
console/src/app/modules/onboarding/onboarding.component.scss
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
@use '@angular/material' as mat;
|
||||||
|
|
||||||
|
@mixin onboarding-theme($theme) {
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
$warn: map-get($theme, warn);
|
||||||
|
$background: map-get($theme, background);
|
||||||
|
$accent: map-get($theme, accent);
|
||||||
|
$primary-color: mat.get-color-from-palette($primary, 500);
|
||||||
|
|
||||||
|
$warn-color: mat.get-color-from-palette($warn, 500);
|
||||||
|
$accent-color: mat.get-color-from-palette($accent, 500);
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
$is-dark-theme: map-get($theme, is-dark);
|
||||||
|
$back: map-get($background, background);
|
||||||
|
|
||||||
|
$list-background-color: mat.get-color-from-palette($background, 300);
|
||||||
|
$card-background-color: mat.get-color-from-palette($background, cards);
|
||||||
|
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
|
||||||
|
$border-selected-color: if($is-dark-theme, #fff, #000);
|
||||||
|
|
||||||
|
.onboarding-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.onboarding-progress-bar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem 0;
|
||||||
|
min-height: 20px;
|
||||||
|
|
||||||
|
.progress {
|
||||||
|
border-radius: 50vw;
|
||||||
|
height: 8px;
|
||||||
|
max-width: 300px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
|
||||||
|
.mat-progress-bar-buffer {
|
||||||
|
background-color: if($is-dark-theme, rgb(69, 91, 84), #cccccc) !important;
|
||||||
|
}
|
||||||
|
.mat-progress-bar-background {
|
||||||
|
fill: if($is-dark-theme, rgb(69, 91, 84), #cccccc) !important;
|
||||||
|
}
|
||||||
|
.mat-progress-bar-fill:after {
|
||||||
|
background-color: var(--success);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.prog-desc {
|
||||||
|
font-style: italic;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-card-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0 -1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
.action-card {
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
margin: 1rem;
|
||||||
|
flex-basis: 270px;
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding-top: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-height: 166px;
|
||||||
|
transition: box-shadow 0.1s ease-in;
|
||||||
|
|
||||||
|
.action-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
|
||||||
|
.text-block {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: map-get($foreground, text);
|
||||||
|
padding-top: 1rem;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-space {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state-circle {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
transform: translateX(-50%) translateY(-50%);
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 1.5rem;
|
||||||
|
width: 1.5rem;
|
||||||
|
border-radius: 50vw;
|
||||||
|
margin-right: 1rem;
|
||||||
|
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
|
||||||
|
box-shadow: 0 0 3px #0000001a;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success-icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
width: 1.2rem;
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.warn-icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
width: 1.2rem;
|
||||||
|
color: map-get($foreground, text);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-left: 0rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.alldone {
|
||||||
|
.state-circle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OnboardingComponent } from './onboarding.component';
|
||||||
|
|
||||||
|
describe('OnboardingComponent', () => {
|
||||||
|
let component: OnboardingComponent;
|
||||||
|
let fixture: ComponentFixture<OnboardingComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [OnboardingComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(OnboardingComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
16
console/src/app/modules/onboarding/onboarding.component.ts
Normal file
16
console/src/app/modules/onboarding/onboarding.component.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { AdminService } from 'src/app/services/admin.service';
|
||||||
|
import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-onboarding',
|
||||||
|
templateUrl: './onboarding.component.html',
|
||||||
|
styleUrls: ['./onboarding.component.scss'],
|
||||||
|
})
|
||||||
|
export class OnboardingComponent {
|
||||||
|
public actions = this.adminService.progressEvents;
|
||||||
|
|
||||||
|
constructor(public adminService: AdminService) {
|
||||||
|
this.adminService.loadEvents.next(ONBOARDING_EVENTS);
|
||||||
|
}
|
||||||
|
}
|
31
console/src/app/modules/onboarding/onboarding.module.ts
Normal file
31
console/src/app/modules/onboarding/onboarding.module.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { MatRippleModule } from '@angular/material/core';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
|
||||||
|
import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/legacy-tooltip';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ShortcutsModule } from 'src/app/modules/shortcuts/shortcuts.module';
|
||||||
|
|
||||||
|
import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { EventPipeModule } from 'src/app/pipes/event-pipe/event-pipe.module';
|
||||||
|
import { OnboardingComponent } from './onboarding.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [OnboardingComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatIconModule,
|
||||||
|
TranslateModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
ShortcutsModule,
|
||||||
|
MatRippleModule,
|
||||||
|
RouterModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
MatLegacyProgressBarModule,
|
||||||
|
EventPipeModule,
|
||||||
|
],
|
||||||
|
exports: [OnboardingComponent],
|
||||||
|
})
|
||||||
|
export default class OnboardingModule {}
|
@ -14,7 +14,6 @@
|
|||||||
.org-context-card {
|
.org-context-card {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
border: 1px solid #ffffff30;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { AppCreateComponent } from './app-create.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AppCreateComponent,
|
||||||
|
data: { animation: 'DetailPage' },
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class AppCreateRoutingModule {}
|
26
console/src/app/pages/app-create/app-create.component.html
Normal file
26
console/src/app/pages/app-create/app-create.component.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<cnsl-create-layout title="{{ 'APP.PAGES.CREATE' | translate }}" (closed)="close()">
|
||||||
|
<div class="app-create-main-content">
|
||||||
|
<h1>{{ 'APP.PAGES.CREATE_SELECT_PROJECT' | translate }}</h1>
|
||||||
|
|
||||||
|
<cnsl-search-project-autocomplete
|
||||||
|
class="block"
|
||||||
|
[autocompleteType]="ProjectAutocompleteType.PROJECT_OWNED"
|
||||||
|
(selectionChanged)="selectProject($any($event.project))"
|
||||||
|
>
|
||||||
|
</cnsl-search-project-autocomplete>
|
||||||
|
|
||||||
|
<div [innerHtml]="'APP.PAGES.CREATE_NEW_PROJECT' | translate : { url: '/projects/create' }"></div>
|
||||||
|
|
||||||
|
<div class="app-create-btn-container">
|
||||||
|
<button
|
||||||
|
color="primary"
|
||||||
|
mat-raised-button
|
||||||
|
class="continue-button"
|
||||||
|
[disabled]="!projectId"
|
||||||
|
(click)="goToAppCreatePage()"
|
||||||
|
>
|
||||||
|
{{ 'ACTIONS.CONTINUE' | translate }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</cnsl-create-layout>
|
25
console/src/app/pages/app-create/app-create.component.scss
Normal file
25
console/src/app/pages/app-create/app-create.component.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
h1 {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-create-main-content {
|
||||||
|
max-width: 35rem;
|
||||||
|
|
||||||
|
.app-create-btn-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 3rem;
|
||||||
|
|
||||||
|
.continue-button {
|
||||||
|
margin-top: 3rem;
|
||||||
|
display: block;
|
||||||
|
padding: 0.5rem 4rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.complexity-view {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OrgCreateComponent } from './org-create.component';
|
||||||
|
|
||||||
|
describe('OrgCreateComponent', () => {
|
||||||
|
let component: OrgCreateComponent;
|
||||||
|
let fixture: ComponentFixture<OrgCreateComponent>;
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [OrgCreateComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(OrgCreateComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
45
console/src/app/pages/app-create/app-create.component.ts
Normal file
45
console/src/app/pages/app-create/app-create.component.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||||
|
import { MatLegacySlideToggleChange as MatSlideToggleChange } from '@angular/material/legacy-slide-toggle';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { ProjectAutocompleteType } from 'src/app/modules/search-project-autocomplete/search-project-autocomplete.component';
|
||||||
|
import { lowerCaseValidator, numberValidator, symbolValidator, upperCaseValidator } from 'src/app/pages/validators';
|
||||||
|
import { SetUpOrgRequest } from 'src/app/proto/generated/zitadel/admin_pb';
|
||||||
|
import { PasswordComplexityPolicy } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||||
|
import { Project } from 'src/app/proto/generated/zitadel/project_pb';
|
||||||
|
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-app-create',
|
||||||
|
templateUrl: './app-create.component.html',
|
||||||
|
styleUrls: ['./app-create.component.scss'],
|
||||||
|
})
|
||||||
|
export class AppCreateComponent {
|
||||||
|
public projectId: string = '';
|
||||||
|
public ProjectAutocompleteType: any = ProjectAutocompleteType;
|
||||||
|
|
||||||
|
constructor(private router: Router, breadcrumbService: BreadcrumbService) {
|
||||||
|
const bread: Breadcrumb = {
|
||||||
|
type: BreadcrumbType.ORG,
|
||||||
|
routerLink: ['/org'],
|
||||||
|
};
|
||||||
|
breadcrumbService.setBreadcrumb([bread]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public goToAppCreatePage(): void {
|
||||||
|
this.router.navigate(['/projects', this.projectId, 'apps', 'create']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public close(): void {
|
||||||
|
window.history.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectProject(project: Project.AsObject): void {
|
||||||
|
if (project.id) {
|
||||||
|
this.projectId = project.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
console/src/app/pages/app-create/app-create.module.ts
Normal file
43
console/src/app/pages/app-create/app-create.module.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
||||||
|
import { MatLegacyCheckboxModule as MatCheckboxModule } from '@angular/material/legacy-checkbox';
|
||||||
|
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
|
||||||
|
import { MatLegacySlideToggleModule as MatSlideToggleModule } from '@angular/material/legacy-slide-toggle';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
|
import { CreateLayoutModule } from 'src/app/modules/create-layout/create-layout.module';
|
||||||
|
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
|
||||||
|
import { InputModule } from 'src/app/modules/input/input.module';
|
||||||
|
import { PasswordComplexityViewModule } from 'src/app/modules/password-complexity-view/password-complexity-view.module';
|
||||||
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
|
||||||
|
import { SearchProjectAutocompleteModule } from 'src/app/modules/search-project-autocomplete/search-project-autocomplete.module';
|
||||||
|
import { AppCreateRoutingModule } from './app-create-routing.module';
|
||||||
|
import { AppCreateComponent } from './app-create.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [AppCreateComponent],
|
||||||
|
imports: [
|
||||||
|
AppCreateRoutingModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
InfoSectionModule,
|
||||||
|
InputModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
SearchProjectAutocompleteModule,
|
||||||
|
MatSelectModule,
|
||||||
|
CreateLayoutModule,
|
||||||
|
HasRolePipeModule,
|
||||||
|
TranslateModule,
|
||||||
|
HasRoleModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
PasswordComplexityViewModule,
|
||||||
|
MatSlideToggleModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export default class AppCreateModule {}
|
@ -1,10 +1,11 @@
|
|||||||
<div class="max-width-container">
|
<div class="max-width-container">
|
||||||
<div class="home-wrapper enlarged-container">
|
<div class="home-wrapper enlarged-container">
|
||||||
<div class="header">
|
<ng-container *ngIf="['iam.read$'] | hasRole | async; else defaultHome">
|
||||||
<h1 class="title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
|
<cnsl-onboarding></cnsl-onboarding>
|
||||||
</div>
|
</ng-container>
|
||||||
|
<ng-template #defaultHome>
|
||||||
<cnsl-shortcuts></cnsl-shortcuts>
|
<cnsl-shortcuts></cnsl-shortcuts>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<p class="disclaimer cnsl-secondary-text">{{ 'HOME.DISCLAIMER' | translate }}</p>
|
<p class="disclaimer cnsl-secondary-text">{{ 'HOME.DISCLAIMER' | translate }}</p>
|
||||||
|
|
||||||
|
@ -125,6 +125,7 @@
|
|||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 5rem;
|
margin-bottom: 5rem;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fill-space {
|
.fill-space {
|
||||||
|
@ -9,6 +9,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
import { ShortcutsModule } from 'src/app/modules/shortcuts/shortcuts.module';
|
import { ShortcutsModule } from 'src/app/modules/shortcuts/shortcuts.module';
|
||||||
|
|
||||||
|
import OnboardingModule from 'src/app/modules/onboarding/onboarding.module';
|
||||||
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
import { HomeRoutingModule } from './home-routing.module';
|
import { HomeRoutingModule } from './home-routing.module';
|
||||||
import { HomeComponent } from './home.component';
|
import { HomeComponent } from './home.component';
|
||||||
|
|
||||||
@ -20,10 +22,12 @@ import { HomeComponent } from './home.component';
|
|||||||
HasRoleModule,
|
HasRoleModule,
|
||||||
HomeRoutingModule,
|
HomeRoutingModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
|
HasRolePipeModule,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
MatTooltipModule,
|
MatTooltipModule,
|
||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
ShortcutsModule,
|
ShortcutsModule,
|
||||||
|
OnboardingModule,
|
||||||
MatRippleModule,
|
MatRippleModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnDestroy } from '@angular/core';
|
||||||
import { ActivatedRoute, Params } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { take } from 'rxjs';
|
import { Subject, takeUntil } from 'rxjs';
|
||||||
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
|
||||||
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
||||||
@ -27,7 +27,7 @@ import {
|
|||||||
templateUrl: './instance-settings.component.html',
|
templateUrl: './instance-settings.component.html',
|
||||||
styleUrls: ['./instance-settings.component.scss'],
|
styleUrls: ['./instance-settings.component.scss'],
|
||||||
})
|
})
|
||||||
export class InstanceSettingsComponent {
|
export class InstanceSettingsComponent implements OnDestroy {
|
||||||
public id: string = '';
|
public id: string = '';
|
||||||
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
public PolicyComponentServiceType: any = PolicyComponentServiceType;
|
||||||
public settingsList: SidenavSetting[] = [
|
public settingsList: SidenavSetting[] = [
|
||||||
@ -52,6 +52,8 @@ export class InstanceSettingsComponent {
|
|||||||
SECRETS,
|
SECRETS,
|
||||||
SECURITY,
|
SECURITY,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private destroy$: Subject<void> = new Subject();
|
||||||
constructor(breadcrumbService: BreadcrumbService, activatedRoute: ActivatedRoute) {
|
constructor(breadcrumbService: BreadcrumbService, activatedRoute: ActivatedRoute) {
|
||||||
const breadcrumbs = [
|
const breadcrumbs = [
|
||||||
new Breadcrumb({
|
new Breadcrumb({
|
||||||
@ -62,11 +64,16 @@ export class InstanceSettingsComponent {
|
|||||||
];
|
];
|
||||||
breadcrumbService.setBreadcrumb(breadcrumbs);
|
breadcrumbService.setBreadcrumb(breadcrumbs);
|
||||||
|
|
||||||
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
|
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
if (id) {
|
if (id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.destroy$.next();
|
||||||
|
this.destroy$.complete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,15 @@ const routes: Routes = [
|
|||||||
roles: ['project.create'],
|
roles: ['project.create'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'app-create',
|
||||||
|
canActivate: [RoleGuard],
|
||||||
|
data: {
|
||||||
|
animation: 'AddPage',
|
||||||
|
roles: ['project.app.write'],
|
||||||
|
},
|
||||||
|
loadChildren: () => import('../app-create/app-create.module'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: ':projectid',
|
path: ':projectid',
|
||||||
loadChildren: () => import('./owned-projects/owned-projects.module'),
|
loadChildren: () => import('./owned-projects/owned-projects.module'),
|
||||||
|
12
console/src/app/pipes/event-pipe/event-pipe.module.ts
Normal file
12
console/src/app/pipes/event-pipe/event-pipe.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { LocalizedDatePipeModule } from '../localized-date-pipe/localized-date-pipe.module';
|
||||||
|
import { TimestampToDatePipeModule } from '../timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||||
|
import { EventPipe } from './event.pipe';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [EventPipe],
|
||||||
|
imports: [CommonModule, TimestampToDatePipeModule, LocalizedDatePipeModule],
|
||||||
|
exports: [EventPipe],
|
||||||
|
})
|
||||||
|
export class EventPipeModule {}
|
26
console/src/app/pipes/event-pipe/event.pipe.ts
Normal file
26
console/src/app/pipes/event-pipe/event.pipe.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { Event } from 'src/app/proto/generated/zitadel/event_pb';
|
||||||
|
import { LocalizedDatePipe } from '../localized-date-pipe/localized-date.pipe';
|
||||||
|
import { TimestampToDatePipe } from '../timestamp-to-date-pipe/timestamp-to-date.pipe';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'event',
|
||||||
|
})
|
||||||
|
export class EventPipe implements PipeTransform {
|
||||||
|
constructor(private translateService: TranslateService) {}
|
||||||
|
|
||||||
|
public transform(event?: Event.AsObject): any {
|
||||||
|
if (event && event.editor?.displayName && event.creationDate) {
|
||||||
|
const timestampToDate = new TimestampToDatePipe().transform(event.creationDate);
|
||||||
|
const datePipeOutput = new LocalizedDatePipe(this.translateService).transform(timestampToDate);
|
||||||
|
return `${event.editor?.displayName} last changed it on ${datePipeOutput}`;
|
||||||
|
} else if (event && event.creationDate) {
|
||||||
|
const timestampToDate = new TimestampToDatePipe().transform(event.creationDate);
|
||||||
|
const datePipeOutput = new LocalizedDatePipe(this.translateService).transform(timestampToDate);
|
||||||
|
return `done on ${datePipeOutput}`;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { BehaviorSubject, catchError, finalize, from, map, Observable, of, Subject, switchMap, tap } from 'rxjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ActivateLabelPolicyRequest,
|
ActivateLabelPolicyRequest,
|
||||||
@ -225,15 +226,84 @@ import {
|
|||||||
UpdateSMTPConfigRequest,
|
UpdateSMTPConfigRequest,
|
||||||
UpdateSMTPConfigResponse,
|
UpdateSMTPConfigResponse,
|
||||||
} from '../proto/generated/zitadel/admin_pb';
|
} from '../proto/generated/zitadel/admin_pb';
|
||||||
|
import { Event } from '../proto/generated/zitadel/event_pb';
|
||||||
import { SearchQuery } from '../proto/generated/zitadel/member_pb';
|
import { SearchQuery } from '../proto/generated/zitadel/member_pb';
|
||||||
import { ListQuery } from '../proto/generated/zitadel/object_pb';
|
import { ListQuery } from '../proto/generated/zitadel/object_pb';
|
||||||
import { GrpcService } from './grpc.service';
|
import { GrpcService } from './grpc.service';
|
||||||
|
import { StorageLocation, StorageService } from './storage.service';
|
||||||
|
|
||||||
|
export interface OnboardingActions {
|
||||||
|
order: number;
|
||||||
|
eventType: string;
|
||||||
|
oneof: string[];
|
||||||
|
link: string | string[];
|
||||||
|
fragment?: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnboardingEvent = { order: number; link: string; fragment: string | undefined; event: Event.AsObject | undefined };
|
||||||
|
type OnboardingEventEntries = Array<[string, OnboardingEvent]> | [];
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class AdminService {
|
export class AdminService {
|
||||||
constructor(private readonly grpcService: GrpcService) {}
|
public hideOnboarding: boolean = false;
|
||||||
|
public loadEvents: Subject<OnboardingActions[]> = new Subject();
|
||||||
|
public onboardingLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||||
|
public progressEvents$: Observable<OnboardingEventEntries> = this.loadEvents.pipe(
|
||||||
|
tap(() => this.onboardingLoading.next(true)),
|
||||||
|
switchMap((actions) => {
|
||||||
|
const searchForTypes = actions.map((oe) => oe.oneof).flat();
|
||||||
|
const eventsReq = new ListEventsRequest().setAsc(true).setEventTypesList(searchForTypes).setAsc(false);
|
||||||
|
return from(this.listEvents(eventsReq)).pipe(
|
||||||
|
map((events) => {
|
||||||
|
const el = events.toObject().eventsList.filter((e) => e.editor?.service !== 'System-API');
|
||||||
|
|
||||||
|
let obj: { [type: string]: OnboardingEvent } = {};
|
||||||
|
actions.map((action) => {
|
||||||
|
const filtered = el.filter((event) => event.type?.type && action.oneof.includes(event.type.type));
|
||||||
|
(obj as any)[action.eventType] = filtered.length
|
||||||
|
? { order: action.order, link: action.link, fragment: action.fragment, event: filtered[0] }
|
||||||
|
: { order: action.order, link: action.link, fragment: action.fragment, event: undefined };
|
||||||
|
});
|
||||||
|
|
||||||
|
const toArray = Object.entries(obj).sort(([key0, a], [key1, b]) => a.order - b.order);
|
||||||
|
|
||||||
|
const toDo = toArray.filter(([key, value]) => value.event === undefined);
|
||||||
|
const done = toArray.filter(([key, value]) => !!value.event);
|
||||||
|
|
||||||
|
return [...toDo, ...done];
|
||||||
|
}),
|
||||||
|
tap((events) => {
|
||||||
|
const total = events.length;
|
||||||
|
const done = events.map(([type, value]) => value.event !== undefined).filter((res) => !!res).length;
|
||||||
|
const percentage = Math.round((done / total) * 100);
|
||||||
|
this.progressDone.next(done);
|
||||||
|
this.progressTotal.next(total);
|
||||||
|
this.progressPercentage.next(percentage);
|
||||||
|
this.progressAllDone.next(done === total);
|
||||||
|
}),
|
||||||
|
catchError((error) => {
|
||||||
|
console.error(error);
|
||||||
|
return of([]);
|
||||||
|
}),
|
||||||
|
finalize(() => this.onboardingLoading.next(false)),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
public progressEvents: BehaviorSubject<OnboardingEventEntries> = new BehaviorSubject<OnboardingEventEntries>([]);
|
||||||
|
public progressPercentage: BehaviorSubject<number> = new BehaviorSubject(0);
|
||||||
|
public progressDone: BehaviorSubject<number> = new BehaviorSubject(0);
|
||||||
|
public progressTotal: BehaviorSubject<number> = new BehaviorSubject(0);
|
||||||
|
public progressAllDone: BehaviorSubject<boolean> = new BehaviorSubject(true);
|
||||||
|
|
||||||
|
constructor(private readonly grpcService: GrpcService, private storageService: StorageService) {
|
||||||
|
this.progressEvents$.subscribe(this.progressEvents);
|
||||||
|
|
||||||
|
this.hideOnboarding =
|
||||||
|
this.storageService.getItem('onboarding-dismissed', StorageLocation.local) === 'true' ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
public setDefaultOrg(orgId: string): Promise<SetDefaultOrgResponse.AsObject> {
|
public setDefaultOrg(orgId: string): Promise<SetDefaultOrgResponse.AsObject> {
|
||||||
const req = new SetDefaultOrgRequest();
|
const req = new SetDefaultOrgRequest();
|
||||||
|
22
console/src/app/utils/onboarding.ts
Normal file
22
console/src/app/utils/onboarding.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { OnboardingActions } from '../services/admin.service';
|
||||||
|
|
||||||
|
export const ONBOARDING_EVENTS: OnboardingActions[] = [
|
||||||
|
{
|
||||||
|
order: 0,
|
||||||
|
eventType: 'instance.policy.label.added',
|
||||||
|
oneof: ['instance.policy.label.added', 'instance.policy.label.changed'],
|
||||||
|
link: ['/settings'],
|
||||||
|
fragment: 'branding',
|
||||||
|
},
|
||||||
|
{ order: 1, eventType: 'project.added', oneof: ['project.added'], link: ['/projects/create'] },
|
||||||
|
{ order: 2, eventType: 'project.application.added', oneof: ['project.application.added'], link: ['/projects/app-create'] },
|
||||||
|
{ order: 3, eventType: 'user.human.added', oneof: ['user.human.added'], link: ['/users/create'] },
|
||||||
|
{
|
||||||
|
order: 4,
|
||||||
|
eventType: 'instance.smtp.config.added',
|
||||||
|
oneof: ['instance.smtp.config.added', 'instance.smtp.config.changed'],
|
||||||
|
link: ['/settings'],
|
||||||
|
fragment: 'notifications',
|
||||||
|
},
|
||||||
|
{ order: 5, eventType: 'user.grant.added', oneof: ['user.grant.added'], link: ['/grant-create'] },
|
||||||
|
];
|
@ -42,6 +42,41 @@
|
|||||||
"ADD": "Zum Hinzufügen Kachel halten und ziehen"
|
"ADD": "Zum Hinzufügen Kachel halten und ziehen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"DESCRIPTION": "Dein Onboarding-prozess",
|
||||||
|
"COMPLETED": "abgeschlossen",
|
||||||
|
"DISMISS": "schließen",
|
||||||
|
"CARD": {
|
||||||
|
"TITLE": "Bringe deine Instanz zum Laufen",
|
||||||
|
"DESCRIPTION": "Diese Checkliste hilft bei der Einrichtung Ihrer Instanz und führt Sie durch die wichtigsten Schritte"
|
||||||
|
},
|
||||||
|
"EVENTS": {
|
||||||
|
"instance.policy.label.added": {
|
||||||
|
"title": "Branding anpassen",
|
||||||
|
"description": "Definiere Farben und Form des Login-UIs und uploade deine Logos und Icons."
|
||||||
|
},
|
||||||
|
"instance.smtp.config.added": {
|
||||||
|
"title": "SMTP Benachrichtigungseinstellungen",
|
||||||
|
"description": "Konfiguriere deinen Mailserver."
|
||||||
|
},
|
||||||
|
"project.added": {
|
||||||
|
"title": "Erstelle ein Projekt",
|
||||||
|
"description": "Erstelle dein erstes Projekt und definiere Rollen"
|
||||||
|
},
|
||||||
|
"project.application.added": {
|
||||||
|
"title": "Erstelle eine App",
|
||||||
|
"description": "Erstelle deine erste Web-, native, API oder SAML-applikation und konfiguriere den Authentification-flow."
|
||||||
|
},
|
||||||
|
"user.human.added": {
|
||||||
|
"title": "Erfasse Benutzer",
|
||||||
|
"description": "Erstelle Benutzer die später deine Apps nutzen können."
|
||||||
|
},
|
||||||
|
"user.grant.added": {
|
||||||
|
"title": "Berechtige Benutzer",
|
||||||
|
"description": "Erlaube es deinen Nutzern auf deine Apps zuzugreifen und gebe ihnen Rollen."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MENU": {
|
"MENU": {
|
||||||
"INSTANCE": "Instance",
|
"INSTANCE": "Instance",
|
||||||
"DASHBOARD": "Home",
|
"DASHBOARD": "Home",
|
||||||
@ -1725,6 +1760,8 @@
|
|||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"DESCRIPTION": "Hier kannst Du Deine Applikationen bearbeiten und deren Konfiguration anpassen.",
|
"DESCRIPTION": "Hier kannst Du Deine Applikationen bearbeiten und deren Konfiguration anpassen.",
|
||||||
"CREATE": "Applikation erstellen",
|
"CREATE": "Applikation erstellen",
|
||||||
|
"CREATE_SELECT_PROJECT": "Wähle zuerst dein Projekt aus",
|
||||||
|
"CREATE_NEW_PROJECT": "oder erstelle ein neues <a href='{{url}}' title='Create project'>hier</a>.",
|
||||||
"CREATE_DESC_TITLE": "Gebe die Daten der Anwendung Schritt für Schritt ein.",
|
"CREATE_DESC_TITLE": "Gebe die Daten der Anwendung Schritt für Schritt ein.",
|
||||||
"CREATE_DESC_SUB": "Es wird automatisch eine empfohlene Konfiguration generiert.",
|
"CREATE_DESC_SUB": "Es wird automatisch eine empfohlene Konfiguration generiert.",
|
||||||
"STATE": "Status",
|
"STATE": "Status",
|
||||||
|
@ -42,6 +42,41 @@
|
|||||||
"ADD": "Hold and drag a tile to add"
|
"ADD": "Hold and drag a tile to add"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"DESCRIPTION": "Your onboarding process",
|
||||||
|
"COMPLETED": "completed",
|
||||||
|
"DISMISS": "No thanks, I'm a pro.",
|
||||||
|
"CARD": {
|
||||||
|
"TITLE": "Get your ZITADEL running",
|
||||||
|
"DESCRIPTION": "This checklist helps to setup your instance and guides your through the most essential steps"
|
||||||
|
},
|
||||||
|
"EVENTS": {
|
||||||
|
"instance.policy.label.added": {
|
||||||
|
"title": "Setup your brand",
|
||||||
|
"description": "Define coloring and shape of your login and upload your logo and icons."
|
||||||
|
},
|
||||||
|
"instance.smtp.config.added": {
|
||||||
|
"title": "Setup your SMTP settings",
|
||||||
|
"description": "Set your own mail server settings."
|
||||||
|
},
|
||||||
|
"project.added": {
|
||||||
|
"title": "Create your first project",
|
||||||
|
"description": "Add your first project and define its roles and authorizations."
|
||||||
|
},
|
||||||
|
"project.application.added": {
|
||||||
|
"title": "Create your first application",
|
||||||
|
"description": "Create a web, native, api or saml application and setup your authentication flow."
|
||||||
|
},
|
||||||
|
"user.human.added": {
|
||||||
|
"title": "Add users",
|
||||||
|
"description": "Add your application users"
|
||||||
|
},
|
||||||
|
"user.grant.added": {
|
||||||
|
"title": "Grant users",
|
||||||
|
"description": "Allow users to access your application and setup their role."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MENU": {
|
"MENU": {
|
||||||
"INSTANCE": "Instance",
|
"INSTANCE": "Instance",
|
||||||
"DASHBOARD": "Home",
|
"DASHBOARD": "Home",
|
||||||
@ -124,6 +159,7 @@
|
|||||||
"NEXT": "Next",
|
"NEXT": "Next",
|
||||||
"MORE": "more",
|
"MORE": "more",
|
||||||
"STEP": "Step",
|
"STEP": "Step",
|
||||||
|
"SETUP": "Setup",
|
||||||
"TABLE": {
|
"TABLE": {
|
||||||
"SHOWUSER": "Show user {{value}}"
|
"SHOWUSER": "Show user {{value}}"
|
||||||
}
|
}
|
||||||
@ -1725,6 +1761,8 @@
|
|||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"DESCRIPTION": "Here you can edit your application data and it's configuration.",
|
"DESCRIPTION": "Here you can edit your application data and it's configuration.",
|
||||||
"CREATE": "Create application",
|
"CREATE": "Create application",
|
||||||
|
"CREATE_SELECT_PROJECT": "Select your project first",
|
||||||
|
"CREATE_NEW_PROJECT": "or create a new one <a href='{{url}}' title='Create project'>here</a>.",
|
||||||
"CREATE_DESC_TITLE": "Enter Your Application Details Step by Step",
|
"CREATE_DESC_TITLE": "Enter Your Application Details Step by Step",
|
||||||
"CREATE_DESC_SUB": "A recommended configuration will be automatically generated.",
|
"CREATE_DESC_SUB": "A recommended configuration will be automatically generated.",
|
||||||
"STATE": "Status",
|
"STATE": "Status",
|
||||||
|
@ -42,6 +42,41 @@
|
|||||||
"ADD": "Maintenir et faire glisser une tuile pour ajouter"
|
"ADD": "Maintenir et faire glisser une tuile pour ajouter"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"DESCRIPTION": "Votre processus d'intégration",
|
||||||
|
"COMPLETED": "terminé",
|
||||||
|
"DISMISS": "fermer",
|
||||||
|
"CARD": {
|
||||||
|
"TITLE": "Faites fonctionner votre ZITADEL",
|
||||||
|
"DESCRIPTION": "Cette liste de contrôle vous aide à configurer votre instance et vous guide à travers les étapes les plus essentielles."
|
||||||
|
},
|
||||||
|
"EVENTS": {
|
||||||
|
"instance.policy.label.added": {
|
||||||
|
"title": "Créez votre marque",
|
||||||
|
"description": "Définissez la couleur et la forme de votre connexion et téléchargez votre logo et vos icônes."
|
||||||
|
},
|
||||||
|
"instance.smtp.config.added": {
|
||||||
|
"title": "Configurez vos paramètres SMTP",
|
||||||
|
"description": "Définissez vos propres paramètres de serveur de messagerie"
|
||||||
|
},
|
||||||
|
"project.added": {
|
||||||
|
"title": "Créez votre premier projet",
|
||||||
|
"description": "Ajoutez votre premier projet et définissez ses rôles et autorisations."
|
||||||
|
},
|
||||||
|
"project.application.added": {
|
||||||
|
"title": "Créez votre première application",
|
||||||
|
"description": "Créez une application web, native, api ou saml et configurez votre flux d'authentification."
|
||||||
|
},
|
||||||
|
"user.human.added": {
|
||||||
|
"title": "Ajouter des utilisateurs",
|
||||||
|
"description": "Ajouter les utilisateurs de votre application"
|
||||||
|
},
|
||||||
|
"user.grant.added": {
|
||||||
|
"title": "Utilisateurs de subventions",
|
||||||
|
"description": "Autorisez les utilisateurs à accéder à votre application et définissez leur rôle."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MENU": {
|
"MENU": {
|
||||||
"INSTANCE": "Instance",
|
"INSTANCE": "Instance",
|
||||||
"DASHBOARD": "Accueil",
|
"DASHBOARD": "Accueil",
|
||||||
@ -1724,9 +1759,11 @@
|
|||||||
"TITLE": "Application",
|
"TITLE": "Application",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"DESCRIPTION": "Ici vous pouvez modifier les données de votre application et sa configuration.",
|
"DESCRIPTION": "Ici vous pouvez modifier les données de votre application et sa configuration.",
|
||||||
"CREATE_OIDC": "Application OIDC",
|
"CREATE": "Application OIDC",
|
||||||
"CREATE_OIDC_DESC_TITLE": "Entrez les détails de votre application étape par étape",
|
"CREATE_SELECT_PROJECT": "Sélectionnez d'abord votre projet",
|
||||||
"CREATE_OIDC_DESC_SUB": "Une configuration recommandée sera automatiquement générée.",
|
"CREATE_NEW_PROJECT": "ou créez-en un nouveau <a href='{{url}}' title='Create project'>here</a>.",
|
||||||
|
"CREATE_DESC_TITLE": "Entrez les détails de votre application étape par étape",
|
||||||
|
"CREATE_DESC_SUB": "Une configuration recommandée sera automatiquement générée.",
|
||||||
"STATE": "Statut",
|
"STATE": "Statut",
|
||||||
"DATECREATED": "Créé",
|
"DATECREATED": "Créé",
|
||||||
"DATECHANGED": "Modifié",
|
"DATECHANGED": "Modifié",
|
||||||
|
@ -42,6 +42,41 @@
|
|||||||
"ADD": "Per aggiungere, tieni premuto e trascina il riquadro"
|
"ADD": "Per aggiungere, tieni premuto e trascina il riquadro"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"DESCRIPTION": "Your onboarding process",
|
||||||
|
"COMPLETED": "completed",
|
||||||
|
"DISMISS": "chiudi",
|
||||||
|
"CARD": {
|
||||||
|
"TITLE": "Fate funzionare il vostro ZITADEL",
|
||||||
|
"DESCRIPTION": "Questa lista di azioni aiuta a configurare la vostra istanza e vi guida attraverso i passaggi più essenziali."
|
||||||
|
},
|
||||||
|
"EVENTS": {
|
||||||
|
"instance.policy.label.added": {
|
||||||
|
"title": "Imposta il tuo marchio",
|
||||||
|
"description": "Definisci la colorazione e il design del vostro login e caricate il vostro logo e le vostre icone."
|
||||||
|
},
|
||||||
|
"instance.smtp.config.added": {
|
||||||
|
"title": "Configura le impostazioni SMTP",
|
||||||
|
"description": "Imposta il proprio server di posta"
|
||||||
|
},
|
||||||
|
"project.added": {
|
||||||
|
"title": "Crea il tuo primo progetto",
|
||||||
|
"description": "Aggiungere il primo progetto e definire i ruoli e le autorizzazioni."
|
||||||
|
},
|
||||||
|
"project.application.added": {
|
||||||
|
"title": "Crea la tua prima applicazione",
|
||||||
|
"description": "Crea un'applicazione web, nativa, api o saml e imposta il flusso di autenticazione."
|
||||||
|
},
|
||||||
|
"user.human.added": {
|
||||||
|
"title": "Aggiungi utenti",
|
||||||
|
"description": "Aggiungi gli utenti dell'applicazione"
|
||||||
|
},
|
||||||
|
"user.grant.added": {
|
||||||
|
"title": "Crea autorizzazioni per gli utenti",
|
||||||
|
"description": "Consenti agli utenti di accedere alla tua applicazione e imposta il loro ruolo."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MENU": {
|
"MENU": {
|
||||||
"INSTANCE": "Istanza",
|
"INSTANCE": "Istanza",
|
||||||
"DASHBOARD": "Pagina iniziale",
|
"DASHBOARD": "Pagina iniziale",
|
||||||
@ -1726,6 +1761,8 @@
|
|||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"DESCRIPTION": "Qui puoi modificare i dati della tua applicazione e la sua configurazione.",
|
"DESCRIPTION": "Qui puoi modificare i dati della tua applicazione e la sua configurazione.",
|
||||||
"CREATE": "Crea Applicazione",
|
"CREATE": "Crea Applicazione",
|
||||||
|
"CREATE_SELECT_PROJECT": "Seleziona il tuo progetto",
|
||||||
|
"CREATE_NEW_PROJECT": "o crea uno nuovo <a href='{{url}}' title='Crea progetto'>qui</a>.",
|
||||||
"CREATE_DESC_TITLE": "Inserisci i dettagli della tua applicazione passo dopo passo",
|
"CREATE_DESC_TITLE": "Inserisci i dettagli della tua applicazione passo dopo passo",
|
||||||
"CREATE_DESC_SUB": "Una configurazione raccomandata sar\u00e0 generata automaticamente.",
|
"CREATE_DESC_SUB": "Una configurazione raccomandata sar\u00e0 generata automaticamente.",
|
||||||
"STATE": "Stato",
|
"STATE": "Stato",
|
||||||
|
@ -42,6 +42,41 @@
|
|||||||
"ADD": "Przytrzymaj i przeciągnij kafel, aby go dodać"
|
"ADD": "Przytrzymaj i przeciągnij kafel, aby go dodać"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"DESCRIPTION": "Twój proces wprowadzania na rynek",
|
||||||
|
"COMPLETED": "zakończone",
|
||||||
|
"DISMISS": "zamknąć",
|
||||||
|
"CARD": {
|
||||||
|
"TITLE": "Uruchom swój ZITADEL",
|
||||||
|
"DESCRIPTION": "Ta lista kontrolna pomoże Ci skonfigurować instancję i poprowadzi Cię przez najważniejsze kroki."
|
||||||
|
},
|
||||||
|
"EVENTS": {
|
||||||
|
"instance.policy.label.added": {
|
||||||
|
"title": "Skonfiguruj swoją markę",
|
||||||
|
"description": "Zdefiniuj kolorystykę i kształt swojego loginu oraz wgraj swoje logo i ikony."
|
||||||
|
},
|
||||||
|
"instance.smtp.config.added": {
|
||||||
|
"title": "Ustawienia SMTP",
|
||||||
|
"description": "Ustawienie własnego serwera pocztowego"
|
||||||
|
},
|
||||||
|
"project.added": {
|
||||||
|
"title": "Stwórz swój pierwszy projekt",
|
||||||
|
"description": "Dodaj swój pierwszy projekt i określ jego role i uprawnienia."
|
||||||
|
},
|
||||||
|
"project.application.added": {
|
||||||
|
"title": "Utwórz swoją pierwszą aplikację",
|
||||||
|
"description": "Utwórz aplikację internetową, natywną, api lub saml i skonfiguruj swój przepływ uwierzytelniania."
|
||||||
|
},
|
||||||
|
"user.human.added": {
|
||||||
|
"title": "Dodaj użytkowników",
|
||||||
|
"description": "Dodaj użytkowników aplikacji"
|
||||||
|
},
|
||||||
|
"user.grant.added": {
|
||||||
|
"title": "Użytkownicy dotacji",
|
||||||
|
"description": "Pozwól użytkownikom na dostęp do Twojej aplikacji i ustaw ich rolę."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MENU": {
|
"MENU": {
|
||||||
"INSTANCE": "Instancja",
|
"INSTANCE": "Instancja",
|
||||||
"DASHBOARD": "Strona główna",
|
"DASHBOARD": "Strona główna",
|
||||||
@ -1725,6 +1760,8 @@
|
|||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"DESCRIPTION": "Tutaj możesz edytować dane swojej aplikacji i jej konfigurację.",
|
"DESCRIPTION": "Tutaj możesz edytować dane swojej aplikacji i jej konfigurację.",
|
||||||
"CREATE": "Utwórz aplikację",
|
"CREATE": "Utwórz aplikację",
|
||||||
|
"CREATE_SELECT_PROJECT": "Najpierw wybierz swój projekt",
|
||||||
|
"CREATE_NEW_PROJECT": "lub utworzyć nowy <a href='{{url}}' title='Utwórz projekt'>tutaj</a>.",
|
||||||
"CREATE_DESC_TITLE": "Wprowadź szczegóły swojej aplikacji krok po kroku",
|
"CREATE_DESC_TITLE": "Wprowadź szczegóły swojej aplikacji krok po kroku",
|
||||||
"CREATE_DESC_SUB": "Automatycznie zostanie wygenerowana zalecana konfiguracja.",
|
"CREATE_DESC_SUB": "Automatycznie zostanie wygenerowana zalecana konfiguracja.",
|
||||||
"STATE": "Status",
|
"STATE": "Status",
|
||||||
|
@ -42,6 +42,41 @@
|
|||||||
"ADD": "按住并拖动来添加"
|
"ADD": "按住并拖动来添加"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ONBOARDING": {
|
||||||
|
"DESCRIPTION": "你的入职过程",
|
||||||
|
"COMPLETED": "已完成",
|
||||||
|
"DISMISS": "隐藏",
|
||||||
|
"CARD": {
|
||||||
|
"TITLE": "让你的ZITADEL运转起来",
|
||||||
|
"DESCRIPTION": "这份清单有助于设置你的实例,并指导你完成最重要的步骤"
|
||||||
|
},
|
||||||
|
"EVENTS": {
|
||||||
|
"instance.policy.label.added": {
|
||||||
|
"title": "设置你的品牌",
|
||||||
|
"description": "定义你的登录的颜色和形状,上传你的标志和图标。"
|
||||||
|
},
|
||||||
|
"instance.smtp.config.added": {
|
||||||
|
"title": "SMTP设置",
|
||||||
|
"description": "设置你自己的邮件服务器设置"
|
||||||
|
},
|
||||||
|
"project.added": {
|
||||||
|
"title": "创建你的第一个项目",
|
||||||
|
"description": "添加你的第一个项目并定义其角色和授权。"
|
||||||
|
},
|
||||||
|
"project.application.added": {
|
||||||
|
"title": "创建你的第一个应用程序",
|
||||||
|
"description": "创建一个web、native、api或saml应用程序并设置你的认证流程。"
|
||||||
|
},
|
||||||
|
"user.human.added": {
|
||||||
|
"title": "添加用户",
|
||||||
|
"description": "添加你的应用程序用户"
|
||||||
|
},
|
||||||
|
"user.grant.added": {
|
||||||
|
"title": "授予用户",
|
||||||
|
"description": "允许用户访问你的应用程序并设置他们的角色。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"MENU": {
|
"MENU": {
|
||||||
"INSTANCE": "实例",
|
"INSTANCE": "实例",
|
||||||
"DASHBOARD": "首页",
|
"DASHBOARD": "首页",
|
||||||
@ -1723,9 +1758,11 @@
|
|||||||
"TITLE": "应用",
|
"TITLE": "应用",
|
||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"DESCRIPTION": "在这里您可以编辑您的应用程序数据及其配置。",
|
"DESCRIPTION": "在这里您可以编辑您的应用程序数据及其配置。",
|
||||||
"CREATE_OIDC": "OIDC 应用",
|
"CREATE": "OIDC 应用",
|
||||||
"CREATE_OIDC_DESC_TITLE": "逐步输入您的应用详情",
|
"CREATE_SELECT_PROJECT": "首先选择你的项目",
|
||||||
"CREATE_OIDC_DESC_SUB": "将自动生成推荐的配置。",
|
"CREATE_NEW_PROJECT": "或创建一个新的<a href='{url}}' title='创建项目'>这里</a>。",
|
||||||
|
"CREATE_DESC_TITLE": "逐步输入您的应用详情",
|
||||||
|
"CREATE_DESC_SUB": "将自动生成推荐的配置。",
|
||||||
"STATE": "状态",
|
"STATE": "状态",
|
||||||
"DATECREATED": "创建于",
|
"DATECREATED": "创建于",
|
||||||
"DATECHANGED": "修改于",
|
"DATECHANGED": "修改于",
|
||||||
|
1
console/src/assets/mdi/shield-check.svg
Normal file
1
console/src/assets/mdi/shield-check.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M10,17L6,13L7.41,11.59L10,14.17L16.59,7.58L18,9M12,1L3,5V11C3,16.55 6.84,21.74 12,23C17.16,21.74 21,16.55 21,11V5L12,1Z" /></svg>
|
After Width: | Height: | Size: 198 B |
@ -44,6 +44,8 @@
|
|||||||
@import 'src/app/modules/meta-layout/meta.scss';
|
@import 'src/app/modules/meta-layout/meta.scss';
|
||||||
@import 'src/app/pages/projects/owned-projects/project-grant-detail/project-grant-illustration/project-grant-illustration.component';
|
@import 'src/app/pages/projects/owned-projects/project-grant-detail/project-grant-illustration/project-grant-illustration.component';
|
||||||
@import 'src/app/modules/accounts-card/accounts-card.component.scss';
|
@import 'src/app/modules/accounts-card/accounts-card.component.scss';
|
||||||
|
@import 'src/app/modules/onboarding-card/onboarding-card.component.scss';
|
||||||
|
@import 'src/app/modules/onboarding/onboarding.component.scss';
|
||||||
@import 'src/app/modules/filter/filter.component.scss';
|
@import 'src/app/modules/filter/filter.component.scss';
|
||||||
@import 'src/app/modules/policies/message-texts/message-texts.component.scss';
|
@import 'src/app/modules/policies/message-texts/message-texts.component.scss';
|
||||||
@import 'src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.scss';
|
@import 'src/app/modules/policies/private-labeling-policy/private-labeling-policy.component.scss';
|
||||||
@ -110,6 +112,8 @@
|
|||||||
@include toast-theme($theme);
|
@include toast-theme($theme);
|
||||||
@include keyboard-shortcuts-theme($theme);
|
@include keyboard-shortcuts-theme($theme);
|
||||||
@include project-grant-illustration-theme($theme);
|
@include project-grant-illustration-theme($theme);
|
||||||
|
@include onboarding-card-theme($theme);
|
||||||
|
@include onboarding-theme($theme);
|
||||||
@include refresh-table-theme($theme);
|
@include refresh-table-theme($theme);
|
||||||
@include accounts-card-theme($theme);
|
@include accounts-card-theme($theme);
|
||||||
@include sidenav-theme($theme);
|
@include sidenav-theme($theme);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user