mirror of
https://github.com/zitadel/zitadel.git
synced 2025-01-07 22:07:40 +00:00
fix: new context component (#2823)
This commit is contained in:
parent
2f7d8ca557
commit
d1cb7fdc9f
@ -20,40 +20,19 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<button class="org-button" (click)="loadOrgs()" *ngIf="user && org" mat-button [matMenuTriggerFor]="menu"
|
<div class="org-context-wrapper" (clickOutside)="showOrgContext ? showOrgContext = false: null">
|
||||||
(menuOpened)="focusFilter()">{{org?.name ? org.name : 'NO NAME'}}
|
<button class="org-button dontcloseonclick" (click)="showOrgContext = !showOrgContext" *ngIf="user && org"
|
||||||
<mat-icon>
|
mat-button>{{org?.name
|
||||||
arrow_drop_down</mat-icon>
|
?
|
||||||
</button>
|
org.name : 'NO NAME'}}
|
||||||
|
<mat-icon class="dontcloseonclick">
|
||||||
|
arrow_drop_down</mat-icon>
|
||||||
|
</button>
|
||||||
|
<cnsl-org-context class="context_card" *ngIf="showOrgContext" (closedCard)="showOrgContext = false" [org]="org"
|
||||||
|
(setOrg)="setActiveOrg($event)">
|
||||||
|
</cnsl-org-context>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-menu class="menu" #menu="matMenu">
|
|
||||||
<div class="spinner-w">
|
|
||||||
<mat-spinner diameter="20" *ngIf="orgLoading$ | async" color="accent">
|
|
||||||
</mat-spinner>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="filter-wrapper">
|
|
||||||
<input cnslInput class="filter-input" [formControl]="filterControl" autocomplete="off"
|
|
||||||
(click)="$event.stopPropagation()" placeholder="{{'ORG.PAGES.FILTERPLACEHOLDER' | translate}}" #input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="org-wrapper">
|
|
||||||
<button [ngClass]="{'active': temporg.id === org?.id}" [disabled]="!temporg.id"
|
|
||||||
*ngFor="let temporg of orgs$ | async" mat-menu-item (click)="setActiveOrg(temporg)">
|
|
||||||
{{temporg?.name ? temporg.name : 'NO NAME'}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="show-all" mat-menu-item [routerLink]="[ '/org/overview' ]">{{'MENU.SHOWORGS' |
|
|
||||||
translate}}</button>
|
|
||||||
|
|
||||||
<ng-template cnslHasRole [hasRole]="['org.create','iam.write']">
|
|
||||||
<button mat-menu-item [routerLink]="[ '/org/create' ]">
|
|
||||||
<mat-icon class="avatar">add</mat-icon>
|
|
||||||
{{'MENU.NEWORG' | translate}}
|
|
||||||
</button>
|
|
||||||
</ng-template>
|
|
||||||
</mat-menu>
|
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
|
|
||||||
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">{{'MENU.DOCUMENTATION'
|
<a class="doc-link" href="https://docs.zitadel.ch" mat-stroked-button target="_blank">{{'MENU.DOCUMENTATION'
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
.org-button {
|
.org-button {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding-right: .5rem;
|
padding-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.context-menu {
|
.context-menu {
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
background-color: #2d2e30;
|
background-color: #2d2e30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,6 +46,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.org-context-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
.context_card {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
left: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.icon-container {
|
.icon-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -73,7 +88,7 @@
|
|||||||
top: 60px;
|
top: 60px;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: .5rem;
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,18 +132,18 @@
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
margin-right: .5rem;
|
margin-right: 0.5rem;
|
||||||
border-top-right-radius: 1.5rem;
|
border-top-right-radius: 1.5rem;
|
||||||
border-bottom-right-radius: 1.5rem;
|
border-bottom-right-radius: 1.5rem;
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
margin: .5rem 1rem;
|
margin: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.iam-i {
|
.iam-i {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
max-height: 24px;
|
max-height: 24px;
|
||||||
margin: .5rem 1rem;
|
margin: 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
@ -184,7 +199,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.slash {
|
.slash {
|
||||||
margin: 0 .5rem;
|
margin: 0 0.5rem;
|
||||||
color: var(--grey);
|
color: var(--grey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +222,7 @@
|
|||||||
|
|
||||||
.theme-section {
|
.theme-section {
|
||||||
display: block;
|
display: block;
|
||||||
padding: 0 .5rem;
|
padding: 0 0.5rem;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
@ -217,7 +232,7 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
margin: .5rem;
|
margin: 0.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: linear-gradient(315deg, #e6e6e6, #fff);
|
background: linear-gradient(315deg, #e6e6e6, #fff);
|
||||||
}
|
}
|
||||||
@ -227,7 +242,7 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
width: 30px;
|
width: 30px;
|
||||||
margin: .5rem;
|
margin: 0.5rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background: linear-gradient(315deg, #000, #000);
|
background: linear-gradient(315deg, #000, #000);
|
||||||
}
|
}
|
||||||
@ -252,7 +267,7 @@
|
|||||||
display: block;
|
display: block;
|
||||||
background-color: #81868a40;
|
background-color: #81868a40;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin: .5rem 0;
|
margin: 0.5rem 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 10px;
|
min-width: 10px;
|
||||||
}
|
}
|
||||||
@ -266,7 +281,7 @@
|
|||||||
|
|
||||||
@mixin textvar($theme) {
|
@mixin textvar($theme) {
|
||||||
.filter-form {
|
.filter-form {
|
||||||
margin: 0 .5rem;
|
margin: 0 0.5rem;
|
||||||
/* stylelint-disable */
|
/* stylelint-disable */
|
||||||
$foreground: map-get($theme, foreground);
|
$foreground: map-get($theme, foreground);
|
||||||
color: mat.get-color-from-palette($foreground, text) !important;
|
color: mat.get-color-from-palette($foreground, text) !important;
|
||||||
@ -276,30 +291,7 @@
|
|||||||
$primary: map-get($theme, primary);
|
$primary: map-get($theme, primary);
|
||||||
color: mat.get-color-from-palette($primary, 300) !important;
|
color: mat.get-color-from-palette($primary, 300) !important;
|
||||||
border-bottom: 1px solid var(--grey);
|
border-bottom: 1px solid var(--grey);
|
||||||
margin-bottom: .5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
/* stylelint-enable */
|
/* stylelint-enable */
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.filter-wrapper {
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.spinner-w {
|
|
||||||
top: 1rem;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.org-wrapper {
|
|
||||||
max-height: 350px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
import { BreakpointObserver } from '@angular/cdk/layout';
|
import { BreakpointObserver } from '@angular/cdk/layout';
|
||||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||||
import { DOCUMENT, ViewportScroller } from '@angular/common';
|
import { DOCUMENT, ViewportScroller } from '@angular/common';
|
||||||
import { Component, ElementRef, HostBinding, Inject, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, HostBinding, Inject, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { FormControl } from '@angular/forms';
|
|
||||||
import { MatIconRegistry } from '@angular/material/icon';
|
import { MatIconRegistry } from '@angular/material/icon';
|
||||||
import { MatDrawer } from '@angular/material/sidenav';
|
import { MatDrawer } from '@angular/material/sidenav';
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||||
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
import { LangChangeEvent, TranslateService } from '@ngx-translate/core';
|
||||||
import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs';
|
import { Observable, of, Subject } from 'rxjs';
|
||||||
import { catchError, debounceTime, finalize, map, take, takeUntil } from 'rxjs/operators';
|
import { map, take, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations';
|
import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations';
|
||||||
import { TextQueryMethod } from './proto/generated/zitadel/object_pb';
|
import { Org } from './proto/generated/zitadel/org_pb';
|
||||||
import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb';
|
|
||||||
import { LabelPolicy, PrivacyPolicy } from './proto/generated/zitadel/policy_pb';
|
import { LabelPolicy, PrivacyPolicy } from './proto/generated/zitadel/policy_pb';
|
||||||
import { AuthenticationService } from './services/authentication.service';
|
import { AuthenticationService } from './services/authentication.service';
|
||||||
import { GrpcAuthService } from './services/grpc-auth.service';
|
import { GrpcAuthService } from './services/grpc-auth.service';
|
||||||
@ -29,7 +27,6 @@ import { UpdateService } from './services/update.service';
|
|||||||
})
|
})
|
||||||
export class AppComponent implements OnDestroy {
|
export class AppComponent implements OnDestroy {
|
||||||
@ViewChild('drawer') public drawer!: MatDrawer;
|
@ViewChild('drawer') public drawer!: MatDrawer;
|
||||||
@ViewChild('input', { static: false }) input!: ElementRef;
|
|
||||||
public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
|
public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
|
||||||
map((result) => {
|
map((result) => {
|
||||||
return result.matches;
|
return result.matches;
|
||||||
@ -38,16 +35,13 @@ export class AppComponent implements OnDestroy {
|
|||||||
@HostBinding('class') public componentCssClass: string = 'dark-theme';
|
@HostBinding('class') public componentCssClass: string = 'dark-theme';
|
||||||
|
|
||||||
public showAccount: boolean = false;
|
public showAccount: boolean = false;
|
||||||
|
public showOrgContext: boolean = false;
|
||||||
public org!: Org.AsObject;
|
public org!: Org.AsObject;
|
||||||
public orgs$: Observable<Org.AsObject[]> = of([]);
|
|
||||||
// public user!: User.AsObject;
|
// public user!: User.AsObject;
|
||||||
public isDarkTheme: Observable<boolean> = of(true);
|
public isDarkTheme: Observable<boolean> = of(true);
|
||||||
|
|
||||||
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
|
||||||
|
|
||||||
public showProjectSection: boolean = false;
|
public showProjectSection: boolean = false;
|
||||||
|
|
||||||
public filterControl: FormControl = new FormControl('');
|
|
||||||
private destroy$: Subject<void> = new Subject();
|
private destroy$: Subject<void> = new Subject();
|
||||||
public labelpolicy!: LabelPolicy.AsObject;
|
public labelpolicy!: LabelPolicy.AsObject;
|
||||||
|
|
||||||
@ -215,10 +209,6 @@ export class AppComponent implements OnDestroy {
|
|||||||
this.language = language.lang;
|
this.language = language.lang;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filterControl.valueChanges.pipe(debounceTime(300)).subscribe((value) => {
|
|
||||||
this.loadOrgs(value.trim().toLowerCase());
|
|
||||||
});
|
|
||||||
|
|
||||||
this.hideAdminWarn = localStorage.getItem('hideAdministratorWarning') === 'true' ? true : false;
|
this.hideAdminWarn = localStorage.getItem('hideAdministratorWarning') === 'true' ? true : false;
|
||||||
|
|
||||||
this.loadPolicies();
|
this.loadPolicies();
|
||||||
@ -290,29 +280,6 @@ export class AppComponent implements OnDestroy {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public loadOrgs(filter?: string): void {
|
|
||||||
let query;
|
|
||||||
if (filter) {
|
|
||||||
query = new OrgQuery();
|
|
||||||
const orgNameQuery = new OrgNameQuery();
|
|
||||||
orgNameQuery.setName(filter);
|
|
||||||
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
|
||||||
query.setNameQuery(orgNameQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.orgLoading$.next(true);
|
|
||||||
this.orgs$ = from(this.authService.listMyProjectOrgs(10, 0, query ? [query] : undefined)).pipe(
|
|
||||||
map((resp) => {
|
|
||||||
return resp.resultList.sort((left, right) => left.name.localeCompare(right.name));
|
|
||||||
}),
|
|
||||||
catchError(() => of([])),
|
|
||||||
finalize(() => {
|
|
||||||
this.orgLoading$.next(false);
|
|
||||||
this.focusFilter();
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public prepareRoute(outlet: RouterOutlet): boolean {
|
public prepareRoute(outlet: RouterOutlet): boolean {
|
||||||
return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
|
return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
|
||||||
}
|
}
|
||||||
@ -350,6 +317,7 @@ export class AppComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setActiveOrg(org: Org.AsObject): void {
|
public setActiveOrg(org: Org.AsObject): void {
|
||||||
|
console.log(this.org);
|
||||||
this.org = org;
|
this.org = org;
|
||||||
this.authService.setActiveOrg(org);
|
this.authService.setActiveOrg(org);
|
||||||
this.loadPrivateLabelling();
|
this.loadPrivateLabelling();
|
||||||
@ -366,10 +334,4 @@ export class AppComponent implements OnDestroy {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
focusFilter(): void {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.input.nativeElement.focus();
|
|
||||||
}, 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import { MatCardModule } from '@angular/material/card';
|
|||||||
import { MatNativeDateModule } from '@angular/material/core';
|
import { MatNativeDateModule } from '@angular/material/core';
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatMenuModule } from '@angular/material/menu';
|
|
||||||
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
import { MatProgressBarModule } from '@angular/material/progress-bar';
|
||||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
@ -36,6 +35,7 @@ import { OutsideClickModule } from './directives/outside-click/outside-click.mod
|
|||||||
import { AccountsCardModule } from './modules/accounts-card/accounts-card.module';
|
import { AccountsCardModule } from './modules/accounts-card/accounts-card.module';
|
||||||
import { AvatarModule } from './modules/avatar/avatar.module';
|
import { AvatarModule } from './modules/avatar/avatar.module';
|
||||||
import { InputModule } from './modules/input/input.module';
|
import { InputModule } from './modules/input/input.module';
|
||||||
|
import { OrgContextModule } from './modules/org-context/org-context.module';
|
||||||
import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module';
|
import { WarnDialogModule } from './modules/warn-dialog/warn-dialog.module';
|
||||||
import { SignedoutComponent } from './pages/signedout/signedout.component';
|
import { SignedoutComponent } from './pages/signedout/signedout.component';
|
||||||
import { HasFeaturePipeModule } from './pipes/has-feature-pipe/has-feature-pipe.module';
|
import { HasFeaturePipeModule } from './pipes/has-feature-pipe/has-feature-pipe.module';
|
||||||
@ -109,6 +109,7 @@ const authConfig: AuthConfig = {
|
|||||||
MatNativeDateModule,
|
MatNativeDateModule,
|
||||||
QuicklinkModule,
|
QuicklinkModule,
|
||||||
AccountsCardModule,
|
AccountsCardModule,
|
||||||
|
OrgContextModule,
|
||||||
HasRoleModule,
|
HasRoleModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
@ -126,7 +127,6 @@ const authConfig: AuthConfig = {
|
|||||||
MatProgressSpinnerModule,
|
MatProgressSpinnerModule,
|
||||||
MatToolbarModule,
|
MatToolbarModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatMenuModule,
|
|
||||||
MatSnackBarModule,
|
MatSnackBarModule,
|
||||||
AvatarModule,
|
AvatarModule,
|
||||||
WarnDialogModule,
|
WarnDialogModule,
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<div class="card" cnslOutsideClick (clickOutside)="closeCard($event)">
|
||||||
|
<div class="spinner-w">
|
||||||
|
<mat-spinner diameter="20" *ngIf="orgLoading$ | async" color="accent">
|
||||||
|
</mat-spinner>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-wrapper">
|
||||||
|
<input cnslInput class="filter-input" [formControl]="filterControl" autocomplete="off"
|
||||||
|
(click)="$event.stopPropagation()" placeholder="{{'ORG.PAGES.FILTERPLACEHOLDER' | translate}}" #input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="org-wrapper">
|
||||||
|
<button mat-button [ngClass]="{'active': temporg.id === org?.id}" [disabled]="!temporg.id"
|
||||||
|
*ngFor="let temporg of orgs$ | async" (click)="setActiveOrg(temporg)">
|
||||||
|
{{temporg?.name ? temporg.name : 'NO NAME'}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button mat-button class="show-all" [routerLink]="[ '/org/overview' ]">{{'MENU.SHOWORGS' |
|
||||||
|
translate}}</button>
|
||||||
|
|
||||||
|
<ng-template cnslHasRole [hasRole]="['org.create','iam.write']">
|
||||||
|
<button mat-button [routerLink]="[ '/org/create' ]">
|
||||||
|
<mat-icon class="avatar">add</mat-icon>
|
||||||
|
{{'MENU.NEWORG' | translate}}
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
@ -0,0 +1,43 @@
|
|||||||
|
.card {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
z-index: 200;
|
||||||
|
border: 1px solid #ffffff30;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0;
|
||||||
|
min-width: 220px;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.filter-wrapper {
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-w {
|
||||||
|
top: 1rem;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-all {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.org-wrapper {
|
||||||
|
max-height: 350px;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
button {
|
||||||
|
text-align: start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { OrgContextComponent } from './org-context.component';
|
||||||
|
|
||||||
|
describe('OrgContextComponent', () => {
|
||||||
|
let component: OrgContextComponent;
|
||||||
|
let fixture: ComponentFixture<OrgContextComponent>;
|
||||||
|
|
||||||
|
beforeEach(
|
||||||
|
waitForAsync(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [OrgContextComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(OrgContextComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
79
console/src/app/modules/org-context/org-context.component.ts
Normal file
79
console/src/app/modules/org-context/org-context.component.ts
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
|
import { FormControl } from '@angular/forms';
|
||||||
|
import { BehaviorSubject, catchError, debounceTime, finalize, from, map, Observable, of } from 'rxjs';
|
||||||
|
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
|
||||||
|
import { Org, OrgNameQuery, OrgQuery } from 'src/app/proto/generated/zitadel/org_pb';
|
||||||
|
import { AuthenticationService } from 'src/app/services/authentication.service';
|
||||||
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-org-context',
|
||||||
|
templateUrl: './org-context.component.html',
|
||||||
|
styleUrls: ['./org-context.component.scss'],
|
||||||
|
})
|
||||||
|
export class OrgContextComponent implements OnInit {
|
||||||
|
public orgLoading$: BehaviorSubject<any> = new BehaviorSubject(false);
|
||||||
|
public orgs$: Observable<Org.AsObject[]> = of([]);
|
||||||
|
public filterControl: FormControl = new FormControl('');
|
||||||
|
@Input() public org!: Org.AsObject;
|
||||||
|
@ViewChild('input', { static: false }) input!: ElementRef;
|
||||||
|
@Output() public closedCard: EventEmitter<void> = new EventEmitter();
|
||||||
|
@Output() public setOrg: EventEmitter<Org.AsObject> = new EventEmitter();
|
||||||
|
|
||||||
|
constructor(public authService: AuthenticationService, private auth: GrpcAuthService) {
|
||||||
|
this.filterControl.valueChanges.pipe(debounceTime(500)).subscribe((value) => {
|
||||||
|
this.loadOrgs(value.trim().toLowerCase());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.focusFilter();
|
||||||
|
this.loadOrgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setActiveOrg(org: Org.AsObject) {
|
||||||
|
this.setOrg.emit(org);
|
||||||
|
this.closedCard.emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public loadOrgs(filter?: string): void {
|
||||||
|
if (!filter) {
|
||||||
|
const value = this.input?.nativeElement?.value;
|
||||||
|
if (value) {
|
||||||
|
filter = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let query;
|
||||||
|
if (filter) {
|
||||||
|
query = new OrgQuery();
|
||||||
|
const orgNameQuery = new OrgNameQuery();
|
||||||
|
orgNameQuery.setName(filter);
|
||||||
|
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||||
|
query.setNameQuery(orgNameQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.orgLoading$.next(true);
|
||||||
|
this.orgs$ = from(this.auth.listMyProjectOrgs(10, 0, query ? [query] : undefined)).pipe(
|
||||||
|
map((resp) => {
|
||||||
|
return resp.resultList.sort((left, right) => left.name.localeCompare(right.name));
|
||||||
|
}),
|
||||||
|
catchError(() => of([])),
|
||||||
|
finalize(() => {
|
||||||
|
this.orgLoading$.next(false);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public closeCard(element: HTMLElement): void {
|
||||||
|
if (!element.classList.contains('dontcloseonclick') && !element.classList.contains('mat-button-wrapper')) {
|
||||||
|
this.closedCard.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private focusFilter(): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.input.nativeElement.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
33
console/src/app/modules/org-context/org-context.module.ts
Normal file
33
console/src/app/modules/org-context/org-context.module.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
||||||
|
import { OutsideClickModule } from 'src/app/directives/outside-click/outside-click.module';
|
||||||
|
|
||||||
|
import { InputModule } from '../input/input.module';
|
||||||
|
import { OrgContextComponent } from './org-context.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [OrgContextComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatIconModule,
|
||||||
|
RouterModule,
|
||||||
|
MatProgressSpinnerModule,
|
||||||
|
MatButtonModule,
|
||||||
|
InputModule,
|
||||||
|
OutsideClickModule,
|
||||||
|
TranslateModule,
|
||||||
|
MatButtonModule,
|
||||||
|
HasRoleModule,
|
||||||
|
],
|
||||||
|
exports: [OrgContextComponent],
|
||||||
|
})
|
||||||
|
export class OrgContextModule {}
|
@ -108,7 +108,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ACTIONS": {
|
"ACTIONS": {
|
||||||
"ACTIONS": "Aktionen",
|
"ACTIONS": "Actions",
|
||||||
"RENAME": "Rename",
|
"RENAME": "Rename",
|
||||||
"SET": "Set",
|
"SET": "Set",
|
||||||
"COPY": "Copy to Clipboard",
|
"COPY": "Copy to Clipboard",
|
||||||
@ -1051,7 +1051,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"STATE": {
|
"STATE": {
|
||||||
"TITLE": "State",
|
"TITLE": "Status",
|
||||||
"0": "Not defined",
|
"0": "Not defined",
|
||||||
"1": "Active",
|
"1": "Active",
|
||||||
"2": "Inactive"
|
"2": "Inactive"
|
||||||
@ -1108,7 +1108,7 @@
|
|||||||
"GRANTEDORG": "Granted Organisation",
|
"GRANTEDORG": "Granted Organisation",
|
||||||
"RESOURCEOWNER": "Resource Owner"
|
"RESOURCEOWNER": "Resource Owner"
|
||||||
},
|
},
|
||||||
"STATE": "State",
|
"STATE": "Status",
|
||||||
"STATES": {
|
"STATES": {
|
||||||
"1": "Active",
|
"1": "Active",
|
||||||
"2": "Inactive"
|
"2": "Inactive"
|
||||||
@ -1247,7 +1247,7 @@
|
|||||||
"ID": "ID",
|
"ID": "ID",
|
||||||
"NAME": "Name",
|
"NAME": "Name",
|
||||||
"CONFIG": "Configuration",
|
"CONFIG": "Configuration",
|
||||||
"STATE": "State",
|
"STATE": "Status",
|
||||||
"ISSUER": "Issuer",
|
"ISSUER": "Issuer",
|
||||||
"SCOPESLIST": "Scopes List",
|
"SCOPESLIST": "Scopes List",
|
||||||
"CLIENTID": "Client ID",
|
"CLIENTID": "Client ID",
|
||||||
@ -1341,7 +1341,7 @@
|
|||||||
"CREATE_OIDC": "OIDC Application",
|
"CREATE_OIDC": "OIDC Application",
|
||||||
"CREATE_OIDC_DESC_TITLE": "Enter Your Application Details Step by Step",
|
"CREATE_OIDC_DESC_TITLE": "Enter Your Application Details Step by Step",
|
||||||
"CREATE_OIDC_DESC_SUB": "A recommended configuration will be automatically generated.",
|
"CREATE_OIDC_DESC_SUB": "A recommended configuration will be automatically generated.",
|
||||||
"STATE": "State",
|
"STATE": "Status",
|
||||||
"DATECREATED": "Created",
|
"DATECREATED": "Created",
|
||||||
"DATECHANGED": "Changed",
|
"DATECHANGED": "Changed",
|
||||||
"URLS": "Urls",
|
"URLS": "Urls",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user