feat(console): integrate app (#7417)

* docs, frameworks view

* project select, integrate app page

* fix search project autocomplete

* framework autocomplete

* framwork select component, integrate, mapping to oidc config

* param

* fix route handler

* setname projectid context

* app-create page without context

* show description of app type, info section

* redirects section

* updatevalue observable

* fix redirect uris section

* i18n

* setup config

* backbutton behavior, cleanup

* cleanup

* lint

* allow other framework jump off

* dev mode warning

* navigate to project

* rm import

* i18n, guide link

* edit name dialog

* show warning for duplicate name
This commit is contained in:
Max Peintner 2024-02-28 17:52:21 +01:00 committed by GitHub
parent f4c72cbe14
commit 0fcdfe460c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
64 changed files with 2381 additions and 176 deletions

View File

@ -23,7 +23,12 @@
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": ["src/favicon.ico", "src/assets", "src/manifest.webmanifest"],
"assets": [
"src/favicon.ico",
"src/assets",
"src/manifest.webmanifest",
{ "glob": "**/*", "input": "../docs/static/img", "output": "assets/docs/img" }
],
"styles": ["src/styles.scss"],
"scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"],
"stylePreprocessorOptions": {
@ -81,6 +86,7 @@
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "console:build:production"

View File

@ -29,6 +29,7 @@
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@grpc/grpc-js": "^1.9.3",
"@netlify/framework-info": "^9.8.10",
"@ngx-translate/core": "^15.0.0",
"angular-oauth2-oidc": "^15.0.1",
"angularx-qrcode": "^16.0.0",

View File

@ -0,0 +1,35 @@
<form>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'QUICKSTART.FRAMEWORK' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" #nameInput [formControl]="myControl" [matAutocomplete]="auto" />
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngIf="isLoading()" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option *ngFor="let framework of filteredOptions | async" [value]="framework.id">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
</div>
<span class="fill-space"></span>
<span>{{ framework.title }}</span>
</div>
</div>
</mat-option>
<mat-option *ngIf="withCustom" [value]="'other'">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper"></div>
<span class="fill-space"></span>
<span>{{ 'QUICKSTART.FRAMEWORK_OTHER' | translate }}</span>
</div>
</div>
</mat-option>
</mat-autocomplete>
</cnsl-form-field>
</form>

View File

@ -0,0 +1,69 @@
@mixin framework-autocomplete-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($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);
.full-width {
width: 100%;
}
input {
max-width: 500px;
}
.framework-option {
display: flex;
align-items: center;
.framework-option-column {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
width: 100%;
span {
line-height: normal;
}
.fill-space {
flex: 1;
}
.img-wrapper {
width: 50px;
margin-right: 1rem;
img {
width: 100%;
height: 100%;
max-width: 30px;
max-height: 30px;
object-fit: contain;
object-position: center;
}
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FrameworkAutocompleteComponent } from './framework-autocomplete.component';
describe('FrameworkAutocompleteComponent', () => {
let component: FrameworkAutocompleteComponent;
let fixture: ComponentFixture<FrameworkAutocompleteComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FrameworkAutocompleteComponent],
});
fixture = TestBed.createComponent(FrameworkAutocompleteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,63 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, signal } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { InputModule } from 'src/app/modules/input/input.module';
import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Observable, map, of, startWith, switchMap, tap } from 'rxjs';
import { Framework } from '../quickstart/quickstart.component';
@Component({
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'cnsl-framework-autocomplete',
templateUrl: './framework-autocomplete.component.html',
styleUrls: ['./framework-autocomplete.component.scss'],
imports: [
TranslateModule,
RouterModule,
MatSelectModule,
MatAutocompleteModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
FormsModule,
CommonModule,
MatButtonModule,
InputModule,
],
})
export class FrameworkAutocompleteComponent implements OnInit {
public isLoading = signal(false);
@Input() public frameworkId?: string;
@Input() public frameworks: Framework[] = [];
@Input() public withCustom: boolean = false;
public myControl: FormControl = new FormControl();
@Output() public selectionChanged: EventEmitter<string> = new EventEmitter();
public filteredOptions: Observable<Framework[]> = of([]);
constructor() {}
public ngOnInit() {
this.filteredOptions = this.myControl.valueChanges.pipe(
startWith(''),
map((value) => {
return this._filter(value || '');
}),
);
}
private _filter(value: string): Framework[] {
const filterValue = value.toLowerCase();
return this.frameworks
.filter((option) => option.id)
.filter((option) => option.title.toLowerCase().includes(filterValue));
}
public selected(event: MatAutocompleteSelectedEvent): void {
this.selectionChanged.emit(event.option.value);
}
}

View File

@ -0,0 +1,17 @@
<h2 mat-dialog-title>{{ 'QUICKSTART.DIALOG.CHANGE.TITLE' | translate }}</h2>
<mat-dialog-content>
{{ 'QUICKSTART.DIALOG.CHANGE.DESCRIPTION' | translate }}
<div class="framework-change-block">
<cnsl-framework-autocomplete
[frameworkId]="data.framework.id"
[frameworks]="data.frameworks"
(selectionChanged)="findFramework($event)"
></cnsl-framework-autocomplete>
</div>
</mat-dialog-content>
<div>
<mat-dialog-actions class="actions">
<button mat-stroked-button mat-dialog-close>{{ 'ACTIONS.CANCEL' | translate }}</button>
<button color="primary" mat-raised-button (click)="close()" cdkFocusInitial>{{ 'ACTIONS.CHANGE' | translate }}</button>
</mat-dialog-actions>
</div>

View File

@ -0,0 +1,10 @@
.framework-change-block {
display: flex;
flex-direction: column;
align-items: stretch;
}
.actions {
display: flex;
justify-content: space-between;
}

View File

@ -0,0 +1,41 @@
import { Component, Inject, signal } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import {
MAT_DIALOG_DATA,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogModule,
MatDialogRef,
MatDialogTitle,
} from '@angular/material/dialog';
import { FrameworkAutocompleteComponent } from '../framework-autocomplete/framework-autocomplete.component';
import { Framework } from '../quickstart/quickstart.component';
import { TranslateModule } from '@ngx-translate/core';
@Component({
selector: 'cnsl-framework-change-dialog',
templateUrl: './framework-change-dialog.component.html',
styleUrls: ['./framework-change-dialog.component.scss'],
standalone: true,
imports: [MatButtonModule, MatDialogModule, TranslateModule, FrameworkAutocompleteComponent],
})
export class FrameworkChangeDialogComponent {
public framework = signal<Framework | undefined>(undefined);
constructor(
public dialogRef: MatDialogRef<FrameworkChangeDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {
this.framework.set(data.framework);
}
public findFramework(id: string) {
const temp = this.data.frameworks.find((f: Framework) => f.id === id);
this.framework.set(temp);
}
public close() {
this.dialogRef.close(this.framework());
}
}

View File

@ -0,0 +1,14 @@
<div class="framework-change-wrapper">
<div class="framework-card-wrapper">
<div class="framework-card card" *ngIf="framework | async as framework">
<div [routerLink]="['/app-create']" [queryParams]="{ id: framework.docsLink }" class="">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
</div>
<span>{{ framework.title }}</span>
</div>
<button (click)="openDialog()" mat-stroked-button>
{{ 'ACTIONS.CHANGE' | translate }}
</button>
</div>
</div>

View File

@ -0,0 +1,82 @@
@mixin framework-change-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($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);
.framework-change-wrapper {
.framework-card-wrapper {
display: flex;
align-items: center;
gap: 1rem;
.framework-card {
position: relative;
flex-shrink: 0;
text-decoration: none;
border-radius: 0.5rem;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: flex;
flex-direction: row;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
// background-color: if($is-dark-theme, map-get($background, state), #e4e7e4);
// box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2);
padding: 0 0.5rem;
img {
width: 100%;
height: 100%;
max-width: 40px;
max-height: 40px;
object-fit: contain;
object-position: center;
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
span {
margin: 0.5rem;
text-align: center;
color: map-get($foreground, text);
}
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
margin-bottom: 0.5rem;
color: map-get($primary, 400);
.icon {
margin-left: 0rem;
}
}
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FrameworkChangeComponent } from './framework-change.component';
describe('FrameworkChangeComponent', () => {
let component: FrameworkChangeComponent;
let fixture: ComponentFixture<FrameworkChangeComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [FrameworkChangeComponent],
});
fixture = TestBed.createComponent(FrameworkChangeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,88 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, OnDestroy, OnInit, Output, effect, signal } from '@angular/core';
import { ActivatedRoute, Params, RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { MatButtonModule } from '@angular/material/button';
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { FrameworkAutocompleteComponent } from '../framework-autocomplete/framework-autocomplete.component';
import { Framework } from '../quickstart/quickstart.component';
import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
import {
MatDialog,
MatDialogActions,
MatDialogClose,
MatDialogContent,
MatDialogModule,
MatDialogRef,
MatDialogTitle,
} from '@angular/material/dialog';
import { FrameworkChangeDialogComponent } from './framework-change-dialog.component';
@Component({
standalone: true,
selector: 'cnsl-framework-change',
templateUrl: './framework-change.component.html',
styleUrls: ['./framework-change.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule, FrameworkAutocompleteComponent],
})
export class FrameworkChangeComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject();
public framework: BehaviorSubject<Framework | undefined> = new BehaviorSubject<Framework | undefined>(undefined);
public showFrameworkAutocomplete = signal<boolean>(false);
@Output() public frameworkChanged: EventEmitter<Framework> = new EventEmitter();
public frameworks: Framework[] = frameworkDefinition.map((f) => {
return {
...f,
fragment: '',
imgSrcDark: `assets${f.imgSrcDark}`,
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
};
});
constructor(
private activatedRoute: ActivatedRoute,
private dialog: MatDialog,
) {
this.framework.pipe(takeUntil(this.destroy$)).subscribe((value) => {
this.frameworkChanged.emit(value);
});
}
public ngOnInit() {
this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
const { framework } = params;
if (framework) {
this.findFramework(framework);
}
});
}
public ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
public findFramework(id: string) {
const temp = this.frameworks.find((f) => f.id === id);
this.framework.next(temp);
this.frameworkChanged.emit(temp);
}
public openDialog(): void {
const ref = this.dialog.open(FrameworkChangeDialogComponent, {
width: '400px',
data: {
framework: this.framework.value,
frameworks: this.frameworks,
},
});
ref.afterClosed().subscribe((resp) => {
if (resp) {
this.framework.next(resp);
}
});
}
}

View File

@ -0,0 +1,53 @@
<div class="configuration-wrapper">
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.NAME' | translate }}
</span>
<span class="right name">
<span>{{ name }}</span>
<button (click)="changeName.emit()" mat-icon-button><i class="las la-pen"></i></button>
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.TYPE' | translate }}
</span>
<span class="right">
{{ 'APP.OIDC.APPTYPE.' + configuration.appType | translate }}
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.GRANT' | translate }}
</span>
<span class="right" *ngIf="configuration.grantTypesList && configuration.grantTypesList.length > 0">
[<span *ngFor="let element of configuration.grantTypesList ?? []; index as i">
{{ 'APP.OIDC.GRANT.' + element | translate }}
{{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
</span>
<span class="right" *ngIf="configuration.responseTypesList && configuration.responseTypesList.length > 0">
[<span *ngFor="let element of configuration.responseTypesList ?? []; index as i">
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
{{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.AUTHMETHOD' | translate }}
</span>
<span class="right">
<span>
{{ 'APP.OIDC.AUTHMETHOD.' + configuration.authMethodType | translate }}
</span>
</span>
</div>
</div>

View File

@ -0,0 +1,23 @@
.configuration-wrapper {
.row {
display: flex;
justify-content: space-between;
align-items: center;
.left,
.right {
margin-bottom: 0.5rem;
font-size: 14px;
}
.name {
display: flex;
align-items: center;
button {
margin-right: -0.5rem;
margin-left: 0.25rem;
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QuickstartComponent } from './quickstart.component';
describe('QuickstartComponent', () => {
let component: QuickstartComponent;
let fixture: ComponentFixture<QuickstartComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [QuickstartComponent],
});
fixture = TestBed.createComponent(QuickstartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,35 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { MatButtonModule } from '@angular/material/button';
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { AddOIDCAppRequest } from 'src/app/proto/generated/zitadel/management_pb';
export type FrameworkDefinition = {
id?: FrameworkName | string;
title: string;
imgSrcDark: string;
imgSrcLight?: string;
docsLink: string;
external?: boolean;
};
export type Framework = FrameworkDefinition & {
fragment: string;
};
@Component({
standalone: true,
selector: 'cnsl-oidc-app-configuration',
templateUrl: './oidc-configuration.component.html',
styleUrls: ['./oidc-configuration.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
})
export class OIDCConfigurationComponent {
@Input() public name?: string;
@Input() public configuration: AddOIDCAppRequest.AsObject = new AddOIDCAppRequest().toObject();
@Output() public changeName: EventEmitter<string> = new EventEmitter();
}

View File

@ -0,0 +1,23 @@
<div class="quickstart-header">
<div class="quickstart-left">
<h2>{{ 'QUICKSTART.TITLE' | translate }}</h2>
<p class="description">{{ 'QUICKSTART.DESCRIPTION' | translate }}</p>
<div class="btn-wrapper">
<a mat-raised-button color="primary" [routerLink]="['/projects', 'app-create']">{{
'QUICKSTART.BTN_START' | translate
}}</a>
<a mat-stroked-button color="primary" href="https://zitadel.com/docs/sdk-examples/introduction" target="_blank">{{
'QUICKSTART.BTN_LEARNMORE' | translate
}}</a>
</div>
</div>
<div class="quickstart-card-wrapper">
<ng-container *ngFor="let framework of frameworks.slice(0, 18)">
<a [routerLink]="['/projects', 'app-create']" [queryParams]="{ framework: framework.id }" class="quickstart-card card">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" alt="{{ framework.title }}" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" alt="{{ framework.title }}" />
</a>
</ng-container>
</div>
</div>

View File

@ -0,0 +1,121 @@
@mixin quickstart-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($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);
.quickstart-header {
display: flex;
flex-direction: row;
margin: 0 -2rem;
padding: 2rem;
margin-bottom: 2rem;
gap: 5rem;
justify-content: space-between;
background-color: map-get($background, metadata-section);
.quickstart-left {
display: flex;
flex-direction: column;
max-width: 400px;
.btn-wrapper {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
margin: 1rem 0;
}
}
.quickstart-card-wrapper {
display: grid;
grid-template-columns: repeat(6, 1fr);
grid-column-gap: 1rem;
grid-row-gap: 1rem;
grid-auto-columns: 0;
overflow-x: hidden;
box-sizing: border-box;
max-width: 600px;
margin-left: auto;
.quickstart-card {
position: relative;
flex-shrink: 0;
text-decoration: none;
cursor: pointer;
border-radius: 0.5rem;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: flex;
flex-direction: column;
height: 60px;
width: 60px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2);
color: var(--success);
opacity: 0.8;
&:hover {
border: 2px solid var(--success);
opacity: 1;
}
img {
width: 100%;
height: 100%;
max-width: 40px;
max-height: 40px;
object-fit: contain;
object-position: center;
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
span {
margin: 0.5rem;
text-align: center;
color: map-get($foreground, text);
}
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
margin-bottom: 0.5rem;
color: map-get($primary, 400);
.icon {
margin-left: 0rem;
}
}
&:hover {
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.12);
}
}
}
}
}

View File

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { QuickstartComponent } from './quickstart.component';
describe('QuickstartComponent', () => {
let component: QuickstartComponent;
let fixture: ComponentFixture<QuickstartComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [QuickstartComponent],
});
fixture = TestBed.createComponent(QuickstartComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,42 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { MatButtonModule } from '@angular/material/button';
import { listFrameworks, hasFramework, getFramework } from '@netlify/framework-info';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { OIDC_CONFIGURATIONS } from 'src/app/utils/framework';
export type FrameworkDefinition = {
id?: FrameworkName | string;
title: string;
description?: string;
imgSrcDark: string;
imgSrcLight?: string;
docsLink: string;
external?: boolean;
};
export type Framework = FrameworkDefinition & {
fragment: string;
};
@Component({
standalone: true,
selector: 'cnsl-quickstart',
templateUrl: './quickstart.component.html',
styleUrls: ['./quickstart.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
})
export class QuickstartComponent {
public frameworks: FrameworkDefinition[] = frameworkDefinition
.filter((f) => f.id && OIDC_CONFIGURATIONS[f.id])
.map((f) => {
return {
...f,
imgSrcDark: `assets${f.imgSrcDark}`,
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
};
});
}

View File

@ -1,10 +1,17 @@
<div
class="info-section-row"
[ngClass]="{ info: type === 'INFO', warn: type === 'WARN', alert: type === 'ALERT', fit: fitWidth }"
[ngClass]="{
info: type === 'INFO',
warn: type === 'WARN',
alert: type === 'ALERT',
success: type === 'SUCCESS',
fit: fitWidth
}"
>
<i *ngIf="type === 'INFO'" class="icon las la-info"></i>
<i *ngIf="type === 'WARN'" class="icon las la-exclamation"></i>
<i *ngIf="type === 'ALERT'" class="icon las la-exclamation"></i>
<i *ngIf="type === 'SUCCESS'" class="icon las la-check-circle"></i>
<div class="info-section-content">
<ng-content></ng-content>

View File

@ -37,13 +37,19 @@ export class SearchProjectAutocompleteComponent implements OnInit, OnDestroy {
@Output() public selectionChanged: EventEmitter<{
project: Project.AsObject | GrantedProject.AsObject;
type: ProjectType;
name: string;
}> = new EventEmitter();
@Output() public valueChanged: EventEmitter<string> = new EventEmitter();
private unsubscribed$: Subject<void> = new Subject();
constructor(private mgmtService: ManagementService) {
this.myControl.valueChanges
.pipe(
takeUntil(this.unsubscribed$),
tap((value) => {
const name = typeof value === 'string' ? value : value.name ? value.name : '';
this.valueChanged.emit(name);
}),
debounceTime(200),
tap(() => (this.isLoading = true)),
switchMap((value) => {
@ -124,44 +130,6 @@ export class SearchProjectAutocompleteComponent implements OnInit, OnDestroy {
return project && project.projectName ? `${project.projectName}` : project && project.name ? `${project.name}` : '';
}
public add(event: MatChipInputEvent): void {
if (!this.matAutocomplete.isOpen) {
const input = event.chipInput?.inputElement;
const value = event.value;
if ((value || '').trim()) {
const index = this.filteredProjects.findIndex((project) => {
if (project?.projectName) {
return project.projectName === value;
} else if (project?.name) {
return project.name === value;
} else {
return false;
}
});
if (index > -1) {
if (this.projects && this.projects.length > 0) {
this.projects.push(this.filteredProjects[index]);
} else {
this.projects = [this.filteredProjects[index]];
}
}
}
if (input) {
input.value = '';
}
}
}
public remove(project: GrantedProject.AsObject): void {
const index = this.projects.indexOf(project);
if (index >= 0) {
this.projects.splice(index, 1);
}
}
public selected(event: MatAutocompleteSelectedEvent): void {
const p: Project.AsObject | GrantedProject.AsObject = event.option.value;
const type = (p as Project.AsObject).id
@ -170,8 +138,17 @@ export class SearchProjectAutocompleteComponent implements OnInit, OnDestroy {
? ProjectType.PROJECTTYPE_GRANTED
: ProjectType.PROJECTTYPE_OWNED;
const name = (p as Project.AsObject).name
? (p as Project.AsObject).name
: (p as GrantedProject.AsObject).projectName
? (p as GrantedProject.AsObject).projectName
: '';
console.log(name);
this.selectionChanged.emit({
project: p,
name,
type: type,
});
}

View File

@ -1,25 +1,53 @@
<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-framework-change *ngIf="initialParam()" (frameworkChanged)="framework.set($event)"></cnsl-framework-change>
<div class="content-wrapper" [ngClass]="{ reverse: initialParam() }">
<div>
<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
class="block"
[autocompleteType]="ProjectAutocompleteType.PROJECT_OWNED"
(selectionChanged)="selectProject($any($event))"
(valueChanged)="projectName = $any($event)"
>
</cnsl-search-project-autocomplete>
<p>{{ 'APP.PAGES.CREATE_NEW_PROJECT' | translate }}</p>
</div>
<div *ngIf="!initialParam()">
<h1>{{ 'QUICKSTART.SELECT_FRAMEWORK' | translate }}</h1>
<cnsl-framework-autocomplete
*ngIf="frameworks"
[frameworkId]="framework()?.id"
[frameworks]="frameworks"
[withCustom]="true"
(selectionChanged)="findFramework($event)"
></cnsl-framework-autocomplete>
</div>
</div>
<cnsl-info-section *ngIf="error()" [type]="InfoSectionType.WARN"
><span class="error-msg">{{ error() }}</span></cnsl-info-section
>
</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()"
[disabled]="(!project && !projectName) || !(framework() || customFramework())"
(click)="project && project.name === projectName ? goToAppIntegratePage() : createProjectAndContinue()"
>
{{ 'ACTIONS.CONTINUE' | translate }}
{{
!project && !projectName
? ('ACTIONS.CONTINUE' | translate)
: project && project.name === projectName
? ('ACTIONS.CONTINUEWITH' | translate: { value: project.name })
: ('QUICKSTART.CREATEPROJECTFORAPP' | translate: { value: projectName })
}}
</button>
</div>
</div>

View File

@ -5,6 +5,20 @@ h1 {
.app-create-main-content {
max-width: 35rem;
.content-wrapper {
display: flex;
flex-direction: column;
&.reverse {
flex-direction: column-reverse;
}
}
.error-msg {
margin-top: 0.25rem;
display: block;
}
.app-create-btn-container {
display: flex;
align-items: center;
@ -17,9 +31,4 @@ h1 {
padding: 0 4rem;
}
}
.complexity-view {
width: 100%;
margin: 0 0.5rem;
}
}

View File

@ -1,19 +1,19 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { OrgCreateComponent } from './org-create.component';
import { AppCreateComponent } from './app-create.component';
describe('OrgCreateComponent', () => {
let component: OrgCreateComponent;
let fixture: ComponentFixture<OrgCreateComponent>;
describe('AppCreateComponent', () => {
let component: AppCreateComponent;
let fixture: ComponentFixture<AppCreateComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [OrgCreateComponent],
declarations: [AppCreateComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(OrgCreateComponent);
fixture = TestBed.createComponent(AppCreateComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -1,40 +1,144 @@
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { Component, OnDestroy, signal } from '@angular/core';
import { ActivatedRoute, Navigation, Params, Router } from '@angular/router';
import { Subject, takeUntil } from 'rxjs';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { ProjectType } from 'src/app/modules/project-members/project-members-datasource';
import { ProjectAutocompleteType } from 'src/app/modules/search-project-autocomplete/search-project-autocomplete.component';
import { Project } from 'src/app/proto/generated/zitadel/project_pb';
import { AddProjectRequest, AddProjectResponse } from 'src/app/proto/generated/zitadel/management_pb';
import { GrantedProject, Project } from 'src/app/proto/generated/zitadel/project_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { Framework } from 'src/app/components/quickstart/quickstart.component';
import frameworkDefinition from '../../../../../docs/frameworks.json';
import { NavigationService } from 'src/app/services/navigation.service';
import { Location } from '@angular/common';
@Component({
selector: 'cnsl-app-create',
templateUrl: './app-create.component.html',
styleUrls: ['./app-create.component.scss'],
})
export class AppCreateComponent {
public projectId: string = '';
export class AppCreateComponent implements OnDestroy {
public InfoSectionType: any = InfoSectionType;
public project?: {
project: Project.AsObject | GrantedProject.AsObject;
type: ProjectType;
name: string;
} = undefined;
public ProjectAutocompleteType: any = ProjectAutocompleteType;
public projectName: string = '';
public error = signal('');
public framework = signal<Framework | undefined>(undefined);
public customFramework = signal<boolean>(false);
public initialParam = signal<string>('');
public destroy$: Subject<void> = new Subject();
public frameworks: Framework[] = frameworkDefinition.map((f) => {
return {
...f,
fragment: '',
imgSrcDark: `assets${f.imgSrcDark}`,
imgSrcLight: `assets${f.imgSrcLight ? f.imgSrcLight : f.imgSrcDark}`,
};
});
constructor(
private router: Router,
private mgmtService: ManagementService,
breadcrumbService: BreadcrumbService,
activatedRoute: ActivatedRoute,
private _location: Location,
private navigation: NavigationService,
) {
const bread: Breadcrumb = {
type: BreadcrumbType.ORG,
routerLink: ['/org'],
};
breadcrumbService.setBreadcrumb([bread]);
activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((params: Params) => {
const { framework } = params;
if (framework) {
this.initialParam.set(framework);
}
});
}
public goToAppCreatePage(): void {
this.router.navigate(['/projects', this.projectId, 'apps', 'create']);
public findFramework(id: string) {
if (id !== 'other') {
this.customFramework.set(false);
const temp = this.frameworks.find((f) => f.id === id);
this.framework.set(temp);
} else {
this.customFramework.set(true);
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public goToAppIntegratePage(): void {
if (this.project && this.customFramework()) {
const id = (this.project.project as Project.AsObject).id
? (this.project.project as Project.AsObject).id
: (this.project.project as GrantedProject.AsObject).projectId
? (this.project.project as GrantedProject.AsObject).projectId
: '';
this.router.navigate(['/projects', id, 'apps', 'create']);
} else if (this.project && this.framework()) {
const id = (this.project.project as Project.AsObject).id
? (this.project.project as Project.AsObject).id
: (this.project.project as GrantedProject.AsObject).projectId
? (this.project.project as GrantedProject.AsObject).projectId
: '';
this.router.navigate(['/projects', id, 'apps', 'integrate'], { queryParams: { framework: this.framework()?.id } });
}
}
public close(): void {
window.history.back();
}
public selectProject(project: Project.AsObject): void {
if (project.id) {
this.projectId = project.id;
if (this.navigation.isBackPossible) {
this._location.back();
} else {
if (this.project && this.framework()) {
const id = (this.project.project as Project.AsObject).id
? (this.project.project as Project.AsObject).id
: (this.project.project as GrantedProject.AsObject).projectId
? (this.project.project as GrantedProject.AsObject).projectId
: '';
this.router.navigate(['/projects', id]);
} else {
this.router.navigate(['/projects']);
}
}
}
public selectProject(project: {
project: Project.AsObject | GrantedProject.AsObject;
type: ProjectType;
name: string;
}): void {
if (project) {
this.project = project;
}
}
public createProjectAndContinue() {
const project = new AddProjectRequest();
project.setName(this.projectName);
return this.mgmtService
.addProject(project.toObject())
.then((resp: AddProjectResponse.AsObject) => {
this.error.set('');
this.router.navigate(['/projects', resp.id, 'apps', 'integrate'], {
queryParams: { framework: this.framework()?.id },
});
})
.catch((error) => {
const { message } = error;
this.error.set(message);
});
}
}

View File

@ -17,11 +17,15 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
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';
import { FrameworkAutocompleteComponent } from 'src/app/components/framework-autocomplete/framework-autocomplete.component';
import { FrameworkChangeComponent } from 'src/app/components/framework-change/framework-change.component';
@NgModule({
declarations: [AppCreateComponent],
imports: [
FrameworkChangeComponent,
AppCreateRoutingModule,
FrameworkAutocompleteComponent,
CommonModule,
FormsModule,
ReactiveFormsModule,
@ -35,9 +39,6 @@ import { AppCreateComponent } from './app-create.component';
HasRolePipeModule,
TranslateModule,
HasRoleModule,
MatCheckboxModule,
PasswordComplexityViewModule,
MatSlideToggleModule,
],
})
export default class AppCreateModule {}

View File

@ -2,6 +2,8 @@
<h1 class="home-title" data-e2e="authenticated-welcome">{{ 'HOME.WELCOME' | translate }}</h1>
<div class="home-wrapper enlarged-container">
<cnsl-quickstart></cnsl-quickstart>
<ng-container *ngIf="['iam.read$'] | hasRole | async; else defaultHome">
<cnsl-onboarding></cnsl-onboarding>
</ng-container>

View File

@ -19,6 +19,7 @@
.home-title {
font-size: 2rem;
margin-bottom: 1rem;
margin-top: 3rem;
}
.home-wrapper {

View File

@ -13,10 +13,12 @@ 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 { HomeComponent } from './home.component';
import { QuickstartComponent } from 'src/app/components/quickstart/quickstart.component';
@NgModule({
declarations: [HomeComponent],
imports: [
QuickstartComponent,
CommonModule,
MatIconModule,
HasRoleModule,

View File

@ -57,6 +57,13 @@
</cnsl-top-view>
<div class="max-width-container">
<cnsl-info-section *ngIf="isNew()" class="problem" [type]="InfoSectionType.INFO">
<div class="jumptoproject-row">
<span class="jumptoproject">{{ 'APP.PAGES.JUMPTOPROJECT' | translate }}</span>
<a [routerLink]="['/projects', projectId]" color="primary" mat-raised-button>{{ 'ACTIONS.CONFIGURE' | translate }}</a>
</div>
</cnsl-info-section>
<div
class="compliance"
*ngIf="app && app.oidcConfig && app.oidcConfig.complianceProblemsList && app.oidcConfig.complianceProblemsList?.length"

View File

@ -125,6 +125,16 @@
}
}
.jumptoproject-row {
display: flex;
justify-content: space-between;
.jumptoproject {
margin-top: 0.25rem;
display: block;
}
}
.compliance {
padding-bottom: 1rem;
padding-top: 0.5rem;

View File

@ -1,6 +1,6 @@
import { COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewEncapsulation, signal } from '@angular/core';
import { AbstractControl, FormControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { MatDialog } from '@angular/material/dialog';
@ -149,6 +149,7 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public settingsList: SidenavSetting[] = [{ id: 'configuration', i18nKey: 'APP.CONFIGURATION' }];
public currentSetting: string | undefined = this.settingsList[0].id;
public isNew = signal<boolean>(false);
constructor(
private envSvc: EnvironmentService,
public translate: TranslateService,
@ -245,6 +246,9 @@ export class AppDetailComponent implements OnInit, OnDestroy {
public ngOnInit(): void {
const projectId = this.route.snapshot.paramMap.get('projectid');
const appId = this.route.snapshot.paramMap.get('appid');
const isNew = this.route.snapshot.queryParamMap.get('new');
this.isNew.set(isNew === 'true');
if (projectId && appId) {
this.projectId = projectId;

View File

@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { AppCreateComponent } from '../apps/app-create/app-create.component';
import { AppDetailComponent } from '../apps/app-detail/app-detail.component';
import { IntegrateAppComponent } from './integrate/integrate.component';
const routes: Routes = [
{
@ -10,6 +11,11 @@ const routes: Routes = [
component: AppCreateComponent,
data: { animation: 'AddPage' },
},
{
path: 'integrate',
component: IntegrateAppComponent,
data: { animation: 'AddPage' },
},
{
path: ':appid',
component: AppDetailComponent,

View File

@ -43,6 +43,9 @@ import { AuthMethodDialogComponent } from './app-detail/auth-method-dialog/auth-
import { AppSecretDialogComponent } from './app-secret-dialog/app-secret-dialog.component';
import { AppsRoutingModule } from './apps-routing.module';
import { RedirectUrisComponent } from './redirect-uris/redirect-uris.component';
import { IntegrateAppComponent } from './integrate/integrate.component';
import { OIDCConfigurationComponent } from 'src/app/components/oidc-configuration/oidc-configuration.component';
import { FrameworkChangeComponent } from 'src/app/components/framework-change/framework-change.component';
@NgModule({
declarations: [
@ -50,12 +53,15 @@ import { RedirectUrisComponent } from './redirect-uris/redirect-uris.component';
AppDetailComponent,
AppSecretDialogComponent,
RedirectUrisComponent,
IntegrateAppComponent,
AdditionalOriginsComponent,
AuthMethodDialogComponent,
],
imports: [
FrameworkChangeComponent,
CommonModule,
A11yModule,
OIDCConfigurationComponent,
RedirectPipeModule,
NameDialogModule,
AppRadioModule,

View File

@ -0,0 +1,138 @@
<div class="app-integrate-wrapper">
<div class="integrate-layout-container">
<div class="max-width-container">
<div class="top-control">
<button (click)="close()" mat-icon-button matTooltip="{{ 'ACTIONS.CLOSE' | translate }}">
<mat-icon *ngIf="navigation.isBackPossible">arrow_back</mat-icon>
<mat-icon *ngIf="!navigation.isBackPossible">close</mat-icon>
</button>
<span class="abort">{{ 'APP.PAGES.CREATE' | translate }}</span>
<mat-progress-spinner
class="progress-spinner"
color="primary"
*ngIf="loading"
diameter="30"
mode="indeterminate"
></mat-progress-spinner>
</div>
</div>
</div>
<div class="max-width-container">
<div class="offset-content">
<h1>{{ 'QUICKSTART.ALMOSTDONE' | translate }}</h1>
<p>{{ 'QUICKSTART.REVIEWCONFIGURATION_DESCRIPTION' | translate: { value: framework()?.title } }}</p>
<div class="grid-layout">
<div>
<cnsl-framework-change
*ngIf="framework"
class="framework-selector"
(frameworkChanged)="setFramework($event)"
></cnsl-framework-change>
<div class="steps">
<div class="step">
<span class="step-title">{{ 'PROJECT.PAGES.TITLE' | translate }}</span>
<span>{{ projectName$ | async }}</span>
</div>
<div class="step top-border">
<a *ngIf="framework()" [href]="'https://zitadel.com' + framework()?.docsLink" target="_blank"
>{{ framework()?.title }} {{ 'QUICKSTART.GUIDE' | translate }}</a
>
<a href="https://zitadel.com/docs/sdk-examples/introduction" target="_blank">{{
'QUICKSTART.BROWSEEXAMPLES' | translate
}}</a>
</div>
</div>
</div>
<div class="card-wrapper">
<cnsl-card class="review-card" title="{{ 'QUICKSTART.REVIEWCONFIGURATION' | translate }}">
<cnsl-info-section *ngIf="showRenameWarning | async" [type]="InfoSectionType.WARN"
><span class="duplicate-name-warning">{{
'QUICKSTART.DUPLICATEAPPRENAME' | translate
}}</span></cnsl-info-section
>
<cnsl-oidc-app-configuration
*ngIf="(oidcAppRequest | async)?.toObject() as config"
[name]="(oidcAppRequest | async)?.toObject()?.name"
(changeName)="editName()"
[configuration]="config"
></cnsl-oidc-app-configuration>
<cnsl-info-section *ngIf="framework()?.description as desc" [type]="InfoSectionType.INFO">
<p class="review-description">
{{ desc }}
</p>
</cnsl-info-section>
</cnsl-card>
<cnsl-card
title="{{ 'QUICKSTART.REDIRECTS' | translate }}"
description="{{ 'APP.OIDC.REDIRECTTITLE' | translate }}"
>
<cnsl-info-section [type]="InfoSectionType.ALERT">
<span class="redirect-description">
{{ 'QUICKSTART.DEVMODEWARN' | translate }}
</span>
</cnsl-info-section>
<cnsl-info-section *ngIf="(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE">
<span class="redirect-description">
{{ 'APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate }}
</span>
</cnsl-info-section>
<cnsl-info-section
*ngIf="
(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_WEB ||
(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_USER_AGENT
"
>
<span class="redirect-description">
{{ 'APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate }}
</span>
</cnsl-info-section>
<cnsl-redirect-uris
*ngIf="requestRedirectValuesSubject$"
class="redirect-section"
[disabled]="false"
[isNative]="(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
[(ngModel)]="redirectUris"
[getValues]="requestRedirectValuesSubject$"
title="{{ 'APP.OIDC.REDIRECT' | translate }}"
[devMode]="true"
data-e2e="redirect-uris"
>
</cnsl-redirect-uris>
<cnsl-redirect-uris
*ngIf="requestRedirectValuesSubject$"
class="redirect-section"
[disabled]="false"
[(ngModel)]="postLogoutUrisList"
[getValues]="requestRedirectValuesSubject$"
title="{{ 'APP.OIDC.POSTLOGOUTREDIRECT' | translate }}"
[isNative]="(oidcAppRequest | async)?.toObject()?.appType === OIDCAppType.OIDC_APP_TYPE_NATIVE"
[devMode]="true"
data-e2e="postlogout-uris"
>
</cnsl-redirect-uris>
</cnsl-card>
</div>
</div>
<div class="app-integrate-actions">
<button
mat-raised-button
[disabled]="loading"
class="create-button"
color="primary"
(click)="createApp()"
data-e2e="create-button"
>
{{ 'ACTIONS.CREATE' | translate }}
</button>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,214 @@
h1 {
font-weight: 500;
font-size: 2rem;
}
.integrate-layout-container {
margin: 0 -2rem 2rem -2rem;
padding: 3rem 2rem 14rem 2rem;
.top-control {
display: flex;
align-items: center;
.abort {
font-size: 1.2rem;
margin-left: 1.5rem;
text-transform: uppercase;
font-size: 14px;
opacity: 0.8;
letter-spacing: 0.05em;
}
.progress-spinner {
margin-left: 2rem;
}
}
}
.offset-content {
margin-top: -14rem;
}
.grid-layout {
margin-top: 2rem;
display: grid;
grid-template-columns: [first] 300px [second] auto;
grid-column-gap: 5rem;
.framework-selector {
margin-bottom: 2rem;
display: block;
}
.steps {
margin-top: 0.5rem;
.step {
display: flex;
flex-direction: column;
padding: 1rem 0;
.step-title {
font-weight: 700;
font-size: 12px;
opacity: 0.8;
margin-bottom: 0.5rem;
text-transform: uppercase;
}
a {
margin-bottom: 0.5rem;
}
}
}
.redirect-p {
margin-top: 0;
}
.redirect-description {
font-size: 14px;
margin: 0.25rem 0 0 0;
display: block;
}
}
@media only screen and (max-width: 900px) {
.integrate-layout-container {
margin: 0 -2rem 2rem -2rem;
background: transparent;
padding: 3rem 2rem 14rem 2rem;
}
.grid-layout {
display: flex;
flex-direction: column;
.steps {
display: none;
}
}
}
.card-wrapper {
margin-top: -1rem;
.duplicate-name-warning {
margin-top: 0.25rem;
display: block;
}
}
@mixin app-integrate-theme($theme) {
$primary: map-get($theme, primary);
$warn: map-get($theme, warn);
$background: map-get($theme, background);
$accent: map-get($theme, accent);
$primary-color: map-get($primary, 500);
$warn-color: map-get($warn, 500);
$accent-color: map-get($accent, 500);
$foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$list-background-color: map-get($background, 300);
$card-background-color: map-get($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);
.integrate-layout-container {
background: map-get($background, metadata-section);
}
.review-description {
font-size: 14px;
margin: 0.25rem 0 0 0;
display: block;
}
.grid-layout {
.step {
&.top-border {
border-top: 2px solid $border-color;
}
}
a {
color: $primary-color;
}
}
.framework-card-wrapper {
display: flex;
align-items: center;
gap: 1rem;
.framework-card {
position: relative;
flex-shrink: 0;
text-decoration: none;
border-radius: 0.5rem;
box-sizing: border-box;
transition: all 0.1s ease-in;
display: flex;
flex-direction: row;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
width: fit-content;
border: 1px solid rgba(#8795a1, 0.2);
padding: 0 0.5rem;
img {
width: 100%;
height: 100%;
max-width: 40px;
max-height: 40px;
object-fit: contain;
object-position: center;
}
.dark-only {
display: if($is-dark-theme, block, none);
}
.light-only {
display: if($is-dark-theme, none, block);
}
span {
margin: 0.5rem;
text-align: center;
color: map-get($foreground, text);
}
.action-row {
display: flex;
align-items: center;
justify-content: flex-end;
font-size: 14px;
margin-bottom: 0.5rem;
color: map-get($primary, 400);
.icon {
margin-left: 0rem;
}
}
}
}
}
.app-integrate-actions {
margin-top: 2rem;
display: flex;
align-items: center;
justify-content: flex-end;
.create-button {
height: 3.5rem;
padding: 0 4rem;
}
}

View File

@ -0,0 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IntegrateAppComponent } from './integrate.component';
describe('IntegrateAppComponent', () => {
let component: IntegrateAppComponent;
let fixture: ComponentFixture<IntegrateAppComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [IntegrateAppComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(IntegrateAppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,220 @@
import { C, COMMA, ENTER, SPACE } from '@angular/cdk/keycodes';
import { StepperSelectionEvent } from '@angular/cdk/stepper';
import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit, Signal, computed, effect, signal } from '@angular/core';
import { AbstractControl, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { Buffer } from 'buffer';
import { BehaviorSubject, Subject, Subscription, combineLatest } from 'rxjs';
import { debounceTime, map, takeUntil } from 'rxjs/operators';
import { RadioItemAuthType } from 'src/app/modules/app-radio/app-auth-method-radio/app-auth-method-radio.component';
import { requiredValidator } from 'src/app/modules/form-field/validators/validators';
import {
APIAuthMethodType,
OIDCAppType,
OIDCAuthMethodType,
OIDCGrantType,
OIDCResponseType,
} from 'src/app/proto/generated/zitadel/app_pb';
import {
AddAPIAppRequest,
AddAPIAppResponse,
AddOIDCAppRequest,
AddOIDCAppResponse,
AddSAMLAppRequest,
} from 'src/app/proto/generated/zitadel/management_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import { AppSecretDialogComponent } from '../app-secret-dialog/app-secret-dialog.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
import { Framework } from 'src/app/components/quickstart/quickstart.component';
import { OIDC_CONFIGURATIONS } from 'src/app/utils/framework';
import { NavigationService } from 'src/app/services/navigation.service';
import { NameDialogComponent } from 'src/app/modules/name-dialog/name-dialog.component';
@Component({
selector: 'cnsl-integrate',
templateUrl: './integrate.component.html',
styleUrls: ['./integrate.component.scss'],
})
export class IntegrateAppComponent implements OnInit, OnDestroy {
private destroy$: Subject<void> = new Subject();
public projectId: string = '';
public loading: boolean = false;
public InfoSectionType: any = InfoSectionType;
public framework = signal<Framework | undefined>(undefined);
public showRenameWarning: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public oidcAppRequest: BehaviorSubject<AddOIDCAppRequest> = new BehaviorSubject(new AddOIDCAppRequest());
public OIDCAppType: any = OIDCAppType;
public requestRedirectValuesSubject$: Subject<void> = new Subject();
constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
private toast: ToastService,
private dialog: MatDialog,
private mgmtService: ManagementService,
private _location: Location,
private breadcrumbService: BreadcrumbService,
public navigation: NavigationService,
) {
effect(() => {
const fwId = this.framework()?.id;
const fw = this.framework();
if (fw && fwId) {
const request = OIDC_CONFIGURATIONS[fwId];
request.setProjectId(this.projectId);
request.setName(fw.title);
request.setDevMode(true);
this.requestRedirectValuesSubject$.next();
this.showRenameWarning.next(false);
this.oidcAppRequest.next(request);
return request;
} else {
const request = new AddOIDCAppRequest();
this.oidcAppRequest.next(request);
return request;
}
});
}
public projectName$ = combineLatest([this.mgmtService.ownedProjects, this.mgmtService.grantedProjects]).pipe(
map(([projects, grantedProjects]) => {
const project = projects.find((project) => project.id === this.activatedRoute.snapshot.paramMap.get('projectid'));
const grantedproject = grantedProjects.find(
(grantedproject) => grantedproject.projectId === this.activatedRoute.snapshot.paramMap.get('projectid'),
);
return project?.name ?? grantedproject?.projectName ?? '';
}),
);
public setFramework(framework: Framework | undefined) {
this.framework.set(framework);
}
public ngOnInit(): void {
const projectId = this.activatedRoute.snapshot.paramMap.get('projectid');
if (projectId) {
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.ORG,
routerLink: ['/org'],
}),
new Breadcrumb({
type: BreadcrumbType.PROJECT,
name: '',
param: { key: 'projectid', value: projectId },
routerLink: ['/projects', projectId],
isZitadel: false,
}),
];
this.projectId = projectId;
this.breadcrumbService.setBreadcrumb(breadcrumbs);
}
}
public ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public close(): void {
if (this.navigation.isBackPossible) {
this._location.back();
} else {
this.router.navigate(['/projects', this.projectId]);
}
}
public createApp(): void {
this.loading = true;
this.mgmtService
.addOIDCApp(this.oidcAppRequest.getValue())
.then((resp) => {
this.loading = false;
this.showRenameWarning.next(false);
this.toast.showInfo('APP.TOAST.CREATED', true);
if (resp.clientSecret) {
this.showSavedDialog(resp);
} else {
this.router.navigate(['projects', this.projectId, 'apps', resp.appId], { queryParams: { new: true } });
}
})
.catch((error) => {
if (error.code === 6) {
this.showRenameWarning.next(true);
}
this.loading = false;
this.toast.showError(error);
});
}
public editName() {
const dialogRef = this.dialog.open(NameDialogComponent, {
data: {
name: this.oidcAppRequest.getValue()?.getName() ?? '',
titleKey: 'APP.NAMEDIALOG.TITLE',
descKey: 'APP.NAMEDIALOG.DESCRIPTION',
labelKey: 'APP.NAMEDIALOG.NAME',
},
width: '400px',
});
dialogRef.afterClosed().subscribe((name) => {
if (name && name !== this.framework()?.title) {
const request = this.oidcAppRequest.getValue();
request.setName(name);
this.showRenameWarning.next(false);
this.oidcAppRequest.next(request);
}
});
}
public showSavedDialog(added: AddOIDCAppResponse.AsObject | AddAPIAppResponse.AsObject): void {
let clientSecret = '';
if (added.clientSecret) {
clientSecret = added.clientSecret;
}
let clientId = '';
if (added.clientId) {
clientId = added.clientId;
}
const dialogRef = this.dialog.open(AppSecretDialogComponent, {
data: {
clientSecret: clientSecret,
clientId: clientId,
},
});
dialogRef.afterClosed().subscribe(() => {
this.router.navigate(['projects', this.projectId, 'apps', added.appId], { queryParams: { new: true } });
});
}
public get redirectUris() {
return this.oidcAppRequest.getValue().toObject().redirectUrisList;
}
public set redirectUris(value: string[]) {
const request = this.oidcAppRequest.getValue();
request.setRedirectUrisList(value);
this.oidcAppRequest.next(request);
}
public get postLogoutUrisList() {
return this.oidcAppRequest.getValue().toObject().postLogoutRedirectUrisList;
}
public set postLogoutUrisList(value: string[]) {
const request = this.oidcAppRequest.getValue();
request.setPostLogoutRedirectUrisList(value);
this.oidcAppRequest.next(request);
}
}

View File

@ -1,7 +1,7 @@
import { DatePipe } from '@angular/common';
import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment';
import moment from 'moment';
import { supportedLanguages } from 'src/app/utils/language';
@Pipe({

View File

@ -0,0 +1,70 @@
import { Framework } from '@netlify/framework-info/lib/types';
import { AddOIDCAppRequest } from '../proto/generated/zitadel/management_pb';
import { FrameworkName } from '@netlify/framework-info/lib/generated/frameworkNames';
import { OIDCAppType, OIDCAuthMethodType, OIDCGrantType, OIDCResponseType } from '../proto/generated/zitadel/app_pb';
type OidcAppConfigurations = {
[framework: string]: AddOIDCAppRequest;
};
export const OIDC_CONFIGURATIONS: OidcAppConfigurations = {
// user agent applications
['angular']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_USER_AGENT)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:4200/callback'])
.setPostLogoutRedirectUrisList(['http://localhost:4200']),
['react']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_USER_AGENT)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:3000/callback'])
.setPostLogoutRedirectUrisList(['http://localhost:3000']),
['vue']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_USER_AGENT)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:5173/auth/signinwin/zitadel'])
.setPostLogoutRedirectUrisList(['http://localhost:5173']),
// web applications
['next']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:3000/callback'])
.setPostLogoutRedirectUrisList(['http://localhost:3000']),
['java']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:18080/webapp/login/oauth2/code/zitadel'])
.setPostLogoutRedirectUrisList(['http://localhost:18080/webapp']),
['symfony']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_BASIC)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:8000/login_check'])
.setPostLogoutRedirectUrisList(['http://localhost:8000/logout']),
['django']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_WEB)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:8000/oidc/callback/'])
.setPostLogoutRedirectUrisList(['http://localhost:8000/oidc/logout/ ']),
// native
['flutter']: new AddOIDCAppRequest()
.setAppType(OIDCAppType.OIDC_APP_TYPE_NATIVE)
.setAuthMethodType(OIDCAuthMethodType.OIDC_AUTH_METHOD_TYPE_NONE)
.setResponseTypesList([OIDCResponseType.OIDC_RESPONSE_TYPE_CODE])
.setGrantTypesList([OIDCGrantType.OIDC_GRANT_TYPE_AUTHORIZATION_CODE])
.setRedirectUrisList(['http://localhost:4444/auth.html', 'com.example.zitadelflutter'])
.setPostLogoutRedirectUrisList(['http://localhost:4444', 'com.example.zitadelflutter']),
};

View File

@ -119,6 +119,30 @@
"SETTINGS": "Настройки",
"CUSTOMERPORTAL": "Портал за клиенти"
},
"QUICKSTART": {
"TITLE": "Интегрирайте ZITADEL във вашето приложение",
"DESCRIPTION": "Интегрирайте ZITADEL във вашето приложение или използвайте някой от нашите образци, за да започнете за минути.",
"BTN_START": "Създаване на приложение",
"BTN_LEARNMORE": "Научете повече",
"CREATEPROJECTFORAPP": "Създаване на проект {{value}}",
"SELECT_FRAMEWORK": "Изберете рамка",
"FRAMEWORK": "Рамка",
"FRAMEWORK_OTHER": "други (OIDC, SAML, API)",
"ALMOSTDONE": "Почти сте готови.",
"REVIEWCONFIGURATION": "Конфигурация на прегледа",
"REVIEWCONFIGURATION_DESCRIPTION": "Създадохме основна конфигурация за {{value}} приложения. След създаването можете да адаптирате тази конфигурация към вашите нужди.",
"REDIRECTS": "Конфигуриране на пренасочвания",
"DEVMODEWARN": "Режимът Dev е активиран по подразбиране. Можете да актуализирате стойностите за производство по-късно.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Преглед на примери",
"DUPLICATEAPPRENAME": "Вече съществува приложение със същото име. Моля, изберете друго име.",
"DIALOG": {
"CHANGE": {
"TITLE": "Рамка за промяна",
"DESCRIPTION": "Изберете една от наличните рамки за бърза настройка на вашето приложение."
}
}
},
"ACTIONS": {
"ACTIONS": "Действия",
"FILTER": "Филтър",
@ -138,6 +162,7 @@
"ADD": "Добавете",
"CREATE": "Създавайте",
"CONTINUE": "продължи",
"CONTINUEWITH": "Продължете с {{value}}",
"BACK": "обратно",
"CLOSE": "Близо",
"CLEAR": "ясно",
@ -1952,6 +1977,7 @@
"DATECHANGED": "Променен",
"URLS": "URL адреси",
"DELETE": "Изтриване на приложение",
"JUMPTOPROJECT": "За да конфигурирате роли, оторизации и други, отидете до проекта.",
"DETAIL": {
"TITLE": "детайл",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Nastavení",
"CUSTOMERPORTAL": "Zákaznický portál"
},
"QUICKSTART": {
"TITLE": "Integrovat ZITADEL do vaší aplikace",
"DESCRIPTION": "Integrujte ZITADEL do své aplikace nebo použijte některou z našich ukázek a začněte během několika minut.",
"BTN_START": "Vytvořit aplikaci",
"BTN_LEARNMORE": "Zjistěte více",
"CREATEPROJECTFORAPP": "Vytvořit projekt {{value}}",
"SELECT_FRAMEWORK": "Vybrat framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "jiný (OIDC, SAML, API)",
"ALMOSTDONE": "Jste téměř hotovi.",
"REVIEWCONFIGURATION": "Přezkoumání konfigurace",
"REVIEWCONFIGURATION_DESCRIPTION": "Vytvořili jsme základní konfiguraci pro {{hodnota}} aplikace. Tuto konfiguraci můžete po vytvoření přizpůsobit svým potřebám.",
"REDIRECTS": "Konfigurace přesměrování",
"DEVMODEWARN": "Dev Mode je ve výchozím nastavení povolen. Hodnoty pro produkci můžete aktualizovat později.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Procházet ukázky",
"DUPLICATEAPPRENAME": "Aplikace se stejným neme již existuje. Vyberte prosím jiný název.",
"DIALOG": {
"CHANGE": {
"TITLE": "Rámec změn",
"DESCRIPTION": "Vyberte si jeden z dostupných frameworků pro rychlé nastavení vaší aplikace."
}
}
},
"ACTIONS": {
"ACTIONS": "Akce",
"FILTER": "Filtr",
@ -138,6 +162,7 @@
"ADD": "Přidat",
"CREATE": "Vytvořit",
"CONTINUE": "Pokračovat",
"CONTINUEWITH": "Pokračovat s {{value}}",
"BACK": "Zpět",
"CLOSE": "Zavřít",
"CLEAR": "Vyčistit",
@ -1971,6 +1996,7 @@
"DATECHANGED": "Změněno",
"URLS": "URL adresy",
"DELETE": "Smazat aplikaci",
"JUMPTOPROJECT": "Chcete-li nakonfigurovat role, oprávnění a další, přejděte do projektu.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Einstellungen",
"CUSTOMERPORTAL": "Kundenportal"
},
"QUICKSTART": {
"TITEL": "Integriere ZITADEL in deine Anwendung",
"DESCRIPTION": "Integriere ZITADEL in deine Anwendung oder verwende eines unserer Beispiele, um in wenigen Minuten loszulegen.",
"BTN_START": "Anwendung erstellen",
"BTN_LEARNMORE": "Mehr erfahren",
"CREATEPROJECTFORAPP": "Projekt erstellen {{value}}",
"SELECT_FRAMEWORK": "Framework auswählen",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Andere (OIDC, SAML, API)",
"ALMOSTDONE": "Wir sind fast fertig.",
"REVIEWCONFIGURATION": "Konfiguration überprüfen",
"REVIEWCONFIGURATION_DESCRIPTION": "Wir haben eine Grundkonfiguration für {{wert}}-Anwendungen erstellt. Du kannst diese Konfiguration nach der Erstellung an deine Bedürfnisse anpassen.",
"REDIRECTS": "Redirects konfigurieren",
"DEVMODEWARN": "Der Dev-Modus ist standardmäßig aktiviert. Sie können Werte für die Produktion später aktualisieren.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Beispiele durchsuchen",
"DUPLICATEAPPRENAME": "Es gibt bereits eine App mit demselben Neme. Bitte wählen Sie einen anderen Namen.",
"DIALOG": {
"CHANGE": {
"TITLE": "Framework ändern",
"DESCRIPTION": "Wähle eines der verfügbaren Frameworks für die schnelle Einrichtung deiner Anwendung."
}
}
},
"ACTIONS": {
"ACTIONS": "Aktionen",
"FILTER": "Filter",
@ -138,6 +162,7 @@
"ADD": "Hinzufügen",
"CREATE": "Erstellen",
"CONTINUE": "Weiter",
"CONTINUEWITH": "Mit {{value}} fortfahren",
"BACK": "Zurück",
"CLOSE": "Schliessen",
"CLEAR": "Zurücksetzen",
@ -1961,6 +1986,7 @@
"DATECHANGED": "Geändert",
"URLS": "URLs",
"DELETE": "App löschen",
"JUMPTOPROJECT": "Um Rollen, Berechtigungen und mehr zu konfigurieren, navigieren Sie zum Projekt.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -43,7 +43,7 @@
}
},
"ONBOARDING": {
"DESCRIPTION": "Your onboarding process",
"DESCRIPTION": "Your next steps",
"MOREDESCRIPTION": "more shortcuts",
"COMPLETED": "completed",
"DISMISS": "No thanks, I'm a pro.",
@ -119,6 +119,30 @@
"SETTINGS": "Settings",
"CUSTOMERPORTAL": "Customer Portal"
},
"QUICKSTART": {
"TITLE": "Integrate ZITADEL into your application",
"DESCRIPTION": "Integrate ZITADEL into your application or use one of our samples to get started in minutes.",
"BTN_START": "Create Application",
"BTN_LEARNMORE": "Learn More",
"CREATEPROJECTFORAPP": "Create Project {{value}}",
"SELECT_FRAMEWORK": "Select Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Other (OIDC, SAML, API)",
"ALMOSTDONE": "You're almost done.",
"REVIEWCONFIGURATION": "Review Configuration",
"REVIEWCONFIGURATION_DESCRIPTION": "We've created a basic configuration for {{value}} applications. You can adapt this configuration to your needs after creation.",
"REDIRECTS": "Configure redirects",
"DEVMODEWARN": "Dev Mode is enabled by default. You can update values for production later.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Browse Examples and SDKs",
"DUPLICATEAPPRENAME": "An app with the same name exists already. Please choose a different name.",
"DIALOG": {
"CHANGE": {
"TITLE": "Change Framework",
"DESCRIPTION": "Choose one of the available frameworks for quick setup of your application."
}
}
},
"ACTIONS": {
"ACTIONS": "Actions",
"FILTER": "Filter",
@ -138,6 +162,7 @@
"ADD": "Add",
"CREATE": "Create",
"CONTINUE": "Continue",
"CONTINUEWITH": "Continue with {{value}}",
"BACK": "Back",
"CLOSE": "Close",
"CLEAR": "Clear",
@ -1972,7 +1997,7 @@
"DESCRIPTION": "Here you can edit your application data and it's configuration.",
"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_NEW_PROJECT": "or enter the name for your new project",
"CREATE_DESC_TITLE": "Enter Your Application Details Step by Step",
"CREATE_DESC_SUB": "A recommended configuration will be automatically generated.",
"STATE": "Status",
@ -1980,6 +2005,7 @@
"DATECHANGED": "Changed",
"URLS": "URLs",
"DELETE": "Delete App",
"JUMPTOPROJECT": "To configure roles, authorizations and more, navigate to the project.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Ajustes",
"CUSTOMERPORTAL": "Portal del cliente"
},
"QUICKSTART": {
"TITLE": "Integra ZITADEL en tu aplicación",
"DESCRIPTION": "Integra ZITADEL en tu aplicación o utiliza uno de nuestros ejemplos para empezar en cuestión de minutos",
"BTN_START": "Crear aplicación",
"BTN_LEARNMORE": "Aprende más",
"CREATEPROJECTFORAPP": "Crear proyecto {{value}}",
"SELECT_FRAMEWORK": "Seleccionar marco",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Otro (OIDC, SAML, API)",
"ALMOSTDONE": "Ya casi has terminado",
"REVIEWCONFIGURATION": "Configuración de revisión",
"REVIEWCONFIGURATION_DESCRIPTION": "Hemos creado una configuración básica para aplicaciones {{value}}. Puedes adaptar esta configuración a tus necesidades después de crearla.",
"REDIRECTS": "Configurar redirecciones",
"DEVMODEWARN": "El modo de desarrollo está habilitado de forma predeterminada. Puede actualizar los valores para la producción más adelante.",
"GUIDE": "Guía",
"BROWSEEXAMPLES": "Explora ejemplos",
"DUPLICATEAPPRENAME": "Ya existe una aplicación con el mismo nombre. Por favor, elige un nombre diferente.",
"DIALOG": {
"CAMBIAR": {
"TITLE": "Cambio framework",
"DESCRIPTION": "Elige uno de los frameworks disponibles para configurar rápidamente tu aplicación."
}
}
},
"ACTIONS": {
"ACTIONS": "Acciones",
"FILTER": "Filtrar",
@ -138,6 +162,7 @@
"ADD": "Añadir",
"CREATE": "Crear",
"CONTINUE": "Continuar",
"CONTINUEWITH": "Continuar con {{value}}",
"BACK": "Atrás",
"CLOSE": "Cerrar",
"CLEAR": "Limpiar",
@ -1959,6 +1984,7 @@
"DATECHANGED": "Cambiada",
"URLS": "URLs",
"DELETE": "Borrar App",
"JUMPTOPROJECT": "Para configurar roles, autorizaciones y más, navegue hasta el proyecto.",
"DETAIL": {
"TITLE": "Detalle",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Paramètres",
"CUSTOMERPORTAL": "Customer Portal"
},
"QUICKSTART": {
"TITLE": "Intégrer ZITADEL dans votre application",
"DESCRIPTION": "Intégrez ZITADEL dans votre application ou utilisez l'un de nos échantillons pour démarrer en quelques minutes",
"BTN_START": "Créer une application",
"BTN_LEARNMORE": "En savoir plus",
"CREATEPROJECTFORAPP": "Créer un projet {{value}}",
"SELECT_FRAMEWORK": "Select Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Autre (OIDC, SAML, API)",
"ALMOSTDONE": "Vous avez presque terminé",
"REVIEWCONFIGURATION": "Review Configuration",
"REVIEWCONFIGURATION_DESCRIPTION": "Nous avons créé une configuration de base pour les applications {{value}}. Vous pouvez adapter cette configuration à vos besoins après sa création.",
"REDIRECTS": "Configurer les redirections",
"DEVMODEWARN": "Le mode développement est activé par défaut. Vous pouvez mettre à jour les valeurs pour la production ultérieurement.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Parcourez les exemples",
"DUPLICATEAPPRENAME": "Une application avec ce nom existe déjà. Veuillez choisir un autre nom.",
"DIALOG": {
"CHANGE": {
"TITLE": "Cadre de changement",
"DESCRIPTION": "Choisissez l'un des frameworks disponibles pour une configuration rapide de votre application."
}
}
},
"ACTIONS": {
"ACTIONS": "Actions",
"FILTER": "Filtrer",
@ -138,6 +162,7 @@
"ADD": "Ajouter",
"CREATE": "Créer",
"CONTINUE": "Continuer",
"CONTINUEWITH": "Continuez avec {{value}}",
"BACK": "Retour",
"CLOSE": "Fermer",
"CLEAR": "Effacer",
@ -1962,6 +1987,7 @@
"DATECHANGED": "Modifié",
"URLS": "URLs",
"DELETE": "Supprimer l'application",
"JUMPTOPROJECT": "Pour configurer des rôles, des autorisations et bien plus encore, accédez au projet.",
"DETAIL": {
"TITLE": "Détail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Impostazioni",
"CUSTOMERPORTAL": "Customer Portal"
},
"QUICKSTART": {
"TITLE": "Integra ZITADEL nella tua applicazione",
"DESCRIPTION": "Integra ZITADEL nella tua applicazione o utilizza uno dei nostri esempi per iniziare in pochi minuti",
"BTN_START": "Crea applicazione",
"BTN_LEARNMORE": "Mostra di più",
"CREATEPROJECTFORAPP": "Crea progetto {{value}}",
"SELECT_FRAMEWORK": "Seleziona Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Altro (OIDC, SAML, API)",
"ALMOSTDONE": "Hai quasi finito",
"REVIEWCONFIGURATION": "Controlla la configurazione",
"REVIEWCONFIGURATION_DESCRIPTION": "Abbiamo creato una configurazione base per le applicazioni {{value}}. Può adattare la configurazione alle proprie esigenze dopo la creazione.",
"REDIRECTS": "Configura i reindirizzamenti",
"DEVMODEWARN": "La modalità sviluppatore è abilitata per impostazione predefinita. È possibile aggiornare i valori per la produzione in un secondo momento.",
"GUIDE": "Guida",
"BROWSEEXAMPLES": "Esplora esempi e SDK",
"DUPLICATEAPPRENAME": "Un'app con lo stesso nome esiste già. Scegli un nome diverso.",
"DIALOG": {
"CHANGE": {
"TITLE": "Modifica framework",
"DESCRIPTION": "Scegliere uno dei framework disponibili per configurare rapidamente la propria applicazione"
}
}
},
"ACTIONS": {
"ACTIONS": "Azioni",
"FILTER": "Filtra",
@ -138,6 +162,7 @@
"ADD": "Aggiungi",
"CREATE": "Crea",
"CONTINUE": "Continua",
"CONTINUEWITH": "Continue con {{value}}",
"BACK": "Indietro",
"CLOSE": "chiudi",
"CLEAR": "Resetta",
@ -1962,6 +1987,7 @@
"DATECHANGED": "Cambiato",
"URLS": "URLs",
"DELETE": "Rimuovi App",
"JUMPTOPROJECT": "Per configurare ruoli, autorizzazioni e altro, vai al progetto.",
"DETAIL": {
"TITLE": "Dettagli",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "設定",
"CUSTOMERPORTAL": "カスタマーポータル"
},
"QUICKSTART": {
"TITLE": "ZITADELをアプリケーションに統合する",
"DESCRIPTION": "あなたのアプリケーションにZITADELを組み込むか、当社のサンプルを使って数分で始められます。",
"BTN_START": "アプリケーションの作成",
"BTN_LEARNMORE": "さらに詳しく",
"CREATEPROJECTFORAPP": "プロジェクトの作成 {{value}}",
"SELECT_FRAMEWORK": "フレームワークを選択",
"FRAMEWORK": "フレームワーク",
"FRAMEWORK_OTHER": "他の (OIDC, SAML, API)",
"ALMOSTDONE": "あと少しだ。",
"REVIEWCONFIGURATION": "レビュー構成",
"REVIEWCONFIGURATION_DESCRIPTION": "ここでは{{value}}アプリケーションの基本設定を作成しました。この構成は作成後にあなたのニーズに合わせることができます。",
"REDIRECTS": "リダイレクトの設定",
"DEVMODEWARN": "開発モードはデフォルトで有効になっています。実稼働用の値は後で更新できます。",
"GUIDE": "ガイド",
"BROWSEEXAMPLES": "サンプルを見る",
"DUPLICATEAPPRENAME": "同じ名前のアプリがすでに存在します。別の名前を選択してください。",
"DIALOG": {
"CHANGE": {
"TITLE": "変更の枠組み",
"DESCRIPTION": "利用可能なフレームワークのいずれかを選択して、アプリケーションをすばやくセットアップできます。"
}
}
},
"ACTIONS": {
"ACTIONS": "アクション",
"FILTER": "絞り込み",
@ -138,6 +162,7 @@
"ADD": "追加",
"CREATE": "作成",
"CONTINUE": "次へ",
"CONTINUEWITH": "{{value}} に進みます",
"BACK": "戻る",
"CLOSE": "閉じる",
"CLEAR": "消去する",
@ -1953,6 +1978,7 @@
"DATECHANGED": "更新日",
"URLS": "URL",
"DELETE": "アプリを削除する",
"JUMPTOPROJECT": "ロール、権限などを構成するには、プロジェクトに移動します。",
"DETAIL": {
"TITLE": "詳細",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Подесувања",
"CUSTOMERPORTAL": "Портал за клиенти"
},
"QUICKSTART": {
"TITLE": "Интегрирајте го ZITADEL во вашата апликација",
"DESCRIPTION": "Интегрирајте го ZITADEL во вашата апликација или користете еден од нашите примероци за да започнете за неколку минути.",
"BTN_START": "Креирај апликација",
"BTN_LEARNMORE": "Научи повеќе",
"CREATEPROJECTFORAPP": "Креирај проект {{value}}",
"SELECT_FRAMEWORK": "Изберете Рамка",
"FRAMEWORK": "Рамка",
"FRAMEWORK_OTHER": "Друго (OIDC, SAML, API)",
"ALMOSTDONE": "Речиси сте готови.",
"REVIEWCONFIGURATION": "Прегледајте ја конфигурацијата",
"REVIEWCONFIGURATION_DESCRIPTION": "Создадовме основна конфигурација за {{value}} апликации. Можете да ја прилагодите оваа конфигурација на вашите потреби по креирањето.",
"REDIRECTS": "Конфигурирајте пренасочувања",
"DEVMODEWARN": "Режимот на развој е стандардно овозможен. Може да ги ажурирате вредностите за производство подоцна.",
"GUIDE": "Водич",
"BROWSEEXAMPLES": "Прегледај примероци",
"DUPLICATEAPPRENAME": "Веќе постои апликација со истиот непријател. Ве молиме изберете друго име.",
"DIALOG": {
"CHANGE": {
"TITLE": "Променете ја рамката",
"DESCRIPTION": "Изберете една од достапните рамки за брзо поставување на вашата апликација."
}
}
},
"ACTIONS": {
"ACTIONS": "Акции",
"FILTER": "Филтер",
@ -138,6 +162,7 @@
"ADD": "Додади",
"CREATE": "Креирај",
"CONTINUE": "Продолжи",
"CONTINUEWITH": "Продолжи со {{value}}",
"BACK": "Назад",
"CLOSE": "Затвори",
"CLEAR": "Исчисти",
@ -1959,6 +1984,7 @@
"DATECHANGED": "Изменето",
"URLS": "URLs",
"DELETE": "Избриши апликација",
"JUMPTOPROJECT": "За да ги конфигурирате улогите, овластувањата и друго, одете до проектот.",
"DETAIL": {
"TITLE": "Детали",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Instellingen",
"CUSTOMERPORTAL": "Klantenportaal"
},
"QUICKSTART": {
"TITLE": "Integreer ZITADEL in uw toepassing",
"DESCRIPTION": "Integreer ZITADEL in uw toepassing of gebruik een van onze voorbeelden om binnen enkele minuten aan de slag te gaan.",
"BTN_START": "Applicatie maken",
"BTN_LEARNMORE": "Meer informatie",
"CREATEPROJECTFORAPP": "Creëer Project {{value}}",
"SELECT_FRAMEWORK": "Kies Framework",
"FRAMEWORK": "Framework",
"FRAMEWORK_OTHER": "Ander (OIDC, SAML, API)",
"ALMOSTDONE": "Je bent bijna klaar",
"REVIEWCONFIGURATION": "Reviewconfiguratie",
"REVIEWCONFIGURATION_DESCRIPTION": "We hebben een basisconfiguratie gemaakt voor {{value}} toepassingen. Je kunt deze configuratie na het aanmaken aanpassen aan je behoeften.",
"REDIRECTS": "Configureer omleidingen",
"DEVMODEWARN": "Dev-modus is standaard ingeschakeld. U kunt waarden voor productie later bijwerken.",
"GUIDE": "Guide",
"BROWSEEXAMPLES": "Bekijk voorbeelden",
"DUPLICATEAPPRENAME": "Er bestaat al een app met dezelfde aartsvijand. Kies een andere naam.",
"DIALOG": {
"CHANGE": {
"TITLE": "Verander Framework",
"DESCRIPTION": "Kies een van de beschikbare frameworks voor het snel instellen van je applicatie."
}
}
},
"ACTIONS": {
"ACTIONS": "Acties",
"FILTER": "Filter",
@ -138,6 +162,7 @@
"ADD": "Toevoegen",
"CREATE": "Aanmaken",
"CONTINUE": "Doorgaan",
"CONTINUEWITH": "Ga verder met {{value}}",
"BACK": "Terug",
"CLOSE": "Sluiten",
"CLEAR": "Leegmaken",
@ -1980,6 +2005,7 @@
"DATECHANGED": "Gewijzigd",
"URLS": "URL's",
"DELETE": "Verwijder App",
"JUMPTOPROJECT": "Om rollen, autorisaties en meer te configureren, navigeert u naar het project.",
"DETAIL": {
"TITLE": "Detail",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Ustawienia",
"CUSTOMERPORTAL": "Portal klienta"
},
"QUICKSTART": {
"TITLE": "Zintegruj ZITADEL ze swoją aplikacją",
"DESCRIPTION": "Zintegruj ZITADEL ze swoją aplikacją lub skorzystaj z jednego z naszych przykładów, aby rozpocząć pracę w ciągu kilku minut",
"BTN_START": "Utwórz aplikację",
"BTN_LEARNMORE": "Dowiedz się więcej",
"CREATEPROJECTFORAPP": "Utwórz projekt {{value}}",
"SELECT_FRAMEWORK": "Wybierz Framework",
"FRAMEWORK": "Ramy",
"FRAMEWORK_OTHER": "Inny (OIDC, SAML, API)",
"ALMOSTDONE": "Już prawie skończyłeś",
"REVIEWCONFIGURATION": "Konfiguracja recenzji",
"REVIEWCONFIGURATION_DESCRIPTION": "Stworzyliśmy podstawową konfigurację dla aplikacji {{value}}. Możesz dostosować tę konfigurację do swoich potrzeb po jej utworzeniu.",
"REDIRECTS": "Skonfiguruj przekierowania",
"DEVMODEWARN": "Tryb deweloperski jest domyślnie włączony. Możesz później zaktualizować wartości dla produkcji.",
"GUIDE": "Przewodnik",
"BROWSEEXAMPLES": "Przeglądaj przykłady",
"DUPLICATEAPPRENAME": "Aplikacja o tej samej nazwie już istnieje. Proszę wybrać inną nazwę.",
"DIALOG": {
"CHANGE": {
"TITLE": "Ramy zmian",
"DESCRIPTION": "Wybierz jeden z dostępnych frameworków, aby szybko skonfigurować swoją aplikację"
}
}
},
"ACTIONS": {
"ACTIONS": "Akcje",
"FILTER": "Filtruj",
@ -138,6 +162,7 @@
"ADD": "Dodaj",
"CREATE": "Utwórz",
"CONTINUE": "Kontynuuj",
"CONTINUEWITH": "Kontynuuj z {{value}}",
"BACK": "Wstecz",
"CLOSE": "Zamknij",
"CLEAR": "Wyczyść",
@ -1962,6 +1987,7 @@
"DATECHANGED": "Zmienione",
"URLS": "Adresy URL",
"DELETE": "Usuń aplikację",
"JUMPTOPROJECT": "Aby skonfigurować role, uprawnienia i nie tylko, przejdź do projektu.",
"DETAIL": {
"TITLE": "Szczegóły",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "Configurações",
"CUSTOMERPORTAL": "Portal do Cliente"
},
"QUICKSTART": {
"TITLE": "Integrar a ZITADEL na sua aplicação",
"DESCRIÇÃO": "Integre a ZITADEL na sua aplicação ou utilize uma das nossas amostras para começar em minutos.",
"BTN_START": "Criar aplicação",
"BTN_LEARNMORE": "Saiba mais",
"CREATEPROJECTFORAPP": "Criar projeto {{value}}",
"SELECT_FRAMEWORK": "Selecionar estrutura",
"FRAMEWORK": "Estrutura",
"FRAMEWORK_OTHER": "Outro (OIDC, SAML, API)",
"ALMOSTDONE": "Está quase a terminar",
"REVIEWCONFIGURATION": "Configuração de revisão",
"REVIEWCONFIGURATION_DESCRIPTION": "Criámos uma configuração básica para aplicações {{value}}. Pode adaptar esta configuração às suas necessidades após a criação.",
"REDIRECTS": "Configurar redireccionamentos",
"DEVMODEWARN": "O modo Dev está habilitado por padrão. Você pode atualizar os valores para produção posteriormente.",
"GUIDE": "Guia",
"BROWSEEXAMPLES": "Navegar por exemplos",
"DUPLICATEAPPRENAME": "Já existe um aplicativo com o mesmo neme. Escolha um nome diferente.",
"DIALOG": {
"CHANGE": {
"TITLE": "Alterar Quadro",
"DESCRIÇÃO": "Escolha uma das estruturas disponíveis para uma configuração rápida da sua aplicação."
}
}
},
"ACTIONS": {
"ACTIONS": "Ações",
"FILTER": "Filtrar",
@ -138,6 +162,7 @@
"ADD": "Adicionar",
"CREATE": "Criar",
"CONTINUE": "Continuar",
"CONTINUEWITH": "Continue com {{value}}",
"BACK": "Voltar",
"CLOSE": "Fechar",
"CLEAR": "Limpar",
@ -1957,6 +1982,7 @@
"DATECHANGED": "Alterado",
"URLS": "URLs",
"DELETE": "Excluir App",
"JUMPTOPROJECT": "Para configurar funções, autorizações e muito mais, navegue até o projeto.",
"DETAIL": {
"TITLE": "Detalhe",
"STATE": {

View File

@ -115,6 +115,30 @@
"SETTINGS": "Настройки",
"CUSTOMERPORTAL": "Клиентский портал"
},
"QUICKSTART": {
"TITLE": "Интегрируйте ZITADEL в свое приложение",
"ОПИСАНИЕ": "Интегрируйте ZITADEL в свое приложение или воспользуйтесь одним из наших образцов, чтобы начать работу за считанные минуты",
"BTN_START": "Создать приложение",
"BTN_LEARNMORE": "Узнать больше",
"CREATEPROJECTFORAPP": "Создать проект {{value}}",
"SELECT_FRAMEWORK": "Выбрать фреймворк",
"FRAMEWORK": "Рамка",
"FRAMEWORK_OTHER": "Другое (OIDC, SAML, API)",
"ALMOSTDONE": "Вы почти закончили",
"REVIEWCONFIGURATION": "Конфигурация обзора",
"REVIEWCONFIGURATION_DESCRIPTION": "Мы создали базовую конфигурацию для {{value}} приложений. После создания вы можете адаптировать эту конфигурацию под свои нужды.",
"REDIRECTS": "Настроить редиректы",
"DEVMODEWARN": "Режим разработки включен по умолчанию. Значения для производства можно обновить позже.",
"GUIDE": "Руководство",
"BROWSEEXAMPLES": "Просмотреть примеры",
"DUPLICATEAPPRENAME": "Приложение с таким названием уже существует. Пожалуйста, выберите другое имя.",
"DIALOG": {
"CHANGE": {
"TITLE": "Рамки изменений",
"ОПИСАНИЕ": "Выберите один из доступных фреймворков для быстрой настройки вашего приложения"
}
}
},
"ACTIONS": {
"ACTIONS": "Действия",
"FILTER": "Фильтр",
@ -134,6 +158,7 @@
"ADD": "Добавить",
"CREATE": "Создать",
"CONTINUE": "Продолжить",
"CONTINUEWITH": "Продолжить с {{value}}",
"BACK": "Назад",
"CLOSE": "Закрыть",
"CLEAR": "Очистить",
@ -1956,6 +1981,7 @@
"DATECHANGED": "Дата изменения",
"URLS": "URL-адреса",
"DELETE": "Удалить приложение",
"JUMPTOPROJECT": "Чтобы настроить роли, полномочия и многое другое, перейдите к проекту.",
"DETAIL": {
"TITLE": "Деталь",
"STATE": {

View File

@ -119,6 +119,30 @@
"SETTINGS": "设置",
"CUSTOMERPORTAL": "客户门户网站"
},
"QUICKSTART": {
"TITLE": "将 ZITADEL 集成到您的应用程序中",
"DESCRIPTION": "将 ZITADEL 集成到您的应用程序中,或使用我们的示例,几分钟内即可开始使用。",
"BTN_START": "创建应用程序",
"BTN_LEARNMORE": "了解更多",
"CREATEPROJECTFORAPP": "创建项目 {{value}}",
"SELECT_FRAMEWORK": "选择框架",
"FRAMEWORK": "框架",
"FRAMEWORK_OTHER": "其他 (OIDC, SAML, API)",
"ALMOSTDONE": "就快好了",
"REVIEWCONFIGURATION": "审查配置",
"REVIEWCONFIGURATION_DESCRIPTION": "我们为 {{value}} 应用程序创建了一个基本配置。创建后,您可以根据自己的需要调整该配置。",
"REDIRECTS": "配置重定向",
"DEVMODEWARN": "默认情况下启用开发模式。您可以稍后更新生产值。",
"GUIDE": "指南",
"BROWSEEXAMPLES": "浏览示例",
"DUPLICATEAPPRENAME": "具有相同 neme 的应用程序已经存在。请选择不同的名称。",
"DIALOG": {
"CHANGE": {
"TITLE": "变革框架",
"DESCRIPTION": "从可用的框架中选择一个,快速设置您的应用程序。"
}
}
},
"ACTIONS": {
"ACTIONS": "操作",
"FILTER": "过滤",
@ -138,6 +162,7 @@
"ADD": "添加",
"CREATE": "创建",
"CONTINUE": "继续",
"CONTINUEWITH": "继续 {{value}}",
"BACK": "返回",
"CLOSE": "关闭",
"CLEAR": "清除",
@ -1961,6 +1986,7 @@
"DATECHANGED": "修改于",
"URLS": "URLs",
"DELETE": "删除应用",
"JUMPTOPROJECT": "要配置角色、授权等,请导航到项目。",
"DETAIL": {
"TITLE": "详情",
"STATE": {

View File

@ -29,6 +29,7 @@
@import 'src/app/modules/top-view/top-view.component';
@import 'src/app/pages/projects/projects.component';
@import 'src/app/modules/edit-text/edit-text.component.scss';
@import 'src/app/pages/projects/apps/integrate/integrate.component.scss';
@import 'src/app/modules/providers/providers.scss';
@import 'src/app/pages/users/user-detail/auth-user-detail/auth-user-detail.component';
@import 'src/app/pages/users/user-detail/user-detail/user-detail.component';
@ -43,10 +44,14 @@
@import 'src/app/modules/form-field/field/form-field.component.scss';
@import 'src/app/modules/label/label.component.scss';
@import 'src/app/modules/string-list/string-list.component.scss';
@import 'src/app/components/quickstart/quickstart.component.scss';
@import 'src/app/components/framework-autocomplete/framework-autocomplete.component.scss';
@import 'src/app/components/framework-change/framework-change.component.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/modules/accounts-card/accounts-card.component.scss';
@import 'src/app/modules/onboarding-card/onboarding-card.component.scss';
@import 'src/app/pages/app-create/app-create.component.scss';
@import 'src/app/modules/onboarding/onboarding.component.scss';
@import 'src/app/modules/filter/filter.component.scss';
@import 'src/app/modules/policies/message-texts/message-texts.component.scss';
@ -83,7 +88,9 @@
@include info-overlay-theme($theme);
@include app-auth-method-radio-theme($theme);
@include security-policy-theme($theme);
@include framework-change-theme($theme);
@include search-user-autocomplete-theme($theme);
@include quickstart-theme($theme);
@include project-role-chips-theme($theme);
@include card-theme($theme);
@include idp-settings-theme($theme);
@ -124,6 +131,7 @@
@include refresh-table-theme($theme);
@include accounts-card-theme($theme);
@include sidenav-theme($theme);
@include framework-autocomplete-theme($theme);
@include info-section-theme($theme);
@include actions-theme($theme);
@include filter-theme($theme);
@ -131,11 +139,12 @@
@include project-grid-theme($theme);
@include granted-project-detail-theme($theme);
@include user-grants-theme($theme);
@include app-create-theme($theme);
@include info-row-theme($theme);
@include redirect-uris-theme($theme);
@include action-keys-theme($theme);
@include codemirror-theme($theme);
@include contact-theme($theme);
@include app-create-theme($theme);
@include app-integrate-theme($theme);
@include domain-verification-theme($theme);
}

View File

@ -400,6 +400,7 @@ $caos-dark-app-theme: modify-palette($caos-dark-app-theme, foreground, $cnsl-dar
$background: map-get($caos-light-app-theme, background);
$foreground: map-get($caos-light-app-theme, foreground);
$primary: map-get($caos-light-app-theme, primary);
--warn: #cd3d56;
--success: #10b981;
@ -477,6 +478,7 @@ $caos-dark-app-theme: modify-palette($caos-dark-app-theme, foreground, $cnsl-dar
.dark-theme {
$background: map-get($caos-dark-app-theme, background);
$foreground: map-get($caos-dark-app-theme, foreground);
$primary: map-get($caos-dark-app-theme, primary);
--warn: #ff3b5b;
--success: #10b981;

View File

@ -4,11 +4,6 @@
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
// .data-e2e-success {
// background-color: map-get($background, cards) !important;
// color: var(--success) !important;
// }
.data-e2e-failure {
.mdc-snackbar__surface {
background-color: map-get($background, toast) !important;

View File

@ -16,6 +16,8 @@
"experimentalDecorators": true,
"moduleResolution": "node",
"importHelpers": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"target": "ES2022",
"module": "ES2022",
"useDefineForClassFields": false,

View File

@ -2910,6 +2910,22 @@
"@material/theme" "15.0.0-canary.bc9ae6c9c.0"
tslib "^2.1.0"
"@netlify/framework-info@^9.8.10":
version "9.8.10"
resolved "https://registry.yarnpkg.com/@netlify/framework-info/-/framework-info-9.8.10.tgz#a18589f132dafb5cb7f86c05a9895b9118633fe1"
integrity sha512-VT8ejAaB/XU2xRpdpQinHUO1YL3+BMx6LJ49wJk2u9Yq/VI1/gYCi5VqbqTHBQXJUlOi84YuiRlrDBsLpPr8eg==
dependencies:
ajv "^8.12.0"
filter-obj "^5.0.0"
find-up "^6.3.0"
is-plain-obj "^4.0.0"
locate-path "^7.0.0"
p-filter "^3.0.0"
p-locate "^6.0.0"
process "^0.11.10"
read-pkg-up "^9.0.0"
semver "^7.3.8"
"@ngtools/webpack@16.2.2":
version "16.2.2"
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-16.2.2.tgz#55fac744d1aca4542fb9a4ff16a48d2b384ffd37"
@ -3344,6 +3360,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.7.0.tgz#c03de4572f114a940bc2ca909a33ddb2b925e470"
integrity sha512-zI22/pJW2wUZOVyguFaUL1HABdmSVxpXrzIqkjsHmyUjNhPoWM1CKfvVuXfetHhIok4RY573cqS0mZ1SJEnoTg==
"@types/normalize-package-data@^2.4.1":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901"
integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==
"@types/opentype.js@^1.3.8":
version "1.3.8"
resolved "https://registry.yarnpkg.com/@types/opentype.js/-/opentype.js-1.3.8.tgz#741be92429d1c2d64b5fa79cf692f74b49d6007f"
@ -3845,6 +3866,14 @@ aggregate-error@^3.0.0:
clean-stack "^2.0.0"
indent-string "^4.0.0"
aggregate-error@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-4.0.1.tgz#25091fe1573b9e0be892aeda15c7c66a545f758e"
integrity sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==
dependencies:
clean-stack "^4.0.0"
indent-string "^5.0.0"
ajv-formats@2.1.1, ajv-formats@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz"
@ -3864,7 +3893,7 @@ ajv-keywords@^5.1.0:
dependencies:
fast-deep-equal "^3.1.3"
ajv@8.12.0, ajv@^8.0.0, ajv@^8.9.0:
ajv@8.12.0, ajv@^8.0.0, ajv@^8.12.0, ajv@^8.9.0:
version "8.12.0"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz"
integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==
@ -4490,6 +4519,13 @@ clean-stack@^2.0.0:
resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
clean-stack@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-4.2.0.tgz#c464e4cde4ac789f4e0735c5d75beb49d7b30b31"
integrity sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==
dependencies:
escape-string-regexp "5.0.0"
cli-cursor@3.1.0, cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz"
@ -5318,6 +5354,11 @@ escape-html@~1.0.3:
resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
escape-string-regexp@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
@ -5647,6 +5688,11 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
filter-obj@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-5.1.0.tgz#5bd89676000a713d7db2e197f660274428e524ed"
integrity sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==
finalhandler@1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz"
@ -5845,6 +5891,11 @@ function-bind@^1.1.1:
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
gauge@^4.0.3:
version "4.0.4"
resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz"
@ -6100,6 +6151,13 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
hasown@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.1.tgz#26f48f039de2c0f8d3356c223fb8d50253519faa"
integrity sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==
dependencies:
function-bind "^1.1.2"
hdr-histogram-js@^2.0.1:
version "2.0.3"
resolved "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz"
@ -6114,6 +6172,13 @@ hdr-histogram-percentiles-obj@^3.0.0:
resolved "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz"
integrity sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==
hosted-git-info@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.1.0.tgz#827b82867e9ff1c8d0c4d9d53880397d2c86d224"
integrity sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==
dependencies:
lru-cache "^6.0.0"
hosted-git-info@^6.0.0:
version "6.1.1"
resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz"
@ -6345,6 +6410,11 @@ indent-string@^4.0.0:
resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
indent-string@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5"
integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==
infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz"
@ -6433,6 +6503,13 @@ is-core-module@^2.11.0, is-core-module@^2.8.1:
dependencies:
has "^1.0.3"
is-core-module@^2.5.0:
version "2.13.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
dependencies:
hasown "^2.0.0"
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz"
@ -6499,6 +6576,11 @@ is-plain-obj@^3.0.0:
resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz"
integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==
is-plain-obj@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
is-plain-object@^2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz"
@ -7009,7 +7091,7 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
locate-path@^7.1.0:
locate-path@^7.0.0, locate-path@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-7.2.0.tgz#69cb1779bd90b35ab1e771e1f2f89a202c2a8a8a"
integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==
@ -7503,6 +7585,16 @@ nopt@^6.0.0:
dependencies:
abbrev "^1.0.0"
normalize-package-data@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e"
integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==
dependencies:
hosted-git-info "^4.0.1"
is-core-module "^2.5.0"
semver "^7.3.4"
validate-npm-package-license "^3.0.1"
normalize-package-data@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz"
@ -7769,6 +7861,13 @@ os-tmpdir@~1.0.1, os-tmpdir@~1.0.2:
resolved "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz"
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
p-filter@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-3.0.0.tgz#ce50e03b24b23930e11679ab8694bd09a2d7ed35"
integrity sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==
dependencies:
p-map "^5.1.0"
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
@ -7818,6 +7917,13 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
p-map@^5.1.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/p-map/-/p-map-5.5.0.tgz#054ca8ca778dfa4cf3f8db6638ccb5b937266715"
integrity sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==
dependencies:
aggregate-error "^4.0.0"
p-retry@^4.5.0:
version "4.6.2"
resolved "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz"
@ -7867,7 +7973,7 @@ parent-module@^1.0.0:
dependencies:
callsites "^3.0.0"
parse-json@^5.0.0:
parse-json@^5.0.0, parse-json@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz"
integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
@ -8130,6 +8236,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
promise-inflight@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz"
@ -8307,6 +8418,25 @@ read-package-json@^6.0.0:
normalize-package-data "^5.0.0"
npm-normalize-package-bin "^3.0.0"
read-pkg-up@^9.0.0:
version "9.1.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-9.1.0.tgz#38ca48e0bc6c6b260464b14aad9bcd4e5b1fbdc3"
integrity sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==
dependencies:
find-up "^6.3.0"
read-pkg "^7.1.0"
type-fest "^2.5.0"
read-pkg@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-7.1.0.tgz#438b4caed1ad656ba359b3e00fd094f3c427a43e"
integrity sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==
dependencies:
"@types/normalize-package-data" "^2.4.1"
normalize-package-data "^3.0.2"
parse-json "^5.2.0"
type-fest "^2.0.0"
readable-stream@^2.0.1, readable-stream@~2.3.6:
version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
@ -8676,6 +8806,13 @@ semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.3.4:
version "7.6.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d"
integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==
dependencies:
lru-cache "^6.0.0"
send@0.18.0:
version "0.18.0"
resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz"
@ -9369,6 +9506,11 @@ type-fest@^0.21.3:
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz"
integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
type-fest@^2.0.0, type-fest@^2.5.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b"
integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz"
@ -9516,7 +9658,7 @@ v8-compile-cache@2.3.0:
resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz"
integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
validate-npm-package-license@^3.0.4:
validate-npm-package-license@^3.0.1, validate-npm-package-license@^3.0.4:
version "3.0.4"
resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz"
integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==

View File

@ -8,93 +8,11 @@ To achieve your goals as fast as possible, we provide you with SDKs, Example Rep
The SDKs and Integration depend on the framework and language you are using.
import { Tile } from "../../src/components/tile";
import { Frameworks } from "../../src/components/frameworks";
### Resources
<div className="tile-wrapper">
<Tile title="Angular"
imageSource="/docs/img/tech/angular.svg"
link="/docs/sdk-examples/angular"></Tile>
<Tile title="Flutter"
imageSource="/docs/img/tech/flutter.svg"
link="/docs/sdk-examples/flutter"></Tile>
<Tile title="Go"
imageSource="/docs/img/tech/golang.svg"
link="/docs/sdk-examples/go"></Tile>
<Tile
title="Java"
imageSource="/docs/img/tech/java.svg"
link="/docs/sdk-examples/java"></Tile>
<Tile
title="NestJS"
imageSource="/docs/img/tech/nestjs.svg"
link="/docs/sdk-examples/nestjs"></Tile>
<Tile
title="Next.js"
imageSource="/docs/img/tech/nextjs.svg"
imageSourceLight="/docs/img/tech/nextjslight.svg"
link="/docs/sdk-examples/nextjs"></Tile>
<Tile
title="Python Django"
imageSource="/docs/img/tech/django.png"
link="/docs/sdk-examples/python-django"></Tile>
<Tile
title="Python Flask"
imageSource="/docs/img/tech/flask.svg"
imageSourceLight="/docs/img/tech/flasklight.svg"
link="/docs/sdk-examples/python-flask"></Tile>
<Tile
title="React"
imageSource="/docs/img/tech/react.png"
link="/docs/sdk-examples/react"></Tile>
<Tile
title="Symfony"
imageSource="/docs/img/tech/php.svg"
link="/docs/sdk-examples/symfony"></Tile>
<Tile
title="Vue.js"
imageSource="/docs/img/tech/vue.svg"
link="/docs/sdk-examples/vue"></Tile>
<Tile
title="Dart"
imageSource="/docs/img/tech/dart.svg"
link="https://github.com/smartive/zitadel-dart"
external="true"></Tile>
<Tile
title="Elixir"
imageSource="/docs/img/tech/elixir.svg"
link="https://github.com/maennchen/zitadel_api"
external="true"></Tile>
<Tile
title="NextAuth"
imageSource="/docs/img/tech/nextjs.svg"
imageSourceLight="/docs/img/tech/nextjslight.svg"
link="https://next-auth.js.org/providers/zitadel"
external="true"></Tile>
<Tile
title="Node.js"
imageSource="/docs/img/tech/nodejs.svg"
link="https://www.npmjs.com/package/@zitadel/node"
external="true"></Tile>
<Tile
title=".Net"
imageSource="/docs/img/tech/dotnet.svg"
link="https://github.com/smartive/zitadel-net"
external="true"></Tile>
<Tile
title="Passport.js"
imageSource="/docs/img/tech/passportjs.svg"
link="https://github.com/buehler/node-passport-zitadel"
external="true"></Tile>
<Tile
title="Rust"
imageSourceLight="/docs/img/tech/rust.svg"
imageSource="/docs/img/tech/rustlight.svg"
link="https://github.com/smartive/zitadel-rust"
external="true"></Tile>
</div>
<Frameworks />
### OIDC Libraries

115
docs/frameworks.json Normal file
View File

@ -0,0 +1,115 @@
[
{
"id": "angular",
"title": "Angular",
"description": "This preset sets up an OIDC configuration with Authentication Code Flow, secured by PKCE",
"imgSrcLight": "/docs/img/tech/angular.svg",
"imgSrcDark": "/docs/img/tech/angular.svg",
"docsLink": "/docs/sdk-examples/angular"
},
{
"id": "flutter",
"title": "Flutter",
"imgSrcDark": "/docs/img/tech/flutter.svg",
"docsLink": "/docs/sdk-examples/flutter"
},
{
"title": "Go",
"imgSrcDark": "/docs/img/tech/golang.svg",
"docsLink": "/docs/sdk-examples/go"
},
{
"id": "java",
"title": "Java",
"imgSrcDark": "/docs/img/tech/java.svg",
"docsLink": "/docs/sdk-examples/java"
},
{
"title": "NestJS",
"imgSrcDark": "/docs/img/tech/nestjs.svg",
"docsLink": "/docs/sdk-examples/nestjs"
},
{
"id": "next",
"title": "Next.js",
"imgSrcDark": "/docs/img/tech/nextjs.svg",
"imgSrcLight": "/docs/img/tech/nextjslight.svg",
"docsLink": "/docs/sdk-examples/nextjs"
},
{
"id": "django",
"title": "Python Django",
"imgSrcDark": "/docs/img/tech/django.png",
"docsLink": "/docs/sdk-examples/python-django"
},
{
"title": "Python Flask",
"imgSrcDark": "/docs/img/tech/flask.svg",
"imgSrcLight": "/docs/img/tech/flasklight.svg",
"docsLink": "/docs/sdk-examples/python-flask"
},
{
"id": "react",
"title": "React",
"description": "This preset sets up an OIDC configuration with Authentication Code Flow, secured by PKCE",
"imgSrcDark": "/docs/img/tech/react.png",
"docsLink": "/docs/sdk-examples/react"
},
{
"id": "symfony",
"title": "Symfony",
"imgSrcDark": "/docs/img/tech/php.svg",
"docsLink": "/docs/sdk-examples/symfony"
},
{
"id": "vue",
"title": "Vue.js",
"description": "This preset sets up an OIDC configuration with Authentication Code Flow, secured by PKCE",
"imgSrcDark": "/docs/img/tech/vue.svg",
"docsLink": "/docs/sdk-examples/vue"
},
{
"title": "Dart",
"imgSrcDark": "/docs/img/tech/dart.svg",
"docsLink": "https://github.com/smartive/zitadel-dart",
"external": true
},
{
"title": "Elixir",
"imgSrcDark": "/docs/img/tech/elixir.svg",
"docsLink": "https://github.com/maennchen/zitadel_api",
"external": true
},
{
"title": "NextAuth",
"imgSrcDark": "/docs/img/tech/nextjs.svg",
"imgSrcLight": "/docs/img/tech/nextjslight.svg",
"docsLink": "https://next-auth.js.org/providers/zitadel",
"external": true
},
{
"title": "Node.js",
"imgSrcDark": "/docs/img/tech/nodejs.svg",
"docsLink": "https://www.npmjs.com/package/@zitadel/node",
"external": true
},
{
"title": ".Net",
"imgSrcDark": "/docs/img/tech/dotnet.svg",
"docsLink": "https://github.com/smartive/zitadel-net",
"external": true
},
{
"title": "Passport.js",
"imgSrcDark": "/docs/img/tech/passportjs.svg",
"docsLink": "https://github.com/buehler/node-passport-zitadel",
"external": true
},
{
"title": "Rust",
"imgSrcLight": "/docs/img/tech/rust.svg",
"imgSrcDark": "/docs/img/tech/rustlight.svg",
"docsLink": "https://github.com/smartive/zitadel-rust",
"external": true
}
]

View File

@ -0,0 +1,21 @@
import React from "react";
import { Tile } from "./tile";
import frameworks from "../../frameworks.json";
export function Frameworks({}) {
return (
<div className="tile-wrapper">
{frameworks.map((framework) => {
return (
<Tile
title={framework.title}
imageSource={framework.imgSrcDark}
imageSourceLight={framework.imgSrcLight}
link={framework.docsLink}
external={framework.external}
></Tile>
);
})}
</div>
);
}