feat(console): project privatelabelling, catch query param to set org context (#2277)

* feat: privatelabeling setting, query param for context

* lint

* Update console/src/assets/i18n/de.json

Co-authored-by: Fabi <[email protected]>

* Update console/src/assets/i18n/de.json

Co-authored-by: Fabi <[email protected]>

* Update console/src/assets/i18n/en.json

Co-authored-by: Fabi <[email protected]>

* Update console/src/assets/i18n/en.json

Co-authored-by: Fabi <[email protected]>

Co-authored-by: Fabi <[email protected]>
This commit is contained in:
Max Peintner
2021-08-31 08:25:24 +02:00
committed by GitHub
co-authored by Fabi
parent e8da0e3f4f
commit c884a11f1b
16 changed files with 300 additions and 59 deletions
+20 -9
View File
@@ -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<void> = 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 {
@@ -40,12 +40,5 @@
<div class="divider"></div>
<ng-container
*ngIf="serviceType == PolicyComponentServiceType.MGMT && (['login_policy.idp'] | hasFeature | async) == false">
<cnsl-info-section type="WARN">{{'FEATURES.NOTAVAILABLE' | translate: ({value:
'login_policy.idp'})}}
</cnsl-info-section>
</ng-container>
<app-policy-grid [currentPolicy]="currentPolicy" [type]="serviceType" tagForFilter="text"></app-policy-grid>
</app-detail-layout>
@@ -0,0 +1,22 @@
<h1 class="title">{{'PROJECT.PAGES.PRIVATELABEL.DIALOG.TITLE' | translate}} {{data?.number}}</h1>
<p class="desc">{{'PROJECT.PAGES.PRIVATELABEL.DIALOG.DESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<mat-radio-group
class="example-radio-group"
[(ngModel)]="setting">
<mat-radio-button class="radio-button" *ngFor="let selection of settings" [value]="selection">
<span class="label">{{'PROJECT.PAGES.PRIVATELABEL.'+selection+'.TITLE' | translate}}</span>
</mat-radio-button>
</mat-radio-group>
<cnsl-info-section class="info">{{'PROJECT.PAGES.PRIVATELABEL.'+setting+'.DESC' | translate}}</cnsl-info-section>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial color="primary" mat-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
</button>
<button [disabled]="setting == undefined" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
(click)="closeDialog(setting)">
{{'ACTIONS.OK' | translate}}
</button>
</div>
@@ -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;
}
}
@@ -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<ProjectPrivateLabelingDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ProjectPrivateLabelingDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectPrivateLabelingDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -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<ProjectPrivateLabelingDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.setting = data.setting;
}
closeDialog(setting?: PrivateLabelingSetting): void {
this.dialogRef.close(setting);
}
}
@@ -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 { }
@@ -34,7 +34,7 @@
<div class="meta-row">
<span class="first">{{'PROJECT.STATE.TITLE' | translate}}:</span>
<span *ngIf="project && project.state !== undefined" class="state"
[ngClass]="{'active': project.state === ProjectState.PROJECTSTATE_ACTIVE, 'inactive': project.state === ProjectState.PROJECTSTATE_INACTIVE}">{{'PROJECT.STATE.'+project.state | translate}}</span>
[ngClass]="{'active': project.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE, 'inactive': project.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE}">{{'PROJECT.STATE.'+project.state | translate}}</span>
</div>
</div>
@@ -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;
@@ -53,6 +53,14 @@
</div>
<ng-container *ngIf="project">
<div class="privatelabel-info">
<h2 class="setting-title">{{'PROJECT.PAGES.PRIVATELABEL.TITLE' | translate}}</h2>
<p class="setting-desc">
<span>{{'PROJECT.PAGES.PRIVATELABEL.'+project.privateLabelingSetting+'.TITLE' | translate}}</span>
<button (click)="openPrivateLabelingDialog()" mat-icon-button><i class="las la-edit"></i></button>
</p>
</div>
<ng-template appHasRole [appHasRole]="['project.app.read:' + project.id, 'project.app.read']">
<app-application-grid *ngIf="grid" [disabled]="isZitadel" (changeView)="grid = false"
[projectId]="projectId"></app-application-grid>
@@ -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;
@@ -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<void> {
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);
@@ -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 { }
+15 -4
View File
@@ -140,11 +140,22 @@ export class GrpcAuthService {
public async getActiveOrg(id?: string): Promise<Org.AsObject> {
if (id) {
const org = this.storage.getItem<Org.AsObject>(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) {