mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-12 00:55:12 +00:00
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:
co-authored by
conblem
Stefan Benz
Florian Forster
parent
cccba3f8f3
commit
f963ea9f86
@@ -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"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user