fix(console): remove rehauled navigation #10863 (#10912)

# Which Problems Are Solved
The new breadcrumbs were causing problems with some more advanced
Zitadel setups. This pr removes the new breadcrumbs to revert to the old
navigation behaviour.

# How the Problems Are Solved
Most of the changes could be kept but those specific to the navigation
where mostly reverted.

# Additional Changes
Updated some dependencies.

# Additional Context
- Closes #10863
This commit is contained in:
Ramon
2026-01-16 08:30:09 +01:00
committed by GitHub
parent 13be5dc46d
commit e401da3cfa
49 changed files with 667 additions and 1452 deletions
@@ -5,9 +5,7 @@ import { OrgTableModule } from 'src/app/modules/org-table/org-table.module';
import { ActionsRoutingModule } from './actions-routing.module';
import { ActionsComponent } from './actions.component';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { SidenavModule } from 'src/app/modules/sidenav/sidenav.module';
import { ActionsTwoActionsComponent } from 'src/app/modules/actions-two/actions-two-actions/actions-two-actions.component';
import ActionsTwoModule from 'src/app/modules/actions-two/actions-two.module';
import { FormsModule } from '@angular/forms';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
+3 -16
View File
@@ -1,4 +1,4 @@
import { Component, effect } from '@angular/core';
import { Component } from '@angular/core';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
@@ -27,8 +27,6 @@ export class HomeComponent {
protected readonly PolicyComponentServiceType = PolicyComponentServiceType;
private readonly permissions = this.newAuthService.listMyZitadelPermissionsQuery();
constructor(
public authService: GrpcAuthService,
private readonly newAuthService: NewAuthService,
@@ -37,24 +35,13 @@ export class HomeComponent {
private readonly router: Router,
) {
const bread: Breadcrumb = {
type: BreadcrumbType.INSTANCE,
routerLink: ['/'],
type: BreadcrumbType.ORG,
routerLink: ['/org'],
};
breadcrumbService.setBreadcrumb([bread]);
const theme = localStorage.getItem('theme');
this.dark = theme === 'dark-theme' ? true : theme === 'light-theme' ? false : true;
effect(() => {
const permission = this.permissions.data();
if (!permission) {
return;
}
if (permission.includes('iam.read')) {
return;
}
this.router.navigate(['/org']).then();
});
}
}
@@ -1,8 +1,8 @@
import { Component, DestroyRef } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { BehaviorSubject, defer, from, Observable, of, shareReplay, TimeoutError } from 'rxjs';
import { catchError, finalize, map, timeout } from 'rxjs/operators';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, from, Observable, of } from 'rxjs';
import { catchError, finalize, map } from 'rxjs/operators';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { InstanceDetail, State } from 'src/app/proto/generated/zitadel/instance_pb';
@@ -34,10 +34,11 @@ import {
FAILEDEVENTS,
EVENTS,
FEATURESETTINGS,
ACTIONS,
ACTIONS_TARGETS,
} from 'src/app/modules/settings-list/settings';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { EnvironmentService } from 'src/app/services/environment.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'cnsl-instance',
@@ -77,6 +78,9 @@ export class InstanceComponent {
VIEWS,
EVENTS,
FAILEDEVENTS,
//actions
ACTIONS,
ACTIONS_TARGETS,
// others
PRIVACYPOLICY,
LANGUAGES,
@@ -3,18 +3,26 @@
*ngIf="['org.write:' + org.id, 'org.write$'] | hasRole as hasWrite$"
[hasBackButton]="false"
title="{{ org.name }}"
[isActive]="org.state === OrganizationState.ACTIVE"
[isInactive]="org.state === OrganizationState.INACTIVE"
[isActive]="$any(org.state) === OrganizationState.ACTIVE"
[isInactive]="$any(org.state) === OrganizationState.INACTIVE"
[hasContributors]="true"
stateTooltip="{{ 'ORG.STATE.' + org.state | translate }}"
[hasActions]="hasWrite$ | async"
>
<ng-container topActions *ngIf="hasWrite$ | async">
<button mat-menu-item *ngIf="org.state === OrganizationState.ACTIVE" (click)="changeState(OrganizationState.INACTIVE)">
<button
mat-menu-item
*ngIf="$any(org.state) === OrganizationState.ACTIVE"
(click)="changeState(OrganizationState.INACTIVE)"
>
{{ 'ORG.PAGES.DEACTIVATE' | translate }}
</button>
<button mat-menu-item *ngIf="org.state === OrganizationState.INACTIVE" (click)="changeState(OrganizationState.ACTIVE)">
<button
mat-menu-item
*ngIf="$any(org.state) === OrganizationState.INACTIVE"
(click)="changeState(OrganizationState.ACTIVE)"
>
{{ 'ORG.PAGES.REACTIVATE' | translate }}
</button>
@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Component, effect, OnInit, signal } from '@angular/core';
import { ChangeDetectorRef, Component, OnInit, signal } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { BehaviorSubject, from, lastValueFrom, Observable, of } from 'rxjs';
@@ -19,7 +19,9 @@ import { ToastService } from 'src/app/services/toast.service';
import { NewOrganizationService } from '../../../services/new-organization.service';
import { injectMutation } from '@tanstack/angular-query-experimental';
import { Organization, OrganizationState } from '@zitadel/proto/zitadel/org/v2/org_pb';
import { toObservable } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { GrpcAuthService } from '../../../services/grpc-auth.service';
import { Org } from '@zitadel/proto/zitadel/org_pb';
@Component({
selector: 'cnsl-org-detail',
@@ -53,6 +55,7 @@ export class OrgDetailComponent implements OnInit {
protected reloadChanges = signal(true);
constructor(
private readonly authService: GrpcAuthService,
private readonly dialog: MatDialog,
private readonly mgmtService: ManagementService,
private readonly toast: ToastService,
@@ -67,17 +70,14 @@ export class OrgDetailComponent implements OnInit {
};
breadcrumbService.setBreadcrumb([bread]);
effect(() => {
const orgId = this.newOrganizationService.orgId();
if (!orgId) {
authService.activeOrgChanged.pipe(takeUntilDestroyed()).subscribe((org) => {
if (!org) {
return;
}
this.loadMembers();
this.loadMetadata();
});
// force rerender changes because it is not reactive to orgId changes
toObservable(this.newOrganizationService.orgId).subscribe(() => {
// force rerender changes because it is not reactive to orgId changes
this.reloadChanges.set(false);
cdr.detectChanges();
this.reloadChanges.set(true);
@@ -137,7 +137,7 @@ export class OrgDetailComponent implements OnInit {
}
}
public async deleteOrg(org: Organization) {
public async deleteOrg(org: Organization | Org) {
const mgmtUserData = {
confirmKey: 'ACTIONS.DELETE',
cancelKey: 'ACTIONS.CANCEL',
@@ -265,7 +265,7 @@ export class OrgDetailComponent implements OnInit {
});
}
public async renameOrg(org: Organization): Promise<void> {
public async renameOrg(org: Organization | Org): Promise<void> {
const dialogRef = this.dialog.open(NameDialogComponent, {
data: {
name: org.name,
@@ -286,7 +286,7 @@ export class OrgDetailComponent implements OnInit {
this.toast.showInfo('ORG.TOAST.UPDATED', true);
const resp = await this.mgmtService.getMyOrg();
if (resp.org) {
await this.newOrganizationService.setOrgId(resp.org.id);
await this.authService.getActiveOrg(resp.org.id);
}
} catch (error) {
this.toast.showError(error);
@@ -7,7 +7,6 @@ import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
import { BehaviorSubject, Observable, Subject, takeUntil } from 'rxjs';
import { ProjectType } from 'src/app/modules/project-members/project-members-datasource';
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { GrantedProject, Project, ProjectState } from 'src/app/proto/generated/zitadel/project_pb';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageKey, StorageLocation, StorageService } from 'src/app/services/storage.service';
@@ -56,7 +56,6 @@ export class UserCreateV2Component implements OnInit {
private readonly passwordComplexityPolicy$: Observable<PasswordComplexityPolicy>;
protected readonly authenticationFactor$: Observable<AuthenticationFactor>;
private readonly useLoginV2$: Observable<LoginV2FeatureFlag | undefined>;
private orgId = this.organizationService.getOrgId();
constructor(
private readonly router: Router,
@@ -70,7 +69,6 @@ export class UserCreateV2Component implements OnInit {
private readonly route: ActivatedRoute,
protected readonly location: Location,
private readonly authService: GrpcAuthService,
private readonly organizationService: NewOrganizationService,
) {
this.userForm = this.buildUserForm();
@@ -186,11 +184,11 @@ export class UserCreateV2Component implements OnInit {
private async createUserV2Try(authenticationFactor: AuthenticationFactor) {
this.loading.set(true);
this.organizationService.getOrgId();
const activeOrg = await this.authService.getActiveOrg();
const userValues = this.userForm.getRawValue();
const humanReq: MessageInitShape<typeof AddHumanUserRequestSchema> = {
organization: { org: { case: 'orgId', value: this.orgId() } },
organization: { org: { case: 'orgId', value: activeOrg.id } },
username: userValues.username,
profile: {
givenName: userValues.givenName,
@@ -22,6 +22,7 @@ import {
delay,
distinctUntilChanged,
EMPTY,
from,
Observable,
of,
ReplaySubject,
@@ -35,7 +36,7 @@ import { PaginatorComponent } from 'src/app/modules/paginator/paginator.componen
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
import { ToastService } from 'src/app/services/toast.service';
import { UserService } from 'src/app/services/user.service';
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
import { SearchQuery as UserSearchQuery } from 'src/app/proto/generated/zitadel/user_pb';
import { Type, UserFieldName } from '@zitadel/proto/zitadel/user/v2/query_pb';
import { UserState, User } from '@zitadel/proto/zitadel/user/v2/user_pb';
@@ -43,6 +44,8 @@ import { MessageInitShape } from '@bufbuild/protobuf';
import { ListUsersRequestSchema, ListUsersResponse } from '@zitadel/proto/zitadel/user/v2/user_service_pb';
import { UserState as UserStateV1 } from 'src/app/proto/generated/zitadel/user_pb';
import { NewOrganizationService } from 'src/app/services/new-organization.service';
import { AuthenticationService } from 'src/app/services/authentication.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
type ListUsersRequest = MessageInitShape<typeof ListUsersRequestSchema>;
type QueriesArray = NonNullable<ListUsersRequest['queries']>;
@@ -124,6 +127,8 @@ export class UserTableComponent implements OnInit {
private readonly route: ActivatedRoute,
private readonly destroyRef: DestroyRef,
private readonly newOrganizationService: NewOrganizationService,
private readonly authenticationService: AuthenticationService,
private readonly authService: GrpcAuthService,
) {
this.type$ = this.getType$().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
this.users$ = this.getUsers(this.type$).pipe(shareReplay({ refCount: true, bufferSize: 1 }));
@@ -229,7 +234,7 @@ export class UserTableComponent implements OnInit {
}
private getQueries(type$: Observable<Type>): Observable<Query[]> {
const orgId$ = toObservable(this.newOrganizationService.orgId).pipe(filter(Boolean));
const orgId$ = this.getActiveOrgId().pipe(filter(Boolean));
return this.searchQueries$.pipe(
startWith([]),
combineLatestWith(type$, orgId$),
@@ -474,4 +479,20 @@ export class UserTableComponent implements OnInit {
const selected = this.selection.selected;
return selected ? selected.findIndex((user) => user.state !== UserState.INACTIVE) > -1 : false;
}
private getActiveOrgId() {
return this.authenticationService.authenticationChanged.pipe(
startWith(true),
filter(Boolean),
switchMap(() =>
from(this.authService.getActiveOrg()).pipe(
catchError((err) => {
this.toast.showError(err);
return of(undefined);
}),
),
),
map((org) => org?.id),
);
}
}