feat(console): improve org search performance in create project grant page #11121 (#12057)

<!--
Please inform yourself about the contribution guidelines on submitting a
PR here:
https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#submit-a-pull-request-pr.
Take note of how PR/commit titles should be written and replace the
template texts in the sections below. Don't remove any of the sections.
It is important that the commit history clearly shows what is changed
and why.
Important: By submitting a contribution you agree to the terms from our
Licensing Policy as described here:
https://github.com/zitadel/zitadel/blob/main/LICENSING.md#community-contributions.
-->

# Which Problems Are Solved

When creating a new Project Grant in the Zitadel console, the UI becomes
extremely sluggish/unusable. The issue appears tied to the organization
search dropdown attempting to load a very large dataset.

# How the Problems Are Solved

The refactored `SearchOrgAutocompleteComponent` uses server side
filtering and ondemand loading using infinite scrolling. This makes sure
on a subset of organizations is ever loaded on the client.

# Additional Changes

- Added tailwindcss for easier styling
- Removed build warnings
- Removed some of the SCSS syntax warnings

# Additional Context
- Closes #11121

---------

Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
Ramon
2026-04-22 11:46:27 +02:00
committed by GitHub
co-authored by Max Peintner
parent bfffd69d60
commit dd9dcf9f72
79 changed files with 517 additions and 582 deletions
+1 -1
View File
@@ -101,7 +101,7 @@
"node-forge": "^1.4.0", "node-forge": "^1.4.0",
"nodemon": "^3.1.14", "nodemon": "^3.1.14",
"postcss": "^8.5.8", "postcss": "^8.5.8",
"prettier": "^3.8.1", "prettier": "^3.8.3",
"prettier-plugin-organize-imports": "^4.3.0", "prettier-plugin-organize-imports": "^4.3.0",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",
"sass": "^1.98.0", "sass": "^1.98.0",
+5
View File
@@ -0,0 +1,5 @@
{
"plugins": {
"@tailwindcss/postcss": {}
}
}
+10 -35
View File
@@ -22,9 +22,7 @@
"base": "dist/console" "base": "dist/console"
}, },
"index": "src/index.html", "index": "src/index.html",
"polyfills": [ "polyfills": ["zone.js"],
"zone.js"
],
"tsConfig": "tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": [
@@ -37,23 +35,13 @@
"output": "assets/docs/img/tech" "output": "assets/docs/img/tech"
} }
], ],
"styles": [ "styles": ["src/styles.scss", "src/tailwind.css"],
"src/styles.scss" "scripts": ["./node_modules/tinycolor2/dist/tinycolor-min.js"],
],
"scripts": [
"./node_modules/tinycolor2/dist/tinycolor-min.js"
],
"stylePreprocessorOptions": { "stylePreprocessorOptions": {
"sass": { "sass": {
"silenceDeprecations": [ "silenceDeprecations": ["import", "mixed-decls"]
"import",
"mixed-decls"
]
}, },
"includePaths": [ "includePaths": ["node_modules", "."]
"node_modules",
"."
]
}, },
"allowedCommonJsDependencies": [ "allowedCommonJsDependencies": [
"opentype.js", "opentype.js",
@@ -158,29 +146,18 @@
"test": { "test": {
"builder": "@angular/build:karma", "builder": "@angular/build:karma",
"options": { "options": {
"polyfills": [ "polyfills": ["zone.js", "zone.js/testing"],
"zone.js",
"zone.js/testing"
],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": ["src/favicon.ico", "src/assets"],
"src/favicon.ico", "styles": ["src/styles.scss"],
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": [] "scripts": []
} }
}, },
"lint": { "lint": {
"builder": "@angular-eslint/builder:lint", "builder": "@angular-eslint/builder:lint",
"options": { "options": {
"lintFilePatterns": [ "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
"src/**/*.ts",
"src/**/*.html"
]
} }
} }
} }
@@ -188,9 +165,7 @@
}, },
"cli": { "cli": {
"analytics": false, "analytics": false,
"schematicCollections": [ "schematicCollections": ["@angular-eslint/schematics"],
"@angular-eslint/schematics"
],
"packageManager": "pnpm" "packageManager": "pnpm"
}, },
"schematics": { "schematics": {
+4 -1
View File
@@ -75,6 +75,7 @@
"@types/opentype.js": "^1.3.9", "@types/opentype.js": "^1.3.9",
"@types/qrcode": "^1.5.6", "@types/qrcode": "^1.5.6",
"@types/uuid": "^11.0.0", "@types/uuid": "^11.0.0",
"@tailwindcss/postcss": "^4.2.2",
"@typescript-eslint/eslint-plugin": "^8.58.0", "@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0", "@typescript-eslint/parser": "^8.58.0",
"@zitadel/client": "workspace:*", "@zitadel/client": "workspace:*",
@@ -92,8 +93,10 @@
"karma-coverage-istanbul-reporter": "^3.0.3", "karma-coverage-istanbul-reporter": "^3.0.3",
"karma-jasmine": "^5.1.0", "karma-jasmine": "^5.1.0",
"karma-jasmine-html-reporter": "^2.2.0", "karma-jasmine-html-reporter": "^2.2.0",
"prettier": "^3.8.1", "postcss": "^8.5.8",
"prettier": "^3.8.3",
"prettier-plugin-organize-imports": "^4.3.0", "prettier-plugin-organize-imports": "^4.3.0",
"tailwindcss": "^4.2.2",
"typescript": "5.9.2" "typescript": "5.9.2"
} }
} }
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.full-width { .full-width {
width: 100%; width: 100%;
@@ -58,11 +58,11 @@
} }
.dark-only { .dark-only {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
.light-only { .light-only {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
} }
} }
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.framework-change-wrapper { .framework-change-wrapper {
.framework-card-wrapper { .framework-card-wrapper {
@@ -36,7 +36,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: fit-content; width: fit-content;
// background-color: if($is-dark-theme, map-get($background, state), #e4e7e4); // background-color: if(sass($is-dark-theme): map-get($background, state); else: #e4e7e4);
// box-shadow: 0 0 3px #0000001a; // box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2); border: 1px solid rgba(#8795a1, 0.2);
padding: 0 0.5rem; padding: 0 0.5rem;
@@ -51,11 +51,11 @@
} }
.dark-only { .dark-only {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
.light-only { .light-only {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
span { span {
@@ -21,10 +21,10 @@
<span class="left cnsl-secondary-text"> <span class="left cnsl-secondary-text">
{{ 'APP.GRANT' | translate }} {{ 'APP.GRANT' | translate }}
</span> </span>
@if (configuration.grantTypesList && configuration.grantTypesList.length > 0) { @if (configuration.grantTypesList && configuration.grantTypesList.length) {
<span class="right"> <span class="right">
[ [
@for (element of configuration.grantTypesList ?? []; track element; let i = $index) { @for (element of configuration.grantTypesList; track element; let i = $index) {
<span> <span>
{{ 'APP.OIDC.GRANT.' + element | translate }} {{ 'APP.OIDC.GRANT.' + element | translate }}
{{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }} {{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }}
@@ -38,10 +38,10 @@
<span class="left cnsl-secondary-text"> <span class="left cnsl-secondary-text">
{{ 'APP.OIDC.RESPONSETYPE' | translate }} {{ 'APP.OIDC.RESPONSETYPE' | translate }}
</span> </span>
@if (configuration.responseTypesList && configuration.responseTypesList.length > 0) { @if (configuration.responseTypesList && configuration.responseTypesList.length) {
<span class="right"> <span class="right">
[ [
@for (element of configuration.responseTypesList ?? []; track element; let i = $index) { @for (element of configuration.responseTypesList; track element; let i = $index) {
<span> <span>
{{ 'APP.OIDC.RESPONSE.' + element | translate }} {{ 'APP.OIDC.RESPONSE.' + element | translate }}
{{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }} {{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }}
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.quickstart-header { .quickstart-header {
display: flex; display: flex;
@@ -86,11 +86,11 @@
} }
.dark-only { .dark-only {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
.light-only { .light-only {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
span { span {
@@ -6,9 +6,9 @@ import { Directive, ElementRef, EventEmitter, HostListener, Output } from '@angu
}) })
export class ScrollableDirective { export class ScrollableDirective {
// when using this directive, add overflow-y scroll to css // when using this directive, add overflow-y scroll to css
@Output() scrollPosition: EventEmitter<any> = new EventEmitter(); @Output() scrollPosition: EventEmitter<'bottom' | 'top'> = new EventEmitter();
constructor(public el: ElementRef) {} constructor(private readonly el: ElementRef) {}
@HostListener('scroll', ['$event']) @HostListener('scroll', ['$event'])
public onScroll(event: any): void { public onScroll(event: any): void {
@@ -11,7 +11,7 @@
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background); $back: map-get($background, background);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$secondary-text: map-get($foreground, secondary-text); $secondary-text: map-get($foreground, secondary-text);
.accounts-card { .accounts-card {
@@ -62,7 +62,7 @@
.action-keys-row { .action-keys-row {
.key-overlay { .key-overlay {
z-index: 0; z-index: 0;
background: if($is-dark-theme, #fff, #000); background: if(sass($is-dark-theme): #fff; else: #000);
opacity: 0.15; opacity: 0.15;
} }
} }
@@ -3,7 +3,7 @@
$primary-color: map-get($primary, 500); $primary-color: map-get($primary, 500);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background); $background: map-get($theme, background);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
.auth-method-radio-button-wrapper { .auth-method-radio-button-wrapper {
display: flex; display: flex;
@@ -29,7 +29,7 @@
box-sizing: border-box; box-sizing: border-box;
&:focus { &:focus {
border: 1px solid if($is-dark-theme, white, black); border: 1px solid if(sass($is-dark-theme): white; else: black);
} }
} }
@@ -39,7 +39,7 @@
box-shadow: 0 0 6px rgb(0 0 0 / 10%); box-shadow: 0 0 6px rgb(0 0 0 / 10%);
.cnsl-radio-header span { .cnsl-radio-header span {
color: if($is-dark-theme, white, white); color: if(sass($is-dark-theme): white; else: white);
} }
} }
@@ -108,7 +108,7 @@
span { span {
margin: 2rem; margin: 2rem;
font-size: 30px; font-size: 30px;
color: if($is-dark-theme, #00000050, #ffffff50); color: if(sass($is-dark-theme): #00000050; else: #ffffff50);
} }
} }
@@ -11,7 +11,7 @@
$primary-color: map-get($primary, 500); $primary-color: map-get($primary, 500);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$background: map-get($theme, background); $background: map-get($theme, background);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
input[type='radio'].app { input[type='radio'].app {
appearance: none; appearance: none;
@@ -20,7 +20,7 @@
box-sizing: border-box; box-sizing: border-box;
&:focus { &:focus {
border: 1px solid if($is-dark-theme, white, black); border: 1px solid if(sass($is-dark-theme): white; else: black);
} }
} }
@@ -31,7 +31,7 @@
box-shadow: 0 0 6px rgb(0 0 0 / 10%); box-shadow: 0 0 6px rgb(0 0 0 / 10%);
.cnsl-type-radio-header span { .cnsl-type-radio-header span {
color: if($is-dark-theme, white, white); color: if(sass($is-dark-theme): white; else: white);
} }
} }
@@ -64,7 +64,7 @@
span { span {
margin: 2rem; margin: 2rem;
font-size: 30px; font-size: 30px;
color: if($is-dark-theme, #00000050, #ffffff50); color: if(sass($is-dark-theme): #00000050; else: #ffffff50);
} }
} }
+3 -3
View File
@@ -4,8 +4,8 @@
$background: map-get($theme, background); $background: map-get($theme, background);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.card { .card {
background-color: $card-background-color; background-color: $card-background-color;
@@ -57,7 +57,7 @@
.cnsl-chip { .cnsl-chip {
height: auto; height: auto;
border: 1px solid $border-color; border: 1px solid $border-color;
background: if($is-dark-theme, map-get($background, state), #e4e7e4) !important; background: if(sass($is-dark-theme): map-get($background, state) ; else: #e4e7e4) !important;
.cnsl-chip-content { .cnsl-chip-content {
display: flex; display: flex;
@@ -1,6 +1,6 @@
@mixin domain-verification-theme($theme) { @mixin domain-verification-theme($theme) {
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
.entry { .entry {
margin: 0.5rem 0; margin: 0.5rem 0;
@@ -24,7 +24,7 @@
min-height: 80px; min-height: 80px;
&.defaulttext { &.defaulttext {
color: if($is-dark-theme, #ffffff50, #00000050); color: if(sass($is-dark-theme): #ffffff50; else: #00000050);
} }
} }
@@ -18,7 +18,7 @@
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$primary: map-get($theme, primary); $primary: map-get($theme, primary);
$primary-color: map-get($primary, 500); $primary-color: map-get($primary, 500);
@@ -9,8 +9,8 @@
$secondary-text: map-get($foreground, secondary-text); $secondary-text: map-get($foreground, secondary-text);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$link-hover-color: if($is-dark-theme, map-get($primary, 200), $primary-color); $link-hover-color: if(sass($is-dark-theme): map-get($primary, 200) ; else: $primary-color);
$link-color: if($is-dark-theme, $lighter-primary-color, $primary-color); $link-color: if(sass($is-dark-theme): $lighter-primary-color; else: $primary-color);
.filter-events-wrapper { .filter-events-wrapper {
border-radius: 0.5rem; border-radius: 0.5rem;
@@ -38,7 +38,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0 0.5rem 0.5rem 0.5rem; padding: 0 0.5rem 0.5rem 0.5rem;
border-bottom: 2px solid if($is-dark-theme, #ffffff15, #00000015); border-bottom: 2px solid if(sass($is-dark-theme): #ffffff15; else: #00000015);
.filter-middle { .filter-middle {
margin: 0 1rem; margin: 0 1rem;
@@ -73,7 +73,7 @@
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
padding: 0 0.5rem; padding: 0 0.5rem;
background-color: if($is-dark-theme, #00000020, #00000008); background-color: if(sass($is-dark-theme): #00000020; else: #00000008);
margin: 0 -0.5rem; margin: 0 -0.5rem;
.aggregate-type-select .mat-mdc-select { .aggregate-type-select .mat-mdc-select {
@@ -11,7 +11,7 @@
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background); $back: map-get($background, background);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
.cnsl-filter-button-wrapper { .cnsl-filter-button-wrapper {
position: relative; position: relative;
@@ -48,7 +48,7 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
border-bottom: 2px solid if($is-dark-theme, #ffffff15, #00000015); border-bottom: 2px solid if(sass($is-dark-theme): #ffffff15; else: #00000015);
.filter-middle { .filter-middle {
margin: 0 1rem; margin: 0 1rem;
@@ -74,7 +74,7 @@
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 0.5rem; padding: 0 0.5rem;
background-color: if($is-dark-theme, #00000020, #00000008); background-color: if(sass($is-dark-theme): #00000020; else: #00000008);
margin: 0 -0.5rem; margin: 0 -0.5rem;
.nomethod { .nomethod {
@@ -13,7 +13,7 @@
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$back: map-get($background, background); $back: map-get($background, background);
$secondary-text: map-get($foreground, secondary-text); $secondary-text: map-get($foreground, secondary-text);
$link-hover-color: if($is-dark-theme, $lighter-primary-color, $darker-primary-color); $link-hover-color: if(sass($is-dark-theme): $lighter-primary-color; else: $darker-primary-color);
.footer-wrapper { .footer-wrapper {
padding: 2rem; padding: 2rem;
@@ -71,7 +71,7 @@
.swisssvg { .swisssvg {
width: 14rem; width: 14rem;
margin: 1rem 0; margin: 1rem 0;
color: if($is-dark-theme, #ffffff60, #00000060); color: if(sass($is-dark-theme): #ffffff60; else: #00000060);
.cross { .cross {
fill: white; fill: white;
@@ -64,7 +64,7 @@
// z-index: 502; // z-index: 502;
&:hover { &:hover {
background-color: if($is-dark-theme, #ffffff10, #00000010); background-color: if(sass($is-dark-theme): #ffffff10; else: #00000010);
} }
@media only screen and (max-width: 700px) { @media only screen and (max-width: 700px) {
@@ -97,7 +97,7 @@
transition: all ease 0.2s; transition: all ease 0.2s;
&:hover { &:hover {
background-color: if($is-dark-theme, #ffffff10, #00000010); background-color: if(sass($is-dark-theme): #ffffff10; else: #00000010);
} }
.svgspan { .svgspan {
@@ -146,7 +146,7 @@
} }
&:hover { &:hover {
background-color: if($is-dark-theme, #ffffff10, #00000010); background-color: if(sass($is-dark-theme): #ffffff10; else: #00000010);
} }
.mob { .mob {
@@ -274,7 +274,7 @@
border-radius: 50%; border-radius: 50%;
transition: border ease 0.15s; transition: border ease 0.15s;
padding: 0; padding: 0;
border: 2px solid if($is-dark-theme, #ffffff80, #00000010); border: 2px solid if(sass($is-dark-theme): #ffffff80; else: #00000010);
outline: none; outline: none;
background: none; background: none;
@@ -284,7 +284,7 @@
&:hover, &:hover,
&:focus { &:focus {
border: 2px solid if($is-dark-theme, #fff, map-get($primary, 200)); border: 2px solid if(sass($is-dark-theme): #fff; else: map-get($primary, 200));
} }
.docs { .docs {
@@ -234,7 +234,7 @@
</table> </table>
</div> </div>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'IDP.EMPTY' | translate }}</span> <span>{{ 'IDP.EMPTY' | translate }}</span>
@@ -26,11 +26,11 @@
} }
&.dark { &.dark {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
&.light { &.light {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
} }
@@ -12,7 +12,7 @@
$back: map-get($background, background); $back: map-get($background, background);
.cnsl-overlay-backdrop { .cnsl-overlay-backdrop {
background-color: if($is-dark-theme, #00000080, #ffffff80); background-color: if(sass($is-dark-theme): #00000080; else: #ffffff80);
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
} }
@@ -25,7 +25,7 @@
padding: 1rem; padding: 1rem;
font-size: 14px; font-size: 14px;
width: 300px; width: 300px;
border: 1px solid if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); border: 1px solid if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
&::before { &::before {
visibility: hidden; visibility: hidden;
@@ -2,7 +2,7 @@
<div class="info-row"> <div class="info-row">
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'USER.PAGES.STATE' | translate }}</p> <p class="info-row-title">{{ 'USER.PAGES.STATE' | translate }}</p>
@if (user?.state) { @if (user.state) {
<p <p
class="state" class="state"
[ngClass]="{ [ngClass]="{
@@ -60,7 +60,7 @@
<div class="info-row"> <div class="info-row">
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'IAM.PAGES.STATE' | translate }}</p> <p class="info-row-title">{{ 'IAM.PAGES.STATE' | translate }}</p>
@if (instance?.state) { @if (instance.state) {
<p <p
class="state" class="state"
[ngClass]="{ [ngClass]="{
@@ -74,25 +74,25 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'INSTANCEID' | translate }}</p> <p class="info-row-title">{{ 'INSTANCEID' | translate }}</p>
@if (instance?.id) { @if (instance.id) {
<p class="info-row-desc">{{ instance.id }}</p> <p class="info-row-desc">{{ instance.id }}</p>
} }
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'NAME' | translate }}</p> <p class="info-row-title">{{ 'NAME' | translate }}</p>
@if (instance?.name) { @if (instance.name) {
<p class="info-row-desc">{{ instance.name }}</p> <p class="info-row-desc">{{ instance.name }}</p>
} }
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'VERSION' | translate }}</p> <p class="info-row-title">{{ 'VERSION' | translate }}</p>
@if (instance?.version) { @if (instance.version) {
<p class="info-row-desc">{{ instance.version }}</p> <p class="info-row-desc">{{ instance.version }}</p>
} }
</div> </div>
<div class="info-wrapper width"> <div class="info-wrapper width">
<p class="info-row-title">{{ 'IAM.PAGES.DOMAINLIST' | translate }}</p> <p class="info-row-title">{{ 'IAM.PAGES.DOMAINLIST' | translate }}</p>
@for (domain of instance?.domainsList; track domain) { @for (domain of instance.domainsList; track domain) {
<div class="copy-row"> <div class="copy-row">
<button <button
[disabled]="copied === domain.domain" [disabled]="copied === domain.domain"
@@ -108,7 +108,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p> <p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
@if (instance?.details?.creationDate; as creationDate) { @if (instance.details?.creationDate; as creationDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -116,7 +116,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p> <p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
@if (instance?.details?.changeDate; as changeDate) { @if (instance.details?.changeDate; as changeDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -129,7 +129,7 @@
<div class="info-row"> <div class="info-row">
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.STATE' | translate }}</p> <p class="info-row-title">{{ 'ORG.PAGES.STATE' | translate }}</p>
@if (org?.state) { @if (org.state) {
<p <p
class="state" class="state"
[ngClass]="{ [ngClass]="{
@@ -143,7 +143,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.ID' | translate }}</p> <p class="info-row-title">{{ 'ORG.PAGES.ID' | translate }}</p>
@if (org?.id) { @if (org.id) {
<p class="info-row-desc">{{ org.id }}</p> <p class="info-row-desc">{{ org.id }}</p>
} }
</div> </div>
@@ -165,7 +165,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p> <p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
@if (org?.details?.creationDate; as creationDate) { @if (org.details?.creationDate; as creationDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -173,7 +173,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p> <p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
@if (org?.details?.changeDate; as changeDate) { @if (org.details?.changeDate; as changeDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -186,7 +186,7 @@
<div class="info-row"> <div class="info-row">
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
@if (project?.state) { @if (project.state) {
<p <p
class="state" class="state"
[ngClass]="{ [ngClass]="{
@@ -200,13 +200,13 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p>
@if (project?.id) { @if (project.id) {
<p class="info-row-desc">{{ project.id }}</p> <p class="info-row-desc">{{ project.id }}</p>
} }
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
@if (project?.details?.creationDate; as creationDate) { @if (project.details?.creationDate; as creationDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -214,7 +214,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
@if (project?.details?.changeDate; as changeDate) { @if (project.details?.changeDate; as changeDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -227,7 +227,7 @@
<div class="info-row"> <div class="info-row">
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
@if (grantedProject?.state) { @if (grantedProject.state) {
<p <p
class="state" class="state"
[ngClass]="{ [ngClass]="{
@@ -241,19 +241,19 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p>
@if (grantedProject?.projectId) { @if (grantedProject.projectId) {
<p class="info-row-desc">{{ grantedProject.projectId }}</p> <p class="info-row-desc">{{ grantedProject.projectId }}</p>
} }
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.GRANT.GRANTID' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.GRANT.GRANTID' | translate }}</p>
@if (grantedProject?.grantId) { @if (grantedProject.grantId) {
<p class="info-row-desc">{{ grantedProject.grantId }}</p> <p class="info-row-desc">{{ grantedProject.grantId }}</p>
} }
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
@if (grantedProject?.details?.creationDate; as creationDate) { @if (grantedProject.details?.creationDate; as creationDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -261,7 +261,7 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p> <p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
@if (grantedProject?.details?.changeDate; as changeDate) { @if (grantedProject.details?.changeDate; as changeDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -274,7 +274,7 @@
<div class="info-row"> <div class="info-row">
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.STATE' | translate }}</p> <p class="info-row-title">{{ 'APP.PAGES.STATE' | translate }}</p>
@if (app?.state) { @if (app.state) {
<p <p
class="state" class="state"
[ngClass]="{ [ngClass]="{
@@ -286,7 +286,7 @@
</p> </p>
} }
</div> </div>
@if (app?.apiConfig?.authMethodType; as authMethodType) { @if (app.apiConfig?.authMethodType; as authMethodType) {
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p> <p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
<p class="info-row-desc"> <p class="info-row-desc">
@@ -294,7 +294,7 @@
</p> </p>
</div> </div>
} }
@if (app?.oidcConfig?.authMethodType; as authMethodType) { @if (app.oidcConfig?.authMethodType; as authMethodType) {
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p> <p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
<p class="info-row-desc"> <p class="info-row-desc">
@@ -304,13 +304,13 @@
} }
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.ID' | translate }}</p> <p class="info-row-title">{{ 'APP.PAGES.ID' | translate }}</p>
@if (app?.id) { @if (app.id) {
<p class="info-row-desc">{{ app.id }}</p> <p class="info-row-desc">{{ app.id }}</p>
} }
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.DATECREATED' | translate }}</p> <p class="info-row-title">{{ 'APP.PAGES.DATECREATED' | translate }}</p>
@if (app?.details?.creationDate; as creationDate) { @if (app.details?.creationDate; as creationDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
@@ -318,13 +318,12 @@
</div> </div>
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.DATECHANGED' | translate }}</p> <p class="info-row-title">{{ 'APP.PAGES.DATECHANGED' | translate }}</p>
@if (app?.details?.changeDate; as changeDate) { @if (app.details?.changeDate; as changeDate) {
<p class="info-row-desc"> <p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }} {{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p> </p>
} }
</div> </div>
@if (app) {
<div class="info-wrapper"> <div class="info-wrapper">
<p class="info-row-title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p> <p class="info-row-title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p>
@if (app.oidcConfig?.clientId) { @if (app.oidcConfig?.clientId) {
@@ -358,6 +357,5 @@
</div> </div>
} }
</div> </div>
}
</div> </div>
} }
@@ -104,7 +104,7 @@
} }
.keyboard-shortcuts-group { .keyboard-shortcuts-group {
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
border: 1px solid $border-color; border: 1px solid $border-color;
background-color: $card-background-color; background-color: $card-background-color;
@@ -128,7 +128,7 @@
.keyboard-shortcuts-action-key { .keyboard-shortcuts-action-key {
.keyboard-shortcuts-key-overlay { .keyboard-shortcuts-key-overlay {
background: if($is-dark-theme, #fff, #000); background: if(sass($is-dark-theme): #fff; else: #000);
} }
} }
} }
@@ -26,7 +26,7 @@
.cnsl-form-field-disabled { .cnsl-form-field-disabled {
.cnsl-label-wrapper { .cnsl-label-wrapper {
color: if($is-dark-theme, #ffffff80, #00000061); color: if(sass($is-dark-theme): #ffffff80; else: #00000061);
} }
} }
@@ -51,7 +51,7 @@
padding: 2px; padding: 2px;
margin: -2px -9px -2px 8px; margin: -2px -9px -2px 8px;
margin-left: 6px; margin-left: 6px;
background-color: if($is-dark-theme, #ffffff20, #00000020); background-color: if(sass($is-dark-theme): #ffffff20; else: #00000020);
line-height: 18px; line-height: 18px;
font-size: 11px; font-size: 11px;
transition: background 0.2s ease; transition: background 0.2s ease;
@@ -69,7 +69,7 @@
} }
&:hover { &:hover {
background: if($is-dark-theme, #ffffff40, #00000010); background: if(sass($is-dark-theme): #ffffff40; else: #00000010);
} }
&.active { &.active {
@@ -78,7 +78,7 @@
.c_label { .c_label {
.count { .count {
background-color: if($is-dark-theme, #00000020, #ffffff20); background-color: if(sass($is-dark-theme): #00000020; else: #ffffff20);
} }
} }
} }
@@ -82,7 +82,7 @@
padding: 2px; padding: 2px;
margin: -2px -9px -2px 8px; margin: -2px -9px -2px 8px;
margin-left: 6px; margin-left: 6px;
background-color: if($is-dark-theme, #ffffff20, #00000020); background-color: if(sass($is-dark-theme): #ffffff20; else: #00000020);
line-height: 18px; line-height: 18px;
font-size: 11px; font-size: 11px;
transition: background 0.2s ease; transition: background 0.2s ease;
@@ -100,7 +100,7 @@
} }
&:hover { &:hover {
background: if($is-dark-theme, #ffffff40, #00000010); background: if(sass($is-dark-theme): #ffffff40; else: #00000010);
} }
&.active { &.active {
@@ -110,7 +110,7 @@
.c_label { .c_label {
.count { .count {
display: inline-block; display: inline-block;
background-color: if($is-dark-theme, #00000020, #ffffff20); background-color: if(sass($is-dark-theme): #00000020; else: #ffffff20);
} }
} }
} }
@@ -136,7 +136,7 @@
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
.slash { .slash {
color: if($is-dark-theme, #525454, #d1d5d9); color: if(sass($is-dark-theme): #525454; else: #d1d5d9);
} }
.org-button { .org-button {
@@ -185,7 +185,7 @@
margin-right: 0.5rem; margin-right: 0.5rem;
width: 100px; width: 100px;
min-width: 50px; min-width: 50px;
background: if($is-dark-theme, #ffffff20, #00000010); background: if(sass($is-dark-theme): #ffffff20; else: #00000010);
border: none; border: none;
padding: 2px; padding: 2px;
border-radius: 50vw; border-radius: 50vw;
@@ -193,7 +193,7 @@
display: flex; display: flex;
&:hover { &:hover {
background: if($is-dark-theme, #ffffff, $primary-color); background: if(sass($is-dark-theme): #ffffff; else: $primary-color);
transition: background ease 0.15s; transition: background ease 0.15s;
} }
@@ -227,7 +227,7 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
background: if($is-dark-theme, #fff, #000); background: if(sass($is-dark-theme): #fff; else: #000);
opacity: 0.15; opacity: 0.15;
border-radius: 4px; border-radius: 4px;
} }
@@ -70,7 +70,7 @@
width: 1.5rem; width: 1.5rem;
border-radius: 50vw; border-radius: 50vw;
margin-right: 1rem; margin-right: 1rem;
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4); background-color: if(sass($is-dark-theme): map-get($background, state) ; else: #e4e7e4);
box-shadow: 0 0 3px #0000001a; box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2); border: 1px solid rgba(#8795a1, 0.2);
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.onboarding-header { .onboarding-header {
display: flex; display: flex;
@@ -141,7 +141,7 @@
width: 1.5rem; width: 1.5rem;
border-radius: 50vw; border-radius: 50vw;
margin-right: 1rem; margin-right: 1rem;
background-color: if($is-dark-theme, map-get($background, state), #e4e7e4); background-color: if(sass($is-dark-theme): map-get($background, state) ; else: #e4e7e4);
box-shadow: 0 0 3px #0000001a; box-shadow: 0 0 3px #0000001a;
border: 1px solid rgba(#8795a1, 0.2); border: 1px solid rgba(#8795a1, 0.2);
@@ -87,11 +87,11 @@
} }
&.dark { &.dark {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
&.light { &.light {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
} }
@@ -69,11 +69,11 @@
} }
&.dark { &.dark {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
&.light { &.light {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
} }
@@ -2,7 +2,7 @@
@use '../../../label/label.component.scss' as label; @use '../../../label/label.component.scss' as label;
@mixin preview-theme($show-dark) { @mixin preview-theme($show-dark) {
$theme: if($show-dark, $caos-dark-app-theme, $caos-light-app-theme); $theme: if(sass($show-dark): $caos-dark-app-theme; else: $caos-light-app-theme);
@include input.input-theme($theme); @include input.input-theme($theme);
@include label.cnsl-label-theme($theme); @include label.cnsl-label-theme($theme);
@@ -7,8 +7,8 @@
$is-dark-theme: map.get($theme, is-dark); $is-dark-theme: map.get($theme, is-dark);
$background: map.get($theme, background); $background: map.get($theme, background);
$foreground: map.get($theme, foreground); $foreground: map.get($theme, foreground);
$p-border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $p-border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
.privatelabeling-policy { .privatelabeling-policy {
.header { .header {
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.project-role-chip { .project-role-chip {
display: flex; display: flex;
@@ -118,7 +118,7 @@
<tr class="highlight" mat-row *matRowDef="let role; columns: displayedColumns"></tr> <tr class="highlight" mat-row *matRowDef="let role; columns: displayedColumns"></tr>
</table> </table>
</div> </div>
@if ((dataSource.loading$ | async) === false && !dataSource?.totalResult) { @if ((dataSource.loading$ | async) === false && !dataSource.totalResult) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'PROJECT.ROLE.EMPTY' | translate }}</span> <span>{{ 'PROJECT.ROLE.EMPTY' | translate }}</span>
@@ -25,11 +25,11 @@
} }
&.dark { &.dark {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
&.light { &.light {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
} }
@@ -1,36 +1,57 @@
<form> <form>
<cnsl-form-field class="full-width"> <cnsl-form-field>
<cnsl-label>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_FORMFIELD' | translate }}</cnsl-label> <cnsl-label>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_FORMFIELD' | translate }}</cnsl-label>
<span class="tw:relative">
<input <input
cnslInput cnslInput
type="text" type="text"
placeholder="Organization XY" class="tw:max-w-125"
#nameInput [class.tw:pr-9]="query.isFetching()"
[formControl]="myControl"
[matAutocomplete]="auto" [matAutocomplete]="auto"
[formControl]="searchControl"
name="name"
data-e2e="add-org-input" data-e2e="add-org-input"
#input
(keyup)="searchSignal.set(input.value)"
/> />
@if (query.isFetching()) {
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn"> <span class="tw:absolute tw:inset-2 tw:flex tw:items-center tw:justify-end">
@if (isLoading) { <mat-spinner diameter="25" color="primary"></mat-spinner>
<mat-option class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
}
@for (org of filteredOrgs; track org) {
<mat-option [value]="org">
<div class="org-option" data-e2e="org-option">
<div class="org-option-column">
<span>{{ org.name }}</span>
<span class="fill-space"></span>
<span class="smaller cnsl-secondary-text">{{ org.primaryDomain }}</span>
</div>
</div>
</mat-option>
}
</mat-autocomplete>
<span class="org-autocomplete-target-desc">
{{ 'PROJECT.GRANT.CREATE.SEL_ORG_DESC' | translate }}
</span> </span>
}
</span>
<mat-autocomplete
#auto="matAutocomplete"
[displayWith]="displayOrg"
[requireSelection]="true"
[autoActiveFirstOption]="true"
(optionSelected)="searchControl.value && selectionChanged.emit(searchControl.value)"
>
<div cnslScrollable class="tw:overflow-y-scroll tw:max-h-full" (scrollPosition)="onScroll($event)">
@for (page of query.data()?.pages; track $index) {
@for (org of page.result; track org.id) {
<mat-option
class="tw:max-w-full tw:relative"
[value]="org"
data-e2e="org-option"
[matTooltip]="org.name"
(mouseenter)="showTooltip.set(name.scrollWidth > name.clientWidth)"
(mouseleave)="showTooltip.set(false)"
[matTooltipDisabled]="!showTooltip()"
>
<div
class="tw:absolute tw:inset-0 tw:flex tw:h-full tw:w-full tw:min-w-0 tw:flex-col tw:justify-center"
data-e2e="org-option"
>
<span #name class="tw:block tw:mr-4 tw:ml-4 tw:truncate">{{ org.name }}</span>
<span class="tw:block tw:mr-4 tw:ml-4 tw:truncate tw:text-[13px] tw:text-gray-500">{{
org.primaryDomain
}}</span>
</div>
</mat-option>
}
}
</div>
</mat-autocomplete>
</cnsl-form-field> </cnsl-form-field>
</form> </form>
@@ -1,38 +0,0 @@
.full-width {
width: 100%;
}
input {
max-width: 500px;
}
.org-option {
display: flex;
align-items: center;
.org-option-column {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: center;
width: 100%;
span {
line-height: normal;
}
.fill-space {
flex: 1;
}
.smaller {
font-size: 13px;
}
}
}
.org-autocomplete-target-desc {
font-size: 14px;
display: block;
margin-top: 0.5rem;
}
@@ -1,24 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { SearchOrgAutocompleteComponent } from './search-org-autocomplete.component';
describe('SearchOrgComponent', () => {
let component: SearchOrgAutocompleteComponent;
let fixture: ComponentFixture<SearchOrgAutocompleteComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [SearchOrgAutocompleteComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SearchOrgAutocompleteComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,85 +1,135 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core'; import { ChangeDetectionStrategy, Component, effect, inject, output, signal } from '@angular/core';
import { UntypedFormControl } from '@angular/forms'; import { NewAuthService } from 'src/app/services/new-auth.service';
import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { injectInfiniteQuery, keepPreviousData } from '@tanstack/angular-query-experimental';
import { debounceTime, from, map, Subject, switchMap, takeUntil, tap } from 'rxjs'; import { UserService } from 'src/app/services/user.service';
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb'; import { AuthService, ListMyProjectOrgsRequestSchema } from '@zitadel/proto/zitadel/auth_pb';
import { Org, OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { AuthenticationService } from 'src/app/services/authentication.service'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { MatInputModule } from '@angular/material/input';
import { InputModule } from '../input/input.module';
import { TranslatePipe } from '@ngx-translate/core';
import { TextQueryMethod } from '@zitadel/proto/zitadel/object_pb';
import { ScrollingModule } from '@angular/cdk/scrolling';
import { Org, OrgFieldName, OrgState } from '@zitadel/proto/zitadel/org_pb';
import { MessageInitShape } from '@bufbuild/protobuf';
import { MatTooltip } from '@angular/material/tooltip';
import { requiredValidator } from '../form-field/validators/validators';
import { ScrollableDirective } from 'src/app/directives/scrollable/scrollable.directive';
import { toObservable, toSignal } from '@angular/core/rxjs-interop';
import { debounceTime } from 'rxjs';
import { MatProgressSpinner } from '@angular/material/progress-spinner';
import { ToastService } from 'src/app/services/toast.service';
@Component({ @Component({
selector: 'cnsl-search-org-autocomplete', selector: 'cnsl-search-org-autocomplete',
templateUrl: './search-org-autocomplete.component.html', templateUrl: './search-org-autocomplete.component.html',
styleUrls: ['./search-org-autocomplete.component.scss'], standalone: true,
standalone: false, changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
ScrollingModule,
MatInputModule,
MatAutocompleteModule,
ReactiveFormsModule,
InputModule,
TranslatePipe,
FormsModule,
MatTooltip,
ScrollableDirective,
MatProgressSpinner,
],
}) })
export class SearchOrgAutocompleteComponent implements OnInit, OnDestroy { export class SearchOrgAutocompleteComponent {
public myControl: UntypedFormControl = new UntypedFormControl(); public readonly selectionChanged = output<Org>();
public filteredOrgs: Array<Org.AsObject> = [];
public isLoading: boolean = false;
@ViewChild('auto') public matAutocomplete!: MatAutocomplete;
@Output() public selectionChanged: EventEmitter<Org.AsObject> = new EventEmitter();
private unsubscribed$: Subject<void> = new Subject(); protected readonly searchControl = new FormControl<Org | null>(null, { validators: [requiredValidator] });
constructor( protected readonly searchSignal = signal('');
public authService: AuthenticationService, protected readonly debouncedSearchSignal = toSignal(toObservable(this.searchSignal).pipe(debounceTime(250)), {
private auth: GrpcAuthService, initialValue: this.searchSignal(),
) { });
this.myControl.valueChanges
.pipe(
takeUntil(this.unsubscribed$),
debounceTime(200),
tap(() => (this.isLoading = true)),
switchMap((value) => {
const stateQuery = new OrgQuery();
const orgStateQuery = new OrgStateQuery();
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
stateQuery.setStateQuery(orgStateQuery);
let queries: OrgQuery[] = [stateQuery]; private readonly authService = inject(NewAuthService);
private readonly userService = inject(UserService);
if (value) { protected readonly query = injectInfiniteQuery(() => {
const nameQuery = new OrgQuery(); const search = this.debouncedSearchSignal();
const orgNameQuery = new OrgNameQuery();
orgNameQuery.setName(value); const stateQuery = [
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE); {
nameQuery.setNameQuery(orgNameQuery); query: {
queries = [stateQuery, nameQuery]; case: 'stateQuery',
value: {
state: OrgState.ACTIVE,
},
},
},
] satisfies MessageInitShape<typeof ListMyProjectOrgsRequestSchema>['queries'];
return {
queryKey: [this.userService.userId(), AuthService.name, AuthService.method.listMyProjectOrgs.name, 'infinite', search],
placeholderData: keepPreviousData,
queryFn: async ({ pageParam, signal }) =>
this.authService.listMyProjectOrgs(
{
query: {
limit: 20,
offset: BigInt(pageParam.offset),
},
sortingColumn: OrgFieldName.NAME,
queries: search
? [
...stateQuery,
{
query: {
case: 'nameQuery',
value: {
name: search,
method: TextQueryMethod.CONTAINS_IGNORE_CASE,
},
},
},
]
: stateQuery,
},
signal,
),
initialPageParam: {
offset: 0,
},
getNextPageParam: (lastPage, allPages, { offset }) => {
const nextPageParam = { offset: offset + lastPage.result.length };
const loadedCount = allPages.reduce((count, page) => count + page.result.length, 0);
if (loadedCount < lastPage.details!.totalResult) {
return nextPageParam;
} }
return from(this.auth.listMyProjectOrgs(undefined, 0, queries)).pipe( return undefined;
map((resp) => { },
return resp.resultList.sort((left, right) => left.name.localeCompare(right.name)); };
}), });
);
}), // used to only open matTooltip if the text gets truncated
) protected readonly showTooltip = signal(false);
.subscribe((returnValue) => {
this.isLoading = false; constructor() {
this.filteredOrgs = returnValue; const toastService = inject(ToastService);
effect(() => {
const error = this.query.error();
if (error) {
toastService.showError(error);
}
}); });
} }
public ngOnInit(): void { protected async onScroll(position: 'bottom' | 'top') {
const query = new OrgQuery(); if (this.query.isFetchingNextPage() || !this.query.hasNextPage() || position === 'top') {
const orgStateQuery = new OrgStateQuery(); return;
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
query.setStateQuery(orgStateQuery);
this.auth.listMyProjectOrgs(undefined, 0, [query]).then((orgs) => {
this.filteredOrgs = orgs.resultList;
});
} }
public ngOnDestroy(): void { await this.query.fetchNextPage();
this.unsubscribed$.next();
} }
public displayFn(org?: Org.AsObject): string { protected displayOrg(org: Org | null) {
return org ? `${org.name}` : ''; return org?.name ?? '';
}
public selected(event: MatAutocompleteSelectedEvent): void {
this.selectionChanged.emit(event.option.value);
} }
} }
@@ -1,32 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatChipsModule } from '@angular/material/chips';
import { MatIconModule } from '@angular/material/icon';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatSelectModule } from '@angular/material/select';
import { TranslateModule } from '@ngx-translate/core';
import { InputModule } from 'src/app/modules/input/input.module';
import { SearchOrgAutocompleteComponent } from './search-org-autocomplete.component';
@NgModule({
declarations: [SearchOrgAutocompleteComponent],
imports: [
CommonModule,
MatAutocompleteModule,
MatChipsModule,
MatButtonModule,
InputModule,
MatIconModule,
ReactiveFormsModule,
MatProgressSpinnerModule,
FormsModule,
TranslateModule,
MatSelectModule,
],
exports: [SearchOrgAutocompleteComponent],
})
export class SearchOrgAutocompleteModule {}
@@ -9,8 +9,8 @@
$secondary-text: map-get($foreground, secondary-text); $secondary-text: map-get($foreground, secondary-text);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$link-hover-color: if($is-dark-theme, map-get($primary, 200), $primary-color); $link-hover-color: if(sass($is-dark-theme): map-get($primary, 200) ; else: $primary-color);
$link-color: if($is-dark-theme, $lighter-primary-color, $primary-color); $link-color: if(sass($is-dark-theme): $lighter-primary-color; else: $primary-color);
.user-autocomplete-found { .user-autocomplete-found {
margin: 0.5rem 0; margin: 0.5rem 0;
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.shortcut-container { .shortcut-container {
width: 100%; width: 100%;
@@ -32,12 +32,12 @@
font-size: 11px; font-size: 11px;
letter-spacing: 0.05em; letter-spacing: 0.05em;
text-transform: uppercase; text-transform: uppercase;
color: if($is-dark-theme, #ffffff60, #00000060); color: if(sass($is-dark-theme): #ffffff60; else: #00000060);
margin-top: 1rem; margin-top: 1rem;
font-weight: 600; font-weight: 600;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding-top: 1rem; padding-top: 1rem;
border-top: 1px solid if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); border-top: 1px solid if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
&:nth-child(2) { &:nth-child(2) {
border-top: none; border-top: none;
@@ -131,7 +131,7 @@
</table> </table>
</div> </div>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'SMTP.LIST.EMPTY' | translate }}</span> <span>{{ 'SMTP.LIST.EMPTY' | translate }}</span>
@@ -139,7 +139,6 @@
} }
<cnsl-paginator <cnsl-paginator
#paginator
class="paginator" class="paginator"
[timestamp]="configsResult?.details?.viewTimestamp" [timestamp]="configsResult?.details?.viewTimestamp"
[length]="configsResult?.details?.totalResult || 0" [length]="configsResult?.details?.totalResult || 0"
@@ -21,11 +21,11 @@
flex-shrink: 0; flex-shrink: 0;
&.dark { &.dark {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
&.light { &.light {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
} }
@@ -9,7 +9,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
max-width: 400px; max-width: 400px;
background: if($is-dark-theme, #00000020, map-get($background, cards)); background: if(sass($is-dark-theme): #00000020; else: map-get($background, cards));
margin-left: -1rem; margin-left: -1rem;
margin-right: -1rem; margin-right: -1rem;
padding: 0 1rem 0.5rem 1rem; padding: 0 1rem 0.5rem 1rem;
@@ -3,7 +3,7 @@
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
.cnsl-table-action-wrapper { .cnsl-table-action-wrapper {
height: 36px; height: 36px;
@@ -8,7 +8,7 @@
.theme-flex { .theme-flex {
display: flex; display: flex;
align-items: center; align-items: center;
border: 1px solid if($is-dark-theme, #ffffff20, #00000020); border: 1px solid if(sass($is-dark-theme): #ffffff20; else: #00000020);
border-radius: 50vw; border-radius: 50vw;
padding: 0.25rem; padding: 0.25rem;
@@ -46,7 +46,7 @@
} }
&.active { &.active {
background-color: if($is-dark-theme, #ffffff10, #00000010) !important; background-color: if(sass($is-dark-theme): #ffffff10; else: #00000010) !important;
.moon, .moon,
.sun { .sun {
opacity: 1; opacity: 1;
@@ -196,7 +196,7 @@
></tr> ></tr>
</table> </table>
</div> </div>
@if ((dataSource.loading$ | async) === false && !dataSource?.totalResult) { @if ((dataSource.loading$ | async) === false && !dataSource.totalResult) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'GRANTS.EMPTY' | translate }}</span> <span>{{ 'GRANTS.EMPTY' | translate }}</span>
@@ -13,8 +13,8 @@
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.home-title { .home-title {
font-size: 2rem; font-size: 2rem;
@@ -50,7 +50,7 @@
<div class="flow-type"> <div class="flow-type">
<i class="type-icon las la-dot-circle"></i> <i class="type-icon las la-dot-circle"></i>
<span>{{ flow.type?.name?.localizedMessage }}</span> <span>{{ flow.type?.name?.localizedMessage }}</span>
@if (flow.type && (flow.triggerActionsList?.length ?? 0) > 0) { @if (flow.type && flow.triggerActionsList.length > 0) {
<button <button
matTooltip="{{ 'ACTIONS.CLEAR' | translate }}" matTooltip="{{ 'ACTIONS.CLEAR' | translate }}"
mat-icon-button mat-icon-button
@@ -67,12 +67,16 @@
<mat-icon svgIcon="mdi_arrow_right_bottom" class="icon"></mat-icon> <mat-icon svgIcon="mdi_arrow_right_bottom" class="icon"></mat-icon>
<span>{{ trigger.triggerType?.name?.localizedMessage }}</span> <span>{{ trigger.triggerType?.name?.localizedMessage }}</span>
<span class="fill-space"></span> <span class="fill-space"></span>
<button color="warn" mat-icon-button (click)="removeTriggerActionsList(i)"> <button color="warn" mat-icon-button (click)="removeTriggerActionsList(flow, i)">
<i class="las la-trash"></i> <i class="las la-trash"></i>
</button> </button>
</div> </div>
<span class="fill-space"></span> <span class="fill-space"></span>
<div class="flow-action-wrapper" cdkDropList (cdkDropListDropped)="drop(i, trigger.actionsList, $event)"> <div
class="flow-action-wrapper"
cdkDropList
(cdkDropListDropped)="drop(flow, i, trigger.actionsList, $event)"
>
@for (action of trigger.actionsList; track action) { @for (action of trigger.actionsList; track action) {
<div cdkDrag cdkDragLockAxis="y" cdkDragBoundary=".action-wrapper" class="flow-action"> <div cdkDrag cdkDragLockAxis="y" cdkDragBoundary=".action-wrapper" class="flow-action">
<i class="las la-code"></i> <i class="las la-code"></i>
@@ -21,7 +21,7 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
standalone: false, standalone: false,
}) })
export class ActionsComponent { export class ActionsComponent {
protected flow!: Flow.AsObject; protected flow?: Flow.AsObject;
protected typeControl: UntypedFormControl = new UntypedFormControl(); protected typeControl: UntypedFormControl = new UntypedFormControl();
@@ -124,23 +124,23 @@ export class ActionsComponent {
}); });
} }
drop(triggerActionsListIndex: number, array: any[], event: CdkDragDrop<Action.AsObject[]>) { drop(flow: Flow.AsObject, triggerActionsListIndex: number, array: any[], event: CdkDragDrop<Action.AsObject[]>) {
moveItemInArray(array, event.previousIndex, event.currentIndex); moveItemInArray(array, event.previousIndex, event.currentIndex);
this.saveFlow(triggerActionsListIndex); this.saveFlow(flow, triggerActionsListIndex);
} }
saveFlow(index: number) { saveFlow(flow: Flow.AsObject, index: number) {
if ( if (
this.flow.type && flow.type &&
this.flow.triggerActionsList && flow.triggerActionsList &&
this.flow.triggerActionsList[index] && flow.triggerActionsList[index] &&
this.flow.triggerActionsList[index]?.triggerType flow.triggerActionsList[index]?.triggerType
) { ) {
this.mgmtService this.mgmtService
.setTriggerActions( .setTriggerActions(
this.flow.triggerActionsList[index].actionsList.map((action) => action.id), flow.triggerActionsList[index].actionsList.map((action) => action.id),
this.flow.type.id, flow.type.id,
this.flow.triggerActionsList[index].triggerType?.id ?? '', flow.triggerActionsList[index].triggerType?.id ?? '',
) )
.then(() => { .then(() => {
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true); this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
@@ -151,8 +151,8 @@ export class ActionsComponent {
} }
} }
protected removeTriggerActionsList(index: number) { protected removeTriggerActionsList(flow: Flow.AsObject, index: number) {
if (this.flow.type && this.flow.triggerActionsList && this.flow.triggerActionsList[index]) { if (flow.type && flow.triggerActionsList && flow.triggerActionsList[index]) {
const dialogRef = this.dialog.open(WarnDialogComponent, { const dialogRef = this.dialog.open(WarnDialogComponent, {
data: { data: {
confirmKey: 'ACTIONS.CLEAR', confirmKey: 'ACTIONS.CLEAR',
@@ -166,10 +166,10 @@ export class ActionsComponent {
dialogRef.afterClosed().subscribe((resp) => { dialogRef.afterClosed().subscribe((resp) => {
if (resp) { if (resp) {
this.mgmtService this.mgmtService
.setTriggerActions([], this.flow?.type?.id ?? '', this.flow.triggerActionsList[index].triggerType?.id ?? '') .setTriggerActions([], flow?.type?.id ?? '', flow.triggerActionsList[index].triggerType?.id ?? '')
.then(() => { .then(() => {
this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true); this.toast.showInfo('FLOWS.TOAST.ACTIONSSET', true);
this.loadFlow(this.flow?.type?.id ?? ''); this.loadFlow(flow?.type?.id ?? '');
}) })
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
@@ -68,12 +68,12 @@
</cnsl-info-section> </cnsl-info-section>
} }
@if (app && app.oidcConfig && app.oidcConfig.complianceProblemsList && app.oidcConfig.complianceProblemsList?.length) { @if (app && app.oidcConfig && app.oidcConfig.complianceProblemsList && app.oidcConfig.complianceProblemsList.length) {
<div class="compliance"> <div class="compliance">
<h2 class="compliance-title">{{ 'APP.COMPLIANCE' | translate }}</h2> <h2 class="compliance-title">{{ 'APP.COMPLIANCE' | translate }}</h2>
<cnsl-info-section class="problem" [type]="InfoSectionType.ALERT"> <cnsl-info-section class="problem" [type]="InfoSectionType.ALERT">
<ul style="margin: 0"> <ul style="margin: 0">
@for (problem of app.oidcConfig?.complianceProblemsList || []; track problem) { @for (problem of app.oidcConfig.complianceProblemsList; track problem) {
<li style="margin: 0 0 0.5rem 0"> <li style="margin: 0 0 0.5rem 0">
{{ problem.localizedMessage }} {{ problem.localizedMessage }}
</li> </li>
@@ -444,7 +444,7 @@
@if (currentSetting.id === 'additional-origins') { @if (currentSetting.id === 'additional-origins') {
<cnsl-card title=" {{ 'APP.ADDITIONALORIGINS' | translate }}"> <cnsl-card title=" {{ 'APP.ADDITIONALORIGINS' | translate }}">
<p class="app-desc cnsl-secondary-text">{{ 'APP.ADDITIONALORIGINSDESC' | translate }}</p> <p class="app-desc cnsl-secondary-text">{{ 'APP.ADDITIONALORIGINSDESC' | translate }}</p>
@if (additionalOriginsList && appType && appType?.value !== undefined) { @if (additionalOriginsList && appType?.value !== undefined) {
<cnsl-additional-origins <cnsl-additional-origins
class="app-input" class="app-input"
[canWrite]="canWrite" [canWrite]="canWrite"
@@ -507,7 +507,7 @@
description="{{ 'USER.MACHINE.KEYSDESC' | translate }}" description="{{ 'USER.MACHINE.KEYSDESC' | translate }}"
> >
<cnsl-client-keys [projectId]="projectId" [appId]="app.id"></cnsl-client-keys> <cnsl-client-keys [projectId]="projectId" [appId]="app.id"></cnsl-client-keys>
@if (apiForm && app?.apiConfig) { @if (apiForm && app.apiConfig) {
<div class="btn-container"> <div class="btn-container">
<button <button
class="submit-button" class="submit-button"
@@ -160,7 +160,7 @@
.current-auth-method { .current-auth-method {
display: flex; display: flex;
align-items: center; align-items: center;
border: 1px solid if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); border: 1px solid if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
color: white; color: white;
border-radius: 0.5rem; border-radius: 0.5rem;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
@@ -115,8 +115,8 @@ h1 {
$list-background-color: map-get($background, 300); $list-background-color: map-get($background, 300);
$card-background-color: map-get($background, cards); $card-background-color: map-get($background, cards);
$border-color: if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); $border-color: if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
$border-selected-color: if($is-dark-theme, #fff, #000); $border-selected-color: if(sass($is-dark-theme): #fff; else: #000);
.integrate-layout-container { .integrate-layout-container {
background: map-get($background, metadata-section); background: map-get($background, metadata-section);
@@ -172,11 +172,11 @@ h1 {
} }
.dark-only { .dark-only {
display: if($is-dark-theme, block, none); display: if(sass($is-dark-theme): block; else: none);
} }
.light-only { .light-only {
display: if($is-dark-theme, none, block); display: if(sass($is-dark-theme): none; else: block);
} }
span { span {
@@ -6,7 +6,7 @@
> >
@if (currentCreateStep === 1) { @if (currentCreateStep === 1) {
<h1>{{ 'PROJECT.GRANT.CREATE.SEL_ORG' | translate }}</h1> <h1>{{ 'PROJECT.GRANT.CREATE.SEL_ORG' | translate }}</h1>
<cnsl-search-org-autocomplete class="block" (selectionChanged)="selectOrg($event)"> </cnsl-search-org-autocomplete> <cnsl-search-org-autocomplete (selectionChanged)="selectOrg($event)" class="block"> </cnsl-search-org-autocomplete>
@if (org) { @if (org) {
<span> {{ 'PROJECT.GRANT.CREATE.FOR_ORG' | translate }} {{ org.name }} - {{ org.primaryDomain }} </span> <span> {{ 'PROJECT.GRANT.CREATE.FOR_ORG' | translate }} {{ org.name }} - {{ org.primaryDomain }} </span>
} }
@@ -1,11 +1,11 @@
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { Component, OnDestroy, OnInit } from '@angular/core'; import { Component, DestroyRef, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Subject, takeUntil } from 'rxjs';
import { Org } from 'src/app/proto/generated/zitadel/org_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 { ToastService } from 'src/app/services/toast.service';
import { Org } from '@zitadel/proto/zitadel/org_pb';
import { NewMgmtService } from 'src/app/services/new-mgmt.service';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
const ROUTEPARAM = 'projectid'; const ROUTEPARAM = 'projectid';
@@ -15,8 +15,8 @@ const ROUTEPARAM = 'projectid';
styleUrls: ['./project-grant-create.component.scss'], styleUrls: ['./project-grant-create.component.scss'],
standalone: false, standalone: false,
}) })
export class ProjectGrantCreateComponent implements OnInit, OnDestroy { export class ProjectGrantCreateComponent implements OnInit {
public org?: Org.AsObject; public org?: Org;
public projectId: string = ''; public projectId: string = '';
public grantId: string = ''; public grantId: string = '';
public rolesKeyList: string[] = []; public rolesKeyList: string[] = [];
@@ -24,17 +24,17 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
public createSteps: number = 2; public createSteps: number = 2;
public currentCreateStep: number = 1; public currentCreateStep: number = 1;
private destroy$: Subject<void> = new Subject();
constructor( constructor(
private route: ActivatedRoute, private readonly route: ActivatedRoute,
private toast: ToastService, private readonly toast: ToastService,
private mgmtService: ManagementService, private readonly mgmtService: NewMgmtService,
private _location: Location, private readonly _location: Location,
private breadcrumbService: BreadcrumbService, private readonly breadcrumbService: BreadcrumbService,
private readonly destroyRef: DestroyRef,
) {} ) {}
public ngOnInit(): void { public ngOnInit(): void {
this.route.params.pipe(takeUntil(this.destroy$)).subscribe((params) => { this.route.params.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => {
this.projectId = params['projectid']; this.projectId = params['projectid'];
const breadcrumbs = [ const breadcrumbs = [
@@ -53,25 +53,6 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
}); });
} }
public ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
public searchOrg(domain: string): void {
this.mgmtService
.getOrgByDomainGlobal(domain)
.then((ret) => {
if (ret.org) {
const tmp = ret.org;
this.org = tmp;
}
})
.catch((error) => {
this.toast.showError(error);
});
}
public close(): void { public close(): void {
this._location.back(); this._location.back();
} }
@@ -79,7 +60,7 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
public addGrant(): void { public addGrant(): void {
if (this.org) { if (this.org) {
this.mgmtService this.mgmtService
.addProjectGrant(this.org.id, this.projectId, this.rolesKeyList) .addProjectGrant({ grantedOrgId: this.org.id, projectId: this.projectId, roleKeys: this.rolesKeyList })
.then(() => { .then(() => {
this.close(); this.close();
}) })
@@ -89,7 +70,7 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
} }
} }
public selectOrg(org: Org.AsObject): void { public selectOrg(org: Org): void {
this.org = org; this.org = org;
} }
@@ -14,8 +14,8 @@ import { CreateLayoutModule } from 'src/app/modules/create-layout/create-layout.
import { InputModule } from 'src/app/modules/input/input.module'; import { InputModule } from 'src/app/modules/input/input.module';
import { ProjectRolesTableModule } from 'src/app/modules/project-roles-table/project-roles-table.module'; import { ProjectRolesTableModule } from 'src/app/modules/project-roles-table/project-roles-table.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { SearchOrgAutocompleteComponent } from 'src/app/modules/search-org-autocomplete/search-org-autocomplete.component';
import { SearchOrgAutocompleteModule } from 'src/app/modules/search-org-autocomplete/search-org-autocomplete.module';
import { ProjectGrantCreateRoutingModule } from './project-grant-create-routing.module'; import { ProjectGrantCreateRoutingModule } from './project-grant-create-routing.module';
import { ProjectGrantCreateComponent } from './project-grant-create.component'; import { ProjectGrantCreateComponent } from './project-grant-create.component';
@@ -39,7 +39,7 @@ import { ProjectGrantCreateComponent } from './project-grant-create.component';
MatProgressSpinnerModule, MatProgressSpinnerModule,
FormsModule, FormsModule,
TranslateModule, TranslateModule,
SearchOrgAutocompleteModule, SearchOrgAutocompleteComponent,
], ],
}) })
export default class ProjectGrantCreateModule {} export default class ProjectGrantCreateModule {}
@@ -28,13 +28,13 @@
</div> </div>
<mat-menu #actions="matMenu" xPosition="before"> <mat-menu #actions="matMenu" xPosition="before">
@if (grant?.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE) { @if (grant && grant.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE) {
<button mat-menu-item (click)="changeState(ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE)"> <button mat-menu-item (click)="changeState(grant, ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE)">
{{ 'USER.PAGES.DEACTIVATE' | translate }} {{ 'USER.PAGES.DEACTIVATE' | translate }}
</button> </button>
} }
@if (grant?.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE) { @if (grant && grant.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE) {
<button mat-menu-item (click)="changeState(ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE)"> <button mat-menu-item (click)="changeState(grant, ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE)">
{{ 'USER.PAGES.REACTIVATE' | translate }} {{ 'USER.PAGES.REACTIVATE' | translate }}
</button> </button>
} }
@@ -48,8 +48,8 @@
<cnsl-project-grant-illustration <cnsl-project-grant-illustration
[grantedProject]="grant" [grantedProject]="grant"
[projectRoleOptions]="projectRoleOptions" [projectRoleOptions]="projectRoleOptions"
(roleRemoved)="removeRole($event)" (roleRemoved)="removeRole(grant, $event)"
(editRoleClicked)="editRoles()" (editRoleClicked)="editRoles(grant)"
> >
</cnsl-project-grant-illustration> </cnsl-project-grant-illustration>
} }
@@ -63,15 +63,15 @@
[canWrite]="['project.grant.member.write', 'project.grant.member.write:' + grant.grantId] | hasRole | async" [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" [canDelete]="['project.grant.member.delete', 'project.grant.member.delete:' + grant.grantId] | hasRole | async"
[memberRoleOptions]="memberRoleOptions" [memberRoleOptions]="memberRoleOptions"
(updateRoles)="updateMemberRoles($event.member, $event.change)" (updateRoles)="updateMemberRoles(grant, $event.member, $event.change)"
(deleteMember)="removeProjectMember($event)" (deleteMember)="removeProjectMember(grant, $event)"
[factoryLoadFunc]="changePageFactory" [factoryLoadFunc]="changePageFactory"
(changedSelection)="selection = $event" (changedSelection)="selection = $event"
[refreshTrigger]="changePage" [refreshTrigger]="changePage"
> >
<button <button
selectactions selectactions
(click)="removeProjectMemberSelection()" (click)="removeProjectMemberSelection(grant)"
[disabled]=" [disabled]="
(['project.grant.member.delete', 'project.grant.member.delete:' + grant.grantId] | hasRole | async) === false (['project.grant.member.delete', 'project.grant.member.delete:' + grant.grantId] | hasRole | async) === false
" "
@@ -82,7 +82,7 @@
<div class="cnsl-action-button"> <div class="cnsl-action-button">
<i class="las la-trash"></i> <i class="las la-trash"></i>
<span>{{ 'ACTIONS.SELECTIONDELETE' | translate }}</span> <span>{{ 'ACTIONS.SELECTIONDELETE' | translate }}</span>
<cnsl-action-keys [type]="ActionKeysType.DELETE" (actionTriggered)="removeProjectMemberSelection()"> <cnsl-action-keys [type]="ActionKeysType.DELETE" (actionTriggered)="removeProjectMemberSelection(grant)">
</cnsl-action-keys> </cnsl-action-keys>
</div> </div>
</button> </button>
@@ -92,13 +92,13 @@
[disabled]=" [disabled]="
(['project.grant.member.write', 'project.grant.member.write:' + grant.grantId] | hasRole | async) === false (['project.grant.member.write', 'project.grant.member.write:' + grant.grantId] | hasRole | async) === false
" "
(click)="openAddMember()" (click)="openAddMember(grant)"
color="primary" color="primary"
mat-raised-button mat-raised-button
> >
<div class="cnsl-action-button"> <div class="cnsl-action-button">
<mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }} <mat-icon class="icon">add</mat-icon>{{ 'ACTIONS.NEW' | translate }}
<cnsl-action-keys (actionTriggered)="openAddMember()"> </cnsl-action-keys> <cnsl-action-keys (actionTriggered)="openAddMember(grant)"> </cnsl-action-keys>
</div> </div>
</button> </button>
</cnsl-members-table> </cnsl-members-table>
@@ -27,7 +27,7 @@ import { ProjectGrantMembersDataSource } from './project-grant-members-datasourc
export class ProjectGrantDetailComponent { export class ProjectGrantDetailComponent {
public INITIALPAGESIZE: number = 25; public INITIALPAGESIZE: number = 25;
public grant!: GrantedProject.AsObject; public grant: GrantedProject.AsObject | undefined;
public projectid: string = ''; public projectid: string = '';
public grantid: string = ''; public grantid: string = '';
@@ -99,13 +99,13 @@ export class ProjectGrantDetailComponent {
}); });
} }
public changeState(newState: ProjectGrantState): void { public changeState(grant: GrantedProject.AsObject, newState: ProjectGrantState): void {
if (newState === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE) { if (newState === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE) {
this.mgmtService this.mgmtService
.reactivateProjectGrant(this.grantid, this.projectid) .reactivateProjectGrant(this.grantid, this.projectid)
.then(() => { .then(() => {
this.toast.showInfo('PROJECT.TOAST.REACTIVATED', true); this.toast.showInfo('PROJECT.TOAST.REACTIVATED', true);
this.grant.state = newState; grant.state = newState;
}) })
.catch((error) => { .catch((error) => {
this.toast.showError(error); this.toast.showError(error);
@@ -115,7 +115,7 @@ export class ProjectGrantDetailComponent {
.deactivateProjectGrant(this.grantid, this.projectid) .deactivateProjectGrant(this.grantid, this.projectid)
.then(() => { .then(() => {
this.toast.showInfo('PROJECT.TOAST.DEACTIVATED', true); this.toast.showInfo('PROJECT.TOAST.DEACTIVATED', true);
this.grant.state = newState; grant.state = newState;
setTimeout(() => { setTimeout(() => {
this.changePage.emit(); this.changePage.emit();
}, 1000); }, 1000);
@@ -144,56 +144,25 @@ export class ProjectGrantDetailComponent {
}); });
} }
updateRoles(selectionChange: string[]): void { public async removeProjectMemberSelection(grant: GrantedProject.AsObject) {
this.mgmtService await Promise.all(this.selection.map((member) => this.removeProjectMember(grant, member)));
.updateProjectGrant(this.grant.grantId, this.grant.projectId, selectionChange)
.then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUPDATED', true);
setTimeout(() => {
this.changePage.emit();
}, 1000);
})
.catch((error) => {
this.toast.showError(error);
this.changePage.emit();
});
} }
public removeProjectMemberSelection(): void { public async removeProjectMember(grant: GrantedProject.AsObject, member: Member.AsObject) {
Promise.all( try {
this.selection.map((member) => { await this.mgmtService.removeProjectGrantMember(grant.projectId, grant.grantId, member.userId);
return this.mgmtService
.removeProjectGrantMember(this.grant.projectId, this.grant.grantId, member.userId)
.then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTMEMBERREMOVED', true); this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTMEMBERREMOVED', true);
setTimeout(() => {
this.changePage.emit(); await new Promise<void>((res) => setTimeout(res, 1000));
}, 1000); } catch (error) {
})
.catch((error) => {
this.changePage.emit();
this.toast.showError(error); this.toast.showError(error);
}); } finally {
}), this.changePage.emit();
); }
} }
public removeProjectMember(member: Member.AsObject): void { public async openAddMember(grant: GrantedProject.AsObject): Promise<any> {
this.mgmtService
.removeProjectGrantMember(this.grant.projectId, this.grant.grantId, member.userId)
.then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTMEMBERREMOVED', true);
setTimeout(() => {
this.changePage.emit();
}, 1000);
})
.catch((error) => {
this.changePage.emit();
this.toast.showError(error);
});
}
public async openAddMember(): Promise<any> {
const dialogRef = this.dialog.open(MemberCreateDialogComponent, { const dialogRef = this.dialog.open(MemberCreateDialogComponent, {
data: { data: {
creationType: CreationType.PROJECT_GRANTED, creationType: CreationType.PROJECT_GRANTED,
@@ -210,7 +179,7 @@ export class ProjectGrantDetailComponent {
const userIds = users.map((user) => user.id); const userIds = users.map((user) => user.id);
Promise.all( Promise.all(
userIds.map((userid: string) => { userIds.map((userid: string) => {
return this.mgmtService.addProjectGrantMember(this.grant.projectId, this.grant.grantId, userid, resp.roles); return this.mgmtService.addProjectGrantMember(grant.projectId, grant.grantId, userid, resp.roles);
}), }),
) )
.then(() => { .then(() => {
@@ -228,9 +197,9 @@ export class ProjectGrantDetailComponent {
}); });
} }
updateMemberRoles(member: Member.AsObject, selectionChange: string[]): void { updateMemberRoles(grant: GrantedProject.AsObject, member: Member.AsObject, selectionChange: string[]): void {
this.mgmtService this.mgmtService
.updateProjectGrantMember(this.grant.projectId, this.grant.grantId, member.userId, selectionChange) .updateProjectGrantMember(grant.projectId, grant.grantId, member.userId, selectionChange)
.then(() => { .then(() => {
setTimeout(() => { setTimeout(() => {
this.changePage.emit(); this.changePage.emit();
@@ -243,13 +212,13 @@ export class ProjectGrantDetailComponent {
}); });
} }
removeRole(role: string): void { removeRole(grant: GrantedProject.AsObject, role: string): void {
const index = this.grant.grantedRoleKeysList.findIndex((r) => r === role); const index = grant.grantedRoleKeysList.findIndex((r) => r === role);
if (index > -1) { if (index > -1) {
this.grant.grantedRoleKeysList.splice(index, 1); grant.grantedRoleKeysList.splice(index, 1);
this.mgmtService this.mgmtService
.updateProjectGrant(this.grant.grantId, this.grant.projectId, this.grant.grantedRoleKeysList) .updateProjectGrant(grant.grantId, grant.projectId, grant.grantedRoleKeysList)
.then(() => { .then(() => {
setTimeout(() => { setTimeout(() => {
this.changePage.emit(); this.changePage.emit();
@@ -263,13 +232,13 @@ export class ProjectGrantDetailComponent {
} }
} }
public editRoles(): void { public editRoles(grant: GrantedProject.AsObject): void {
const dialogRef = this.dialog.open<UserGrantRoleDialogComponent, UserGrantRoleDialogData, UserGrantRoleDialogResult>( const dialogRef = this.dialog.open<UserGrantRoleDialogComponent, UserGrantRoleDialogData, UserGrantRoleDialogResult>(
UserGrantRoleDialogComponent, UserGrantRoleDialogComponent,
{ {
data: { data: {
projectId: this.grant.projectId, projectId: grant.projectId,
selectedRoleKeysList: this.grant.grantedRoleKeysList, selectedRoleKeysList: grant.grantedRoleKeysList,
i18nTitle: 'PROJECT.GRANT.EDITTITLE', i18nTitle: 'PROJECT.GRANT.EDITTITLE',
}, },
width: '600px', width: '600px',
@@ -281,10 +250,10 @@ export class ProjectGrantDetailComponent {
return; return;
} }
this.mgmtService this.mgmtService
.updateProjectGrant(this.grant.grantId, this.grant.projectId, resp.roles) .updateProjectGrant(grant.grantId, grant.projectId, resp.roles)
.then(() => { .then(() => {
this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUPDATED', true); this.toast.showInfo('PROJECT.GRANT.TOAST.PROJECTGRANTUPDATED', true);
this.grant.grantedRoleKeysList = resp.roles; grant.grantedRoleKeysList = resp.roles;
setTimeout(() => { setTimeout(() => {
this.changePage.emit(); this.changePage.emit();
}, 1000); }, 1000);
@@ -1,9 +1,9 @@
@mixin project-grant-illustration-theme($theme) { @mixin project-grant-illustration-theme($theme) {
$foreground: map-get($theme, foreground); $foreground: map-get($theme, foreground);
$is-dark-theme: map-get($theme, is-dark); $is-dark-theme: map-get($theme, is-dark);
$overlayColor: if($is-dark-theme, #ffffff10, #00000008); $overlayColor: if(sass($is-dark-theme): #ffffff10; else: #00000008);
$highlightedOverlayColor: if($is-dark-theme, #68c4cf14, #68c4cf14); $highlightedOverlayColor: if(sass($is-dark-theme): #68c4cf14; else: #68c4cf14);
$borderColor: if($is-dark-theme, #ffffff20, #00000020); $borderColor: if(sass($is-dark-theme): #ffffff20; else: #00000020);
.pgi { .pgi {
display: flex; display: flex;
@@ -136,7 +136,7 @@
<tr class="highlight" mat-row *matRowDef="let grant; columns: displayedColumns"></tr> <tr class="highlight" mat-row *matRowDef="let grant; columns: displayedColumns"></tr>
</table> </table>
</div> </div>
@if ((dataSource.loading$ | async) === false && !dataSource?.totalResult) { @if ((dataSource.loading$ | async) === false && !dataSource.totalResult) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'PROJECT.GRANT.EMPTY' | translate }}</span> <span>{{ 'PROJECT.GRANT.EMPTY' | translate }}</span>
@@ -158,7 +158,7 @@
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table> </table>
</div> </div>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'PROJECT.TABLE.EMPTY' | translate }}</span> <span>{{ 'PROJECT.TABLE.EMPTY' | translate }}</span>
@@ -73,7 +73,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table> </table>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'USER.PASSWORDLESS.EMPTY' | translate }}</span> <span>{{ 'USER.PASSWORDLESS.EMPTY' | translate }}</span>
@@ -93,7 +93,7 @@
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table> </table>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'USER.MFA.EMPTY' | translate }}</span> <span>{{ 'USER.MFA.EMPTY' | translate }}</span>
@@ -74,7 +74,7 @@
</table> </table>
</div> </div>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'USER.EXTERNALIDP.EMPTY' | translate }}</span> <span>{{ 'USER.EXTERNALIDP.EMPTY' | translate }}</span>
@@ -72,7 +72,7 @@
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr> <tr class="highlight" mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table> </table>
@if ((loading$ | async) === false && !dataSource?.data?.length) { @if ((loading$ | async) === false && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'USER.PASSWORDLESS.EMPTY' | translate }}</span> <span>{{ 'USER.PASSWORDLESS.EMPTY' | translate }}</span>
@@ -233,7 +233,7 @@
></tr> ></tr>
</table> </table>
</div> </div>
@if (!loading() && !dataSource?.data?.length) { @if (!loading() && !dataSource.data.length) {
<div class="no-content-row"> <div class="no-content-row">
<i class="las la-exclamation"></i> <i class="las la-exclamation"></i>
<span>{{ 'USER.TABLE.EMPTY' | translate }}</span> <span>{{ 'USER.TABLE.EMPTY' | translate }}</span>
+6 -1
View File
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { GrpcService } from './grpc.service'; import { GrpcService } from './grpc.service';
import { create } from '@bufbuild/protobuf'; import { create, MessageInitShape } from '@bufbuild/protobuf';
import { import {
AddMyAuthFactorOTPSMSResponse, AddMyAuthFactorOTPSMSResponse,
GetMyLoginPolicyResponse, GetMyLoginPolicyResponse,
@@ -19,6 +19,7 @@ import {
ListMyMetadataResponse, ListMyMetadataResponse,
VerifyMyPhoneResponse, VerifyMyPhoneResponse,
ListMyZitadelPermissionsResponse, ListMyZitadelPermissionsResponse,
ListMyProjectOrgsRequestSchema,
} from '@zitadel/proto/zitadel/auth_pb'; } from '@zitadel/proto/zitadel/auth_pb';
import { injectQuery } from '@tanstack/angular-query-experimental'; import { injectQuery } from '@tanstack/angular-query-experimental';
import { UserService } from './user.service'; import { UserService } from './user.service';
@@ -82,4 +83,8 @@ export class NewAuthService {
queryFn: () => this.listMyZitadelPermissions().then(({ result }) => result), queryFn: () => this.listMyZitadelPermissions().then(({ result }) => result),
})); }));
} }
public listMyProjectOrgs(req: MessageInitShape<typeof ListMyProjectOrgsRequestSchema>, signal?: AbortSignal) {
return this.grpcService.authNew.listMyProjectOrgs(req, { signal });
}
} }
@@ -2,6 +2,7 @@ import { Injectable } from '@angular/core';
import { GrpcService } from './grpc.service'; import { GrpcService } from './grpc.service';
import { import {
AddOrgResponse, AddOrgResponse,
AddProjectGrantRequestSchema,
DeactivateOrgResponse, DeactivateOrgResponse,
GenerateMachineSecretRequestSchema, GenerateMachineSecretRequestSchema,
GenerateMachineSecretResponse, GenerateMachineSecretResponse,
@@ -123,4 +124,8 @@ export class NewMgmtService {
public addOrg(name: string): Promise<AddOrgResponse> { public addOrg(name: string): Promise<AddOrgResponse> {
return this.grpcService.mgmtNew.addOrg({ name }); return this.grpcService.mgmtNew.addOrg({ name });
} }
public addProjectGrant(req: MessageInitShape<typeof AddProjectGrantRequestSchema>) {
return this.grpcService.mgmtNew.addProjectGrant(req);
}
} }
+2 -2
View File
@@ -4,11 +4,11 @@
$is-dark-theme: map.get($theme, is-dark); $is-dark-theme: map.get($theme, is-dark);
.cm-s-material.CodeMirror { .cm-s-material.CodeMirror {
background-color: if($is-dark-theme, #00000020, #263238); background-color: if(sass($is-dark-theme): #00000020; else: #263238);
color: #eff; color: #eff;
font-size: 14px; font-size: 14px;
border-radius: 4px; border-radius: 4px;
border: 1px solid if($is-dark-theme, #403e3e, #00000040); border: 1px solid if(sass($is-dark-theme): #403e3e; else: #00000040);
} }
} }
+6 -6
View File
@@ -20,8 +20,8 @@
transform: all 0.2 linear; transform: all 0.2 linear;
font-size: 1rem; font-size: 1rem;
border: none; border: none;
border: 1px solid if($is-dark-theme, #f9f7f775, #1a191954); border: 1px solid if(sass($is-dark-theme): #f9f7f775; else: #1a191954);
background-color: if($is-dark-theme, #00000040, #00000004); background-color: if(sass($is-dark-theme): #00000040; else: #00000004);
border-radius: 4px; border-radius: 4px;
height: 40px; height: 40px;
padding: 10px; padding: 10px;
@@ -34,7 +34,7 @@
margin-bottom: 2px; margin-bottom: 2px;
&:hover { &:hover {
border-color: if($is-dark-theme, #e0e0e0, #1a1b1b); border-color: if(sass($is-dark-theme): #e0e0e0; else: #1a1b1b);
} }
&:active, &:active,
@@ -49,11 +49,11 @@
} }
&[disabled] { &[disabled] {
border-color: if($is-dark-theme, #36373850, #cccdce50); border-color: if(sass($is-dark-theme): #36373850; else: #cccdce50);
color: if($is-dark-theme, #ffffff80, #00000061); color: if(sass($is-dark-theme): #ffffff80; else: #00000061);
&::placeholder { &::placeholder {
color: if($is-dark-theme, #ffffff80, #00000061); color: if(sass($is-dark-theme): #ffffff80; else: #00000061);
} }
} }
} }
+14 -14
View File
@@ -88,7 +88,7 @@
} }
.no-content-row { .no-content-row {
background-color: if($is-dark-theme, #ffffff10, #f4f4f470); background-color: if(sass($is-dark-theme): #ffffff10; else: #f4f4f470);
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
display: flex; display: flex;
@@ -123,41 +123,41 @@
font-weight: 500; font-weight: 500;
font-size: 12px; font-size: 12px;
white-space: nowrap; white-space: nowrap;
background-color: if($is-dark-theme, map.get($background, state), #e4e7e4); background-color: if(sass($is-dark-theme): map.get($background, state) ; else: #e4e7e4);
box-shadow: 0 0 3px #0000001a; box-shadow: 0 0 3px #0000001a;
&.active { &.active {
background-color: if($is-dark-theme, map.get($background, state-active), #cbf4c9); background-color: if(sass($is-dark-theme): map.get($background, state-active) ; else: #cbf4c9);
color: if($is-dark-theme, #cbf4c9, #0e6245); color: if(sass($is-dark-theme): #cbf4c9; else: #0e6245);
} }
&.inactive { &.inactive {
color: if($is-dark-theme, #ffc1c1, #620e0e); color: if(sass($is-dark-theme): #ffc1c1; else: #620e0e);
background-color: if($is-dark-theme, map.get($background, state-inactive), #ffc1c1); background-color: if(sass($is-dark-theme): map.get($background, state-inactive) ; else: #ffc1c1);
} }
&.neutral { &.neutral {
background: if($is-dark-theme, #01489c78, #47a8ff82); background: if(sass($is-dark-theme): #01489c78; else: #47a8ff82);
color: if($is-dark-theme, #47a8ff, #01489c); color: if(sass($is-dark-theme): #47a8ff; else: #01489c);
} }
} }
.bg-state { .bg-state {
background-color: if($is-dark-theme, map.get($background, state), #e4e7e4) !important; background-color: if(sass($is-dark-theme): map.get($background, state) ; else: #e4e7e4) !important;
&.active { &.active {
background-color: if($is-dark-theme, map.get($background, state-active), #cbf4c9) !important; background-color: if(sass($is-dark-theme): map.get($background, state-active) ; else: #cbf4c9) !important;
color: if($is-dark-theme, #cbf4c9, #0e6245) !important; color: if(sass($is-dark-theme): #cbf4c9; else: #0e6245) !important;
} }
&.inactive { &.inactive {
color: if($is-dark-theme, #ffc1c1, #620e0e) !important; color: if(sass($is-dark-theme): #ffc1c1; else: #620e0e) !important;
background-color: if($is-dark-theme, map.get($background, state-inactive), #ffc1c1) !important; background-color: if(sass($is-dark-theme): map.get($background, state-inactive) ; else: #ffc1c1) !important;
} }
} }
.color-shadow { .color-shadow {
border: 1px solid if($is-dark-theme, rgba(#8795a1, 0.2), rgba(#8795a1, 0.2)); border: 1px solid if(sass($is-dark-theme): rgba(#8795a1, 0.2) ; else: rgba(#8795a1, 0.2));
box-shadow: 0 0 3px #0000001a; box-shadow: 0 0 3px #0000001a;
} }
} }
+5
View File
@@ -0,0 +1,5 @@
/* https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1158#issuecomment-2968949608 */
@import 'tailwindcss/utilities' prefix(tw);
@import 'tailwindcss/theme' prefix(tw);
@custom-variant dark (&:where(.dark-theme, .dark-theme *));
+28 -19
View File
@@ -432,14 +432,14 @@ importers:
specifier: ^8.5.8 specifier: ^8.5.8
version: 8.5.8 version: 8.5.8
prettier: prettier:
specifier: ^3.8.1 specifier: ^3.8.3
version: 3.8.1 version: 3.8.3
prettier-plugin-organize-imports: prettier-plugin-organize-imports:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0([email protected].1)([email protected]) version: 4.3.0([email protected].3)([email protected])
prettier-plugin-tailwindcss: prettier-plugin-tailwindcss:
specifier: ^0.7.2 specifier: ^0.7.2
version: 0.7.2([email protected]([email protected].1)([email protected]))([email protected].1) version: 0.7.2([email protected]([email protected].3)([email protected]))([email protected].3)
sass: sass:
specifier: ^1.98.0 specifier: ^1.98.0
version: 1.98.0 version: 1.98.0
@@ -624,6 +624,9 @@ importers:
'@eslint/js': '@eslint/js':
specifier: ^10.0.1 specifier: ^10.0.1
version: 10.0.1([email protected]([email protected])) version: 10.0.1([email protected]([email protected]))
'@tailwindcss/postcss':
specifier: ^4.2.2
version: 4.2.2
'@types/file-saver': '@types/file-saver':
specifier: ^2.0.7 specifier: ^2.0.7
version: 2.0.7 version: 2.0.7
@@ -699,12 +702,18 @@ importers:
karma-jasmine-html-reporter: karma-jasmine-html-reporter:
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0([email protected])([email protected]([email protected]))([email protected]) version: 2.2.0([email protected])([email protected]([email protected]))([email protected])
postcss:
specifier: ^8.5.8
version: 8.5.8
prettier: prettier:
specifier: ^3.8.1 specifier: ^3.8.3
version: 3.8.1 version: 3.8.3
prettier-plugin-organize-imports: prettier-plugin-organize-imports:
specifier: ^4.3.0 specifier: ^4.3.0
version: 4.3.0([email protected].1)([email protected]) version: 4.3.0([email protected].3)([email protected])
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
typescript: typescript:
specifier: 5.9.2 specifier: 5.9.2
version: 5.9.2 version: 5.9.2
@@ -807,8 +816,8 @@ importers:
specifier: ^9.0.3 specifier: ^9.0.3
version: 9.0.3 version: 9.0.3
prettier: prettier:
specifier: ^3.8.1 specifier: ^3.8.3
version: 3.8.1 version: 3.8.3
typescript: typescript:
specifier: ^5.9.3 specifier: ^5.9.3
version: 5.9.3 version: 5.9.3
@@ -10450,8 +10459,8 @@ packages:
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
hasBin: true hasBin: true
[email protected].1: [email protected].3:
resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==}
engines: {node: '>=14'} engines: {node: '>=14'}
hasBin: true hasBin: true
@@ -23386,25 +23395,25 @@ snapshots:
[email protected]: {} [email protected]: {}
[email protected]([email protected].1)([email protected]): [email protected]([email protected].3)([email protected]):
dependencies: dependencies:
prettier: 3.8.1 prettier: 3.8.3
typescript: 5.9.2 typescript: 5.9.2
[email protected]([email protected].1)([email protected]): [email protected]([email protected].3)([email protected]):
dependencies: dependencies:
prettier: 3.8.1 prettier: 3.8.3
typescript: 5.9.3 typescript: 5.9.3
[email protected]([email protected]([email protected].1)([email protected]))([email protected].1): [email protected]([email protected]([email protected].3)([email protected]))([email protected].3):
dependencies: dependencies:
prettier: 3.8.1 prettier: 3.8.3
optionalDependencies: optionalDependencies:
prettier-plugin-organize-imports: 4.3.0([email protected].1)([email protected]) prettier-plugin-organize-imports: 4.3.0([email protected].3)([email protected])
[email protected]: {} [email protected]: {}
[email protected].1: {} [email protected].3: {}
[email protected]: {} [email protected]: {}
+1 -1
View File
@@ -12,7 +12,7 @@
"cypress": "^15.13.0", "cypress": "^15.13.0",
"cypress-wait-until": "^3.0.2", "cypress-wait-until": "^3.0.2",
"jsonwebtoken": "^9.0.3", "jsonwebtoken": "^9.0.3",
"prettier": "^3.8.1", "prettier": "^3.8.3",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"uuid": "^13.0.0" "uuid": "^13.0.0"
} }