From 94f9ef966fe30faab0125de4e74650b0564ee008 Mon Sep 17 00:00:00 2001 From: Maximilian Date: Tue, 9 Dec 2025 14:05:04 +0200 Subject: [PATCH] docs: Dynamic Manager Permission Table (#11120) # Which Problems Are Solved Permissions for each manager role are stored in a .yaml file and can't be easily searched in our docs. # How the Problems Are Solved We reference anyways the defaults.yaml file in our docs. The script parses the yaml and produces a markdown table containing all manager roles by manager permissions. # Additional Changes * Updated README with correct production build command # Additional Context --------- Co-authored-by: Florian Forster Co-authored-by: Livio Spring --- docs/README.md | 2 +- docs/docs/concepts/structure/managers.mdx | 1 + docs/docs/guides/manage/console/managers.mdx | 9 ++ docs/docusaurus.config.js | 17 ++++ docs/package.json | 8 +- docs/src/components/PermissionTable/index.jsx | 77 +++++++++++++++++ .../PermissionTable/styles.module.css | 73 ++++++++++++++++ pnpm-lock.yaml | 85 ++++++++++++++----- 8 files changed, 249 insertions(+), 23 deletions(-) create mode 100644 docs/src/components/PermissionTable/index.jsx create mode 100644 docs/src/components/PermissionTable/styles.module.css diff --git a/docs/README.md b/docs/README.md index 94464d7a7..d6007fbeb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ Start a docs server pnpm nx run @zitadel/docs:dev # Or serve a production build -pnpm nx run @zitadel/docs:start +pnpm nx run @zitadel/docs:prod ``` The site will be available at http://localhost:3100 diff --git a/docs/docs/concepts/structure/managers.mdx b/docs/docs/concepts/structure/managers.mdx index 3e1318ea5..194a34d7b 100644 --- a/docs/docs/concepts/structure/managers.mdx +++ b/docs/docs/concepts/structure/managers.mdx @@ -7,6 +7,7 @@ import ManagerDescription from "./_manager_description.mdx"; + Notes: - Read our [guide on Managers](../../guides/manage/console/managers) to learn more about the role concept and how to use Manager roles in ZITADEL. diff --git a/docs/docs/guides/manage/console/managers.mdx b/docs/docs/guides/manage/console/managers.mdx index 626768ed9..cc571f7a2 100644 --- a/docs/docs/guides/manage/console/managers.mdx +++ b/docs/docs/guides/manage/console/managers.mdx @@ -56,3 +56,12 @@ InternalAuthZ: - "iam.read" - "iam.write" ``` + +import PermissionTable from '@site/src/components/PermissionTable'; +import permissionData from '@site/docs/self-hosting/manage/configure/defaults.yaml'; + +# Manager Permission Matrix + +This table is generated dynamically from our configuration file. + + \ No newline at end of file diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index e8786d161..64dea4eb5 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -450,6 +450,23 @@ module.exports = { }, }; }, + function (context, options) { + return { + name: 'docusaurus-yaml-loader', + configureWebpack(config, isServer) { + return { + module: { + rules: [ + { + test: /\.ya?ml$/, + use: 'yaml-loader', + }, + ], + }, + }; + }, + }; + }, ], markdown: { mermaid: true, diff --git a/docs/package.json b/docs/package.json index 37398dc80..b86553263 100644 --- a/docs/package.json +++ b/docs/package.json @@ -29,8 +29,8 @@ "@docusaurus/theme-search-algolia": "^3.8.1", "@headlessui/react": "^1.7.4", "@heroicons/react": "^2.0.13", - "@signalwire/docusaurus-plugin-llms-txt": "^1.2.0", "@inkeep/cxkit-docusaurus": "^0.5.89", + "@signalwire/docusaurus-plugin-llms-txt": "^1.2.0", "autoprefixer": "^10.4.13", "clsx": "^1.2.1", "docusaurus-plugin-image-zoom": "^3.0.1", @@ -44,7 +44,8 @@ "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.2.0", "react-google-charts": "^5.2.1", - "react-player": "^2.15.1" + "react-player": "^2.15.1", + "unicode-property-aliases-ecmascript": "^2.2.0" }, "browserslist": { "production": [ @@ -61,6 +62,7 @@ "devDependencies": { "@docusaurus/module-type-aliases": "^3.8.1", "@docusaurus/types": "^3.8.1", - "tailwindcss": "^3.2.4" + "tailwindcss": "^3.2.4", + "yaml-loader": "^0.8.1" } } \ No newline at end of file diff --git a/docs/src/components/PermissionTable/index.jsx b/docs/src/components/PermissionTable/index.jsx new file mode 100644 index 000000000..75455d896 --- /dev/null +++ b/docs/src/components/PermissionTable/index.jsx @@ -0,0 +1,77 @@ +import React, { useMemo } from 'react'; +import styles from './styles.module.css'; + +// Note: We no longer need 'js-yaml' here because the loader parses it before the component sees it. + +export default function PermissionTable({ data }) { + const { roles, permissions, matrix } = useMemo(() => { + if (!data) return { roles: [], permissions: [], matrix: {} }; + + // 1. Identify all sources. The 'data' prop is already a JS Object. + const sources = [ + data?.InternalAuthZ?.RolePermissionMappings, + data?.SystemAuthZ?.RolePermissionMappings, + ].filter(Boolean).flat(); + + // 2. Aggregate Data + const roleMap = new Map(); + const allPermissions = new Set(); + + sources.forEach((mapping) => { + const roleName = mapping.Role; + const rolePerms = mapping.Permissions || []; + + if (!roleMap.has(roleName)) { + roleMap.set(roleName, new Set()); + } + + rolePerms.forEach((perm) => { + roleMap.get(roleName).add(perm); + allPermissions.add(perm); + }); + }); + + // 3. Sort for display + const sortedRoles = Array.from(roleMap.keys()).sort(); + const sortedPermissions = Array.from(allPermissions).sort(); + + return { + roles: sortedRoles, + permissions: sortedPermissions, + matrix: roleMap, + }; + }, [data]); + + return ( +
+ + + + + {roles.map((role) => ( + + ))} + + + + {permissions.map((perm) => ( + + + {roles.map((role) => { + const hasPermission = matrix.get(role).has(perm); + return ( + + ); + })} + + ))} + +
Permission{role}
{perm} + {hasPermission ? 'yes' : 'no'} +
+
+ ); +} \ No newline at end of file diff --git a/docs/src/components/PermissionTable/styles.module.css b/docs/src/components/PermissionTable/styles.module.css new file mode 100644 index 000000000..eaa711d6d --- /dev/null +++ b/docs/src/components/PermissionTable/styles.module.css @@ -0,0 +1,73 @@ +.container { + overflow-x: auto; + max-width: 100%; + margin: 2rem 0; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 8px; + box-shadow: var(--ifm-global-shadow-lw); +} + +.table { + border-collapse: separate; /* Required for sticky to work */ + border-spacing: 0; + width: 100%; + font-size: 0.85rem; +} + +.table th, +.table td { + padding: 8px 12px; + border-bottom: 1px solid var(--ifm-color-emphasis-200); + border-right: 1px solid var(--ifm-color-emphasis-200); + white-space: nowrap; +} + +/* Sticky Header Row */ +.table thead th { + position: sticky; + top: 0; + background-color: var(--ifm-background-surface-color); + z-index: 10; + border-bottom: 2px solid var(--ifm-color-emphasis-300); + text-align: left; + font-weight: bold; +} + +/* Sticky First Column (Permission Name) */ +.table tbody th { + position: sticky; + left: 0; + background-color: var(--ifm-background-surface-color); + z-index: 5; + border-right: 2px solid var(--ifm-color-emphasis-300); + text-align: left; + /*font-family: var(--ifm-font-family-monospace); + color: var(--ifm-color-primary);*/ +} + +/* Top Left Corner Intersection */ +.table thead th:first-child { + left: 0; + z-index: 20; + border-right: 2px solid var(--ifm-color-emphasis-300); +} + +/* Cell Coloring */ +.yes { + background-color: rgba(0, 255, 0, 0.1); /* Light green tint */ + color: var(--ifm-color-success); + text-align: center; + font-weight: bold; +} + +.no { + color: var(--ifm-color-emphasis-400); + text-align: center; + font-size: 0.8em; +} + +/* Dark mode adjustments automatically handled by Docusaurus CSS vars, + but ensuring background opacity works */ +[data-theme='dark'] .yes { + background-color: rgba(0, 255, 0, 0.15); +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a621126f1..231b4c36e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,7 +140,7 @@ importers: version: 1.0.0 '@vitejs/plugin-react': specifier: ^4.4.1 - version: 4.7.0(vite@5.4.20(@types/node@22.18.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)) + version: 4.7.0(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1)) '@zitadel/client': specifier: workspace:* version: link:../../packages/zitadel-client @@ -218,7 +218,7 @@ importers: version: 5.9.2 vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.9.2)(vite@5.4.20(@types/node@22.18.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)) + version: 5.1.4(typescript@5.9.2)(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1)) vitest: specifier: ^2.0.0 version: 2.1.9(@types/node@22.18.1)(jsdom@26.1.0)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0) @@ -538,6 +538,9 @@ importers: react-player: specifier: ^2.15.1 version: 2.16.1(react@18.3.1) + unicode-property-aliases-ecmascript: + specifier: ^2.2.0 + version: 2.2.0 devDependencies: '@docusaurus/module-type-aliases': specifier: ^3.8.1 @@ -548,6 +551,9 @@ importers: tailwindcss: specifier: ^3.2.4 version: 3.4.14 + yaml-loader: + specifier: ^0.8.1 + version: 0.8.1 packages/zitadel-client: dependencies: @@ -9541,6 +9547,9 @@ packages: jasmine-spec-reporter@7.0.0: resolution: {integrity: sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==} + javascript-stringify@2.1.0: + resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} + jest-diff@30.1.2: resolution: {integrity: sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -9594,6 +9603,10 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsbn@0.1.1: resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} @@ -12101,7 +12114,7 @@ packages: puppeteer@22.15.0: resolution: {integrity: sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q==} engines: {node: '>=18'} - deprecated: < 24.9.0 is no longer supported + deprecated: < 24.15.0 is no longer supported hasBin: true qjobs@1.2.0: @@ -13639,8 +13652,8 @@ packages: resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} engines: {node: '>=4'} - unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} engines: {node: '>=4'} unicorn-magic@0.1.0: @@ -14342,6 +14355,10 @@ packages: yaml-ast-parser@0.0.43: resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yaml-loader@0.8.1: + resolution: {integrity: sha512-BCEndnUoi3BaZmePkwGGe93txRxLgMhBa/gE725v1/GHnura8QvNs7c4+4C1yyhhKoj3Dg63M7IqhA++15j6ww==} + engines: {node: '>= 14'} + yaml@1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} @@ -14967,7 +14984,7 @@ snapshots: dependencies: '@jsdevtools/ono': 7.1.3 '@types/json-schema': 7.0.15 - js-yaml: 4.1.0 + js-yaml: 4.1.1 '@asamuzakjp/css-color@3.2.0': dependencies: @@ -16852,7 +16869,7 @@ snapshots: '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -17336,7 +17353,7 @@ snapshots: '@docusaurus/utils-common': 3.8.1(@swc/core@1.13.5(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.3.1 joi: 17.13.3 - js-yaml: 4.1.0 + js-yaml: 4.1.1 lodash: 4.17.21 tslib: 2.8.1 transitivePeerDependencies: @@ -17361,7 +17378,7 @@ snapshots: globby: 11.1.0 gray-matter: 4.0.3 jiti: 1.21.7 - js-yaml: 4.1.0 + js-yaml: 4.1.1 lodash: 4.17.21 micromatch: 4.0.8 p-queue: 6.6.2 @@ -19587,7 +19604,7 @@ snapshots: colorette: 1.4.0 https-proxy-agent: 7.0.6 js-levenshtein: 1.1.6 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 5.1.6 pluralize: 8.0.0 yaml-ast-parser: 0.0.43 @@ -21054,7 +21071,7 @@ snapshots: dependencies: vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.44.0)(yaml@2.8.1) - '@vitejs/plugin-react@4.7.0(vite@5.4.20(@types/node@22.18.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0))': + '@vitejs/plugin-react@4.7.0(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -21062,7 +21079,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 5.4.20(@types/node@22.18.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0) + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -22322,7 +22339,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.2): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -22332,7 +22349,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.2 @@ -25357,6 +25374,8 @@ snapshots: dependencies: colors: 1.4.0 + javascript-stringify@2.1.0: {} + jest-diff@30.1.2: dependencies: '@jest/diff-sequences': 30.0.1 @@ -25425,6 +25444,10 @@ snapshots: dependencies: argparse: 2.0.1 + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsbn@0.1.1: {} jsdom@26.1.0: @@ -27144,7 +27167,7 @@ snapshots: find-up: 5.0.0 glob: 10.4.5 he: 1.2.0 - js-yaml: 4.1.0 + js-yaml: 4.1.1 log-symbols: 4.1.0 minimatch: 9.0.5 ms: 2.1.3 @@ -30865,11 +30888,11 @@ snapshots: unicode-match-property-ecmascript@2.0.0: dependencies: unicode-canonical-property-names-ecmascript: 2.0.1 - unicode-property-aliases-ecmascript: 2.1.0 + unicode-property-aliases-ecmascript: 2.2.0 unicode-match-property-value-ecmascript@2.2.1: {} - unicode-property-aliases-ecmascript@2.1.0: {} + unicode-property-aliases-ecmascript@2.2.0: {} unicorn-magic@0.1.0: {} @@ -31235,13 +31258,13 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@5.4.20(@types/node@22.18.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)): + vite-tsconfig-paths@5.1.4(typescript@5.9.2)(vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1)): dependencies: debug: 4.4.1(supports-color@5.5.0) globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.2) optionalDependencies: - vite: 5.4.20(@types/node@22.18.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0) + vite: 7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1) transitivePeerDependencies: - supports-color - typescript @@ -31272,6 +31295,24 @@ snapshots: sass: 1.92.1 terser: 5.44.0 + vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(less@4.1.3)(lightningcss@1.30.1)(sass@1.92.1)(terser@5.44.0)(yaml@2.8.1): + dependencies: + esbuild: 0.25.9 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.52.3 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 22.18.1 + fsevents: 2.3.3 + jiti: 2.5.1 + less: 4.1.3 + lightningcss: 1.30.1 + sass: 1.92.1 + terser: 5.44.0 + yaml: 2.8.1 + vite@7.1.5(@types/node@22.18.1)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.90.0)(terser@5.44.0)(yaml@2.8.1): dependencies: esbuild: 0.25.9 @@ -31734,6 +31775,12 @@ snapshots: yaml-ast-parser@0.0.43: {} + yaml-loader@0.8.1: + dependencies: + javascript-stringify: 2.1.0 + loader-utils: 2.0.4 + yaml: 2.8.1 + yaml@1.10.2: {} yaml@2.8.1: {}