diff --git a/console/src/app/app.component.ts b/console/src/app/app.component.ts index 91cd5321c..a0e815d0e 100644 --- a/console/src/app/app.component.ts +++ b/console/src/app/app.component.ts @@ -6,10 +6,10 @@ import { FormControl } from '@angular/forms'; import { MatIconRegistry } from '@angular/material/icon'; import { MatDrawer } from '@angular/material/sidenav'; import { DomSanitizer } from '@angular/platform-browser'; -import { Router, RouterOutlet } from '@angular/router'; +import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { LangChangeEvent, TranslateService } from '@ngx-translate/core'; -import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs'; -import { catchError, debounceTime, finalize, map, take } from 'rxjs/operators'; +import { BehaviorSubject, from, Observable, of, Subject } from 'rxjs'; +import { catchError, debounceTime, finalize, map, take, takeUntil } from 'rxjs/operators'; import { accountCard, adminLineAnimation, navAnimations, routeAnimations, toolbarAnimation } from './animations'; import { TextQueryMethod } from './proto/generated/zitadel/object_pb'; @@ -55,8 +55,7 @@ export class AppComponent implements OnDestroy { public showProjectSection: boolean = false; public filterControl: FormControl = new FormControl(''); - private authSub: Subscription = new Subscription(); - private orgSub: Subscription = new Subscription(); + private destroy$: Subject = new Subject(); public labelpolicy!: LabelPolicy.AsObject; public hideAdminWarn: boolean = true; @@ -76,6 +75,7 @@ export class AppComponent implements OnDestroy { public domSanitizer: DomSanitizer, private router: Router, update: UpdateService, + private activatedRoute: ActivatedRoute, @Inject(DOCUMENT) private document: Document, ) { console.log('%cWait!', 'text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black; color: #5469D4; font-size: 50px'); @@ -174,16 +174,27 @@ export class AppComponent implements OnDestroy { this.domSanitizer.bypassSecurityTrustResourceUrl('assets/mdi/api.svg'), ); + this.activatedRoute.queryParams + .pipe(takeUntil(this.destroy$)) + .subscribe(route => { + const { org } = route; + if (org) { + this.authService.getActiveOrg(org).then(queriedOrg => { + this.org = queriedOrg; + }); + } + }); + this.loadPrivateLabelling(); this.getProjectCount(); - this.orgSub = this.authService.activeOrgChanged.subscribe(org => { + this.authService.activeOrgChanged.pipe(takeUntil(this.destroy$)).subscribe(org => { this.org = org; this.getProjectCount(); }); - this.authSub = this.authenticationService.authenticationChanged.subscribe((authenticated) => { + this.authenticationService.authenticationChanged.pipe(takeUntil(this.destroy$)).subscribe((authenticated) => { if (authenticated) { this.authService.getActiveOrg().then(org => { this.org = org; @@ -217,8 +228,8 @@ export class AppComponent implements OnDestroy { } public ngOnDestroy(): void { - this.authSub.unsubscribe(); - this.orgSub.unsubscribe(); + this.destroy$.next(); + this.destroy$.complete(); } public toggleAdminHide(): void { diff --git a/console/src/app/modules/policies/message-texts/message-texts.component.html b/console/src/app/modules/policies/message-texts/message-texts.component.html index be77861f6..a0bc1b2e3 100644 --- a/console/src/app/modules/policies/message-texts/message-texts.component.html +++ b/console/src/app/modules/policies/message-texts/message-texts.component.html @@ -40,12 +40,5 @@
- - {{'FEATURES.NOTAVAILABLE' | translate: ({value: - 'login_policy.idp'})}} - - - diff --git a/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.html b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.html new file mode 100644 index 000000000..7b6ef6ffb --- /dev/null +++ b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.html @@ -0,0 +1,22 @@ +

{{'PROJECT.PAGES.PRIVATELABEL.DIALOG.TITLE' | translate}} {{data?.number}}

+

{{'PROJECT.PAGES.PRIVATELABEL.DIALOG.DESCRIPTION' | translate}}

