fix(console): text color shades, ui fixes, state handle restore (#3698)

* common project grant dialog, info box, label policy

* text styles, statehandler fix

* dialog, btn alignment, i18n

* search-user theme colors

* filter formfield sizing

* redirect uris

* shortcut layout

* settings grid type rest, shortcuts linking

* login policy, reset button consistency, metadata

* permission checks
This commit is contained in:
Max Peintner
2022-05-25 09:33:18 +02:00
committed by GitHub
parent 09b021b257
commit b6deed3e34
90 changed files with 1055 additions and 883 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ const routes: Routes = [
loadChildren: () => import('./pages/actions/actions.module').then((m) => m.ActionsModule),
canActivate: [AuthGuard, RoleGuard],
data: {
roles: ['org.read'],
roles: ['org.action.read', 'org.flow.read'],
},
},
{
-2
View File
@@ -11,8 +11,6 @@
$warn-color: mat.get-color-from-palette($warn, 500);
$accent-color: mat.get-color-from-palette($accent, 500);
$is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background);
$base: map-get($foreground, base);
.main-container {
display: flex;
+19 -19
View File
@@ -1,7 +1,7 @@
import { BreakpointObserver } from '@angular/cdk/layout';
import { OverlayContainer } from '@angular/cdk/overlay';
import { DOCUMENT, ViewportScroller } from '@angular/common';
import { Component, HostBinding, HostListener, Inject, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Component, HostBinding, HostListener, Inject, OnDestroy, ViewChild } from '@angular/core';
import { MatIconRegistry } from '@angular/material/icon';
import { MatDrawer } from '@angular/material/sidenav';
import { DomSanitizer } from '@angular/platform-browser';
@@ -19,8 +19,7 @@ import { KeyboardShortcutsService } from './services/keyboard-shortcuts/keyboard
import { ManagementService } from './services/mgmt.service';
import { NavigationService } from './services/navigation.service';
import { OverlayWorkflowService } from './services/overlay/overlay-workflow.service';
import { IntroWorkflowOverlays } from './services/overlay/workflows';
import { StorageLocation, StorageService } from './services/storage.service';
import { StorageService } from './services/storage.service';
import { ThemeService } from './services/theme.service';
import { UpdateService } from './services/update.service';
@@ -30,7 +29,7 @@ import { UpdateService } from './services/update.service';
styleUrls: ['./app.component.scss'],
animations: [toolbarAnimation, ...navAnimations, accountCard, routeAnimations, adminLineAnimation],
})
export class AppComponent implements OnInit, OnDestroy {
export class AppComponent implements OnDestroy {
@ViewChild('drawer') public drawer!: MatDrawer;
public isHandset$: Observable<boolean> = this.breakpointObserver.observe('(max-width: 599px)').pipe(
map((result) => {
@@ -218,7 +217,10 @@ export class AppComponent implements OnInit, OnDestroy {
.then((org) => {
this.org = org;
this.startIntroWorkflow();
this.loadPrivateLabelling();
// TODO add when console storage is implemented
// this.startIntroWorkflow();
})
.catch((error) => {
this.router.navigate(['/users/me']);
@@ -235,21 +237,19 @@ export class AppComponent implements OnInit, OnDestroy {
});
}
private startIntroWorkflow(): void {
setTimeout(() => {
const cb = () => {
this.storageService.setItem('intro-dismissed', true, StorageLocation.local);
};
const dismissed = this.storageService.getItem('intro-dismissed', StorageLocation.local);
if (!dismissed) {
this.workflowService.startWorkflow(IntroWorkflowOverlays, cb);
}
}, 1000);
}
// TODO implement Console storage
public ngOnInit(): void {
this.loadPrivateLabelling();
}
// private startIntroWorkflow(): void {
// setTimeout(() => {
// const cb = () => {
// this.storageService.setItem('intro-dismissed', true, StorageLocation.local);
// };
// const dismissed = this.storageService.getItem('intro-dismissed', StorageLocation.local);
// if (!dismissed) {
// this.workflowService.startWorkflow(IntroWorkflowOverlays, cb);
// }
// }, 1000);
// }
public ngOnDestroy(): void {
this.destroy$.next();
@@ -63,6 +63,11 @@
font-size: 15px;
}
.filter-select-method .mat-select {
height: 36px;
padding: 7px 10px;
}
.subquery {
display: flex;
flex-direction: row;
@@ -82,6 +87,8 @@
}
.filter-input-value {
flex: 1;
input {
height: 36px;
font-size: 15px;
@@ -96,7 +96,7 @@
margin: 0.5rem 0;
a {
color: map-get($foreground, base);
color: map-get($foreground, text);
text-decoration: none;
display: flex;
align-items: center;
@@ -57,7 +57,7 @@
.org-link {
font-size: 14px;
color: map-get($foreground, base);
color: map-get($foreground, text);
text-decoration: none;
padding: 0.5rem 0.75rem 0.5rem 0.75rem;
height: 2.5rem;
@@ -90,7 +90,7 @@
.org-switch-button {
font-weight: bold;
border: none;
color: map-get($foreground, base);
color: map-get($foreground, text);
border-radius: 6px;
position: relative;
font-size: 14px;
@@ -118,7 +118,7 @@
svg {
opacity: 0.7;
fill: map-get($foreground, base);
fill: map-get($foreground, text);
height: 1.25rem;
width: 1.25rem;
}
@@ -135,7 +135,7 @@
.breadcrumb-link {
font-size: 14px;
color: map-get($foreground, base);
color: map-get($foreground, text);
text-decoration: none;
padding: 0.5rem 0.75rem 0.5rem 0.75rem;
height: 2.5rem;
@@ -44,7 +44,7 @@
display: flex;
flex-direction: column;
width: 100%;
color: map-get($foreground, base);
color: map-get($foreground, text);
&.top-left {
&::before {
@@ -14,6 +14,7 @@
padding-right: 1rem;
font-size: 14px;
margin: 0.5rem 0;
box-shadow: 0 2px 5px rgb(0 0 0 / 10%);
&.fit {
width: fit-content;
@@ -1,7 +1,7 @@
<h1 mat-dialog-title>
<span>{{data.titleKey | translate}} {{data?.number}}</span>
<span>{{ data.titleKey | translate }} {{ data?.number }}</span>
</h1>
<p class="desc cnsl-secondary-text">{{data.descKey | translate}}</p>
<p class="desc cnsl-secondary-text">{{ data.descKey | translate }}</p>
<div mat-dialog-content>
<cnsl-form-field class="formfield">
<cnsl-label>{{ data.labelKey | translate }}</cnsl-label>
@@ -9,12 +9,11 @@
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button color="primary" mat-stroked-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
<button color="primary" mat-stroked-button (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
<button [disabled]="!name" cdkFocusInitial color="primary" mat-raised-button class="ok-button"
(click)="closeDialog(name)">
{{'ACTIONS.RENAME' | translate}}
<button [disabled]="!name" cdkFocusInitial color="primary" mat-raised-button (click)="closeDialog(name)">
{{ 'ACTIONS.RENAME' | translate }}
</button>
</div>
</div>
@@ -15,10 +15,6 @@ h1 {
display: flex;
justify-content: space-between;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}
@@ -1,25 +1,29 @@
<div class="paginator-wrapper">
<div class="col">
<p class="length"><span>{{length}} </span>{{'PAGINATOR.COUNT' | translate}}</p>
<p class="length">
<span>{{ length }} </span>{{ 'PAGINATOR.COUNT' | translate }}
</p>
<p class="ts cnsl-secondary-text" *ngIf="timestamp">
{{(timestamp | timestampToDate)| localizedDate: 'EEEE dd. MMM YYYY, HH:mm'}}
{{ timestamp | timestampToDate | localizedDate: 'EEEE dd. MMM YYYY, HH:mm' }}
</p>
</div>
<span class="fill-space"></span>
<span class="pos cnsl-secondary-text" *ngIf="!hidePagination">{{pageIndex * pageSize}} - {{pageIndex * pageSize +
pageSize}} </span>
<span class="pos cnsl-secondary-text" *ngIf="!hidePagination"
>{{ pageIndex * pageSize }} - {{ pageIndex * pageSize + pageSize }}
</span>
<div class="row" *ngIf="!hidePagination">
<cnsl-form-field class="size" appearance="outline">
<mat-select [(ngModel)]="pageSize" (selectionChange)="emitChange()">
<mat-select class="paginator-select" [(ngModel)]="pageSize" (selectionChange)="emitChange()">
<mat-option *ngFor="let sizeOption of pageSizeOptions" [value]="sizeOption">
{{sizeOption}}
{{ sizeOption }}
</mat-option>
</mat-select>
</cnsl-form-field>
<button *ngIf="previousPossible" (click)="previous()" [disabled]="!previousPossible"
mat-stroked-button>{{'PAGINATOR.PREVIOUS' |
translate}}</button>
<button *ngIf="nextPossible" (click)="next()" [disabled]="!nextPossible" mat-stroked-button>{{'PAGINATOR.NEXT' |
translate}}</button>
<button *ngIf="previousPossible" (click)="previous()" [disabled]="!previousPossible" mat-stroked-button>
{{ 'PAGINATOR.PREVIOUS' | translate }}
</button>
<button *ngIf="nextPossible" (click)="next()" [disabled]="!nextPossible" mat-stroked-button>
{{ 'PAGINATOR.NEXT' | translate }}
</button>
</div>
</div>
</div>
@@ -50,7 +50,7 @@
}
/* stylelint-disable */
::ng-deep .mat-select {
::ng-deep .paginator-select.mat-select {
min-width: 60px;
height: 36px !important;
padding-top: 8px !important;
@@ -1,4 +1,4 @@
<h1 mat-dialog-title class="title">
<h1 class="title">
<span>{{ data.title | translate }}</span>
</h1>
<div mat-dialog-content>
@@ -1,9 +1,10 @@
.title {
font-size: 1.5rem;
font-size: 1.3rem;
}
.desc {
font-size: 14px;
margin-top: 0;
}
.form-field {
@@ -19,6 +19,7 @@ export class DialogAddTypeComponent {
constructor(public dialogRef: MatDialogRef<DialogAddTypeComponent>, @Inject(MAT_DIALOG_DATA) public data: any) {
this.availableMfaTypes = data.types;
this.newMfaType = data.types && data.types[0] ? data.types[0] : undefined;
}
public closeDialog(): void {
@@ -22,7 +22,7 @@
<button
mat-stroked-button
class="new-mfa cnsl-action-button"
[disabled]="disabled"
[disabled]="disabled || availableSelection.length === 0"
(click)="!disabled ? addMfa() : null"
>
<mat-icon class="icon">add</mat-icon>
@@ -35,7 +35,7 @@
display: block;
&:not(:hover) {
color: var(--grey);
color: map-get($foreground, secondary-text);
}
}
}
@@ -105,27 +105,12 @@ export class FactorTableComponent implements OnInit {
}
public addMfa(): void {
let selection: any[] = [];
if (this.componentType === LoginMethodComponentType.MultiFactor) {
selection = [MultiFactorType.MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION];
} else if (this.componentType === LoginMethodComponentType.SecondFactor) {
selection = [SecondFactorType.SECOND_FACTOR_TYPE_U2F, SecondFactorType.SECOND_FACTOR_TYPE_OTP];
}
this.mfas.forEach((mfa) => {
const index = selection.findIndex((sel) => sel === mfa);
if (index > -1) {
selection.splice(index, 1);
}
});
const dialogRef = this.dialog.open(DialogAddTypeComponent, {
data: {
title: 'MFA.CREATE.TITLE',
desc: 'MFA.CREATE.DESCRIPTION',
componentType: this.componentType,
types: selection,
types: this.availableSelection,
},
width: '400px',
});
@@ -244,4 +229,17 @@ export class FactorTableComponent implements OnInit {
this.getData();
}, to);
}
public get availableSelection(): Array<MultiFactorType | SecondFactorType> {
const allTypes: MultiFactorType[] | SecondFactorType[] =
this.componentType === LoginMethodComponentType.MultiFactor
? [MultiFactorType.MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION]
: this.componentType === LoginMethodComponentType.SecondFactor
? [SecondFactorType.SECOND_FACTOR_TYPE_U2F, SecondFactorType.SECOND_FACTOR_TYPE_OTP]
: [];
const filtered = (allTypes as Array<MultiFactorType | SecondFactorType>).filter((type) => !this.mfas.includes(type));
return filtered;
}
}
@@ -122,7 +122,6 @@ export class LoginPolicyComponent implements OnInit {
)
.pipe(take(1))
.subscribe((allowed) => {
console.log(allowed);
if (allowed) {
this.lifetimeForm.enable();
}
@@ -32,7 +32,7 @@
</cnsl-form-field>
</div>
<div class="content">
<div class="message-text-content">
<cnsl-edit-text
[chips]="chips[currentType]"
[disabled]="(canWrite$ | async) === false"
@@ -43,7 +43,7 @@
></cnsl-edit-text>
</div>
<div class="actions">
<div class="message-text-actions">
<button
class="reset-button"
*ngIf="(getCustomInitMessageTextMap$ | async) && (getCustomInitMessageTextMap$ | async)?.isDefault === false"
@@ -40,11 +40,11 @@
display: block;
}
.content {
.message-text-content {
padding-top: 1rem;
}
.actions {
.message-text-actions {
display: flex;
justify-content: flex-end;
margin-top: 1rem;
@@ -1,11 +1,19 @@
<!-- <cnsl-card
title="{{ 'POLICY.PWD_LOCKOUT.TITLE' | translate }}"
description="{{ 'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate }}"
> -->
<h2>{{ 'POLICY.PWD_LOCKOUT.TITLE' | translate }}</h2>
<p class="cnsl-secondary-text">{{ 'POLICY.PWD_LOCKOUT.DESCRIPTION' | translate }}</p>
<cnsl-info-section class="default" *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="resetPolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
<!-- <cnsl-info-section class="default" *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section> -->
<cnsl-card *ngIf="lockoutData">
<div class="lockout-content">
@@ -38,17 +46,4 @@
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="resetPolicy()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
</div>
<!-- </cnsl-card> -->
@@ -1,7 +1,21 @@
<h2>{{ 'POLICY.PRIVACY_POLICY.TITLE' | translate }}</h2>
<p class="cnsl-secondary-text">{{ 'POLICY.PRIVACY_POLICY.DESCRIPTION' | translate }}</p>
<cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section>
<ng-container *ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault">
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
color="primary"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="resetDefault()"
mat-stroked-button
>
{{ 'POLICY.RESET' | translate }}
</button>
</ng-template>
</ng-container>
<!-- <cnsl-info-section *ngIf="isDefault"> {{ 'POLICY.DEFAULTLABEL' | translate }}</cnsl-info-section> -->
<div>
<form *ngIf="form" [formGroup]="form" class="policy-content">
@@ -26,17 +40,6 @@
</div>
<div class="policy-actions">
<button
*ngIf="privacyPolicy && privacyPolicy.isDefault === false"
class="reset-button"
[disabled]="(canWrite$ | async) === false"
(click)="resetDefault()"
color="warn"
type="submit"
mat-stroked-button
>
<i class="las la-history"></i> {{ 'ACTIONS.RESETDEFAULT' | translate }}
</button>
<button
class="save-button"
[disabled]="(canWrite$ | async) === false"
@@ -61,18 +61,8 @@
display: flex;
justify-content: flex-start;
.save-button,
.reset-button {
.save-button {
display: block;
margin: 0 1rem 0 0;
}
.reset-button {
display: flex;
align-items: center;
i {
margin-bottom: 3px;
}
margin: 0;
}
}
@@ -20,7 +20,7 @@
pointer-events: none;
border-radius: 0.5rem;
transform: scale(0.9);
color: map-get($foreground, base);
color: map-get($foreground, text);
* {
pointer-events: none;
@@ -54,7 +54,7 @@
<ng-template cnslHasRole [hasRole]="['policy.delete']">
<button
class="pl-action-button"
*ngIf="view === View.CURRENT && serviceType === PolicyComponentServiceType.MGMT && !isDefault"
*ngIf="serviceType === PolicyComponentServiceType.MGMT && !isDefault"
matTooltip="{{ 'POLICY.RESET' | translate }}"
color="warn"
(click)="removePolicy()"
@@ -93,6 +93,11 @@
.pl-action-button {
align-self: flex-start;
margin-top: 0.5rem;
margin-right: 0.5rem;
&:last-child {
margin-right: 0;
}
}
}
@@ -1,20 +1,25 @@
<h1 class="title">{{'PROJECT.PAGES.PRIVATELABEL.DIALOG.TITLE' | translate}} {{data?.number}}</h1>
<p class="desc cnsl-secondary-text">{{'PROJECT.PAGES.PRIVATELABEL.DIALOG.DESCRIPTION' | translate}}</p>
<h1 class="title">{{ 'PROJECT.PAGES.PRIVATELABEL.DIALOG.TITLE' | translate }} {{ data?.number }}</h1>
<p class="desc cnsl-secondary-text">{{ 'PROJECT.PAGES.PRIVATELABEL.DIALOG.DESCRIPTION' | translate }}</p>
<div mat-dialog-content>
<mat-radio-group class="example-radio-group" [(ngModel)]="setting">
<mat-radio-group class="project-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>
<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>
<cnsl-info-section class="info">{{ 'PROJECT.PAGES.PRIVATELABEL.' + setting + '.DESC' | translate }}</cnsl-info-section>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial mat-stroked-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
<button cdkFocusInitial mat-stroked-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
[disabled]="setting === undefined"
cdkFocusInitial
color="primary"
mat-raised-button
(click)="closeDialog(setting)"
>
{{ 'ACTIONS.OK' | translate }}
</button>
</div>
</div>
@@ -7,11 +7,16 @@
font-size: 0.9rem;
}
.radio-button {
margin: 0.5rem 0;
.project-radio-group {
display: flex;
flex-direction: column;
.label {
white-space: normal;
.radio-button {
margin: 0.5rem 0;
.label {
white-space: normal;
}
}
}
@@ -24,10 +29,6 @@
display: flex;
justify-content: space-between;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}
@@ -1,5 +1,4 @@
<div class="found" *ngIf="users.length > 0">
<!-- <span class="found-label cnsl-secondary-text">{{'USER.SEARCH.FOUND' | translate}}:</span> -->
<div class="user-autocomplete-found" *ngIf="users.length > 0">
<div class="found-user-row" *ngFor="let user of users; index as i">
<div class="circle">
<cnsl-avatar
@@ -109,9 +108,11 @@
</mat-option>
</mat-autocomplete>
<span class="target-desc">
<span class="user-autocomplete-target-desc">
{{ 'USER.TARGET.SELF' | translate }}
<a (click)="changeTarget()">{{ 'USER.TARGET.CLICKHERE' | translate }}</a>
<a (click)="changeTarget()"
><strong>{{ 'USER.TARGET.CLICKHERE' | translate }}</strong></a
>
</span>
</cnsl-form-field>
</div>
@@ -122,9 +123,11 @@
<cnsl-form-field class="user-create-form-field more-space">
<cnsl-label>{{ 'USER.SEARCH.ADDITIONAL-EXTERNAL' | translate }}</cnsl-label>
<input cnslInput type="text" [formControl]="globalLoginNameControl" placeholder="[email protected]" />
<span class="target-desc">
<span class="user-autocomplete-target-desc">
{{ (target === UserTarget.SELF ? 'USER.TARGET.SELF' : 'USER.TARGET.EXTERNAL') | translate }}
<a (click)="changeTarget()">{{ 'USER.TARGET.CLICKHERE' | translate }}</a>
<a (click)="changeTarget()"
><strong>{{ 'USER.TARGET.CLICKHERE' | translate }}</strong></a
>
</span>
</cnsl-form-field>
@@ -1,15 +1,79 @@
.target-desc {
font-size: 14px;
display: block;
margin-top: 0.5rem;
@use '@angular/material' as mat;
a {
color: #4072b4;
@mixin search-user-autocomplete-theme($theme) {
$primary: map-get($theme, primary);
$primary-color: mat.get-color-from-palette($primary, 500);
$background: map-get($theme, background);
$foreground: map-get($theme, foreground);
$secondary-text: map-get($foreground, secondary-text);
&:hover {
cursor: pointer;
color: #6992c9;
text-decoration: underline;
.user-autocomplete-found {
margin: 0.5rem 0;
border-radius: 0.5rem;
padding: 0.5rem 0;
max-width: 500px;
box-sizing: border-box;
.add-user-row {
margin-left: -4px;
}
.found-user-row {
display: flex;
align-items: center;
padding: 0.25rem 0;
button {
visibility: hidden;
}
&:hover {
button {
visibility: visible;
}
}
}
.found-label {
font-size: 14px;
}
.user-name-column {
display: flex;
flex-direction: column;
justify-content: center;
.smaller {
font-size: 13px;
}
}
.fill-space {
flex: 1;
}
.search-user-dl-btn {
transition: color 0.2s ease;
&:not(:hover) {
color: $secondary-text;
}
}
}
.user-autocomplete-target-desc {
font-size: 14px;
display: block;
margin-top: 0.5rem;
a {
color: $primary-color;
&:hover {
cursor: pointer;
color: mat.get-color-from-palette($primary, 400);
text-decoration: underline;
}
}
}
}
@@ -36,60 +100,6 @@
font-size: 0.8rem;
}
.found {
margin: 0.5rem 0;
border-radius: 0.5rem;
padding: 0.5rem 0;
max-width: 500px;
box-sizing: border-box;
.add-user-row {
margin-left: -4px;
}
.found-user-row {
display: flex;
align-items: center;
padding: 0.25rem 0;
button {
visibility: hidden;
}
&:hover {
button {
visibility: visible;
}
}
}
.found-label {
font-size: 14px;
}
.user-name-column {
display: flex;
flex-direction: column;
justify-content: center;
.smaller {
font-size: 13px;
}
}
.fill-space {
flex: 1;
}
.search-user-dl-btn {
transition: color 0.2s ease;
&:not(:hover) {
color: var(--grey);
}
}
}
.circle {
margin-right: 0.5rem;
}
@@ -2,10 +2,10 @@ export interface SettingLinks {
i18nTitle: string;
i18nDesc: string;
iamRouterLink: any;
orgRouterLink: any;
orgRouterLink?: any;
queryParams: any;
iamWithRole: string[];
orgWithRole: string[];
iamWithRole?: string[];
orgWithRole?: string[];
icon?: string;
svgIcon?: string;
color: string;
@@ -49,12 +49,10 @@ export const PRIVACY_POLICY: SettingLinks = {
export const NOTIFICATION_GROUP: SettingLinks = {
i18nTitle: 'SETTINGS.GROUPS.NOTIFICATIONS',
i18nDesc: '',
i18nDesc: 'SETTINGS.LIST.NOTIFICATIONS_DESC',
iamRouterLink: ['/settings'],
orgRouterLink: ['/org-settings'],
queryParams: { id: 'notifications' },
iamWithRole: ['iam.policy.read'],
orgWithRole: ['policy.read'],
icon: 'las la-bell',
color: 'red',
};
@@ -1,5 +1,5 @@
import { animate, style, transition, trigger } from '@angular/animations';
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { PolicyComponentServiceType } from 'src/app/modules/policies/policy-component-types.enum';
import { SETTINGLINKS, SettingLinks } from './settinglinks';
@@ -35,9 +35,15 @@ import { SETTINGLINKS, SettingLinks } from './settinglinks';
]),
],
})
export class SettingsGridComponent {
export class SettingsGridComponent implements OnInit {
@Input() public type!: PolicyComponentServiceType;
@Input() public tag: string = '';
public PolicyComponentServiceType: any = PolicyComponentServiceType;
public SETTINGS: SettingLinks[] = SETTINGLINKS;
ngOnInit(): void {
this.SETTINGS = this.SETTINGS.filter((setting) =>
this.type === PolicyComponentServiceType.MGMT ? !!setting.orgRouterLink : !!setting.iamRouterLink,
);
}
}
@@ -1,99 +1,132 @@
<div cdkDropListGroup>
<div class="shortcut-container">
<div class="shortcut-title-container">
<h2>{{'HOME.SHORTCUTS.SHORTCUTS' | translate}}</h2>
<button [matTooltip]="(editState ? 'ACTIONS.CLOSE' : 'ACTIONS.EDIT') | translate" class="shortcut-btn"
(click)="editState = !editState" mat-icon-button>
<i *ngIf="!editState" class=" las la-pen"></i>
<mat-icon *ngIf="editState">close</mat-icon>
<h2>{{ 'HOME.SHORTCUTS.SHORTCUTS' | translate }}</h2>
<button
[matTooltip]="(editState ? 'ACTIONS.SAVE' : 'ACTIONS.EDIT') | translate"
class="shortcut-btn"
(click)="editState = !editState"
mat-icon-button
>
<i *ngIf="!editState" class="las la-pen"></i>
<i *ngIf="editState" class="las la-check"></i>
</button>
<button matTooltip="{{'ACTIONS.RESETDEFAULT' | translate}}" (click)="reset()" *ngIf="editState" mat-icon-button>
<button matTooltip="{{ 'ACTIONS.RESETDEFAULT' | translate }}" (click)="reset()" *ngIf="editState" mat-icon-button>
<i *ngIf="editState" class="las la-undo-alt"></i>
</button>
</div>
<div class="shortcut-list-row">
<div cdkDropList [cdkDropListData]="main" class="shortcut-list" (cdkDropListDropped)="drop($event, 'main')">
<p *ngIf="editState" class="shortcut-desc">{{'HOME.SHORTCUTS.REORDER' | translate}}</p>
<p *ngIf="editState" class="shortcut-desc cnsl-secondary-text">{{ 'HOME.SHORTCUTS.REORDER' | translate }}</p>
<ng-container *ngFor="let shortcut of main">
<ng-template cnslHasRole [hasRole]="shortcut.withRole">
<a [routerLink]="!editState ? shortcut.routerLink : null" class="shortcut-box"
[ngClass]="{'edit-state': editState && !shortcut.disabled, 'disabled': editState && shortcut.disabled}"
cdkDrag [cdkDragDisabled]="shortcut.disabled || !editState">
<div class="shortcuts-avatar {{shortcut.color}}">
<a
[routerLink]="!editState ? shortcut.routerLink : null"
[queryParams]="shortcut.queryParams ? shortcut.queryParams : null"
class="shortcut-box"
[ngClass]="{ 'edit-state': editState && !shortcut.disabled, disabled: editState && shortcut.disabled }"
cdkDrag
[cdkDragDisabled]="shortcut.disabled || !editState"
>
<div class="shortcuts-avatar {{ shortcut.color }}">
<mat-icon *ngIf="shortcut.svgIcon" class="icon" [svgIcon]="shortcut.svgIcon"></mat-icon>
<i *ngIf="shortcut.icon" class="icon {{shortcut.icon}}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{shortcut.label}}</span>
<i *ngIf="shortcut.icon" class="icon {{ shortcut.icon }}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{ shortcut.label }}</span>
</div>
<div class="shortcut-col">
<span *ngIf="shortcut.i18nTitle">{{shortcut.i18nTitle | translate}}</span>
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
<span *ngIf="shortcut.i18nTitle">{{ shortcut.i18nTitle | translate }}</span>
<span *ngIf="shortcut.title">{{ shortcut.title }}</span>
</div>
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{'PROJECT.STATE.'+shortcut.state | translate}}"
[ngClass]="{'active': shortcut.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': shortcut.state === ProjectState.PROJECT_STATE_INACTIVE }">
</div>
<div
class="shortcut-state-dot"
*ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{ 'PROJECT.STATE.' + shortcut.state | translate }}"
[ngClass]="{
active: shortcut.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: shortcut.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</a>
</ng-template>
</ng-container>
</div>
<div cdkDropList [cdkDropListData]="secondary" class="shortcut-list"
(cdkDropListDropped)="drop($event, 'secondary')">
<p *ngIf="editState" class="shortcut-desc">{{'HOME.SHORTCUTS.REORDER' | translate}}</p>
<div cdkDropList [cdkDropListData]="secondary" class="shortcut-list" (cdkDropListDropped)="drop($event, 'secondary')">
<p *ngIf="editState" class="shortcut-desc cnsl-secondary-text">{{ 'HOME.SHORTCUTS.REORDER' | translate }}</p>
<ng-container *ngFor="let shortcut of secondary">
<ng-template cnslHasRole [hasRole]="shortcut.withRole">
<a [routerLink]="!editState ? shortcut.routerLink : null" class="shortcut-box"
[ngClass]="{'edit-state': editState && !shortcut.disabled}" cdkDrag
[cdkDragDisabled]="shortcut.disabled || !editState">
<div class="shortcuts-avatar {{shortcut.color}}">
<a
[routerLink]="!editState ? shortcut.routerLink : null"
[queryParams]="shortcut.queryParams ? shortcut.queryParams : null"
class="shortcut-box"
[ngClass]="{ 'edit-state': editState && !shortcut.disabled }"
cdkDrag
[cdkDragDisabled]="shortcut.disabled || !editState"
>
<div class="shortcuts-avatar {{ shortcut.color }}">
<mat-icon *ngIf="shortcut.svgIcon" class="icon" [svgIcon]="shortcut.svgIcon"></mat-icon>
<i *ngIf="shortcut.icon" class="icon {{shortcut.icon}}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{shortcut.label}}</span>
<i *ngIf="shortcut.icon" class="icon {{ shortcut.icon }}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{ shortcut.label }}</span>
</div>
<div class="shortcut-col">
<span *ngIf="shortcut.i18nTitle">{{shortcut.i18nTitle | translate}}</span>
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
<span *ngIf="shortcut.i18nTitle">{{ shortcut.i18nTitle | translate }}</span>
<span *ngIf="shortcut.title">{{ shortcut.title }}</span>
</div>
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{'PROJECT.STATE.'+shortcut.state | translate}}"
[ngClass]="{'active': shortcut.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': shortcut.state === ProjectState.PROJECT_STATE_INACTIVE }">
</div>
<div
class="shortcut-state-dot"
*ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{ 'PROJECT.STATE.' + shortcut.state | translate }}"
[ngClass]="{
active: shortcut.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: shortcut.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</a>
</ng-template>
</ng-container>
</div>
<div cdkDropList [cdkDropListData]="third" class="shortcut-list" (cdkDropListDropped)="drop($event, 'third')">
<p *ngIf="editState" class="shortcut-desc">{{'HOME.SHORTCUTS.REORDER' | translate}}</p>
<p *ngIf="editState" class="shortcut-desc cnsl-secondary-text">{{ 'HOME.SHORTCUTS.REORDER' | translate }}</p>
<ng-container *ngFor="let shortcut of third">
<ng-template cnslHasRole [hasRole]="shortcut.withRole">
<a [routerLink]="!editState ? shortcut.routerLink : null" class="shortcut-box"
[ngClass]="{'edit-state': editState && !shortcut.disabled}" cdkDrag
[cdkDragDisabled]="shortcut.disabled || !editState">
<div class="shortcuts-avatar {{shortcut.color}}">
<a
[routerLink]="!editState ? shortcut.routerLink : null"
[queryParams]="shortcut.queryParams ? shortcut.queryParams : null"
class="shortcut-box"
[ngClass]="{ 'edit-state': editState && !shortcut.disabled }"
cdkDrag
[cdkDragDisabled]="shortcut.disabled || !editState"
>
<div class="shortcuts-avatar {{ shortcut.color }}">
<mat-icon *ngIf="shortcut.svgIcon" class="icon" [svgIcon]="shortcut.svgIcon"></mat-icon>
<i *ngIf="shortcut.icon" class="icon {{shortcut.icon}}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{shortcut.label}}</span>
<i *ngIf="shortcut.icon" class="icon {{ shortcut.icon }}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{ shortcut.label }}</span>
</div>
<div class="shortcut-col">
<span *ngIf="shortcut.i18nTitle">{{shortcut.i18nTitle | translate}}</span>
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
<span *ngIf="shortcut.i18nTitle">{{ shortcut.i18nTitle | translate }}</span>
<span *ngIf="shortcut.title">{{ shortcut.title }}</span>
</div>
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{'PROJECT.STATE.'+shortcut.state | translate}}"
[ngClass]="{'active': shortcut.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': shortcut.state === ProjectState.PROJECT_STATE_INACTIVE }">
</div>
<div
class="shortcut-state-dot"
*ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{ 'PROJECT.STATE.' + shortcut.state | translate }}"
[ngClass]="{
active: shortcut.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: shortcut.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</a>
</ng-template>
</ng-container>
@@ -102,92 +135,48 @@
</div>
<div class="shortcut-container" *ngIf="editState">
<h2>{{'HOME.SHORTCUTS.SETTINGS' | translate}}</h2>
<h2>{{ 'HOME.SHORTCUTS.SETTINGS' | translate }}</h2>
<div class="shortcut-list-row">
<div class="available-shortcut-wrapper">
<p *ngIf="editState" class="shortcut-desc cnsl-secondary-text">{{ 'HOME.SHORTCUTS.REORDER' | translate }}</p>
<div cdkDropList [cdkDropListData]="allRoutes" class="shortcut-list" (cdkDropListDropped)="drop($event, 'main')">
<p *ngIf="editState" class="shortcut-desc">{{'HOME.SHORTCUTS.REORDER' | translate}}</p>
<ng-container *ngFor="let shortcut of allRoutes">
<div
cdkDropList
[cdkDropListData]="allAvailableShortcuts"
class="available-shortcut-list"
(cdkDropListDropped)="drop($event, 'main')"
>
<ng-container *ngFor="let shortcut of allAvailableShortcuts">
<ng-template cnslHasRole [hasRole]="shortcut.withRole">
<div class="shortcut-box"
[ngClass]="{'edit-state': editState && !shortcut.disabled, 'disabled': editState && shortcut.disabled}"
cdkDrag [cdkDragDisabled]="shortcut.disabled">
<div class="shortcuts-avatar {{shortcut.color}}">
<div
class="shortcut-box"
[ngClass]="{ 'edit-state': editState && !shortcut.disabled, disabled: editState && shortcut.disabled }"
cdkDrag
[cdkDragDisabled]="shortcut.disabled"
>
<div class="shortcuts-avatar {{ shortcut.color }}">
<mat-icon *ngIf="shortcut.svgIcon" class="icon" [svgIcon]="shortcut.svgIcon"></mat-icon>
<i *ngIf="shortcut.icon" class="icon {{shortcut.icon}}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{shortcut.label}}</span>
<i *ngIf="shortcut.icon" class="icon {{ shortcut.icon }}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{ shortcut.label }}</span>
</div>
<div class="shortcut-col">
<span *ngIf="shortcut.i18nTitle">{{shortcut.i18nTitle | translate}}</span>
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.i18nDesc">{{shortcut.i18nDesc |
translate}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.desc">{{shortcut.desc}}</span>
<span *ngIf="shortcut.i18nTitle">{{ shortcut.i18nTitle | translate }}</span>
<span *ngIf="shortcut.title">{{ shortcut.title }}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.i18nDesc">{{
shortcut.i18nDesc | translate
}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.desc">{{ shortcut.desc }}</span>
</div>
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{'PROJECT.STATE.'+shortcut.state | translate}}"
[ngClass]="{'active': shortcut.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': shortcut.state === ProjectState.PROJECT_STATE_INACTIVE }">
</div>
</div>
</ng-template>
</ng-container>
</div>
<div cdkDropList [cdkDropListData]="allPolicies" class="shortcut-list"
(cdkDropListDropped)="drop($event, 'secondary')">
<p *ngIf="editState" class="shortcut-desc">{{'HOME.SHORTCUTS.REORDER' | translate}}</p>
<ng-container *ngFor="let shortcut of allPolicies">
<ng-template cnslHasRole [hasRole]="shortcut.withRole">
<div class="shortcut-box" [ngClass]="{'edit-state': editState && !shortcut.disabled}" cdkDrag
[cdkDragDisabled]="shortcut.disabled">
<div class="shortcuts-avatar {{shortcut.color}}">
<mat-icon *ngIf="shortcut.svgIcon" class="icon" [svgIcon]="shortcut.svgIcon"></mat-icon>
<i *ngIf="shortcut.icon" class="icon {{shortcut.icon}}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{shortcut.label}}</span>
</div>
<div class="shortcut-col">
<span *ngIf="shortcut.i18nTitle">{{shortcut.i18nTitle | translate}}</span>
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.i18nDesc">{{shortcut.i18nDesc |
translate}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.desc">{{shortcut.desc}}</span>
</div>
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{'PROJECT.STATE.'+shortcut.state | translate}}"
[ngClass]="{'active': shortcut.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': shortcut.state === ProjectState.PROJECT_STATE_INACTIVE }">
</div>
</div>
</ng-template>
</ng-container>
</div>
<div cdkDropList [cdkDropListData]="allProjects" class="shortcut-list"
(cdkDropListDropped)="drop($event, 'third')">
<p *ngIf="editState" class="shortcut-desc">{{'HOME.SHORTCUTS.REORDER' | translate}}</p>
<ng-container *ngFor="let shortcut of allProjects">
<ng-template cnslHasRole [hasRole]="shortcut.withRole">
<div class="shortcut-box" [ngClass]="{'edit-state': editState && !shortcut.disabled}" cdkDrag
[cdkDragDisabled]="shortcut.disabled">
<div class="shortcuts-avatar {{shortcut.color}}">
<mat-icon *ngIf="shortcut.svgIcon" class="icon" [svgIcon]="shortcut.svgIcon"></mat-icon>
<i *ngIf="shortcut.icon" class="icon {{shortcut.icon}}"></i>
<span *ngIf="shortcut.label" class="shortcuts-avatar-label">{{shortcut.label}}</span>
</div>
<div class="shortcut-col">
<span *ngIf="shortcut.i18nTitle">{{shortcut.i18nTitle | translate}}</span>
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.i18nDesc">{{shortcut.i18nDesc |
translate}}</span>
<span class="shortcut-item-desc cnsl-secondary-text" *ngIf="shortcut.desc">{{shortcut.desc}}</span>
</div>
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{'PROJECT.STATE.'+shortcut.state | translate}}"
[ngClass]="{'active': shortcut.state === ProjectState.PROJECT_STATE_ACTIVE, 'inactive': shortcut.state === ProjectState.PROJECT_STATE_INACTIVE }">
</div>
<div
class="shortcut-state-dot"
*ngIf="shortcut && shortcut.state !== undefined"
matTooltip="{{ 'PROJECT.STATE.' + shortcut.state | translate }}"
[ngClass]="{
active: shortcut.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: shortcut.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</div>
</ng-template>
</ng-container>
@@ -207,4 +196,4 @@
<span *ngIf="shortcut.title">{{shortcut.title}}</span>
</div>
</ng-template>
</ng-template> -->
</ng-template> -->
@@ -62,7 +62,6 @@
.shortcut-desc {
align-self: center;
font-size: 14px;
color: var(--grey);
margin-top: 0;
text-align: center;
}
@@ -89,7 +88,6 @@
.shortcut-desc {
align-self: center;
font-size: 14px;
color: var(--grey);
margin-top: 0;
text-align: center;
}
@@ -209,6 +207,30 @@
}
}
.available-shortcut-wrapper {
border: 1px solid $border-color;
border-radius: 1rem;
padding: 1rem;
.available-shortcut-list {
display: grid;
grid-template-columns: 1fr;
grid-gap: 1rem;
@media only screen and (min-width: 800px) {
grid-template-columns: 1fr 1fr;
}
@media only screen and (min-width: 1200px) {
grid-template-columns: 1fr 1fr 1fr;
}
.shortcut-box {
margin: 0;
}
}
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 1rem;
@@ -17,7 +17,8 @@ export interface ShortcutItem {
i18nTitle?: string;
i18nDesc?: string;
routerLink: any;
withRole: string[];
queryParams?: any;
withRole?: string[];
icon?: string;
label?: string;
svgIcon?: string;
@@ -138,8 +139,9 @@ export class ShortcutsComponent implements OnDestroy {
type: ShortcutType.POLICY,
i18nTitle: p.i18nTitle,
i18nDesc: p.i18nDesc,
routerLink: p.orgRouterLink,
withRole: p.orgWithRole,
routerLink: p.orgRouterLink ?? p.iamRouterLink,
queryParams: p.queryParams,
withRole: p.orgWithRole ?? p.iamWithRole,
icon: p.icon ?? '',
svgIcon: p.svgIcon ?? '',
color: p.color ?? '',
@@ -176,10 +178,10 @@ export class ShortcutsComponent implements OnDestroy {
} else {
switch (listName) {
case 'main':
this.main = [PROFILE_SHORTCUT, CREATE_ORG, CREATE_PROJECT, CREATE_USER];
this.main = [PROFILE_SHORTCUT /* CREATE_ORG, CREATE_PROJECT, CREATE_USER */];
break;
case 'secondary':
this.secondary = [];
this.secondary = [CREATE_ORG, CREATE_PROJECT, CREATE_USER];
// [LOGIN_POLICY, PRIVATELABEL_POLICY].map((p) => {
// const policy: string = {
// i18nTitle: p.i18nTitle,
@@ -194,7 +196,7 @@ export class ShortcutsComponent implements OnDestroy {
// });
break;
case 'third':
this.third = [];
this.third = this.ALL_SHORTCUTS.filter((item) => item.i18nTitle === 'SETTINGS.GROUPS.APPEARANCE');
// [LOGIN_TEXTS_POLICY, MESSAGE_TEXTS_POLICY].map((p) => {
// const policy: ShortcutItem = {
// i18nTitle: p.i18nTitle,
@@ -276,4 +278,8 @@ export class ShortcutsComponent implements OnDestroy {
public get allProjects(): ShortcutItem[] {
return this.all.filter((s) => s.type === ShortcutType.PROJECT);
}
public get allAvailableShortcuts(): ShortcutItem[] {
return [...this.allRoutes, ...this.allPolicies, ...this.allProjects];
}
}
@@ -1,30 +1,37 @@
<span class="title" mat-dialog-title>{{'USER.PERSONALACCESSTOKEN.ADDED.TITLE' | translate}}</span>
<span class="title" mat-dialog-title>{{ 'USER.PERSONALACCESSTOKEN.ADDED.TITLE' | translate }}</span>
<div mat-dialog-content>
<cnsl-info-section [type]="InfoSectionType.WARN"> {{'USER.PERSONALACCESSTOKEN.ADDED.DESCRIPTION' | translate}}
<cnsl-info-section [type]="InfoSectionType.WARN">
{{ 'USER.PERSONALACCESSTOKEN.ADDED.DESCRIPTION' | translate }}
</cnsl-info-section>
<ng-container *ngIf="tokenResponse">
<div class="row">
<p class="left">{{'USER.PERSONALACCESSTOKEN.ID' | translate}}</p>
<p class="right">{{tokenResponse.tokenId}}</p>
<p class="left cnsl-secondary-text">{{ 'USER.PERSONALACCESSTOKEN.ID' | translate }}</p>
<p class="right">{{ tokenResponse.tokenId }}</p>
</div>
<div class="row" *ngIf="tokenResponse.token">
<p class="left">{{'USER.PERSONALACCESSTOKEN.TOKEN' | translate}}</p>
<p class="left cnsl-secondary-text">{{ 'USER.PERSONALACCESSTOKEN.TOKEN' | translate }}</p>
<div class="right">
<button class="ctc" [disabled]="copied === tokenResponse.token"
[matTooltip]="(copied !== tokenResponse.token ? 'ACTIONS.COPY' : 'ACTIONS.COPIED' ) | translate"
cnslCopyToClipboard [valueToCopy]="tokenResponse.token" (copiedValue)="copied = $event" mat-icon-button>
<button
class="ctc"
[disabled]="copied === tokenResponse.token"
[matTooltip]="(copied !== tokenResponse.token ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="tokenResponse.token"
(copiedValue)="copied = $event"
mat-icon-button
>
<i *ngIf="copied !== tokenResponse.token" class="las la-clipboard"></i>
<i *ngIf="copied === tokenResponse.token" class="las la-clipboard-check"></i>
</button>
<span>{{tokenResponse.token}}</span>
<span>{{ tokenResponse.token }}</span>
</div>
</div>
</ng-container>
</div>
<div mat-dialog-actions class="action">
<button color="primary" mat-raised-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
{{ 'ACTIONS.CLOSE' | translate }}
</button>
</div>
</div>
@@ -28,7 +28,6 @@
}
.left {
color: var(--grey);
margin-right: 1rem;
margin-top: 0;
margin-bottom: 0.5rem;
@@ -69,7 +69,7 @@
padding: 0.75rem 0;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
display: flex;
align-items: center;
@@ -16,7 +16,7 @@
opacity: 0.6;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
&:not(:last-child) {
margin-right: 1rem;
@@ -1,6 +1,6 @@
<span class="title" mat-dialog-title>{{'ORG.DOMAINS.ADD.TITLE' | translate}}</span>
<span class="title" mat-dialog-title>{{ 'ORG.DOMAINS.ADD.TITLE' | translate }}</span>
<div mat-dialog-content>
<p class="desc"> {{'ORG.DOMAINS.ADD.DESCRIPTION' | translate}}</p>
<p class="desc cnsl-secondary-text">{{ 'ORG.DOMAINS.ADD.DESCRIPTION' | translate }}</p>
<cnsl-form-field label="Domain" required="true" class="form-field" appearance="outline">
<cnsl-label>Domain</cnsl-label>
@@ -9,11 +9,10 @@
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
{{'ACTIONS.CANCEL' | translate}}
{{ 'ACTIONS.CANCEL' | translate }}
</button>
<button color="primary" mat-raised-button class="ok-button" [disabled]="!newdomain"
(click)="closeDialogWithSuccess()">
{{'ACTIONS.ADD' | translate}}
<button color="primary" mat-raised-button class="ok-button" [disabled]="!newdomain" (click)="closeDialogWithSuccess()">
{{ 'ACTIONS.ADD' | translate }}
</button>
</div>
</div>
@@ -4,7 +4,6 @@
}
.desc {
color: var(--grey);
font-size: 0.9rem;
}
@@ -1,18 +1,34 @@
<div class="max-width-container">
<div class="enlarged-container">
<h1>{{ 'GRANTS.TITLE' | translate }}</h1>
<p class="desc cnsl-secondary-text">{{'GRANTS.DESC' | translate }}</p>
<cnsl-user-grants *ngIf="grantContext === UserGrantContext.NONE"
[displayedColumns]="['select', 'user', 'org', 'projectId','type', 'creationDate','changeDate', 'roleNamesList', 'actions']"
[disableWrite]="((['user.grant.write$'] | hasRole) | async) === false"
[disableDelete]="((['user.grant.delete$'] | hasRole) | async) === false"
[refreshOnPreviousRoutes]="['/grant-create']">
<p class="grants-desc cnsl-secondary-text">{{ 'GRANTS.DESC' | translate }}</p>
<cnsl-user-grants
*ngIf="grantContext === UserGrantContext.NONE"
[displayedColumns]="[
'select',
'user',
'org',
'projectId',
'type',
'creationDate',
'changeDate',
'roleNamesList',
'actions'
]"
[disableWrite]="(['user.grant.write$'] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$'] | hasRole | async) === false"
[refreshOnPreviousRoutes]="['/grant-create']"
>
</cnsl-user-grants>
<cnsl-user-grants *ngIf="grantContext === UserGrantContext.OWNED_PROJECT" [context]="UserGrantContext.OWNED_PROJECT"
[projectId]="projectId" [refreshOnPreviousRoutes]="['/grant-create/project/'+projectId]"
[disableWrite]="((['user.grant.write$', 'user.grant.write:'+projectId] | hasRole) | async) === false"
[disableDelete]="((['user.grant.delete$','user.grant.delete:'+projectId] | hasRole) | async) === false">
<cnsl-user-grants
*ngIf="grantContext === UserGrantContext.OWNED_PROJECT"
[context]="UserGrantContext.OWNED_PROJECT"
[projectId]="projectId"
[refreshOnPreviousRoutes]="['/grant-create/project/' + projectId]"
[disableWrite]="(['user.grant.write$', 'user.grant.write:' + projectId] | hasRole | async) === false"
[disableDelete]="(['user.grant.delete$', 'user.grant.delete:' + projectId] | hasRole | async) === false"
>
</cnsl-user-grants>
</div>
</div>
</div>
@@ -2,7 +2,7 @@ h1 {
margin: 0;
}
.desc {
margin-bottom: 2rem;
.grants-desc {
margin-bottom: 1rem;
font-size: 14px;
}
@@ -1,7 +1,7 @@
<cnsl-top-view
title="{{ app?.name }}"
[hasActions]="isZitadel === false && (['project.app.write:' + projectId, 'project.app.write'] | hasRole | async)"
docLink="https://docs.zitadel.ch/docs/guides/basics/projects"
docLink="https://docs.zitadel.com/docs/guides/basics/projects"
[sub]="app?.oidcConfig ? ('APP.OIDC.APPTYPE.' + app?.oidcConfig?.appType | translate) : 'API'"
[isActive]="app?.state === AppState.APP_STATE_ACTIVE"
[isInactive]="app?.state === AppState.APP_STATE_INACTIVE"
@@ -30,6 +30,11 @@
}
}
.redirect-section {
display: block;
margin-bottom: 1rem;
}
.app-desc {
font-size: 14px;
margin: 0;
@@ -131,6 +136,7 @@
display: flex;
align-items: center;
border: 1px solid if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2));
color: white;
border-radius: 0.5rem;
padding: 0.5rem 1rem;
margin-bottom: 1rem;
@@ -14,18 +14,11 @@
</cnsl-auth-method-radio>
</div>
<div mat-dialog-actions class="action">
<button color="primary" mat-stroked-button class="ok-button" (click)="closeDialog()">
<button color="primary" mat-stroked-button (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
<button
[disabled]="!authmethod"
cdkFocusInitial
color="primary"
mat-raised-button
class="ok-button"
(click)="closeDialogWithMethod()"
>
<button [disabled]="!authmethod" cdkFocusInitial color="primary" mat-raised-button (click)="closeDialogWithMethod()">
{{ 'ACTIONS.SELECT' | translate }}
</button>
</div>
@@ -13,11 +13,7 @@ h1 {
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: 0.5rem;
}
justify-content: space-between;
button {
border-radius: 0.5rem;
@@ -1,27 +1,40 @@
<form class="form" (ngSubmit)="add(redInput)" [attr.data-e2e]="'redirect-uris'">
<cnsl-form-field class="formfield">
<cnsl-label>{{ title }}</cnsl-label>
<form class="redirect-uris-form" (ngSubmit)="add(redInput)" [attr.data-e2e]="'redirect-uris'">
<cnsl-form-field class="formfield">
<cnsl-label>{{ title }}</cnsl-label>
<input #redInput cnslInput placeholder="ex. https://" [formControl]="redirectControl">
</cnsl-form-field>
<button matTooltip="{{'ACTIONS.ADD' | translate}}" type="submit" mat-icon-button
[disabled]="redirectControl.invalid || !canWrite">
<mat-icon>add</mat-icon>
</button>
<input #redInput cnslInput placeholder="ex. https://" [formControl]="redirectControl" />
</cnsl-form-field>
<button
matTooltip="{{ 'ACTIONS.ADD' | translate }}"
type="submit"
mat-icon-button
[disabled]="redirectControl.invalid || !canWrite"
>
<mat-icon>add</mat-icon>
</button>
</form>
<div class="uri-list">
<div *ngFor="let uri of urisList" class="uri-line">
<span class="uri"
[ngClass]="{'green': !devMode && uri?.startsWith('https://'), 'red': !devMode && !uri?.startsWith('https://')}">{{uri}}</span>
<span class="fill-space"></span>
<i *ngIf="!devMode && !(uri | redirect : isNative)" class="las la-exclamation red" [matTooltip]="isNative ? ('APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate) : ('APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate)"></i>
<div class="redirect-uris-list">
<div *ngFor="let uri of urisList" class="uri-line" [ngClass]="{ alert: !devMode && !(uri | redirect: isNative) }">
<span
class="uri"
[ngClass]="{ green: !devMode && uri?.startsWith('https://'), red: !devMode && !uri?.startsWith('https://') }"
>{{ uri }}</span
>
<span class="fill-space"></span>
<i
class="alerticon las la-exclamation red"
[matTooltip]="
isNative ? ('APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate) : ('APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate)
"
></i>
<button matTooltip="{{'ACTIONS.DELETE' | translate}}" mat-icon-button (click)="remove(uri)" class="icon-button">
<mat-icon class="icon">cancel</mat-icon>
</button>
</div>
<button matTooltip="{{ 'ACTIONS.DELETE' | translate }}" mat-icon-button (click)="remove(uri)" class="icon-button">
<mat-icon class="icon">cancel</mat-icon>
</button>
</div>
</div>
<p *ngIf="redirectControl.value && redirectControl.invalid" class="error">
{{'APP.OIDC.REDIRECTNOTVALID' | translate}}</p>
<p *ngIf="redirectControl.value && redirectControl.invalid" class="redirect-uris-error">
{{ 'APP.OIDC.REDIRECTNOTVALID' | translate }}
</p>
@@ -1,4 +1,80 @@
.form {
@use '@angular/material' as mat;
@mixin redirect-uris-theme($theme) {
$foreground: map-get($theme, foreground);
$background: map-get($theme, background);
$is-dark-theme: map-get($theme, is-dark);
$warn: map-get($theme, warn);
$warn-color: map-get($warn, 500);
$button-text-color: map-get($foreground, text);
$button-disabled-text-color: map-get($foreground, disabled-button);
$divider-color: map-get($foreground, dividers);
$secondary-text: map-get($foreground, secondary-text);
.redirect-uris-list {
width: 100%;
.uri-line {
display: flex;
align-items: center;
margin: 0.5rem 0;
padding: 0 0 0 0.75rem;
border-radius: 4px;
background: map-get($background, infosection);
.uri {
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 14px;
overflow-x: auto;
}
.fill-space {
flex: 1;
}
i.green {
font-size: 1rem;
line-height: 35px;
height: 30px;
}
.icon-button {
height: 30px;
line-height: 30px;
.icon {
font-size: 1rem;
margin-bottom: 3px;
}
&:not(:hover) {
color: $secondary-text;
}
}
.alerticon {
display: none;
}
&.alert {
background-color: map-get($background, alertinfosection);
color: map-get($foreground, alertinfosection);
.alerticon {
display: inline;
}
}
}
}
.redirect-uris-error {
font-size: 13px;
color: $warn-color;
margin: 0 0.5rem 1.5rem 0.5rem;
}
}
.redirect-uris-form {
display: flex;
align-items: flex-end;
min-width: 320px;
@@ -12,52 +88,3 @@
margin-right: -0.5rem;
}
}
.uri-list {
margin: 0 0.5rem;
width: 100%;
.uri-line {
display: flex;
align-items: center;
.uri {
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 14px;
overflow-x: auto;
}
.fill-space {
flex: 1;
}
i.green {
font-size: 1rem;
line-height: 35px;
height: 30px;
}
i.red {
font-size: 1.2rem;
}
.icon-button {
height: 30px;
line-height: 30px;
.icon {
font-size: 1rem !important;
}
&:not(:hover) {
color: var(--grey);
}
}
}
}
.error {
font-size: 13px;
color: var(--warn);
margin: 0 0.5rem 1.5rem 0.5rem;
}
@@ -11,7 +11,7 @@ import { ManagementService } from 'src/app/services/mgmt.service';
})
export class ApplicationGridComponent implements OnInit {
@Input() public projectId: string = '';
@Input() public disabled: boolean = false;
@Input() public disabled: boolean = true;
@Output() public changeView: EventEmitter<void> = new EventEmitter();
public appsSubject: BehaviorSubject<App.AsObject[]> = new BehaviorSubject<App.AsObject[]>([]);
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
@@ -6,7 +6,7 @@
[isInactive]="project?.state === ProjectState.PROJECT_STATE_INACTIVE"
[hasContributors]="true"
stateTooltip="{{ 'PROJECT.STATE.' + project?.state | translate }}"
[hasActions]="true"
[hasActions]="['project.app.write:' + project?.id, 'project.app.write$'] | hasRole | async"
>
<ng-template topActions cnslHasRole [hasRole]="['project.write:' + projectId, 'project.write']">
<button mat-menu-item (click)="openNameDialog()" aria-label="Edit project name" *ngIf="isZitadel === false">
@@ -68,8 +68,13 @@
<cnsl-meta-layout>
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList" [queryParam]="'id'">
<ng-container *ngIf="currentSetting === 'general' && project">
<ng-template cnslHasRole [hasRole]="['project.app.read:' + project.id, 'project.app.read']">
<cnsl-application-grid *ngIf="grid" [disabled]="isZitadel" (changeView)="grid = false" [projectId]="projectId">
<ng-template cnslHasRole [hasRole]="['project.app.read:' + project.id, 'project.app.read$']">
<cnsl-application-grid
*ngIf="grid"
[disabled]="isZitadel || (['project.app.write:' + project.id, 'project.app.write$'] | hasRole | async) === false"
(changeView)="grid = false"
[projectId]="projectId"
>
</cnsl-application-grid>
<cnsl-card *ngIf="!grid" title="{{ 'PROJECT.APP.TITLE' | translate }}">
<div class="card-actions" card-actions>
@@ -77,7 +82,12 @@
<i matTooltip="show grid view" class="las la-th-large"></i>
</button>
</div>
<cnsl-applications [disabled]="isZitadel" [projectId]="projectId"></cnsl-applications>
<cnsl-applications
[disabled]="
isZitadel || (['project.app.write:' + project.id, 'project.app.write$'] | hasRole | async) === false
"
[projectId]="projectId"
></cnsl-applications>
</cnsl-card>
</ng-template>
@@ -111,7 +121,9 @@
>
{{ 'PROJECT.ROLE.ASSERTION' | translate }}</mat-checkbox
>
<p class="desc cnsl-secondary-text">{{ 'PROJECT.ROLE.ASSERTION_DESCRIPTION' | translate }}</p>
<cnsl-info-section class="desc cnsl-secondary-text">{{
'PROJECT.ROLE.ASSERTION_DESCRIPTION' | translate
}}</cnsl-info-section>
<mat-checkbox
[(ngModel)]="project.projectRoleCheck"
[disabled]="(['project.write$', 'project.write:' + project.id] | hasRole | async) === false"
@@ -119,7 +131,9 @@
>
{{ 'PROJECT.ROLE.CHECK' | translate }}</mat-checkbox
>
<p class="desc cnsl-secondary-text">{{ 'PROJECT.ROLE.CHECK_DESCRIPTION' | translate }}</p>
<cnsl-info-section class="desc cnsl-secondary-text">{{
'PROJECT.ROLE.CHECK_DESCRIPTION' | translate
}}</cnsl-info-section>
<mat-checkbox
[(ngModel)]="project.hasProjectCheck"
[disabled]="(['project.write$', 'project.write:' + project.id] | hasRole | async) === false"
@@ -127,10 +141,19 @@
>
{{ 'PROJECT.HAS_PROJECT' | translate }}</mat-checkbox
>
<p class="desc cnsl-secondary-text">{{ 'PROJECT.HAS_PROJECT_DESCRIPTION' | translate }}</p>
<cnsl-info-section class="desc cnsl-secondary-text">{{
'PROJECT.HAS_PROJECT_DESCRIPTION' | translate
}}</cnsl-info-section>
<div class="project-detail-btn-container">
<button mat-raised-button color="primary" (click)="saveProject()">{{ 'ACTIONS.SAVE' | translate }}</button>
<button
mat-raised-button
[disabled]="(['project.app.write:' + project.id, 'project.app.write$'] | hasRole | async) === false"
color="primary"
(click)="saveProject()"
>
{{ 'ACTIONS.SAVE' | translate }}
</button>
</div>
</cnsl-card>
</ng-template>
@@ -55,5 +55,5 @@
.desc {
font-size: 14px;
margin-bottom: 2rem;
margin-bottom: 1rem;
}
@@ -19,6 +19,7 @@ import { CardModule } from 'src/app/modules/card/card.module';
import { ChangesModule } from 'src/app/modules/changes/changes.module';
import { ContributorsModule } from 'src/app/modules/contributors/contributors.module';
import { InfoRowModule } from 'src/app/modules/info-row/info-row.module';
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
import { PaginatorModule } from 'src/app/modules/paginator/paginator.module';
@@ -71,6 +72,7 @@ import { OwnedProjectDetailComponent } from './owned-project-detail.component';
TopViewModule,
MatCheckboxModule,
MatSelectModule,
InfoSectionModule,
MatMenuModule,
MatProgressSpinnerModule,
ChangesModule,
@@ -1,65 +1,104 @@
<cnsl-detail-layout [hasBackButton]="true" title="{{ 'PROJECT.GRANT.DETAIL.TITLE' | translate }}">
<p class="subinfo" sub>
<span class="cnsl-secondary-text">{{ 'PROJECT.GRANT.DETAIL.DESC' | translate }}</span>
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/projects#granted-organizations"
target="_blank">
<a
mat-icon-button
href="https://docs.zitadel.com/docs/concepts/structure/projects#granted-organizations"
target="_blank"
>
<i class="las la-info-circle"></i>
</a>
</p>
<div actions>
<button class="actions-trigger-desk" mat-raised-button color="primary" [matMenuTriggerFor]="actions">
<span>{{'ACTIONS.ACTIONS' | translate}}</span>
<span>{{ 'ACTIONS.ACTIONS' | translate }}</span>
<mat-icon class="icon">keyboard_arrow_down</mat-icon>
</button>
<button class="actions-trigger-mob" matTooltip="{{'ACTIONS.ACTIONS' | translate}}" mat-icon-button
[matMenuTriggerFor]="actions">
<button
class="actions-trigger-mob"
matTooltip="{{ 'ACTIONS.ACTIONS' | translate }}"
mat-icon-button
[matMenuTriggerFor]="actions"
>
<i class="las la-ellipsis-v"></i>
</button>
</div>
<mat-menu #actions="matMenu" xPosition="before">
<button mat-menu-item *ngIf="grant?.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE"
(click)="changeState(ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE)">{{'USER.PAGES.DEACTIVATE' |
translate}}</button>
<button mat-menu-item *ngIf="grant?.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE"
(click)="changeState(ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE)">{{'USER.PAGES.REACTIVATE' |
translate}}</button>
<button
mat-menu-item
*ngIf="grant?.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE"
(click)="changeState(ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE)"
>
{{ 'USER.PAGES.DEACTIVATE' | translate }}
</button>
<button
mat-menu-item
*ngIf="grant?.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE"
(click)="changeState(ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE)"
>
{{ 'USER.PAGES.REACTIVATE' | translate }}
</button>
</mat-menu>
<div class="master-row">
<div>
</div>
<div></div>
</div>
<cnsl-project-grant-illustration *ngIf="grant" [grantedProject]="grant" [projectRoleOptions]="projectRoleOptions"
(roleRemoved)="removeRole($event)" (editRoleClicked)="editRoles()">
<cnsl-project-grant-illustration
*ngIf="grant && projectRoleOptions"
[grantedProject]="grant"
[projectRoleOptions]="projectRoleOptions"
(roleRemoved)="removeRole($event)"
(editRoleClicked)="editRoles()"
>
</cnsl-project-grant-illustration>
<h2 class="project-grant-h2">{{ 'PROJECT.GRANT.DETAIL.MEMBERTITLE' | translate }}</h2>
<p class="desc max-width-description">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p>
<p class="desc cnsl-secondary-text max-width-description">{{ 'PROJECT.GRANT.DETAIL.MEMBERDESC' | translate }}</p>
<cnsl-members-table *ngIf="grant" [dataSource]="dataSource"
[canWrite]="['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async"
[canDelete]="['project.grant.member.delete','project.grant.member.delete:' + grant.grantId] | hasRole | async"
[memberRoleOptions]="memberRoleOptions" (updateRoles)="updateMemberRoles($event.member, $event.change)"
(deleteMember)="removeProjectMember($event)" [factoryLoadFunc]="changePageFactory"
(changedSelection)="selection = $event" [refreshTrigger]="changePage">
<button class="cnsl-action-button" selectactions (click)="removeProjectMemberSelection()"
[disabled]="(['project.grant.member.delete','project.grant.member.delete:' + grant.grantId] | hasRole | async) === false"
matTooltip="{{'ORG_DETAIL.TABLE.DELETE' | translate}}" color="warn" mat-raised-button>
<cnsl-members-table
*ngIf="grant"
[dataSource]="dataSource"
[canWrite]="['project.grant.member.write', 'project.grant.member.write:' + grant.grantId] | hasRole | async"
[canDelete]="['project.grant.member.delete', 'project.grant.member.delete:' + grant.grantId] | hasRole | async"
[memberRoleOptions]="memberRoleOptions"
(updateRoles)="updateMemberRoles($event.member, $event.change)"
(deleteMember)="removeProjectMember($event)"
[factoryLoadFunc]="changePageFactory"
(changedSelection)="selection = $event"
[refreshTrigger]="changePage"
>
<button
class="cnsl-action-button"
selectactions
(click)="removeProjectMemberSelection()"
[disabled]="
(['project.grant.member.delete', 'project.grant.member.delete:' + grant.grantId] | hasRole | async) === false
"
matTooltip="{{ 'ORG_DETAIL.TABLE.DELETE' | translate }}"
color="warn"
mat-raised-button
>
<i class="las la-trash"></i>
<span>{{'ACTIONS.SELECTIONDELETE' | translate}}</span>
<span>{{ 'ACTIONS.SELECTIONDELETE' | translate }}</span>
<cnsl-action-keys [type]="ActionKeysType.DELETE" (actionTriggered)="removeProjectMemberSelection()">
</cnsl-action-keys>
</button>
<button class="cnsl-action-button" writeactions color="primary"
[disabled]="(['project.grant.member.write','project.grant.member.write:' + grant.grantId] | hasRole | async) === false"
(click)="openAddMember()" color="primary" mat-raised-button>
<button
class="cnsl-action-button"
writeactions
color="primary"
[disabled]="
(['project.grant.member.write', 'project.grant.member.write:' + grant.grantId] | hasRole | async) === false
"
(click)="openAddMember()"
color="primary"
mat-raised-button
>
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
<cnsl-action-keys (actionTriggered)="openAddMember()">
</cnsl-action-keys>
<cnsl-action-keys (actionTriggered)="openAddMember()"> </cnsl-action-keys>
</button>
</cnsl-members-table>
</cnsl-detail-layout>
</cnsl-detail-layout>
@@ -3,17 +3,15 @@ import { MatDialog } from '@angular/material/dialog';
import { PageEvent } from '@angular/material/paginator';
import { ActivatedRoute } from '@angular/router';
import { ActionKeysType } from 'src/app/modules/action-keys/action-keys.component';
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { UserGrantRoleDialogComponent } from 'src/app/modules/user-grant-role-dialog/user-grant-role-dialog.component';
import { Member } from 'src/app/proto/generated/zitadel/member_pb';
import { GrantedProject, ProjectGrantState, Role } from 'src/app/proto/generated/zitadel/project_pb';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
import { ManagementService } from 'src/app/services/mgmt.service';
import { ToastService } from 'src/app/services/toast.service';
import {
ProjectGrantMembersCreateDialogComponent,
ProjectGrantMembersCreateDialogExportType,
} from './project-grant-members-create-dialog/project-grant-members-create-dialog.component';
import { ProjectGrantMembersDataSource } from './project-grant-members-datasource';
@Component({
@@ -69,25 +67,29 @@ export class ProjectGrantDetailComponent {
);
};
this.mgmtService.getProjectGrantByID(this.grantid, this.projectid).then((resp) => {
if (resp.projectGrant) {
this.grant = resp.projectGrant;
this.mgmtService
.getProjectGrantByID(this.grantid, this.projectid)
.then((resp) => {
console.log(resp);
if (resp.projectGrant) {
this.grant = resp.projectGrant;
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.ORG,
routerLink: ['/org'],
}),
new Breadcrumb({
type: BreadcrumbType.PROJECT,
name: '',
param: { key: 'projectid', value: resp.projectGrant.projectId },
routerLink: ['/projects', resp.projectGrant.projectId],
}),
];
this.breadcrumbService.setBreadcrumb(breadcrumbs);
}
});
const breadcrumbs = [
new Breadcrumb({
type: BreadcrumbType.ORG,
routerLink: ['/org'],
}),
new Breadcrumb({
type: BreadcrumbType.PROJECT,
name: '',
param: { key: 'projectid', value: resp.projectGrant.projectId },
routerLink: ['/projects', resp.projectGrant.projectId],
}),
];
this.breadcrumbService.setBreadcrumb(breadcrumbs);
}
})
.catch(this.toast.showError);
});
}
@@ -176,36 +178,35 @@ export class ProjectGrantDetailComponent {
}
public async openAddMember(): Promise<any> {
const keysList = await this.mgmtService.listProjectGrantMemberRoles();
const dialogRef = this.dialog.open(ProjectGrantMembersCreateDialogComponent, {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
data: {
roleKeysList: keysList.resultList,
creationType: CreationType.PROJECT_GRANTED,
},
width: '400px',
});
dialogRef.afterClosed().subscribe((dataToAdd: ProjectGrantMembersCreateDialogExportType) => {
if (dataToAdd) {
Promise.all(
dataToAdd.userIds.map((userid: string) => {
return this.mgmtService.addProjectGrantMember(
this.grant.projectId,
this.grant.grantId,
userid,
dataToAdd.rolesKeyList,
);
}),
)
.then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTMEMBERADDED', true);
setTimeout(() => {
this.changePage.emit();
}, 3000);
})
.catch((error) => {
this.toast.showError(error);
});
dialogRef.afterClosed().subscribe((resp) => {
if (resp) {
const users: User.AsObject[] = resp.users;
const roles: string[] = resp.roles;
if (users && users.length && roles && roles.length) {
const userIds = users.map((user) => user.id);
Promise.all(
userIds.map((userid: string) => {
return this.mgmtService.addProjectGrantMember(this.grant.projectId, this.grant.grantId, userid, resp.roles);
}),
)
.then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTMEMBERADDED', true);
setTimeout(() => {
this.changePage.emit();
}, 3000);
})
.catch((error) => {
this.toast.showError(error);
});
}
}
});
}
@@ -15,6 +15,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { TranslateModule } from '@ngx-translate/core';
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
import { ActionKeysModule } from 'src/app/modules/action-keys/action-keys.module';
import { MemberCreateDialogModule } from 'src/app/modules/add-member-dialog/member-create-dialog.module';
import { DetailLayoutModule } from 'src/app/modules/detail-layout/detail-layout.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { MembersTableModule } from 'src/app/modules/members-table/members-table.module';
@@ -25,16 +26,12 @@ import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.mod
import { ProjectGrantDetailRoutingModule } from './project-grant-detail-routing.module';
import { ProjectGrantDetailComponent } from './project-grant-detail.component';
import { ProjectGrantIllustrationComponent } from './project-grant-illustration/project-grant-illustration.component';
import {
ProjectGrantMembersCreateDialogModule,
} from './project-grant-members-create-dialog/project-grant-members-create-dialog.module';
@NgModule({
declarations: [ProjectGrantDetailComponent, ProjectGrantIllustrationComponent],
imports: [
CommonModule,
ProjectGrantDetailRoutingModule,
ProjectGrantMembersCreateDialogModule,
MatAutocompleteModule,
HasRoleModule,
MatChipsModule,
@@ -54,6 +51,7 @@ import {
TranslateModule,
MatSelectModule,
DetailLayoutModule,
MemberCreateDialogModule,
HasRolePipeModule,
MembersTableModule,
MatDialogModule,
@@ -13,7 +13,6 @@ export class ProjectGrantIllustrationComponent {
@Output() public editRoleClicked: EventEmitter<void> = new EventEmitter();
ProjectGrantState: any = ProjectGrantState;
constructor() {}
public removeRole(roleKey: string): void {
this.roleRemoved.emit(roleKey);
@@ -1,27 +0,0 @@
<h1 mat-dialog-title>
<span class="title">{{'MEMBER.ADD' | translate}}</span>
</h1>
<p class="desc"> {{'ORG_DETAIL.MEMBER.ADDDESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<cnsl-search-user-autocomplete (selectionChanged)="selectUsers($event)">
</cnsl-search-user-autocomplete>
<cnsl-form-field class="full-width" appearance="outline">
<cnsl-label>{{ 'PROJECT.MEMBER.ROLES' | translate }}</cnsl-label>
<mat-select [(ngModel)]="roleKeyList" multiple>
<mat-option *ngFor="let key of data.roleKeysList" [value]="key">
{{ key }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
{{'ACTIONS.CANCEL' | translate}}
</button>
<button [disabled]="userIds.length === 0 || roleKeyList.length === 0" color="primary" mat-raised-button
class="ok-button" (click)="closeDialogWithSuccess()">
{{'ACTIONS.ADD' | translate}}
</button>
</div>
@@ -1,12 +0,0 @@
.full-width {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: .5rem;
}
}
@@ -1,25 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ProjectGrantMembersCreateDialogComponent } from './project-grant-members-create-dialog.component';
describe('ProjectGrantMembersCreateDialogComponent', () => {
let component: ProjectGrantMembersCreateDialogComponent;
let fixture: ComponentFixture<ProjectGrantMembersCreateDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ProjectGrantMembersCreateDialogComponent],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ProjectGrantMembersCreateDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,42 +0,0 @@
import { Component, Inject } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { User } from 'src/app/proto/generated/zitadel/user_pb';
export interface ProjectGrantMembersCreateDialogExportType {
userIds: string[];
rolesKeyList: string[];
}
@Component({
selector: 'cnsl-project-grant-members-create-dialog',
templateUrl: './project-grant-members-create-dialog.component.html',
styleUrls: ['./project-grant-members-create-dialog.component.scss'],
})
export class ProjectGrantMembersCreateDialogComponent {
public form!: FormGroup;
public userIds: string[] = [];
public roleKeyList: string[] = [];
constructor(
public dialogRef: MatDialogRef<ProjectGrantMembersCreateDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) { }
public selectUsers(users: User.AsObject | User.AsObject[]): void {
if (Array.isArray(users)) {
this.userIds = users.map(user => user.id);
}
}
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
const exportData: ProjectGrantMembersCreateDialogExportType = {
userIds: this.userIds,
rolesKeyList: this.roleKeyList,
};
this.dialogRef.close(exportData);
}
}
@@ -1,27 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { MatDialogModule } from '@angular/material/dialog';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from 'src/app/modules/input/input.module';
import { SearchUserAutocompleteModule } from 'src/app/modules/search-user-autocomplete/search-user-autocomplete.module';
import { ProjectGrantMembersCreateDialogComponent } from './project-grant-members-create-dialog.component';
@NgModule({
declarations: [ProjectGrantMembersCreateDialogComponent],
imports: [
CommonModule,
FormsModule,
MatDialogModule,
MatButtonModule,
TranslateModule,
MatSelectModule,
InputModule,
SearchUserAutocompleteModule,
],
})
export class ProjectGrantMembersCreateDialogModule { }
@@ -1,4 +1,9 @@
<cnsl-create-layout title="{{ 'PROJECT.PAGES.CREATE' | translate }}" [createSteps]="1" [currentCreateStep]="1">
<cnsl-create-layout
title="{{ 'PROJECT.PAGES.CREATE' | translate }}"
[createSteps]="1"
[currentCreateStep]="1"
(closed)="close()"
>
<h1>{{ 'PROJECT.PAGES.CREATE_DESC' | translate }}</h1>
<form cdkFocusRegionStart (ngSubmit)="saveProject()">
<div class="column">
@@ -14,7 +14,7 @@
}
.sub {
margin-bottom: 2rem;
margin-bottom: 1.5rem;
font-size: 14px;
}
@@ -38,7 +38,7 @@
opacity: 0.6;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
&:first-child {
margin-right: 1rem;
@@ -17,7 +17,7 @@
<div class="max-width-container">
<cnsl-meta-layout>
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList">
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList" queryParam="id">
<ng-container *ngIf="currentSetting === 'general'">
<cnsl-card
*ngIf="user && user.human && user.human.profile"
@@ -28,7 +28,7 @@
opacity: 0.6;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
display: flex;
align-items: center;
@@ -2,8 +2,9 @@ import { MediaMatcher } from '@angular/cdk/layout';
import { Location } from '@angular/common';
import { Component, EventEmitter, OnDestroy } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Params } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs';
import { Subscription, take } from 'rxjs';
import { ChangeType } from 'src/app/modules/changes/changes.component';
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
import { UserGrantContext } from 'src/app/modules/user-grants/user-grants-datasource';
@@ -57,7 +58,15 @@ export class AuthUserDetailComponent implements OnDestroy {
private breadcrumbService: BreadcrumbService,
private mediaMatcher: MediaMatcher,
private _location: Location,
activatedRoute: ActivatedRoute,
) {
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.currentSetting = id;
}
});
const mediaq: string = '(max-width: 500px)';
const small = this.mediaMatcher.matchMedia(mediaq).matches;
if (small) {
@@ -1,42 +1,55 @@
<div mat-dialog-title class="title-row">
<h1 class="title">{{'USER.METADATA.TITLE' | translate}}</h1>
<div class="title-row">
<h1 class="metadata-title">{{ 'USER.METADATA.TITLE' | translate }}</h1>
<span class="fill-space"></span>
<p *ngIf="ts" class="ts cnsl-secondary-text">{{ts | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</p>
<p *ngIf="ts" class="ts cnsl-secondary-text">{{ ts | timestampToDate | localizedDate: 'dd. MMM, HH:mm' }}</p>
<mat-spinner *ngIf="loading" diameter="20"></mat-spinner>
<button class="icon-button" mat-icon-button (click)="load()">
<mat-icon class="icon">refresh</mat-icon>
</button>
</div>
<p class="desc">{{'USER.METADATA.DESCRIPTION' | translate}}</p>
<div mat-dialog-content>
<p class="desc">{{ 'USER.METADATA.DESCRIPTION' | translate }}</p>
<div mat-dialog-content class="metadata-dialog-content">
<form *ngFor="let md of metadata; index as i" (ngSubmit)="saveElement(i)">
<div class="content">
<cnsl-form-field #key id="key{{i}}" class="formfield">
<cnsl-form-field #key id="key{{ i }}" class="formfield">
<cnsl-label>{{ 'USER.METADATA.KEY' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="md.key" [ngModelOptions]="{standalone: true}" />
<input cnslInput [(ngModel)]="md.key" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>
<cnsl-form-field #value id="value{{i}}" class="formfield">
<cnsl-form-field #value id="value{{ i }}" class="formfield">
<cnsl-label>{{ 'USER.METADATA.VALUE' | translate }}</cnsl-label>
<input cnslInput [(ngModel)]="md.value" [ngModelOptions]="{standalone: true}" />
<input cnslInput [(ngModel)]="md.value" [ngModelOptions]="{ standalone: true }" />
</cnsl-form-field>
<button mat-icon-button [disabled]="!(md.key && md.value)" class="set-button" type="submit" color="primary"
matTooltip="{{ 'ACTIONS.SAVE' | translate }}">
<button
mat-icon-button
[disabled]="!(md.key && md.value)"
class="set-button"
type="submit"
color="primary"
matTooltip="{{ 'ACTIONS.SAVE' | translate }}"
>
<i class="las la-save"></i>
</button>
<button mat-icon-button (click)="removeEntry(i)" [disabled]="metadata.length < 2 && i === 0 && !md.key"
class="rm-button" type="button" color="warn" matTooltip="{{ 'ACTIONS.REMOVE' | translate }}">
<button
mat-icon-button
(click)="removeEntry(i)"
[disabled]="metadata.length < 2 && i === 0 && !md.key"
class="rm-button"
type="button"
color="warn"
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
>
<i class="las la-trash"></i>
</button>
</div>
</form>
<button color="primary" (click)="addEntry()" mat-stroked-button color="primary" class="continue-button" type="button">
<button (click)="addEntry()" mat-stroked-button class="continue-button" type="button">
<mat-icon>add</mat-icon>
{{ 'ACTIONS.ADD' | translate }}
</button>
</div>
<div mat-dialog-actions class="action">
<button cdkFocusInitial color="primary" mat-stroked-button class="ok-button" (click)="closeDialog()">
{{'ACTIONS.CLOSE' | translate}}
<button cdkFocusInitial mat-stroked-button class="ok-button" (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
</div>
</div>
@@ -2,8 +2,8 @@
display: flex;
align-items: center;
.title {
font-size: 1.5rem;
.metadata-title {
font-size: 1.3rem;
margin: 0;
}
@@ -23,23 +23,27 @@
}
}
.content {
display: flex;
align-items: center;
margin: 0 -0.5rem;
.metadata-dialog-content {
min-width: 400px;
.formfield {
flex: 1;
margin: 0 0.5rem;
.content {
display: flex;
align-items: center;
margin: 0 -0.5rem;
@media only screen and (max-width: 450px) {
flex-basis: 100%;
.formfield {
flex: 1;
margin: 0 0.5rem;
@media only screen and (max-width: 450px) {
flex-basis: 100%;
}
}
}
.rm-button,
.set-button {
margin-top: 14px;
.rm-button,
.set-button {
margin-top: 14px;
}
}
}
@@ -1,22 +1,17 @@
<cnsl-card class="metadata-details" title="{{ 'USER.METADATA.TITLE' | translate }}">
<div card-actions class="actions">
<div class="metadata-actions">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<button mat-raised-button color="primary" class="edit" (click)="editMetadata()">{{'ACTIONS.EDIT' |
translate}}</button>
<button matTooltip="{{'ACTIONS.REFRESH' | translate}}" class="refresh-btn" (click)="loadMetadata()" mat-icon-button
aria-label="refresh contributors">
<mat-icon class="icon">refresh</mat-icon>
</button>
<button mat-raised-button color="primary" class="edit" (click)="editMetadata()">{{ 'ACTIONS.EDIT' | translate }}</button>
</div>
<ng-container *ngIf="metadata?.length; else emptyList">
<div class="metadata-set" *ngFor="let md of metadata">
<span class="first cnsl-secondary-text">{{md.key}}</span>
<span class="first cnsl-secondary-text">{{ md.key }}</span>
<span class="second">{{ md.value }}</span>
</div>
</ng-container>
<ng-template #emptyList>
<p class="empty-desc cnsl-secondary-text">{{'USER.METADATA.EMPTY' | translate}}</p>
<p class="empty-desc cnsl-secondary-text">{{ 'USER.METADATA.EMPTY' | translate }}</p>
</ng-template>
</cnsl-card>
</cnsl-card>
@@ -1,9 +1,10 @@
.metadata-details {
padding-bottom: 1rem;
.actions {
.metadata-actions {
display: flex;
align-items: center;
justify-content: flex-end;
.edit {
font-size: 14px;
@@ -53,6 +54,7 @@
.empty-desc {
margin: 0;
font-size: 14px;
margin-bottom: 0.5rem;
}
.refresh-btn {
@@ -16,8 +16,7 @@ export class MetadataComponent implements OnInit {
public metadata: Metadata.AsObject[] = [];
public loading: boolean = false;
constructor(private dialog: MatDialog, private service: ManagementService, private toast: ToastService,
) { }
constructor(private dialog: MatDialog, private service: ManagementService, private toast: ToastService) {}
ngOnInit(): void {
this.loadMetadata();
@@ -37,17 +36,20 @@ export class MetadataComponent implements OnInit {
public loadMetadata(): Promise<any> {
this.loading = true;
return (this.service as ManagementService).listUserMetadata(this.userId).then(resp => {
this.loading = false;
this.metadata = resp.resultList.map(md => {
return {
key: md.key,
value: atob(md.value as string),
};
return (this.service as ManagementService)
.listUserMetadata(this.userId)
.then((resp) => {
this.loading = false;
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: atob(md.value as string),
};
});
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}).catch((error) => {
this.loading = false;
this.toast.showError(error);
});
}
}
@@ -37,7 +37,9 @@
</cnsl-top-view>
<div *ngIf="loading" class="max-width-container">
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
<div class="sp-wrapper">
<mat-progress-spinner diameter="25" color="primary" mode="indeterminate"></mat-progress-spinner>
</div>
</div>
<div *ngIf="!loading && !user" class="max-width-container">
@@ -46,7 +48,7 @@
<div class="max-width-container" *ngIf="user && (['user.write$', 'user.write:' + user.id] | hasRole) as canWrite$">
<cnsl-meta-layout>
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList">
<cnsl-sidenav [(ngModel)]="currentSetting" [settingsList]="settingsList" queryParam="id">
<div *ngIf="error" class="max-width-container">
<p>{{ error }}</p>
</div>
@@ -42,3 +42,7 @@
.resendemail {
margin-top: 0.5rem;
}
.sp-wrapper {
margin: 1rem 0;
}
@@ -65,9 +65,17 @@ export class UserDetailComponent implements OnInit {
private _location: Location,
private dialog: MatDialog,
private router: Router,
activatedRoute: ActivatedRoute,
private mediaMatcher: MediaMatcher,
breadcrumbService: BreadcrumbService,
) {
activatedRoute.queryParams.pipe(take(1)).subscribe((params: Params) => {
const { id } = params;
if (id) {
this.currentSetting = id;
}
});
breadcrumbService.setBreadcrumb([
new Breadcrumb({
type: BreadcrumbType.ORG,
@@ -3,14 +3,17 @@
<ng-container *ngSwitchCase="Type.TYPE_HUMAN">
<div class="users-title-row">
<h1>{{ 'USER.PAGES.LIST' | translate }}</h1>
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/users" rel="noreferrer"
target="_blank">
<a mat-icon-button href="https://docs.zitadel.com/docs/concepts/structure/users" rel="noreferrer" target="_blank">
<i class="las la-info-circle"></i>
</a>
</div>
<p class="user-list-sub cnsl-secondary-text">{{ 'USER.PAGES.DESCRIPTION' | translate }}</p>
<cnsl-user-table [type]="Type.TYPE_HUMAN" [disabled]="(['user.write$'] | hasRole | async) === false">
<cnsl-user-table
[type]="Type.TYPE_HUMAN"
[canWrite]="!!(['user.write$'] | hasRole | async)"
[canDelete]="!!(['user.delete$'] | hasRole | async)"
>
</cnsl-user-table>
</ng-container>
@@ -18,8 +21,12 @@
<h1>{{ 'USER.PAGES.LISTMACHINE' | translate }}</h1>
<p class="user-list-sub cnsl-secondary-text">{{ 'USER.PAGES.DESCRIPTIONMACHINE' | translate }}</p>
<cnsl-user-table [type]="Type.TYPE_MACHINE" [disabled]="(['user.write$'] | hasRole | async) === false">
<cnsl-user-table
[type]="Type.TYPE_MACHINE"
[canWrite]="!!(['user.write$'] | hasRole | async)"
[canDelete]="!!(['user.delete$'] | hasRole | async)"
>
</cnsl-user-table>
</ng-container>
</div>
</div>
</div>
@@ -14,6 +14,6 @@
}
.user-list-sub {
margin-bottom: 2rem;
margin-bottom: 1.5rem;
font-size: 14px;
}
@@ -1,35 +1,64 @@
<cnsl-refresh-table [loading]="loading$ | async" (refreshed)="refreshPage()" [dataSize]="totalResult"
[hideRefresh]="true" [timestamp]="viewTimestamp" [selection]="selection"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes" [showBorder]="true">
<cnsl-refresh-table
[loading]="loading$ | async"
(refreshed)="refreshPage()"
[dataSize]="totalResult"
[hideRefresh]="true"
[timestamp]="viewTimestamp"
[selection]="selection"
[emitRefreshOnPreviousRoutes]="refreshOnPreviousRoutes"
[showBorder]="true"
>
<div leftActions class="user-table-left-actions">
<button class="type-button" [ngClass]="{'active': type === Type.TYPE_HUMAN}"
(click)="setType(Type.TYPE_HUMAN)">{{'USER.TABLE.TYPES.HUMAN' | translate}}</button>
<button class="type-button" [ngClass]="{'active': type === Type.TYPE_MACHINE}"
(click)="setType(Type.TYPE_MACHINE)">{{'USER.TABLE.TYPES.MACHINE' | translate}}</button>
<button class="type-button" [ngClass]="{ active: type === Type.TYPE_HUMAN }" (click)="setType(Type.TYPE_HUMAN)">
{{ 'USER.TABLE.TYPES.HUMAN' | translate }}
</button>
<button class="type-button" [ngClass]="{ active: type === Type.TYPE_MACHINE }" (click)="setType(Type.TYPE_MACHINE)">
{{ 'USER.TABLE.TYPES.MACHINE' | translate }}
</button>
</div>
<ng-template cnslHasRole [hasRole]="['user.write']" actions>
<button (click)="deactivateSelectedUsers()" class="cnsl-action-button bg-state inactive" mat-raised-button
*ngIf="selection.hasValue() && multipleDeactivatePossible" [disabled]="disabled" color="primary">
<span class="">{{'USER.TABLE.DEACTIVATE' | translate}}</span>
<cnsl-action-keys (actionTriggered)="deactivateSelectedUsers()" [type]="ActionKeysType.DEACTIVATE">
</cnsl-action-keys>
<button
(click)="deactivateSelectedUsers()"
class="cnsl-action-button bg-state inactive"
mat-raised-button
*ngIf="selection.hasValue() && multipleDeactivatePossible"
[disabled]="!canWrite"
color="primary"
>
<span class="">{{ 'USER.TABLE.DEACTIVATE' | translate }}</span>
<cnsl-action-keys (actionTriggered)="deactivateSelectedUsers()" [type]="ActionKeysType.DEACTIVATE"> </cnsl-action-keys>
</button>
<button (click)="reactivateSelectedUsers()" class="cnsl-action-button bg-state active margin-left" mat-raised-button
*ngIf="selection.hasValue() && multipleActivatePossible" [disabled]="disabled" color="primary">
<span class="">{{'USER.TABLE.ACTIVATE' | translate}}</span>
<cnsl-action-keys (actionTriggered)="reactivateSelectedUsers()" [type]="ActionKeysType.REACTIVATE">
</cnsl-action-keys>
<button
(click)="reactivateSelectedUsers()"
class="cnsl-action-button bg-state active margin-left"
mat-raised-button
*ngIf="selection.hasValue() && multipleActivatePossible"
[disabled]="!canWrite"
color="primary"
>
<span class="">{{ 'USER.TABLE.ACTIVATE' | translate }}</span>
<cnsl-action-keys (actionTriggered)="reactivateSelectedUsers()" [type]="ActionKeysType.REACTIVATE"> </cnsl-action-keys>
</button>
<cnsl-filter-user *ngIf="!selection.hasValue()" (filterChanged)="applySearchQuery($any($event))"
(filterOpen)="filterOpen = $event"></cnsl-filter-user>
<a *ngIf="!selection.hasValue()" [routerLink]="[ '/users',type === Type.TYPE_HUMAN ? 'create' : 'create-machine']"
color="primary" mat-raised-button [disabled]="disabled" class="cnsl-action-button">
<cnsl-filter-user
*ngIf="!selection.hasValue()"
(filterChanged)="applySearchQuery($any($event))"
(filterOpen)="filterOpen = $event"
></cnsl-filter-user>
<a
*ngIf="!selection.hasValue()"
[routerLink]="['/users', type === Type.TYPE_HUMAN ? 'create' : 'create-machine']"
color="primary"
mat-raised-button
[disabled]="!canWrite"
class="cnsl-action-button"
>
<mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span>
<cnsl-action-keys *ngIf="!filterOpen"
(actionTriggered)="gotoRouterLink([ '/users',type === Type.TYPE_HUMAN ? 'create' : 'create-machine'])">
<cnsl-action-keys
*ngIf="!filterOpen"
(actionTriggered)="gotoRouterLink(['/users', type === Type.TYPE_HUMAN ? 'create' : 'create-machine'])"
>
</cnsl-action-keys>
</a>
</ng-template>
@@ -38,19 +67,37 @@
<table class="table" mat-table [dataSource]="dataSource" matSort (matSortChange)="sortChange($event)">
<ng-container matColumnDef="select">
<th mat-header-cell *matHeaderCellDef class="selection">
<mat-checkbox [disabled]="disabled" color="primary" (change)="$event ? masterToggle() : null"
<mat-checkbox
[disabled]="!canWrite"
color="primary"
(change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
[indeterminate]="selection.hasValue() && !isAllSelected()"
>
</mat-checkbox>
</th>
<td mat-cell *matCellDef="let user" class="selection">
<mat-checkbox [disabled]="disabled" color="primary" (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null" [checked]="selection.isSelected(user)">
<mat-checkbox
[disabled]="!canWrite"
color="primary"
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(user) : null"
[checked]="selection.isSelected(user)"
>
<cnsl-avatar
*ngIf="user.human && user.human.profile.displayName && user.human?.profile.firstName && user.human?.profile.lastName; else cog"
class="avatar" [name]="user.human.profile.displayName" [avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName" [size]="32">
*ngIf="
user.human &&
user.human.profile.displayName &&
user.human?.profile.firstName &&
user.human?.profile.lastName;
else cog
"
class="avatar"
[name]="user.human.profile.displayName"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[forColor]="user?.preferredLoginName"
[size]="32"
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar [forColor]="user?.preferredLoginName" [isMachine]="true">
@@ -62,58 +109,74 @@
</ng-container>
<ng-container matColumnDef="displayName">
<th mat-header-cell *matHeaderCellDef mat-sort-header
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.DISPLAY_NAME}">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
[ngClass]="{ 'search-active': this.userSearchKey === UserListSearchKey.DISPLAY_NAME }"
>
{{ 'USER.PROFILE.DISPLAYNAME' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span *ngIf="user.human">{{user.human.profile?.displayName}}</span>
<span *ngIf="user.machine">{{user.machine.name}}</span>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span *ngIf="user.human">{{ user.human.profile?.displayName }}</span>
<span *ngIf="user.machine">{{ user.machine.name }}</span>
</td>
</ng-container>
<ng-container matColumnDef="username">
<th mat-header-cell *matHeaderCellDef mat-sort-header
[ngClass]="{'search-active': this.userSearchKey === UserListSearchKey.USER_NAME}">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
[ngClass]="{ 'search-active': this.userSearchKey === UserListSearchKey.USER_NAME }"
>
{{ 'USER.PROFILE.USERNAME' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
{{user.userName}} </td>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
{{ user.userName }}
</td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header
[ngClass]="{'search-active': this.UserListSearchKey === UserListSearchKey.EMAIL}">
<th
mat-header-cell
*matHeaderCellDef
mat-sort-header
[ngClass]="{ 'search-active': this.UserListSearchKey === UserListSearchKey.EMAIL }"
>
{{ 'USER.EMAIL' | translate }}
</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span *ngIf="user.human?.email?.email">{{user.human?.email.email}}</span>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span *ngIf="user.human?.email?.email">{{ user.human?.email.email }}</span>
</td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{ 'USER.DATA.STATE' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="state"
[ngClass]="{'active': user.state === UserState.USER_STATE_ACTIVE, 'inactive': user.state === UserState.USER_STATE_INACTIVE}">
{{ 'USER.DATA.STATE'+user.state | translate }}
<th mat-header-cell *matHeaderCellDef mat-sort-header>{{ 'USER.DATA.STATE' | translate }}</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span
class="state"
[ngClass]="{
active: user.state === UserState.USER_STATE_ACTIVE,
inactive: user.state === UserState.USER_STATE_INACTIVE
}"
>
{{ 'USER.DATA.STATE' + user.state | translate }}
</span>
</td>
</ng-container>
<ng-container matColumnDef="creationDate">
<th mat-header-cell *matHeaderCellDef> {{'USER.TABLE.CREATIONDATE' | translate}} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="no-break">{{user.details.creationDate | timestampToDate | localizedDate: 'fromNow'
}}</span>
<th mat-header-cell *matHeaderCellDef>{{ 'USER.TABLE.CREATIONDATE' | translate }}</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span class="no-break">{{ user.details.creationDate | timestampToDate | localizedDate: 'fromNow' }}</span>
</td>
</ng-container>
<ng-container matColumnDef="changeDate">
<th mat-header-cell *matHeaderCellDef> {{ 'USER.TABLE.CHANGEDATE' | translate }} </th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id ]: null">
<span class="no-break">{{user.details.changeDate | timestampToDate | localizedDate: 'fromNow'
}}</span>
<th mat-header-cell *matHeaderCellDef>{{ 'USER.TABLE.CHANGEDATE' | translate }}</th>
<td mat-cell *matCellDef="let user" [routerLink]="user.id ? ['/users', user.id] : null">
<span class="no-break">{{ user.details.changeDate | timestampToDate | localizedDate: 'fromNow' }}</span>
</td>
</ng-container>
@@ -121,28 +184,41 @@
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
<td mat-cell *matCellDef="let user" class="user-tr-actions">
<cnsl-table-actions>
<button actions matTooltip="{{'ACTIONS.REMOVE' | translate}}" color="warn" (click)="deleteUser(user)"
mat-icon-button>
<button
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
color="warn"
(click)="deleteUser(user)"
[disabled]="!canWrite || !canDelete"
mat-icon-button
>
<i class="las la-trash"></i>
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine">
</tr>
<tr class="highlight pointer" mat-row
*matRowDef="let user; columns: (type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine);">
</tr>
<tr mat-header-row *matHeaderRowDef="type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine"></tr>
<tr
class="highlight pointer"
mat-row
*matRowDef="let user; columns: type === Type.TYPE_HUMAN ? displayedColumnsHuman : displayedColumnsMachine"
></tr>
</table>
</div>
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{'USER.TABLE.EMPTY' | translate}}</span>
<span>{{ 'USER.TABLE.EMPTY' | translate }}</span>
</div>
<cnsl-paginator #paginator class="paginator" [timestamp]="viewTimestamp" [length]="totalResult || 0"
[pageSize]="INITIAL_PAGE_SIZE" [timestamp]="viewTimestamp" [pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"></cnsl-paginator>
</cnsl-refresh-table>
<cnsl-paginator
#paginator
class="paginator"
[timestamp]="viewTimestamp"
[length]="totalResult || 0"
[pageSize]="INITIAL_PAGE_SIZE"
[timestamp]="viewTimestamp"
[pageSizeOptions]="[10, 20, 50, 100]"
(page)="changePage($event)"
></cnsl-paginator>
</cnsl-refresh-table>
@@ -16,7 +16,7 @@
opacity: 0.6;
font-size: 15px;
cursor: pointer;
color: map-get($foreground, base);
color: map-get($foreground, text);
&:first-child {
margin-right: 1rem;
@@ -37,7 +37,7 @@ export class UserTableComponent implements OnInit {
public Type: any = Type;
@Input() public type: Type = Type.TYPE_HUMAN;
@Input() refreshOnPreviousRoutes: string[] = [];
@Input() disabled: boolean = false;
@Input() canWrite: boolean = false;
@ViewChild(PaginatorComponent) public paginator!: PaginatorComponent;
@ViewChild(MatSort) public sort!: MatSort;
public INITIAL_PAGE_SIZE: number = 20;
@@ -77,6 +77,7 @@ export class UserTableComponent implements OnInit {
public filterOpen: boolean = false;
private searchQueries: SearchQuery[] = [];
@Input() public canDelete: boolean = false;
constructor(
private router: Router,
public translate: TranslateService,
@@ -1,37 +1,36 @@
import { Location } from '@angular/common';
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { Router } from '@angular/router';
import { v4 as uuidv4 } from 'uuid';
export abstract class StatehandlerProcessorService {
public abstract createState(url: string): Observable<string | undefined>;
public abstract createState(url: string): string;
public abstract restoreState(state?: string): void;
}
@Injectable()
export class StatehandlerProcessorServiceImpl implements StatehandlerProcessorService {
constructor(private location: Location) {}
constructor(private location: Location, private router: Router) {}
public createState(url: string): Observable<string> {
public createState(url: string): string {
const externalUrl = this.location.prepareExternalUrl(url);
const urlId = uuidv4();
sessionStorage.setItem(urlId, externalUrl);
return of(urlId);
return urlId;
}
public restoreState(state?: string): void {
if (state === undefined) {
return;
} else {
const url = sessionStorage.getItem(state);
if (url === null) {
return;
} else {
sessionStorage.removeItem(state);
// window.location.replace(window.location.origin + url);
this.router.navigate([url]);
}
}
const url = sessionStorage.getItem(state);
if (url === null) {
return;
}
sessionStorage.removeItem(state);
window.location.href = window.location.origin + url;
}
}
@@ -23,7 +23,9 @@ export class StatehandlerServiceImpl implements StatehandlerService, OnDestroy {
map(() => oauthService.state),
takeUntil(this.unsubscribe$),
)
.subscribe((state) => processor.restoreState(state));
.subscribe((state) => {
processor.restoreState(state);
});
}
public initStateHandler(): void {
@@ -47,9 +49,11 @@ export class StatehandlerServiceImpl implements StatehandlerService, OnDestroy {
switchMap((url: string) => {
if (url.includes('?login_hint=')) {
const newUrl = this.removeParam('login_hint', url);
return of(newUrl);
return of(this.processor.createState(newUrl));
} else if (url) {
return of(this.processor.createState(url));
} else {
return this.processor.createState(url);
return of(undefined);
}
}),
);
+2 -1
View File
@@ -57,9 +57,10 @@ export class ThemeService {
}
public saveTextColor(colorHex: string, isDark: boolean): void {
this.primaryColorPalette = this.computeColors(colorHex);
const theme = isDark ? 'dark' : 'light';
document.documentElement.style.setProperty(`--theme-${theme}-${'text'}`, colorHex);
const secondaryTextHex = tinycolor(colorHex).setAlpha(0.78).toHex8String();
document.documentElement.style.setProperty(`--theme-${theme}-${'secondary-text'}`, secondaryTextHex);
}
private computeColors(hex: string): Color[] {