diff --git a/console/src/app/app.component.ts b/console/src/app/app.component.ts index 91cd5321cc..a0e815d0e2 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 be77861f6e..a0bc1b2e3f 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 0000000000..7b6ef6ffb2 --- /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 0000000000..689babd6c1 --- /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 0000000000..a1377fcc0a --- /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 0000000000..153c413aa1 --- /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 0000000000..ac91d52619 --- /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 f114511559..985c9df291 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 57758307ec..f2a1a04061 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 2a3e7f4117..6f84b6b858 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 b20146e592..17db45719d 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 e2bf69d7de..aeb1833825 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 3117737060..19720c6e66 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 e96c2cba3c..19e3e88a0f 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 02054a8d4c..30b95e02d6 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 28056c0871..79b25daabf 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",