+
+ + + {{'PROJECT.PAGES.PRIVATELABEL.'+selection+'.TITLE' | translate}} + + +{{'PROJECT.PAGES.PRIVATELABEL.'+setting+'.DESC' | translate}} +
+
+ + + +
\ No newline at end of file diff --git a/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.scss b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.scss new file mode 100644 index 000000000..689babd6c --- /dev/null +++ b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.scss @@ -0,0 +1,35 @@ +.title { + font-size: 1.2rem; + margin: 0; +} + +.desc { + color: var(--grey); + font-size: .9rem; +} + +.radio-button { + margin: .5rem 0; + + .label { + white-space: normal; + } +} + +.info { + margin: 1rem 0; + display: block; +} + +.action { + display: flex; + justify-content: flex-end; + + .ok-button { + margin-left: .5rem; + } + + button { + border-radius: .5rem; + } +} diff --git a/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.spec.ts b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.spec.ts new file mode 100644 index 000000000..a1377fcc0 --- /dev/null +++ b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { ProjectPrivateLabelingDialogComponent } from './project-private-labeling-dialog.component'; + +describe('ProjectPrivateLabelingDialogComponent', () => { + let component: ProjectPrivateLabelingDialogComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [ProjectPrivateLabelingDialogComponent], + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ProjectPrivateLabelingDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.ts b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.ts new file mode 100644 index 000000000..153c413aa --- /dev/null +++ b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component.ts @@ -0,0 +1,26 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { PrivateLabelingSetting } from 'src/app/proto/generated/zitadel/project_pb'; + +@Component({ + selector: 'app-project-private-labeling-dialog', + templateUrl: './project-private-labeling-dialog.component.html', + styleUrls: ['./project-private-labeling-dialog.component.scss'], +}) +export class ProjectPrivateLabelingDialogComponent { + public setting: PrivateLabelingSetting = PrivateLabelingSetting.PRIVATE_LABELING_SETTING_UNSPECIFIED; + public settings: PrivateLabelingSetting[] = [ + PrivateLabelingSetting.PRIVATE_LABELING_SETTING_UNSPECIFIED, + PrivateLabelingSetting.PRIVATE_LABELING_SETTING_ENFORCE_PROJECT_RESOURCE_OWNER_POLICY, + PrivateLabelingSetting.PRIVATE_LABELING_SETTING_ALLOW_LOGIN_USER_RESOURCE_OWNER_POLICY, + ]; + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any) { + this.setting = data.setting; + } + + closeDialog(setting?: PrivateLabelingSetting): void { + this.dialogRef.close(setting); + } + +} diff --git a/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.module.ts b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.module.ts new file mode 100644 index 000000000..ac91d5261 --- /dev/null +++ b/console/src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.module.ts @@ -0,0 +1,24 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatRadioModule } from '@angular/material/radio'; +import { TranslateModule } from '@ngx-translate/core'; + +import { InfoSectionModule } from '../info-section/info-section.module'; +import { ProjectPrivateLabelingDialogComponent } from './project-private-labeling-dialog.component'; + + + +@NgModule({ + declarations: [ProjectPrivateLabelingDialogComponent], + imports: [ + CommonModule, + TranslateModule, + MatButtonModule, + FormsModule, + MatRadioModule, + InfoSectionModule, + ], +}) +export class ProjectPrivateLabelingDialogModule { } diff --git a/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.html b/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.html index f11451155..985c9df29 100644 --- a/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.html +++ b/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.html @@ -34,7 +34,7 @@
{{'PROJECT.STATE.TITLE' | translate}}: {{'PROJECT.STATE.'+project.state | translate}} + [ngClass]="{'active': project.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE, 'inactive': project.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE}">{{'PROJECT.STATE.'+project.state | translate}}
diff --git a/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.ts b/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.ts index 57758307e..f2a1a0406 100644 --- a/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.ts +++ b/console/src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component.ts @@ -9,7 +9,7 @@ import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-m import { ChangeType } from 'src/app/modules/changes/changes.component'; import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource'; import { Member } from 'src/app/proto/generated/zitadel/member_pb'; -import { GrantedProject, ProjectState } from 'src/app/proto/generated/zitadel/project_pb'; +import { GrantedProject, ProjectGrantState } from 'src/app/proto/generated/zitadel/project_pb'; import { User } from 'src/app/proto/generated/zitadel/user_pb'; import { ManagementService } from 'src/app/services/mgmt.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -24,7 +24,7 @@ export class GrantedProjectDetailComponent implements OnInit, OnDestroy { public grantId: string = ''; public project!: GrantedProject.AsObject; - public ProjectState: any = ProjectState; + public ProjectGrantState: any = ProjectGrantState; public ChangeType: any = ChangeType; private subscription?: Subscription; diff --git a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html index 2a3e7f411..6f84b6b85 100644 --- a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html +++ b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.html @@ -53,6 +53,14 @@ +
+

{{'PROJECT.PAGES.PRIVATELABEL.TITLE' | translate}}

+

+ {{'PROJECT.PAGES.PRIVATELABEL.'+project.privateLabelingSetting+'.TITLE' | translate}} + +

+
+ diff --git a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.scss b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.scss index b20146e59..17db45719 100644 --- a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.scss +++ b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.scss @@ -1,7 +1,7 @@ .head { display: flex; align-items: center; - border-bottom: 1px solid #ffffff20; + border-bottom: 1px solid #81868a30; flex-wrap: wrap; margin-bottom: 1rem; @@ -41,6 +41,30 @@ } } +.privatelabel-info { + display: flex; + flex-direction: column; + border-bottom: 1px solid #81868a30; + padding: .5rem 0 1rem 0; + + .setting-title { + font-size: 14px; + color: var(--grey); + text-transform: uppercase; + margin: 0; + } + + .setting-desc { + margin: 0; + display: flex; + align-items: center; + + .icon-button { + margin-left: .5rem; + } + } +} + .line { .formfield { flex: 1; diff --git a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts index e2bf69d7d..aeb183382 100644 --- a/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts +++ b/console/src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component.ts @@ -8,12 +8,15 @@ import { BehaviorSubject, from, Observable, of, Subscription } from 'rxjs'; import { catchError, finalize, map } from 'rxjs/operators'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { ChangeType } from 'src/app/modules/changes/changes.component'; +import { + ProjectPrivateLabelingDialogComponent, +} from 'src/app/modules/project-private-labeling-dialog/project-private-labeling-dialog.component'; import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource'; import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component'; import { App } from 'src/app/proto/generated/zitadel/app_pb'; import { ListAppsResponse, UpdateProjectRequest } from 'src/app/proto/generated/zitadel/management_pb'; import { Member } from 'src/app/proto/generated/zitadel/member_pb'; -import { Project, ProjectState } from 'src/app/proto/generated/zitadel/project_pb'; +import { PrivateLabelingSetting, Project, ProjectState } from 'src/app/proto/generated/zitadel/project_pb'; import { User } from 'src/app/proto/generated/zitadel/user_pb'; import { ManagementService } from 'src/app/services/mgmt.service'; import { ToastService } from 'src/app/services/toast.service'; @@ -70,6 +73,22 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy { this.subscription?.unsubscribe(); } + public openPrivateLabelingDialog(): void { + const dialogRef = this.dialog.open(ProjectPrivateLabelingDialogComponent, { + data: { + setting: this.project.privateLabelingSetting, + }, + width: '400px', + }); + + dialogRef.afterClosed().subscribe((resp: PrivateLabelingSetting) => { + if (resp !== undefined) { + this.project.privateLabelingSetting = resp; + this.saveProject(); + } + }); + } + private async getData({ id }: Params): Promise { this.projectId = id; @@ -186,6 +205,7 @@ export class OwnedProjectDetailComponent implements OnInit, OnDestroy { req.setProjectRoleAssertion(this.project.projectRoleAssertion); req.setProjectRoleCheck(this.project.projectRoleCheck); req.setHasProjectCheck(this.project.hasProjectCheck); + req.setPrivateLabelingSetting(this.project.privateLabelingSetting); this.mgmtService.updateProject(req).then(() => { this.toast.showInfo('PROJECT.TOAST.UPDATED', true); diff --git a/console/src/app/pages/projects/owned-projects/owned-projects.module.ts b/console/src/app/pages/projects/owned-projects/owned-projects.module.ts index 311773706..19720c6e6 100644 --- a/console/src/app/pages/projects/owned-projects/owned-projects.module.ts +++ b/console/src/app/pages/projects/owned-projects/owned-projects.module.ts @@ -24,45 +24,49 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module'; import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module'; +import { + ProjectPrivateLabelingDialogModule, +} from '../../../modules/project-private-labeling-dialog/project-private-labeling-dialog.module'; import { OwnedProjectGridComponent } from './owned-project-list/owned-project-grid/owned-project-grid.component'; import { OwnedProjectListComponent } from './owned-project-list/owned-project-list.component'; import { OwnedProjectsRoutingModule } from './owned-projects-routing.module'; import { OwnedProjectsComponent } from './owned-projects.component'; @NgModule({ - declarations: [ - OwnedProjectsComponent, - OwnedProjectListComponent, - OwnedProjectGridComponent, - ], - imports: [ - CommonModule, - OwnedProjectsRoutingModule, - UserGrantsModule, - FormsModule, - ReactiveFormsModule, - TranslateModule, - AvatarModule, - ReactiveFormsModule, - HasRoleModule, - MatTableModule, - PaginatorModule, - InputModule, - MatChipsModule, - MatIconModule, - WarnDialogModule, - MatButtonModule, - MatProgressSpinnerModule, - MatProgressBarModule, - MatCheckboxModule, - CardModule, - MatTooltipModule, - MatSortModule, - HasRolePipeModule, - TimestampToDatePipeModule, - LocalizedDatePipeModule, - SharedModule, - RefreshTableModule, - ], + declarations: [ + OwnedProjectsComponent, + OwnedProjectListComponent, + OwnedProjectGridComponent, + ], + imports: [ + CommonModule, + OwnedProjectsRoutingModule, + UserGrantsModule, + FormsModule, + ReactiveFormsModule, + TranslateModule, + AvatarModule, + ReactiveFormsModule, + HasRoleModule, + MatTableModule, + PaginatorModule, + ProjectPrivateLabelingDialogModule, + InputModule, + MatChipsModule, + MatIconModule, + WarnDialogModule, + MatButtonModule, + MatProgressSpinnerModule, + MatProgressBarModule, + MatCheckboxModule, + CardModule, + MatTooltipModule, + MatSortModule, + HasRolePipeModule, + TimestampToDatePipeModule, + LocalizedDatePipeModule, + SharedModule, + RefreshTableModule, + ], }) export class OwnedProjectsModule { } diff --git a/console/src/app/services/grpc-auth.service.ts b/console/src/app/services/grpc-auth.service.ts index e96c2cba3..19e3e88a0 100644 --- a/console/src/app/services/grpc-auth.service.ts +++ b/console/src/app/services/grpc-auth.service.ts @@ -140,11 +140,22 @@ export class GrpcAuthService { public async getActiveOrg(id?: string): Promise { if (id) { - const org = this.storage.getItem(StorageKey.organization); - if (org && this.cachedOrgs.find(tmp => tmp.id === org.id)) { - return org; + const find = this.cachedOrgs.find(tmp => tmp.id === id); + if (find) { + this.setActiveOrg(find); + return Promise.resolve(find); + } else { + const orgs = (await this.listMyProjectOrgs(10, 0)).resultList; + this.cachedOrgs = orgs; + + const toFind = this.cachedOrgs.find(tmp => tmp.id === id); + if (toFind) { + this.setActiveOrg(toFind); + return Promise.resolve(toFind); + } else { + return Promise.reject(new Error('requested organization not found')); + } } - return Promise.reject(new Error('no cached org')); } else { let orgs = this.cachedOrgs; if (orgs.length === 0) { diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 02054a8d4..30b95e02d 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -910,6 +910,25 @@ "OWNED": "Eigene Projekte", "GRANTED": "Berechtigte Projekte" }, + "PRIVATELABEL": { + "TITLE":"Private Labeling Verhalten", + "0": { + "TITLE":"Unspezifiziert", + "DESC":"Sobald der Nutzer identifiziert ist, wird das Privatelabeling der von ihm gewählten Organisation angezeigt, davor wird der Default des Systems angezeigt." + }, + "1": { + "TITLE":"Durchsetzung der Richtlinie des Projekteigentümers", + "DESC":"Das Privatelabeling der Organisation, die Eigentümerin des Projekts ist, wird angezeigt" + }, + "2": { + "TITLE":"Durchsetzung der Richtlinie des Benutzereigentümers", + "DESC":"Das Privatelabeling der Organisation des Projekts wird angezeigt, sobald der Benutzer identifiziert ist, wird jedoch auf die Einstellung der Organisation des identifizierten Benutzers, gewechselt." + }, + "DIALOG":{ + "TITLE":"Privatelabeling Verhalten", + "DESCRIPTION":"Bestimmen Sie das Verhalten des Projektes im Bezug auf das Privatelabeling." + } + }, "PINNED": "Angepinnt", "ALL": "Alle", "CREATEDON": "Erstellt am", diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index 28056c087..79b25daab 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -912,6 +912,25 @@ "OWNED": "Owned Projects", "GRANTED": "Granted Projects" }, + "PRIVATELABEL": { + "TITLE":"Private Labeling Setting", + "0": { + "TITLE":"Unspecified", + "DESC":"As soon as the user is identified, the private labeling of the organisation of the identified user will be shown, before the system default is shown." + }, + "1": { + "TITLE":"Enforce project resource owner Policy", + "DESC":"The private labeling of the organisation which owns the project will be shown" + }, + "2": { + "TITLE":"Allow Login User resource owner policy", + "DESC":"The private labeling of the organization of the project will be shown, but as soon as the user is identified, the setting of the organization of the identified user, will be shown." + }, + "DIALOG":{ + "TITLE":"Privatelabeling Setting", + "DESCRIPTION":"Select the behaviour of the login, when using the project." + } + }, "PINNED": "Pinned", "ALL": "All", "CREATEDON": "Created on",