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

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'],
},
},
{

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;

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();

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -44,7 +44,7 @@
display: flex;
flex-direction: column;
width: 100%;
color: map-get($foreground, base);
color: map-get($foreground, text);
&.top-left {
&::before {

View File

@@ -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;

View File

@@ -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()">
<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)">
<button [disabled]="!name" cdkFocusInitial color="primary" mat-raised-button (click)="closeDialog(name)">
{{ 'ACTIONS.RENAME' | translate }}
</button>
</div>

View File

@@ -15,10 +15,6 @@ h1 {
display: flex;
justify-content: space-between;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}

View File

@@ -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 }}
</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>

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
<h1 mat-dialog-title class="title">
<h1 class="title">
<span>{{ data.title | translate }}</span>
</h1>
<div mat-dialog-content>

View File

@@ -1,9 +1,10 @@
.title {
font-size: 1.5rem;
font-size: 1.3rem;
}
.desc {
font-size: 14px;
margin-top: 0;
}
.form-field {

View File

@@ -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 {

View File

@@ -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>

View File

@@ -35,7 +35,7 @@
display: block;
&:not(:hover) {
color: var(--grey);
color: map-get($foreground, secondary-text);
}
}
}

View File

@@ -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;
}
}

View File

@@ -122,7 +122,6 @@ export class LoginPolicyComponent implements OnInit {
)
.pipe(take(1))
.subscribe((allowed) => {
console.log(allowed);
if (allowed) {
this.lifetimeForm.enable();
}

View File

@@ -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"

View File

@@ -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;

View File

@@ -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> -->

View File

@@ -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"

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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()"

View File

@@ -93,6 +93,11 @@
.pl-action-button {
align-self: flex-start;
margin-top: 0.5rem;
margin-right: 0.5rem;
&:last-child {
margin-right: 0;
}
}
}

View File

@@ -1,7 +1,7 @@
<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>
</mat-radio-button>
@@ -9,12 +9,17 @@
<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()">
<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)">
<button
[disabled]="setting === undefined"
cdkFocusInitial
color="primary"
mat-raised-button
(click)="closeDialog(setting)"
>
{{ 'ACTIONS.OK' | translate }}
</button>
</div>

View File

@@ -7,6 +7,10 @@
font-size: 0.9rem;
}
.project-radio-group {
display: flex;
flex-direction: column;
.radio-button {
margin: 0.5rem 0;
@@ -14,6 +18,7 @@
white-space: normal;
}
}
}
.info {
margin: 1rem 0;
@@ -24,10 +29,6 @@
display: flex;
justify-content: space-between;
.ok-button {
margin-left: 0.5rem;
}
button {
border-radius: 0.5rem;
}

View File

@@ -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="example@externaldomain.com" />
<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>

View File

@@ -1,42 +1,13 @@
.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-create-form-field {
flex: 1;
&.more-space {
margin-bottom: 1rem;
}
}
.line {
display: flex;
max-width: 500px;
button {
margin-top: 30px;
}
}
.sm-dlt {
cursor: pointer;
font-size: 0.8rem;
}
.found {
.user-autocomplete-found {
margin: 0.5rem 0;
border-radius: 0.5rem;
padding: 0.5rem 0;
@@ -85,11 +56,50 @@
transition: color 0.2s ease;
&:not(:hover) {
color: var(--grey);
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;
}
}
}
}
.user-create-form-field {
flex: 1;
&.more-space {
margin-bottom: 1rem;
}
}
.line {
display: flex;
max-width: 500px;
button {
margin-top: 30px;
}
}
.sm-dlt {
cursor: pointer;
font-size: 0.8rem;
}
.circle {
margin-right: 0.5rem;
}

View File

@@ -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',
};

View File

@@ -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,
);
}
}

View File

