docs: Dynamic Manager Permission Table (#11120)

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

# Which Problems Are Solved

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 <florian@zitadel.com>
Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Maximilian
2025-12-09 14:05:04 +02:00
committed by GitHub
co-authored by Florian Forster Livio Spring
parent 541f8058cb
commit 94f9ef966f
8 changed files with 249 additions and 23 deletions
+1 -1
View File
@@ -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
@@ -7,6 +7,7 @@ import ManagerDescription from "./_manager_description.mdx";
<ManagerDescription name="ManagerDescription" />
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.
@@ -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.
<PermissionTable data={permissionData} />
+17
View File
@@ -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,
+5 -3
View File
@@ -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"
}
}
@@ -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 (
<div className={styles.container}>
<table className={styles.table}>
<thead>
<tr>
<th>Permission</th>
{roles.map((role) => (
<th key={role}>{role}</th>
))}
</tr>
</thead>
<tbody>
{permissions.map((perm) => (
<tr key={perm}>
<th>{perm}</th>
{roles.map((role) => {
const hasPermission = matrix.get(role).has(perm);
return (
<td
key={`${role}-${perm}`}
className={hasPermission ? styles.yes : styles.no}
>
{hasPermission ? 'yes' : 'no'}
</td>
);
})}
</tr>
))}
</tbody>
</table>
</div>
);
}
@@ -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);
}
+66 -19
View File
@@ -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: {}