From 3042bbb9932a9c9ce76a1c5b6039fbf3c334260a Mon Sep 17 00:00:00 2001 From: Ramon Date: Mon, 17 Feb 2025 19:25:46 +0100 Subject: [PATCH] feat: Use V2 API's in Console (#9312) # Which Problems Are Solved Solves #8976 # Additional Changes I have done some intensive refactorings and we are using the new @zitadel/client package for GRPC access. # Additional Context - Closes #8976 --------- Co-authored-by: Max Peintner --- console/angular.json | 2 +- console/package.json | 4 + console/src/app/app.component.html | 2 +- console/src/app/app.component.ts | 26 +- .../features/features.component.html | 28 + .../components/features/features.component.ts | 15 +- .../directives/has-role/has-role.directive.ts | 15 +- .../accounts-card/accounts-card.component.ts | 5 +- .../app/modules/footer/footer.component.html | 4 +- .../app/modules/header/header.component.html | 6 +- .../modules/info-row/info-row.component.html | 12 +- .../modules/info-row/info-row.component.ts | 97 ++- .../metadata-dialog.component.ts | 85 +- .../metadata/metadata/metadata.component.html | 9 +- .../metadata/metadata/metadata.component.ts | 41 +- .../modules/paginator/paginator.component.ts | 3 +- .../refresh-table/refresh-table.component.ts | 3 +- .../app/modules/sidenav/sidenav.component.ts | 20 +- .../pages/signedout/signedout.component.ts | 23 +- .../user-create/user-create.component.html | 49 +- .../user-create/user-create.component.scss | 1 + .../user-create/user-create.component.ts | 458 +++++++---- .../auth-user-detail.component.html | 329 ++++---- .../auth-user-detail.component.ts | 722 ++++++++-------- .../edit-dialog/edit-dialog.component.html | 2 +- .../edit-dialog/edit-dialog.component.ts | 22 +- .../resend-email-dialog.component.html | 2 +- .../resend-email-dialog.component.ts | 14 +- .../contact/contact.component.html | 10 +- .../user-detail/contact/contact.component.ts | 22 +- .../detail-form-machine.component.html | 2 +- .../detail-form-machine.component.ts | 94 ++- .../detail-form/detail-form.component.html | 22 +- .../detail-form/detail-form.component.ts | 172 ++-- .../external-idps/external-idps.component.ts | 98 ++- .../password/password.component.html | 30 +- .../password/password.component.ts | 310 ++++--- .../passwordless/passwordless.component.html | 8 +- .../passwordless/passwordless.component.ts | 58 +- .../user-detail/user-detail.component.html | 531 ++++++------ .../user-detail/user-detail.component.ts | 768 ++++++++++-------- .../user-mfa/user-mfa.component.html | 38 +- .../user-mfa/user-mfa.component.ts | 206 ++--- .../user-table/user-table.component.html | 56 +- .../user-table/user-table.component.ts | 152 ++-- .../timestamp-to-date.pipe.ts | 10 +- console/src/app/services/feature.service.ts | 14 +- console/src/app/services/grpc-auth.service.ts | 245 +++--- console/src/app/services/grpc.service.ts | 162 ++-- .../services/interceptors/auth.interceptor.ts | 120 +-- .../exhausted.grpc.interceptor.ts | 18 +- .../services/interceptors/i18n.interceptor.ts | 10 +- .../services/interceptors/org.interceptor.ts | 26 +- console/src/app/services/new-auth.service.ts | 30 + console/src/app/services/new-mgmt.service.ts | 92 +++ console/src/app/services/user.service.ts | 302 +++++++ console/src/app/utils/formatPhone.ts | 2 +- console/src/app/utils/pairwiseStartWith.ts | 11 + console/src/assets/i18n/bg.json | 15 +- console/src/assets/i18n/cs.json | 15 +- console/src/assets/i18n/de.json | 15 +- console/src/assets/i18n/en.json | 15 +- console/src/assets/i18n/es.json | 15 +- console/src/assets/i18n/fr.json | 15 +- console/src/assets/i18n/hu.json | 15 +- console/src/assets/i18n/id.json | 15 +- console/src/assets/i18n/it.json | 15 +- console/src/assets/i18n/ja.json | 15 +- console/src/assets/i18n/ko.json | 15 +- console/src/assets/i18n/mk.json | 15 +- console/src/assets/i18n/nl.json | 15 +- console/src/assets/i18n/pl.json | 15 +- console/src/assets/i18n/pt.json | 15 +- console/src/assets/i18n/ru.json | 15 +- console/src/assets/i18n/sv.json | 15 +- console/src/assets/i18n/zh.json | 15 +- console/yarn.lock | 44 + e2e/cypress/e2e/machines/machines.cy.ts | 4 +- internal/api/grpc/feature/v2/converter.go | 2 + .../api/grpc/feature/v2/converter_test.go | 10 + internal/command/instance_features.go | 3 +- internal/command/instance_features_model.go | 5 + internal/feature/feature.go | 2 + internal/feature/key_enumer.go | 12 +- internal/query/instance_features.go | 1 + internal/query/instance_features_model.go | 3 + .../query/projection/instance_features.go | 4 + .../feature/feature_v2/eventstore.go | 1 + .../repository/feature/feature_v2/feature.go | 1 + proto/zitadel/feature/v2/instance.proto | 14 + 90 files changed, 3679 insertions(+), 2315 deletions(-) create mode 100644 console/src/app/services/new-auth.service.ts create mode 100644 console/src/app/services/new-mgmt.service.ts create mode 100644 console/src/app/services/user.service.ts create mode 100644 console/src/app/utils/pairwiseStartWith.ts diff --git a/console/angular.json b/console/angular.json index 278498ccd7..5564b2c428 100644 --- a/console/angular.json +++ b/console/angular.json @@ -63,7 +63,7 @@ { "type": "initial", "maximumWarning": "8mb", - "maximumError": "9mb" + "maximumError": "10mb" }, { "type": "anyComponentStyle", diff --git a/console/package.json b/console/package.json index fcf3a4bbf8..2c1d38da1b 100644 --- a/console/package.json +++ b/console/package.json @@ -24,6 +24,8 @@ "@angular/platform-browser-dynamic": "^16.2.5", "@angular/router": "^16.2.5", "@angular/service-worker": "^16.2.5", + "@connectrpc/connect": "^2.0.0", + "@connectrpc/connect-web": "^2.0.0", "@ctrl/ngx-codemirror": "^6.1.0", "@fortawesome/angular-fontawesome": "^0.13.0", "@fortawesome/fontawesome-svg-core": "^6.4.2", @@ -31,6 +33,8 @@ "@grpc/grpc-js": "^1.11.2", "@netlify/framework-info": "^9.8.13", "@ngx-translate/core": "^15.0.0", + "@zitadel/client": "^1.0.6", + "@zitadel/proto": "^1.0.3", "angular-oauth2-oidc": "^15.0.1", "angularx-qrcode": "^16.0.0", "buffer": "^6.0.3", diff --git a/console/src/app/app.component.html b/console/src/app/app.component.html index 5b31b33dc4..18c5c72501 100644 --- a/console/src/app/app.component.html +++ b/console/src/app/app.component.html @@ -1,5 +1,5 @@
- + { // We use navigateByUrl as our urls may have queryParams - this.router.navigateByUrl(currentUrl); + this.router.navigateByUrl(currentUrl).then(); }); } @@ -283,18 +283,16 @@ export class AppComponent implements OnDestroy { this.translate.addLangs(supportedLanguages); this.translate.setDefaultLang(fallbackLanguage); - this.authService.userSubject.pipe(takeUntil(this.destroy$)).subscribe((userprofile) => { - if (userprofile) { - const cropped = navigator.language.split('-')[0] ?? fallbackLanguage; - const fallbackLang = cropped.match(supportedLanguagesRegexp) ? cropped : fallbackLanguage; + this.authService.user.pipe(filter(Boolean), takeUntil(this.destroy$)).subscribe((userprofile) => { + const cropped = navigator.language.split('-')[0] ?? fallbackLanguage; + const fallbackLang = cropped.match(supportedLanguagesRegexp) ? cropped : fallbackLanguage; - const lang = userprofile?.human?.profile?.preferredLanguage.match(supportedLanguagesRegexp) - ? userprofile.human.profile?.preferredLanguage - : fallbackLang; - this.translate.use(lang); - this.language = lang; - this.document.documentElement.lang = lang; - } + const lang = userprofile?.human?.profile?.preferredLanguage.match(supportedLanguagesRegexp) + ? userprofile.human.profile?.preferredLanguage + : fallbackLang; + this.translate.use(lang); + this.language = lang; + this.document.documentElement.lang = lang; }); } @@ -308,7 +306,7 @@ export class AppComponent implements OnDestroy { } private setFavicon(theme: string): void { - this.authService.labelpolicy.pipe(takeUntil(this.destroy$)).subscribe((lP) => { + this.authService.labelpolicy$.pipe(startWith(undefined), takeUntil(this.destroy$)).subscribe((lP) => { if (theme === 'dark-theme' && lP?.iconUrlDark) { // Check if asset url is stable, maybe it was deleted but still wasn't applied fetch(lP.iconUrlDark).then((response) => { diff --git a/console/src/app/components/features/features.component.html b/console/src/app/components/features/features.component.html index e663569210..fdd397084a 100644 --- a/console/src/app/components/features/features.component.html +++ b/console/src/app/components/features/features.component.html @@ -403,6 +403,34 @@ 'SETTING.FEATURES.OIDCSINGLEV1SESSIONTERMINATION_DESCRIPTION' | translate }}
+ +
+ {{ 'SETTING.FEATURES.CONSOLEUSEV2USERAPI' | translate }} +
+ + +
+ {{ 'SETTING.FEATURES.STATES.DISABLED' | translate }} +
+
+ +
+ {{ 'SETTING.FEATURES.STATES.ENABLED' | translate }} +
+
+
+
+ {{ + 'SETTING.FEATURES.CONSOLEUSEV2USERAPI_DESCRIPTION' | translate + }} +
diff --git a/console/src/app/components/features/features.component.ts b/console/src/app/components/features/features.component.ts index 327e9d2792..0f8ff761f6 100644 --- a/console/src/app/components/features/features.component.ts +++ b/console/src/app/components/features/features.component.ts @@ -16,13 +16,14 @@ import { InfoSectionModule } from 'src/app/modules/info-section/info-section.mod import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { Event } from 'src/app/proto/generated/zitadel/event_pb'; import { Source } from 'src/app/proto/generated/zitadel/feature/v2beta/feature_pb'; -import { - GetInstanceFeaturesResponse, - SetInstanceFeaturesRequest, -} from 'src/app/proto/generated/zitadel/feature/v2beta/instance_pb'; import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service'; import { FeatureService } from 'src/app/services/feature.service'; import { ToastService } from 'src/app/services/toast.service'; +import { + GetInstanceFeaturesResponse, + SetInstanceFeaturesRequest, +} from '../../proto/generated/zitadel/feature/v2/instance_pb'; +import { withIdentifier } from 'codelyzer/util/astQuery'; enum ToggleState { ENABLED = 'ENABLED', @@ -39,6 +40,7 @@ type ToggleStates = { oidcTokenExchange?: FeatureState; actions?: FeatureState; oidcSingleV1SessionTermination?: FeatureState; + consoleUseV2UserApi?: FeatureState; }; @Component({ @@ -142,6 +144,7 @@ export class FeaturesComponent implements OnDestroy { ); changed = true; } + req.setConsoleUseV2UserApi(this.toggleStates?.consoleUseV2UserApi?.state === ToggleState.ENABLED); if (changed) { this.featureService @@ -232,6 +235,10 @@ export class FeaturesComponent implements OnDestroy { ? ToggleState.ENABLED : ToggleState.DISABLED, }, + consoleUseV2UserApi: { + source: this.featureData.consoleUseV2UserApi?.source || Source.SOURCE_INSTANCE, + state: this.featureData.consoleUseV2UserApi?.enabled ? ToggleState.ENABLED : ToggleState.DISABLED, + }, }; }); } diff --git a/console/src/app/directives/has-role/has-role.directive.ts b/console/src/app/directives/has-role/has-role.directive.ts index b58e1f3a10..9ba21c1dd2 100644 --- a/console/src/app/directives/has-role/has-role.directive.ts +++ b/console/src/app/directives/has-role/has-role.directive.ts @@ -1,18 +1,17 @@ -import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core'; -import { Subject, takeUntil } from 'rxjs'; +import { DestroyRef, Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @Directive({ selector: '[cnslHasRole]', }) -export class HasRoleDirective implements OnDestroy { - private destroy$: Subject = new Subject(); +export class HasRoleDirective { private hasView: boolean = false; @Input() public set hasRole(roles: string[] | RegExp[] | undefined) { if (roles && roles.length > 0) { this.authService .isAllowed(roles) - .pipe(takeUntil(this.destroy$)) + .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((isAllowed) => { if (isAllowed && !this.hasView) { if (this.viewContainerRef.length !== 0) { @@ -38,10 +37,6 @@ export class HasRoleDirective implements OnDestroy { private authService: GrpcAuthService, protected templateRef: TemplateRef, protected viewContainerRef: ViewContainerRef, + private readonly destroyRef: DestroyRef, ) {} - - ngOnDestroy(): void { - this.destroy$.next(); - this.destroy$.complete(); - } } diff --git a/console/src/app/modules/accounts-card/accounts-card.component.ts b/console/src/app/modules/accounts-card/accounts-card.component.ts index 617a41bf6d..2676a5bcf5 100644 --- a/console/src/app/modules/accounts-card/accounts-card.component.ts +++ b/console/src/app/modules/accounts-card/accounts-card.component.ts @@ -4,6 +4,7 @@ import { AuthConfig } from 'angular-oauth2-oidc'; import { Session, User, UserState } from 'src/app/proto/generated/zitadel/user_pb'; import { AuthenticationService } from 'src/app/services/authentication.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; +import { toSignal } from '@angular/core/rxjs-interop'; @Component({ selector: 'cnsl-accounts-card', @@ -18,6 +19,8 @@ export class AccountsCardComponent implements OnInit { public sessions: Session.AsObject[] = []; public loadingUsers: boolean = false; public UserState: any = UserState; + private labelpolicy = toSignal(this.userService.labelpolicy$, { initialValue: undefined }); + constructor( public authService: AuthenticationService, private router: Router, @@ -68,7 +71,7 @@ export class AccountsCardComponent implements OnInit { } public logout(): void { - const lP = JSON.stringify(this.userService.labelpolicy.getValue()); + const lP = JSON.stringify(this.labelpolicy()); localStorage.setItem('labelPolicyOnSignout', lP); this.authService.signout(); diff --git a/console/src/app/modules/footer/footer.component.html b/console/src/app/modules/footer/footer.component.html index 26d863d129..b9eda2d7db 100644 --- a/console/src/app/modules/footer/footer.component.html +++ b/console/src/app/modules/footer/footer.component.html @@ -1,6 +1,6 @@