@@ -2,10 +2,14 @@
<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>
<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>
<mat-icon *ngIf="editState">close</mat-icon>
<i *ngIf="editState" class="las la-check"></i>
</button>
<button matTooltip="{{ 'ACTIONS.RESETDEFAULT' | translate }}" (click)="reset()" *ngIf="editState" mat-icon-button>
<i *ngIf="editState" class="las la-undo-alt"></i>
@@ -14,12 +18,17 @@
<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">
<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>
@@ -32,23 +41,32 @@
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
<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>
[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">
<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>
@@ -61,22 +79,32 @@
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
<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>
[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">
<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>
@@ -90,10 +118,15 @@
<span class="fill-space"></span>
<div class="shortcut-state-dot" *ngIf="shortcut && shortcut.state !== undefined"
<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>
[ngClass]="{
active: shortcut.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: shortcut.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</a>
</ng-template>
</ng-container>
@@ -104,15 +137,23 @@
<div class="shortcut-container" *ngIf="editState">
<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="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>
@@ -121,73 +162,21 @@
<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.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"
<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>
[ngClass]="{
active: shortcut.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: shortcut.state === ProjectState.PROJECT_STATE_INACTIVE
}"
></div>
</div>
</ng-template>
</ng-container>

View File

@@ -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;

View File

@@ -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];
}
}

View File

@@ -1,20 +1,27 @@
<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="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"
<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>
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>

View File

@@ -28,7 +28,6 @@
}
.left {
color: var(--grey);
margin-right: 1rem;
margin-top: 0;
margin-bottom: 0.5rem;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,6 +1,6 @@
<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>
@@ -12,8 +12,7 @@
{{ 'ACTIONS.CANCEL' | translate }}
</button>
<button color="primary" mat-raised-button class="ok-button" [disabled]="!newdomain"
(click)="closeDialogWithSuccess()">
<button color="primary" mat-raised-button class="ok-button" [disabled]="!newdomain" (click)="closeDialogWithSuccess()">
{{ 'ACTIONS.ADD' | translate }}
</button>
</div>

View File

@@ -4,7 +4,6 @@
}
.desc {
color: var(--grey);
font-size: 0.9rem;
}

View File

@@ -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>

View File

@@ -2,7 +2,7 @@ h1 {
margin: 0;
}
.desc {
margin-bottom: 2rem;
.grants-desc {
margin-bottom: 1rem;
font-size: 14px;
}

View File

@@ -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"

View File

@@ -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;

View File

@@ -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>

View File

@@ -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;

View File

@@ -1,21 +1,33 @@
<form class="form" (ngSubmit)="add(redInput)" [attr.data-e2e]="'redirect-uris'">
<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">
<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">
<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>
<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 *ngIf="!devMode && !(uri | redirect : isNative)" class="las la-exclamation red" [matTooltip]="isNative ? ('APP.OIDC.REDIRECTDESCRIPTIONNATIVE' | translate) : ('APP.OIDC.REDIRECTDESCRIPTIONWEB' | translate)"></i>
<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>
@@ -23,5 +35,6 @@
</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>

View File

@@ -1,25 +1,26 @@
.form {
display: flex;
align-items: flex-end;
min-width: 320px;
@use '@angular/material' as mat;
.formfield {
flex: 1;
}
@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);
button {
margin-bottom: 14px;
margin-right: -0.5rem;
}
}
.uri-list {
margin: 0 0.5rem;
.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;
@@ -37,27 +38,53 @@
height: 30px;
}
i.red {
font-size: 1.2rem;
}
.icon-button {
height: 30px;
line-height: 30px;
.icon {
font-size: 1rem !important;
font-size: 1rem;
margin-bottom: 3px;
}
&:not(:hover) {
color: var(--grey);
color: $secondary-text;
}
}
.alerticon {
display: none;
}
&.alert {
background-color: map-get($background, alertinfosection);
color: map-get($foreground, alertinfosection);
.alerticon {
display: inline;
}
}
}
}
.error {
.redirect-uris-error {
font-size: 13px;
color: var(--warn);
color: $warn-color;
margin: 0 0.5rem 1.5rem 0.5rem;
}
}
.redirect-uris-form {
display: flex;
align-items: flex-end;
min-width: 320px;
.formfield {
flex: 1;
}
button {
margin-bottom: 14px;
margin-right: -0.5rem;
}
}

View File

@@ -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);

View File

@@ -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>

View File

@@ -55,5 +55,5 @@
.desc {
font-size: 14px;
margin-bottom: 2rem;
margin-bottom: 1rem;
}

View File

