diff --git a/console/src/app/app.component.html b/console/src/app/app.component.html index a16f6326fa..5b089adbd8 100644 --- a/console/src/app/app.component.html +++ b/console/src/app/app.component.html @@ -20,40 +20,19 @@ - +
+ + + +
- -
- - -
- -
- -
- -
- -
- - - - - - -
{{'MENU.DOCUMENTATION' diff --git a/console/src/app/app.component.scss b/console/src/app/app.component.scss index ae26afb0be..3087245646 100644 --- a/console/src/app/app.component.scss +++ b/console/src/app/app.component.scss @@ -13,7 +13,7 @@ .org-button { font-weight: bold; - padding-right: .5rem; + padding-right: 0.5rem; } .logo { @@ -30,7 +30,7 @@ } .context-menu { - border-radius: .5rem; + border-radius: 0.5rem; 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 { display: flex; justify-content: space-between; @@ -73,7 +88,7 @@ top: 60px; right: 0; overflow: hidden; - border-radius: .5rem; + border-radius: 0.5rem; } } } @@ -117,18 +132,18 @@ text-decoration: none; cursor: pointer; padding: 0 1rem; - margin-right: .5rem; + margin-right: 0.5rem; border-top-right-radius: 1.5rem; border-bottom-right-radius: 1.5rem; .icon { - margin: .5rem 1rem; + margin: 0.5rem 1rem; } .iam-i { object-fit: contain; max-height: 24px; - margin: .5rem 1rem; + margin: 0.5rem 1rem; } .label { @@ -184,7 +199,7 @@ } .slash { - margin: 0 .5rem; + margin: 0 0.5rem; color: var(--grey); } } @@ -207,7 +222,7 @@ .theme-section { display: block; - padding: 0 .5rem; + padding: 0 0.5rem; margin-top: 2rem; align-self: flex-start; border-radius: 1rem; @@ -217,7 +232,7 @@ border-radius: 50%; height: 30px; width: 30px; - margin: .5rem; + margin: 0.5rem; cursor: pointer; background: linear-gradient(315deg, #e6e6e6, #fff); } @@ -227,7 +242,7 @@ border-radius: 50%; height: 30px; width: 30px; - margin: .5rem; + margin: 0.5rem; cursor: pointer; background: linear-gradient(315deg, #000, #000); } @@ -252,7 +267,7 @@ display: block; background-color: #81868a40; height: 1px; - margin: .5rem 0; + margin: 0.5rem 0; flex: 1; min-width: 10px; } @@ -266,7 +281,7 @@ @mixin textvar($theme) { .filter-form { - margin: 0 .5rem; + margin: 0 0.5rem; /* stylelint-disable */ $foreground: map-get($theme, foreground); color: mat.get-color-from-palette($foreground, text) !important; @@ -276,30 +291,7 @@ $primary: map-get($theme, primary); color: mat.get-color-from-palette($primary, 300) !important; border-bottom: 1px solid var(--grey); - margin-bottom: .5rem; + margin-bottom: 0.5rem; } /* 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; -} diff --git a/console/src/app/app.component.ts b/console/src/app/app.component.ts index 05934a25a0..467badae9a 100644 --- a/console/src/app/app.component.ts +++ b/console/src/app/app.component.ts @@ -1,19 +1,17 @@ import { BreakpointObserver } from '@angular/cdk/layout'; import { OverlayContainer } from '@angular/cdk/overlay'; import { DOCUMENT, ViewportScroller } from '@angular/common'; -import { Component, ElementRef, HostBinding, Inject, OnDestroy, ViewChild } from '@angular/core'; -import { FormControl } from '@angular/forms'; +import { Component, HostBinding, Inject, OnDestroy, ViewChild } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { MatDrawer } from '@angular/material/sidenav'; import { DomSanitizer } from '@angular/platform-browser'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; -import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs'; -import { catchError, debounceTime, finalize, map, take, takeUntil } from 'rxjs/operators'; +import { Observable, of, Subject } from 'rxjs'; +import { map, take, takeUntil } from 'rxjs/operators'; import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations'; -import { TextQueryMethod } from './proto/generated/zitadel/object_pb'; -import { Org, OrgNameQuery, OrgQuery } from './proto/generated/zitadel/org_pb'; +import { Org } from './proto/generated/zitadel/org_pb'; import { LabelPolicy, PrivacyPolicy } from './proto/generated/zitadel/policy_pb'; import { AuthenticationService } from './services/authentication.service'; import { GrpcAuthService } from './services/grpc-auth.service'; @@ -29,7 +27,6 @@ import { UpdateService } from './services/update.service'; }) export class AppComponent implements OnDestroy { @ViewChild('drawer') public drawer!: MatDrawer; - @ViewChild('input', { static: false }) input!: ElementRef; public isHandset$: Observable = this.breakpointObserver.observe('(max-width: 599px)').pipe( map((result) => { return result.matches; @@ -38,16 +35,13 @@ export class AppComponent implements OnDestroy { @HostBinding('class') public componentCssClass: string = 'dark-theme'; public showAccount: boolean = false; + public showOrgContext: boolean = false; public org!: Org.AsObject; - public orgs$: Observable = of([]); // public user!: User.AsObject; public isDarkTheme: Observable = of(true); - public orgLoading$: BehaviorSubject = new BehaviorSubject(false); - public showProjectSection: boolean = false; - public filterControl: FormControl = new FormControl(''); private destroy$: Subject = new Subject(); public labelpolicy!: LabelPolicy.AsObject; @@ -215,10 +209,6 @@ export class AppComponent implements OnDestroy { 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.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 { return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation; } @@ -350,6 +317,7 @@ export class AppComponent implements OnDestroy { } public setActiveOrg(org: Org.AsObject): void { + console.log(this.org); this.org = org; this.authService.setActiveOrg(org); this.loadPrivateLabelling(); @@ -366,10 +334,4 @@ export class AppComponent implements OnDestroy { } }); } - - focusFilter(): void { - setTimeout(() => { - this.input.nativeElement.focus(); - }, 0); - } } diff --git a/console/src/app/app.module.ts b/console/src/app/app.module.ts index 7c4f7127e3..7112bd4ef0 100644 --- a/console/src/app/app.module.ts +++ b/console/src/app/app.module.ts @@ -9,7 +9,6 @@ import { MatCardModule } from '@angular/material/card'; import { MatNativeDateModule } from '@angular/material/core'; import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; -import { MatMenuModule } from '@angular/material/menu'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 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 { AvatarModule } from './modules/avatar/avatar.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 { SignedoutComponent } from './pages/signedout/signedout.component'; import { HasFeaturePipeModule } from './pipes/has-feature-pipe/has-feature-pipe.module'; @@ -109,6 +109,7 @@ const authConfig: AuthConfig = { MatNativeDateModule, QuicklinkModule, AccountsCardModule, + OrgContextModule, HasRoleModule, BrowserAnimationsModule, HttpClientModule, @@ -126,7 +127,6 @@ const authConfig: AuthConfig = { MatProgressSpinnerModule, MatToolbarModule, ReactiveFormsModule, - MatMenuModule, MatSnackBarModule, AvatarModule, WarnDialogModule, diff --git a/console/src/app/modules/org-context/org-context.component.html b/console/src/app/modules/org-context/org-context.component.html new file mode 100644 index 0000000000..47339e8242 --- /dev/null +++ b/console/src/app/modules/org-context/org-context.component.html @@ -0,0 +1,28 @@ +
+
+ + +
+ +
+ +
+ +
+ +
+ + + + + + +
\ No newline at end of file diff --git a/console/src/app/modules/org-context/org-context.component.scss b/console/src/app/modules/org-context/org-context.component.scss new file mode 100644 index 0000000000..329ed7b834 --- /dev/null +++ b/console/src/app/modules/org-context/org-context.component.scss @@ -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; + } + } +} diff --git a/console/src/app/modules/org-context/org-context.component.spec.ts b/console/src/app/modules/org-context/org-context.component.spec.ts new file mode 100644 index 0000000000..6202bf2bba --- /dev/null +++ b/console/src/app/modules/org-context/org-context.component.spec.ts @@ -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; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [OrgContextComponent], + }).compileComponents(); + }), + ); + + beforeEach(() => { + fixture = TestBed.createComponent(OrgContextComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/org-context/org-context.component.ts b/console/src/app/modules/org-context/org-context.component.ts new file mode 100644 index 0000000000..b675e0d6c3 --- /dev/null +++ b/console/src/app/modules/org-context/org-context.component.ts @@ -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 = new BehaviorSubject(false); + public orgs$: Observable = of([]); + public filterControl: FormControl = new FormControl(''); + @Input() public org!: Org.AsObject; + @ViewChild('input', { static: false }) input!: ElementRef; + @Output() public closedCard: EventEmitter = new EventEmitter(); + @Output() public setOrg: EventEmitter = 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); + } +} diff --git a/console/src/app/modules/org-context/org-context.module.ts b/console/src/app/modules/org-context/org-context.module.ts new file mode 100644 index 0000000000..4860d9b7e7 --- /dev/null +++ b/console/src/app/modules/org-context/org-context.module.ts @@ -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 {} diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 16c54dfd51..a0ab857bc8 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -108,7 +108,7 @@ } }, "ACTIONS": { - "ACTIONS": "Aktionen", + "ACTIONS": "Actions", "RENAME": "Rename", "SET": "Set", "COPY": "Copy to Clipboard", @@ -1051,7 +1051,7 @@ } }, "STATE": { - "TITLE": "State", + "TITLE": "Status", "0": "Not defined", "1": "Active", "2": "Inactive" @@ -1108,7 +1108,7 @@ "GRANTEDORG": "Granted Organisation", "RESOURCEOWNER": "Resource Owner" }, - "STATE": "State", + "STATE": "Status", "STATES": { "1": "Active", "2": "Inactive" @@ -1247,7 +1247,7 @@ "ID": "ID", "NAME": "Name", "CONFIG": "Configuration", - "STATE": "State", + "STATE": "Status", "ISSUER": "Issuer", "SCOPESLIST": "Scopes List", "CLIENTID": "Client ID", @@ -1341,7 +1341,7 @@ "CREATE_OIDC": "OIDC Application", "CREATE_OIDC_DESC_TITLE": "Enter Your Application Details Step by Step", "CREATE_OIDC_DESC_SUB": "A recommended configuration will be automatically generated.", - "STATE": "State", + "STATE": "Status", "DATECREATED": "Created", "DATECHANGED": "Changed", "URLS": "Urls",