feat(console): rehauled navigation for instances including breadcrumb (#10283)

This PR significantly improves user navigation by introducing a new
instance-level navigation bar. This new bar, positioned above the
existing organization navigation, provides quick access to key sections:
Home, Organizations, Actions, and Settings.

Additionally, the breadcrumb component has been refined for more
consistent behavior, reintroducing intuitive breadcrumb buttons to
easily navigate up the hierarchy. These changes also include design
improvements for a cleaner and more streamlined appearance across the
interface.

<img width="423" height="138" alt="Screenshot 2025-07-17 at 14 55 46"
src="https://github.com/user-attachments/assets/ba9e40a1-1077-4cb6-8735-ac7ab637abe7"
/>
<img width="562" height="132" alt="Screenshot 2025-07-17 at 14 56 41"
src="https://github.com/user-attachments/assets/d85dc673-0df8-4677-9d2b-dc031dde42c3"
/>
<img width="545" height="254" alt="Screenshot 2025-07-17 at 14 56 10"
src="https://github.com/user-attachments/assets/eaf10117-079e-4181-8dbb-60c89b24556a"
/>
<img width="689" height="261" alt="Screenshot 2025-07-17 at 14 56 20"
src="https://github.com/user-attachments/assets/510ad550-1d9a-4c6a-8af1-66cb0b23619c"
/>

---------

Co-authored-by: conblem <[email protected]>
Co-authored-by: Stefan Benz <[email protected]>
Co-authored-by: Florian Forster <[email protected]>
This commit is contained in:
Max Peintner
2025-08-28 11:23:14 +02:00
committed by GitHub
co-authored by conblem Stefan Benz Florian Forster
parent cccba3f8f3
commit f963ea9f86
117 changed files with 3286 additions and 2360 deletions
+48 -14
View File
@@ -1,19 +1,16 @@
<ng-container *ngIf="['iam.read$', 'iam.write$'] | hasRole as iamuser$">
<div class="nav-col" [ngClass]="{ 'is-admin': (iamuser$ | async) }">
<ng-container
*ngIf="breadcrumbService.breadcrumbsExtended$ && (breadcrumbService.breadcrumbsExtended$ | async) as breadc"
>
<ng-container *ngIf="breadcrumbService.breadcrumbsExtended$ | async as breadc">
<ng-container
*ngIf="
breadc[breadc.length - 1] &&
!breadc[breadc.length - 1].hideNav &&
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER &&
breadc[breadc.length - 1].type !== BreadcrumbType.INSTANCE
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER
"
[ngSwitch]="breadc[0].type"
>
<div class="nav-row" @navrow>
<ng-container *ngSwitchCase="BreadcrumbType.ORG">
<ng-container *ngSwitchCase="BreadcrumbType.INSTANCE">
<div class="nav-row-abs" @navrowproject>
<a
class="nav-item"
@@ -24,6 +21,48 @@
<span class="label">{{ 'MENU.DASHBOARD' | translate }}</span>
</a>
<ng-container class="org-list" *ngIf="org">
<ng-template cnslHasRole [hasRole]="['org.read']">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="['/orgs']"
>
<span class="label">{{ 'MENU.ORGS' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['org.action.read']">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLink]="['/actions']"
[routerLinkActiveOptions]="{ exact: false }"
>
<span class="label">{{ 'MENU.ACTIONS' | translate }}</span>
</a>
</ng-template>
<ng-template cnslHasRole [hasRole]="['org.read']">
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="['/instance']"
*ngIf="['iam.policy.read'] | hasRole | async"
>
<span class="label">{{ 'MENU.SETTINGS' | translate }}</span>
</a>
</ng-template>
</ng-container>
<template [ngTemplateOutlet]="shortcutKeyRef"></template>
</div>
</ng-container>
<ng-container *ngSwitchCase="BreadcrumbType.ORG">
<div class="nav-row-abs" @navrowproject>
<ng-container class="org-list" *ngIf="org">
<ng-template cnslHasRole [hasRole]="['org.read']">
<a
@@ -83,7 +122,7 @@
<a
class="nav-item"
[routerLinkActive]="['active']"
[routerLink]="['/actions']"
[routerLink]="['/actions-v1']"
[routerLinkActiveOptions]="{ exact: true }"
>
<span class="label">{{ 'MENU.ACTIONS' | translate }}</span>
@@ -96,12 +135,7 @@
[routerLinkActive]="['active']"
[routerLinkActiveOptions]="{ exact: false }"
[routerLink]="['/org-settings']"
*ngIf="
(['policy.read'] | hasRole | async) &&
((['iam.read$', 'iam.write$'] | hasRole | async) === false ||
(((authService.cachedOrgs | async)?.length ?? 1) > 1 &&
(['iam.read$', 'iam.write$'] | hasRole | async)))
"
*ngIf="['policy.read'] | hasRole | async"
>
<span class="label">{{ 'MENU.SETTINGS' | translate }}</span>
</a>
@@ -120,7 +154,6 @@
<ng-template #shortcutKeyRef>
<ng-container *ngIf="(isHandset$ | async) === false">
<ng-template cnslHasRole [hasRole]="['iam.read']">
<span class="fill-space"></span>
<ng-container *ngIf="!adminService.hideOnboarding && (adminService.progressAllDone | async) === false">
<div
cdkOverlayOrigin
@@ -160,6 +193,7 @@
</ng-container>
</ng-template>
<span class="fill-space"></span>
<div
(click)="openHelp()"
class="nav-shortcut-action-key"
+39 -13
View File
@@ -23,7 +23,8 @@
.nav-row {
position: relative;
width: 100%;
height: 36px;
height: 40px; // Increased height to accommodate the line
overflow: visible; // Allow the indicator line to show below
.nav-row-abs {
padding: 0 2rem;
@@ -32,7 +33,7 @@
display: flex;
align-items: center;
overflow-x: auto;
overflow-y: hidden;
overflow-y: hidden; // Allow the indicator line to show
align-self: stretch;
position: absolute;
top: 0;
@@ -51,19 +52,45 @@
.nav-item {
display: flex;
align-items: center;
font-size: 14px;
line-height: 14px;
padding: 0.4rem 12px;
color: map-get($foreground, text);
transition: all 0.2s ease;
text-decoration: none;
border-radius: 50vw;
font-weight: 500;
margin: 0.25rem 2px;
font-size: 14px;
white-space: nowrap;
position: relative;
height: 36px;
box-sizing: border-box;
height: 27px;
display: flex;
align-items: center;
border: 0;
background: transparent;
border-radius: 6px;
padding: 0 0.5rem;
cursor: pointer;
color: map-get($foreground, text);
opacity: 0.8;
&::after {
content: '';
position: absolute;
bottom: -2px;
left: 0;
right: 0;
width: 0;
height: 2px;
background-color: map-get($foreground, text);
// transition: width 0.2s ease;
border-radius: 2px;
z-index: 999;
}
&:hover {
opacity: 1;
color: map-get($foreground, text);
background-color: if($is-dark-theme, #ffffff10, #00000010);
}
.c_label {
display: flex;
@@ -97,13 +124,12 @@
}
}
&:hover {
background: if($is-dark-theme, #ffffff40, #00000010);
}
&.active {
background-color: $primary-color;
color: map-get($primary, default-contrast);
opacity: 1;
&::after {
width: 100%;
}
.c_label {
.count {
+2 -3
View File
@@ -5,16 +5,15 @@ import { Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/cor
import { UntypedFormControl } from '@angular/forms';
import { Router } from '@angular/router';
import { BehaviorSubject, combineLatest, map, Observable, Subject } from 'rxjs';
import { Org } from 'src/app/proto/generated/zitadel/org_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { AdminService } from 'src/app/services/admin.service';
import { AuthenticationService } from 'src/app/services/authentication.service';
import { BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { EnvironmentService } from 'src/app/services/environment.service';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
import { KeyboardShortcutsService } from 'src/app/services/keyboard-shortcuts/keyboard-shortcuts.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { StorageLocation, StorageService } from 'src/app/services/storage.service';
import { Organization } from '@zitadel/proto/zitadel/org/v2/org_pb';
@Component({
selector: 'cnsl-nav',
@@ -83,7 +82,7 @@ export class NavComponent implements OnDestroy {
}),
);
@Input() public org!: Org.AsObject;
@Input() public org?: Organization | null;
public filterControl: UntypedFormControl = new UntypedFormControl('');
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
public showAccount: boolean = false;