@@ -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,

View File

@@ -1,8 +1,11 @@
<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>
@@ -12,54 +15,90 @@
<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"
<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>
[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>
<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>

View File

@@ -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,7 +67,10 @@ export class ProjectGrantDetailComponent {
);
};
this.mgmtService.getProjectGrantByID(this.grantid, this.projectid).then((resp) => {
this.mgmtService
.getProjectGrantByID(this.grantid, this.projectid)
.then((resp) => {
console.log(resp);
if (resp.projectGrant) {
this.grant = resp.projectGrant;
@@ -87,7 +88,8 @@ export class ProjectGrantDetailComponent {
];
this.breadcrumbService.setBreadcrumb(breadcrumbs);
}
});
})
.catch(this.toast.showError);
});
}
@@ -176,25 +178,23 @@ 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) {
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(
dataToAdd.userIds.map((userid: string) => {
return this.mgmtService.addProjectGrantMember(
this.grant.projectId,
this.grant.grantId,
userid,
dataToAdd.rolesKeyList,
);
userIds.map((userid: string) => {
return this.mgmtService.addProjectGrantMember(this.grant.projectId, this.grant.grantId, userid, resp.roles);
}),
)
.then(() => {
@@ -207,6 +207,7 @@ export class ProjectGrantDetailComponent {
this.toast.showError(error);
});
}
}
});
}

View File

@@ -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,

View File

@@ -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);

View File

@@ -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>

View File

@@ -1,12 +0,0 @@
.full-width {
width: 100%;
}
.action {
display: flex;
justify-content: flex-end;
.ok-button {
margin-left: .5rem;
}
}

View File

@@ -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();
});
});

View File

@@ -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);
}
}

View File

@@ -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 { }

View File

@@ -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">

View File

@@ -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;

View File

@@ -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"

View File

@@ -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;

View File

