diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index a681bedca2..54eaaeeb6a 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -904,6 +904,7 @@ InternalAuthZ: - "project.grant.member.write" - "project.grant.member.delete" - "events.read" + - "milestones.read" - Role: "IAM_OWNER_VIEWER" Permissions: - "iam.read" @@ -929,6 +930,7 @@ InternalAuthZ: - "project.grant.read" - "project.grant.member.read" - "events.read" + - "milestones.read" - Role: "IAM_ORG_MANAGER" Permissions: - "org.read" diff --git a/console/src/app/modules/onboarding-card/onboarding-card.component.html b/console/src/app/modules/onboarding-card/onboarding-card.component.html index 477b27c891..b5bceb9012 100644 --- a/console/src/app/modules/onboarding-card/onboarding-card.component.html +++ b/console/src/app/modules/onboarding-card/onboarding-card.component.html @@ -20,15 +20,18 @@ [routerLink]="action[1].link" [queryParams]="{ id: action[1].fragment }" class="action-element" - [ngClass]="{ done: action[1].event !== undefined }" + [ngClass]="{ done: action[1].reached !== undefined }" >
- check_circle
- {{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }} + {{ 'ONBOARDING.MILESTONES.' + action[0] + '.title' | translate }} keyboard_arrow_right diff --git a/console/src/app/modules/onboarding-card/onboarding-card.component.ts b/console/src/app/modules/onboarding-card/onboarding-card.component.ts index 14b505afb0..8c5fc17de2 100644 --- a/console/src/app/modules/onboarding-card/onboarding-card.component.ts +++ b/console/src/app/modules/onboarding-card/onboarding-card.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { AdminService } from 'src/app/services/admin.service'; -import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding'; +import { ONBOARDING_MILESTONES } from 'src/app/utils/onboarding'; @Component({ selector: 'cnsl-onboarding-card', @@ -11,7 +11,7 @@ import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding'; export class OnboardingCardComponent implements OnInit { public percentageChanged: EventEmitter = new EventEmitter(); public loading$: BehaviorSubject = new BehaviorSubject(false); - public actions = this.adminService.progressEvents; + public actions = this.adminService.progressMilestones; @Output() public dismissedCard: EventEmitter = new EventEmitter(); constructor(public adminService: AdminService) {} @@ -21,6 +21,6 @@ export class OnboardingCardComponent implements OnInit { } ngOnInit() { - this.adminService.loadEvents.next(ONBOARDING_EVENTS); + this.adminService.loadMilestones.next(ONBOARDING_MILESTONES); } } diff --git a/console/src/app/modules/onboarding-card/onboarding-card.module.ts b/console/src/app/modules/onboarding-card/onboarding-card.module.ts index 520c806ae8..58bd99792f 100644 --- a/console/src/app/modules/onboarding-card/onboarding-card.module.ts +++ b/console/src/app/modules/onboarding-card/onboarding-card.module.ts @@ -6,7 +6,7 @@ import { MatLegacyTooltipModule as MatTooltipModule } from '@angular/material/le import { TranslateModule } from '@ngx-translate/core'; import { RouterModule } from '@angular/router'; -import { EventPipeModule } from 'src/app/pipes/event-pipe/event-pipe.module'; +import { MilestonePipeModule } from 'src/app/pipes/milestone-pipe/milestone-pipe.module'; import { OnboardingCardComponent } from './onboarding-card.component'; @NgModule({ @@ -17,7 +17,7 @@ import { OnboardingCardComponent } from './onboarding-card.component'; TranslateModule, RouterModule, MatProgressSpinnerModule, - EventPipeModule, + MilestonePipeModule, MatTooltipModule, ], exports: [OnboardingCardComponent], diff --git a/console/src/app/modules/onboarding/onboarding.component.html b/console/src/app/modules/onboarding/onboarding.component.html index 8ec6111af8..1fe806f356 100644 --- a/console/src/app/modules/onboarding/onboarding.component.html +++ b/console/src/app/modules/onboarding/onboarding.component.html @@ -27,10 +27,13 @@ [routerLink]="action[1].link" [queryParams]="{ id: action[1].fragment }" class="action-card card" - [ngClass]="{ done: action[1].event !== undefined }" + [ngClass]="{ done: action[1].reached !== undefined }" >
- check_circle
@@ -54,16 +57,16 @@
- {{ 'ONBOARDING.EVENTS.' + action[0] + '.title' | translate }} + {{ 'ONBOARDING.MILESTONES.' + action[0] + '.title' | translate }} {{ - 'ONBOARDING.EVENTS.' + action[0] + '.description' | translate + 'ONBOARDING.MILESTONES.' + action[0] + '.description' | translate }}
- {{ 'ONBOARDING.EVENTS.' + action[0] + '.action' | translate }} + {{ 'ONBOARDING.MILESTONES.' + action[0] + '.action' | translate }} keyboard_arrow_right
diff --git a/console/src/app/modules/onboarding/onboarding.component.ts b/console/src/app/modules/onboarding/onboarding.component.ts index d72f129d89..e0938f59d5 100644 --- a/console/src/app/modules/onboarding/onboarding.component.ts +++ b/console/src/app/modules/onboarding/onboarding.component.ts @@ -1,7 +1,7 @@ import { Component } from '@angular/core'; import { AdminService } from 'src/app/services/admin.service'; import { ThemeService } from 'src/app/services/theme.service'; -import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding'; +import { ONBOARDING_MILESTONES } from 'src/app/utils/onboarding'; @Component({ selector: 'cnsl-onboarding', @@ -9,12 +9,12 @@ import { ONBOARDING_EVENTS } from 'src/app/utils/onboarding'; styleUrls: ['./onboarding.component.scss'], }) export class OnboardingComponent { - public actions = this.adminService.progressEvents; + public actions = this.adminService.progressMilestones; constructor( public adminService: AdminService, public themeService: ThemeService, ) { - this.adminService.loadEvents.next(ONBOARDING_EVENTS); + this.adminService.loadMilestones.next(ONBOARDING_MILESTONES); } } diff --git a/console/src/app/modules/onboarding/onboarding.module.ts b/console/src/app/modules/onboarding/onboarding.module.ts index 598d525bd9..7363761b4f 100644 --- a/console/src/app/modules/onboarding/onboarding.module.ts +++ b/console/src/app/modules/onboarding/onboarding.module.ts @@ -9,7 +9,7 @@ import { ShortcutsModule } from 'src/app/modules/shortcuts/shortcuts.module'; import { MatLegacyProgressBarModule } from '@angular/material/legacy-progress-bar'; import { RouterModule } from '@angular/router'; -import { EventPipeModule } from 'src/app/pipes/event-pipe/event-pipe.module'; +import { MilestonePipeModule } from 'src/app/pipes/milestone-pipe/milestone-pipe.module'; import { OnboardingComponent } from './onboarding.component'; @NgModule({ @@ -24,7 +24,7 @@ import { OnboardingComponent } from './onboarding.component'; RouterModule, MatProgressSpinnerModule, MatLegacyProgressBarModule, - EventPipeModule, + MilestonePipeModule, ], exports: [OnboardingComponent], }) diff --git a/console/src/app/pipes/event-pipe/event-pipe.module.ts b/console/src/app/pipes/milestone-pipe/milestone-pipe.module.ts similarity index 72% rename from console/src/app/pipes/event-pipe/event-pipe.module.ts rename to console/src/app/pipes/milestone-pipe/milestone-pipe.module.ts index 16d81152dc..6d887910ce 100644 --- a/console/src/app/pipes/event-pipe/event-pipe.module.ts +++ b/console/src/app/pipes/milestone-pipe/milestone-pipe.module.ts @@ -2,11 +2,11 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { LocalizedDatePipeModule } from '../localized-date-pipe/localized-date-pipe.module'; import { TimestampToDatePipeModule } from '../timestamp-to-date-pipe/timestamp-to-date-pipe.module'; -import { EventPipe } from './event.pipe'; +import { MilestonePipe } from './milestonePipe'; @NgModule({ - declarations: [EventPipe], + declarations: [MilestonePipe], imports: [CommonModule, TimestampToDatePipeModule, LocalizedDatePipeModule], - exports: [EventPipe], + exports: [MilestonePipe], }) -export class EventPipeModule {} +export class MilestonePipeModule {} diff --git a/console/src/app/pipes/event-pipe/event.pipe.ts b/console/src/app/pipes/milestone-pipe/milestonePipe.ts similarity index 50% rename from console/src/app/pipes/event-pipe/event.pipe.ts rename to console/src/app/pipes/milestone-pipe/milestonePipe.ts index 932a1c470b..de556bffa1 100644 --- a/console/src/app/pipes/event-pipe/event.pipe.ts +++ b/console/src/app/pipes/milestone-pipe/milestonePipe.ts @@ -1,22 +1,18 @@ import { Pipe, PipeTransform } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; -import { Event } from 'src/app/proto/generated/zitadel/event_pb'; import { LocalizedDatePipe } from '../localized-date-pipe/localized-date.pipe'; import { TimestampToDatePipe } from '../timestamp-to-date-pipe/timestamp-to-date.pipe'; +import { Milestone } from '../../proto/generated/zitadel/milestone/v1/milestone_pb'; @Pipe({ - name: 'event', + name: 'milestone', }) -export class EventPipe implements PipeTransform { +export class MilestonePipe implements PipeTransform { constructor(private translateService: TranslateService) {} - public transform(event?: Event.AsObject): any { - if (event && event.editor?.displayName && event.creationDate) { - const timestampToDate = new TimestampToDatePipe().transform(event.creationDate); - const datePipeOutput = new LocalizedDatePipe(this.translateService).transform(timestampToDate); - return `${event.editor?.displayName} last changed it on ${datePipeOutput}`; - } else if (event && event.creationDate) { - const timestampToDate = new TimestampToDatePipe().transform(event.creationDate); + public transform(milestone?: Milestone.AsObject): any { + if (milestone && milestone.reachedDate) { + const timestampToDate = new TimestampToDatePipe().transform(milestone.reachedDate); const datePipeOutput = new LocalizedDatePipe(this.translateService).transform(timestampToDate); return `done on ${datePipeOutput}`; } else { diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts index 0b173bdaa8..e2ef755527 100644 --- a/console/src/app/services/admin.service.ts +++ b/console/src/app/services/admin.service.ts @@ -152,6 +152,8 @@ import { ListLoginPolicyMultiFactorsResponse, ListLoginPolicySecondFactorsRequest, ListLoginPolicySecondFactorsResponse, + ListMilestonesRequest, + ListMilestonesResponse, ListProvidersRequest, ListProvidersResponse, ListSecretGeneratorsRequest, @@ -296,85 +298,77 @@ import { SearchQuery } from '../proto/generated/zitadel/member_pb'; import { ListQuery } from '../proto/generated/zitadel/object_pb'; import { GrpcService } from './grpc.service'; import { StorageLocation, StorageService } from './storage.service'; +import { + IsReachedQuery, + Milestone, + MilestoneQuery, + MilestoneType, +} from '../proto/generated/zitadel/milestone/v1/milestone_pb'; export interface OnboardingActions { order: number; - eventType: string; - oneof: string[]; - link: string | string[]; + milestoneType: MilestoneType; + link: string; fragment?: string | undefined; iconClasses?: string; darkcolor: string; lightcolor: string; - aggregateType: string; } -type OnboardingEvent = { +type OnboardingMilestone = { order: number; link: string; fragment: string | undefined; - event: Event.AsObject | undefined; + reached: Milestone.AsObject | undefined; iconClasses?: string; darkcolor: string; lightcolor: string; }; -type OnboardingEventEntries = Array<[string, OnboardingEvent]> | []; +type OnboardingMilestoneEntries = Array<[string, OnboardingMilestone]> | []; @Injectable({ providedIn: 'root', }) export class AdminService { + private readonly milestoneTypePrefixLength = 'MILESTONE_TYPE_'.length; public hideOnboarding: boolean = false; - public loadEvents: Subject = new Subject(); + public loadMilestones: Subject = new Subject(); public onboardingLoading: BehaviorSubject = new BehaviorSubject(false); - public progressEvents$: Observable = this.loadEvents.pipe( + public progressMilestones$: Observable = this.loadMilestones.pipe( tap(() => this.onboardingLoading.next(true)), switchMap((actions) => { - const searchForTypes = actions.map((oe) => oe.oneof).flat(); - const aggregateTypes = actions.map((oe) => oe.aggregateType); - const eventsReq = new ListEventsRequest() - .setAsc(true) - .setEventTypesList(searchForTypes) - .setAggregateTypesList(aggregateTypes) - .setAsc(false); - return from(this.listEvents(eventsReq)).pipe( - map((events) => { - const el = events.toObject().eventsList.filter((e) => e.editor?.service !== 'System-API' && e.editor?.userId); - - let obj: { [type: string]: OnboardingEvent } = {}; + const milestonesListQuery = new ListQuery(); + milestonesListQuery.setAsc(true); + milestonesListQuery.setLimit(20); + const milestoneIsReachedQuery = new IsReachedQuery().setReached(true); + const milestonesQuery = new MilestoneQuery().setIsReachedQuery(milestoneIsReachedQuery); + const milestonesReq = new ListMilestonesRequest().setQuery(milestonesListQuery).setQueriesList([milestonesQuery]); + return from(this.listMilestones(milestonesReq)).pipe( + map((reachedMilestones) => { + let obj: { [type: string]: OnboardingMilestone } = {}; actions.map((action) => { - const filtered = el.filter((event) => event.type?.type && action.oneof.includes(event.type.type)); - (obj as any)[action.eventType] = filtered.length - ? { - order: action.order, - link: action.link, - fragment: action.fragment, - event: filtered[0], - iconClasses: action.iconClasses, - darkcolor: action.darkcolor, - lightcolor: action.lightcolor, - } - : { - order: action.order, - link: action.link, - fragment: action.fragment, - event: undefined, - iconClasses: action.iconClasses, - darkcolor: action.darkcolor, - lightcolor: action.lightcolor, - }; + obj[Object.keys(MilestoneType)[action.milestoneType].substring(this.milestoneTypePrefixLength)] = { + order: action.order, + link: action.link, + fragment: action.fragment, + iconClasses: action.iconClasses, + darkcolor: action.darkcolor, + lightcolor: action.lightcolor, + reached: reachedMilestones.resultList.find((reached) => { + return reached.type.valueOf() == action.milestoneType; + }), + }; }); - const toArray = Object.entries(obj).sort(([key0, a], [key1, b]) => a.order - b.order); - const toDo = toArray.filter(([key, value]) => value.event === undefined); - const done = toArray.filter(([key, value]) => !!value.event); + const toDo = toArray.filter(([key, value]) => value.reached === undefined); + const done = toArray.filter(([key, value]) => !!value.reached); return [...toDo, ...done]; }), - tap((events) => { - const total = events.length; - const done = events.map(([type, value]) => value.event !== undefined).filter((res) => !!res).length; + tap((milestones) => { + const total = milestones.length; + const done = milestones.map(([type, value]) => value.reached !== undefined).filter((res) => !!res).length; const percentage = Math.round((done / total) * 100); this.progressDone.next(done); this.progressTotal.next(total); @@ -390,7 +384,9 @@ export class AdminService { }), ); - public progressEvents: BehaviorSubject = new BehaviorSubject([]); + public progressMilestones: BehaviorSubject = new BehaviorSubject( + [], + ); public progressPercentage: BehaviorSubject = new BehaviorSubject(0); public progressDone: BehaviorSubject = new BehaviorSubject(0); public progressTotal: BehaviorSubject = new BehaviorSubject(0); @@ -400,7 +396,7 @@ export class AdminService { private readonly grpcService: GrpcService, private storageService: StorageService, ) { - this.progressEvents$.subscribe(this.progressEvents); + this.progressMilestones$.subscribe(this.progressMilestones); this.hideOnboarding = this.storageService.getItem('onboarding-dismissed', StorageLocation.local) === 'true' ? true : false; @@ -1254,4 +1250,8 @@ export class AdminService { return this.grpcService.admin.updateIAMMember(req, null).then((resp) => resp.toObject()); } + + public listMilestones(req: ListMilestonesRequest): Promise { + return this.grpcService.admin.listMilestones(req, null).then((resp) => resp.toObject()); + } } diff --git a/console/src/app/utils/color.ts b/console/src/app/utils/color.ts index 5e807adac4..b76982d4fd 100644 --- a/console/src/app/utils/color.ts +++ b/console/src/app/utils/color.ts @@ -25,6 +25,8 @@ export const COLORS = [ { 500: '#d946ef', 200: '#f5d0fe', 300: '#f0abfc', 600: '#c026d3', 700: '#a21caf', 900: '#701a75' }, { 500: '#ec4899', 200: '#fbcfe8', 300: '#f9a8d4', 600: '#db2777', 700: '#be185d', 900: '#831843' }, { 500: '#f43f5e', 200: '#fecdd3', 300: '#fda4af', 600: '#e11d48', 700: '#be123c', 900: '#881337' }, + { 500: '#A89F91', 200: '#D4CDC6', 300: '#BFB6AC', 600: '#8F8378', 700: '#736A60', 900: '#4F4A40' }, + { 500: '#BA9F88', 200: '#E8D3C5', 300: '#D4BAA7', 600: '#9C7A68', 700: '#8A6E5D', 900: '#5F4C42' }, ]; export const WEB_APP_COLOR: Color = COLORS[6]; diff --git a/console/src/app/utils/onboarding.ts b/console/src/app/utils/onboarding.ts index f93c11db1b..70b77f4b66 100644 --- a/console/src/app/utils/onboarding.ts +++ b/console/src/app/utils/onboarding.ts @@ -1,5 +1,6 @@ import { OnboardingActions } from '../services/admin.service'; import { COLORS } from './color'; +import { MilestoneType } from '../proto/generated/zitadel/milestone/v1/milestone_pb'; const reddark: string = COLORS[0][700]; const redlight = COLORS[0][200]; @@ -19,67 +20,66 @@ const purplelight = COLORS[12][200]; const pinkdark: string = COLORS[15][700]; const pinklight = COLORS[15][200]; -export const ONBOARDING_EVENTS: OnboardingActions[] = [ +const sthdark: string = COLORS[18][700]; +const sthlight = COLORS[18][200]; + +export const ONBOARDING_MILESTONES: OnboardingActions[] = [ { order: 0, - eventType: 'project.added', - oneof: ['project.added'], - link: ['/projects/create'], + milestoneType: MilestoneType.MILESTONE_TYPE_PROJECT_CREATED, + link: '/projects/create', iconClasses: 'las la-database', darkcolor: greendark, lightcolor: greenlight, - aggregateType: 'project', }, { order: 1, - eventType: 'project.application.added', - oneof: ['project.application.added'], - link: ['/projects/app-create'], + milestoneType: MilestoneType.MILESTONE_TYPE_APPLICATION_CREATED, + link: '/projects/app-create', iconClasses: 'lab la-openid', darkcolor: purpledark, lightcolor: purplelight, - aggregateType: 'project', - }, - { - order: 2, - eventType: 'user.human.added', - oneof: ['user.human.added'], - link: ['/users/create'], - iconClasses: 'las la-user', - darkcolor: bluedark, - lightcolor: bluelight, - aggregateType: 'user', }, { order: 3, - eventType: 'user.grant.added', - oneof: ['user.grant.added'], - link: ['/grant-create'], + milestoneType: MilestoneType.MILESTONE_TYPE_AUTHENTICATION_SUCCEEDED_ON_APPLICATION, + link: 'https://zitadel.com/docs/guides/integrate/login-users', + iconClasses: 'las la-sign-in-alt', + darkcolor: sthdark, + lightcolor: sthlight, + } /* + { + order: 4, + milestoneType: 'user.human.added', + link: '/users/create', + iconClasses: 'las la-user', + darkcolor: bluedark, + lightcolor: bluelight, + }, + { + order: 5, + milestoneType: 'user.grant.added', + link: '/grant-create', iconClasses: 'las la-shield-alt', darkcolor: reddark, lightcolor: redlight, - aggregateType: 'user_grant', }, { - order: 4, - eventType: 'instance.policy.label.added', - oneof: ['instance.policy.label.added', 'instance.policy.label.changed'], - link: ['/settings'], + order: 6, + milestoneType: 'instance.policy.label.added', + link: '/settings', fragment: 'branding', iconClasses: 'las la-swatchbook', darkcolor: pinkdark, lightcolor: pinklight, - aggregateType: 'instance', }, { - order: 5, - eventType: 'instance.smtp.config.added', - oneof: ['instance.smtp.config.added', 'instance.smtp.config.changed'], - link: ['/settings'], + order: 7, + milestoneType: 'instance.smtp.config.added', + link: '/settings', fragment: 'smtpprovider', iconClasses: 'las la-envelope', darkcolor: yellowdark, lightcolor: yellowlight, - aggregateType: 'instance', - }, + },*/, ]; diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index 1e42930967..482a479a17 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -51,7 +51,7 @@ "TITLE": "Пуснете своя ZITADEL да работи", "DESCRIPTION": "Този контролен списък помага да настроите вашия екземпляр и ви насочва през най-важните стъпки" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Настройте марката си", "description": "Определете цвета и формата на вашето логин и качете вашето лого и икони.", @@ -62,15 +62,20 @@ "description": "Задайте свои собствени настройки на пощенския сървър.", "action": "Настройка на SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Създайте проект", "description": "Добавете проект и определете неговите роли и пълномощия.", "action": "Създайте проект" }, - "project.application.added": { - "title": "Създайте приложение", - "description": "Създайте уеб, естествено, api или saml приложение и настройте своя поток за удостоверяване.", - "action": "Създаване на приложение" + "APPLICATION_CREATED": { + "title": "Регистрирайте приложението си", + "description": "Регистрирайте вашето уеб, естествено, api или saml приложение и настройте поток за удостоверяване.", + "action": "Регистрирайте приложението" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Влезте в приложението си", + "description": "Интегрирайте приложението си с ZITADEL за удостоверяване и го тествайте, като влезете с администраторския си потребител.", + "action": "Влезте" }, "user.human.added": { "title": "Добавете потребители", diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 61b0fb152c..fec4423585 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -51,7 +51,7 @@ "TITLE": "Bringe deine Instanz zum Laufen", "DESCRIPTION": "Diese Checkliste hilft bei der Einrichtung Ihrer Instanz und führt Sie durch die wichtigsten Schritte" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Branding anpassen", "description": "Definiere Farben und Form des Login-UIs und uploade deine Logos und Icons.", @@ -62,15 +62,20 @@ "description": "Konfiguriere deinen Mailserver.", "action": "SMTP einrichten" }, - "project.added": { + "PROJECT_CREATED": { "title": "Erstelle ein Projekt", "description": "Erstelle dein erstes Projekt und definiere Rollen", "action": "Projekt erstellen" }, - "project.application.added": { - "title": "Erstelle eine App", - "description": "Erstelle deine erste Web-, native, API oder SAML-applikation und konfiguriere den Authentification-flow.", - "action": "App erstellen" + "APPLICATION_CREATED": { + "title": "Registriere deine App", + "description": "Registriere deine erste Web-, native, API oder SAML-Applikation und konfiguriere den Authentification-flow.", + "action": "App registrieren" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Logge dich in deine App ein", + "description": "Integriere deine Applikation mit ZITADEL für die Authentifizierung und teste es, indem du dich mit deinem Admin-Benutzer einloggst.", + "action": "Einloggen" }, "user.human.added": { "title": "Erfasse Benutzer", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 17cc6cb90a..dbcff16583 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -51,7 +51,7 @@ "TITLE": "Get your ZITADEL running", "DESCRIPTION": "This checklist helps to setup your instance and guides your through the most essential steps" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Setup your brand", "description": "Define coloring and shape of your login and upload your logo and icons.", @@ -62,15 +62,20 @@ "description": "Set your own mail server settings.", "action": "Setup SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Create a project", "description": "Add a project and define its roles and authorizations.", "action": "Create project" }, - "project.application.added": { - "title": "Create an application", - "description": "Create a web, native, api or saml application and setup your authentication flow.", - "action": "Create app" + "APPLICATION_CREATED": { + "title": "Register your app", + "description": "Register your web, native, api or saml application and setup an authentication flow.", + "action": "Register app" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Log in to your app", + "description": "Integrate your application with ZITADEL for authentication and test it by logging in with your admin user.", + "action": "Log in" }, "user.human.added": { "title": "Add users", diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index 00add3480a..cae80d082c 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -51,7 +51,7 @@ "TITLE": "Ponte en marcha con ZITADEL", "DESCRIPTION": "Esta lista de tareas te ayuda a configurar tu instancia y te guía por los pasos más esenciales" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Configura tu imagen de marca", "description": "Define el esquema de colores, da forma a tu inicio de sesión y sube tu logo y tus iconos.", @@ -62,15 +62,20 @@ "description": "Introduce la configuración de tu propio servidor de correo.", "action": "Configurar SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Crea tu primer proyecto", "description": "Añade tu primer proyecto y define sus roles y autorizaciones.", "action": "Crear proyecto" }, - "project.application.added": { - "title": "Crea tu primera aplicación", - "description": "Crea una aplicación web, nativa, api o saml y configura tu flujo de autenticación.", - "action": "Crear app" + "APPLICATION_CREATED": { + "title": "Registra tu aplicación", + "description": "Registra tu aplicación web, nativa, api o saml y configura tu flujo de autenticación.", + "action": "Registrar app" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Inicia sesión en tu aplicación", + "description": "Integra tu aplicación con ZITADEL para la autenticación y pruébala iniciando sesión con tu usuario administrador.", + "action": "Iniciar sesión" }, "user.human.added": { "title": "Añade usuarios", diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 3c46abcbed..87f94c74c1 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -51,7 +51,7 @@ "TITLE": "Faites fonctionner votre ZITADEL", "DESCRIPTION": "Cette liste de contrôle vous aide à configurer votre instance et vous guide à travers les étapes les plus essentielles." }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Créez votre marque", "description": "Définissez la couleur et la forme de votre connexion et téléchargez votre logo et vos icônes.", @@ -62,15 +62,20 @@ "description": "Définissez paramètres de serveur de messagerie", "action": "Configurez" }, - "project.added": { + "PROJECT_CREATED": { "title": "Créez projet", "description": "Ajoutez projet et définissez ses rôles et autorisations.", "action": "Créez projet" }, - "project.application.added": { - "title": "Créez votre première application", - "description": "Créez une application web, native, api ou saml et configurez votre flux d'authentification.", - "action": "Créez application" + "APPLICATION_CREATED": { + "title": "Enregistrez votre application", + "description": "Enregistrez votre application web, native, api ou saml et configurez un flux d'authentification.", + "action": "Enregistrez l'application" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Connectez-vous à votre application", + "description": "Intégrez votre application avec ZITADEL pour l'authentification et testez-la en vous connectant avec votre utilisateur administrateur.", + "action": "Connexion" }, "user.human.added": { "title": "Ajouter des utilisateurs", diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index cf618d49f6..998ea6a662 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -51,7 +51,7 @@ "TITLE": "Fate funzionare il vostro ZITADEL", "DESCRIPTION": "Questa lista di azioni aiuta a configurare la vostra istanza e vi guida attraverso i passaggi più essenziali." }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Imposta il tuo marchio", "description": "Definisci la colorazione e il design del vostro login e caricate il vostro logo e le vostre icone.", @@ -62,15 +62,20 @@ "description": "Imposta il proprio server di posta", "action": "Configura SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Crea il tuo primo progetto", "description": "Aggiungere il primo progetto e definire i ruoli e le autorizzazioni.", "action": "Crea progetto" }, - "project.application.added": { - "title": "Crea la tua prima applicazione", - "description": "Crea un'applicazione web, nativa, api o saml e imposta il flusso di autenticazione.", - "action": "Crea applicazione" + "APPLICATION_CREATED": { + "title": "Registra la tua app", + "description": "Registra la tua applicazione web, nativa, api o saml e configura un flusso di autenticazione.", + "action": "Registra app" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Accedi alla tua app", + "description": "Integra la tua applicazione con ZITADEL per l'autenticazione e testala accedendo con il tuo utente amministratore.", + "action": "Accedi" }, "user.human.added": { "title": "Aggiungi utenti", diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index 9e0ab6d63f..69f1d9c1fb 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -51,7 +51,7 @@ "TITLE": "ZITADELの起動", "DESCRIPTION": "このチェックリストを使用して、重要な手順を確認しながらインスタンスをセットアップします。" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "ブランドをセットアップする", "description": "ログインの色と形状を定義し、ロゴとアイコンをアップロードします。", @@ -62,15 +62,20 @@ "description": "独自のメールサーバーを設定します。", "action": "SMTP 設定を設定する" }, - "project.added": { + "PROJECT_CREATED": { "title": "最初のプロジェクトを作成する", "description": "最初のプロジェクトを追加し、ロールと認証を定義します。", "action": "プロジェクトを作成" }, - "project.application.added": { - "title": "最初のアプリケーションを作成する", - "description": "Web、ネイティブ、API、またはSAMLアプリケーションを作成し、認証フローをセットアップします。", - "action": "アプリケーションを作成" + "APPLICATION_CREATED": { + "title": "アプリを登録する", + "description": "Web、ネイティブ、API、またはSAMLアプリケーションを登録し、認証フローをセットアップします。", + "action": "アプリを登録する" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "アプリにログインする", + "description": "アプリケーションをZITADELと統合して認証し、管理者ユーザーでログインしてテストします。", + "action": "ログイン" }, "user.human.added": { "title": "ユーザーを追加する", diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 4c5f05fed0..c152cb14b5 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -51,7 +51,7 @@ "TITLE": "Почнете со ZITADEL", "DESCRIPTION": "Оваа листа со чекори помага при подесувањето на вашата инстанца и ве води низ најважните чекори" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Подесете го вашиот бренд", "description": "Дефинирајте боја и форма за вашиот процез за најава и прикачете ги вашите лого и икони.", @@ -62,15 +62,20 @@ "description": "Подесете го вашиот сервер за е-пошта.", "action": "Подеси SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Креирајте проект", "description": "Додадете проект и дефинирајте ги неговите улоги и овластувања.", "action": "Креирај проект" }, - "project.application.added": { - "title": "Креирајте апликација", - "description": "Креирајте веб, нативна, API или SAML апликација и подесете го вашите автентикациски правила.", - "action": "Креирај апликација" + "APPLICATION_CREATED": { + "title": "Регистрирајте ја вашата апликација", + "description": "Регистрирајте ја вашата веб, нативна, API или SAML апликација и подесете ја автентикацијата.", + "action": "Регистрирај апликација" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Најавете се во вашата апликација", + "description": "Интегрирајте ја вашата апликација со ZITADEL за автентикација и тестирајте ја со најавување со вашиот администраторски корисник.", + "action": "Најави се" }, "user.human.added": { "title": "Додадете корисници", diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index 43a0fa385a..02c10021ce 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -51,7 +51,7 @@ "TITLE": "Uruchom swój ZITADEL", "DESCRIPTION": "Ta lista kontrolna pomoże Ci skonfigurować instancję i poprowadzi Cię przez najważniejsze kroki." }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Skonfiguruj swoją markę", "description": "Zdefiniuj kolorystykę i kształt swojego loginu oraz wgraj swoje logo i ikony.", @@ -62,15 +62,20 @@ "description": "Ustawienie własnego serwera pocztowego", "action": "skonfiguruj ustawienia SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Stwórz swój pierwszy projekt", "description": "Dodaj swój pierwszy projekt i określ jego role i uprawnienia.", "action": "Utwórz projekt" }, - "project.application.added": { - "title": "Utwórz swoją pierwszą aplikację", - "description": "Utwórz aplikację internetową, natywną, api lub saml i skonfiguruj swój przepływ uwierzytelniania.", - "action": "Utwórz aplikację" + "APPLICATION_CREATED": { + "title": "Zarejestruj swoją aplikację", + "description": "Zarejestruj swoją aplikację webową, natywną, API lub SAML i skonfiguruj przepływ uwierzytelniania.", + "action": "Zarejestruj aplikację" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Zaloguj się do swojej aplikacji", + "description": "Zintegruj swoją aplikację z ZITADEL w celu uwierzytelniania i przetestuj ją, logując się za pomocą swojego użytkownika administratora.", + "action": "Zaloguj się" }, "user.human.added": { "title": "Dodaj użytkowników", diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index c79da71aca..07deb19921 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -51,7 +51,7 @@ "TITLE": "Inicie o ZITADEL", "DESCRIPTION": "Esta lista de verificação ajuda a configurar sua instância e orienta você nas etapas mais essenciais" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "Configure sua marca", "description": "Defina cores e forma para o seu login e faça o upload do seu logotipo e ícones.", @@ -62,15 +62,20 @@ "description": "Configure as configurações do seu próprio servidor de e-mail.", "action": "Configurar SMTP" }, - "project.added": { + "PROJECT_CREATED": { "title": "Crie um projeto", "description": "Adicione um projeto e defina suas funções e autorizações.", "action": "Criar projeto" }, - "project.application.added": { - "title": "Crie um aplicativo", - "description": "Crie um aplicativo da web, nativo, API ou SAML e configure o fluxo de autenticação.", - "action": "Criar aplicativo" + "APPLICATION_CREATED": { + "title": "Registre seu aplicativo", + "description": "Registre seu aplicativo web, nativo, api ou saml e configure um fluxo de autenticação.", + "action": "Registrar aplicativo" + }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "Faça login no seu aplicativo", + "description": "Integre seu aplicativo com o ZITADEL para autenticação e teste-o fazendo login com seu usuário administrador.", + "action": "Faça login" }, "user.human.added": { "title": "Adicione usuários", diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 6b14890087..626dae19aa 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -51,7 +51,7 @@ "TITLE": "让你的ZITADEL运转起来", "DESCRIPTION": "这份清单有助于设置你的实例,并指导你完成最重要的步骤" }, - "EVENTS": { + "MILESTONES": { "instance.policy.label.added": { "title": "设置你的品牌", "description": "定义你的登录的颜色和形状,上传你的标志和图标。", @@ -62,16 +62,21 @@ "description": "设置你自己的邮件服务器设置", "action": "设置 SMTP 设置" }, - "project.added": { + "PROJECT_CREATED": { "title": "创建你的第一个项目", "description": "添加你的第一个项目并定义其角色和授权。", "action": "创建项目" }, - "project.application.added": { - "title": "创建你的第一个应用程序", + "APPLICATION_CREATED": { + "title": "注册你的应用程序", "description": "创建一个web、native、api或saml应用程序并设置你的认证流程。", "action": "创建应用程序" }, + "AUTHENTICATION_SUCCEEDED_ON_APPLICATION": { + "title": "登录你的应用程序", + "description": "将你的应用程序与 ZITADEL 集成以进行身份验证,并通过使用管理员用户登录来测试它。", + "action": "登录" + }, "user.human.added": { "title": "添加用户", "description": "添加你的应用程序用户", diff --git a/internal/api/grpc/admin/milestone.go b/internal/api/grpc/admin/milestone.go new file mode 100644 index 0000000000..e06e5c15f0 --- /dev/null +++ b/internal/api/grpc/admin/milestone.go @@ -0,0 +1,24 @@ +package admin + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + object_pb "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/pkg/grpc/admin" +) + +func (s *Server) ListMilestones(ctx context.Context, req *admin.ListMilestonesRequest) (*admin.ListMilestonesResponse, error) { + queries, err := listMilestonesToModel(authz.GetInstance(ctx).InstanceID(), req) + if err != nil { + return nil, err + } + resp, err := s.query.SearchMilestones(ctx, []string{authz.GetInstance(ctx).InstanceID()}, queries) + if err != nil { + return nil, err + } + return &admin.ListMilestonesResponse{ + Result: milestoneViewsToPb(resp.Milestones), + Details: object_pb.ToListDetails(resp.Count, resp.Sequence, resp.LastRun), + }, nil +} diff --git a/internal/api/grpc/admin/milestone_converter.go b/internal/api/grpc/admin/milestone_converter.go new file mode 100644 index 0000000000..0419cd3fe0 --- /dev/null +++ b/internal/api/grpc/admin/milestone_converter.go @@ -0,0 +1,99 @@ +package admin + +import ( + "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/query" + "github.com/zitadel/zitadel/internal/repository/milestone" + admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" + milestone_pb "github.com/zitadel/zitadel/pkg/grpc/milestone" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func listMilestonesToModel(instanceID string, req *admin_pb.ListMilestonesRequest) (*query.MilestonesSearchQueries, error) { + offset, limit, asc := object.ListQueryToModel(req.Query) + queries, err := milestoneQueriesToModel(req.GetQueries()) + instanceIDQuery, err := query.NewTextQuery(query.MilestoneInstanceIDColID, instanceID, query.TextEquals) + if err != nil { + return nil, err + } + queries = append(queries, instanceIDQuery) + return &query.MilestonesSearchQueries{ + SearchRequest: query.SearchRequest{ + Offset: offset, + Limit: limit, + Asc: asc, + SortingColumn: milestoneFieldNameToSortingColumn(req.SortingColumn), + }, + Queries: queries, + }, nil +} + +func milestoneQueriesToModel(queries []*milestone_pb.MilestoneQuery) (q []query.SearchQuery, err error) { + q = make([]query.SearchQuery, len(queries)) + for i, query := range queries { + q[i], err = milestoneQueryToModel(query) + if err != nil { + return nil, err + } + } + return q, nil +} + +func milestoneQueryToModel(milestoneQuery *milestone_pb.MilestoneQuery) (query.SearchQuery, error) { + switch q := milestoneQuery.Query.(type) { + case *milestone_pb.MilestoneQuery_IsReachedQuery: + if q.IsReachedQuery.GetReached() { + return query.NewNotNullQuery(query.MilestoneReachedDateColID) + } + return query.NewIsNullQuery(query.MilestoneReachedDateColID) + default: + return nil, errors.ThrowInvalidArgument(nil, "ADMIN-sE7pc", "List.Query.Invalid") + } +} + +func milestoneFieldNameToSortingColumn(field milestone_pb.MilestoneFieldName) query.Column { + switch field { + case milestone_pb.MilestoneFieldName_MILESTONE_FIELD_NAME_REACHED_DATE: + return query.MilestoneReachedDateColID + default: + return query.MilestoneTypeColID + } +} + +func milestoneViewsToPb(milestones []*query.Milestone) []*milestone_pb.Milestone { + resp := make([]*milestone_pb.Milestone, len(milestones)) + for i, idp := range milestones { + resp[i] = modelMilestoneViewToPb(idp) + } + return resp +} + +func modelMilestoneViewToPb(m *query.Milestone) *milestone_pb.Milestone { + mspb := &milestone_pb.Milestone{ + Type: modelMilestoneTypeToPb(m.Type), + } + if !m.ReachedDate.IsZero() { + mspb.ReachedDate = timestamppb.New(m.ReachedDate) + } + return mspb +} + +func modelMilestoneTypeToPb(t milestone.Type) milestone_pb.MilestoneType { + switch t { + case milestone.InstanceCreated: + return milestone_pb.MilestoneType_MILESTONE_TYPE_INSTANCE_CREATED + case milestone.AuthenticationSucceededOnInstance: + return milestone_pb.MilestoneType_MILESTONE_TYPE_AUTHENTICATION_SUCCEEDED_ON_INSTANCE + case milestone.ProjectCreated: + return milestone_pb.MilestoneType_MILESTONE_TYPE_PROJECT_CREATED + case milestone.ApplicationCreated: + return milestone_pb.MilestoneType_MILESTONE_TYPE_APPLICATION_CREATED + case milestone.AuthenticationSucceededOnApplication: + return milestone_pb.MilestoneType_MILESTONE_TYPE_AUTHENTICATION_SUCCEEDED_ON_APPLICATION + case milestone.InstanceDeleted: + return milestone_pb.MilestoneType_MILESTONE_TYPE_INSTANCE_DELETED + default: + return milestone_pb.MilestoneType_MILESTONE_TYPE_UNSPECIFIED + } +} diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index c1f32616b2..c7a2faab6f 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -14,6 +14,7 @@ import "zitadel/event.proto"; import "zitadel/management.proto"; import "zitadel/v1.proto"; import "zitadel/message.proto"; +import "zitadel/milestone/v1/milestone.proto"; import "google/api/annotations.proto"; import "google/api/field_behavior.proto"; @@ -3773,6 +3774,23 @@ service AdminService { permission: "iam.feature.write"; }; } + + rpc ListMilestones(ListMilestonesRequest) returns (ListMilestonesResponse) { + option (google.api.http) = { + post: "/milestones/_search"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "milestones.read"; + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Milestones"; + summary: "Search Milestones"; + description: "Returns a list of reached instance usage milestones." + }; + } } @@ -7892,4 +7910,18 @@ message ActivateFeatureLoginDefaultOrgRequest {} message ActivateFeatureLoginDefaultOrgResponse { zitadel.v1.ObjectDetails details = 1; -} \ No newline at end of file +} + +message ListMilestonesRequest { + //list limitations and ordering + zitadel.v1.ListQuery query = 1; + // the field the result is sorted + zitadel.milestone.v1.MilestoneFieldName sorting_column = 2; + //criteria the client is looking for + repeated zitadel.milestone.v1.MilestoneQuery queries = 3; +} + +message ListMilestonesResponse { + zitadel.v1.ListDetails details = 1; + repeated zitadel.milestone.v1.Milestone result = 2; +} diff --git a/proto/zitadel/milestone/v1/milestone.proto b/proto/zitadel/milestone/v1/milestone.proto new file mode 100644 index 0000000000..36e19beeb7 --- /dev/null +++ b/proto/zitadel/milestone/v1/milestone.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +import "zitadel/object.proto"; +import "validate/validate.proto"; +import "google/protobuf/timestamp.proto"; + +import "protoc-gen-openapiv2/options/annotations.proto"; + +package zitadel.milestone.v1; + +option go_package ="github.com/zitadel/zitadel/pkg/grpc/milestone"; + +enum MilestoneType { + MILESTONE_TYPE_UNSPECIFIED = 0; + MILESTONE_TYPE_INSTANCE_CREATED = 1; + MILESTONE_TYPE_AUTHENTICATION_SUCCEEDED_ON_INSTANCE = 2; + MILESTONE_TYPE_PROJECT_CREATED = 3; + MILESTONE_TYPE_APPLICATION_CREATED = 4; + MILESTONE_TYPE_AUTHENTICATION_SUCCEEDED_ON_APPLICATION = 5; + MILESTONE_TYPE_INSTANCE_DELETED = 6; +} + +enum MilestoneFieldName { + MILESTONE_FIELD_NAME_UNSPECIFIED = 0; + MILESTONE_FIELD_NAME_TYPE = 1; + MILESTONE_FIELD_NAME_REACHED_DATE = 2; +} + +message Milestone { + // For the milestones, the standard details are not projected yet + reserved 1; + reserved "details"; + MilestoneType type = 2; + google.protobuf.Timestamp reached_date = 3; +} + +message MilestoneQuery { + oneof query { + IsReachedQuery is_reached_query = 1; + } +} + +message IsReachedQuery { + bool reached = 1 [ + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + description: "only reached milestones"; + } + ]; +}