mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 23:17:33 +00:00
rehaul havigation
This commit is contained in:
@@ -75,7 +75,7 @@ const routes: Routes = [
|
|||||||
loadChildren: () => import('./pages/actions/actions.module'),
|
loadChildren: () => import('./pages/actions/actions.module'),
|
||||||
canActivate: [authGuard, roleGuard],
|
canActivate: [authGuard, roleGuard],
|
||||||
data: {
|
data: {
|
||||||
roles: ['org.action.read', 'org.flow.read'],
|
roles: ['iam.read', 'iam.read'],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@@ -5,13 +5,12 @@
|
|||||||
*ngIf="
|
*ngIf="
|
||||||
breadc[breadc.length - 1] &&
|
breadc[breadc.length - 1] &&
|
||||||
!breadc[breadc.length - 1].hideNav &&
|
!breadc[breadc.length - 1].hideNav &&
|
||||||
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER &&
|
breadc[breadc.length - 1].type !== BreadcrumbType.AUTHUSER
|
||||||
breadc[breadc.length - 1].type !== BreadcrumbType.INSTANCE
|
|
||||||
"
|
"
|
||||||
[ngSwitch]="breadc[0].type"
|
[ngSwitch]="breadc[0].type"
|
||||||
>
|
>
|
||||||
<div class="nav-row" @navrow>
|
<div class="nav-row" @navrow>
|
||||||
<ng-container *ngSwitchCase="BreadcrumbType.ORG">
|
<ng-container *ngSwitchCase="BreadcrumbType.INSTANCE">
|
||||||
<div class="nav-row-abs" @navrowproject>
|
<div class="nav-row-abs" @navrowproject>
|
||||||
<a
|
<a
|
||||||
class="nav-item"
|
class="nav-item"
|
||||||
@@ -22,6 +21,48 @@
|
|||||||
<span class="label">{{ 'MENU.DASHBOARD' | translate }}</span>
|
<span class="label">{{ 'MENU.DASHBOARD' | translate }}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<ng-container class="org-list" *ngIf="org">
|
||||||
|
<ng-template cnslHasRole [hasRole]="['org.read']">
|
||||||
|
<a
|
||||||
|
class="nav-item"
|
||||||
|
[routerLinkActive]="['active']"
|
||||||
|
[routerLinkActiveOptions]="{ exact: false }"
|
||||||
|
[routerLink]="['/orgs']"
|
||||||
|
>
|
||||||
|
<span class="label">{{ 'MENU.ORGS' | translate }}</span>
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template cnslHasRole [hasRole]="['org.action.read']">
|
||||||
|
<a
|
||||||
|
class="nav-item"
|
||||||
|
[routerLinkActive]="['active']"
|
||||||
|
[routerLink]="['/actions']"
|
||||||
|
[routerLinkActiveOptions]="{ exact: false }"
|
||||||
|
>
|
||||||
|
<span class="label">{{ 'MENU.ACTIONS' | translate }}</span>
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template cnslHasRole [hasRole]="['org.read']">
|
||||||
|
<a
|
||||||
|
class="nav-item"
|
||||||
|
[routerLinkActive]="['active']"
|
||||||
|
[routerLinkActiveOptions]="{ exact: false }"
|
||||||
|
[routerLink]="['/instance']"
|
||||||
|
*ngIf="['policy.read'] | hasRole | async"
|
||||||
|
>
|
||||||
|
<span class="label">{{ 'MENU.SETTINGS' | translate }}</span>
|
||||||
|
</a>
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<template [ngTemplateOutlet]="shortcutKeyRef"></template>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngSwitchCase="BreadcrumbType.ORG">
|
||||||
|
<div class="nav-row-abs" @navrowproject>
|
||||||
<ng-container class="org-list" *ngIf="org">
|
<ng-container class="org-list" *ngIf="org">
|
||||||
<ng-template cnslHasRole [hasRole]="['org.read']">
|
<ng-template cnslHasRole [hasRole]="['org.read']">
|
||||||
<a
|
<a
|
||||||
|
@@ -23,7 +23,8 @@
|
|||||||
.nav-row {
|
.nav-row {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 36px;
|
height: 40px; // Increased height to accommodate the line
|
||||||
|
overflow: visible; // Allow the indicator line to show below
|
||||||
|
|
||||||
.nav-row-abs {
|
.nav-row-abs {
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
@@ -32,7 +33,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overflow-y: hidden;
|
overflow-y: visible; // Allow the indicator line to show
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -51,19 +52,46 @@
|
|||||||
.nav-item {
|
.nav-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
|
||||||
line-height: 14px;
|
|
||||||
padding: 0.4rem 12px;
|
|
||||||
color: map-get($foreground, text);
|
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 50vw;
|
border-radius: 50vw;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin: 0.25rem 2px;
|
margin: 0.25rem 2px;
|
||||||
|
font-size: 14px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
height: 36px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 27px;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: map-get($foreground, text);
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
// Active indicator line
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 1px;
|
||||||
|
background-color: map-get($foreground, text);
|
||||||
|
transition: width 0.2s ease;
|
||||||
|
border-radius: 2px;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
color: map-get($foreground, text);
|
||||||
|
background-color: if($is-dark-theme, #ffffff10, #00000010);
|
||||||
|
}
|
||||||
|
|
||||||
.c_label {
|
.c_label {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -97,14 +125,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: if($is-dark-theme, #ffffff40, #00000010);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: $primary-color;
|
opacity: 1;
|
||||||
color: map-get($primary, default-contrast);
|
color: map-get($primary, default-contrast);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
width: 100%; // Full width for testing
|
||||||
|
}
|
||||||
|
|
||||||
.c_label {
|
.c_label {
|
||||||
.count {
|
.count {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<div class="new-header-wrapper">
|
<div class="new-header-wrapper">
|
||||||
<ng-container *ngIf="myInstanceQuery.data()?.instance as instance">
|
<ng-container *ngIf="myInstanceQuery.data()?.instance as instance">
|
||||||
<ng-container *ngTemplateOutlet="slash"></ng-container>
|
<ng-container *ngTemplateOutlet="slash"></ng-container>
|
||||||
<a class="new-header-breadcrumb" matRipple [matRippleUnbounded]="false" [routerLink]="['/instance']">
|
<a class="new-header-breadcrumb" matRipple [matRippleUnbounded]="false" [routerLink]="['/']">
|
||||||
{{ instance.name }}
|
{{ instance.name }}
|
||||||
</a>
|
</a>
|
||||||
<cnsl-header-button
|
<cnsl-header-button
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
class="new-header-breadcrumb"
|
class="new-header-breadcrumb"
|
||||||
matRipple
|
matRipple
|
||||||
[matRippleUnbounded]="false"
|
[matRippleUnbounded]="false"
|
||||||
[routerLink]="['/']"
|
[routerLink]="['/org']"
|
||||||
>
|
>
|
||||||
{{ org.name }}
|
{{ org.name }}
|
||||||
</a>
|
</a>
|
||||||
|
@@ -1,10 +1,4 @@
|
|||||||
<cnsl-sidenav [indented]="true" [setting]="setting()" (settingChange)="setting.set($event)" [settingsList]="settingsList">
|
<cnsl-sidenav [indented]="true" [setting]="setting()" (settingChange)="setting.set($event)" [settingsList]="settingsList">
|
||||||
<ng-container *ngIf="setting()?.id === 'organizations'">
|
|
||||||
<h2>{{ 'ORG.PAGES.LIST' | translate }}</h2>
|
|
||||||
<p class="org-desc cnsl-secondary-text">{{ 'ORG.PAGES.LISTDESCRIPTION' | translate }}</p>
|
|
||||||
|
|
||||||
<cnsl-org-table></cnsl-org-table>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="setting()?.id === 'features'">
|
<ng-container *ngIf="setting()?.id === 'features'">
|
||||||
<cnsl-features></cnsl-features>
|
<cnsl-features></cnsl-features>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
@@ -74,12 +68,6 @@
|
|||||||
<ng-container *ngIf="setting()?.id === 'failedevents' && serviceType === PolicyComponentServiceType.ADMIN">
|
<ng-container *ngIf="setting()?.id === 'failedevents' && serviceType === PolicyComponentServiceType.ADMIN">
|
||||||
<cnsl-iam-failed-events></cnsl-iam-failed-events>
|
<cnsl-iam-failed-events></cnsl-iam-failed-events>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<!-- todo: figure out permissions -->
|
|
||||||
<ng-container *ngIf="setting()?.id === 'actions'">
|
|
||||||
<cnsl-actions-two-actions />
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="setting()?.id === 'actions_targets'">
|
|
||||||
<cnsl-actions-two-targets />
|
|
||||||
</ng-container>
|
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
</cnsl-sidenav>
|
</cnsl-sidenav>
|
||||||
|
@@ -1,15 +1,6 @@
|
|||||||
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
import { PolicyComponentServiceType } from '../policies/policy-component-types.enum';
|
||||||
import { SidenavSetting } from '../sidenav/sidenav.component';
|
import { SidenavSetting } from '../sidenav/sidenav.component';
|
||||||
|
|
||||||
export const ORGANIZATIONS: SidenavSetting = {
|
|
||||||
id: 'organizations',
|
|
||||||
i18nKey: 'SETTINGS.LIST.ORGS',
|
|
||||||
groupI18nKey: 'SETTINGS.GROUPS.GENERAL',
|
|
||||||
requiredRoles: {
|
|
||||||
[PolicyComponentServiceType.ADMIN]: ['iam.read'],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FEATURESETTINGS: SidenavSetting = {
|
export const FEATURESETTINGS: SidenavSetting = {
|
||||||
id: 'features',
|
id: 'features',
|
||||||
i18nKey: 'SETTINGS.LIST.FEATURESETTINGS',
|
i18nKey: 'SETTINGS.LIST.FEATURESETTINGS',
|
||||||
@@ -222,23 +213,3 @@ export const BRANDING: SidenavSetting = {
|
|||||||
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
[PolicyComponentServiceType.ADMIN]: ['iam.policy.read'],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ACTIONS: SidenavSetting = {
|
|
||||||
id: 'actions',
|
|
||||||
i18nKey: 'SETTINGS.LIST.ACTIONS',
|
|
||||||
groupI18nKey: 'SETTINGS.GROUPS.ACTIONS',
|
|
||||||
requiredRoles: {
|
|
||||||
[PolicyComponentServiceType.ADMIN]: ['action.execution.write', 'action.target.write'],
|
|
||||||
},
|
|
||||||
beta: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ACTIONS_TARGETS: SidenavSetting = {
|
|
||||||
id: 'actions_targets',
|
|
||||||
i18nKey: 'SETTINGS.LIST.TARGETS',
|
|
||||||
groupI18nKey: 'SETTINGS.GROUPS.ACTIONS',
|
|
||||||
requiredRoles: {
|
|
||||||
[PolicyComponentServiceType.ADMIN]: ['action.execution.write', 'action.target.write'],
|
|
||||||
},
|
|
||||||
beta: true,
|
|
||||||
};
|
|
||||||
|
@@ -1,107 +1,14 @@
|
|||||||
<div class="max-width-container">
|
<div class="max-width-container">
|
||||||
<div class="enlarged-container actions-enlarged-container">
|
<div class="enlarged-container">
|
||||||
<div class="actions-title-row">
|
<h1>{{ 'ORG.PAGES.LIST' | translate }}</h1>
|
||||||
<h1>{{ 'DESCRIPTIONS.ACTIONS.TITLE' | translate }}</h1>
|
<p class="org-desc cnsl-secondary-text">{{ 'ORG.PAGES.LISTDESCRIPTION' | translate }}</p>
|
||||||
<a mat-icon-button href="https://zitadel.com/docs/concepts/features/actions" rel="noreferrer" target="_blank">
|
|
||||||
<mat-icon class="icon">info_outline</mat-icon>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<cnsl-info-section [type]="InfoSectionType.ALERT">
|
|
||||||
{{ 'DESCRIPTIONS.ACTIONS.ACTIONSTWO_NOTE' | translate }}
|
|
||||||
</cnsl-info-section>
|
|
||||||
<p class="desc cnsl-secondary-text">{{ 'DESCRIPTIONS.ACTIONS.DESCRIPTION' | translate }}</p>
|
|
||||||
|
|
||||||
<cnsl-info-section class="max-actions" *ngIf="maxActions"
|
<cnsl-meta-layout>
|
||||||
>{{ 'FLOWS.ACTIONSMAX' | translate: { value: maxActions } }}
|
<cnsl-sidenav [(setting)]="currentSetting" [settingsList]="settingsList">
|
||||||
</cnsl-info-section>
|
<ng-container *ngIf="currentSetting.id === 'actions'"> actions </ng-container>
|
||||||
|
|
||||||
<ng-template cnslHasRole [hasRole]="['org.action.read']">
|
<ng-container *ngIf="currentSetting.id === 'targets'"> targets </ng-container>
|
||||||
<cnsl-card
|
</cnsl-sidenav>
|
||||||
title="{{ 'DESCRIPTIONS.ACTIONS.SCRIPTS.TITLE' | translate }}"
|
</cnsl-meta-layout>
|
||||||
description="{{ 'DESCRIPTIONS.ACTIONS.SCRIPTS.DESCRIPTION' | translate }}"
|
|
||||||
>
|
|
||||||
<cnsl-action-table (changedSelection)="selection = $event"></cnsl-action-table>
|
|
||||||
</cnsl-card>
|
|
||||||
</ng-template>
|
|
||||||
|
|
||||||
<div class="title-section">
|
|
||||||
<h2>{{ 'DESCRIPTIONS.ACTIONS.FLOWS.TITLE' | translate }}</h2>
|
|
||||||
<i class="las la-exchange-alt"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="desc cnsl-secondary-text">{{ 'DESCRIPTIONS.ACTIONS.FLOWS.DESCRIPTION' | translate }}</p>
|
|
||||||
|
|
||||||
<ng-template cnslHasRole [hasRole]="['org.flow.read']">
|
|
||||||
<div class="actions-flow">
|
|
||||||
<cnsl-form-field class="formfield">
|
|
||||||
<cnsl-label>{{ 'FLOWS.FLOWTYPE' | translate }}</cnsl-label>
|
|
||||||
<mat-select [formControl]="typeControl">
|
|
||||||
<mat-option *ngFor="let type of typesForSelection" [value]="type">
|
|
||||||
{{ type.name?.localizedMessage }}
|
|
||||||
</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</cnsl-form-field>
|
|
||||||
|
|
||||||
<div *ngIf="flow" class="trigger-wrapper">
|
|
||||||
<div class="actions-topbottomline"></div>
|
|
||||||
|
|
||||||
<div class="flow-type">
|
|
||||||
<i class="type-icon las la-dot-circle"></i>
|
|
||||||
<span>{{ flow.type?.name?.localizedMessage }}</span>
|
|
||||||
<button
|
|
||||||
*ngIf="flow.type && (flow.triggerActionsList?.length ?? 0) > 0"
|
|
||||||
matTooltip="{{ 'ACTIONS.CLEAR' | translate }}"
|
|
||||||
mat-icon-button
|
|
||||||
color="warn"
|
|
||||||
(click)="clearFlow(flow.type.id)"
|
|
||||||
>
|
|
||||||
<i class="type-button-icon las la-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<cnsl-card *ngFor="let trigger of flow.triggerActionsList; index as i" class="trigger">
|
|
||||||
<div class="trigger-top">
|
|
||||||
<mat-icon svgIcon="mdi_arrow_right_bottom" class="icon"></mat-icon>
|
|
||||||
<span>{{ trigger.triggerType?.name?.localizedMessage }}</span>
|
|
||||||
<span class="fill-space"></span>
|
|
||||||
<button color="warn" mat-icon-button (click)="removeTriggerActionsList(i)">
|
|
||||||
<i class="las la-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<span class="fill-space"></span>
|
|
||||||
<div class="flow-action-wrapper" cdkDropList (cdkDropListDropped)="drop(i, trigger.actionsList, $event)">
|
|
||||||
<div
|
|
||||||
cdkDrag
|
|
||||||
cdkDragLockAxis="y"
|
|
||||||
cdkDragBoundary=".action-wrapper"
|
|
||||||
class="flow-action"
|
|
||||||
*ngFor="let action of trigger.actionsList"
|
|
||||||
>
|
|
||||||
<i class="las la-code"></i>
|
|
||||||
<span class="flow-action-name">{{ action.name }}</span>
|
|
||||||
<span class="fill-space"></span>
|
|
||||||
<span
|
|
||||||
class="state"
|
|
||||||
[ngClass]="{
|
|
||||||
active: action.state === ActionState.ACTION_STATE_ACTIVE,
|
|
||||||
inactive: action.state === ActionState.ACTION_STATE_INACTIVE,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ 'FLOWS.STATES.' + action.state | translate }}</span
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</cnsl-card>
|
|
||||||
|
|
||||||
<button *ngIf="flow.type" class="add-btn" mat-raised-button color="primary" (click)="openAddTrigger(flow.type)">
|
|
||||||
<div class="cnsl-action-button">
|
|
||||||
<mat-icon>add</mat-icon>
|
|
||||||
<span>{{ 'FLOWS.ADDTRIGGER' | translate }}</span>
|
|
||||||
<span *ngIf="selection && selection.length"> ({{ selection.length }})</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,186 +1,7 @@
|
|||||||
.actions-title-row {
|
h1 {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
.icon {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
height: 1.2rem;
|
|
||||||
width: 1.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin actions-theme($theme) {
|
.org-desc {
|
||||||
$foreground: map-get($theme, foreground);
|
|
||||||
$background: map-get($theme, background);
|
|
||||||
$is-dark-theme: map-get($theme, is-dark);
|
|
||||||
$primary: map-get($theme, primary);
|
|
||||||
$primary-color: map-get($primary, 500);
|
|
||||||
|
|
||||||
.actions-enlarged-container {
|
|
||||||
h1 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desc {
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
|
||||||
|
|
||||||
.title-section {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 3rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions-flow {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 1000px;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.formfield {
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flow-type {
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
padding: 0 1.5rem;
|
|
||||||
|
|
||||||
.type-icon {
|
|
||||||
color: $primary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-button-icon,
|
|
||||||
.type-icon,
|
|
||||||
span {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-icon,
|
|
||||||
.type-button-icon {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-wrapper {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.trigger {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.trigger-top {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
padding-left: 7px;
|
|
||||||
|
|
||||||
.fill-space {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-right: 1rem;
|
|
||||||
color: $primary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fill-space {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flow-action-wrapper {
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
.flow-action {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
cursor: move;
|
|
||||||
|
|
||||||
.flow-action-name {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fill-space {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.state {
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.actions-topbottomline {
|
|
||||||
position: absolute;
|
|
||||||
top: 26px;
|
|
||||||
bottom: 1.5rem;
|
|
||||||
left: 35px;
|
|
||||||
width: 2px;
|
|
||||||
z-index: 0;
|
|
||||||
background-color: $primary-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
align-self: flex-start;
|
|
||||||
margin: 1rem 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cdk-drag-preview {
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 14px;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
padding: 0 0.5rem;
|
|
||||||
background-color: $primary-color;
|
|
||||||
box-shadow:
|
|
||||||
0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
|
||||||
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
|
||||||
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
|
||||||
|
|
||||||
i {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cdk-drag-placeholder {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cdk-drag-animating {
|
|
||||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,19 +1,19 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
|
||||||
import { ActionsComponent } from './actions.component';
|
import { OrgListComponent } from './actions.component';
|
||||||
|
|
||||||
describe('ActionsComponent', () => {
|
describe('OrgListComponent', () => {
|
||||||
let component: ActionsComponent;
|
let component: OrgListComponent;
|
||||||
let fixture: ComponentFixture<ActionsComponent>;
|
let fixture: ComponentFixture<OrgListComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(waitForAsync(() => {
|
||||||
await TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [ActionsComponent],
|
declarations: [OrgListComponent],
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(ActionsComponent);
|
fixture = TestBed.createComponent(OrgListComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -1,180 +1,28 @@
|
|||||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
import { Component } from '@angular/core';
|
||||||
import { Component, DestroyRef } from '@angular/core';
|
import { enterAnimations } from 'src/app/animations';
|
||||||
import { UntypedFormControl } from '@angular/forms';
|
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
|
||||||
import { ActionKeysType } from 'src/app/modules/action-keys/action-keys.component';
|
|
||||||
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
|
|
||||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
|
||||||
import { Action, ActionState, Flow, FlowType, TriggerType } from 'src/app/proto/generated/zitadel/action_pb';
|
|
||||||
import { SetTriggerActionsRequest } from 'src/app/proto/generated/zitadel/management_pb';
|
|
||||||
import { Breadcrumb, BreadcrumbService, BreadcrumbType } from 'src/app/services/breadcrumb.service';
|
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 { AddFlowDialogComponent } from './add-flow-dialog/add-flow-dialog.component';
|
const ACTIONS: SidenavSetting = { id: 'general', i18nKey: 'MENU.ACTIONS' };
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
const TARGETS: SidenavSetting = { id: 'roles', i18nKey: 'MENU.TARGETS' };
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'cnsl-actions',
|
selector: 'cnsl-actions',
|
||||||
templateUrl: './actions.component.html',
|
templateUrl: './actions.component.html',
|
||||||
styleUrls: ['./actions.component.scss'],
|
styleUrls: ['./actions.component.scss'],
|
||||||
|
animations: [enterAnimations],
|
||||||
})
|
})
|
||||||
export class ActionsComponent {
|
export class ActionsComponent {
|
||||||
protected flow!: Flow.AsObject;
|
public settingsList: SidenavSetting[] = [ACTIONS, TARGETS];
|
||||||
|
public currentSetting = this.settingsList[0];
|
||||||
|
|
||||||
protected typeControl: UntypedFormControl = new UntypedFormControl();
|
constructor(breadcrumbService: BreadcrumbService) {
|
||||||
|
const iamBread = new Breadcrumb({
|
||||||
protected typesForSelection: FlowType.AsObject[] = [];
|
type: BreadcrumbType.INSTANCE,
|
||||||
|
name: 'Instance',
|
||||||
protected selection: Action.AsObject[] = [];
|
routerLink: ['/instance'],
|
||||||
protected InfoSectionType = InfoSectionType;
|
|
||||||
protected ActionKeysType = ActionKeysType;
|
|
||||||
|
|
||||||
protected maxActions: number | null = null;
|
|
||||||
protected ActionState = ActionState;
|
|
||||||
constructor(
|
|
||||||
private mgmtService: ManagementService,
|
|
||||||
breadcrumbService: BreadcrumbService,
|
|
||||||
private dialog: MatDialog,
|
|
||||||
private toast: ToastService,
|
|
||||||
destroyRef: DestroyRef,
|
|
||||||
) {
|
|
||||||
const bread: Breadcrumb = {
|
|
||||||
type: BreadcrumbType.ORG,
|
|
||||||
routerLink: ['/org'],
|
|
||||||
};
|
|
||||||
breadcrumbService.setBreadcrumb([bread]);
|
|
||||||
|
|
||||||
this.getFlowTypes().then();
|
|
||||||
|
|
||||||
this.typeControl.valueChanges.pipe(takeUntilDestroyed(destroyRef)).subscribe((value) => {
|
|
||||||
this.loadFlow((value as FlowType.AsObject).id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getFlowTypes(): Promise<void> {
|
|
||||||
try {
|
|
||||||
let resp = await this.mgmtService.listFlowTypes();
|
|
||||||
this.typesForSelection = resp.resultList;
|
|
||||||
if (!this.flow && resp.resultList[0]) {
|
|
||||||
const type = resp.resultList[0];
|
|
||||||
this.typeControl.setValue(type);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
this.toast.showError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadFlow(id: string) {
|
|
||||||
this.mgmtService.getFlow(id).then((flowResponse) => {
|
|
||||||
if (flowResponse.flow) {
|
|
||||||
this.flow = flowResponse.flow;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public clearFlow(id: string): void {
|
|
||||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
|
||||||
data: {
|
|
||||||
confirmKey: 'ACTIONS.CLEAR',
|
|
||||||
cancelKey: 'ACTIONS.CANCEL',
|
|
||||||
titleKey: 'FLOWS.DIALOG.CLEAR.TITLE',
|
|
||||||
descriptionKey: 'FLOWS.DIALOG.CLEAR.DESCRIPTION',
|
|
||||||
},
|
|
||||||
width: '400px',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe((resp) => {
|
breadcrumbService.setBreadcrumb([iamBread]);
|
||||||
if (resp) {
|
|
||||||
this.mgmtService
|
|
||||||
.clearFlow(id)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('FLOWS.FLOWCLEARED', true);
|
|
||||||
this.loadFlow(id);
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected openAddTrigger(flow: FlowType.AsObject, trigger?: TriggerType.AsObject): void {
|
|
||||||
const dialogRef = this.dialog.open(AddFlowDialogComponent, {
|
|
||||||
data: {
|
|
||||||
flowType: flow,
|
|
||||||
actions: this.selection && this.selection.length ? this.selection : [],
|
|
||||||
},
|
|
||||||
width: '400px',
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe((req: SetTriggerActionsRequest) => {
|
|
||||||
if (req) {
|
|
||||||
this.mgmtService
|
|
||||||
.setTriggerActions(req.getActionIdsList(), req.getFlowType(), req.getTriggerType())
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('FLOWS.FLOWCHANGED', true);
|
|
||||||
this.loadFlow(flow.id);
|
|
||||||
})
|
|
||||||
.catch((error: any) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
drop(triggerActionsListIndex: number, array: any[], event: CdkDragDrop<Action.AsObject[]>) {
|
|
||||||
moveItemInArray(array, event.previousIndex, event.currentIndex);
|
|
||||||
this.saveFlow(triggerActionsListIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
saveFlow(index: number) {
|
|
||||||
if (
|
|
||||||
this.flow.type &&
|
|
||||||
this.flow.triggerActionsList &&
|
|
||||||
this.flow.triggerActionsList[index] &&
|
|
||||||
this.flow.triggerActionsList[index]?.triggerType
|
|
||||||
) {
|
|
||||||
this.mgmtService
|
|
||||||
.setTriggerActions(
|
|
||||||
this.flow.triggerActionsList[index].actionsList.map((action) => action.id),
|
|
||||||
this.flow.type.id,
|
|
||||||
this.flow.triggerActionsList[index].triggerType?.id ?? '',
|
|
||||||
)
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected removeTriggerActionsList(index: number) {
|
|
||||||
if (this.flow.type && this.flow.triggerActionsList && this.flow.triggerActionsList[index]) {
|
|
||||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
|
||||||
data: {
|
|
||||||
confirmKey: 'ACTIONS.CLEAR',
|
|
||||||
cancelKey: 'ACTIONS.CANCEL',
|
|
||||||
titleKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.TITLE',
|
|
||||||
descriptionKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.DESCRIPTION',
|
|
||||||
},
|
|
||||||
width: '400px',
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe((resp) => {
|
|
||||||
if (resp) {
|
|
||||||
this.mgmtService
|
|
||||||
.setTriggerActions([], this.flow?.type?.id ?? '', this.flow.triggerActionsList[index].triggerType?.id ?? '')
|
|
||||||
.then(() => {
|
|
||||||
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
|
||||||
this.loadFlow(this.flow?.type?.id ?? '');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.toast.showError(error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,68 +1,16 @@
|
|||||||
import { DragDropModule } from '@angular/cdk/drag-drop';
|
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCheckboxModule } from '@angular/material/checkbox';
|
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
|
||||||
import { MatTableModule } from '@angular/material/table';
|
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
||||||
import { CodemirrorModule } from '@ctrl/ngx-codemirror';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { HasRoleModule } from 'src/app/directives/has-role/has-role.module';
|
import { OrgTableModule } from 'src/app/modules/org-table/org-table.module';
|
||||||
import { ActionKeysModule } from 'src/app/modules/action-keys/action-keys.module';
|
|
||||||
import { CardModule } from 'src/app/modules/card/card.module';
|
|
||||||
import { FormFieldModule } from 'src/app/modules/form-field/form-field.module';
|
|
||||||
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
|
|
||||||
import { InputModule } from 'src/app/modules/input/input.module';
|
|
||||||
import { PaginatorModule } from 'src/app/modules/paginator/paginator.module';
|
|
||||||
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
|
||||||
import { TableActionsModule } from 'src/app/modules/table-actions/table-actions.module';
|
|
||||||
import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module';
|
|
||||||
import { DurationToSecondsPipeModule } from 'src/app/pipes/duration-to-seconds-pipe/duration-to-seconds-pipe.module';
|
|
||||||
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
|
||||||
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
|
||||||
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
|
||||||
|
|
||||||
import { ActionTableComponent } from './action-table/action-table.component';
|
|
||||||
import { ActionsRoutingModule } from './actions-routing.module';
|
import { ActionsRoutingModule } from './actions-routing.module';
|
||||||
import { ActionsComponent } from './actions.component';
|
import { ActionsComponent } from './actions.component';
|
||||||
import { AddActionDialogComponent } from './add-action-dialog/add-action-dialog.component';
|
import { MetaLayoutModule } from 'src/app/modules/meta-layout/meta-layout.module';
|
||||||
import { AddFlowDialogComponent } from './add-flow-dialog/add-flow-dialog.component';
|
import { SidenavModule } from 'src/app/modules/sidenav/sidenav.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [ActionsComponent, ActionTableComponent, AddActionDialogComponent, AddFlowDialogComponent],
|
declarations: [ActionsComponent],
|
||||||
imports: [
|
imports: [CommonModule, ActionsRoutingModule, OrgTableModule, TranslateModule, MetaLayoutModule, SidenavModule],
|
||||||
CommonModule,
|
exports: [ActionsComponent],
|
||||||
FormsModule,
|
|
||||||
ActionsRoutingModule,
|
|
||||||
TranslateModule,
|
|
||||||
MatDialogModule,
|
|
||||||
RefreshTableModule,
|
|
||||||
MatTableModule,
|
|
||||||
PaginatorModule,
|
|
||||||
MatButtonModule,
|
|
||||||
ReactiveFormsModule,
|
|
||||||
MatIconModule,
|
|
||||||
DurationToSecondsPipeModule,
|
|
||||||
TimestampToDatePipeModule,
|
|
||||||
LocalizedDatePipeModule,
|
|
||||||
HasRoleModule,
|
|
||||||
ActionKeysModule,
|
|
||||||
MatTooltipModule,
|
|
||||||
CardModule,
|
|
||||||
MatCheckboxModule,
|
|
||||||
InputModule,
|
|
||||||
FormFieldModule,
|
|
||||||
MatSelectModule,
|
|
||||||
WarnDialogModule,
|
|
||||||
DragDropModule,
|
|
||||||
InfoSectionModule,
|
|
||||||
HasRolePipeModule,
|
|
||||||
TableActionsModule,
|
|
||||||
CodemirrorModule,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export default class ActionsModule {}
|
export default class ActionsModule {}
|
||||||
|
@@ -27,8 +27,8 @@ export class HomeComponent {
|
|||||||
public themeService: ThemeService,
|
public themeService: ThemeService,
|
||||||
) {
|
) {
|
||||||
const bread: Breadcrumb = {
|
const bread: Breadcrumb = {
|
||||||
type: BreadcrumbType.ORG,
|
type: BreadcrumbType.INSTANCE,
|
||||||
routerLink: ['/org'],
|
routerLink: ['/'],
|
||||||
};
|
};
|
||||||
|
|
||||||
breadcrumbService.setBreadcrumb([bread]);
|
breadcrumbService.setBreadcrumb([bread]);
|
||||||
|
@@ -33,10 +33,7 @@ import {
|
|||||||
VIEWS,
|
VIEWS,
|
||||||
FAILEDEVENTS,
|
FAILEDEVENTS,
|
||||||
EVENTS,
|
EVENTS,
|
||||||
ORGANIZATIONS,
|
|
||||||
FEATURESETTINGS,
|
FEATURESETTINGS,
|
||||||
ACTIONS,
|
|
||||||
ACTIONS_TARGETS,
|
|
||||||
} from 'src/app/modules/settings-list/settings';
|
} from 'src/app/modules/settings-list/settings';
|
||||||
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
import { SidenavSetting } from 'src/app/modules/sidenav/sidenav.component';
|
||||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||||
@@ -58,10 +55,7 @@ export class InstanceComponent {
|
|||||||
|
|
||||||
protected id: string = '';
|
protected id: string = '';
|
||||||
protected readonly defaultSettingsList: SidenavSetting[] = [
|
protected readonly defaultSettingsList: SidenavSetting[] = [
|
||||||
ORGANIZATIONS,
|
|
||||||
FEATURESETTINGS,
|
FEATURESETTINGS,
|
||||||
ACTIONS,
|
|
||||||
ACTIONS_TARGETS,
|
|
||||||
// notifications
|
// notifications
|
||||||
// { showWarn: true, ...NOTIFICATIONS },
|
// { showWarn: true, ...NOTIFICATIONS },
|
||||||
NOTIFICATIONS,
|
NOTIFICATIONS,
|
||||||
|
17
console/src/app/pages/org-actions/actions-routing.module.ts
Normal file
17
console/src/app/pages/org-actions/actions-routing.module.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
|
import { ActionsComponent } from './actions.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: ActionsComponent,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule],
|
||||||
|
})
|
||||||
|
export class ActionsRoutingModule {}
|
107
console/src/app/pages/org-actions/actions.component.html
Normal file
107
console/src/app/pages/org-actions/actions.component.html
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
<div class="max-width-container">
|
||||||
|
<div class="enlarged-container actions-enlarged-container">
|
||||||
|
<div class="actions-title-row">
|
||||||
|
<h1>{{ 'DESCRIPTIONS.ACTIONS.TITLE' | translate }}</h1>
|
||||||
|
<a mat-icon-button href="https://zitadel.com/docs/concepts/features/actions" rel="noreferrer" target="_blank">
|
||||||
|
<mat-icon class="icon">info_outline</mat-icon>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<cnsl-info-section [type]="InfoSectionType.ALERT">
|
||||||
|
{{ 'DESCRIPTIONS.ACTIONS.ACTIONSTWO_NOTE' | translate }}
|
||||||
|
</cnsl-info-section>
|
||||||
|
<p class="desc cnsl-secondary-text">{{ 'DESCRIPTIONS.ACTIONS.DESCRIPTION' | translate }}</p>
|
||||||
|
|
||||||
|
<cnsl-info-section class="max-actions" *ngIf="maxActions"
|
||||||
|
>{{ 'FLOWS.ACTIONSMAX' | translate: { value: maxActions } }}
|
||||||
|
</cnsl-info-section>
|
||||||
|
|
||||||
|
<ng-template cnslHasRole [hasRole]="['org.action.read']">
|
||||||
|
<cnsl-card
|
||||||
|
title="{{ 'DESCRIPTIONS.ACTIONS.SCRIPTS.TITLE' | translate }}"
|
||||||
|
description="{{ 'DESCRIPTIONS.ACTIONS.SCRIPTS.DESCRIPTION' | translate }}"
|
||||||
|
>
|
||||||
|
<cnsl-action-table (changedSelection)="selection = $event"></cnsl-action-table>
|
||||||
|
</cnsl-card>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<div class="title-section">
|
||||||
|
<h2>{{ 'DESCRIPTIONS.ACTIONS.FLOWS.TITLE' | translate }}</h2>
|
||||||
|
<i class="las la-exchange-alt"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="desc cnsl-secondary-text">{{ 'DESCRIPTIONS.ACTIONS.FLOWS.DESCRIPTION' | translate }}</p>
|
||||||
|
|
||||||
|
<ng-template cnslHasRole [hasRole]="['org.flow.read']">
|
||||||
|
<div class="actions-flow">
|
||||||
|
<cnsl-form-field class="formfield">
|
||||||
|
<cnsl-label>{{ 'FLOWS.FLOWTYPE' | translate }}</cnsl-label>
|
||||||
|
<mat-select [formControl]="typeControl">
|
||||||
|
<mat-option *ngFor="let type of typesForSelection" [value]="type">
|
||||||
|
{{ type.name?.localizedMessage }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</cnsl-form-field>
|
||||||
|
|
||||||
|
<div *ngIf="flow" class="trigger-wrapper">
|
||||||
|
<div class="actions-topbottomline"></div>
|
||||||
|
|
||||||
|
<div class="flow-type">
|
||||||
|
<i class="type-icon las la-dot-circle"></i>
|
||||||
|
<span>{{ flow.type?.name?.localizedMessage }}</span>
|
||||||
|
<button
|
||||||
|
*ngIf="flow.type && (flow.triggerActionsList?.length ?? 0) > 0"
|
||||||
|
matTooltip="{{ 'ACTIONS.CLEAR' | translate }}"
|
||||||
|
mat-icon-button
|
||||||
|
color="warn"
|
||||||
|
(click)="clearFlow(flow.type.id)"
|
||||||
|
>
|
||||||
|
<i class="type-button-icon las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<cnsl-card *ngFor="let trigger of flow.triggerActionsList; index as i" class="trigger">
|
||||||
|
<div class="trigger-top">
|
||||||
|
<mat-icon svgIcon="mdi_arrow_right_bottom" class="icon"></mat-icon>
|
||||||
|
<span>{{ trigger.triggerType?.name?.localizedMessage }}</span>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<button color="warn" mat-icon-button (click)="removeTriggerActionsList(i)">
|
||||||
|
<i class="las la-trash"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<div class="flow-action-wrapper" cdkDropList (cdkDropListDropped)="drop(i, trigger.actionsList, $event)">
|
||||||
|
<div
|
||||||
|
cdkDrag
|
||||||
|
cdkDragLockAxis="y"
|
||||||
|
cdkDragBoundary=".action-wrapper"
|
||||||
|
class="flow-action"
|
||||||
|
*ngFor="let action of trigger.actionsList"
|
||||||
|
>
|
||||||
|
<i class="las la-code"></i>
|
||||||
|
<span class="flow-action-name">{{ action.name }}</span>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<span
|
||||||
|
class="state"
|
||||||
|
[ngClass]="{
|
||||||
|
active: action.state === ActionState.ACTION_STATE_ACTIVE,
|
||||||
|
inactive: action.state === ActionState.ACTION_STATE_INACTIVE,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
{{ 'FLOWS.STATES.' + action.state | translate }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</cnsl-card>
|
||||||
|
|
||||||
|
<button *ngIf="flow.type" class="add-btn" mat-raised-button color="primary" (click)="openAddTrigger(flow.type)">
|
||||||
|
<div class="cnsl-action-button">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
<span>{{ 'FLOWS.ADDTRIGGER' | translate }}</span>
|
||||||
|
<span *ngIf="selection && selection.length"> ({{ selection.length }})</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
186
console/src/app/pages/org-actions/actions.component.scss
Normal file
186
console/src/app/pages/org-actions/actions.component.scss
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
.actions-title-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
.icon {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
height: 1.2rem;
|
||||||
|
width: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin actions-theme($theme) {
|
||||||
|
$foreground: map-get($theme, foreground);
|
||||||
|
$background: map-get($theme, background);
|
||||||
|
$is-dark-theme: map-get($theme, is-dark);
|
||||||
|
$primary: map-get($theme, primary);
|
||||||
|
$primary-color: map-get($primary, 500);
|
||||||
|
|
||||||
|
.actions-enlarged-container {
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 3rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-flow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 1000px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.formfield {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-type {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
padding: 0 1.5rem;
|
||||||
|
|
||||||
|
.type-icon {
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-button-icon,
|
||||||
|
.type-icon,
|
||||||
|
span {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-icon,
|
||||||
|
.type-button-icon {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-wrapper {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.trigger-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding-left: 7px;
|
||||||
|
|
||||||
|
.fill-space {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 1rem;
|
||||||
|
color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-space {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flow-action-wrapper {
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.flow-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
cursor: move;
|
||||||
|
|
||||||
|
.flow-action-name {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fill-space {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-topbottomline {
|
||||||
|
position: absolute;
|
||||||
|
top: 26px;
|
||||||
|
bottom: 1.5rem;
|
||||||
|
left: 35px;
|
||||||
|
width: 2px;
|
||||||
|
z-index: 0;
|
||||||
|
background-color: $primary-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
align-self: flex-start;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-preview {
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
background-color: $primary-color;
|
||||||
|
box-shadow:
|
||||||
|
0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||||
|
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||||
|
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||||
|
|
||||||
|
i {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cdk-drag-animating {
|
||||||
|
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
console/src/app/pages/org-actions/actions.component.spec.ts
Normal file
24
console/src/app/pages/org-actions/actions.component.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ActionsComponent } from './actions.component';
|
||||||
|
|
||||||
|
describe('ActionsComponent', () => {
|
||||||
|
let component: ActionsComponent;
|
||||||
|
let fixture: ComponentFixture<ActionsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ActionsComponent],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ActionsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
180
console/src/app/pages/org-actions/actions.component.ts
Normal file
180
console/src/app/pages/org-actions/actions.component.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||||
|
import { Component, DestroyRef } from '@angular/core';
|
||||||
|
import { UntypedFormControl } from '@angular/forms';
|
||||||
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
|
import { ActionKeysType } from 'src/app/modules/action-keys/action-keys.component';
|
||||||
|
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
|
||||||
|
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||||
|
import { Action, ActionState, Flow, FlowType, TriggerType } from 'src/app/proto/generated/zitadel/action_pb';
|
||||||
|
import { SetTriggerActionsRequest } from 'src/app/proto/generated/zitadel/management_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 { AddFlowDialogComponent } from './add-flow-dialog/add-flow-dialog.component';
|
||||||
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'cnsl-actions',
|
||||||
|
templateUrl: './actions.component.html',
|
||||||
|
styleUrls: ['./actions.component.scss'],
|
||||||
|
})
|
||||||
|
export class ActionsComponent {
|
||||||
|
protected flow!: Flow.AsObject;
|
||||||
|
|
||||||
|
protected typeControl: UntypedFormControl = new UntypedFormControl();
|
||||||
|
|
||||||
|
protected typesForSelection: FlowType.AsObject[] = [];
|
||||||
|
|
||||||
|
protected selection: Action.AsObject[] = [];
|
||||||
|
protected InfoSectionType = InfoSectionType;
|
||||||
|
protected ActionKeysType = ActionKeysType;
|
||||||
|
|
||||||
|
protected maxActions: number | null = null;
|
||||||
|
protected ActionState = ActionState;
|
||||||
|
constructor(
|
||||||
|
private mgmtService: ManagementService,
|
||||||
|
breadcrumbService: BreadcrumbService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private toast: ToastService,
|
||||||
|
destroyRef: DestroyRef,
|
||||||
|
) {
|
||||||
|
const bread: Breadcrumb = {
|
||||||
|
type: BreadcrumbType.ORG,
|
||||||
|
routerLink: ['/org'],
|
||||||
|
};
|
||||||
|
breadcrumbService.setBreadcrumb([bread]);
|
||||||
|
|
||||||
|
this.getFlowTypes().then();
|
||||||
|
|
||||||
|
this.typeControl.valueChanges.pipe(takeUntilDestroyed(destroyRef)).subscribe((value) => {
|
||||||
|
this.loadFlow((value as FlowType.AsObject).id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFlowTypes(): Promise<void> {
|
||||||
|
try {
|
||||||
|
let resp = await this.mgmtService.listFlowTypes();
|
||||||
|
this.typesForSelection = resp.resultList;
|
||||||
|
if (!this.flow && resp.resultList[0]) {
|
||||||
|
const type = resp.resultList[0];
|
||||||
|
this.typeControl.setValue(type);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.toast.showError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadFlow(id: string) {
|
||||||
|
this.mgmtService.getFlow(id).then((flowResponse) => {
|
||||||
|
if (flowResponse.flow) {
|
||||||
|
this.flow = flowResponse.flow;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearFlow(id: string): void {
|
||||||
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.CLEAR',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'FLOWS.DIALOG.CLEAR.TITLE',
|
||||||
|
descriptionKey: 'FLOWS.DIALOG.CLEAR.DESCRIPTION',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((resp) => {
|
||||||
|
if (resp) {
|
||||||
|
this.mgmtService
|
||||||
|
.clearFlow(id)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('FLOWS.FLOWCLEARED', true);
|
||||||
|
this.loadFlow(id);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected openAddTrigger(flow: FlowType.AsObject, trigger?: TriggerType.AsObject): void {
|
||||||
|
const dialogRef = this.dialog.open(AddFlowDialogComponent, {
|
||||||
|
data: {
|
||||||
|
flowType: flow,
|
||||||
|
actions: this.selection && this.selection.length ? this.selection : [],
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((req: SetTriggerActionsRequest) => {
|
||||||
|
if (req) {
|
||||||
|
this.mgmtService
|
||||||
|
.setTriggerActions(req.getActionIdsList(), req.getFlowType(), req.getTriggerType())
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('FLOWS.FLOWCHANGED', true);
|
||||||
|
this.loadFlow(flow.id);
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(triggerActionsListIndex: number, array: any[], event: CdkDragDrop<Action.AsObject[]>) {
|
||||||
|
moveItemInArray(array, event.previousIndex, event.currentIndex);
|
||||||
|
this.saveFlow(triggerActionsListIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFlow(index: number) {
|
||||||
|
if (
|
||||||
|
this.flow.type &&
|
||||||
|
this.flow.triggerActionsList &&
|
||||||
|
this.flow.triggerActionsList[index] &&
|
||||||
|
this.flow.triggerActionsList[index]?.triggerType
|
||||||
|
) {
|
||||||
|
this.mgmtService
|
||||||
|
.setTriggerActions(
|
||||||
|
this.flow.triggerActionsList[index].actionsList.map((action) => action.id),
|
||||||
|
this.flow.type.id,
|
||||||
|
this.flow.triggerActionsList[index].triggerType?.id ?? '',
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected removeTriggerActionsList(index: number) {
|
||||||
|
if (this.flow.type && this.flow.triggerActionsList && this.flow.triggerActionsList[index]) {
|
||||||
|
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||||
|
data: {
|
||||||
|
confirmKey: 'ACTIONS.CLEAR',
|
||||||
|
cancelKey: 'ACTIONS.CANCEL',
|
||||||
|
titleKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.TITLE',
|
||||||
|
descriptionKey: 'FLOWS.DIALOG.REMOVEACTIONSLIST.DESCRIPTION',
|
||||||
|
},
|
||||||
|
width: '400px',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe((resp) => {
|
||||||
|
if (resp) {
|
||||||
|
this.mgmtService
|
||||||
|
.setTriggerActions([], this.flow?.type?.id ?? '', this.flow.triggerActionsList[index].triggerType?.id ?? '')
|
||||||
|
.then(() => {
|
||||||
|
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
|
||||||
|
this.loadFlow(this.flow?.type?.id ?? '');
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.toast.showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
console/src/app/pages/org-actions/actions.module.ts
Normal file
68
console/src/app/pages/org-actions/actions.module.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { DragDropModule } from '@angular/cdk/drag-drop';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCheckboxModule } from '@angular/material/checkbox';
|
||||||
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
import { CodemirrorModule } from '@ctrl/ngx-codemirror';
|
||||||
|
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 { CardModule } from 'src/app/modules/card/card.module';
|
||||||
|
import { FormFieldModule } from 'src/app/modules/form-field/form-field.module';
|
||||||
|
import { InfoSectionModule } from 'src/app/modules/info-section/info-section.module';
|
||||||
|
import { InputModule } from 'src/app/modules/input/input.module';
|
||||||
|
import { PaginatorModule } from 'src/app/modules/paginator/paginator.module';
|
||||||
|
import { RefreshTableModule } from 'src/app/modules/refresh-table/refresh-table.module';
|
||||||
|
import { TableActionsModule } from 'src/app/modules/table-actions/table-actions.module';
|
||||||
|
import { WarnDialogModule } from 'src/app/modules/warn-dialog/warn-dialog.module';
|
||||||
|
import { DurationToSecondsPipeModule } from 'src/app/pipes/duration-to-seconds-pipe/duration-to-seconds-pipe.module';
|
||||||
|
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
|
||||||
|
import { LocalizedDatePipeModule } from 'src/app/pipes/localized-date-pipe/localized-date-pipe.module';
|
||||||
|
import { TimestampToDatePipeModule } from 'src/app/pipes/timestamp-to-date-pipe/timestamp-to-date-pipe.module';
|
||||||
|
|
||||||
|
import { ActionTableComponent } from './action-table/action-table.component';
|
||||||
|
import { ActionsRoutingModule } from './actions-routing.module';
|
||||||
|
import { ActionsComponent } from './actions.component';
|
||||||
|
import { AddActionDialogComponent } from './add-action-dialog/add-action-dialog.component';
|
||||||
|
import { AddFlowDialogComponent } from './add-flow-dialog/add-flow-dialog.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ActionsComponent, ActionTableComponent, AddActionDialogComponent, AddFlowDialogComponent],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
ActionsRoutingModule,
|
||||||
|
TranslateModule,
|
||||||
|
MatDialogModule,
|
||||||
|
RefreshTableModule,
|
||||||
|
MatTableModule,
|
||||||
|
PaginatorModule,
|
||||||
|
MatButtonModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatIconModule,
|
||||||
|
DurationToSecondsPipeModule,
|
||||||
|
TimestampToDatePipeModule,
|
||||||
|
LocalizedDatePipeModule,
|
||||||
|
HasRoleModule,
|
||||||
|
ActionKeysModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
CardModule,
|
||||||
|
MatCheckboxModule,
|
||||||
|
InputModule,
|
||||||
|
FormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
WarnDialogModule,
|
||||||
|
DragDropModule,
|
||||||
|
InfoSectionModule,
|
||||||
|
HasRolePipeModule,
|
||||||
|
TableActionsModule,
|
||||||
|
CodemirrorModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export default class ActionsModule {}
|
@@ -37,7 +37,7 @@
|
|||||||
@import 'src/app/pages/users/user-list/user-table/user-table.component';
|
@import 'src/app/pages/users/user-list/user-table/user-table.component';
|
||||||
@import 'src/app/pages/users/user-detail/contact/contact.component';
|
@import 'src/app/pages/users/user-detail/contact/contact.component';
|
||||||
@import 'src/app/pages/projects/project-grid/project-grid.component';
|
@import 'src/app/pages/projects/project-grid/project-grid.component';
|
||||||
@import 'src/app/pages/actions/actions.component';
|
@import 'src/app/pages/org-actions/actions.component';
|
||||||
@import 'src/app/app.component.scss';
|
@import 'src/app/app.component.scss';
|
||||||
@import './styles/color.scss';
|
@import './styles/color.scss';
|
||||||
@import 'src/app/pages/instance/instance.component.scss';
|
@import 'src/app/pages/instance/instance.component.scss';
|
||||||
|
Reference in New Issue
Block a user