@@ -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) {

View File

@@ -1,5 +1,5 @@
<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>
<mat-spinner *ngIf="loading" diameter="20"></mat-spinner>
@@ -8,7 +8,7 @@
</button>
</div>
<p class="desc">{{ 'USER.METADATA.DESCRIPTION' | translate }}</p>
<div mat-dialog-content>
<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">
@@ -20,23 +20,36 @@
<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()">
<button cdkFocusInitial mat-stroked-button class="ok-button" (click)="closeDialog()">
{{ 'ACTIONS.CLOSE' | translate }}
</button>
</div>

View File

@@ -2,8 +2,8 @@
display: flex;
align-items: center;
.title {
font-size: 1.5rem;
.metadata-title {
font-size: 1.3rem;
margin: 0;
}
@@ -23,6 +23,9 @@
}
}
.metadata-dialog-content {
min-width: 400px;
.content {
display: flex;
align-items: center;
@@ -42,6 +45,7 @@
margin-top: 14px;
}
}
}
.action {
display: flex;

View File

@@ -1,12 +1,7 @@
<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">

View File

@@ -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 {

View File

@@ -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,15 +36,18 @@ export class MetadataComponent implements OnInit {
public loadMetadata(): Promise<any> {
this.loading = true;
return (this.service as ManagementService).listUserMetadata(this.userId).then(resp => {
return (this.service as ManagementService)
.listUserMetadata(this.userId)
.then((resp) => {
this.loading = false;
this.metadata = resp.resultList.map(md => {
this.metadata = resp.resultList.map((md) => {
return {
key: md.key,
value: atob(md.value as string),
};
});
}).catch((error) => {
})
.catch((error) => {
this.loading = false;
this.toast.showError(error);
});

View File

@@ -37,8 +37,10 @@
</cnsl-top-view>
<div *ngIf="loading" class="max-width-container">
<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">
<p class="no-user-error">{{ 'USER.PAGES.NOUSER' | translate }}</p>
@@ -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>

View File

@@ -42,3 +42,7 @@
.resendemail {
margin-top: 0.5rem;
}
.sp-wrapper {
margin: 1rem 0;
}

View File

@@ -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,

View File

@@ -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,7 +21,11 @@
<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>

View File

@@ -14,6 +14,6 @@
}
.user-list-sub {
margin-bottom: 2rem;
margin-bottom: 1.5rem;
font-size: 14px;
}

View File

@@ -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">
<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>
<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">
<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>
<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,8 +109,12 @@
</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">
@@ -73,17 +124,26 @@
</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>
{{ 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">
@@ -94,8 +154,13 @@
<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}">
<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>
@@ -104,16 +169,14 @@
<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>
<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>
<span class="no-break">{{ user.details.changeDate | timestampToDate | localizedDate: 'fromNow' }}</span>
</td>
</ng-container>
@@ -121,20 +184,26 @@
<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>
@@ -142,7 +211,14 @@
<i class="las la-exclamation"></i>
<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-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>

View File

@@ -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;

View File

@@ -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,

View File

@@ -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.href = window.location.origin + url;
// window.location.replace(window.location.origin + url);
this.router.navigate([url]);
}
}
}
}

View File

@@ -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);
}
}),
);

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[] {

View File

@@ -35,7 +35,7 @@
},
"SHORTCUTS": {
"SHORTCUTS": "Shortcuts",
"SETTINGS": "Organisation Einstellungen",
"SETTINGS": "Verfügbare Shortcuts",
"PROJECTS": "Projekte",
"REORDER": "Zum Verschieben Kachel halten un ziehen",
"ADD": "Zum Hinzufügen Kachel halten und ziehen"
@@ -135,7 +135,7 @@
"TEXT": "Hier können Sie zwischen Ihren Benutzerkonten wechseln und Ihre Sessions und Ihr Profil verwalten."
},
"NAV": {
"TEXT": "Diese Navigation ändert sich basierend auf den Breadcrumbs darüber."
"TEXT": "Diese Navigation ändert sich basierend auf Ihrer Organisation oder Instanz"
},
"CONTEXTCHANGED": {
"TEXT": "Achtung! Soeben wurde die Organisation gewechselt."
@@ -818,6 +818,7 @@
"LOCKOUT": "Sperrmechanismen",
"COMPLEXITY": "Passwordkomplexität",
"NOTIFICATIONS": "Benachrichtigungen",
"NOTIFICATIONS_DESC": "SMTP und SMS Einstellungen",
"MESSAGETEXTS": "Benachrichtigungstexte",
"IDP": "Identity Provider",
"DOMAIN": "Domain Einstellungen",
@@ -1181,11 +1182,11 @@
"DESC": "Sobald der Nutzer identifiziert ist, wird das Branding der von ihm gewählten Organisation angezeigt, davor wird der Default des Systems angezeigt."
},
"1": {
"TITLE": "Durchsetzung der Richtlinie des Projekteigentümers",
"TITLE": "Einstellung des Projekteigentümers",
"DESC": "Das Branding der Organisation, die Eigentümerin des Projekts ist, wird angezeigt"
},
"2": {
"TITLE": "Durchsetzung der Richtlinie des Benutzereigentümers",
"TITLE": "Einstellung des Benutzereigentümers",
"DESC": "Das Branding der Organisation des Projekts wird angezeigt, sobald der Benutzer identifiziert ist, wird jedoch auf die Einstellung der Organisation des identifizierten Benutzers, gewechselt."
},
"DIALOG": {
@@ -1549,6 +1550,9 @@
}
},
"DIALOG": {
"CONFIG": {
"TITLE": "OIDC configuration ändern"
},
"DELETE": {
"TITLE": "App löschen",
"DESCRIPTION": "Wollen Sie diese App wirklich löschen?"

View File

@@ -35,7 +35,7 @@
},
"SHORTCUTS": {
"SHORTCUTS": "Shortcuts",
"SETTINGS": "Organization Settings",
"SETTINGS": "Available shortcuts",
"PROJECTS": "Projects",
"REORDER": "Hold and drag the tile to move it",
"ADD": "Hold and drag a tile to add"
@@ -135,7 +135,7 @@
"TEXT": "Here you can switch between your user accounts and manage your sessions and profile."
},
"NAV": {
"TEXT": "This navigation changes based on the breadcrumbs above."
"TEXT": "This navigation changes based on your selected oranization above or your instance"
},
"CONTEXTCHANGED": {
"TEXT": "Attention! The organization context has changed."
@@ -818,6 +818,7 @@
"LOCKOUT": "Lockout",
"COMPLEXITY": "Password complexity",
"NOTIFICATIONS": "Notification providers and SMTP",
"NOTIFICATIONS_DESC": "SMTP and SMS Settings",
"MESSAGETEXTS": "Message Texts",
"IDP": "Identity Providers",
"DOMAIN": "Domain settings",
@@ -1181,11 +1182,11 @@
"DESC": "As soon as the user is identified, the branding of the organization of the identified user will be shown, before the system default is shown."
},
"1": {
"TITLE": "Enforce project resource owner Policy",
"TITLE": "Use project setting",
"DESC": "The branding of the organization which owns the project will be shown"
},
"2": {
"TITLE": "Allow Login User resource owner policy",
"TITLE": "Use User Organization setting",
"DESC": "The branding of the organization of the project will be shown, but as soon as the user is identified, the setting of the organization of the identified user, will be shown."
},
"DIALOG": {
@@ -1549,6 +1550,9 @@
}
},
"DIALOG": {
"CONFIG": {
"TITLE": "Change OIDC Configuration"
},
"DELETE": {
"TITLE": "Delete App",
"DESCRIPTION": "Do you really want to delete this application?"

View File

@@ -34,8 +34,8 @@
"DESCRIPTION": "Iniziare rapidamente con ZITADEL."
},
"SHORTCUTS": {
"SHORTCUTS": "Shortcuts",
"SETTINGS": "Impostazioni dell' organizazzione",
"SHORTCUTS": "Scorciatoie",
"SETTINGS": "Scorciatoie disponibili",
"PROJECTS": "Progetti",
"REORDER": "Per spostare, tieni premuto e trascina il riquadro",
"ADD": "Per aggiungere, tieni premuto e trascina il riquadro"
@@ -135,7 +135,7 @@
"TEXT": "Qui puoi passare da un account utente all'altro e gestire le sessioni e il profilo."
},
"NAV": {
"TEXT": "Questa navigazione cambia in base ai breadcrumb sopra."
"TEXT": "Questa navigazione cambia in base all' organizzazione impostata sopra e la tua istanza."
},
"CONTEXTCHANGED": {
"TEXT": "Attenzione! L'organizzazione è appena stata cambiata."
@@ -818,6 +818,7 @@
"LOCKOUT": "Meccanismi di bloccaggio",
"COMPLEXITY": "complessità della password",
"NOTIFICATIONS": "Notifiche",
"NOTIFICATIONS_DESC": "Impostazioni SMTP e SMS",
"MESSAGETEXTS": "Testi di notifica",
"IDP": "Identity Providers",
"DOMAIN": "Impostazioni del dominio",
@@ -1181,11 +1182,11 @@
"DESC": "Non appena l'utente viene identificato, viene mostrata l'impostazione Branding dell'organizzazione, prima che venga mostrato il default del sistema."
},
"1": {
"TITLE": "Applica l'impostazione Branding delle risorse del progetto",
"TITLE": "Applica l'impostazione del progetto",
"DESC": "Branding dell'organizzazione del progetto sar\u00e0 mostrata"
},
"2": {
"TITLE": "Consentire l'accesso all'impostazione Branding dell'utente",
"TITLE": "Applica l'impostazione dell' organizzazione dell' utente",
"DESC": "Verr\u00e0 mostrata l'impostazione Branding dell'organizzazione del progetto, ma non appena l'utente viene identificato, verr\u00e0 mostrata l'impostazione dell'organizzazione dell'utente identificato."
},
"DIALOG": {
@@ -1549,6 +1550,9 @@
}
},
"DIALOG": {
"CONFIG": {
"TITLE": "Configurazione OIDC"
},
"DELETE": {
"TITLE": "Rimuovere App",
"DESCRIPTION": "Vuoi davvero rimuovere questa applicazione?"

View File

@@ -23,6 +23,7 @@
@import 'src/app/pages/projects/owned-projects/owned-project-detail/owned-project-detail.component';
@import 'src/app/pages/projects/granted-projects/granted-project-detail/granted-project-detail.component';
@import 'src/app/pages/projects/apps/app-detail/app-detail.component';
@import 'src/app/pages/projects/apps/redirect-uris/redirect-uris.component';
@import 'src/app/modules/top-view/top-view.component';
@import 'src/app/pages/projects/projects.component';
@import 'src/app/modules/edit-text/edit-text.component.scss';
@@ -51,6 +52,7 @@
@import 'src/app/pages/actions/add-action-dialog/add-action-dialog.component';
@import 'src/app/modules/project-role-chip/project-role-chip.component';
@import 'src/app/pages/home/home.component.scss';
@import 'src/app/modules/search-user-autocomplete/search-user-autocomplete.component.scss';
@import 'src/app/modules/policies/login-policy/factor-table/factor-table.component.scss';
@import 'src/app/modules/info-overlay/info-overlay.component.scss';
@import './styles/codemirror.scss';
@@ -67,6 +69,7 @@
@include top-view-theme($theme);
@include info-overlay-theme($theme);
@include app-auth-method-radio-theme($theme);
@include search-user-autocomplete-theme($theme);
@include project-role-chips-theme($theme);
@include card-theme($theme);
@include footer-theme($theme);
@@ -109,6 +112,7 @@
@include user-grants-theme($theme);
@include info-row-theme($theme);
@include action-dialog-theme($theme);
@include redirect-uris-theme($theme);
@include action-keys-theme($theme);
@include codemirror-theme($theme);
@include contact-theme($theme);

View File

@@ -229,6 +229,9 @@ $caos-light-warn: (
$caos-dark-theme-text: var(--theme-dark-text);
$caos-light-theme-text: var(--theme-light-text);
$caos-dark-theme-secondary-text: var(--theme-dark-secondary-text);
$caos-light-theme-secondary-text: var(--theme-light-secondary-text);
$caos-dark-theme-background: (
status-bar: map_get($caos-dark-background, 300),
app-bar: map_get($caos-dark-background, 500),
@@ -287,7 +290,7 @@ $caos-light-theme-background: (
unselected-chip: map_get($caos-light-background, 300),
disabled-list-option: map_get($caos-light-background, 200),
tooltip: map_get($mat-gray, 700),
infosection: map_get($caos-light-primary, 100),
infosection: map_get($caos-light-background, 600),
warninfosection: #ffc1c1,
alertinfosection: rgb(251, 191, 36),
successinfosection: #cbf4c9,
@@ -307,7 +310,7 @@ $caos-dark-theme-foreground: (
disabled-text: $light-disabled-text,
elevation: black,
hint-text: $light-disabled-text,
secondary-text: $light-secondary-text,
secondary-text: $caos-dark-theme-secondary-text,
placeholder-text: rgba(white, 0.4),
icon: rgba(white, 0.54),
icons: rgba(white, 0.54),
@@ -332,7 +335,7 @@ $caos-light-theme-foreground: (
disabled-text: $dark-disabled-text,
elevation: black,
hint-text: $dark-disabled-text,
secondary-text: $dark-secondary-text,
secondary-text: $caos-light-theme-secondary-text,
placeholder-text: rgba(black, 0.3),
icon: rgba(black, 0.54),
icons: rgba(black, 0.54),
@@ -340,7 +343,7 @@ $caos-light-theme-foreground: (
slider-min: rgba(black, 0.87),
slider-off: rgba(black, 0.26),
slider-off-active: rgba(black, 0.38),
infosection: #4a4a4a,
infosection: map-get(map-get($caos-light-background, contrast), 600),
warninfosection: #620e0e,
alertinfosection: rgb(146, 64, 14),
successinfosection: #0e6245,
@@ -514,7 +517,7 @@ $custom-typography: mat.define-typography-config(
::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
box-shadow: 0 0 6px rgba(0, 0, 0, 0.3);
background-color: map-get($background, background);
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1) !important;

View File

@@ -21,7 +21,7 @@
font-size: 1rem;
border: none;
border: 1px solid if($is-dark-theme, #f9f7f725, #1a191938);
background-color: if($is-dark-theme, #00000020, #00000005);
background-color: if($is-dark-theme, #00000020, #00000004);
border-radius: 4px;
height: 40px;
padding: 10px;