chore: deps update (#11968)

# Which Problems Are Solved
This pr updates major and minor dependencies and is the first step on
getting our dependabot alerts cut down.

# How the Problems Are Solved

Depedency updates across the board eg:
- Upgrade Angular to v21
- Upgrade next.js to v16.2
- Upgrade tailwind to v4 in the login
- Upgrade vitest to v4 in the login

This is an uncompleted list refer to the changed files for a full
overview of all the updates.

# Additional Changes

Migrated all control flow in the console to the modern control flow
syntax.
Fixed the dependsOn setting for the @zitadel/login:test-unit nx target.

# Additional Context

- Closes https://github.com/zitadel/zitadel/issues/11274

---------

Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
Ramon
2026-04-02 15:53:08 +02:00
committed by GitHub
co-authored by Max Peintner
parent 891c7473ca
commit 0945195f68
241 changed files with 15156 additions and 14966 deletions
+15 -15
View File
@@ -25,9 +25,9 @@
"@headlessui/react": "^2.2.9",
"@heroicons/react": "2.2.0",
"@inkeep/cxkit-react": "^0.5.115",
"@scalar/api-reference": "^1.44.23",
"@shikijs/rehype": "^3.22.0",
"@shikijs/types": "^3.22.0",
"@scalar/api-reference": "^1.49.8",
"@shikijs/rehype": "^3.23.0",
"@shikijs/types": "^3.23.0",
"@types/js-yaml": "^4.0.9",
"clsx": "2.1.1",
"fumadocs-core": "16.6.15",
@@ -36,41 +36,41 @@
"fumadocs-ui": "16.6.15",
"js-yaml": "^4.1.1",
"lucide-react": "^0.574.0",
"mixpanel-browser": "^2.74.0",
"mixpanel-browser": "^2.77.0",
"next": "16.1.6",
"next-themes": "^0.4.6",
"react": "^19.2.4",
"react-copy-to-clipboard": "^5.1.0",
"react-copy-to-clipboard": "^5.1.1",
"react-dom": "^19.2.4",
"react-google-charts": "^5.2.1",
"shiki": "^3.22.0",
"shiki": "^3.23.0",
"tailwind-merge": "^3.5.0",
"vanilla-cookieconsent": "3.1.0",
"zod": "^4.3.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/postcss": "^4.1.18",
"@tailwindcss/postcss": "^4.2.2",
"@tailwindcss/typography": "^0.5.19",
"@types/mdx": "^2.0.13",
"@types/node": "^25.2.3",
"@types/node": "^25.5.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"eslint": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-mdx": "^3.7.0",
"glob": "^13.0.5",
"globals": "^17.3.0",
"glob": "^13.0.6",
"globals": "^17.4.0",
"next-validate-link": "^1.6.4",
"postcss": "^8.5.6",
"postcss": "^8.5.8",
"raw-loader": "^4.0.2",
"remark-heading-id": "^1.0.1",
"semver": "^7.7.4",
"tailwindcss": "^4.1.18",
"tailwindcss": "^4.2.2",
"tailwindcss-animate": "^1.0.7",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
+45 -45
View File
@@ -23,93 +23,93 @@
"*": "prettier --write --ignore-unknown"
},
"dependencies": {
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-node": "^2.1.1",
"@headlessui/react": "^2.2.9",
"@heroicons/react": "^2.2.0",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/api-logs": "^0.211.0",
"@opentelemetry/auto-instrumentations-node": "^0.69.0",
"@opentelemetry/resource-detector-container": "^0.8.2",
"@opentelemetry/resource-detector-gcp": "^0.46.0",
"@opentelemetry/resources": "^2.5.0",
"@opentelemetry/sdk-node": "^0.211.0",
"@opentelemetry/winston-transport": "^0.21.0",
"@opentelemetry/api": "^1.9.1",
"@opentelemetry/api-logs": "^0.214.0",
"@opentelemetry/auto-instrumentations-node": "^0.72.0",
"@opentelemetry/resource-detector-container": "^0.8.5",
"@opentelemetry/resource-detector-gcp": "^0.49.0",
"@opentelemetry/resources": "^2.6.1",
"@opentelemetry/sdk-node": "^0.214.0",
"@opentelemetry/winston-transport": "^0.24.0",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/forms": "^0.5.11",
"clsx": "^2.1.1",
"copy-to-clipboard": "^3.3.3",
"deepmerge": "^4.3.1",
"escape-html": "^1.0.3",
"lucide-react": "^0.562.0",
"lucide-react": "^0.577.0",
"moment": "^2.30.1",
"next": "16.1.6",
"next-intl": "^4.8.3",
"next-themes": "^0.2.1",
"next": "16.2.2",
"next-intl": "^4.8.4",
"next-themes": "^0.4.6",
"nice-grpc": "^2.1.14",
"qrcode.react": "^4.2.0",
"react": "19.2.4",
"react-dom": "19.2.4",
"react-hook-form": "^7.71.1",
"react-hook-form": "^7.72.0",
"server-only": "^0.0.1",
"tinycolor2": "^1.6.0",
"uuid": "^13.0.0",
"@connectrpc/connect-node": "^2.1.1",
"@connectrpc/connect": "^2.1.1",
"winston": "^3.17.0"
"winston": "^3.19.0"
},
"devDependencies": {
"@eslint/js": "^10.0.0",
"@eslint/js": "^10.0.1",
"@faker-js/faker": "^9.9.0",
"@next/eslint-plugin-next": "16.1.6",
"@opentelemetry/sdk-trace-base": "^2.5.0",
"@opentelemetry/sdk-trace-node": "^2.5.0",
"@next/eslint-plugin-next": "16.2.2",
"@opentelemetry/sdk-trace-base": "^2.6.1",
"@opentelemetry/sdk-trace-node": "^2.6.1",
"@otplib/core": "^12.0.1",
"@otplib/plugin-crypto": "^12.0.1",
"@otplib/plugin-thirty-two": "^12.0.1",
"@playwright/test": "^1.58.0",
"@playwright/test": "^1.59.0",
"@tailwindcss/postcss": "^4.2.2",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2",
"@types/escape-html": "^1.0.4",
"@types/node": "^22.19.7",
"@types/node": "^25.5.0",
"@types/node-forge": "^1.3.14",
"@types/react": "19.2.7",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/tinycolor2": "^1.4.6",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"@vercel/git-hooks": "1.0.0",
"@types/uuid": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"@vercel/git-hooks": "^1.0.0",
"@vitejs/plugin-react": "^4.7.0",
"@zitadel/client": "workspace:*",
"@zitadel/proto": "workspace:*",
"autoprefixer": "^10.4.23",
"concurrently": "^9.2.1",
"dotenv-cli": "^8.0.0",
"env-cmd": "^10.1.0",
"esbuild": "^0.27.3",
"eslint": "^10.0.0",
"dotenv-cli": "^11.0.0",
"env-cmd": "^11.0.0",
"esbuild": "^0.27.4",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-cypress": "^4.2.0",
"eslint-plugin-cypress": "^4.3.0",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-react-hooks": "^7.0.1",
"gaxios": "^7.1.3",
"globals": "^17.0.0",
"gaxios": "^7.1.4",
"globals": "^17.4.0",
"grpc-tools": "^1.13.1",
"jsdom": "^26.1.0",
"lint-staged": "15.5.1",
"node-forge": "^1.3.3",
"nodemon": "^3.1.11",
"postcss": "^8.5.6",
"lint-staged": "^16.4.0",
"node-forge": "^1.4.0",
"nodemon": "^3.1.14",
"postcss": "^8.5.8",
"prettier": "^3.8.1",
"prettier-plugin-organize-imports": "^4.3.0",
"prettier-plugin-tailwindcss": "^0.7.2",
"sass": "^1.97.3",
"start-server-and-test": "^2.1.3",
"tailwindcss": "3.4.14",
"testcontainers": "^11.11.0",
"ts-proto": "^2.11.1",
"sass": "^1.98.0",
"start-server-and-test": "^3.0.0",
"tailwindcss": "^4.2.2",
"testcontainers": "^11.13.0",
"ts-proto": "^2.11.6",
"typescript": "^5.9.3",
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^2.1.9"
"vitest": "^4.1.2"
}
}
+1 -2
View File
@@ -1,6 +1,5 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
"@tailwindcss/postcss": {},
},
};
+3 -3
View File
@@ -44,7 +44,7 @@
},
"build-vercel": {
"description": "Builds the Next.js Login application for Vercel deployment",
"cache": true,
"cache": false,
"dependsOn": ["^build"],
"inputs": [
"default",
@@ -54,7 +54,7 @@
"!{projectRoot}/acceptance/**/*",
"!{projectRoot}/dockerized/**/*"
],
"outputs": ["{projectRoot}/.next", "!{projectRoot}/.next/cache", "!{projectRoot}/.next/standalone"]
"outputs": ["{projectRoot}/.next/!(cache|standalone)/**"]
},
"lint": {
"description": "Runs all linters",
@@ -77,7 +77,7 @@
},
"test-unit": {
"description": "Runs unit tests using Vitest",
"dependsOn": ["^build"]
"dependsOn": ["build"]
},
"test-dockerized": {
"description": "Runs dockerized integration tests (OpenTelemetry and custom CA certificate tests)",
@@ -50,7 +50,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
{postErrorRedirectUrl && (
<Link href={postErrorRedirectUrl}>
<Button className="w-full rounded-md bg-primary-light-500 px-4 py-3 text-center transition-all hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400">
<Button className="bg-primary-light-500 hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400 w-full rounded-md px-4 py-3 text-center transition-all">
<Translated i18nKey="accountNotFound.backToLogin" namespace="idp" />
</Button>
</Link>
@@ -50,7 +50,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
{postErrorRedirectUrl && (
<Link href={postErrorRedirectUrl}>
<Button className="w-full rounded-md bg-primary-light-500 px-4 py-3 text-center transition-all hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400">
<Button className="bg-primary-light-500 hover:bg-primary-light-400 dark:bg-primary-dark-500 dark:hover:bg-primary-dark-400 w-full rounded-md px-4 py-3 text-center transition-all">
<Translated i18nKey="registrationFailed.backToLogin" namespace="idp" />
</Button>
</Link>
+2 -2
View File
@@ -51,7 +51,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
<Suspense
fallback={
<BackgroundWrapper
className={`relative flex min-h-screen flex-col justify-center bg-background-light-600 dark:bg-background-dark-600`}
className={`bg-background-light-600 dark:bg-background-dark-600 relative flex min-h-screen flex-col justify-center`}
>
<div className="relative mx-auto w-full max-w-[440px] py-8">
<Skeleton>
@@ -66,7 +66,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
>
<LanguageProvider>
<BackgroundWrapper
className={`relative flex min-h-screen flex-col justify-center bg-background-light-600 dark:bg-background-dark-600`}
className={`bg-background-light-600 dark:bg-background-dark-600 relative flex min-h-screen flex-col justify-center`}
>
<div className="relative mx-auto w-full max-w-[1100px] py-8">
<div>{children}</div>
@@ -71,7 +71,7 @@ export default async function Page(props: { searchParams: Promise<Record<string
)}
{loginSettings?.allowExternalIdp && !!identityProviders?.length && (
<div className="w-full pb-4 pt-6">
<div className="w-full pt-6 pb-4">
<SignInWithIdp
identityProviders={identityProviders}
requestId={requestId}
+1 -1
View File
@@ -22,7 +22,7 @@ export function AddressBar({ domain }: Props) {
</svg>
</div>
<div className="flex space-x-1 text-sm font-medium">
<div className="max-w-[150px] overflow-hidden text-ellipsis px-2 text-gray-500">
<div className="max-w-[150px] overflow-hidden px-2 text-ellipsis text-gray-500">
<span className="whitespace-nowrap">{domain}</span>
</div>
{pathname ? (
+2 -2
View File
@@ -26,8 +26,8 @@ export function Alert({ children, type = AlertType.ALERT }: Props) {
[neutral]: type === AlertType.INFO,
})}
>
{type === AlertType.ALERT && <ExclamationTriangleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />}
{type === AlertType.INFO && <InformationCircleIcon className="ml-2 mr-2 h-5 w-5 flex-shrink-0" />}
{type === AlertType.ALERT && <ExclamationTriangleIcon className="mr-2 ml-2 h-5 w-5 flex-shrink-0" />}
{type === AlertType.INFO && <InformationCircleIcon className="mr-2 ml-2 h-5 w-5 flex-shrink-0" />}
<span className="w-full text-sm">{children}</span>
</div>
);
+2 -2
View File
@@ -26,7 +26,7 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) {
return (
<div
className={`dark:group-focus:ring-offset-blue dark:text-blue pointer-events-none flex h-[100px] w-[100px] cursor-default items-center justify-center rounded-full bg-primary-light-500 text-primary-light-contrast-500 transition-colors duration-200 hover:bg-primary-light-400 group-focus:outline-none group-focus:ring-2 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 ${
className={`dark:group-focus:ring-offset-blue dark:text-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 pointer-events-none flex h-[100px] w-[100px] cursor-default items-center justify-center rounded-full transition-colors duration-200 group-focus:ring-2 group-focus:outline-none ${
shadow ? "shadow" : ""
}`}
style={resolvedTheme === "light" ? avatarStyleLight : avatarStyleDark}
@@ -36,7 +36,7 @@ export function AppAvatar({ appName, imageUrl, shadow }: AvatarProps) {
height={48}
width={48}
alt="avatar"
className="h-full w-full rounded-full border border-divider-light dark:border-divider-dark"
className="border-divider-light dark:border-divider-dark h-full w-full rounded-full border"
src={imageUrl}
/>
) : (
+1 -1
View File
@@ -181,7 +181,7 @@ export const PASSWORD = (alreadyAdded: boolean, link: string) => {
function Setup() {
return (
<div className="absolute right-2 top-0 transform">
<div className="absolute top-0 right-2 transform">
<StateBadge evenPadding={true} state={BadgeState.Success}>
<CheckIcon className="h-4 w-4" />
</StateBadge>
@@ -29,11 +29,11 @@ export function AuthenticationMethodRadio({
value={method}
data-testid={method + "-radio"}
className={({ focus, checked }) =>
`${focus ? "ring-2 ring-primary-light-500 ring-opacity-60 dark:ring-white/20" : ""} ${
`${focus ? "ring-primary-light-500 ring-opacity-60 ring-2 dark:ring-white/20" : ""} ${
checked
? "bg-background-light-400 ring-2 ring-primary-light-500 dark:bg-background-dark-400 dark:ring-primary-dark-500"
? "bg-background-light-400 ring-primary-light-500 dark:bg-background-dark-400 dark:ring-primary-dark-500 ring-2"
: "bg-background-light-400 dark:bg-background-dark-400"
} boder-divider-light relative flex h-full flex-1 cursor-pointer rounded-lg border px-5 py-4 hover:shadow-lg focus:outline-none dark:border-divider-dark dark:hover:bg-white/10`
} boder-divider-light dark:border-divider-dark relative flex h-full flex-1 cursor-pointer rounded-lg border px-5 py-4 hover:shadow-lg focus:outline-none dark:hover:bg-white/10`
}
>
<div className="flex w-full flex-col items-center text-sm">
+2 -2
View File
@@ -54,7 +54,7 @@ export function Avatar({ size = "base", name, loginName, imageUrl, shadow }: Ava
return (
<div
className={`dark:group-focus:ring-offset-blue dark:text-blue pointer-events-none flex h-full w-full flex-shrink-0 cursor-default items-center justify-center bg-primary-light-500 text-primary-light-contrast-500 transition-colors duration-200 hover:bg-primary-light-400 group-focus:outline-none group-focus:ring-2 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 ${avatarRoundness} ${
className={`dark:group-focus:ring-offset-blue dark:text-blue bg-primary-light-500 text-primary-light-contrast-500 hover:bg-primary-light-400 group-focus:ring-primary-light-200 dark:bg-primary-dark-300 dark:text-primary-dark-contrast-300 hover:dark:bg-primary-dark-500 dark:group-focus:ring-primary-dark-400 pointer-events-none flex h-full w-full flex-shrink-0 cursor-default items-center justify-center transition-colors duration-200 group-focus:ring-2 group-focus:outline-none ${avatarRoundness} ${
shadow ? "shadow" : ""
} ${
size === "large"
@@ -72,7 +72,7 @@ export function Avatar({ size = "base", name, loginName, imageUrl, shadow }: Ava
height={48}
width={48}
alt="avatar"
className={`h-full w-full border border-divider-light dark:border-divider-dark ${avatarRoundness}`}
className={`border-divider-light dark:border-divider-dark h-full w-full border ${avatarRoundness}`}
src={imageUrl}
/>
) : (
+1 -1
View File
@@ -56,7 +56,7 @@ export const Boundary = ({
})}
>
<div
className={clsx("absolute -top-2 flex space-x-1 text-[9px] uppercase leading-4 tracking-widest", {
className={clsx("absolute -top-2 flex space-x-1 text-[9px] leading-4 tracking-widest uppercase", {
"left-3 lg:left-5": size === "small",
"left-4 lg:left-9": size === "default",
})}
+1 -1
View File
@@ -31,7 +31,7 @@ export const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(function Che
disabled={disabled}
type="checkbox"
className={classNames(
"form-checkbox rounded border-gray-300 text-primary-light-500 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 focus:ring-offset-0 dark:text-primary-dark-500",
"form-checkbox text-primary-light-500 focus:ring-opacity-50 dark:text-primary-dark-500 rounded border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-offset-0",
className,
)}
{...props}
@@ -83,7 +83,7 @@ export function ChooseSecondFactorToSetup({
</div>
{!force && (
<button
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
className="hover:text-primary-light-500 dark:hover:text-primary-dark-500 text-sm transition-all"
onClick={async () => {
const skipResponse = await skipMFAAndContinueWithNextUrl({
userId,
+2 -2
View File
@@ -48,7 +48,7 @@ export function ConsentScreen({
<div className="flex w-full flex-col items-center space-y-4 pt-4">
<ul className="w-full list-disc space-y-2">
{scopes?.length === 0 && (
<span className="flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 text-sm transition-all dark:bg-background-dark-400">
<span className="border-divider-light bg-background-light-400 dark:bg-background-dark-400 flex w-full flex-row items-center rounded-md border px-4 py-2 text-sm transition-all">
<Translated i18nKey="device.scope.openid" namespace="device" />
</span>
)}
@@ -62,7 +62,7 @@ export function ConsentScreen({
return (
<li
key={s}
className="flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 text-sm transition-all dark:bg-background-dark-400"
className="border-divider-light bg-background-light-400 dark:bg-background-dark-400 flex w-full flex-row items-center rounded-md border px-4 py-2 text-sm transition-all"
>
<span>{resolvedDescription}</span>
</li>
+1 -1
View File
@@ -69,7 +69,7 @@ export function DynamicTheme({
{hasLeftRightStructure && (
<div className="flex flex-col items-start space-y-4 text-left">
{/* Apply larger styling to the content */}
<div className="space-y-6 [&_h1]:text-left [&_h1]:text-4xl [&_h1]:leading-tight [&_h1]:text-gray-900 [&_h1]:dark:text-white [&_h1]:lg:text-4xl [&_p]:text-left [&_p]:leading-relaxed [&_p]:text-gray-700 [&_p]:dark:text-gray-300">
<div className="space-y-6 [&_h1]:text-left [&_h1]:text-4xl [&_h1]:leading-tight [&_h1]:text-gray-900 [&_h1]:lg:text-4xl [&_h1]:dark:text-white [&_p]:text-left [&_p]:leading-relaxed [&_p]:text-gray-700 [&_p]:dark:text-gray-300">
{leftContent}
</div>
</div>
@@ -33,7 +33,7 @@ export const BaseButton = forwardRef<HTMLButtonElement, SignInWithIdentityProvid
ref={ref}
disabled={formStatus.pending}
className={clsx(
`flex flex-1 cursor-pointer flex-row items-center px-4 text-sm text-text-light-500 outline-none transition-all hover:border-black focus:border-primary-light-500 dark:text-text-dark-500 hover:dark:border-white focus:dark:border-primary-dark-500`,
`text-text-light-500 focus:border-primary-light-500 dark:text-text-dark-500 focus:dark:border-primary-dark-500 flex flex-1 cursor-pointer flex-row items-center px-4 text-sm transition-all outline-none hover:border-black hover:dark:border-white`,
buttonRoundness,
idpButtonAppearance,
`bg-background-light-400 dark:bg-background-dark-500`, // Keep background as fallback for non-glass themes
+3 -3
View File
@@ -59,7 +59,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
const actualRoundness = roundness || getDefaultInputRoundness();
return (
<label className="relative flex flex-col text-12px text-input-light-label dark:text-input-dark-label">
<label className="text-12px text-input-light-label dark:text-input-dark-label relative flex flex-col">
<span className={`mb-1 leading-3 ${error ? "text-warn-light-500 dark:text-warn-dark-500" : ""}`}>
{label} {required && "*"}
</span>
@@ -80,7 +80,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
{suffix && (
<span
className={clsx(
"absolute bottom-[22px] right-[3px] z-30 translate-y-1/2 transform bg-background-light-500 p-2 dark:bg-background-dark-500",
"bg-background-light-500 dark:bg-background-dark-500 absolute right-[3px] bottom-[22px] z-30 translate-y-1/2 transform p-2",
// Extract just the roundness part for the suffix (no padding)
actualRoundness.split(" ")[0], // Take only the first part (rounded-full, rounded-md, etc.)
)}
@@ -89,7 +89,7 @@ export const TextInput = forwardRef<HTMLInputElement, TextInputProps>(
</span>
)}
<div className="leading-14.5px h-14.5px flex flex-row items-center text-12px text-warn-light-500 dark:text-warn-dark-500">
<div className="leading-14.5px h-14.5px text-12px text-warn-light-500 dark:text-warn-dark-500 flex flex-row items-center">
<span>{error ? error : " "}</span>
</div>
@@ -45,19 +45,19 @@ export function LanguageSwitcher({ languages }: { languages: Lang[] }) {
<Listbox value={selected} onChange={handleChange}>
<ListboxButton
className={clsx(
`relative block w-full py-1.5 pl-3 pr-8 text-left text-sm/6 text-black dark:text-white ${switcherRoundness}`,
`relative block w-full py-1.5 pr-8 pl-3 text-left text-sm/6 text-black dark:text-white ${switcherRoundness}`,
cardAppearance,
"focus:outline-none data-[focus]:outline-2 data-[focus]:-outline-offset-2 data-[focus]:outline-white/25",
)}
>
{selected.name}
<ChevronDownIcon className="group pointer-events-none absolute right-2.5 top-2.5 size-4" aria-hidden="true" />
<ChevronDownIcon className="group pointer-events-none absolute top-2.5 right-2.5 size-4" aria-hidden="true" />
</ListboxButton>
<ListboxOptions
anchor="bottom"
transition
className={clsx(
`w-[var(--button-width)] rounded-md border border-black/5 bg-background-light-500 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none dark:border-white/5 dark:bg-background-dark-500`,
`bg-background-light-500 dark:bg-background-dark-500 w-[var(--button-width)] rounded-md border border-black/5 p-1 [--anchor-gap:var(--spacing-1)] focus:outline-none dark:border-white/5`,
"transition duration-100 ease-in data-[leave]:data-[closed]:opacity-0",
)}
>
@@ -65,7 +65,7 @@ export function LanguageSwitcher({ languages }: { languages: Lang[] }) {
<ListboxOption
key={lang.code}
value={lang}
className={`group flex cursor-default select-none items-center gap-2 px-3 py-1.5 data-[focus]:bg-black/10 dark:data-[focus]:bg-white/10 ${switcherRoundness}`}
className={`group flex cursor-default items-center gap-2 px-3 py-1.5 select-none data-[focus]:bg-black/10 dark:data-[focus]:bg-white/10 ${switcherRoundness}`}
>
<CheckIcon className="invisible size-4 group-data-[selected]:visible" />
<div className="text-sm/6 text-black dark:text-white">{lang.name}</div>
@@ -95,7 +95,7 @@ export function LDAPUsernamePasswordForm({
data-testid="username-text-input"
/>
<div className={`${error && "transform-gpu animate-shake"}`}>
<div className={`${error && "animate-shake transform-gpu"}`}>
<TextInput
type="password"
autoComplete="password"
+1 -1
View File
@@ -223,7 +223,7 @@ export function LoginOTP({ host, loginName, sessionId, requestId, organization,
aria-label="Resend OTP Code"
disabled={loading}
type="button"
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
className="text-primary-light-500 hover:text-primary-light-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 ml-4 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
onClick={async () => {
setLoading(true);
const response = await updateSessionForOTPChallenge();
@@ -29,7 +29,7 @@ function CheckIcon({ title }: { title: string }) {
function CrossIcon({ title }: { title: string }) {
return (
<svg
className="las la-times mr-2 h-6 w-6 flex-none text-lg text-warn-light-500 dark:text-warn-dark-500"
className="las la-times text-warn-light-500 dark:text-warn-dark-500 mr-2 h-6 w-6 flex-none text-lg"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
+2 -2
View File
@@ -112,7 +112,7 @@ export function PasswordForm({ loginSettings, loginName, organization, defaultOr
<>
{samlData && <AutoSubmitForm url={samlData.url} fields={samlData.fields} />}
<form className="w-full">
<div className={`${error && "transform-gpu animate-shake"}`}>
<div className={`${error && "animate-shake transform-gpu"}`}>
<TextInput
type="password"
autoComplete="password"
@@ -123,7 +123,7 @@ export function PasswordForm({ loginSettings, loginName, organization, defaultOr
/>
{!loginSettings?.hidePasswordReset && (
<button
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
className="hover:text-primary-light-500 dark:hover:text-primary-dark-500 text-sm transition-all"
onClick={() => resetPasswordAndContinue()}
type="button"
disabled={loading}
@@ -32,7 +32,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
return (
<>
<p className="mt-4 flex flex-row items-center text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500">
<p className="text-text-light-secondary-500 dark:text-text-dark-secondary-500 mt-4 flex flex-row items-center text-sm">
<Translated i18nKey="agreeTo" namespace="register" />
{legal?.helpLink && (
<span>
@@ -73,7 +73,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
/>
<div className="mr-4 w-[28rem]">
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
<p className="text-text-light-500 dark:text-text-dark-500 text-sm">
<Link href={legal.tosLink} className="underline" target="_blank">
<Translated i18nKey="termsOfService" namespace="register" />
</Link>
@@ -99,7 +99,7 @@ export function PrivacyPolicyCheckboxes({ legal, onChange }: Props) {
/>
<div className="mr-4 w-[28rem]">
<p className="text-sm text-text-light-500 dark:text-text-dark-500">
<p className="text-text-light-500 dark:text-text-dark-500 text-sm">
<Link href={legal.privacyPolicyLink} className="underline" target="_blank">
<Translated i18nKey="privacyPolicy" namespace="register" />
</Link>
+1 -1
View File
@@ -159,7 +159,7 @@ export function RegisterForm({
{/* show chooser if both methods are allowed */}
{loginSettings && loginSettings.allowLocalAuthentication && loginSettings.passkeysType == PasskeysType.ALLOWED && (
<>
<p className="ztdl-p mb-6 mt-4 block text-left">
<p className="ztdl-p mt-4 mb-6 block text-left">
<Translated i18nKey="selectMethod" namespace="register" />
</p>
@@ -28,7 +28,7 @@ const SelfServiceItem = ({ name, link }: { name: string; link: string }) => {
<Link
prefetch={false}
href={link}
className="group flex w-full flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
className="group border-divider-light bg-background-light-400 dark:bg-background-dark-400 flex w-full flex-row items-center rounded-md border px-4 py-2 transition-all hover:shadow-lg dark:hover:bg-white/10"
>
{name}
</Link>
@@ -47,7 +47,7 @@ export function SessionClearItem({ session, reload }: { session: Session; reload
reload();
});
}}
className="group flex flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
className="group border-divider-light bg-background-light-400 dark:bg-background-dark-400 flex flex-row items-center rounded-md border px-4 py-2 transition-all hover:shadow-lg dark:hover:bg-white/10"
>
<div className="pr-4">
<Avatar
@@ -59,9 +59,9 @@ export function SessionClearItem({ session, reload }: { session: Session; reload
<div className="flex flex-col items-start overflow-hidden">
<span className="">{session.factors?.user?.displayName}</span>
<span className="text-ellipsis text-xs opacity-80">{session.factors?.user?.loginName}</span>
<span className="text-xs text-ellipsis opacity-80">{session.factors?.user?.loginName}</span>
{valid ? (
<span className="text-ellipsis text-xs opacity-80">
<span className="text-xs text-ellipsis opacity-80">
{verifiedAt && (
<Translated
i18nKey="verifiedAt"
@@ -72,7 +72,7 @@ export function SessionClearItem({ session, reload }: { session: Session; reload
</span>
) : (
verifiedAt && (
<span className="text-ellipsis text-xs opacity-80">
<span className="text-xs text-ellipsis opacity-80">
expired {session.expirationDate && moment(timestampDate(session.expirationDate)).fromNow()}
</span>
)
@@ -81,7 +81,7 @@ export function SessionClearItem({ session, reload }: { session: Session; reload
<span className="flex-grow"></span>
<div className="relative flex flex-row items-center">
<div className="mr-6 flex hidden items-center justify-center rounded-full bg-[#ff0000]/10 px-2 py-[2px] text-xs text-warn-light-500 transition-all group-hover:block dark:bg-[#ff0000]/10 dark:text-warn-dark-500">
<div className="text-warn-light-500 dark:text-warn-dark-500 mr-6 flex hidden items-center justify-center rounded-full bg-[#ff0000]/10 px-2 py-[2px] text-xs transition-all group-hover:block dark:bg-[#ff0000]/10">
<Translated i18nKey="clear" namespace="logout" />
</div>
+6 -6
View File
@@ -92,7 +92,7 @@ export function SessionItem({ session, reload, requestId }: { session: Session;
}
}
}}
className="group flex flex-row items-center rounded-md border border-divider-light bg-background-light-400 px-4 py-2 transition-all hover:shadow-lg dark:bg-background-dark-400 dark:hover:bg-white/10"
className="group border-divider-light bg-background-light-400 dark:bg-background-dark-400 flex flex-row items-center rounded-md border px-4 py-2 transition-all hover:shadow-lg dark:hover:bg-white/10"
>
<div className="pr-4">
<Avatar
@@ -104,15 +104,15 @@ export function SessionItem({ session, reload, requestId }: { session: Session;
<div className="flex flex-col items-start overflow-hidden">
<span className="">{session.factors?.user?.displayName}</span>
<span className="text-ellipsis text-xs opacity-80">{session.factors?.user?.loginName}</span>
<span className="text-xs text-ellipsis opacity-80">{session.factors?.user?.loginName}</span>
{valid ? (
<span className="text-ellipsis text-xs opacity-80">
<span className="text-xs text-ellipsis opacity-80">
<Translated i18nKey="verified" namespace="accounts" />{" "}
{verifiedAt && moment(timestampDate(verifiedAt)).fromNow()}
</span>
) : (
verifiedAt && (
<span className="text-ellipsis text-xs opacity-80">
<span className="text-xs text-ellipsis opacity-80">
<Translated i18nKey="expired" namespace="accounts" />{" "}
{session.expirationDate && moment(timestampDate(session.expirationDate)).fromNow()}
</span>
@@ -129,7 +129,7 @@ export function SessionItem({ session, reload, requestId }: { session: Session;
)}
<XCircleIcon
className="h-5 w-5 opacity-50 transition-all hover:opacity-100 group-hover:block sm:hidden"
className="h-5 w-5 opacity-50 transition-all group-hover:block hover:opacity-100 sm:hidden"
onClick={async (event: React.MouseEvent) => {
event.preventDefault();
event.stopPropagation();
@@ -143,7 +143,7 @@ export function SessionItem({ session, reload, requestId }: { session: Session;
{valid && session.expirationDate && (
<Tooltip.Portal>
<Tooltip.Content
className="z-50 select-none rounded-md border bg-background-light-500 px-3 py-2 text-xs text-black shadow-xl dark:border-white/20 dark:bg-background-dark-500 dark:text-white"
className="bg-background-light-500 dark:bg-background-dark-500 z-50 rounded-md border px-3 py-2 text-xs text-black shadow-xl select-none dark:border-white/20 dark:text-white"
sideOffset={5}
>
Expires {moment(timestampDate(session.expirationDate)).fromNow()}
@@ -186,7 +186,7 @@ export function SetPasswordForm({
aria-label="Resend OTP Code"
disabled={loading}
type="button"
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
className="text-primary-light-500 hover:text-primary-light-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 ml-4 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
onClick={() => {
resendCode();
}}
+1 -1
View File
@@ -2,7 +2,7 @@ import { ReactNode } from "react";
export function Skeleton({ children }: { children?: ReactNode }) {
return (
<div className="skeleton flex flex-row items-center justify-center rounded-lg bg-background-light-600 px-8 py-12 dark:bg-background-dark-600">
<div className="skeleton bg-background-light-600 dark:bg-background-dark-600 flex flex-row items-center justify-center rounded-lg px-8 py-12">
{children}
</div>
);
+1 -1
View File
@@ -4,7 +4,7 @@ export const Spinner: FC<{ className?: string }> = ({ className = "" }) => {
return (
<svg
role="status"
className={`${className} inline-block animate-spin fill-primary-light-500 text-black/10 dark:fill-primary-dark-500 dark:text-white/10`}
className={`${className} fill-primary-light-500 dark:fill-primary-dark-500 inline-block animate-spin text-black/10 dark:text-white/10`}
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
+1 -1
View File
@@ -17,7 +17,7 @@ export const Tab = ({ path, item: { slug, text } }: { path: string; item: Item }
return (
<Link
href={href}
className={clsx("mr-2 mt-2 rounded-lg px-3 py-1 text-sm font-medium", {
className={clsx("mt-2 mr-2 rounded-lg px-3 py-1 text-sm font-medium", {
"bg-gray-700 text-gray-100 hover:bg-gray-500 hover:text-white": !isActive,
"bg-blue-500 text-white": isActive,
})}
+2 -1
View File
@@ -54,7 +54,8 @@ export const ThemeWrapper = ({ children, branding }: Props) => {
break;
}
}
}, [branding?.themeMode, setNextTheme]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [branding?.themeMode]);
return <div>{children}</div>;
};
+1 -1
View File
@@ -106,7 +106,7 @@ export function TotpRegister({ uri, loginName, sessionId, requestId, organizatio
{uri && (
<>
<QRCodeSVG className="my-4 h-40 w-40 rounded-md bg-white p-2" value={uri} />
<div className="my-2 mb-4 flex w-96 rounded-lg border border-divider-light px-4 py-2 pr-2 text-sm dark:border-divider-dark">
<div className="border-divider-light dark:border-divider-dark my-2 mb-4 flex w-96 rounded-lg border px-4 py-2 pr-2 text-sm">
<Link href={uri} target="_blank" className="flex-1 overflow-x-auto">
{uri}
</Link>
+2 -2
View File
@@ -40,12 +40,12 @@ export function UserAvatar({ loginName, displayName, showDropdown, searchParams
<div>
<Avatar size="small" name={displayName ?? loginName ?? ""} loginName={loginName ?? ""} />
</div>
<span className="ml-4 max-w-[250px] overflow-hidden text-ellipsis pr-4 text-14px">{loginName}</span>
<span className="text-14px ml-4 max-w-[250px] overflow-hidden pr-4 text-ellipsis">{loginName}</span>
<span className="flex-grow"></span>
{showDropdown && (
<Link
href={"/accounts?" + params}
className={`ml-4 mr-1 flex items-center justify-center p-1 transition-all hover:bg-black/10 dark:hover:bg-white/10 ${userAvatarRoundness}`}
className={`mr-1 ml-4 flex items-center justify-center p-1 transition-all hover:bg-black/10 dark:hover:bg-white/10 ${userAvatarRoundness}`}
>
<ChevronDownIcon className="h-4 w-4" />
</Link>
+1 -1
View File
@@ -115,7 +115,7 @@ export function UsernameForm({
/>
{allowRegister && (
<button
className="text-sm transition-all hover:text-primary-light-500 dark:hover:text-primary-dark-500"
className="hover:text-primary-light-500 dark:hover:text-primary-dark-500 text-sm transition-all"
onClick={() => {
const registerParams = new URLSearchParams();
if (organization) {
+1 -1
View File
@@ -153,7 +153,7 @@ export function VerifyForm({ userId, loginName, organization, requestId, code, i
aria-label="Resend Code"
disabled={loading}
type="button"
className="ml-4 cursor-pointer text-primary-light-500 hover:text-primary-light-400 disabled:cursor-default disabled:text-gray-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 dark:disabled:text-gray-700"
className="text-primary-light-500 hover:text-primary-light-400 dark:text-primary-dark-500 hover:dark:text-primary-dark-400 ml-4 cursor-pointer disabled:cursor-default disabled:text-gray-400 dark:disabled:text-gray-700"
onClick={() => {
resendCode();
}}
+2 -2
View File
@@ -130,7 +130,7 @@ describe("processIDPCallback", () => {
};
beforeEach(async () => {
vi.clearAllMocks();
vi.resetAllMocks();
// Import mocked modules
const { headers } = await import("next/headers");
@@ -937,7 +937,7 @@ describe("validateIDPLinkingPermissions", () => {
let validateIDPLinkingPermissions: any;
beforeEach(async () => {
vi.clearAllMocks();
vi.resetAllMocks();
// Import mocked modules
const { getLoginSettings, getActiveIdentityProviders } = await import("../zitadel");
+4 -4
View File
@@ -1,6 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
// https://github.com/tailwindlabs/tailwindcss/discussions/16173
@import "tailwindcss";
@config "../../tailwind.config.mjs";
@layer base {
h1,
@@ -9,7 +9,7 @@
}
.ztdl-p {
@apply text-sm text-text-light-secondary-500 dark:text-text-dark-secondary-500;
@apply text-text-light-secondary-500 dark:text-text-dark-secondary-500 text-sm;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"buildCommand": "pnpm nx run @zitadel/login:build-vercel"
"buildCommand": "NX_DAEMON=false NX_DISABLE_DB=true pnpm nx run @zitadel/login:build-vercel"
}
+43 -44
View File
@@ -13,78 +13,77 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^20.3.16",
"@angular/cdk": "^20.2.14",
"@angular/common": "^20.3.16",
"@angular/compiler": "^20.3.16",
"@angular/core": "^20.3.16",
"@angular/forms": "^20.3.16",
"@angular/material": "^20.2.14",
"@angular/material-moment-adapter": "^20.2.14",
"@angular/platform-browser": "^20.3.16",
"@angular/platform-browser-dynamic": "^20.3.16",
"@angular/router": "^20.3.16",
"@angular/service-worker": "^20.3.16",
"@angular/animations": "^21.2.6",
"@angular/cdk": "^21.2.4",
"@angular/common": "^21.2.6",
"@angular/compiler": "^21.2.6",
"@angular/core": "^21.2.6",
"@angular/forms": "^21.2.6",
"@angular/material": "^21.2.4",
"@angular/material-moment-adapter": "^21.2.4",
"@angular/platform-browser": "^21.2.6",
"@angular/platform-browser-dynamic": "^21.2.6",
"@angular/router": "^21.2.6",
"@angular/service-worker": "^21.2.6",
"@bufbuild/protobuf": "^2.11.0",
"@connectrpc/connect": "~2.0.4",
"@connectrpc/connect-web": "~2.0.4",
"@ctrl/ngx-codemirror": "^6.1.0",
"@fortawesome/angular-fontawesome": "^3.0.0",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@netlify/build-info": "^10.3.0",
"@ng-icons/core": "^32.5.0",
"@ng-icons/heroicons": "^32.5.0",
"@connectrpc/connect": "^2.1.1",
"@connectrpc/connect-web": "^2.1.1",
"@ctrl/ngx-codemirror": "^7.0.0",
"@fortawesome/angular-fontawesome": "^4.0.0",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
"@netlify/build-info": "^10.4.0",
"@ng-icons/core": "^33.2.0",
"@ng-icons/heroicons": "^33.2.0",
"@ngx-translate/core": "^17.0.0",
"@tanstack/angular-query-experimental": "^5.90.24",
"@tanstack/angular-query-experimental": "^5.96.1",
"angular-oauth2-oidc": "^20.0.2",
"angularx-qrcode": "^20.0.0",
"angularx-qrcode": "^21.0.5",
"buffer": "^6.0.3",
"codemirror": "^5.65.20",
"codemirror": "^5.65.21",
"file-saver": "^2.0.5",
"flag-icons": "^7.5.0",
"google-protobuf": "~3.21.4",
"grpc-web": "~1.5.0",
"google-protobuf": "^4.0.2",
"grpc-web": "^2.0.2",
"i18n-iso-countries": "^7.14.0",
"libphonenumber-js": "^1.12.35",
"libphonenumber-js": "^1.12.41",
"material-design-icons-iconfont": "^6.7.0",
"moment": "^2.30.1",
"ngx-color": "^9.0.0",
"ngx-color": "^10.1.0",
"opentype.js": "^1.3.4",
"posthog-js": "^1.335.3",
"posthog-js": "^1.364.5",
"rxjs": "^7.8.2",
"tinycolor2": "^1.6.0",
"tslib": "^2.8.1",
"uuid": "^10.0.0",
"uuid": "^13.0.0",
"zone.js": "~0.15.1"
},
"devDependencies": {
"@angular-eslint/eslint-plugin": "21.3.0",
"@angular-eslint/eslint-plugin-template": "21.3.0",
"@angular-eslint/template-parser": "21.3.0",
"@angular/build": "^20.3.15",
"@angular/cli": "^20.3.15",
"@angular/compiler-cli": "^20.3.16",
"@angular/language-service": "^20.3.16",
"@eslint/js": "^10.0.0",
"@angular/build": "^21.2.6",
"@angular/cli": "^21.2.6",
"@angular/compiler-cli": "^21.2.6",
"@angular/language-service": "^21.2.6",
"@eslint/js": "^10.0.1",
"@types/file-saver": "^2.0.7",
"@types/google-protobuf": "~3.15.12",
"@types/google-protobuf": "^3.15.12",
"@types/jasmine": "~5.1.15",
"@types/jasminewd2": "~2.0.13",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^22.19.7",
"@types/node": "^25.5.0",
"@types/opentype.js": "^1.3.9",
"@types/qrcode": "^1.5.6",
"@types/uuid": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0",
"@types/uuid": "^11.0.0",
"@typescript-eslint/eslint-plugin": "^8.58.0",
"@typescript-eslint/parser": "^8.58.0",
"@zitadel/client": "workspace:*",
"@zitadel/proto": "workspace:*",
"eslint": "^10.0.0",
"eslint": "^10.1.0",
"eslint-config-prettier": "^10.1.8",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-import": "^2.32.0",
"globals": "^17.0.0",
"globals": "^17.4.0",
"jasmine-core": "~5.12.1",
"jasmine-spec-reporter": "~7.0.0",
"karma": "^6.4.4",
@@ -92,7 +91,7 @@
"karma-coverage": "^2.2.1",
"karma-coverage-istanbul-reporter": "^3.0.3",
"karma-jasmine": "^5.1.0",
"karma-jasmine-html-reporter": "^2.1.0",
"karma-jasmine-html-reporter": "^2.2.0",
"prettier": "^3.8.1",
"prettier-plugin-organize-imports": "^4.3.0",
"typescript": "5.9.2"
+2 -3
View File
@@ -1,12 +1,11 @@
<div class="main-container">
<ng-container *ngIf="authService.user | async as user">
@if (authService.user | async; as user) {
<cnsl-header
[org]="activeOrganizationQuery.data()"
[user]="user"
[isDarkTheme]="componentCssClass === 'dark-theme'"
(changedActiveOrg)="changedOrg()"
/>
<cnsl-nav
id="mainnav"
class="nav"
@@ -15,7 +14,7 @@
[user]="user"
[isDarkTheme]="componentCssClass === 'dark-theme'"
></cnsl-nav>
</ng-container>
}
<div class="router-container" [@routeAnimations]="prepareRoute(outlet)">
<div class="outlet">
@@ -1,40 +1,41 @@
<div class="feature-row" *ngIf="toggleState$ | async as toggleState">
<span>{{ 'SETTING.FEATURES.' + (toggleStateKey | uppercase) | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleState.enabled"
(change)="toggleChange.emit(toggleState)"
name="displayview"
>
<mat-button-toggle [value]="false">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
<div
*ngIf="!toggleState.enabled && (isInherited$ | async)"
class="current-dot"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
<mat-button-toggle [value]="true">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
<div
*ngIf="toggleState.enabled && (isInherited$ | async)"
class="current-dot"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
</div>
</mat-button-toggle>
</mat-button-toggle-group>
@if (toggleState$ | async; as toggleState) {
<div class="feature-row">
<span>{{ 'SETTING.FEATURES.' + (toggleStateKey | uppercase) | translate }}</span>
<div class="row">
<mat-button-toggle-group
class="theme-toggle"
class="buttongroup"
[(ngModel)]="toggleState.enabled"
(change)="toggleChange.emit(toggleState)"
name="displayview"
>
<mat-button-toggle [value]="false">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.DISABLED' | translate }}</span>
@if (!toggleState.enabled && (isInherited$ | async)) {
<div
class="current-dot"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.DISABLED' | translate }}"
></div>
}
</div>
</mat-button-toggle>
<mat-button-toggle [value]="true">
<div class="toggle-row">
<span>{{ 'SETTING.FEATURES.STATES.ENABLED' | translate }}</span>
@if (toggleState.enabled && (isInherited$ | async)) {
<div
class="current-dot"
matTooltip="{{ 'SETTING.FEATURES.INHERITEDINDICATOR_DESCRIPTION.ENABLED' | translate }}"
></div>
}
</div>
</mat-button-toggle>
</mat-button-toggle-group>
</div>
<ng-content></ng-content>
@if ('SETTING.FEATURES.' + (toggleStateKey | uppercase) + '_DESCRIPTION' | translate; as i18nDescription) {
<cnsl-info-section class="feature-info">{{ i18nDescription }}</cnsl-info-section>
}
</div>
<ng-content></ng-content>
<cnsl-info-section
class="feature-info"
*ngIf="'SETTING.FEATURES.' + (toggleStateKey | uppercase) + '_DESCRIPTION' | translate as i18nDescription"
>{{ i18nDescription }}</cnsl-info-section
>
</div>
}
@@ -1,4 +1,4 @@
import { AsyncPipe, NgIf, UpperCasePipe } from '@angular/common';
import { AsyncPipe, UpperCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { MatTooltipModule } from '@angular/material/tooltip';
@@ -23,7 +23,6 @@ import { map } from 'rxjs/operators';
MatTooltipModule,
InfoSectionModule,
AsyncPipe,
NgIf,
],
})
export class FeatureToggleComponent<TKey extends ToggleStateKeys, TValue extends ToggleStates[TKey]> {
@@ -18,15 +18,21 @@
</button>
</ng-template>
<cnsl-card *ngIf="toggleStates$ | async as toggleStates">
<div class="features">
<cnsl-feature-toggle
*ngFor="let key of FEATURE_KEYS"
[toggleStateKey]="key"
[toggleState]="toggleStates[key]"
(toggleChange)="saveFeature(key, $event)"
></cnsl-feature-toggle>
<cnsl-login-v2-feature-toggle [toggleState]="toggleStates.loginV2" (toggleChanged)="saveFeature('loginV2', $event)" />
</div>
</cnsl-card>
@if (toggleStates$ | async; as toggleStates) {
<cnsl-card>
<div class="features">
@for (key of FEATURE_KEYS; track key) {
<cnsl-feature-toggle
[toggleStateKey]="key"
[toggleState]="toggleStates[key]"
(toggleChange)="saveFeature(key, $event)"
></cnsl-feature-toggle>
}
<cnsl-login-v2-feature-toggle
[toggleState]="toggleStates.loginV2"
(toggleChanged)="saveFeature('loginV2', $event)"
/>
</div>
</cnsl-card>
}
</div>
@@ -4,32 +4,40 @@
<input cnslInput type="text" placeholder="" #nameInput [formControl]="myControl" [matAutocomplete]="auto" />
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngIf="isLoading()" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option *ngFor="let framework of filteredOptions | async" [value]="framework.id">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
@if (isLoading()) {
<mat-option class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
}
@for (framework of filteredOptions | async; track framework) {
<mat-option [value]="framework.id">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper">
@if (framework.imgSrcDark) {
<img class="dark-only" [src]="framework.imgSrcDark" />
}
@if (framework.imgSrcLight) {
<img class="light-only" [src]="framework.imgSrcLight" />
}
</div>
<span class="fill-space"></span>
<span>{{ framework.title }}</span>
</div>
<span class="fill-space"></span>
<span>{{ framework.title }}</span>
</div>
</div>
</mat-option>
<mat-option *ngIf="withCustom" [value]="'other'">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper"></div>
<span class="fill-space"></span>
<span>{{ 'QUICKSTART.FRAMEWORK_OTHER' | translate }}</span>
</mat-option>
}
@if (withCustom) {
<mat-option [value]="'other'">
<div class="framework-option">
<div class="framework-option-column">
<div class="img-wrapper"></div>
<span class="fill-space"></span>
<span>{{ 'QUICKSTART.FRAMEWORK_OTHER' | translate }}</span>
</div>
</div>
</div>
</mat-option>
</mat-option>
}
</mat-autocomplete>
</cnsl-form-field>
</form>
@@ -1,18 +1,22 @@
<div class="framework-change-wrapper">
<div class="framework-card-wrapper">
<div class="framework-card card" *ngIf="framework | async as framework; else frameworkFallback">
<div>
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" />
@if (framework | async; as framework) {
<div class="framework-card card">
<div>
@if (framework.imgSrcDark) {
<img class="dark-only" [src]="framework.imgSrcDark" />
}
@if (framework.imgSrcLight) {
<img class="light-only" [src]="framework.imgSrcLight" />
}
</div>
<span>{{ framework.title }}</span>
</div>
<span>{{ framework.title }}</span>
</div>
<ng-template #frameworkFallback>
} @else {
<div class="framework-card card">
<span>{{ 'QUICKSTART.SELECT_FRAMEWORK' | translate }}</span>
</div>
</ng-template>
}
<button (click)="openDialog()" mat-stroked-button>
{{ 'ACTIONS.CHANGE' | translate }}
@@ -21,23 +21,35 @@
<span class="left cnsl-secondary-text">
{{ 'APP.GRANT' | translate }}
</span>
<span class="right" *ngIf="configuration.grantTypesList && configuration.grantTypesList.length > 0">
[<span *ngFor="let element of configuration.grantTypesList ?? []; index as i">
{{ 'APP.OIDC.GRANT.' + element | translate }}
{{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
@if (configuration.grantTypesList && configuration.grantTypesList.length > 0) {
<span class="right">
[
@for (element of configuration.grantTypesList ?? []; track element; let i = $index) {
<span>
{{ 'APP.OIDC.GRANT.' + element | translate }}
{{ i < configuration.grantTypesList.length - 1 ? ', ' : '' }}
</span>
}
]
</span>
}
</div>
<div class="row">
<span class="left cnsl-secondary-text">
{{ 'APP.OIDC.RESPONSETYPE' | translate }}
</span>
<span class="right" *ngIf="configuration.responseTypesList && configuration.responseTypesList.length > 0">
[<span *ngFor="let element of configuration.responseTypesList ?? []; index as i">
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
{{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }} </span
>]
</span>
@if (configuration.responseTypesList && configuration.responseTypesList.length > 0) {
<span class="right">
[
@for (element of configuration.responseTypesList ?? []; track element; let i = $index) {
<span>
{{ 'APP.OIDC.RESPONSE.' + element | translate }}
{{ i < configuration.responseTypesList.length - 1 ? ', ' : '' }}
</span>
}
]
</span>
}
</div>
<div class="row">
@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
@@ -25,7 +24,7 @@ export type Framework = FrameworkDefinition & {
selector: 'cnsl-oidc-app-configuration',
templateUrl: './oidc-configuration.component.html',
styleUrls: ['./oidc-configuration.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
imports: [TranslateModule, RouterModule, MatButtonModule],
})
export class OIDCConfigurationComponent {
@Input() public name?: string;
@@ -13,11 +13,15 @@
</div>
</div>
<div class="quickstart-card-wrapper">
<ng-container *ngFor="let framework of frameworks.slice(0, 18)">
@for (framework of frameworks.slice(0, 18); track framework) {
<a [routerLink]="['/projects', 'app-create']" [queryParams]="{ framework: framework.id }" class="quickstart-card card">
<img class="dark-only" *ngIf="framework.imgSrcDark" [src]="framework.imgSrcDark" alt="{{ framework.title }}" />
<img class="light-only" *ngIf="framework.imgSrcLight" [src]="framework.imgSrcLight" alt="{{ framework.title }}" />
@if (framework.imgSrcDark) {
<img class="dark-only" [src]="framework.imgSrcDark" alt="{{ framework.title }}" />
}
@if (framework.imgSrcLight) {
<img class="light-only" [src]="framework.imgSrcLight" alt="{{ framework.title }}" />
}
</a>
</ng-container>
}
</div>
</div>
@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
@@ -27,7 +26,7 @@ export type Framework = FrameworkDefinition & {
selector: 'cnsl-quickstart',
templateUrl: './quickstart.component.html',
styleUrls: ['./quickstart.component.scss'],
imports: [TranslateModule, RouterModule, CommonModule, MatButtonModule],
imports: [TranslateModule, RouterModule, MatButtonModule],
})
export class QuickstartComponent {
public frameworks: FrameworkDefinition[] = frameworkDefinition
@@ -1,51 +1,56 @@
<div class="accounts-card" *ngIf="user$ | async as user">
<cnsl-avatar
(click)="editUserProfile()"
class="avatar"
[ngClass]="{ 'iam-user': iamuser }"
[forColor]="user.preferredLoginName"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[name]="user.human?.profile?.displayName ?? ''"
[size]="80"
>
</cnsl-avatar>
<span class="u-name">{{ user?.human?.profile?.displayName ? user?.human?.profile?.displayName : 'A' }}</span>
<span class="u-email" *ngIf="user?.preferredLoginName">{{ user?.preferredLoginName }}</span>
<button (click)="editUserProfile()" mat-stroked-button>{{ 'USER.EDITACCOUNT' | translate }}</button>
<div class="l-accounts">
<mat-progress-bar *ngIf="(sessions$ | async) === null" color="primary" mode="indeterminate"></mat-progress-bar>
<a class="row" *ngFor="let session of sessions$ | async" (click)="selectAccount(session.loginName)">
<cnsl-avatar
*ngIf="session && session.displayName"
class="small-avatar"
[avatarUrl]="session.avatarUrl || ''"
[name]="session.displayName"
[forColor]="session.loginName"
[size]="32"
>
</cnsl-avatar>
<div class="col">
<span class="user-title">{{ session.displayName ? session.displayName : session.userName }} </span>
<span class="loginname">{{ session.loginName }}</span>
<span class="state inactive" *ngIf="$any(session.authState) === UserState.USER_STATE_INACTIVE">{{
'USER.STATE.' + session.authState | translate
}}</span>
</div>
<mat-icon>keyboard_arrow_right</mat-icon>
</a>
<a class="row" (click)="selectNewAccount()">
<div class="icon-wrapper">
<i class="las la-user-plus"></i>
</div>
<span class="col">
<span class="user-title">{{ 'USER.ADDACCOUNT' | translate }}</span>
</span>
<mat-icon>keyboard_arrow_right</mat-icon>
</a>
@if (user$ | async; as user) {
<div class="accounts-card">
<cnsl-avatar
(click)="editUserProfile()"
class="avatar"
[ngClass]="{ 'iam-user': iamuser }"
[forColor]="user.preferredLoginName"
[avatarUrl]="user.human?.profile?.avatarUrl || ''"
[name]="user.human?.profile?.displayName ?? ''"
[size]="80"
>
</cnsl-avatar>
<span class="u-name">{{ user?.human?.profile?.displayName ? user?.human?.profile?.displayName : 'A' }}</span>
@if (user?.preferredLoginName) {
<span class="u-email">{{ user?.preferredLoginName }}</span>
}
<button (click)="editUserProfile()" mat-stroked-button>{{ 'USER.EDITACCOUNT' | translate }}</button>
<div class="l-accounts">
@if ((sessions$ | async) === null) {
<mat-progress-bar color="primary" mode="indeterminate"></mat-progress-bar>
}
@for (session of sessions$ | async; track session) {
<a class="row" (click)="selectAccount(session.loginName)">
@if (session && session.displayName) {
<cnsl-avatar
class="small-avatar"
[avatarUrl]="session.avatarUrl || ''"
[name]="session.displayName"
[forColor]="session.loginName"
[size]="32"
>
</cnsl-avatar>
}
<div class="col">
<span class="user-title">{{ session.displayName ? session.displayName : session.userName }} </span>
<span class="loginname">{{ session.loginName }}</span>
@if ($any(session.authState) === UserState.USER_STATE_INACTIVE) {
<span class="state inactive">{{ 'USER.STATE.' + session.authState | translate }}</span>
}
</div>
<mat-icon>keyboard_arrow_right</mat-icon>
</a>
}
<a class="row" (click)="selectNewAccount()">
<div class="icon-wrapper">
<i class="las la-user-plus"></i>
</div>
<span class="col">
<span class="user-title">{{ 'USER.ADDACCOUNT' | translate }}</span>
</span>
<mat-icon>keyboard_arrow_right</mat-icon>
</a>
</div>
<button (click)="logout()" color="warn" mat-stroked-button>{{ 'MENU.LOGOUT' | translate }}</button>
</div>
<button (click)="logout()" color="warn" mat-stroked-button>{{ 'MENU.LOGOUT' | translate }}</button>
</div>
}
@@ -1,62 +1,81 @@
<div
*ngIf="type !== ActionKeysType.ORGSWITCHER && (isHandset$ | async) === false"
class="action-keys-wrapper"
[ngSwitch]="type"
[ngClass]="{ 'without-margin': withoutMargin, 'no-contrast-mode': doNotUseContrast }"
>
<div *ngSwitchCase="ActionKeysType.CLEAR" class="action-keys-row">
<div class="action-key esc">
<div class="key-overlay"></div>
<span>ESC</span>
</div>
@if (type !== ActionKeysType.ORGSWITCHER && (isHandset$ | async) === false) {
<div class="action-keys-wrapper" [ngClass]="{ 'without-margin': withoutMargin, 'no-contrast-mode': doNotUseContrast }">
@switch (type) {
@case (ActionKeysType.CLEAR) {
<div class="action-keys-row">
<div class="action-key esc">
<div class="key-overlay"></div>
<span>ESC</span>
</div>
</div>
}
@case (ActionKeysType.ADD) {
<div class="action-keys-row" data-e2e="action-key-add">
<div class="action-key">
<div class="key-overlay"></div>
<span>N</span>
</div>
</div>
}
@case (ActionKeysType.DELETE) {
<div class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
@if (isMacLike || isIOS) {
<span>⌘</span>
} @else {
<span>crtl</span>
}
</div>
+
<div class="action-key">
<div class="key-overlay"></div>
<span>BS</span>
</div>
</div>
}
@case (ActionKeysType.DEACTIVATE) {
<div class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
@if (isMacLike || isIOS) {
<span>⌘</span>
} @else {
<span>crtl</span>
}
</div>
+
<div class="action-key">
<div class="key-overlay"></div>
<span>↓</span>
</div>
</div>
}
@case (ActionKeysType.REACTIVATE) {
<div class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
@if (isMacLike || isIOS) {
<span>⌘</span>
} @else {
<span>crtl</span>
}
</div>
+
<div class="action-key">
<div class="key-overlay"></div>
<span>↑</span>
</div>
</div>
}
@case (ActionKeysType.FILTER) {
<div class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
<span>F</span>
</div>
</div>
}
}
</div>
<div *ngSwitchCase="ActionKeysType.ADD" class="action-keys-row" data-e2e="action-key-add">
<div class="action-key">
<div class="key-overlay"></div>
<span>N</span>
</div>
</div>
<div *ngSwitchCase="ActionKeysType.DELETE" class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
<span *ngIf="isMacLike || isIOS; else otherOS">⌘</span>
</div>
+
<div class="action-key">
<div class="key-overlay"></div>
<span>BS</span>
</div>
</div>
<div *ngSwitchCase="ActionKeysType.DEACTIVATE" class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
<span *ngIf="isMacLike || isIOS; else otherOS">⌘</span>
</div>
+
<div class="action-key">
<div class="key-overlay"></div>
<span>↓</span>
</div>
</div>
<div *ngSwitchCase="ActionKeysType.REACTIVATE" class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
<span *ngIf="isMacLike || isIOS; else otherOS">⌘</span>
</div>
+
<div class="action-key">
<div class="key-overlay"></div>
<span>↑</span>
</div>
</div>
<div *ngSwitchCase="ActionKeysType.FILTER" class="action-keys-row">
<div class="action-key">
<div class="key-overlay"></div>
<span>F</span>
</div>
</div>
</div>
<ng-template #otherOS>
<span>crtl</span>
</ng-template>
}
@@ -24,9 +24,11 @@
<th mat-header-cell *matHeaderCellDef>{{ 'ACTIONSTWO.EXECUTION.TABLE.TARGET' | translate }}</th>
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
<div class="target-key">
<cnsl-project-role-chip *ngFor="let target of row.mappedTargets; trackBy: trackTarget" [roleName]="target.name">
{{ target.name }}
</cnsl-project-role-chip>
@for (target of row.mappedTargets; track trackTarget($index, target)) {
<cnsl-project-role-chip [roleName]="target.name">
{{ target.name }}
</cnsl-project-role-chip>
}
</div>
</td>
</ng-container>
@@ -3,12 +3,16 @@
<input #input cnslInput type="text" placeholder="" [formControl]="control" [matAutocomplete]="autocomplete" />
<mat-autocomplete requireSelection #autocomplete="matAutocomplete">
<mat-option *ngIf="items === undefined" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option *ngFor="let item of items | filter: input | async" [value]="item">
<span>{{ item }}</span>
</mat-option>
@if (items === undefined) {
<mat-option class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
}
@for (item of items | filter: input | async; track item) {
<mat-option [value]="item">
<span>{{ item }}</span>
</mat-option>
}
</mat-autocomplete>
<ng-content></ng-content>
@@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, Component, Input, Pipe, PipeTransform } from '@angular/core';
import { AsyncPipe, NgForOf, NgIf } from '@angular/common';
import { AsyncPipe } from '@angular/common';
import { FormFieldModule } from 'src/app/modules/form-field/form-field.module';
import { InputModule } from 'src/app/modules/input/input.module';
import { LabelModule } from 'src/app/modules/label/label.module';
@@ -39,8 +39,6 @@ class Filter implements PipeTransform {
MatOptionModule,
MatProgressSpinnerModule,
TranslateModule,
NgIf,
NgForOf,
ReactiveFormsModule,
],
})
@@ -1,79 +1,70 @@
<form *ngIf="form$ | async as form" [formGroup]="form.form" class="form-grid" (ngSubmit)="submit(form)">
<ng-container *ngIf="form.case === 'request' || form.case === 'response'">
<p class="cnsl-secondary-text">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.REQ_RESP_DESCRIPTION' | translate }}</p>
<div class="emailVerified">
<mat-checkbox [formControl]="form.form.controls.all">
<div class="execution-condition-text">
<span>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL.TITLE' | translate }}</span>
<span class="description cnsl-secondary-text">{{
'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL.DESCRIPTION' | translate
}}</span>
</div>
</mat-checkbox>
@if (form$ | async; as form) {
<form [formGroup]="form.form" class="form-grid" (ngSubmit)="submit(form)">
@if (form.case === 'request' || form.case === 'response') {
<p class="cnsl-secondary-text">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.REQ_RESP_DESCRIPTION' | translate }}</p>
<div class="emailVerified">
<mat-checkbox [formControl]="form.form.controls.all">
<div class="execution-condition-text">
<span>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL.TITLE' | translate }}</span>
<span class="description cnsl-secondary-text">{{
'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL.DESCRIPTION' | translate
}}</span>
</div>
</mat-checkbox>
</div>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_SERVICE.TITLE' | translate }}</p>
<cnsl-actions-two-add-action-autocomplete-input
label="ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_SERVICE.DESCRIPTION"
[items]="listExecutionServicesQuery.data()"
[control]="form.form.controls.service"
></cnsl-actions-two-add-action-autocomplete-input>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_METHOD.TITLE' | translate }}</p>
<cnsl-actions-two-add-action-autocomplete-input
label="ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_METHOD.DESCRIPTION"
[items]="filteredExecutionMethods()"
[control]="form.form.controls.method"
></cnsl-actions-two-add-action-autocomplete-input>
}
@if (form.case === 'function') {
<cnsl-actions-two-add-action-autocomplete-input
label="ACTIONSTWO.EXECUTION.DIALOG.CONDITION.FUNCTIONNAME.TITLE"
[items]="listExecutionFunctionsQuery.data()"
[control]="form.form.controls.name"
>
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.FUNCTIONNAME.DESCRIPTION' | translate
}}</span>
</cnsl-actions-two-add-action-autocomplete-input>
}
@if (form.case === 'event') {
<div class="emailVerified">
<mat-checkbox [formControl]="form.form.controls.all">
<div class="execution-condition-text">
<span>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL.TITLE' | translate }}</span>
<span class="description cnsl-secondary-text">{{
'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL_EVENTS' | translate
}}</span>
</div>
</mat-checkbox>
</div>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_GROUP.TITLE' | translate }}</p>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_GROUP.DESCRIPTION' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" [formControl]="form.form.controls.group" />
</cnsl-form-field>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_EVENT.TITLE' | translate }}</p>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_EVENT.DESCRIPTION' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" [formControl]="form.form.controls.event" />
</cnsl-form-field>
}
<div class="actions">
<button mat-stroked-button (click)="back.emit()">
{{ 'ACTIONS.BACK' | translate }}
</button>
<button [disabled]="form.form.invalid" color="primary" mat-raised-button type="submit">
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_SERVICE.TITLE' | translate }}</p>
<cnsl-actions-two-add-action-autocomplete-input
label="ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_SERVICE.DESCRIPTION"
[items]="listExecutionServicesQuery.data()"
[control]="form.form.controls.service"
></cnsl-actions-two-add-action-autocomplete-input>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_METHOD.TITLE' | translate }}</p>
<cnsl-actions-two-add-action-autocomplete-input
label="ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_METHOD.DESCRIPTION"
[items]="filteredExecutionMethods()"
[control]="form.form.controls.method"
></cnsl-actions-two-add-action-autocomplete-input>
</ng-container>
<ng-container *ngIf="form.case === 'function'">
<cnsl-actions-two-add-action-autocomplete-input
label="ACTIONSTWO.EXECUTION.DIALOG.CONDITION.FUNCTIONNAME.TITLE"
[items]="listExecutionFunctionsQuery.data()"
[control]="form.form.controls.name"
>
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.FUNCTIONNAME.DESCRIPTION' | translate
}}</span>
</cnsl-actions-two-add-action-autocomplete-input>
</ng-container>
<ng-container *ngIf="form.case === 'event'">
<div class="emailVerified">
<mat-checkbox [formControl]="form.form.controls.all">
<div class="execution-condition-text">
<span>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL.TITLE' | translate }}</span>
<span class="description cnsl-secondary-text">{{
'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.ALL_EVENTS' | translate
}}</span>
</div>
</mat-checkbox>
</div>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_GROUP.TITLE' | translate }}</p>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_GROUP.DESCRIPTION' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" [formControl]="form.form.controls.group" />
</cnsl-form-field>
<p class="condition-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_EVENT.TITLE' | translate }}</p>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CONDITION.SELECT_EVENT.DESCRIPTION' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" [formControl]="form.form.controls.event" />
</cnsl-form-field>
</ng-container>
<div class="actions">
<button mat-stroked-button (click)="back.emit()">
{{ 'ACTIONS.BACK' | translate }}
</button>
<button [disabled]="form.form.invalid" color="primary" mat-raised-button type="submit">
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
</form>
</form>
}
@@ -1,28 +1,35 @@
<h2 *ngIf="!data.execution" mat-dialog-title>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CREATE_TITLE' | translate }}</h2>
<h2 *ngIf="data.execution" mat-dialog-title>{{ 'ACTIONSTWO.EXECUTION.DIALOG.UPDATE_TITLE' | translate }}</h2>
@if (!data.execution) {
<h2 mat-dialog-title>{{ 'ACTIONSTWO.EXECUTION.DIALOG.CREATE_TITLE' | translate }}</h2>
}
@if (data.execution) {
<h2 mat-dialog-title>{{ 'ACTIONSTWO.EXECUTION.DIALOG.UPDATE_TITLE' | translate }}</h2>
}
<mat-dialog-content>
<div class="framework-change-block" [ngSwitch]="page()">
<cnsl-actions-two-add-action-type
*ngSwitchCase="Page.Type"
[initialValue]="typeSignal()"
(back)="back()"
(continue)="typeSignal.set($event); continue()"
></cnsl-actions-two-add-action-type>
<cnsl-actions-two-add-action-condition
*ngSwitchCase="Page.Condition"
[conditionType]="typeSignal()"
(back)="back()"
(continue)="conditionSignal.set({ conditionType: { case: typeSignal(), value: $event } }); continue()"
></cnsl-actions-two-add-action-condition>
<cnsl-actions-two-add-action-target
*ngSwitchCase="Page.Target"
(back)="back()"
[hideBackButton]="!!data.execution"
(continue)="targetsSignal.set($event); continue()"
[preselectedTargetIds]="preselectedTargetIds"
></cnsl-actions-two-add-action-target>
<div class="framework-change-block">
@switch (page()) {
@case (Page.Type) {
<cnsl-actions-two-add-action-type
[initialValue]="typeSignal()"
(back)="back()"
(continue)="typeSignal.set($event); continue()"
></cnsl-actions-two-add-action-type>
}
@case (Page.Condition) {
<cnsl-actions-two-add-action-condition
[conditionType]="typeSignal()"
(back)="back()"
(continue)="conditionSignal.set({ conditionType: { case: typeSignal(), value: $event } }); continue()"
></cnsl-actions-two-add-action-condition>
}
@case (Page.Target) {
<cnsl-actions-two-add-action-target
(back)="back()"
[hideBackButton]="!!data.execution"
(continue)="targetsSignal.set($event); continue()"
[preselectedTargetIds]="preselectedTargetIds"
></cnsl-actions-two-add-action-target>
}
}
</div>
</mat-dialog-content>
@@ -9,7 +9,7 @@ import {
ConditionType,
} from './actions-two-add-action-condition/actions-two-add-action-condition.component';
import { ActionsTwoAddActionTargetComponent } from './actions-two-add-action-target/actions-two-add-action-target.component';
import { CommonModule } from '@angular/common';
import { Condition, Execution } from '@zitadel/proto/zitadel/action/v2/execution_pb';
import { Subject } from 'rxjs';
import { SetExecutionRequestSchema } from '@zitadel/proto/zitadel/action/v2/action_service_pb';
@@ -54,7 +54,6 @@ export type ActionTwoAddActionDialogResult = MessageInitShape<typeof SetExecutio
templateUrl: './actions-two-add-action-dialog.component.html',
styleUrls: ['./actions-two-add-action-dialog.component.scss'],
imports: [
CommonModule,
MatButtonModule,
MatDialogModule,
TranslateModule,
@@ -1,72 +1,72 @@
<form *ngIf="form$ | async as form" class="form-grid" [formGroup]="form" (ngSubmit)="submit()">
<p class="target-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.TARGET.DESCRIPTION' | translate }}</p>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.EXECUTION.DIALOG.TARGET.TARGET.DESCRIPTION' | translate }}</cnsl-label>
<input
cnslInput
#trigger="matAutocompleteTrigger"
#input
type="text"
[formControl]="form.controls.autocomplete"
[matAutocomplete]="autoservice"
(keydown.enter)="handleEnter($event, form); input.blur(); trigger.closePanel()"
/>
<mat-autocomplete #autoservice="matAutocomplete">
<mat-option *ngIf="targets().state === 'loading'" class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
<mat-option
*ngFor="let target of selectableTargets(); trackBy: trackTarget"
#option
(click)="addTarget(target, form); option.deselect()"
[value]="target.name"
>
{{ target.name }}
</mat-option>
</mat-autocomplete>
</cnsl-form-field>
<table mat-table cdkDropList (cdkDropListDropped)="drop($event, form)" [dataSource]="dataSource" [trackBy]="trackTarget">
<ng-container matColumnDef="order">
<th mat-header-cell *matHeaderCellDef>Reorder</th>
<td mat-cell *cnslCellDef="let row; let i = index; dataSource: dataSource">
<i class="las la-bars"></i>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
<cnsl-project-role-chip [roleName]="row.name">{{ row.name }}</cnsl-project-role-chip>
</td>
</ng-container>
<ng-container matColumnDef="deleteAction" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *cnslCellDef="let i = index; dataSource: dataSource">
<cnsl-table-actions>
<button
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
color="warn"
(click)="removeTarget(i, form)"
mat-icon-button
>
<i class="las la-trash"></i>
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['order', 'name', 'deleteAction']"></tr>
<tr class="drag-row" cdkDrag mat-row *matRowDef="let row; columns: ['order', 'name', 'deleteAction']"></tr>
</table>
<div class="actions">
<button *ngIf="!hideBackButton" mat-stroked-button (click)="back.emit()">
{{ 'ACTIONS.BACK' | translate }}
</button>
<span class="fill-space"></span>
<button color="primary" [disabled]="form.invalid" mat-raised-button type="submit">
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
</form>
@if (form$ | async; as form) {
<form class="form-grid" [formGroup]="form" (ngSubmit)="submit()">
<p class="target-description">{{ 'ACTIONSTWO.EXECUTION.DIALOG.TARGET.DESCRIPTION' | translate }}</p>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.EXECUTION.DIALOG.TARGET.TARGET.DESCRIPTION' | translate }}</cnsl-label>
<input
cnslInput
#trigger="matAutocompleteTrigger"
#input
type="text"
[formControl]="form.controls.autocomplete"
[matAutocomplete]="autoservice"
(keydown.enter)="handleEnter($event, form); input.blur(); trigger.closePanel()"
/>
<mat-autocomplete #autoservice="matAutocomplete">
@if (targets().state === 'loading') {
<mat-option class="is-loading">
<mat-spinner diameter="30"></mat-spinner>
</mat-option>
}
@for (target of selectableTargets(); track trackTarget($index, target)) {
<mat-option #option (click)="addTarget(target, form); option.deselect()" [value]="target.name">
{{ target.name }}
</mat-option>
}
</mat-autocomplete>
</cnsl-form-field>
<table mat-table cdkDropList (cdkDropListDropped)="drop($event, form)" [dataSource]="dataSource" [trackBy]="trackTarget">
<ng-container matColumnDef="order">
<th mat-header-cell *matHeaderCellDef>Reorder</th>
<td mat-cell *cnslCellDef="let row; let i = index; dataSource: dataSource">
<i class="las la-bars"></i>
</td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef>Name</th>
<td mat-cell *cnslCellDef="let row; dataSource: dataSource">
<cnsl-project-role-chip [roleName]="row.name">{{ row.name }}</cnsl-project-role-chip>
</td>
</ng-container>
<ng-container matColumnDef="deleteAction" stickyEnd>
<th mat-header-cell *matHeaderCellDef></th>
<td mat-cell *cnslCellDef="let i = index; dataSource: dataSource">
<cnsl-table-actions>
<button
actions
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
color="warn"
(click)="removeTarget(i, form)"
mat-icon-button
>
<i class="las la-trash"></i>
</button>
</cnsl-table-actions>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="['order', 'name', 'deleteAction']"></tr>
<tr class="drag-row" cdkDrag mat-row *matRowDef="let row; columns: ['order', 'name', 'deleteAction']"></tr>
</table>
<div class="actions">
@if (!hideBackButton) {
<button mat-stroked-button (click)="back.emit()">
{{ 'ACTIONS.BACK' | translate }}
</button>
}
<span class="fill-space"></span>
<button color="primary" [disabled]="form.invalid" mat-raised-button type="submit">
{{ 'ACTIONS.CONTINUE' | translate }}
</button>
</div>
</form>
}
@@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output, signal } from '@angular/core';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
@@ -20,7 +19,7 @@ import { ConditionType } from '../actions-two-add-action-condition/actions-two-a
selector: 'cnsl-actions-two-add-action-type',
templateUrl: './actions-two-add-action-type.component.html',
styleUrls: ['./actions-two-add-action-type.component.scss'],
imports: [TranslateModule, MatRadioModule, RouterModule, ReactiveFormsModule, FormsModule, CommonModule, MatButtonModule],
imports: [TranslateModule, MatRadioModule, RouterModule, ReactiveFormsModule, FormsModule, MatButtonModule],
})
export class ActionsTwoAddActionTypeComponent {
protected readonly typeForm: ReturnType<typeof this.buildActionTypeForm> = this.buildActionTypeForm();
@@ -2,71 +2,70 @@
<mat-dialog-content>
<p class="target-description">{{ 'ACTIONSTWO.TARGET.CREATE.DESCRIPTION' | translate }}</p>
<form *ngIf="targetForm" class="form-grid" [formGroup]="targetForm" (ngSubmit)="closeWithResult()">
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.NAME' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" formControlName="name" />
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.TARGET.CREATE.NAME_DESCRIPTION' | translate
}}</span>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.ENDPOINT' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" formControlName="endpoint" />
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.TARGET.CREATE.ENDPOINT_DESCRIPTION' | translate
}}</span>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.TYPE' | translate }}</cnsl-label>
<mat-select [formControl]="targetForm.controls.type" name="type">
<mat-option *ngFor="let type of targetTypes" [value]="type">
{{ 'ACTIONSTWO.TARGET.CREATE.TYPES.' + type | translate }}
</mat-option>
</mat-select>
<span class="name-hint cnsl-secondary-text types-description">
{{ 'ACTIONSTWO.TARGET.CREATE.TYPES_DESCRIPTION' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.PAYLOAD_TYPE' | translate }}</cnsl-label>
<mat-select [formControl]="targetForm.controls.payloadType" name="payloadType">
<mat-option *ngFor="let type of payloadTypes" [value]="type">
{{ 'ACTIONSTWO.TARGET.CREATE.PAYLOAD_TYPES.' + type | translate }}
</mat-option>
</mat-select>
<span class="name-hint cnsl-secondary-text types-description">
{{ 'ACTIONSTWO.TARGET.CREATE.PAYLOAD_TYPE_DESCRIPTION' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.TIMEOUT' | translate }}</cnsl-label>
<input cnslInput type="number" placeholder="10" [formControl]="targetForm.controls.timeout" />
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.TARGET.CREATE.TIMEOUT_DESCRIPTION' | translate
}}</span>
</cnsl-form-field>
<mat-checkbox
*ngIf="targetForm.controls.type.value === 'restWebhook' || targetForm.controls.type.value === 'restCall'"
class="target-checkbox"
[formControl]="targetForm.controls.interruptOnError"
>
<div class="target-condition-text">
<span>{{ 'ACTIONSTWO.TARGET.CREATE.INTERRUPT_ON_ERROR' | translate }}</span>
<span class="description cnsl-secondary-text">{{
'ACTIONSTWO.TARGET.CREATE.INTERRUPT_ON_ERROR_DESCRIPTION' | translate
@if (targetForm) {
<form class="form-grid" [formGroup]="targetForm" (ngSubmit)="closeWithResult()">
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.NAME' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" formControlName="name" />
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.TARGET.CREATE.NAME_DESCRIPTION' | translate
}}</span>
<span [style.color]="'var(--warn)'" class="description cnsl-secondary-text"
>{{ 'ACTIONSTWO.TARGET.CREATE.INTERRUPT_ON_ERROR_WARNING' | translate }}
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.ENDPOINT' | translate }}</cnsl-label>
<input cnslInput type="text" placeholder="" formControlName="endpoint" />
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.TARGET.CREATE.ENDPOINT_DESCRIPTION' | translate
}}</span>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.TYPE' | translate }}</cnsl-label>
<mat-select [formControl]="targetForm.controls.type" name="type">
@for (type of targetTypes; track type) {
<mat-option [value]="type">
{{ 'ACTIONSTWO.TARGET.CREATE.TYPES.' + type | translate }}
</mat-option>
}
</mat-select>
<span class="name-hint cnsl-secondary-text types-description">
{{ 'ACTIONSTWO.TARGET.CREATE.TYPES_DESCRIPTION' | translate }}
</span>
</div>
</mat-checkbox>
</form>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.PAYLOAD_TYPE' | translate }}</cnsl-label>
<mat-select [formControl]="targetForm.controls.payloadType" name="payloadType">
@for (type of payloadTypes; track type) {
<mat-option [value]="type">
{{ 'ACTIONSTWO.TARGET.CREATE.PAYLOAD_TYPES.' + type | translate }}
</mat-option>
}
</mat-select>
<span class="name-hint cnsl-secondary-text types-description">
{{ 'ACTIONSTWO.TARGET.CREATE.PAYLOAD_TYPE_DESCRIPTION' | translate }}
</span>
</cnsl-form-field>
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'ACTIONSTWO.TARGET.CREATE.TIMEOUT' | translate }}</cnsl-label>
<input cnslInput type="number" placeholder="10" [formControl]="targetForm.controls.timeout" />
<span class="name-hint cnsl-secondary-text" cnslHint>{{
'ACTIONSTWO.TARGET.CREATE.TIMEOUT_DESCRIPTION' | translate
}}</span>
</cnsl-form-field>
@if (targetForm.controls.type.value === 'restWebhook' || targetForm.controls.type.value === 'restCall') {
<mat-checkbox class="target-checkbox" [formControl]="targetForm.controls.interruptOnError">
<div class="target-condition-text">
<span>{{ 'ACTIONSTWO.TARGET.CREATE.INTERRUPT_ON_ERROR' | translate }}</span>
<span class="description cnsl-secondary-text">{{
'ACTIONSTWO.TARGET.CREATE.INTERRUPT_ON_ERROR_DESCRIPTION' | translate
}}</span>
<span [style.color]="'var(--warn)'" class="description cnsl-secondary-text"
>{{ 'ACTIONSTWO.TARGET.CREATE.INTERRUPT_ON_ERROR_WARNING' | translate }}
</span>
</div>
</mat-checkbox>
}
</form>
}
</mat-dialog-content>
<div>
<mat-dialog-actions class="actions">
@@ -2,7 +2,7 @@ import { Component, Inject } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
import { TranslateModule } from '@ngx-translate/core';
import { CommonModule } from '@angular/common';
import { FormBuilder, FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { InputModule } from '../../input/input.module';
@@ -21,7 +21,6 @@ type TargetTypes = ActionTwoAddTargetDialogComponent['targetTypes'][number];
templateUrl: './actions-two-add-target-dialog.component.html',
styleUrls: ['./actions-two-add-target-dialog.component.scss'],
imports: [
CommonModule,
MatButtonModule,
MatDialogModule,
ReactiveFormsModule,
@@ -5,9 +5,11 @@
<cnsl-form-field class="form-field">
<cnsl-label>{{ 'USER.MACHINE.TYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="type">
<mat-option *ngFor="let t of types" [value]="t">
{{ 'USER.MACHINE.KEYTYPES.' + t | translate }}
</mat-option>
@for (t of types; track t) {
<mat-option [value]="t">
{{ 'USER.MACHINE.KEYTYPES.' + t | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
@@ -16,10 +18,12 @@
<input cnslInput [matDatepicker]="picker" [min]="startDate" [formControl]="dateControl" />
<mat-datepicker-toggle style="top: 0" cnslSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker [startAt]="startDate"></mat-datepicker>
<span cnslError *ngIf="dateControl && dateControl.errors && dateControl.errors['matDatepickerMin']?.min">
{{ 'USER.MACHINE.CHOOSEDATEAFTER' | translate }}:
{{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate: 'EEE dd. MMM' }}
</span>
@if (dateControl && dateControl.errors && dateControl.errors['matDatepickerMin']?.min) {
<span cnslError>
{{ 'USER.MACHINE.CHOOSEDATEAFTER' | translate }}:
{{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate: 'EEE dd. MMM' }}
</span>
}
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
@@ -5,17 +5,18 @@
<div mat-dialog-content>
<p class="desc cnsl-secondary-text">{{ 'ORG_DETAIL.MEMBER.ADDDESCRIPTION' | translate }}</p>
<ng-container *ngIf="showCreationTypeSelector">
@if (showCreationTypeSelector) {
<cnsl-form-field class="full-width">
<cnsl-label>{{ 'MEMBER.CREATIONTYPE' | translate }}</cnsl-label>
<mat-select [(ngModel)]="creationType" (selectionChange)="loadRoles()">
<mat-option *ngFor="let type of creationTypes" [value]="type.type" [disabled]="(type.disabled$ | async) === false">
{{ 'MEMBER.CREATIONTYPES.' + type.type | translate }}
</mat-option>
@for (type of creationTypes; track type) {
<mat-option [value]="type.type" [disabled]="(type.disabled$ | async) === false">
{{ 'MEMBER.CREATIONTYPES.' + type.type | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<ng-container *ngIf="creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED">
@if (creationType === CreationType.PROJECT_OWNED || creationType === CreationType.PROJECT_GRANTED) {
<p>{{ 'PROJECT.GRANT.CREATE.SEL_PROJECT' | translate }}</p>
<cnsl-search-project-autocomplete
class="block"
@@ -29,26 +30,22 @@
"
>
</cnsl-search-project-autocomplete>
</ng-container>
</ng-container>
}
}
<cnsl-search-user-autocomplete [users]="preselectedUsers" (selectionChanged)="users = $any($event)">
</cnsl-search-user-autocomplete>
<div class="roles-selection">
<mat-checkbox
class="role-cb"
*ngFor="let role of memberRoleOptions"
color="primary"
(change)="toggleRole(role)"
[checked]="roles.includes(role)"
>
<div class="role-cb-content">
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
<span data-e2e="role-checkbox">{{ role | roletransform }}</span>
<i class="info-hover las la-question-circle" matTooltip="{{ 'MEMBERROLES.' + role | translate }}"></i>
</div>
</mat-checkbox>
@for (role of memberRoleOptions; track role) {
<mat-checkbox class="role-cb" color="primary" (change)="toggleRole(role)" [checked]="roles.includes(role)">
<div class="role-cb-content">
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
<span data-e2e="role-checkbox">{{ role | roletransform }}</span>
<i class="info-hover las la-question-circle" matTooltip="{{ 'MEMBERROLES.' + role | translate }}"></i>
</div>
</mat-checkbox>
}
</div>
</div>
<div mat-dialog-actions class="action">
@@ -2,19 +2,15 @@
<div mat-dialog-content>
<p class="desc">{{ 'MEMBER.EDITFOR' | translate: { value: data.user } }}</p>
<div class="roles-selection">
<mat-checkbox
class="role-cb"
*ngFor="let role of allRoles"
color="primary"
(change)="toggleRole(role)"
[checked]="selectedRoles.includes(role)"
>
<div class="role-cb-content">
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
<span>{{ role | roletransform }}</span>
<i class="info-hover las la-question-circle" matTooltip="{{ 'MEMBERROLES.' + role | translate }}"></i>
</div>
</mat-checkbox>
@for (role of allRoles; track role) {
<mat-checkbox class="role-cb" color="primary" (change)="toggleRole(role)" [checked]="selectedRoles.includes(role)">
<div class="role-cb-content">
<div class="cnsl-chip-dot" [style.background]="getColor(role)"></div>
<span>{{ role | roletransform }}</span>
<i class="info-hover las la-question-circle" matTooltip="{{ 'MEMBERROLES.' + role | translate }}"></i>
</div>
</mat-checkbox>
}
</div>
</div>
<div mat-dialog-actions class="action">
@@ -7,10 +7,12 @@
<input cnslInput [matDatepicker]="picker" [min]="startDate" [formControl]="dateControl" />
<mat-datepicker-toggle style="top: 0" cnslSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker startView="year" [startAt]="startDate"></mat-datepicker>
<span cnslError *ngIf="dateControl && dateControl.errors && dateControl.errors['matDatepickerMin']?.min">
{{ 'USER.PERSONALACCESSTOKEN.ADD.CHOOSEDATEAFTER' | translate }}:
{{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate: 'EEE dd. MMM' }}
</span>
@if (dateControl && dateControl.errors && dateControl.errors['matDatepickerMin']?.min) {
<span cnslError>
{{ 'USER.PERSONALACCESSTOKEN.ADD.CHOOSEDATEAFTER' | translate }}:
{{ dateControl.errors['matDatepickerMin'].min.toDate() | localizedDate: 'EEE dd. MMM' }}
</span>
}
</cnsl-form-field>
</div>
<div mat-dialog-actions class="action">
@@ -1,5 +1,5 @@
<div class="auth-method-radio-button-wrapper" [ngClass]="{ compact: compact }">
<ng-container *ngFor="let method of authMethods; index as i">
@for (method of authMethods; track method; let i = $index) {
<input
type="radio"
[disabled]="method.disabled"
@@ -13,43 +13,50 @@
[ngClass]="{ compact: compact, first: i === 0, last: i === authMethods.length - 1 }"
[for]="method.key"
>
<div
class="recommended"
[ngClass]="{ not: method.notRecommended }"
*ngIf="method.recommended || method.notRecommended"
>
{{ (method.recommended ? 'APP.OIDC.RECOMMENDED' : 'APP.OIDC.NOTRECOMMENDED') | translate }}
</div>
@if (method.recommended || method.notRecommended) {
<div class="recommended" [ngClass]="{ not: method.notRecommended }">
{{ (method.recommended ? 'APP.OIDC.RECOMMENDED' : 'APP.OIDC.NOTRECOMMENDED') | translate }}
</div>
}
<div class="cnsl-radio-header" [ngStyle]="{ background: method.background }">
<span>{{ method.prefix }}</span>
<div class="current" *ngIf="current === method.key">{{ 'APP.OIDC.CURRENT' | translate }}</div>
@if (current === method.key) {
<div class="current">{{ 'APP.OIDC.CURRENT' | translate }}</div>
}
</div>
<p>{{ method.titleI18nKey | translate }}</p>
<p class="type-desc cnsl-secondary-text">{{ method.descI18nKey | translate }}</p>
<span class="fill-space"></span>
<div class="app-specs cnsl-secondary-text">
<div class="row" *ngIf="isOIDC && method && method.responseType !== undefined">
<span class="row-entry">{{ 'APP.OIDC.RESPONSETYPE' | translate }}</span>
<span>{{ 'APP.OIDC.RESPONSE.' + method.responseType.toString() | translate }}</span>
</div>
<div class="row" *ngIf="isOIDC && method.grantType !== undefined">
<span class="row-entry">{{ 'APP.GRANT' | translate }}</span>
<span
><span class="space" *ngFor="let grant of method.grantType">{{
'APP.OIDC.GRANT.' + grant.toString() | translate
}}</span></span
>
</div>
<div class="row" *ngIf="isOIDC && method.authMethod !== undefined">
<span class="row-entry">{{ 'APP.AUTHMETHOD' | translate }}</span>
<span>{{ 'APP.OIDC.AUTHMETHOD.' + method.authMethod.toString() | translate }}</span>
</div>
<div class="row" *ngIf="!isOIDC && method.apiAuthMethod !== undefined">
<span class="row-entry">{{ 'APP.AUTHMETHOD' | translate }}</span>
<span>{{ 'APP.API.AUTHMETHOD.' + method.apiAuthMethod.toString() | translate }}</span>
</div>
@if (isOIDC && method && method.responseType !== undefined) {
<div class="row">
<span class="row-entry">{{ 'APP.OIDC.RESPONSETYPE' | translate }}</span>
<span>{{ 'APP.OIDC.RESPONSE.' + method.responseType.toString() | translate }}</span>
</div>
}
@if (isOIDC && method.grantType !== undefined) {
<div class="row">
<span class="row-entry">{{ 'APP.GRANT' | translate }}</span>
<span>
@for (grant of method.grantType; track grant) {
<span class="space">{{ 'APP.OIDC.GRANT.' + grant.toString() | translate }}</span>
}
</span>
</div>
}
@if (isOIDC && method.authMethod !== undefined) {
<div class="row">
<span class="row-entry">{{ 'APP.AUTHMETHOD' | translate }}</span>
<span>{{ 'APP.OIDC.AUTHMETHOD.' + method.authMethod.toString() | translate }}</span>
</div>
}
@if (!isOIDC && method.apiAuthMethod !== undefined) {
<div class="row">
<span class="row-entry">{{ 'APP.AUTHMETHOD' | translate }}</span>
<span>{{ 'APP.API.AUTHMETHOD.' + method.apiAuthMethod.toString() | translate }}</span>
</div>
}
</div>
</label>
</ng-container>
}
</div>
@@ -1,5 +1,5 @@
<div class="app-type-radio-button-wrapper">
<ng-container *ngFor="let type of types">
@for (type of types; track type) {
<input class="app" type="radio" (change)="emitChange()" [value]="type" [(ngModel)]="selected" [id]="type.prefix" />
<label class="cnsl-type-radio-button" [for]="type.prefix">
<div class="cnsl-type-radio-header" [ngStyle]="{ background: type.background }">
@@ -8,7 +8,9 @@
<p>{{ type.titleI18nKey | translate }}</p>
<p class="type-desc cnsl-secondary-text">{{ type.descI18nKey | translate }}</p>
<span class="fill-space"></span>
<span class="cnsl-type-protocol state" *ngIf="type.protocol">{{ type.protocol }}</span>
@if (type.protocol) {
<span class="cnsl-type-protocol state">{{ type.protocol }}</span>
}
</label>
</ng-container>
}
</div>
@@ -13,11 +13,12 @@
}"
[ngClass]="{ active: active }"
>
<ng-container *ngIf="isMachine; else human">
@if (isMachine) {
<i class="machine-icon las la-robot"></i>
</ng-container>
<ng-template #human>
} @else {
<span class="dontcloseonclick">{{ credentials }}</span>
<img class="dontcloseonclick" *ngIf="avatarUrl" [src]="avatarUrl" (error)="errorHandler($event)" />
</ng-template>
@if (avatarUrl) {
<img class="dontcloseonclick" [src]="avatarUrl" (error)="errorHandler($event)" />
}
}
</div>
@@ -1,19 +1,37 @@
<div class="card" [ngClass]="{ nomargin: nomargin, warn: warn }" data-e2e="app-card">
<div *ngIf="title || description" class="header" [ngClass]="{ 'bottom-margin': expanded }">
<div *ngIf="title" class="row">
<h2 class="title" data-e2e="app-card-title">{{ title }}</h2>
<span class="fill-space"></span>
<ng-content select="[card-actions]"></ng-content>
<button class="button" type="button" matTooltip="Expand or collapse" mat-icon-button (click)="expanded = !expanded">
<mat-icon *ngIf="!expanded">keyboard_arrow_down</mat-icon>
<mat-icon *ngIf="expanded">keyboard_arrow_up</mat-icon>
</button>
@if (title || description) {
<div class="header" [ngClass]="{ 'bottom-margin': expanded }">
@if (title) {
<div class="row">
<h2 class="title" data-e2e="app-card-title">{{ title }}</h2>
<span class="fill-space"></span>
<ng-content select="[card-actions]"></ng-content>
<button
class="button"
type="button"
matTooltip="Expand or collapse"
mat-icon-button
(click)="expanded = !expanded"
>
@if (!expanded) {
<mat-icon>keyboard_arrow_down</mat-icon>
}
@if (expanded) {
<mat-icon>keyboard_arrow_up</mat-icon>
}
</button>
</div>
}
@if (description) {
<p class="desc cnsl-secondary-text">
{{ description }}
</p>
}
</div>
<p *ngIf="description" class="desc cnsl-secondary-text">
{{ description }}
</p>
</div>
<div class="card-content" *ngIf="expanded">
<ng-content></ng-content>
</div>
}
@if (expanded) {
<div class="card-content">
<ng-content></ng-content>
</div>
}
</div>
@@ -3,49 +3,63 @@
</div>
<div class="scroll-container">
<li class="item" *ngFor="let hist of data | async; index as histindex">
<span *ngIf="hist.values[0].dates[0]" class="date">
{{ hist.values[0].dates[0] | timestampToDate | localizedDate: 'dd. MMM yyyy' }}
</span>
<div class="item" *ngFor="let dayelement of hist.values; index as i">
<div class="row">
<cnsl-avatar
matTooltip="{{ dayelement.editorDisplayName }}"
*ngIf="dayelement.editorDisplayName; else spacer"
class="avatar"
[name]="dayelement.editorDisplayName"
[size]="32"
[forColor]="dayelement?.editorPreferredLoginName ?? 'A'"
[avatarUrl]="dayelement.editorAvatarUrl || ''"
>
</cnsl-avatar>
<ng-template #spacer>
<div class="spacer"></div>
</ng-template>
<div class="change-actions">
<div class="change-action" *ngFor="let action of dayelement.eventTypes; index as j">
<div>
<span class="msg">{{ action.localizedMessage }}</span>
<span
class="block"
matTooltip="{{ dayelement.dates[j] | timestampToDate | localizedDate: 'dd. MM yyyy, HH:mm:ss.SSS' }}"
>{{ dayelement.dates[j] | timestampToDate | localizedDate: 'HH:mm' }}</span
@for (hist of data | async; track hist; let histindex = $index) {
<li class="item">
@if (hist.values[0].dates[0]) {
<span class="date">
{{ hist.values[0].dates[0] | timestampToDate | localizedDate: 'dd. MMM yyyy' }}
</span>
}
@for (dayelement of hist.values; track dayelement; let i = $index) {
<div class="item">
<div class="row">
@if (dayelement.editorDisplayName) {
<cnsl-avatar
matTooltip="{{ dayelement.editorDisplayName }}"
class="avatar"
[name]="dayelement.editorDisplayName"
[size]="32"
[forColor]="dayelement?.editorPreferredLoginName ?? 'A'"
[avatarUrl]="dayelement.editorAvatarUrl || ''"
>
</cnsl-avatar>
} @else {
<div class="spacer"></div>
}
<div class="change-actions">
@for (action of dayelement.eventTypes; track action; let j = $index) {
<div class="change-action">
<div>
<span class="msg">{{ action.localizedMessage }}</span>
<span
class="block"
matTooltip="{{ dayelement.dates[j] | timestampToDate | localizedDate: 'dd. MM yyyy, HH:mm:ss.SSS' }}"
>{{ dayelement.dates[j] | timestampToDate | localizedDate: 'HH:mm' }}</span
>
</div>
</div>
}
</div>
</div>
</div>
</div>
</div>
</li>
}
</li>
}
<div class="ch-sp-wrapper">
<mat-spinner *ngIf="loading | async" diameter="25"></mat-spinner>
@if (loading | async) {
<mat-spinner diameter="25"></mat-spinner>
}
</div>
<button (click)="more()" class="load-more-button" *ngIf="!bottom" mat-stroked-button>
{{ 'CHANGES.LOADMORE' | translate }}
</button>
@if (!bottom) {
<button (click)="more()" class="load-more-button" mat-stroked-button>
{{ 'CHANGES.LOADMORE' | translate }}
</button>
}
<div *ngIf="bottom" class="end-container">
<span class="cnsl-secondary-text">{{ 'CHANGES.BOTTOM' | translate }}</span>
</div>
@if (bottom) {
<div class="end-container">
<span class="cnsl-secondary-text">{{ 'CHANGES.BOTTOM' | translate }}</span>
</div>
}
</div>
@@ -1,9 +1,11 @@
<div class="contributor-groups">
<div class="contributor-people">
<div class="contributor-img-list" [ngClass]="{ 'padd-left': totalResult > 0 }" [@cardAnimation]="totalResult">
<mat-spinner class="spinner" diameter="20" *ngIf="loading"></mat-spinner>
<ng-container *ngIf="totalResult < 10; else compact">
<ng-container *ngFor="let member of membersSubject | async; index as i">
@if (loading) {
<mat-spinner class="spinner" diameter="20"></mat-spinner>
}
@if (totalResult < 10) {
@for (member of membersSubject | async; track member; let i = $index) {
<div
@animate
(click)="emitShowDetail()"
@@ -12,24 +14,23 @@
[ngStyle]="{ 'z-index': 20 - i }"
data-e2e="member-avatar"
>
<cnsl-avatar
*ngIf="member && member.userType === UserType.TYPE_HUMAN; else cog"
class="contributor-avatar dontcloseonclick"
[avatarUrl]="member.avatarUrl || ''"
[name]="member.displayName"
[forColor]="member.preferredLoginName"
[size]="32"
>
</cnsl-avatar>
<ng-template #cog>
@if (member && member.userType === UserType.TYPE_HUMAN) {
<cnsl-avatar
class="contributor-avatar dontcloseonclick"
[avatarUrl]="member.avatarUrl || ''"
[name]="member.displayName"
[forColor]="member.preferredLoginName"
[size]="32"
>
</cnsl-avatar>
} @else {
<cnsl-avatar [forColor]="member.preferredLoginName" [isMachine]="true">
<i class="las la-robot"></i>
</cnsl-avatar>
</ng-template>
}
</div>
</ng-container>
</ng-container>
<ng-template #compact>
}
} @else {
<div
(click)="emitShowDetail()"
class="contributor-avatar-circle"
@@ -37,7 +38,7 @@
>
<span>{{ totalResult }}</span>
</div>
</ng-template>
}
<button
class="add-img"
[ngClass]="{ 'no-margin': totalResult === 0 }"
@@ -4,10 +4,12 @@
<button (click)="close()" mat-icon-button matTooltip="{{ 'ACTIONS.CLOSE' | translate }}">
<mat-icon>close</mat-icon>
</button>
<span class="abort">{{ title }}</span
><span class="abort-2" *ngIf="createSteps > 1"
>{{ 'ACTIONS.STEP' | translate }} {{ currentCreateStep }} {{ 'ACTIONS.OF' | translate }} {{ createSteps }}</span
>
<span class="abort">{{ title }}</span>
@if (createSteps > 1) {
<span class="abort-2"
>{{ 'ACTIONS.STEP' | translate }} {{ currentCreateStep }} {{ 'ACTIONS.OF' | translate }} {{ createSteps }}</span
>
}
</div>
<div class="create-layout-content">
@@ -4,9 +4,11 @@
<div class="detail-layout-top-view">
<div>
<div class="back-row">
<a *ngIf="hasBackButton" cnslBack mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
@if (hasBackButton) {
<a cnslBack mat-icon-button>
<mat-icon class="icon">arrow_back</mat-icon>
</a>
}
<div>
<h1>{{ title }}</h1>
<ng-content select="[sub]"></ng-content>
@@ -1,76 +1,87 @@
<h2 class="title" mat-dialog-title>{{ 'IAM.EVENTS.DIALOG.TITLE' | translate }}</h2>
<div mat-dialog-content>
<div class="event-data-column" *ngIf="event && (event | toobject) as event">
<div class="data-row" *ngIf="event && event.type && event.type.localized && event.type.localized.localizedMessage">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.TYPE' | translate }}</span>
<span>{{ event.type.localized.localizedMessage }}</span>
</div>
<div class="data-row" *ngIf="event && event.creationDate">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.CREATIONDATE' | translate }}</span>
<span>{{ event.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</div>
<div class="data-row" *ngIf="event && event.aggregate && event.aggregate.id">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.AGGREGATEID' | translate }}</span>
<span>{{ event.aggregate.id }}</span>
</div>
<div class="data-row" *ngIf="event && event.aggregate && event.aggregate.resourceOwner">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.RESOURCEOWNER' | translate }}</span>
<span>{{ event.aggregate.resourceOwner }}</span>
</div>
<div
class="data-row"
*ngIf="
@if (event && (event | toobject); as event) {
<div class="event-data-column">
@if (event && event.type && event.type.localized && event.type.localized.localizedMessage) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.TYPE' | translate }}</span>
<span>{{ event.type.localized.localizedMessage }}</span>
</div>
}
@if (event && event.creationDate) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.CREATIONDATE' | translate }}</span>
<span>{{ event.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm' }}</span>
</div>
}
@if (event && event.aggregate && event.aggregate.id) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.AGGREGATEID' | translate }}</span>
<span>{{ event.aggregate.id }}</span>
</div>
}
@if (event && event.aggregate && event.aggregate.resourceOwner) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.RESOURCEOWNER' | translate }}</span>
<span>{{ event.aggregate.resourceOwner }}</span>
</div>
}
@if (
event &&
event.aggregate &&
event.aggregate.type &&
event.aggregate.type.localized &&
event.aggregate.type.localized.localizedMessage
"
>
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.AGGREGATETYPE' | translate }}</span>
<span class="state aggregate-type">{{ event.aggregate.type.localized.localizedMessage }}</span>
) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.AGGREGATETYPE' | translate }}</span>
<span class="state aggregate-type">{{ event.aggregate.type.localized.localizedMessage }}</span>
</div>
}
@if (event && event.editor?.userId) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.EDITORID' | translate }}</span>
<span>{{ event.editor?.userId }}</span>
</div>
}
@if (event && event.editor?.displayName) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.EDITOR' | translate }}</span>
<span>{{ event.editor?.displayName }}</span>
</div>
}
@if (event && event.sequence) {
<div class="data-row">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.SEQUENCE' | translate }}</span>
<span>{{ event.sequence }}</span>
</div>
}
</div>
}
<div class="data-row" *ngIf="event && event.editor?.userId">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.EDITORID' | translate }}</span>
<span>{{ event.editor?.userId }}</span>
@if (opened$ | async) {
<div class="code">
@if (event) {
<ngx-codemirror
[ngModel]="event | topayload | json"
[options]="{
height: 'auto',
readOnly: true,
lineNumbers: true,
lineWrapping: true,
indentWithTabs: false,
tabSize: 2,
theme: 'material',
mode: {
name: 'javascript',
json: true,
statementIndent: 2,
},
}"
></ngx-codemirror>
}
</div>
<div class="data-row" *ngIf="event && event.editor?.displayName">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.EDITOR' | translate }}</span>
<span>{{ event.editor?.displayName }}</span>
</div>
<div class="data-row" *ngIf="event && event.sequence">
<span class="label cnsl-secondary-text">{{ 'IAM.EVENTS.SEQUENCE' | translate }}</span>
<span>{{ event.sequence }}</span>
</div>
</div>
<div class="code" *ngIf="opened$ | async">
<ngx-codemirror
*ngIf="event"
[ngModel]="event | topayload | json"
[options]="{
height: 'auto',
readOnly: true,
lineNumbers: true,
lineWrapping: true,
indentWithTabs: false,
tabSize: 2,
theme: 'material',
mode: {
name: 'javascript',
json: true,
statementIndent: 2,
},
}"
></ngx-codemirror>
</div>
}
</div>
<div mat-dialog-actions class="action">
<button mat-stroked-button (click)="closeDialog()">
@@ -14,110 +14,139 @@
</div>
<div mat-dialog-content cdkFocusInitial autofocus>
<div *ngIf="showNew && !(dns || http)">
<p class="domain-desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION' | translate }}</p>
<p class="domain-desc">
{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTML' | translate }}
@if (showNew && !(dns || http)) {
<div>
<p class="domain-desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION' | translate }}</p>
<p class="domain-desc">
{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTML' | translate }}
</p>
<p class="domain-desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS' | translate }}</p>
<p class="domain-desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_SKIP' | translate }}</p>
</div>
}
@if (showNew && !(dns || http)) {
<cnsl-info-section [type]="InfoSectionType.WARN" class="desc">{{
'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_DESC' | translate
}}</cnsl-info-section>
}
@if (!showNew && !(dns || http)) {
<p class="desc">
{{
'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_ONGOING'
| translate: { value: domain?.validationType == 1 ? 'HTTP' : 'DNS' }
}}
</p>
<p class="domain-desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS' | translate }}</p>
<p class="domain-desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_SKIP' | translate }}</p>
</div>
}
<cnsl-info-section [type]="InfoSectionType.WARN" *ngIf="showNew && !(dns || http)" class="desc">{{
'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_DESC' | translate
}}</cnsl-info-section>
<p *ngIf="!showNew && !(dns || http)" class="desc">
{{
'ORG.PAGES.ORGDOMAIN.VERIFICATION_VALIDATION_ONGOING'
| translate: { value: domain?.validationType == 1 ? 'HTTP' : 'DNS' }
}}
</p>
<ng-container *ngIf="showNew">
<div *ngIf="http">
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTTP_DESC' | translate: { value: domain?.domainName } }}</p>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTTP_URL_LABEL' | translate }}</p>
<div class="domain-entry" *ngIf="http.url">
<p>{{ http.url }}</p>
<button
color="primary"
[disabled]="copied === data.clientSecret"
matTooltip="copy to clipboard"
cnslCopyToClipboard
[valueToCopy]="http.url"
(copiedValue)="copied = $event"
mat-icon-button
>
<i *ngIf="copied !== http.url" class="las la-clipboard"></i>
<i *ngIf="copied === http.url" class="las la-clipboard-check"></i>
</button>
@if (showNew) {
@if (http) {
<div>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTTP_DESC' | translate: { value: domain?.domainName } }}</p>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTTP_URL_LABEL' | translate }}</p>
@if (http.url) {
<div class="domain-entry">
<p>{{ http.url }}</p>
<button
color="primary"
[disabled]="copied === data.clientSecret"
matTooltip="copy to clipboard"
cnslCopyToClipboard
[valueToCopy]="http.url"
(copiedValue)="copied = $event"
mat-icon-button
>
@if (copied !== http.url) {
<i class="las la-clipboard"></i>
}
@if (copied === http.url) {
<i class="las la-clipboard-check"></i>
}
</button>
</div>
}
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTTP_FILE_LABEL' | translate }}</p>
<button mat-stroked-button (click)="saveFile()" color="primary">{{ 'ORG.PAGES.DOWNLOAD_FILE' | translate }}</button>
</div>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_HTTP_FILE_LABEL' | translate }}</p>
<button mat-stroked-button (click)="saveFile()" color="primary">{{ 'ORG.PAGES.DOWNLOAD_FILE' | translate }}</button>
</div>
<div *ngIf="dns">
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS_DESC' | translate: { value: domain?.domainName } }}</p>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS_HOST_LABEL' | translate }}</p>
<div class="domain-line">
<div class="domain-entry" *ngIf="dns.token">
<p>{{ dnsChallenge }}</p>
<button
color="primary"
[disabled]="copied === data.clientSecret"
matTooltip="copy to clipboard"
cnslCopyToClipboard
[valueToCopy]="dnsChallenge"
(copiedValue)="copied = $event"
mat-icon-button
>
<i *ngIf="copied !== dnsChallenge" class="las la-clipboard"></i>
<i *ngIf="copied === dnsChallenge" class="las la-clipboard-check"></i>
</button>
}
@if (dns) {
<div>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS_DESC' | translate: { value: domain?.domainName } }}</p>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS_HOST_LABEL' | translate }}</p>
<div class="domain-line">
@if (dns.token) {
<div class="domain-entry">
<p>{{ dnsChallenge }}</p>
<button
color="primary"
[disabled]="copied === data.clientSecret"
matTooltip="copy to clipboard"
cnslCopyToClipboard
[valueToCopy]="dnsChallenge"
(copiedValue)="copied = $event"
mat-icon-button
>
@if (copied !== dnsChallenge) {
<i class="las la-clipboard"></i>
}
@if (copied === dnsChallenge) {
<i class="las la-clipboard-check"></i>
}
</button>
</div>
}
<span>.{{ domain?.domainName }}</span>
</div>
<span>.{{ domain?.domainName }}</span>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS_CHALLENGE_LABEL' | translate }}</p>
@if (dns.token) {
<div class="domain-entry">
<p>{{ dns.token }}</p>
<button
color="primary"
[disabled]="copied === data.clientSecret"
matTooltip="copy to clipboard"
cnslCopyToClipboard
[valueToCopy]="dns.token"
(copiedValue)="copied = $event"
mat-icon-button
>
@if (copied !== dns.token) {
<i class="las la-clipboard"></i>
}
@if (copied === dns.token) {
<i class="las la-clipboard-check"></i>
}
</button>
</div>
}
</div>
<p class="desc">{{ 'ORG.PAGES.ORGDOMAIN.VERIFICATION_DNS_CHALLENGE_LABEL' | translate }}</p>
<div class="domain-entry" *ngIf="dns.token">
<p>{{ dns.token }}</p>
<button
color="primary"
[disabled]="copied === data.clientSecret"
matTooltip="copy to clipboard"
cnslCopyToClipboard
[valueToCopy]="dns.token"
(copiedValue)="copied = $event"
mat-icon-button
>
<i *ngIf="copied !== dns.token" class="las la-clipboard"></i>
<i *ngIf="copied === dns.token" class="las la-clipboard-check"></i>
</button>
</div>
</div>
</ng-container>
}
}
</div>
<div mat-dialog-actions class="action">
<button
*ngIf="!showNew || dns || http"
color="primary"
type="submit"
mat-raised-button
class="verify-button"
(click)="validate()"
>
{{ 'ACTIONS.VERIFY' | translate }}
</button>
<mat-spinner class="spinner" *ngIf="validating" diameter="20" mode="indeterminate"></mat-spinner>
@if (!showNew || dns || http) {
<button color="primary" type="submit" mat-raised-button class="verify-button" (click)="validate()">
{{ 'ACTIONS.VERIFY' | translate }}
</button>
}
@if (validating) {
<mat-spinner class="spinner" diameter="20" mode="indeterminate"></mat-spinner>
}
<button *ngIf="!showNew" mat-stroked-button color="primary" (click)="showNew = true">
{{ 'ORG.PAGES.ORGDOMAIN.RESETMETHOD' | translate }}
</button>
@if (!showNew) {
<button mat-stroked-button color="primary" (click)="showNew = true">
{{ 'ORG.PAGES.ORGDOMAIN.RESETMETHOD' | translate }}
</button>
}
<div>
<button color="primary" *ngIf="showNew && !(dns || http)" mat-raised-button (click)="loadHttpToken()">HTTP</button>
<button color="primary" *ngIf="showNew && !(dns || http)" mat-raised-button (click)="loadDnsToken()">DNS</button>
@if (showNew && !(dns || http)) {
<button color="primary" mat-raised-button (click)="loadHttpToken()">HTTP</button>
}
@if (showNew && !(dns || http)) {
<button color="primary" mat-raised-button (click)="loadDnsToken()">DNS</button>
}
</div>
<button mat-stroked-button (click)="closeDialog()">
@@ -1,4 +1,4 @@
<ng-container *ngIf="['org.write$'] | hasRole as canwrite$">
@if (['org.write$'] | hasRole; as canwrite$) {
<div class="domain-top-view">
<div>
<div class="domain-title-row">
@@ -15,7 +15,6 @@
<p class="domain-desc cnsl-secondary-text">{{ 'ORG.DOMAINS.DESCRIPTION' | translate }}</p>
</div>
<span class="fill-space"></span>
<button
[disabled]="(canwrite$ | async) === false"
matTooltip="Add domain"
@@ -31,39 +30,40 @@
</div>
</button>
</div>
<cnsl-card *ngFor="let domain of domains" class="domain-card">
<div class="domain">
<span class="title">{{ domain.domainName }}</span>
<i matTooltip="verified" *ngIf="domain.isVerified" class="verified las la-check-circle"></i>
<i matTooltip="primary" *ngIf="domain.isPrimary" class="primary las la-star"></i>
<a
*ngIf="domain.isVerified && !domain.isPrimary && (canwrite$ | async)"
class="primaryset"
(click)="setPrimary(domain)"
>{{ 'ORG.DOMAINS.SETPRIMARY' | translate }}</a
>
<span class="fill-space"></span>
<button
mat-icon-button
[disabled]="(canwrite$ | async) === false || domain.isVerified"
*ngIf="verifyOrgDomains"
(click)="verifyDomain(domain)"
>
<i class="las la-pen"></i>
</button>
<button
class="domain-rem-button"
[disabled]="(canwrite$ | async) === false || domain.isPrimary"
matTooltip="Remove domain"
color="warn"
mat-icon-button
(click)="removeDomain(domain.domainName)"
>
<i class="las la-trash"></i>
</button>
</div>
</cnsl-card>
</ng-container>
@for (domain of domains; track domain) {
<cnsl-card class="domain-card">
<div class="domain">
<span class="title">{{ domain.domainName }}</span>
@if (domain.isVerified) {
<i matTooltip="verified" class="verified las la-check-circle"></i>
}
@if (domain.isPrimary) {
<i matTooltip="primary" class="primary las la-star"></i>
}
@if (domain.isVerified && !domain.isPrimary && (canwrite$ | async)) {
<a class="primaryset" (click)="setPrimary(domain)">{{ 'ORG.DOMAINS.SETPRIMARY' | translate }}</a>
}
<span class="fill-space"></span>
@if (verifyOrgDomains) {
<button
mat-icon-button
[disabled]="(canwrite$ | async) === false || domain.isVerified"
(click)="verifyDomain(domain)"
>
<i class="las la-pen"></i>
</button>
}
<button
class="domain-rem-button"
[disabled]="(canwrite$ | async) === false || domain.isPrimary"
matTooltip="Remove domain"
color="warn"
mat-icon-button
(click)="removeDomain(domain.domainName)"
>
<i class="las la-trash"></i>
</button>
</div>
</cnsl-card>
}
}
@@ -1,66 +1,80 @@
<div *ngIf="currentMap">
<form [formGroup]="form">
<ng-container *ngFor="let key of current$ | async | keyvalue">
<div *ngIf="key.key !== 'isDefault'" class="edit-text-block">
<div class="edit-text-flex" *ngIf="default$ | async as defaultmap">
<cnsl-form-field class="edit-text-formfield">
<cnsl-label>{{ key.key }}</cnsl-label>
<textarea
class="edit-text-area"
cnslInput
[formControlName]="key.key"
[placeholder]="$any(defaultmap[key.key])"
[name]="key.key"
[ngClass]="{ defaulttext: form.get(key.key)?.value === '' }"
></textarea>
<div class="edit-text-chips" *ngIf="warnText[key.key] === undefined">
<ng-container *ngFor="let chip of chips">
<div
class="chip"
cnslCopyToClipboard
[valueToCopy]="chip.value"
(copiedValue)="copied = $event"
(click)="addChip(key.key, chip.value)"
>
<span class="key">{{ chip.key | translate }}</span>
<span class="value">{{ chip.value }}</span>
<i *ngIf="copied !== chip.value" class="las la-clipboard"></i>
<i *ngIf="copied === chip.value" class="las la-clipboard-check"></i>
@if (currentMap) {
<div>
<form [formGroup]="form">
@for (key of current$ | async | keyvalue; track key) {
@if (key.key !== 'isDefault') {
<div class="edit-text-block">
@if (default$ | async; as defaultmap) {
<div class="edit-text-flex">
<cnsl-form-field class="edit-text-formfield">
<cnsl-label>{{ key.key }}</cnsl-label>
<textarea
class="edit-text-area"
cnslInput
[formControlName]="key.key"
[placeholder]="$any(defaultmap[key.key])"
[name]="key.key"
[ngClass]="{ defaulttext: form.get(key.key)?.value === '' }"
></textarea>
@if (warnText[key.key] === undefined) {
<div class="edit-text-chips">
@for (chip of chips; track chip) {
<div
class="chip"
cnslCopyToClipboard
[valueToCopy]="chip.value"
(copiedValue)="copied = $event"
(click)="addChip(key.key, chip.value)"
>
<span class="key">{{ chip.key | translate }}</span>
<span class="value">{{ chip.value }}</span>
@if (copied !== chip.value) {
<i class="las la-clipboard"></i>
}
@if (copied === chip.value) {
<i class="las la-clipboard-check"></i>
}
</div>
}
</div>
}
</cnsl-form-field>
<div class="edit-text-actions">
<button
matTooltip="{{ 'ACTIONS.RESETDEFAULT' | translate }}"
mat-icon-button
[disabled]="form.get(key.key)?.value === defaultmap[key.key] || disabled"
(click)="form.get(key.key)?.setValue(defaultmap[key.key])"
(mouseenter)="
form.get(key.key)?.value !== defaultmap[key.key] && setWarnText(key.key, $any(defaultmap[key.key]))
"
(mouseleave)="setWarnText(key.key, undefined)"
>
<i class="las la-history"></i>
</button>
<button
matTooltip="{{ 'ACTIONS.RESETCURRENT' | translate }}"
mat-icon-button
[disabled]="form.get(key.key)?.value === currentMap[key.key] || disabled"
(click)="form.get(key.key)?.setValue(currentMap[key.key])"
(mouseenter)="
form.get(key.key)?.value !== currentMap[key.key] && setWarnText(key.key, $any(currentMap[key.key]))
"
(mouseleave)="setWarnText(key.key, undefined)"
>
<i class="las la-undo"></i>
</button>
</div>
</ng-container>
</div>
</cnsl-form-field>
<div class="edit-text-actions">
<button
matTooltip="{{ 'ACTIONS.RESETDEFAULT' | translate }}"
mat-icon-button
[disabled]="form.get(key.key)?.value === defaultmap[key.key] || disabled"
(click)="form.get(key.key)?.setValue(defaultmap[key.key])"
(mouseenter)="
form.get(key.key)?.value !== defaultmap[key.key] && setWarnText(key.key, $any(defaultmap[key.key]))
"
(mouseleave)="setWarnText(key.key, undefined)"
>
<i class="las la-history"></i>
</button>
<button
matTooltip="{{ 'ACTIONS.RESETCURRENT' | translate }}"
mat-icon-button
[disabled]="form.get(key.key)?.value === currentMap[key.key] || disabled"
(click)="form.get(key.key)?.setValue(currentMap[key.key])"
(mouseenter)="
form.get(key.key)?.value !== currentMap[key.key] && setWarnText(key.key, $any(currentMap[key.key]))
"
(mouseleave)="setWarnText(key.key, undefined)"
>
<i class="las la-undo"></i>
</button>
</div>
}
</div>
</div>
</div>
<cnsl-info-section *ngIf="warnText[key.key] !== undefined" class="edit-text-info" [type]="InfoSectionType.WARN">
{{ 'ACTIONS.RESETTO' | translate }} <cite>'{{ warnText[key.key] }}'</cite></cnsl-info-section
>
</ng-container>
</form>
</div>
}
@if (warnText[key.key] !== undefined) {
<cnsl-info-section class="edit-text-info" [type]="InfoSectionType.WARN">
{{ 'ACTIONS.RESETTO' | translate }} <cite>'{{ warnText[key.key] }}'</cite></cnsl-info-section
>
}
}
</form>
</div>
}
@@ -22,49 +22,57 @@
<ng-container matColumnDef="editor">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.EDITOR' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<div class="editor-row" *ngIf="event.editor as editor">
<!-- <cnsl-avatar
@if (event | toobject; as event) {
@if (event.editor; as editor) {
<div class="editor-row">
<!-- <cnsl-avatar
*ngIf="editor && editor.displayName; else cog"
class="avatar"
[name]="editor.displayName"
[avatarUrl]="editor.avatarUrl || ''"
[forColor]="editor.preferredLoginName ?? editor.displayName"
[size]="32"
>
>
</cnsl-avatar>
<ng-template #cog>
<cnsl-avatar [forColor]="editor?.preferredLoginName ?? 'franz'" [isMachine]="true">
<i class="las la-robot"></i>
</cnsl-avatar> </ng-template
> -->
<span class="name" *ngIf="editor.displayName">{{ editor.displayName }}</span>
<span class="state" *ngIf="editor.service">{{ editor.service }}</span>
</div>
</ng-container>
> -->
@if (editor.displayName) {
<span class="name">{{ editor.displayName }}</span>
}
@if (editor.service) {
<span class="state">{{ editor.service }}</span>
}
</div>
}
}
</td>
</ng-container>
<ng-container matColumnDef="aggregate">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.AGGREGATE' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
@if (event | toobject; as event) {
<div class="aggregate-row">
<span class="id">{{ event.aggregate.id }}</span
><span class="state" *ngIf="event.aggregate?.type?.localized?.localizedMessage">{{
event.aggregate.type.localized.localizedMessage
}}</span>
<span class="id">{{ event.aggregate.id }}</span>
@if (event.aggregate?.type?.localized?.localizedMessage) {
<span class="state">{{ event.aggregate.type.localized.localizedMessage }}</span>
}
</div>
</ng-container>
}
</td>
</ng-container>
<ng-container matColumnDef="resourceOwner">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.RESOURCEOWNER' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
<span *ngIf="event.aggregate.resourceOwner">{{ event.aggregate.resourceOwner }}</span>
</ng-container>
@if (event | toobject; as event) {
@if (event.aggregate.resourceOwner) {
<span>{{ event.aggregate.resourceOwner }}</span>
}
}
</td>
</ng-container>
@@ -73,9 +81,9 @@
{{ 'IAM.EVENTS.SEQUENCE' | translate }}
</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
@if (event | toobject; as event) {
{{ event.sequence }}
</ng-container>
}
</td>
</ng-container>
@@ -84,32 +92,34 @@
{{ 'IAM.EVENTS.CREATIONDATE' | translate }}
</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | toobject as event">
@if (event | toobject; as event) {
<span>{{ event?.creationDate | timestampToDate | localizedDate: 'EEE dd. MMM, HH:mm:ss' }}</span>
</ng-container>
}
</td>
</ng-container>
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.TYPE' | translate }}</th>
<td mat-cell *matCellDef="let event" data-e2e="event-type-cell">
<ng-container *ngIf="event | toobject as event">
<span *ngIf="event.type?.localized?.localizedMessage">{{ event.type.localized.localizedMessage }}</span>
</ng-container>
@if (event | toobject; as event) {
@if (event.type?.localized?.localizedMessage) {
<span>{{ event.type.localized.localizedMessage }}</span>
}
}
</td>
</ng-container>
<ng-container matColumnDef="payload">
<th mat-header-cell *matHeaderCellDef>{{ 'IAM.EVENTS.PAYLOAD' | translate }}</th>
<td mat-cell *matCellDef="let event">
<ng-container *ngIf="event | topayload as payload">
@if (event | topayload; as payload) {
<span>{{ payload | json }}</span>
<div class="btn-wrapper">
<button class="open-in-dialog-btn" mat-icon-button (click)="openDialog(event)">
<mat-icon svgIcon="mdi_arrow_expand"></mat-icon>
</button>
</div>
</ng-container>
}
</td>
</ng-container>
@@ -9,7 +9,9 @@
<div class="cnsl-action-button">
<i class="las la-filter no-margin"></i>
<span>{{ 'ACTIONS.FILTER' | translate }}</span>
<span *ngIf="queryCount" class="filter-count">{{ queryCount }}</span>
@if (queryCount) {
<span class="filter-count">{{ queryCount }}</span>
}
<cnsl-action-keys
[doNotUseContrast]="true"
[type]="ActionKeysType.FILTER"
@@ -37,190 +39,211 @@
{{ 'ACTIONS.APPLY' | translate }}
</button>
</div>
<form *ngIf="form" [formGroup]="form" (ngSubmit)="emitChange()">
<div *ngIf="isLoading" class="sp_wrapper"><mat-spinner diameter="20"></mat-spinner></div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="userFilterSet" name="userFilterSet" class="cb" formControlName="userFilterSet"
>{{ 'IAM.EVENTS.FILTERS.USER.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="userFilterSet?.value">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.USER.IDLABEL' | translate }}</cnsl-label>
<input cnslInput id="editorUserId" name="editorUserId" formControlName="editorUserId" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="aggregateFilterSet" name="aggregateFilterSet" class="cb" formControlName="aggregateFilterSet"
>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="aggregateFilterSet?.value">
<cnsl-form-field class="aggregate-type-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.TYPELABEL' | translate }}</cnsl-label>
<mat-select
id="aggregateTypesList"
name="aggregateTypesList"
formControlName="aggregateTypesList"
[multiple]="true"
>
<mat-select-trigger>
<span *ngIf="aggregateTypesList?.value && aggregateTypesList?.value.length">{{
aggregateTypesList?.value[0]?.localized?.localizedMessage || ''
}}</span>
<small *ngIf="(aggregateTypesList?.value?.length || 0) > 1" class="cnsl-secondary-text">
(+{{ (aggregateTypesList?.value?.length || 0) - 1 }}
{{ aggregateTypesList?.value?.length === 2 ? 'other' : 'others' }})
</small>
</mat-select-trigger>
<mat-option *ngFor="let aggregate of aggregateTypes" [value]="aggregate">
<span *ngIf="aggregate.localized && aggregate.localized.localizedMessage">
{{ aggregate.localized.localizedMessage }}
</span>
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.IDLABEL' | translate }}</cnsl-label>
<input id="aggregateId" formControlName="aggregateId" cnslInput name="aggregateId" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox
id="eventTypesFilterSet"
name="eventTypesFilterSet"
class="cb"
formControlName="eventTypesFilterSet"
data-e2e="event-type-filter-checkbox"
>{{ 'IAM.EVENTS.FILTERS.TYPE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="eventTypesFilterSet?.value">
<cnsl-form-field class="event-types-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.TYPE.TYPELABEL' | translate }}</cnsl-label>
<mat-select id="eventTypesList" formControlName="eventTypesList" [multiple]="true">
<mat-select-trigger>
<span *ngIf="eventTypesList?.value && eventTypesList?.value.length">{{
eventTypesList?.value[0]?.localized?.localizedMessage || ''
}}</span>
<small *ngIf="(eventTypesList?.value?.length || 0) > 1" class="cnsl-secondary-text">
(+{{ (eventTypesList?.value?.length || 0) - 1 }}
{{ eventTypesList?.value?.length === 2 ? 'other' : 'others' }})
</small>
</mat-select-trigger>
<mat-option *ngFor="let eventType of eventTypes" [value]="eventType">
<ng-container *ngIf="eventType.localized && eventType.localized.localizedMessage">
{{ eventType.localized.localizedMessage }}
</ng-container>
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox
id="resourceOwnerFilterSet"
name="resourceOwnerFilterSet"
class="cb"
formControlName="resourceOwnerFilterSet"
>{{ 'IAM.EVENTS.FILTERS.RESOURCEOWNER.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="resourceOwnerFilterSet?.value">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.RESOURCEOWNER.LABEL' | translate }}</cnsl-label>
<input cnslInput id="resourceOwner" name="resourceOwner" formControlName="resourceOwner" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="sequenceFilterSet" name="sequenceFilterSet" class="cb" formControlName="sequenceFilterSet"
>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
<div class="filter-events-sub" *ngIf="sequenceFilterSet?.value">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.LABEL' | translate }}</cnsl-label>
<input cnslInput id="sequence" name="sequence" formControlName="sequence" />
</cnsl-form-field>
</div>
</div>
<div class="filter-events-section">
<mat-radio-group aria-label="Select an option" class="filter-events-sub" formControlName="creationDateFilterType">
<mat-radio-button [value]="CreationDateFilterType.FROM">
{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_FROM' | translate }}
</mat-radio-button>
<mat-radio-button [value]="CreationDateFilterType.RANGE">
{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_RANGE' | translate }}
</mat-radio-button>
</mat-radio-group>
<div class="filter-events-sub">
<ng-container *ngIf="creationDateFilterType!.value === CreationDateFilterType.FROM">
<mat-form-field class="datetime-input">
<input
class="datetime"
matInput
type="datetime-local"
name="creationDateFrom"
[value]="creationDateFrom!.value | date: 'yyyy-MM-dd HH:mm:ss'"
(change)="creationDateFrom = $event.target"
/>
</mat-form-field>
</ng-container>
<ng-container *ngIf="creationDateFilterType!.value === CreationDateFilterType.RANGE">
<div class="datetime-range">
<mat-form-field class="datetime-input">
<cnsl-label
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_SINCE' | translate }}
<input
matInput
type="datetime-local"
name="creationDateSince"
[value]="creationDateSince!.value | date: 'yyyy-MM-dd HH:mm:ss'"
(change)="creationDateSince = $event.target"
/>
</cnsl-label>
</mat-form-field>
<mat-form-field class="datetime-input">
<cnsl-label
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_UNTIL' | translate }}
<input
matInput
type="datetime-local"
name="creationDateUntil"
[value]="creationDateUntil!.value | date: 'yyyy-MM-dd HH:mm:ss'"
(change)="creationDateUntil = $event.target"
/>
</cnsl-label>
</mat-form-field>
@if (form) {
<form [formGroup]="form" (ngSubmit)="emitChange()">
@if (isLoading) {
<div class="sp_wrapper"><mat-spinner diameter="20"></mat-spinner></div>
}
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="userFilterSet" name="userFilterSet" class="cb" formControlName="userFilterSet"
>{{ 'IAM.EVENTS.FILTERS.USER.CHECKBOX' | translate }}
</mat-checkbox>
</div>
@if (userFilterSet?.value) {
<div class="filter-events-sub">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.USER.IDLABEL' | translate }}</cnsl-label>
<input cnslInput id="editorUserId" name="editorUserId" formControlName="editorUserId" />
</cnsl-form-field>
</div>
</ng-container>
}
</div>
</div>
<div class="filter-events-section">
<cnsl-form-field class="aggregate-type-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SORT' | translate }}</cnsl-label>
<mat-select id="isAsc" formControlName="isAsc">
<mat-option [value]="false"> {{ 'IAM.EVENTS.FILTERS.DESC' | translate }} </mat-option>
<mat-option [value]="true">{{ 'IAM.EVENTS.FILTERS.ASC' | translate }} </mat-option>
</mat-select>
</cnsl-form-field>
</div>
</form>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="aggregateFilterSet" name="aggregateFilterSet" class="cb" formControlName="aggregateFilterSet"
>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
@if (aggregateFilterSet?.value) {
<div class="filter-events-sub">
<cnsl-form-field class="aggregate-type-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.TYPELABEL' | translate }}</cnsl-label>
<mat-select
id="aggregateTypesList"
name="aggregateTypesList"
formControlName="aggregateTypesList"
[multiple]="true"
>
<mat-select-trigger>
@if (aggregateTypesList?.value && aggregateTypesList?.value.length) {
<span>{{ aggregateTypesList?.value[0]?.localized?.localizedMessage || '' }}</span>
}
@if ((aggregateTypesList?.value?.length || 0) > 1) {
<small class="cnsl-secondary-text">
(+{{ (aggregateTypesList?.value?.length || 0) - 1 }}
{{ aggregateTypesList?.value?.length === 2 ? 'other' : 'others' }})
</small>
}
</mat-select-trigger>
@for (aggregate of aggregateTypes; track aggregate) {
<mat-option [value]="aggregate">
@if (aggregate.localized && aggregate.localized.localizedMessage) {
<span>
{{ aggregate.localized.localizedMessage }}
</span>
}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.AGGREGATE.IDLABEL' | translate }}</cnsl-label>
<input id="aggregateId" formControlName="aggregateId" cnslInput name="aggregateId" />
</cnsl-form-field>
</div>
}
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox
id="eventTypesFilterSet"
name="eventTypesFilterSet"
class="cb"
formControlName="eventTypesFilterSet"
data-e2e="event-type-filter-checkbox"
>{{ 'IAM.EVENTS.FILTERS.TYPE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
@if (eventTypesFilterSet?.value) {
<div class="filter-events-sub">
<cnsl-form-field class="event-types-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.TYPE.TYPELABEL' | translate }}</cnsl-label>
<mat-select id="eventTypesList" formControlName="eventTypesList" [multiple]="true">
<mat-select-trigger>
@if (eventTypesList?.value && eventTypesList?.value.length) {
<span>{{ eventTypesList?.value[0]?.localized?.localizedMessage || '' }}</span>
}
@if ((eventTypesList?.value?.length || 0) > 1) {
<small class="cnsl-secondary-text">
(+{{ (eventTypesList?.value?.length || 0) - 1 }}
{{ eventTypesList?.value?.length === 2 ? 'other' : 'others' }})
</small>
}
</mat-select-trigger>
@for (eventType of eventTypes; track eventType) {
<mat-option [value]="eventType">
@if (eventType.localized && eventType.localized.localizedMessage) {
{{ eventType.localized.localizedMessage }}
}
</mat-option>
}
</mat-select>
</cnsl-form-field>
</div>
}
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox
id="resourceOwnerFilterSet"
name="resourceOwnerFilterSet"
class="cb"
formControlName="resourceOwnerFilterSet"
>{{ 'IAM.EVENTS.FILTERS.RESOURCEOWNER.CHECKBOX' | translate }}
</mat-checkbox>
</div>
@if (resourceOwnerFilterSet?.value) {
<div class="filter-events-sub">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.RESOURCEOWNER.LABEL' | translate }}</cnsl-label>
<input cnslInput id="resourceOwner" name="resourceOwner" formControlName="resourceOwner" />
</cnsl-form-field>
</div>
}
</div>
<div class="filter-events-section">
<div class="checkbox-wrapper">
<mat-checkbox id="sequenceFilterSet" name="sequenceFilterSet" class="cb" formControlName="sequenceFilterSet"
>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.CHECKBOX' | translate }}
</mat-checkbox>
</div>
@if (sequenceFilterSet?.value) {
<div class="filter-events-sub">
<cnsl-form-field class="filter-input-value">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SEQUENCE.LABEL' | translate }}</cnsl-label>
<input cnslInput id="sequence" name="sequence" formControlName="sequence" />
</cnsl-form-field>
</div>
}
</div>
<div class="filter-events-section">
<mat-radio-group
aria-label="Select an option"
class="filter-events-sub"
formControlName="creationDateFilterType"
>
<mat-radio-button [value]="CreationDateFilterType.FROM">
{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_FROM' | translate }}
</mat-radio-button>
<mat-radio-button [value]="CreationDateFilterType.RANGE">
{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.RADIO_RANGE' | translate }}
</mat-radio-button>
</mat-radio-group>
<div class="filter-events-sub">
@if (creationDateFilterType!.value === CreationDateFilterType.FROM) {
<mat-form-field class="datetime-input">
<input
class="datetime"
matInput
type="datetime-local"
name="creationDateFrom"
[value]="creationDateFrom!.value | date: 'yyyy-MM-dd HH:mm:ss'"
(change)="creationDateFrom = $event.target"
/>
</mat-form-field>
}
@if (creationDateFilterType!.value === CreationDateFilterType.RANGE) {
<div class="datetime-range">
<mat-form-field class="datetime-input">
<cnsl-label
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_SINCE' | translate }}
<input
matInput
type="datetime-local"
name="creationDateSince"
[value]="creationDateSince!.value | date: 'yyyy-MM-dd HH:mm:ss'"
(change)="creationDateSince = $event.target"
/>
</cnsl-label>
</mat-form-field>
<mat-form-field class="datetime-input">
<cnsl-label
>{{ 'IAM.EVENTS.FILTERS.CREATIONDATE.LABEL_UNTIL' | translate }}
<input
matInput
type="datetime-local"
name="creationDateUntil"
[value]="creationDateUntil!.value | date: 'yyyy-MM-dd HH:mm:ss'"
(change)="creationDateUntil = $event.target"
/>
</cnsl-label>
</mat-form-field>
</div>
}
</div>
</div>
<div class="filter-events-section">
<cnsl-form-field class="aggregate-type-select">
<cnsl-label>{{ 'IAM.EVENTS.FILTERS.SORT' | translate }}</cnsl-label>
<mat-select id="isAsc" formControlName="isAsc">
<mat-option [value]="false"> {{ 'IAM.EVENTS.FILTERS.DESC' | translate }} </mat-option>
<mat-option [value]="true">{{ 'IAM.EVENTS.FILTERS.ASC' | translate }} </mat-option>
</mat-select>
</cnsl-form-field>
</div>
</form>
}
</div>
</ng-template>
</div>
@@ -8,19 +8,22 @@
(change)="changeCheckbox(SubQuery.NAME, $event)"
>{{ 'FILTER.ORGNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.NAME) as eq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="eq.getName()" (change)="setValue(SubQuery.NAME, eq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.NAME); as eq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="eq.getName()" (change)="setValue(SubQuery.NAME, eq, $event)" />
</cnsl-form-field>
</div>
}
</div>
<div class="name-query">
@@ -31,19 +34,22 @@
(change)="changeCheckbox(SubQuery.STATE, $event)"
>{{ 'FILTER.STATE' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.STATE) as sq">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-select-value">
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE, sq, $event)">
<mat-option *ngFor="let state of states" [value]="state">
{{ 'USER.STATE.' + state | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.STATE); as sq) {
<div class="subquery">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-select-value">
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE, sq, $event)">
@for (state of states; track state) {
<mat-option [value]="state">
{{ 'USER.STATE.' + state | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
</div>
}
</div>
</div>
@@ -55,33 +61,37 @@
(change)="changeCheckbox(SubQuery.DOMAIN, $event)"
>{{ 'FILTER.PRIMARYDOMAIN' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.DOMAIN) as dq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dq.getMethod()" (selectionChange)="setMethod(dq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="dq.getDomain()" (change)="setValue(SubQuery.DOMAIN, dq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.DOMAIN); as dq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dq.getMethod()" (selectionChange)="setMethod(dq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="dq.getDomain()" (change)="setValue(SubQuery.DOMAIN, dq, $event)" />
</cnsl-form-field>
</div>
}
</div>
<div class="id-query">
<mat-checkbox id="id" class="cb" [checked]="getSubFilter(SubQuery.ID)" (change)="changeCheckbox(SubQuery.ID, $event)"
>{{ 'FILTER.ORGID' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.ID) as idq">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="idq.getId()" (change)="setValue(SubQuery.ID, idq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.ID); as idq) {
<div class="subquery">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="idq.getId()" (change)="setValue(SubQuery.ID, idq, $event)" />
</cnsl-form-field>
</div>
}
</div>
</cnsl-filter>
@@ -8,19 +8,22 @@
(change)="changeCheckbox(SubQuery.NAME, $event)"
>{{ 'FILTER.PROJECTNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.NAME) as eq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="eq.getName()" (change)="setValue(SubQuery.NAME, eq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.NAME); as eq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="eq.getName()" (change)="setValue(SubQuery.NAME, eq, $event)" />
</cnsl-form-field>
</div>
}
</div>
</div>
</cnsl-filter>
@@ -8,24 +8,27 @@
(change)="changeCheckbox(SubQuery.DISPLAYNAME, $event)"
>{{ 'FILTER.DISPLAYNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.DISPLAYNAME) as dnq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dnq.getMethod()" (selectionChange)="setMethod(dnq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input
cnslInput
name="value"
[value]="dnq.getDisplayName()"
(change)="setValue(SubQuery.DISPLAYNAME, dnq, $event)"
/>
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.DISPLAYNAME); as dnq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dnq.getMethod()" (selectionChange)="setMethod(dnq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input
cnslInput
name="value"
[value]="dnq.getDisplayName()"
(change)="setValue(SubQuery.DISPLAYNAME, dnq, $event)"
/>
</cnsl-form-field>
</div>
}
</div>
<div class="usernane-query">
@@ -36,19 +39,22 @@
(change)="changeCheckbox(SubQuery.USERNAME, $event)"
>{{ 'FILTER.USERNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.USERNAME) as unq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="unq.getMethod()" (selectionChange)="setMethod(unq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="unq.getUserName()" (change)="setValue(SubQuery.USERNAME, unq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.USERNAME); as unq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="unq.getMethod()" (selectionChange)="setMethod(unq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="unq.getUserName()" (change)="setValue(SubQuery.USERNAME, unq, $event)" />
</cnsl-form-field>
</div>
}
</div>
<div class="email-query">
@@ -59,19 +65,22 @@
(change)="changeCheckbox(SubQuery.ORGNAME, $event)"
>{{ 'FILTER.ORGNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.ORGNAME) as onq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="onq.getMethod()" (selectionChange)="setMethod(onq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="onq.getOrgName()" (change)="setValue(SubQuery.ORGNAME, onq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.ORGNAME); as onq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="onq.getMethod()" (selectionChange)="setMethod(onq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="onq.getOrgName()" (change)="setValue(SubQuery.ORGNAME, onq, $event)" />
</cnsl-form-field>
</div>
}
</div>
<div class="email-query">
@@ -82,24 +91,27 @@
(change)="changeCheckbox(SubQuery.PROJECTNAME, $event)"
>{{ 'FILTER.PROJECTNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.PROJECTNAME) as pnq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="pnq.getMethod()" (selectionChange)="setMethod(pnq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input
cnslInput
name="value"
[value]="pnq.getProjectName()"
(change)="setValue(SubQuery.PROJECTNAME, pnq, $event)"
/>
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.PROJECTNAME); as pnq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="pnq.getMethod()" (selectionChange)="setMethod(pnq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input
cnslInput
name="value"
[value]="pnq.getProjectName()"
(change)="setValue(SubQuery.PROJECTNAME, pnq, $event)"
/>
</cnsl-form-field>
</div>
}
</div>
</div>
</cnsl-filter>
@@ -8,19 +8,22 @@
(change)="changeCheckbox(SubQuery.STATE, $event)"
>{{ 'FILTER.STATE' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.STATE) as sq">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-select-value">
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE, sq, $event)">
<mat-option *ngFor="let state of states" [value]="state">
{{ 'USER.STATE.' + state | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.STATE); as sq) {
<div class="subquery">
<span class="nomethod cnsl-secondary-text">
{{ 'FILTER.METHODS.1' | translate }}
</span>
<cnsl-form-field class="filter-select-value">
<mat-select [value]="sq.getState()" (selectionChange)="setValue(SubQuery.STATE, sq, $event)">
@for (state of states; track state) {
<mat-option [value]="state">
{{ 'USER.STATE.' + state | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
</div>
}
</div>
<div class="name-query">
@@ -31,24 +34,27 @@
(change)="changeCheckbox(SubQuery.DISPLAYNAME, $event)"
>{{ 'FILTER.DISPLAYNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.DISPLAYNAME) as dnq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dnq.getMethod()" (selectionChange)="setMethod(dnq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input
cnslInput
name="value"
[value]="dnq.getDisplayName()"
(change)="setValue(SubQuery.DISPLAYNAME, dnq, $event)"
/>
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.DISPLAYNAME); as dnq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="dnq.getMethod()" (selectionChange)="setMethod(dnq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input
cnslInput
name="value"
[value]="dnq.getDisplayName()"
(change)="setValue(SubQuery.DISPLAYNAME, dnq, $event)"
/>
</cnsl-form-field>
</div>
}
</div>
<div class="email-query">
@@ -59,19 +65,22 @@
(change)="changeCheckbox(SubQuery.EMAIL, $event)"
>{{ 'FILTER.EMAIL' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.EMAIL) as eq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="eq.getEmailAddress()" (change)="setValue(SubQuery.EMAIL, eq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.EMAIL); as eq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="eq.getMethod()" (selectionChange)="setMethod(eq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="eq.getEmailAddress()" (change)="setValue(SubQuery.EMAIL, eq, $event)" />
</cnsl-form-field>
</div>
}
</div>
<div class="usernane-query">
@@ -82,19 +91,22 @@
(change)="changeCheckbox(SubQuery.USERNAME, $event)"
>{{ 'FILTER.USERNAME' | translate }}
</mat-checkbox>
<div class="subquery" *ngIf="getSubFilter(SubQuery.USERNAME) as unq">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="unq.getMethod()" (selectionChange)="setMethod(unq, $event)">
<mat-option *ngFor="let method of methods" [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="unq.getUserName()" (change)="setValue(SubQuery.USERNAME, unq, $event)" />
</cnsl-form-field>
</div>
@if (getSubFilter(SubQuery.USERNAME); as unq) {
<div class="subquery">
<cnsl-form-field class="filter-select-method">
<mat-select [value]="unq.getMethod()" (selectionChange)="setMethod(unq, $event)">
@for (method of methods; track method) {
<mat-option [value]="method">
{{ 'FILTER.METHODS.' + method | translate }}
</mat-option>
}
</mat-select>
</cnsl-form-field>
<cnsl-form-field class="filter-input-value">
<input cnslInput name="value" [value]="unq.getUserName()" (change)="setValue(SubQuery.USERNAME, unq, $event)" />
</cnsl-form-field>
</div>
}
</div>
</div>
</cnsl-filter>
@@ -3,7 +3,9 @@
<div class="cnsl-action-button">
<i class="las la-filter no-margin"></i>
<span>{{ 'ACTIONS.FILTER' | translate }}</span>
<span *ngIf="queryCount" class="filter-count">{{ queryCount }}</span>
@if (queryCount) {
<span class="filter-count">{{ queryCount }}</span>
}
<cnsl-action-keys [doNotUseContrast]="true" [type]="ActionKeysType.FILTER" (actionTriggered)="toggleFilter()">
</cnsl-action-keys>
</div>
@@ -1,35 +1,43 @@
<div class="footer-wrapper">
<div class="footer-row">
<div class="footer-links" *ngIf="authService.privacypolicy$ | async as pP">
<a target="_blank" *ngIf="pP?.tosLink" rel="noreferrer" [href]="pP?.tosLink" external>
<span>{{ 'FOOTER.LINKS.TOS' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
<a target="_blank" *ngIf="pP?.privacyLink" rel="noreferrer" [href]="pP?.privacyLink" external>
<span>{{ 'FOOTER.LINKS.PP' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
</div>
@if (authService.privacypolicy$ | async; as pP) {
<div class="footer-links">
@if (pP?.tosLink) {
<a target="_blank" rel="noreferrer" [href]="pP?.tosLink" external>
<span>{{ 'FOOTER.LINKS.TOS' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
}
@if (pP?.privacyLink) {
<a target="_blank" rel="noreferrer" [href]="pP?.privacyLink" external>
<span>{{ 'FOOTER.LINKS.PP' | translate }}</span>
<i class="las la-external-link-alt"></i>
</a>
}
</div>
}
<div class="footer-socials" *ngIf="authService.labelpolicy$ | async as lP">
<ng-container *ngIf="lP?.disableWatermark === false">
<a target="_blank" rel="noreferrer" href="https://github.com/zitadel">
<i class="text-3xl lab la-github"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://twitter.com/zitadel">
<fa-icon [icon]="faXTwitter" size="xl"></fa-icon>
</a>
<a target="_blank" rel="noreferrer" href="https://www.linkedin.com/company/zitadel/">
<i class="text-3xl lab la-linkedin"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://zitadel.com/chat">
<i class="text-3xl lab la-discord"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://www.youtube.com/channel/UCUAWJUNYaRn1yIVrZxEfsuA">
<i class="text-3xl lab la-youtube"></i>
</a>
</ng-container>
</div>
@if (authService.labelpolicy$ | async; as lP) {
<div class="footer-socials">
@if (lP?.disableWatermark === false) {
<a target="_blank" rel="noreferrer" href="https://github.com/zitadel">
<i class="text-3xl lab la-github"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://twitter.com/zitadel">
<fa-icon [icon]="faXTwitter" size="xl"></fa-icon>
</a>
<a target="_blank" rel="noreferrer" href="https://www.linkedin.com/company/zitadel/">
<i class="text-3xl lab la-linkedin"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://zitadel.com/chat">
<i class="text-3xl lab la-discord"></i>
</a>
<a target="_blank" rel="noreferrer" href="https://www.youtube.com/channel/UCUAWJUNYaRn1yIVrZxEfsuA">
<i class="text-3xl lab la-youtube"></i>
</a>
}
</div>
}
<div class="theme">
<cnsl-theme-setting></cnsl-theme-setting>
@@ -1,9 +1,9 @@
<ng-template #labelTemplate>
<label class="cnsl-label-wrapper" [attr.for]="_control.id" [attr.aria-owns]="_control.id">
<ng-content select="cnsl-label"></ng-content>
<span *ngIf="_control.required && !hideRequiredMarker" aria-hidden="true" class="cnsl-form-field-required-marker"
>*</span
>
@if (_control.required && !hideRequiredMarker) {
<span aria-hidden="true" class="cnsl-form-field-required-marker">*</span>
}
</label>
</ng-template>
@@ -13,17 +13,25 @@
<ng-content></ng-content>
<ng-content select="cnslSuffix"></ng-content>
</div>
<ng-container *ngIf="help$ && help$ | async as help">
<div [ngSwitch]="help.type" class="cnsl-form-field-subscript-wrapper">
<div *ngSwitchCase="'hints'" class="cnsl-form-field-hint-wrapper" [@transitionMessages]="_subscriptAnimationState">
<ng-content select="cnsl-hint"></ng-content>
</div>
<div *ngSwitchCase="'errors'" class="cnsl-form-field-error-wrapper" [@transitionMessages]="_subscriptAnimationState">
<span cnslError *ngFor="let err of help.validationErrors">
{{ err.i18nKey | translate: err.params }}
</span>
<ng-content select="cnsl-error"></ng-content>
</div>
@if (help$ && help$ | async; as help) {
<div class="cnsl-form-field-subscript-wrapper">
@switch (help.type) {
@case ('hints') {
<div class="cnsl-form-field-hint-wrapper" [@transitionMessages]="_subscriptAnimationState">
<ng-content select="cnsl-hint"></ng-content>
</div>
}
@case ('errors') {
<div class="cnsl-form-field-error-wrapper" [@transitionMessages]="_subscriptAnimationState">
@for (err of help.validationErrors; track err) {
<span cnslError>
{{ err.i18nKey | translate: err.params }}
</span>
}
<ng-content select="cnsl-error"></ng-content>
</div>
}
}
</div>
</ng-container>
}
</div>
@@ -1,44 +1,41 @@
<mat-toolbar class="header-wrapper">
<div class="header-content">
<div *ngIf="authService.labelPolicyLoading$ | async; else logo" class="logo-placeholder">
<mat-spinner [diameter]="20"></mat-spinner>
</div>
<ng-template #logo>
<a class="title custom" [routerLink]="['/']" *ngIf="authService.labelpolicy$ | async as lP; else defaultHome">
<img
class="logo"
alt="home logo"
*ngIf="isDarkTheme; else customlighttheme"
[src]="lP.iconUrlDark ? lP.iconUrlDark : './assets/images/zitadel-logo-solo-light.svg'"
(error)="errorHandler($event, './assets/images/zitadel-logo-solo-light.svg')"
/>
<ng-template #customlighttheme>
<img
alt="home logo"
class="logo"
[src]="lP.iconUrl ? lP.iconUrl : './assets/images/zitadel-logo-solo-dark.svg'"
(error)="errorHandler($event, './assets/images/zitadel-logo-solo-dark.svg')"
/>
</ng-template>
</a>
</ng-template>
<ng-template #defaultHome>
<a class="title" [routerLink]="(authService.zitadelPermissions | async)?.length ? ['/'] : ['/users', 'me']">
<img
class="logo"
alt="zitadel logo"
*ngIf="isDarkTheme; else lighttheme"
src="./assets/images/zitadel-logo-solo-light.svg"
/>
<ng-template #lighttheme>
<img alt="zitadel logo" class="logo" src="./assets/images/zitadel-logo-solo-dark.svg" />
</ng-template>
</a>
</ng-template>
@if (authService.labelPolicyLoading$ | async) {
<div class="logo-placeholder">
<mat-spinner [diameter]="20"></mat-spinner>
</div>
} @else {
@if (authService.labelpolicy$ | async; as lP) {
<a class="title custom" [routerLink]="['/']">
@if (isDarkTheme) {
<img
class="logo"
alt="home logo"
[src]="lP.iconUrlDark ? lP.iconUrlDark : './assets/images/zitadel-logo-solo-light.svg'"
(error)="errorHandler($event, './assets/images/zitadel-logo-solo-light.svg')"
/>
} @else {
<img
alt="home logo"
class="logo"
[src]="lP.iconUrl ? lP.iconUrl : './assets/images/zitadel-logo-solo-dark.svg'"
(error)="errorHandler($event, './assets/images/zitadel-logo-solo-dark.svg')"
/>
}
</a>
} @else {
<a class="title" [routerLink]="(authService.zitadelPermissions | async)?.length ? ['/'] : ['/users', 'me']">
@if (isDarkTheme) {
<img class="logo" alt="zitadel logo" src="./assets/images/zitadel-logo-solo-light.svg" />
} @else {
<img alt="zitadel logo" class="logo" src="./assets/images/zitadel-logo-solo-dark.svg" />
}
</a>
}
}
<ng-container *ngFor="let bread of breadcrumbService.breadcrumbs$ | async as bc; index as i">
<ng-container *ngIf="bread.type === BreadcrumbType.INSTANCE">
@if (bread.type === BreadcrumbType.INSTANCE) {
<ng-template cnslHasRole [hasRole]="['iam.read']">
<svg
class="slash hide-on-small"
@@ -53,16 +50,15 @@
>
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="breadcrumb-context hide-on-small">
<a matRipple [matRippleUnbounded]="false" class="breadcrumb-link" [routerLink]="bread.routerLink">
{{ 'MENU.INSTANCE' | translate }}
</a>
</div>
</ng-template>
</ng-container>
}
<ng-container *ngIf="bread.type === BreadcrumbType.ORG">
@if (bread.type === BreadcrumbType.ORG) {
<svg
class="slash"
viewBox="0 0 24 24"
@@ -76,20 +72,13 @@
>
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="org-context">
<a
*ngIf="org() as org"
matRipple
[matRippleUnbounded]="false"
class="org-link"
id="orglink"
[routerLink]="['/org']"
>
{{ org.name ? org.name : 'NO NAME' }}</a
>
<div class="org-context-wrapper" *ngIf="org">
@if (org(); as org) {
<a matRipple [matRippleUnbounded]="false" class="org-link" id="orglink" [routerLink]="['/org']">
{{ org.name ? org.name : 'NO NAME' }}</a
>
}
<div class="org-context-wrapper">
<button
cdkOverlayOrigin
#trigger="cdkOverlayOrigin"
@@ -113,7 +102,6 @@
[type]="ActionKeysType.ORGSWITCHER"
></cnsl-action-keys>
</button>
<ng-template
cdkConnectedOverlay
[cdkConnectedOverlayOrigin]="trigger"
@@ -125,20 +113,21 @@
(backdropClick)="showOrgContext = false"
(detach)="showOrgContext = false"
>
<cnsl-org-context
class="context_card"
*ngIf="showOrgContext && org() as org"
(closedCard)="showOrgContext = false"
[org]="org"
(setOrg)="setActiveOrg($event)"
>
</cnsl-org-context>
@if (showOrgContext && org(); as org) {
<cnsl-org-context
class="context_card"
(closedCard)="showOrgContext = false"
[org]="org"
(setOrg)="setActiveOrg($event)"
>
</cnsl-org-context>
}
</ng-template>
</div>
</div>
</ng-container>
}
<ng-container *ngIf="bread.type !== BreadcrumbType.INSTANCE && bread.type !== BreadcrumbType.ORG">
@if (bread.type !== BreadcrumbType.INSTANCE && bread.type !== BreadcrumbType.ORG) {
<svg
class="slash"
viewBox="0 0 24 24"
@@ -152,7 +141,6 @@
>
<path d="M16.88 3.549L7.12 20.451"></path>
</svg>
<div class="breadcrumb-context">
<a
matRipple
@@ -161,58 +149,54 @@
[ngClass]="{ maxwidth: bc.length > 1 }"
[routerLink]="bread.routerLink"
>
<ng-container *ngIf="i !== bc.length - 1; else defLabel">
@if (i !== bc.length - 1) {
<span class="desk">{{ bread.name }}</span>
<span class="mob">...</span>
</ng-container>
<ng-template #defLabel>
} @else {
<span>{{ bread.name }}</span>
</ng-template>
}
</a>
</div>
</ng-container>
}
</ng-container>
<span class="fill-space"></span>
<ng-container *ngIf="authService.privacypolicy$ | async as pP">
<a class="custom-link" *ngIf="pP.customLink" href="{{ pP.customLink }}" mat-stroked-button target="_blank">
{{ pP.customLinkText }}
</a>
@if (authService.privacypolicy$ | async; as pP) {
@if (pP.customLink) {
<a class="custom-link" href="{{ pP.customLink }}" mat-stroked-button target="_blank">
{{ pP.customLinkText }}
</a>
}
@if (pP.docsLink) {
<a class="doc-link" href="{{ pP.docsLink }}" mat-stroked-button target="_blank">
{{ 'MENU.DOCUMENTATION' | translate }}
</a>
}
}
<a class="doc-link" *ngIf="pP.docsLink" href="{{ pP.docsLink }}" mat-stroked-button target="_blank">
{{ 'MENU.DOCUMENTATION' | translate }}
</a>
</ng-container>
@if (!isOnMe) {
<div class="system-rel">
@if (!isOnInstance && (['iam.read$', 'iam.write$'] | hasRole | async)) {
<a id="systembutton" [routerLink]="['/instance']" class="iam-settings" mat-stroked-button>
<div class="cnsl-action-button">
<span class="iam-label">{{ 'MENU.INSTANCE' | translate }}</span>
<i class="las la-cog"></i>
</div>
</a>
}
@if (isOnInstance && (['org.read'] | hasRole | async)) {
<a id="orgbutton" [routerLink]="['/org']" class="org-settings" mat-stroked-button>
<div class="cnsl-action-button">
<span class="iam-label">{{ 'MENU.ORGANIZATION' | translate }}</span>
<i class="las la-cog"></i>
</div>
</a>
}
</div>
}
<div class="system-rel" *ngIf="!isOnMe">
<a
id="systembutton"
*ngIf="!isOnInstance && (['iam.read$', 'iam.write$'] | hasRole | async)"
[routerLink]="['/instance']"
class="iam-settings"
mat-stroked-button
>
<div class="cnsl-action-button">
<span class="iam-label">{{ 'MENU.INSTANCE' | translate }}</span>
<i class="las la-cog"></i>
</div>
</a>
<a
id="orgbutton"
*ngIf="isOnInstance && (['org.read'] | hasRole | async)"
[routerLink]="['/org']"
class="org-settings"
mat-stroked-button
>
<div class="cnsl-action-button">
<span class="iam-label">{{ 'MENU.ORGANIZATION' | translate }}</span>
<i class="las la-cog"></i>
</div>
</a>
</div>
<ng-container *ngIf="user && user.id">
@if (user && user.id) {
<div class="account-card-wrapper">
<button
cdkOverlayOrigin
@@ -244,15 +228,16 @@
(backdropClick)="showAccount = false"
(detach)="showAccount = false"
>
<cnsl-accounts-card
class="a_card"
*ngIf="showAccount"
(closedCard)="showAccount = false"
[user]="user"
[iamuser]="['iam.write$'] | hasRole | async"
>
</cnsl-accounts-card>
@if (showAccount) {
<cnsl-accounts-card
class="a_card"
(closedCard)="showAccount = false"
[user]="user"
[iamuser]="['iam.write$'] | hasRole | async"
>
</cnsl-accounts-card>
}
</ng-template>
</ng-container>
}
</div>
</mat-toolbar>
@@ -13,16 +13,12 @@
<span>{{ 'IDP.AVAILABILITY' | translate }}</span>
</th>
<td class="pointer availability" mat-cell *matCellDef="let idp">
<i
matTooltip="{{ 'IDP.AVAILABLE' | translate }}"
*ngIf="isEnabled(idp) && idp.state === IDPState.IDP_STATE_ACTIVE"
class="idp-available las la-check-circle"
></i>
<i
matTooltip="{{ 'IDP.AVAILABLEBUTINACTIVE' | translate }}"
*ngIf="isEnabled(idp) && idp.state === IDPState.IDP_STATE_INACTIVE"
class="idp-not-available las la-check-circle"
></i>
@if (isEnabled(idp) && idp.state === IDPState.IDP_STATE_ACTIVE) {
<i matTooltip="{{ 'IDP.AVAILABLE' | translate }}" class="idp-available las la-check-circle"></i>
}
@if (isEnabled(idp) && idp.state === IDPState.IDP_STATE_INACTIVE) {
<i matTooltip="{{ 'IDP.AVAILABLEBUTINACTIVE' | translate }}" class="idp-not-available las la-check-circle"></i>
}
</td>
</ng-container>
@@ -36,59 +32,87 @@
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef>{{ 'IDP.TYPE' | translate }}</th>
<td class="pointer" mat-cell *matCellDef="let idp">
<div [ngSwitch]="idp.type">
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_AZURE_AD">
<img class="idp-logo" src="./assets/images/idp/ms.svg" alt="azure ad" />
Microsoft
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_GOOGLE">
<img class="idp-logo" src="./assets/images/idp/google.png" alt="google" />
Google
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_GITHUB">
<img class="idp-logo dark" src="./assets/images/idp/github-dark.svg" alt="github" />
<img class="idp-logo light" src="./assets/images/idp/github.svg" alt="github" />
GitHub
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_OIDC">
<mat-icon class="idp-icon" svgIcon="mdi_openid" alt="oidc" />
Generic OIDC
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_OAUTH">
<img class="idp-logo" src="./assets/images/idp/oauth.svg" alt="oauth" />
Generic OAuth
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_JWT">
<mat-icon class="idp-icon" svgIcon="mdi_jwt" alt="jwt" />
Generic JWT
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_GITLAB">
<img class="idp-logo" src="./assets/images/idp/gitlab.svg" alt="gitlab" />
Gitlab
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_GITLAB_SELF_HOSTED">
<img class="idp-logo" src="./assets/images/idp/gitlab.svg" alt="gitlab self hosted" />
Gitlab Self Hosted
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_GITHUB_ES">
<img class="idp-logo dark" src="./assets/images/idp/github-dark.svg" alt="github" />
<img class="idp-logo light" src="./assets/images/idp/github.svg" alt="github" />
GitHub Enterprise Server
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_LDAP">
<i class="idp-icon las la-building"></i>
Active Directory / LDAP
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_APPLE">
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
Apple
</div>
<div class="idp-table-provider-type" *ngSwitchCase="ProviderType.PROVIDER_TYPE_SAML">
<img class="idp-logo" src="./assets/images/idp/saml-icon.svg" alt="saml" />
SAML
</div>
<div class="idp-table-provider-type" *ngSwitchDefault>coming soon</div>
<div>
@switch (idp.type) {
@case (ProviderType.PROVIDER_TYPE_AZURE_AD) {
<div class="idp-table-provider-type">
<img class="idp-logo" src="./assets/images/idp/ms.svg" alt="azure ad" />
Microsoft
</div>
}
@case (ProviderType.PROVIDER_TYPE_GOOGLE) {
<div class="idp-table-provider-type">
<img class="idp-logo" src="./assets/images/idp/google.png" alt="google" />
Google
</div>
}
@case (ProviderType.PROVIDER_TYPE_GITHUB) {
<div class="idp-table-provider-type">
<img class="idp-logo dark" src="./assets/images/idp/github-dark.svg" alt="github" />
<img class="idp-logo light" src="./assets/images/idp/github.svg" alt="github" />
GitHub
</div>
}
@case (ProviderType.PROVIDER_TYPE_OIDC) {
<div class="idp-table-provider-type">
<mat-icon class="idp-icon" svgIcon="mdi_openid" alt="oidc" />
Generic OIDC
</div>
}
@case (ProviderType.PROVIDER_TYPE_OAUTH) {
<div class="idp-table-provider-type">
<img class="idp-logo" src="./assets/images/idp/oauth.svg" alt="oauth" />
Generic OAuth
</div>
}
@case (ProviderType.PROVIDER_TYPE_JWT) {
<div class="idp-table-provider-type">
<mat-icon class="idp-icon" svgIcon="mdi_jwt" alt="jwt" />
Generic JWT
</div>
}
@case (ProviderType.PROVIDER_TYPE_GITLAB) {
<div class="idp-table-provider-type">
<img class="idp-logo" src="./assets/images/idp/gitlab.svg" alt="gitlab" />
Gitlab
</div>
}
@case (ProviderType.PROVIDER_TYPE_GITLAB_SELF_HOSTED) {
<div class="idp-table-provider-type">
<img class="idp-logo" src="./assets/images/idp/gitlab.svg" alt="gitlab self hosted" />
Gitlab Self Hosted
</div>
}
@case (ProviderType.PROVIDER_TYPE_GITHUB_ES) {
<div class="idp-table-provider-type">
<img class="idp-logo dark" src="./assets/images/idp/github-dark.svg" alt="github" />
<img class="idp-logo light" src="./assets/images/idp/github.svg" alt="github" />
GitHub Enterprise Server
</div>
}
@case (ProviderType.PROVIDER_TYPE_LDAP) {
<div class="idp-table-provider-type">
<i class="idp-icon las la-building"></i>
Active Directory / LDAP
</div>
}
@case (ProviderType.PROVIDER_TYPE_APPLE) {
<div class="idp-table-provider-type">
<img class="idp-logo apple dark" src="./assets/images/idp/apple-dark.svg" alt="apple" />
<img class="idp-logo apple light" src="./assets/images/idp/apple.svg" alt="apple" />
Apple
</div>
}
@case (ProviderType.PROVIDER_TYPE_SAML) {
<div class="idp-table-provider-type">
<img class="idp-logo" src="./assets/images/idp/saml-icon.svg" alt="saml" />
SAML
</div>
}
@default {
<div class="idp-table-provider-type">coming soon</div>
}
}
</div>
</td>
</ng-container>
@@ -132,71 +156,75 @@
<th class="idp-table-actions" mat-header-cell *matHeaderCellDef></th>
<td class="idp-table-actions" mat-cell *matCellDef="let idp">
<cnsl-table-actions>
<button
actions
*ngIf="!isEnabled(idp)"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: '',
]
| hasRole
| async) === false
"
mat-icon-button
matTooltip="{{ 'IDP.SETAVAILABLE' | translate }}"
(click)="addIdp(idp); $event.stopPropagation()"
>
<i class="las la-check-circle"></i>
</button>
<button
actions
*ngIf="isEnabled(idp)"
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: '',
]
| hasRole
| async) === false
"
mat-icon-button
matTooltip="{{ 'IDP.SETUNAVAILABLE' | translate }}"
(click)="removeIdp(idp); $event.stopPropagation()"
>
<i class="las la-times-circle"></i>
</button>
<button
actions
*ngIf="
(serviceType === PolicyComponentServiceType.MGMT && idp.owner === IDPOwnerType.IDP_OWNER_TYPE_ORG) ||
(serviceType === PolicyComponentServiceType.ADMIN && idp.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM)
"
[disabled]="
(serviceType === PolicyComponentServiceType.MGMT && idp?.providerType === IDPOwnerType.IDP_OWNER_TYPE_ORG) ||
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: '',
]
| hasRole
| async) === false
"
mat-icon-button
color="warn"
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
(click)="deleteIdp(idp); $event.stopPropagation()"
>
<i class="las la-trash"></i>
</button>
@if (!isEnabled(idp)) {
<button
actions
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: '',
]
| hasRole
| async) === false
"
mat-icon-button
matTooltip="{{ 'IDP.SETAVAILABLE' | translate }}"
(click)="addIdp(idp); $event.stopPropagation()"
>
<i class="las la-check-circle"></i>
</button>
}
@if (isEnabled(idp)) {
<button
actions
[disabled]="
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: '',
]
| hasRole
| async) === false
"
mat-icon-button
matTooltip="{{ 'IDP.SETUNAVAILABLE' | translate }}"
(click)="removeIdp(idp); $event.stopPropagation()"
>
<i class="las la-times-circle"></i>
</button>
}
@if (
(serviceType === PolicyComponentServiceType.MGMT && idp.owner === IDPOwnerType.IDP_OWNER_TYPE_ORG) ||
(serviceType === PolicyComponentServiceType.ADMIN && idp.owner === IDPOwnerType.IDP_OWNER_TYPE_SYSTEM)
) {
<button
actions
[disabled]="
(serviceType === PolicyComponentServiceType.MGMT &&
idp?.providerType === IDPOwnerType.IDP_OWNER_TYPE_ORG) ||
([
serviceType === PolicyComponentServiceType.ADMIN
? 'iam.idp.write'
: serviceType === PolicyComponentServiceType.MGMT
? 'org.idp.write'
: '',
]
| hasRole
| async) === false
"
mat-icon-button
color="warn"
matTooltip="{{ 'ACTIONS.REMOVE' | translate }}"
(click)="deleteIdp(idp); $event.stopPropagation()"
>
<i class="las la-trash"></i>
</button>
}
</cnsl-table-actions>
</td>
</ng-container>
@@ -206,10 +234,12 @@
</table>
</div>
<div *ngIf="(loading$ | async) === false && !dataSource?.data?.length" class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{ 'IDP.EMPTY' | translate }}</span>
</div>
@if ((loading$ | async) === false && !dataSource?.data?.length) {
<div class="no-content-row">
<i class="las la-exclamation"></i>
<span>{{ 'IDP.EMPTY' | translate }}</span>
</div>
}
<cnsl-paginator
#paginator
@@ -1,32 +1,19 @@
<span class="title" mat-dialog-title>{{ data.titleKey | translate: data.titleParam }}</span>
<span class="title" mat-dialog-title>{{ data.titleKey | translate }}</span>
<div mat-dialog-content>
<div class="icon-wrapper" *ngIf="data.icon">
<i class="icon {{ data.icon }}"></i>
</div>
<p class="desc cnsl-secondary-text">{{ data.descriptionKey | translate: data.descriptionParam }}</p>
<cnsl-info-section *ngIf="data.infoSectionKey" [type]="InfoSectionType.INFO">{{
data.warnSectionKey | translate
}}</cnsl-info-section>
<p *ngIf="data.hintKey" class="desc cnsl-secondary-text">{{ data.hintKey | translate: { value: data.confirmation } }}</p>
<cnsl-form-field *ngIf="data.confirmation && data.confirmationKey" class="formfield">
<cnsl-label>{{ data.confirmationKey | translate: { value: data.confirmation } }}</cnsl-label>
<input cnslInput [(ngModel)]="confirm" data-e2e="confirm-dialog-input" />
</cnsl-form-field>
<p class="desc cnsl-secondary-text">{{ data.descriptionKey | translate }}</p>
</div>
<div mat-dialog-actions class="action">
<button *ngIf="data.cancelKey" mat-stroked-button (click)="closeDialog()">
<button mat-stroked-button (click)="dialogRef.close(false)">
{{ data.cancelKey | translate }}
</button>
<span class="fill-space"></span>
<button
color="primary"
[disabled]="data.confirmation && confirm !== data.confirmation"
mat-raised-button
class="ok-button"
(click)="closeDialogWithSuccess()"
(click)="dialogRef.close(true)"
data-e2e="confirm-dialog-button"
>
{{ data.confirmKey | translate }}
@@ -1,24 +0,0 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { InfoDialogComponent } from './info-dialog.component';
describe('InfoDialogComponent', () => {
let component: InfoDialogComponent;
let fixture: ComponentFixture<InfoDialogComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [InfoDialogComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InfoDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -1,27 +1,28 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Component, inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { InfoSectionType } from '../info-section/info-section.component';
import { TranslateModule } from '@ngx-translate/core';
import { MatButtonModule } from '@angular/material/button';
export type InfoDialogData = {
confirmKey: string;
cancelKey: string;
titleKey: string;
descriptionKey: string;
};
export type InfoDialogResult = boolean;
@Component({
selector: 'cnsl-info-dialog',
templateUrl: './info-dialog.component.html',
styleUrls: ['./info-dialog.component.scss'],
standalone: false,
imports: [TranslateModule, MatDialogModule, MatButtonModule],
})
export class InfoDialogComponent {
public confirm: string = '';
InfoSectionType: any = InfoSectionType;
constructor(
public dialogRef: MatDialogRef<InfoDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: any,
) {}
protected readonly InfoSectionType = InfoSectionType;
public closeDialog(): void {
this.dialogRef.close(false);
}
public closeDialogWithSuccess(): void {
this.dialogRef.close(true);
}
protected readonly dialogRef = inject<MatDialogRef<InfoDialogComponent>>(MatDialogRef);
protected readonly data = inject<InfoDialogData>(MAT_DIALOG_DATA);
}
@@ -1,16 +0,0 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button';
import { TranslateModule } from '@ngx-translate/core';
import { MatDialogModule } from '@angular/material/dialog';
import { InfoSectionModule } from '../info-section/info-section.module';
import { InputModule } from '../input/input.module';
import { InfoDialogComponent } from './info-dialog.component';
@NgModule({
declarations: [InfoDialogComponent],
imports: [CommonModule, FormsModule, MatDialogModule, TranslateModule, InfoSectionModule, MatButtonModule, InputModule],
})
export class InfoDialogModule {}
@@ -1,24 +1,27 @@
<div class="context_intro_overlay">
<div class="overlay-info-row">
<span class="info"
>INFO <span *ngIf="data.number && data.count">{{ data.number }}/{{ data.count }}</span></span
>
>INFO
@if (data.number && data.count) {
<span>{{ data.number }}/{{ data.count }}</span>
}
</span>
<button mat-icon-button (click)="workflowService.reset()">
<mat-icon>close</mat-icon>
</button>
</div>
<p>{{ data.i18nText | translate }}</p>
<div class="overlay-actions">
<button
(click)="workflowService.nextStep()"
*ngIf="workflowService.nextPossible && !workflowService.isLast"
mat-stroked-button
>
{{ 'ACTIONS.NEXT' | translate }}
</button>
@if (workflowService.nextPossible && !workflowService.isLast) {
<button (click)="workflowService.nextStep()" mat-stroked-button>
{{ 'ACTIONS.NEXT' | translate }}
</button>
}
<button *ngIf="workflowService.isLast" (click)="workflowService.nextStep()" color="primary" mat-raised-button>
{{ 'ACTIONS.FINISHED' | translate }}
</button>
@if (workflowService.isLast) {
<button (click)="workflowService.nextStep()" color="primary" mat-raised-button>
{{ 'ACTIONS.FINISHED' | translate }}
</button>
}
</div>
</div>
@@ -1,308 +1,363 @@
<div class="info-row" *ngIf="user">
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.PAGES.STATE' | translate }}</p>
<p
*ngIf="user?.state"
class="state"
[ngClass]="{
active: user.state === UserState.USER_STATE_ACTIVE,
inactive: user.state === UserState.USER_STATE_INACTIVE,
}"
>
{{ (isV2(user) ? 'USER.STATEV2.' : 'USER.STATE.') + user.state | translate }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.ID' | translate }}</p>
<p *ngIf="userId" class="info-row-desc">{{ userId }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.DETAILS.DATECREATED' | translate }}</p>
<p *ngIf="creationDate" class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.DETAILS.DATECHANGED' | translate }}</p>
<p *ngIf="changeDate" class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'USER.PAGES.LOGINMETHODS' | translate }}</p>
<div class="copy-row" *ngFor="let login of loginMethods">
<button
[disabled]="copied === login"
[matTooltip]="(copied !== login ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="login"
(copiedValue)="copied = $event"
data-e2e="copy-loginname"
>
{{ login }}
</button>
@if (user) {
<div class="info-row">
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.PAGES.STATE' | translate }}</p>
@if (user?.state) {
<p
class="state"
[ngClass]="{
active: user.state === UserState.USER_STATE_ACTIVE,
inactive: user.state === UserState.USER_STATE_INACTIVE,
}"
>
{{ (isV2(user) ? 'USER.STATEV2.' : 'USER.STATE.') + user.state | translate }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.ID' | translate }}</p>
@if (userId) {
<p class="info-row-desc">{{ userId }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.DETAILS.DATECREATED' | translate }}</p>
@if (creationDate) {
<p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'USER.DETAILS.DATECHANGED' | translate }}</p>
@if (changeDate) {
<p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'USER.PAGES.LOGINMETHODS' | translate }}</p>
@for (login of loginMethods; track login) {
<div class="copy-row">
<button
[disabled]="copied === login"
[matTooltip]="(copied !== login ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="login"
(copiedValue)="copied = $event"
data-e2e="copy-loginname"
>
{{ login }}
</button>
</div>
}
</div>
</div>
</div>
}
<div class="info-row" *ngIf="instance">
<div class="info-wrapper">
<p class="info-row-title">{{ 'IAM.PAGES.STATE' | translate }}</p>
<p
*ngIf="instance?.state"
class="state"
[ngClass]="{
active: instance.state === State.STATE_RUNNING,
inactive: instance.state === State.STATE_STOPPED || instance.state === State.STATE_STOPPING,
}"
>
{{ 'IAM.STATE.' + instance.state | translate }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'INSTANCEID' | translate }}</p>
<p *ngIf="instance?.id" class="info-row-desc">{{ instance.id }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'NAME' | translate }}</p>
<p *ngIf="instance?.name" class="info-row-desc">{{ instance.name }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'VERSION' | translate }}</p>
<p *ngIf="instance?.version" class="info-row-desc">{{ instance.version }}</p>
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'IAM.PAGES.DOMAINLIST' | translate }}</p>
<div class="copy-row" *ngFor="let domain of instance?.domainsList">
<button
[disabled]="copied === domain.domain"
[matTooltip]="(copied !== domain.domain ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="domain.domain"
(copiedValue)="copied = $event"
>
{{ domain.domain }}
</button>
@if (instance) {
<div class="info-row">
<div class="info-wrapper">
<p class="info-row-title">{{ 'IAM.PAGES.STATE' | translate }}</p>
@if (instance?.state) {
<p
class="state"
[ngClass]="{
active: instance.state === State.STATE_RUNNING,
inactive: instance.state === State.STATE_STOPPED || instance.state === State.STATE_STOPPING,
}"
>
{{ 'IAM.STATE.' + instance.state | translate }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'INSTANCEID' | translate }}</p>
@if (instance?.id) {
<p class="info-row-desc">{{ instance.id }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'NAME' | translate }}</p>
@if (instance?.name) {
<p class="info-row-desc">{{ instance.name }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'VERSION' | translate }}</p>
@if (instance?.version) {
<p class="info-row-desc">{{ instance.version }}</p>
}
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'IAM.PAGES.DOMAINLIST' | translate }}</p>
@for (domain of instance?.domainsList; track domain) {
<div class="copy-row">
<button
[disabled]="copied === domain.domain"
[matTooltip]="(copied !== domain.domain ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="domain.domain"
(copiedValue)="copied = $event"
>
{{ domain.domain }}
</button>
</div>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
@if (instance?.details?.creationDate; as creationDate) {
<p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
@if (instance?.details?.changeDate; as changeDate) {
<p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
</div>
}
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
<p *ngIf="instance?.details?.creationDate as creationDate" class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
<p *ngIf="instance?.details?.changeDate as changeDate" class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
</div>
<div class="info-row" *ngIf="org">
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.STATE' | translate }}</p>
<p
*ngIf="org?.state"
class="state"
[ngClass]="{ active: org.state === OrgState.ORG_STATE_ACTIVE, inactive: org.state === OrgState.ORG_STATE_INACTIVE }"
>
{{ 'ORG.STATE.' + org.state | translate }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.ID' | translate }}</p>
<p *ngIf="org?.id" class="info-row-desc">{{ org.id }}</p>
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'ORG.PAGES.PRIMARYDOMAIN' | translate }}</p>
<div class="copy-row" *ngIf="org.primaryDomain">
<button
[disabled]="copied === org.primaryDomain"
[matTooltip]="(copied !== org.primaryDomain ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="org.primaryDomain"
(copiedValue)="copied = $event"
>
{{ org.primaryDomain }}
</button>
@if (org) {
<div class="info-row">
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.STATE' | translate }}</p>
@if (org?.state) {
<p
class="state"
[ngClass]="{
active: org.state === OrgState.ORG_STATE_ACTIVE,
inactive: org.state === OrgState.ORG_STATE_INACTIVE,
}"
>
{{ 'ORG.STATE.' + org.state | translate }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.ID' | translate }}</p>
@if (org?.id) {
<p class="info-row-desc">{{ org.id }}</p>
}
</div>
<div class="info-wrapper width">
<p class="info-row-title">{{ 'ORG.PAGES.PRIMARYDOMAIN' | translate }}</p>
@if (org.primaryDomain) {
<div class="copy-row">
<button
[disabled]="copied === org.primaryDomain"
[matTooltip]="(copied !== org.primaryDomain ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="org.primaryDomain"
(copiedValue)="copied = $event"
>
{{ org.primaryDomain }}
</button>
</div>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
@if (org?.details?.creationDate; as creationDate) {
<p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
@if (org?.details?.changeDate; as changeDate) {
<p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
</div>
}
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
<p *ngIf="org?.details?.creationDate as creationDate" class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
@if (project) {
<div class="info-row">
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
@if (project?.state) {
<p
class="state"
[ngClass]="{
active: project.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: project.state === ProjectState.PROJECT_STATE_INACTIVE,
}"
>
{{ 'PROJECT.STATE.' + project.state | translate }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p>
@if (project?.id) {
<p class="info-row-desc">{{ project.id }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
@if (project?.details?.creationDate; as creationDate) {
<p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
@if (project?.details?.changeDate; as changeDate) {
<p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
</div>
}
<div class="info-wrapper">
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
<p *ngIf="org?.details?.changeDate as changeDate" class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
@if (grantedProject) {
<div class="info-row">
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
@if (grantedProject?.state) {
<p
class="state"
[ngClass]="{
active: grantedProject.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE,
inactive: grantedProject.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE,
}"
>
{{ 'PROJECT.STATE.' + grantedProject.state | translate }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p>
@if (grantedProject?.projectId) {
<p class="info-row-desc">{{ grantedProject.projectId }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.GRANT.GRANTID' | translate }}</p>
@if (grantedProject?.grantId) {
<p class="info-row-desc">{{ grantedProject.grantId }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
@if (grantedProject?.details?.creationDate; as creationDate) {
<p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
@if (grantedProject?.details?.changeDate; as changeDate) {
<p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
</div>
</div>
}
<div class="info-row" *ngIf="project">
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
<p
*ngIf="project?.state"
class="state"
[ngClass]="{
active: project.state === ProjectState.PROJECT_STATE_ACTIVE,
inactive: project.state === ProjectState.PROJECT_STATE_INACTIVE,
}"
>
{{ 'PROJECT.STATE.' + project.state | translate }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p>
<p *ngIf="project?.id" class="info-row-desc">{{ project.id }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
<p *ngIf="project?.details?.creationDate as creationDate" class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
<p *ngIf="project?.details?.changeDate as changeDate" class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
</div>
<div class="info-row" *ngIf="grantedProject">
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
<p
*ngIf="grantedProject?.state"
class="state"
[ngClass]="{
active: grantedProject.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE,
inactive: grantedProject.state === ProjectGrantState.PROJECT_GRANT_STATE_INACTIVE,
}"
>
{{ 'PROJECT.STATE.' + grantedProject.state | translate }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.ID' | translate }}</p>
<p *ngIf="grantedProject?.projectId" class="info-row-desc">{{ grantedProject.projectId }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.GRANT.GRANTID' | translate }}</p>
<p *ngIf="grantedProject?.grantId" class="info-row-desc">{{ grantedProject.grantId }}</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
<p *ngIf="grantedProject?.details?.creationDate as creationDate" class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
<p *ngIf="grantedProject?.details?.changeDate as changeDate" class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
</div>
</div>
<ng-container *ngIf="app">
@if (app) {
<div class="info-row">
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.STATE' | translate }}</p>
<p
*ngIf="app?.state"
class="state"
[ngClass]="{ active: app.state === AppState.APP_STATE_ACTIVE, inactive: app.state === AppState.APP_STATE_INACTIVE }"
>
{{ 'APP.PAGES.DETAIL.STATE.' + app.state | translate }}
</p>
@if (app?.state) {
<p
class="state"
[ngClass]="{
active: app.state === AppState.APP_STATE_ACTIVE,
inactive: app.state === AppState.APP_STATE_INACTIVE,
}"
>
{{ 'APP.PAGES.DETAIL.STATE.' + app.state | translate }}
</p>
}
</div>
<div class="info-wrapper" *ngIf="app?.apiConfig?.authMethodType as authMethodType">
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
<p class="info-row-desc">
{{ 'APP.API.AUTHMETHOD.' + authMethodType | translate }}
</p>
</div>
<div class="info-wrapper" *ngIf="app?.oidcConfig?.authMethodType as authMethodType">
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
<p class="info-row-desc">
{{ 'APP.OIDC.AUTHMETHOD.' + authMethodType | translate }}
</p>
</div>
@if (app?.apiConfig?.authMethodType; as authMethodType) {
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
<p class="info-row-desc">
{{ 'APP.API.AUTHMETHOD.' + authMethodType | translate }}
</p>
</div>
}
@if (app?.oidcConfig?.authMethodType; as authMethodType) {
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
<p class="info-row-desc">
{{ 'APP.OIDC.AUTHMETHOD.' + authMethodType | translate }}
</p>
</div>
}
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.ID' | translate }}</p>
<p *ngIf="app?.id" class="info-row-desc">{{ app.id }}</p>
@if (app?.id) {
<p class="info-row-desc">{{ app.id }}</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.DATECREATED' | translate }}</p>
<p *ngIf="app?.details?.creationDate as creationDate" class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
@if (app?.details?.creationDate; as creationDate) {
<p class="info-row-desc">
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.PAGES.DATECHANGED' | translate }}</p>
<p *ngIf="app?.details?.changeDate as changeDate" class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
@if (app?.details?.changeDate; as changeDate) {
<p class="info-row-desc">
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM yyyy, HH:mm' }}
</p>
}
</div>
<div class="info-wrapper" *ngIf="app">
<p class="info-row-title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p>
<div class="copy-row" *ngIf="app.oidcConfig?.clientId">
<button
*ngIf="app.oidcConfig?.clientId as clientId"
[disabled]="copied === clientId"
[matTooltip]="(copied !== clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="clientId"
(copiedValue)="copied = $event"
>
{{ clientId }}
</button>
@if (app) {
<div class="info-wrapper">
<p class="info-row-title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p>
@if (app.oidcConfig?.clientId) {
<div class="copy-row">
@if (app.oidcConfig?.clientId; as clientId) {
<button
[disabled]="copied === clientId"
[matTooltip]="(copied !== clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="clientId"
(copiedValue)="copied = $event"
>
{{ clientId }}
</button>
}
</div>
}
@if (app.apiConfig?.clientId) {
<div class="copy-row">
@if (app.apiConfig?.clientId; as clientId) {
<button
[disabled]="copied === clientId"
[matTooltip]="(copied !== clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="clientId"
(copiedValue)="copied = $event"
>
{{ clientId }}
</button>
}
</div>
}
</div>
<div class="copy-row" *ngIf="app.apiConfig?.clientId">
<button
*ngIf="app.apiConfig?.clientId as clientId"
[disabled]="copied === clientId"
[matTooltip]="(copied !== clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
cnslCopyToClipboard
[valueToCopy]="clientId"
(copiedValue)="copied = $event"
>
{{ clientId }}
</button>
</div>
</div>
}
</div>
</ng-container>
}
@@ -7,9 +7,15 @@
fit: fitWidth,
}"
>
<i *ngIf="type === infoSectionType.INFO" class="icon las la-info"></i>
<i *ngIf="type === infoSectionType.WARN" class="icon las la-exclamation"></i>
<i *ngIf="type === infoSectionType.ALERT" class="icon las la-exclamation"></i>
@if (type === infoSectionType.INFO) {
<i class="icon las la-info"></i>
}
@if (type === infoSectionType.WARN) {
<i class="icon las la-exclamation"></i>
}
@if (type === infoSectionType.ALERT) {
<i class="icon las la-exclamation"></i>
}
<div class="info-section-content">
<ng-content></ng-content>

Some files were not shown because too many files have changed in this diff Show More