@scalio-cloud/ui 0.25.0 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/nav.css +14 -1
- package/package.json +1 -1
- package/react/labels.js +2 -2
- package/react/roles.js +153 -32
- package/CHANGELOG.md +0 -266
package/README.md
CHANGED
|
@@ -335,13 +335,14 @@ see `fonts/licenses/`. Icons in `react/icons.js`: Lucide, ISC license, see `lice
|
|
|
335
335
|
|
|
336
336
|
## Release
|
|
337
337
|
|
|
338
|
-
1. Change the files
|
|
338
|
+
1. Change the files and bump `version` in `package.json` (semver; tag without `v`).
|
|
339
339
|
2. Merge to `main`, then release it: in the Scalio Dev cockpit (row Scalio UI, "Freigeben" with the version) or
|
|
340
340
|
in GitHub (Actions -> publish -> Run workflow on main, or create the tag/release yourself). The `publish`
|
|
341
341
|
workflow tests and publishes that version to npmjs.com via trusted publishing (no token; npm org
|
|
342
342
|
`scalio-cloud`, trusted publisher Scalio-Cloud / scalio-ui / publish.yml) and, when started by hand or from
|
|
343
|
-
the cockpit, creates the tag and GitHub release with the CHANGELOG lines.
|
|
344
|
-
`
|
|
343
|
+
the cockpit, creates the tag and GitHub release with the CHANGELOG lines. The changelog lives in scalio-docs
|
|
344
|
+
`changelog/scalio-ui/` (ADR 0066): every change adds a file `changelog.d/<name>.md` there, and before the release a
|
|
345
|
+
docs PR runs `python3 tools/changelog.py --repo scalio-ui release <version>`.
|
|
345
346
|
3. Apps update with `yarn upgrade @scalio-cloud/ui@<version>` in their own PR.
|
|
346
347
|
|
|
347
348
|
`npm test` checks: light/dark variables match, the preset only uses defined variables, all font files exist,
|
package/nav.css
CHANGED
|
@@ -484,6 +484,19 @@
|
|
|
484
484
|
.dark .scalio-roles-yes { color: #4ade80; }
|
|
485
485
|
.scalio-roles-no { color: hsl(var(--muted-foreground)); }
|
|
486
486
|
.scalio-roles-lic { margin-left: 0.375rem; }
|
|
487
|
+
.scalio-roles-cell > svg, .scalio-roles-cell > input, .scalio-roles-cell > .scalio-roles-no { display: block; margin-left: auto; margin-right: auto; }
|
|
488
|
+
.scalio-roles-table th.scalio-roles-col.is-mine, .scalio-roles-cell.is-mine { background: hsl(var(--primary) / 0.08); }
|
|
489
|
+
.scalio-roles-table th.scalio-roles-col.is-mine .scalio-roles-name { color: hsl(var(--primary)); }
|
|
490
|
+
.scalio-roles-mine { margin-bottom: 0.25rem; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.15em; color: hsl(var(--primary)); }
|
|
491
|
+
.scalio-tenant-list { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; border: 1px solid hsl(var(--border)); border-radius: 2px; }
|
|
492
|
+
.scalio-tenant-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; padding: 0.625rem 0.875rem; border-bottom: 1px solid hsl(var(--border)); }
|
|
493
|
+
.scalio-tenant-row:last-child { border-bottom: 0; }
|
|
494
|
+
.scalio-tenant-who { min-width: 0; }
|
|
495
|
+
.scalio-tenant-domain { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; font-weight: 600; overflow-wrap: anywhere; }
|
|
496
|
+
.scalio-tenant-id { margin-top: 0.125rem; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: hsl(var(--muted-foreground)); overflow-wrap: anywhere; }
|
|
497
|
+
.scalio-tenant-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }
|
|
498
|
+
.scalio-tenant-manual { display: flex; flex-direction: column; gap: 0.5rem; max-width: 28rem; }
|
|
499
|
+
.scalio-btn.is-disabled { pointer-events: none; opacity: 0.5; }
|
|
487
500
|
.scalio-roles-source { display: flex; flex-direction: column; gap: 0.875rem; padding: 1rem; border: 1px solid hsl(var(--border)); border-radius: 2px; background: hsl(var(--card)); font-size: 0.875rem; }
|
|
488
501
|
|
|
489
502
|
/* Setup wizard (SetupWizard, ADR 0060): design of the Nexus wizard, first built in Kontor */
|
|
@@ -493,7 +506,7 @@
|
|
|
493
506
|
padding: 1.5rem; border: 1px solid hsl(var(--border)); border-radius: 2px; background: hsl(var(--background)); color: hsl(var(--foreground));
|
|
494
507
|
box-shadow: 0 16px 40px rgb(0 0 0 / 0.25); font-size: 0.875rem;
|
|
495
508
|
}
|
|
496
|
-
.scalio-wizard.is-wide { max-width:
|
|
509
|
+
.scalio-wizard.is-wide { max-width: 72rem; }
|
|
497
510
|
.scalio-wizard-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; }
|
|
498
511
|
.scalio-wizard-title { margin: 0; font-family: 'Chivo', sans-serif; font-size: 1.5rem; font-weight: 900; line-height: 1.2; text-transform: uppercase; letter-spacing: -0.025em; }
|
|
499
512
|
.scalio-wizard-subtitle { margin: 0.25rem 0 0; color: hsl(var(--muted-foreground)); }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalio-cloud/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "Scalio Control Room design tokens, Tailwind preset, self-hosted fonts, the shared app header, sign-in layout, sidebar and page blocks",
|
|
5
5
|
"license": "SEE LICENSE IN README.md",
|
|
6
6
|
"private": false,
|
package/react/labels.js
CHANGED
|
@@ -64,7 +64,7 @@ const LABELS = {
|
|
|
64
64
|
graceTip: "Diese Version liegt nach Ihrem Lizenzdatum und wechselt nach der Kulanzzeit in den Nur-Lesen-Modus.",
|
|
65
65
|
},
|
|
66
66
|
accountGroups: { people: "Personen", access: "Rechte" },
|
|
67
|
-
accountTabs: { users: "Benutzer", roles: "Rollen", entra: "
|
|
67
|
+
accountTabs: { users: "Benutzer", roles: "Rollen", entra: "Rollen aus Entra ID", signIn: "Anmeldung", organizations: "Organisationen" },
|
|
68
68
|
passwordPolicy: {
|
|
69
69
|
label: "Anmeldung mit Passwort",
|
|
70
70
|
local: "Erlaubt",
|
|
@@ -460,7 +460,7 @@ const LABELS = {
|
|
|
460
460
|
graceTip: "This version is past your license date and turns read-only after the grace period.",
|
|
461
461
|
},
|
|
462
462
|
accountGroups: { people: "People", access: "Permissions" },
|
|
463
|
-
accountTabs: { users: "Users", roles: "Roles", entra: "
|
|
463
|
+
accountTabs: { users: "Users", roles: "Roles", entra: "Roles from Entra ID", signIn: "Sign-in", organizations: "Organizations" },
|
|
464
464
|
passwordPolicy: {
|
|
465
465
|
label: "Password sign-in",
|
|
466
466
|
local: "Allowed",
|
package/react/roles.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Konten und Rollen -> Rollen and ->
|
|
2
|
+
// Konten und Rollen -> Rollen and -> Rollen aus Entra ID, the same in every Scalio app (scalio-docs concepts/app-roles.md,
|
|
3
3
|
// roles/R1-R6). rolesApi(api, opts) maps the shared role API of scalio-platform `roles_router` (GET /roles/permissions,
|
|
4
4
|
// GET/POST /roles, PATCH/DELETE /roles/{id}); RoleAdmin shows "welche Rolle darf was" (permissions as rows, roles as
|
|
5
|
-
// columns), built-in roles read-only, own roles with checkboxes, add / copy / rename / delete
|
|
6
|
-
// the role source (Scalio app or Entra app roles) with the Entra values
|
|
5
|
+
// columns), built-in roles read-only, own roles with checkboxes, add / copy / rename / delete; the column of the signed-in
|
|
6
|
+
// person's role is marked. RoleSourceSettings is the role source (Scalio app or Entra app roles) with the Entra values
|
|
7
|
+
// and the list of tenants whose roles count, with «Tenant hinzufügen» by admin consent (ADR 0068).
|
|
7
8
|
const React = require("react");
|
|
8
9
|
const h = React.createElement;
|
|
9
10
|
const Icons = require("./icons");
|
|
@@ -47,8 +48,26 @@ const TEXT = {
|
|
|
47
48
|
sourceEntra: "Microsoft Entra ID (App-Rollen)",
|
|
48
49
|
hintLocal: "Sie vergeben die Rollen unter Benutzer. Werte für Entra ID, falls Sie später umstellen:",
|
|
49
50
|
hintEntra: "Ab der nächsten Anmeldung entscheiden die App-Rollen der Unternehmensanwendung «Scalio» für jedes Microsoft-Konto. Wer keine Rolle hat, kann sich nicht mehr anmelden. Eigene Rollen und lokale Konten vergeben Sie weiter hier. Werte:",
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
yourRole: "Ihre Rolle",
|
|
52
|
+
tenants: "Vertraute Tenants",
|
|
53
|
+
tenantsHint: "Nur App-Rollen aus diesen Tenants zählen, und Personen aus diesen Tenants können sich anmelden.",
|
|
54
|
+
tenantsEmpty: "Noch kein Tenant. Mit «Tenant hinzufügen» gibt ein Admin des Tenants die Zustimmung für die App «Scalio».",
|
|
55
|
+
tenantAdd: "Tenant hinzufügen",
|
|
56
|
+
tenantAddHint: "Sie melden sich als Admin des Tenants bei Microsoft an und stimmen der App «Scalio» zu.",
|
|
57
|
+
tenantManual: "Von Hand eintragen",
|
|
58
|
+
tenantManualHint: "Nur wenn ein Admin des Tenants der App «Scalio» schon zugestimmt hat.",
|
|
59
|
+
tenantId: "Tenant-ID",
|
|
60
|
+
tenantAddManual: "Eintragen",
|
|
61
|
+
tenantInvalid: "Das ist keine Tenant-ID (GUID).",
|
|
62
|
+
tenantRemove: "Entfernen",
|
|
63
|
+
tenantRemoveTitle: "Tenant «{name}» entfernen?",
|
|
64
|
+
tenantRemoveText: "App-Rollen aus diesem Tenant zählen dann nicht mehr. Die Unternehmensanwendung im Tenant bleibt; entfernen Sie sie dort bei Bedarf selbst.",
|
|
65
|
+
tenantFixed: "Fest",
|
|
66
|
+
tenantAdded: "Tenant hinzugefügt.",
|
|
67
|
+
tenantRemoved: "Tenant entfernt.",
|
|
68
|
+
consentCancelled: "Die Zustimmung wurde abgebrochen.",
|
|
69
|
+
consentFailed: "Die Zustimmung hat nicht geklappt. Melden Sie sich mit einem Konto an, das in diesem Tenant Admin ist.",
|
|
70
|
+
consentForbidden: "Nur Admins dieser App können einen Tenant hinzufügen.",
|
|
52
71
|
entraConfirm: "Rollen aus Entra ID übernehmen?",
|
|
53
72
|
entraConfirmText: "Ab der nächsten Anmeldung entscheiden die App-Rollen in Entra ID für jedes Microsoft-Konto. Personen ohne Rolle können sich nicht mehr anmelden.",
|
|
54
73
|
entraAction: "Entra ID verwenden",
|
|
@@ -90,8 +109,26 @@ const TEXT = {
|
|
|
90
109
|
sourceEntra: "Microsoft Entra ID (app roles)",
|
|
91
110
|
hintLocal: "You give roles under Users. Values for Entra ID, in case you switch later:",
|
|
92
111
|
hintEntra: "From the next sign-in, the app roles of the enterprise application “Scalio” decide for every Microsoft account. People without a role can no longer sign in. Own roles and local accounts are still given here. Values:",
|
|
93
|
-
|
|
94
|
-
|
|
112
|
+
yourRole: "Your role",
|
|
113
|
+
tenants: "Trusted tenants",
|
|
114
|
+
tenantsHint: "Only app roles from these tenants count, and people from these tenants can sign in.",
|
|
115
|
+
tenantsEmpty: "No tenant yet. With “Add tenant” an admin of the tenant consents to the app “Scalio”.",
|
|
116
|
+
tenantAdd: "Add tenant",
|
|
117
|
+
tenantAddHint: "You sign in to Microsoft as an admin of the tenant and consent to the app “Scalio”.",
|
|
118
|
+
tenantManual: "Enter by hand",
|
|
119
|
+
tenantManualHint: "Only if an admin of the tenant already consented to the app “Scalio”.",
|
|
120
|
+
tenantId: "Tenant ID",
|
|
121
|
+
tenantAddManual: "Add",
|
|
122
|
+
tenantInvalid: "This is not a tenant ID (GUID).",
|
|
123
|
+
tenantRemove: "Remove",
|
|
124
|
+
tenantRemoveTitle: "Remove the tenant “{name}”?",
|
|
125
|
+
tenantRemoveText: "App roles from this tenant no longer count. The enterprise application stays in the tenant; remove it there yourself if needed.",
|
|
126
|
+
tenantFixed: "Fixed",
|
|
127
|
+
tenantAdded: "Tenant added.",
|
|
128
|
+
tenantRemoved: "Tenant removed.",
|
|
129
|
+
consentCancelled: "The consent was cancelled.",
|
|
130
|
+
consentFailed: "The consent did not work. Sign in with an account that is an admin in this tenant.",
|
|
131
|
+
consentForbidden: "Only admins of this app can add a tenant.",
|
|
95
132
|
entraConfirm: "Take roles from Entra ID?",
|
|
96
133
|
entraConfirmText: "From the next sign-in, the app roles in Entra ID decide for every Microsoft account. People without a role can no longer sign in.",
|
|
97
134
|
entraAction: "Use Entra ID",
|
|
@@ -265,9 +302,11 @@ function RoleAdmin(props) {
|
|
|
265
302
|
const byGroup = (g) => catalog.permissions.filter((p) => (p.group || "") === g || (!groups.includes(p.group || "") && g === groups[0]));
|
|
266
303
|
const usersText = (r) => (r.users === null ? "" : r.users === 1 ? L.oneUser : fill(L.users, { n: r.users }));
|
|
267
304
|
|
|
305
|
+
const mine = (r) => props.currentRole !== undefined && props.currentRole !== null && String(props.currentRole) === r.id;
|
|
268
306
|
const head = h("tr", null,
|
|
269
307
|
h("th", { className: "scalio-roles-perm" }, L.permission),
|
|
270
|
-
roles.map((r) => h("th", { key: r.id, className: cx("scalio-roles-col", r.builtin ? "is-builtin" : "is-own"), "data-testid": `role-col-${r.id}` },
|
|
308
|
+
roles.map((r) => h("th", { key: r.id, className: cx("scalio-roles-col", r.builtin ? "is-builtin" : "is-own", mine(r) && "is-mine"), "data-testid": `role-col-${r.id}` },
|
|
309
|
+
mine(r) ? h("div", { className: "scalio-roles-mine", "data-testid": "role-yours" }, L.yourRole) : null,
|
|
271
310
|
h("div", { className: "scalio-roles-name" }, tr(r.name, lang)),
|
|
272
311
|
h("div", { className: "scalio-roles-meta" },
|
|
273
312
|
h("span", null, r.builtin ? L.builtin : L.own),
|
|
@@ -282,10 +321,10 @@ function RoleAdmin(props) {
|
|
|
282
321
|
const cell = (r, p) => {
|
|
283
322
|
const has = r.admin || r.permissions.includes(p.key);
|
|
284
323
|
if (r.builtin || !canEdit) {
|
|
285
|
-
return h("td", { key: r.id, className: "scalio-roles-cell", "data-testid": `role-cell-${r.id}-${p.key}` },
|
|
324
|
+
return h("td", { key: r.id, className: cx("scalio-roles-cell", mine(r) && "is-mine"), "data-testid": `role-cell-${r.id}-${p.key}` },
|
|
286
325
|
has ? h(Icons.Check, { className: "scalio-icon scalio-roles-yes", "aria-label": "✓" }) : h("span", { className: "scalio-roles-no", "aria-label": "–" }, "–"));
|
|
287
326
|
}
|
|
288
|
-
return h("td", { key: r.id, className: "scalio-roles-cell" },
|
|
327
|
+
return h("td", { key: r.id, className: cx("scalio-roles-cell", mine(r) && "is-mine") },
|
|
289
328
|
h("input", { type: "checkbox", checked: has, disabled: busy, onChange: () => toggle(r, p.key),
|
|
290
329
|
"aria-label": `${tr(r.name, lang)}: ${tr(p.label, lang)}`, "data-testid": `role-check-${r.id}-${p.key}` }));
|
|
291
330
|
};
|
|
@@ -303,7 +342,7 @@ function RoleAdmin(props) {
|
|
|
303
342
|
body.push(h("tr", { key: "admin-only", className: "scalio-roles-group" }, h("td", { colSpan: roles.length + 1 }, L.adminOnly)));
|
|
304
343
|
body.push(h("tr", { key: "admin-row" },
|
|
305
344
|
h("td", { className: "scalio-roles-perm" }, L.adminAll),
|
|
306
|
-
roles.map((r) => h("td", { key: r.id, className: "scalio-roles-cell" },
|
|
345
|
+
roles.map((r) => h("td", { key: r.id, className: cx("scalio-roles-cell", mine(r) && "is-mine") },
|
|
307
346
|
r.admin ? h(Icons.Check, { className: "scalio-icon scalio-roles-yes", "aria-label": "✓" }) : h("span", { className: "scalio-roles-no" }, "–")))));
|
|
308
347
|
|
|
309
348
|
const dialogEl = dialog ? h(Dialog, { title: dialog.mode === "add" ? L.addTitle : L.renameTitle, onClose: close, testId: "role-dialog" },
|
|
@@ -339,46 +378,128 @@ function RoleAdmin(props) {
|
|
|
339
378
|
dialogEl);
|
|
340
379
|
}
|
|
341
380
|
|
|
342
|
-
|
|
343
|
-
|
|
381
|
+
const GUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
382
|
+
|
|
383
|
+
// A tenant entry: "tid" (old lists) or {tid, domain, fixed, label, href}.
|
|
384
|
+
function tenantOf(x) {
|
|
385
|
+
if (typeof x === "string") return { tid: x.trim().toLowerCase(), domain: "" };
|
|
386
|
+
return Object.assign({}, x, { tid: String(x.tid || x.id || "").trim().toLowerCase(), domain: x.domain || "" });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Back from «Tenant hinzufügen»: ?entra_tenant=added|cancelled|failed|forbidden (scalio-identity 0.5.0).
|
|
390
|
+
function consentResult() {
|
|
391
|
+
if (typeof window === "undefined" || !window.location) return "";
|
|
392
|
+
try {
|
|
393
|
+
const url = new URL(window.location.href);
|
|
394
|
+
const res = url.searchParams.get("entra_tenant") || "";
|
|
395
|
+
if (res && window.history && window.history.replaceState) {
|
|
396
|
+
url.searchParams.delete("entra_tenant");
|
|
397
|
+
window.history.replaceState(window.history.state, "", url.pathname + url.search + url.hash);
|
|
398
|
+
}
|
|
399
|
+
return res;
|
|
400
|
+
} catch (e) {
|
|
401
|
+
return "";
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Props: value {source: "local"|"entra", values: [...], tenants?: [tid | {tid, domain, fixed}]},
|
|
406
|
+
// onSave({source, tenants: [{tid, domain}]}) -> Promise, tenants (show the tenant list), consentUrl (button «Tenant
|
|
407
|
+
// hinzufügen», e.g. "/api/auth/scalio/consent"), manual (default true: enter a tenant ID by hand), fixed (one fixed
|
|
408
|
+
// tenant, no add or remove: Nexus), extraTenants [{tid, domain, label, href}] and extraTitle (view only: Rosetta's
|
|
409
|
+
// customer tenants), lang, labels, confirm, notify, formatError, disabled, testId.
|
|
344
410
|
function RoleSourceSettings(props) {
|
|
345
411
|
const lang = props.lang === "en" ? "en" : "de";
|
|
346
412
|
const L = rolesText(lang, props.labels);
|
|
347
413
|
const v = props.value || { source: "local", values: [] };
|
|
414
|
+
const list = (v.tenants || []).map(tenantOf).filter((t) => t.tid);
|
|
348
415
|
const [source, setSource] = React.useState(v.source || "local");
|
|
349
|
-
const [
|
|
416
|
+
const [manual, setManual] = React.useState(null); // null = closed, else the typed id
|
|
350
417
|
const [busy, setBusy] = React.useState(false);
|
|
351
|
-
|
|
352
|
-
const
|
|
353
|
-
const
|
|
354
|
-
|
|
418
|
+
const [error, setError] = React.useState("");
|
|
419
|
+
const say = (text, tone) => props.notify && props.notify(text, tone || "success");
|
|
420
|
+
const fmt = props.formatError || errorText;
|
|
421
|
+
React.useEffect(() => { setSource(v.source || "local"); }, [v.source]);
|
|
422
|
+
React.useEffect(() => {
|
|
423
|
+
const res = consentResult();
|
|
424
|
+
if (res === "added") say(L.tenantAdded);
|
|
425
|
+
else if (res === "cancelled") say(L.consentCancelled, "error");
|
|
426
|
+
else if (res === "forbidden") say(L.consentForbidden, "error");
|
|
427
|
+
else if (res) say(L.consentFailed, "error");
|
|
428
|
+
}, []);
|
|
429
|
+
const dirty = source !== (v.source || "local");
|
|
430
|
+
const store = async (src, tenants, okText) => {
|
|
355
431
|
setBusy(true);
|
|
432
|
+
setError("");
|
|
356
433
|
try {
|
|
357
|
-
await props.onSave({ source, tenants: tenants.
|
|
358
|
-
|
|
434
|
+
await props.onSave({ source: src, tenants: tenants.map((t) => ({ tid: t.tid, domain: t.domain || "" })) });
|
|
435
|
+
say(okText || L.saved);
|
|
436
|
+
return true;
|
|
359
437
|
} catch (e) {
|
|
360
|
-
|
|
438
|
+
say(fmt(e), "error");
|
|
439
|
+
return false;
|
|
361
440
|
} finally {
|
|
362
441
|
setBusy(false);
|
|
363
442
|
}
|
|
364
443
|
};
|
|
444
|
+
const save = async () => {
|
|
445
|
+
if (source === "entra" && v.source !== "entra" && !(await ask(props.confirm, L.entraConfirm, L.entraConfirmText, L.entraAction, false))) return;
|
|
446
|
+
store(source, list.filter((t) => !t.fixed));
|
|
447
|
+
};
|
|
448
|
+
const remove = async (t) => {
|
|
449
|
+
if (!(await ask(props.confirm, fill(L.tenantRemoveTitle, { name: t.domain || t.tid }), L.tenantRemoveText, L.tenantRemove))) return;
|
|
450
|
+
store(v.source || "local", list.filter((x) => !x.fixed && x.tid !== t.tid), L.tenantRemoved);
|
|
451
|
+
};
|
|
452
|
+
const addManual = async (e) => {
|
|
453
|
+
e.preventDefault();
|
|
454
|
+
const tid = (manual || "").trim().toLowerCase();
|
|
455
|
+
if (!GUID.test(tid)) { setError(L.tenantInvalid); return; }
|
|
456
|
+
if (list.some((t) => t.tid === tid)) { setManual(null); return; }
|
|
457
|
+
if (await store(v.source || "local", [...list.filter((t) => !t.fixed), { tid, domain: "" }], L.tenantAdded)) setManual(null);
|
|
458
|
+
};
|
|
365
459
|
const values = (v.values || []).map((x) => (typeof x === "string" ? x : x.value || x.name)).filter(Boolean);
|
|
460
|
+
const off = props.disabled || busy;
|
|
461
|
+
const row = (t, extra) => h("li", { key: t.tid, className: "scalio-tenant-row", "data-testid": `tenant-${t.tid}` },
|
|
462
|
+
h("div", { className: "scalio-tenant-who" },
|
|
463
|
+
h("div", { className: "scalio-tenant-domain" }, t.href ? h("a", { href: t.href }, t.domain || t.label || t.tid) : (t.domain || t.label || t.tid),
|
|
464
|
+
t.label && t.domain ? h("span", { className: "scalio-badge" }, t.label) : null),
|
|
465
|
+
t.domain || t.label ? h("div", { className: "scalio-tenant-id" }, t.tid) : null),
|
|
466
|
+
extra ? null : t.fixed || props.fixed
|
|
467
|
+
? h("span", { className: "scalio-badge" }, L.tenantFixed)
|
|
468
|
+
: h("button", { type: "button", className: "scalio-btn is-ghost scalio-roles-tool is-danger", onClick: () => remove(t), disabled: off, "data-testid": `tenant-remove-${t.tid}` }, L.tenantRemove));
|
|
469
|
+
const tenantBlock = props.tenants ? h("div", { className: "scalio-users-field", "data-testid": "role-source-tenants" },
|
|
470
|
+
h("div", { className: "scalio-field-label" }, L.tenants),
|
|
471
|
+
list.length
|
|
472
|
+
? h("ul", { className: "scalio-tenant-list" }, list.map((t) => row(t, false)))
|
|
473
|
+
: h("p", { className: "scalio-users-hint", "data-testid": "tenants-empty" }, L.tenantsEmpty),
|
|
474
|
+
h("p", { className: "scalio-users-hint" }, L.tenantsHint),
|
|
475
|
+
props.fixed ? null : h("div", { className: "scalio-tenant-actions" },
|
|
476
|
+
props.consentUrl ? h("a", { className: cx("scalio-btn is-primary", off && "is-disabled"), href: props.consentUrl, title: L.tenantAddHint, "data-testid": "tenant-add" },
|
|
477
|
+
h(Icons.Plus, { className: "scalio-icon" }), L.tenantAdd) : null,
|
|
478
|
+
props.manual !== false && manual === null ? h("button", { type: "button", className: "scalio-btn", onClick: () => { setError(""); setManual(""); }, disabled: off, "data-testid": "tenant-manual" }, L.tenantManual) : null),
|
|
479
|
+
manual !== null ? h("form", { className: "scalio-tenant-manual", onSubmit: addManual },
|
|
480
|
+
h("label", { className: "scalio-field-label", htmlFor: "tenant-manual-id" }, L.tenantId),
|
|
481
|
+
h("input", { id: "tenant-manual-id", className: "scalio-input", value: manual, autoFocus: true, placeholder: "00000000-0000-0000-0000-000000000000",
|
|
482
|
+
onChange: (e) => setManual(e.target.value), disabled: off, "data-testid": "tenant-manual-id" }),
|
|
483
|
+
h("p", { className: "scalio-users-hint" }, L.tenantManualHint),
|
|
484
|
+
error ? h("p", { className: "scalio-notice is-error", role: "alert", "data-testid": "tenant-manual-error" }, error) : null,
|
|
485
|
+
h("div", { className: "scalio-tenant-actions" },
|
|
486
|
+
h("button", { type: "button", className: "scalio-btn is-ghost", onClick: () => setManual(null) }, L.cancel),
|
|
487
|
+
h("button", { type: "submit", className: "scalio-btn is-primary", disabled: off || !manual.trim(), "data-testid": "tenant-manual-save" }, L.tenantAddManual))) : null,
|
|
488
|
+
props.extraTenants && props.extraTenants.length ? h("div", { className: "scalio-users-field", "data-testid": "role-source-extra" },
|
|
489
|
+
props.extraTitle ? h("div", { className: "scalio-field-label" }, props.extraTitle) : null,
|
|
490
|
+
h("ul", { className: "scalio-tenant-list" }, props.extraTenants.map(tenantOf).map((t) => row(t, true)))) : null) : null;
|
|
366
491
|
return h("div", { className: "scalio-roles-source", "data-testid": props.testId || "role-source" },
|
|
367
492
|
h("div", { className: "scalio-users-field" },
|
|
368
493
|
h("label", { className: "scalio-field-label", htmlFor: "role-source-select" }, L.source),
|
|
369
|
-
h("
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
h("input", { id: "role-source-tenants", className: "scalio-input", value: tenants, disabled: props.disabled || busy,
|
|
376
|
-
onChange: (e) => setTenants(e.target.value), "data-testid": "role-source-tenants" }),
|
|
377
|
-
h("p", { className: "scalio-users-hint" }, L.tenantsHint)) : null,
|
|
494
|
+
h("div", { className: "scalio-tenant-actions" },
|
|
495
|
+
h("select", { id: "role-source-select", className: "scalio-input scalio-users-select", value: source, disabled: off,
|
|
496
|
+
onChange: (e) => setSource(e.target.value), "data-testid": "role-source-select" },
|
|
497
|
+
h("option", { value: "local" }, L.sourceLocal),
|
|
498
|
+
h("option", { value: "entra" }, L.sourceEntra)),
|
|
499
|
+
h("button", { type: "button", className: "scalio-btn is-primary", onClick: save, disabled: off || !dirty, "data-testid": "role-source-save" }, L.save))),
|
|
378
500
|
h("p", { className: "scalio-users-hint", "data-testid": "role-source-hint" }, source === "entra" ? L.hintEntra : L.hintLocal, " ",
|
|
379
501
|
values.map((x, i) => h(React.Fragment, { key: x }, i ? ", " : "", h("code", null, x)))),
|
|
380
|
-
|
|
381
|
-
h("button", { type: "button", className: "scalio-btn is-primary", onClick: save, disabled: props.disabled || busy || !dirty, "data-testid": "role-source-save" }, L.save)));
|
|
502
|
+
tenantBlock);
|
|
382
503
|
}
|
|
383
504
|
|
|
384
505
|
module.exports = { RoleAdmin, RoleSourceSettings, rolesApi, rolesText };
|
package/CHANGELOG.md
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
## 0.25.0
|
|
4
|
-
Rollen, Microsoft Entra und eigene Rollen in allen Apps angleichen (scalio-docs `concepts/app-roles.md`, `roles/R1`–`R6`).
|
|
5
|
-
- New `RoleAdmin` for Konten und Rollen → Rollen: table «welche Rolle darf was» (permissions as rows, grouped; roles as
|
|
6
|
-
columns with number of accounts and a license mark), built-in roles read-only, own roles with checkboxes, «Rolle
|
|
7
|
-
anlegen» (empty or with the permissions of another role), copy, rename, delete (the app refuses roles that are still
|
|
8
|
-
given). A row «Nur für Administratoren» shows what only the administrator may do.
|
|
9
|
-
- `rolesApi(api, {base, permissions})` for the shared role API of scalio-platform 0.3.0 `roles_router`
|
|
10
|
-
(`GET /roles/permissions`, `GET/POST /roles`, `PATCH/DELETE /roles/{id}`; Nexus passes `permissions: "/permissions"`).
|
|
11
|
-
Texts of permissions and roles may be strings or `{de, en}`.
|
|
12
|
-
- New `RoleSourceSettings` for Konten und Rollen → Microsoft Entra: role source (this app or Entra app roles), the
|
|
13
|
-
Entra values, optional tenant IDs, confirmation before switching to Entra.
|
|
14
|
-
- Styles `.scalio-roles*` in nav.css. No token changes, nothing changes until an app uses the new components.
|
|
15
|
-
- `UserAdmin` (follow-up to 0.23.0, built then but not released): badge «Eingeladen» for open invitations (`invited`
|
|
16
|
-
on an account, `invite_only` for a row that is only an invitation; such a row offers only «Einladung zurückziehen»,
|
|
17
|
-
which calls `DELETE /users/{id}`); when the invitation mail could not be sent, the app returns `invite_link` and
|
|
18
|
-
`UserAdmin` shows the link once; badges below the address, actions wrap, last sign-in only on wide screens,
|
|
19
|
-
disabled fields greyed.
|
|
20
|
-
|
|
21
|
-
## 0.24.0
|
|
22
|
-
Einrichtungsassistent als Standard für alle Apps (scalio-docs ADR 0060, contract `interfaces/setup-wizard.md`).
|
|
23
|
-
- New `SetupWizard`: the first-step dialog of Nexus and Kontor as a shared shell. Steps come as configuration
|
|
24
|
-
(`id`, `label`, `title`, `description`, `content`, `onNext`, `canNext`, `optional`); stepper on top, «Schritt n von
|
|
25
|
-
m», Zurück/Weiter, «Überspringen» for optional steps, errors of `onNext` keep the step open, short success message
|
|
26
|
-
at the end. Without `onClose` it cannot be closed; `wide` for steps with tables. Texts DE/EN in `labels.wizard`.
|
|
27
|
-
- `setupWizardApi(api, {base})` for `GET/PUT /api/setup/wizard` (reads snake_case and camelCase), `useSetupWizard()`
|
|
28
|
-
(loads the state, opens once by itself at the saved step while the setup is needed, saves each step, completes),
|
|
29
|
-
`openSetupWizard()` and `SetupWizardButton` for Einstellungen.
|
|
30
|
-
- Styles `.scalio-wizard*` in nav.css. No token changes, nothing changes until an app uses the wizard.
|
|
31
|
-
|
|
32
|
-
## 0.23.0
|
|
33
|
-
Benutzerverwaltung in allen Apps angleichen (scalio-docs ADR 0059, `users/B1`–`B6`, contract `interfaces/app-users.md`).
|
|
34
|
-
- New `UserAdmin` for Konten und Rollen → Benutzer, the same in every app: list with search and badges (Microsoft,
|
|
35
|
-
Passwort, Rolle aus Entra, Notfall-Administrator, Gesperrt, Wartet auf Freigabe), «Benutzer hinzufügen» (Microsoft,
|
|
36
|
-
Passwort with a start password shown once, or invitation by e-mail), «Bearbeiten» (name and e-mail only for accounts
|
|
37
|
-
without Microsoft, role, access, lock, emergency administrator) with the actions new start password, remove password,
|
|
38
|
-
reset second factor, unlink Microsoft and delete; waiting accounts: role, access, «Freischalten», «Zusammenführen».
|
|
39
|
-
The app passes roles and an optional access column/field (customers, organisation).
|
|
40
|
-
- `usersApi(api, {base})` for the shared routes (`/users`, Nexus `/admins`), reads snake_case and camelCase.
|
|
41
|
-
- `MicrosoftConnection` for the profile section «Verknüpfungen» (link or unlink one's own Microsoft account).
|
|
42
|
-
- New icons `Pencil`, `Link2`; modal dialog styles (`.scalio-dialog*`). No token changes, nothing changes until an app
|
|
43
|
-
uses the new components.
|
|
44
|
-
|
|
45
|
-
## 0.22.0
|
|
46
|
-
Login, Navigation und Banner vereinheitlichen (scalio-docs UISPEC section 3, ADR 0042 Nachtrag 2026-11-01).
|
|
47
|
-
- **Notice in the look of the test mode banner:** thin frame in the tone, 3px bar on the left, tinted background,
|
|
48
|
-
icon in the tone, text in the normal text colour (also for errors). Colours: red = `error` (Fehler), orange = `warn`
|
|
49
|
-
(wichtig), green = `ok` (gute Meldung), blue = `info` (Aktion). New aliases `important`, `success`, `action`
|
|
50
|
-
(`noticeKind`). Banners keep the full frame; stacked banners share one line. Dark mode has its own tones.
|
|
51
|
-
- `SelfTestBanner` is now a `Notice` banner (kind warn) and takes an optional `action` (e.g. link to «Tests»).
|
|
52
|
-
- **Behaviour change:** `normalizeTheme()` returns `system` when nothing (or something unknown) is stored, so every
|
|
53
|
-
app starts like the device on the first visit and on the login page (before: `light`).
|
|
54
|
-
- New `initialLang(stored, navLang)` / `browserLang()`: stored choice wins, otherwise German for a German browser and
|
|
55
|
-
English for everything else, the same in every app.
|
|
56
|
-
- **Visible change:** every `Notice` gets the new frame and colours (info notices turn from Klein blue to a lighter
|
|
57
|
-
blue, error text is no longer red, only bar and icon). No token changes.
|
|
58
|
-
|
|
59
|
-
## 0.21.0
|
|
60
|
-
Testmodus (scalio-docs ADR 0053, package scalio-selftest).
|
|
61
|
-
- `SelfTestPanel` for the page «Tests»: last runs with verdict, scope, source, version and counts; details per
|
|
62
|
-
section; «Neu fehlgeschlagen» and «Wieder in Ordnung» since the run before; «Jetzt ausführen» and «Abbrechen» with
|
|
63
|
-
live progress; schedule (scope and hour of the nightly run). `selftestApi(api)` maps the routes `/e2e/*`.
|
|
64
|
-
- `SelfTestBanner`: hint «Testmodus (SAFE)» while the test mode is on.
|
|
65
|
-
- CSS in `nav.css` (`.scalio-selftest*`).
|
|
66
|
-
- **Visible change:** only in apps that show the new page. No token changes.
|
|
67
|
-
|
|
68
|
-
## 0.20.0
|
|
69
|
-
Scalio Ideen (ADR 0050): feature requests from the apps, votes and comments, kept centrally in Compass.
|
|
70
|
-
- `supportApi(api, { ideas: true })` adds the routes of scalio-connect 0.8.0 (`ideas_router`). The support drawer then
|
|
71
|
-
gets a third tab «Ideen»: ideas of this app (most votes, newest, done), vote and take back, «Meine», idea page with
|
|
72
|
-
the answer from Scalio and comments, and «Idee vorschlagen» with similar ideas shown while typing.
|
|
73
|
-
- With ideas on, the ticket category «Wunsch» leaves the ticket form. A self-hosted installation without a connection
|
|
74
|
-
shows a link to the public page (`<portal>/ideen`), also in the link-only support view.
|
|
75
|
-
- New export `IdeasPanel`; CSS in `nav.css` (`.scalio-idea-*`, `.scalio-ideas*`).
|
|
76
|
-
- **Visible change:** only in apps that pass `{ ideas: true }`. No token changes.
|
|
77
|
-
|
|
78
|
-
## 0.19.0
|
|
79
|
-
Settings card "Scalio Beacon" for the apps (Beacon phase 3, scalio-docs `concepts/beacon-suite.md`).
|
|
80
|
-
- `BeaconConnection` with `peersApi(api)`: pair the installation with a Beacon instance (address + one-time code from
|
|
81
|
-
Beacon), show the pairing (address, version, last contact, queue), test it and disconnect. Routes from
|
|
82
|
-
scalio-connect 0.8.0 (`scalio_connect.peers.web.admin_router`).
|
|
83
|
-
- Labels `beacon*` in German and English.
|
|
84
|
-
- **Visible change:** none until an app shows the card. No token changes.
|
|
85
|
-
|
|
86
|
-
## 0.18.1
|
|
87
|
-
Page actions after Markus' feedback (2026-10-31, UISPEC section 2 "Page actions"). 0.18.0 was released before these
|
|
88
|
-
changes were merged; apps use 0.18.1.
|
|
89
|
-
- The slot is shown only when a page has actions and pushes the menu down; no reserved room, no separator line.
|
|
90
|
-
- 0.25rem above the buttons, 0.875rem from the last button to the next section heading (half the usual gap).
|
|
91
|
-
- Only Übersicht (first item of a first section without heading) is pinned; further items of that section stay in
|
|
92
|
-
the menu (Pulse: Geräte).
|
|
93
|
-
- Buttons in the slot: same height (2.25rem), one line, long labels end with "…". Labels without "Neu" (README).
|
|
94
|
-
- **Visible change:** only in apps that set `pageActions`. No token changes.
|
|
95
|
-
|
|
96
|
-
## 0.18.0
|
|
97
|
-
Page actions in the sidebar, first version (Markus 2026-10-31, reference Scalio Kontor; scalio-docs ADR 0042 Nachtrag).
|
|
98
|
-
- New `react/actions.js`: `PageActions` puts a page's actions (Neu …, Import, Zurück) into a fixed slot in the
|
|
99
|
-
sidebar right below Übersicht from 768px; on phones they stay in the page header. `ActionSlot`, `useDesktop()`.
|
|
100
|
-
- `Sidebar` gets `pageActions`: Übersicht pinned on top, then the slot (room for two buttons, line below), then the
|
|
101
|
-
sections. `shell.css`: an empty `.scalio-page-actions` is hidden. `react-dom` is an optional peer dependency.
|
|
102
|
-
- **Visible change:** none until an app sets `pageActions`. No token changes.
|
|
103
|
-
|
|
104
|
-
## 0.17.0
|
|
105
|
-
Scalio Pulse is built (scalio-docs ADR 0043, P1 started 2026-10-31).
|
|
106
|
-
- Product switcher: "Pulse" is no longer marked "bald"; it behaves like the other apps (link from `SCALIO_APP_LINKS`).
|
|
107
|
-
- `manualUrl("pulse")`: docs.scaliopulse.com / devdocs.scaliopulse.com.
|
|
108
|
-
- **Visible change:** after the upgrade, Pulse in the switcher loses the "bald" badge and is greyed out until the
|
|
109
|
-
instance has a link for it. No token changes.
|
|
110
|
-
|
|
111
|
-
## 0.16.0
|
|
112
|
-
Same profile page in every app, reference Scalio Nexus (Markus 2026-10-31, scalio-docs ADR 0042 Nachtrag).
|
|
113
|
-
- New `react/profile.js`: `ProfileIdentity` (letters, name, email · role), `ThemeChoice` (Hell, Dunkel, System as
|
|
114
|
-
option cards), `LanguageChoice` (flags, `fixed` for an instance-wide language), `TimezoneChoice` (searchable
|
|
115
|
-
list, "Automatisch" = browser), `PasswordChange` (current, new, confirm; mismatch check, error from the API).
|
|
116
|
-
- Helpers: `setUserTimezone`, `currentTz`, `browserTz`, `listTimezones`, `tzOffset` (apps format dates with
|
|
117
|
-
`currentTz()`), `applyTheme(mode)` / `resolveTheme` / `normalizeTheme` for light, dark and system.
|
|
118
|
-
- Icons `Globe`, `Clock`; labels `profile_` (DE/EN). Default security hint now names authenticator app and passkeys.
|
|
119
|
-
- **Visible change:** none until an app uses the new parts. No token changes.
|
|
120
|
-
|
|
121
|
-
## 0.15.0
|
|
122
|
-
New app Scalio Beacon (service desk, scalio-docs ADR 0044).
|
|
123
|
-
- Product switcher: entry "Beacon" (icon `RadioTower`) between Citadel and Pulse, available like the others.
|
|
124
|
-
- `manualUrl("beacon")`: docs.scaliobeacon.com / devdocs.scaliobeacon.com.
|
|
125
|
-
- **Visible change:** every app shows "Beacon" in the switcher after its upgrade (greyed out until the instance
|
|
126
|
-
has a link in `SCALIO_APP_LINKS`). No token changes.
|
|
127
|
-
|
|
128
|
-
## 0.14.0
|
|
129
|
-
UI alignment round 1, follow-up (scalio-docs ADR 0042, Markus 2026-10-30).
|
|
130
|
-
- `accountGroups({ lang, base, show })` and `accountArea(part)`: Konten und Rollen in the structure of the settings
|
|
131
|
-
page (groups Personen, Rechte, Sicherheit; one address per area, `/accounts/<area>`).
|
|
132
|
-
- `SettingsLayout` takes `root` (e.g. "Konten und Rollen") for the crumb, title fallback and nav label.
|
|
133
|
-
- `SubNav` with `value` and `onSelect` renders in-page tabs (buttons, `role="tablist"`).
|
|
134
|
-
- New CSS classes `.scalio-tabs` / `.scalio-tab` in `nav.css`: in-page tabs (shadcn Tabs) look like the sub
|
|
135
|
-
navigation (underline, like Nexus "Gruppen").
|
|
136
|
-
- **Visible change:** none until an app uses the new parts. No token changes.
|
|
137
|
-
|
|
138
|
-
## 0.13.0
|
|
139
|
-
UI alignment round 1 (scalio-docs ADR 0041).
|
|
140
|
-
- `editionBadges(license, { admin, demo, lang })` and `EditionBadges`: edition badge next to the product name from the
|
|
141
|
-
scalio-license state (Free, plan, trial with days left, demo; read-only or grace for administrators). `Brand`
|
|
142
|
-
takes `brand.badges` and shows them on the name line.
|
|
143
|
-
- `adminSection({ audit, accounts, settings })`: the sidebar section "Verwaltung" with fixed order, words and icons
|
|
144
|
-
(new inline icons `ScrollText`, `Users`, `Settings`, `Info`).
|
|
145
|
-
- `ApiStatus` / `useApiStatus(check)`: nothing while the API answers, a red badge "API nicht erreichbar" when not.
|
|
146
|
-
- `Notice` gets an icon, `action`, `onDismiss` ("Später") and `banner` (full width above the page).
|
|
147
|
-
- `accountTabs()` (Benutzer, Rollen, Microsoft Entra, Anmeldung, Organisationen), `PasswordPolicySelect` and
|
|
148
|
-
`policyAllowed()`: "nur Notfall-Administrator" and "aus" stay disabled until an emergency administrator exists.
|
|
149
|
-
- **Visible change:** existing `Notice` boxes get an icon in front and lay out as a row. Everything else only where
|
|
150
|
-
an app uses the new parts. No token changes.
|
|
151
|
-
|
|
152
|
-
## 0.12.0
|
|
153
|
-
- New manual button in `AppHeader` (prop `docsUrl`): a book icon between theme and support that opens the product
|
|
154
|
-
manual in a new tab; `DocsButton` on its own, `manualUrl(product, { stage, lang })` builds the address
|
|
155
|
-
(`devdocs.<domain>` on dev and test, `docs.<domain>` on production, demo and self-hosted, German under `/de/`).
|
|
156
|
-
**Visible change** only in apps that pass `docsUrl`; no token changes.
|
|
157
|
-
|
|
158
|
-
## 0.11.0
|
|
159
|
-
- Self-hosted support (ADR 0022, T11; needs scalio-connect 0.7.0 in the backend): `supportApi` gets `status()` and
|
|
160
|
-
`setButtonHidden(hidden)`. The support drawer asks `status()` first; a self-hosted installation without a
|
|
161
|
-
connection shows no form, only a note and the button "Zum Scalio-Portal" (`<portal>/support`). The card
|
|
162
|
-
"Support-Verbindung" gets the switch "Support-Knopf in der Kopfzeile ausblenden". **Visible change** only where
|
|
163
|
-
the backend reports `link_only` or the client has `setButtonHidden`; no token changes.
|
|
164
|
-
|
|
165
|
-
## 0.10.0
|
|
166
|
-
- `LicensePanel` takes more from an app's own `map`: a `hint` per usage bar (e.g. base + add-on seats), add-ons with
|
|
167
|
-
`badges`, `detail`, `tone` (`info`/`warn`) and `removable: false`, `addonErrors` (invalid add-ons, removable),
|
|
168
|
-
`addonsHidden`, `addonsReadOnly` and `keyReadOnly` (a note instead of the form) and a `copied` text per row.
|
|
169
|
-
Lets Nexus keep its trials, demo mode and badges on the shared panel. **No visible change** for apps that use
|
|
170
|
-
`mapScalioLicense`.
|
|
171
|
-
|
|
172
|
-
## 0.9.0
|
|
173
|
-
- New `CommandPalette` (⌘K / Strg+K): finds every page of the sidebar data (`nav`) plus extra `pages`, remembers the
|
|
174
|
-
last visited pages, and takes app commands as `groups` (Nexus: AI and guarded actions). Arrow keys, Enter, Esc,
|
|
175
|
-
no dialog library. Helpers `openCommandPalette()` (for the sidebar search button), `paletteShortcut(lang)`,
|
|
176
|
-
`paletteGroups`, `palettePages`. `LABELS` get the palette texts, `Icons` get History and CornerDownLeft.
|
|
177
|
-
**No visible change** until an app uses it; Nexus keeps its test ids (`command-palette-input`, `cmd-nav-*`).
|
|
178
|
-
|
|
179
|
-
## 0.8.1
|
|
180
|
-
- `SupportConnection` fills in the portal address of the instance's stage (`default_portal` from scalio-connect
|
|
181
|
-
0.5.0: dev -> portaldev, test -> portaltest, others -> portal.scaliocloud.com). **Visible change:** the address
|
|
182
|
-
field is no longer empty on the settings card. No change with older backends.
|
|
183
|
-
|
|
184
|
-
## 0.8.0
|
|
185
|
-
- New `SecurityFactors` (profile: authenticator app, backup codes, passkeys) with `securityApi(api, webauthn)`, and
|
|
186
|
-
`LicensePanel` (status, usage, add-ons, key) with `licenseApi(api)` and `mapScalioLicense`. They replace the copies
|
|
187
|
-
in Citadel, Foundry, Rosetta and Nexus. `LABELS` get `mfa` and `license` texts, `Icons` get Smartphone, KeyRound,
|
|
188
|
-
Trash, Copy, Puzzle, Plus, Alert, and `.scalio-btn` gets the variants `is-primary`, `is-ghost`, `is-danger`.
|
|
189
|
-
**No visible change** until an app uses the new blocks; then they look the same in every app.
|
|
190
|
-
|
|
191
|
-
## 0.7.0
|
|
192
|
-
- `SupportDrawer` takes `client` (from the new `supportApi(api)`) and `page`: tabs "Neue Anfrage" (category,
|
|
193
|
-
subject, description, technical details) and "Meine Tickets" (list, answers from support, reply, rating) for the
|
|
194
|
-
central support desk (ADR 0022). New settings card `SupportConnection` (portal address and one-time code, status,
|
|
195
|
-
check, disconnect). `LABELS` get the support texts. **Visible change only for apps that pass `client`**; without
|
|
196
|
-
it the drawer keeps its single form.
|
|
197
|
-
|
|
198
|
-
## 0.6.2
|
|
199
|
-
- **Visible change for apps using `SettingsLayout`:** the settings area uses the full width of the window (was at most
|
|
200
|
-
64rem), so wide screens show more cards side by side. No change for other apps.
|
|
201
|
-
|
|
202
|
-
## 0.6.1
|
|
203
|
-
- **Visible change for apps using `SettingsLayout`:** the settings page has no outer padding of its own any more;
|
|
204
|
-
the app's content area brings it (0.6.0 had both, so the page got a double margin). No change for other apps.
|
|
205
|
-
|
|
206
|
-
## 0.6.0
|
|
207
|
-
- New `SettingsLayout` and `SettingsSection` for the settings page (UISPEC "Einstellungen"): sub-navigation on
|
|
208
|
-
the left in fixed groups (Allgemein, app groups, Verbindungen, Benachrichtigungen, Sicherheit, System) with a
|
|
209
|
-
search, a select on phones, crumb and title of the area; areas look like the profile page. Below three areas
|
|
210
|
-
the sub-navigation is left out. `LABELS` gets the group names. No change for apps that do not use them.
|
|
211
|
-
|
|
212
|
-
## 0.5.2
|
|
213
|
-
- Navigation items take `match` (further paths that mark the item active), for one menu item over several pages.
|
|
214
|
-
- New `SubNav` (tabs as links on top of a page) and `WorkspaceSwitcher` (Identity / HR in Nexus). No change for
|
|
215
|
-
apps that do not use them.
|
|
216
|
-
|
|
217
|
-
## 0.5.1
|
|
218
|
-
- Phones: the context select (tenant, customer) is hidden in the header below 640px, there is no room next to
|
|
219
|
-
the header buttons. `AppShell` takes `drawerTop` and shows it above the navigation in the phone drawer.
|
|
220
|
-
|
|
221
|
-
## 0.5.0
|
|
222
|
-
- **Visible change:** `rounded-sm` is now 2px as UISPEC says (was 0px from the preset). Buttons, fields, cards and
|
|
223
|
-
badges in Nexus, Citadel and Foundry get barely visible round corners; Rosetta already had 2px and can drop its
|
|
224
|
-
own `borderRadius` override.
|
|
225
|
-
- New app frame in `@scalio-cloud/ui/react`: `AppShell`, `Sidebar` (navigation as data: sections, hidden items,
|
|
226
|
-
badges, search button, one main action, version), `NavDrawer` for phones, `ContextSelect` for tenant, customer or
|
|
227
|
-
organisation in the header.
|
|
228
|
-
- Moved here from the apps: `ProductSwitcher` with `PRODUCTS`, `switcherHref`, `stageBadge`, `scalioFeatures`,
|
|
229
|
-
`useScalioApps`, and `SupportDrawer` (the app passes the send function). Same test ids as before.
|
|
230
|
-
- Page blocks: `KpiTile`, `EmptyState`, `Loading`, `Notice`, `StatusBadge`; `PageHeader` takes `back`.
|
|
231
|
-
- `LABELS` gets the shared navigation words (Übersicht, Verwaltung, Protokoll, Konten und Rollen, Einstellungen, ...).
|
|
232
|
-
- New `nav.css` (imported by `index.css`). No other visual change for apps that do not use the new parts.
|
|
233
|
-
|
|
234
|
-
## 0.4.1
|
|
235
|
-
- No change to tokens, styles or components. CI and publish workflow on current action versions (past Node 20).
|
|
236
|
-
|
|
237
|
-
## 0.4.0
|
|
238
|
-
- New sign-in building blocks in `@scalio-cloud/ui/react`: `AuthLayout` (app brand on a dark grid on the left, form
|
|
239
|
-
on the right, language and optional theme top right, small brand above the form on phones), `AuthHeader`,
|
|
240
|
-
`MicrosoftButton`, `AuthDivider` ("oder mit E-Mail"), `AuthMessage`.
|
|
241
|
-
- New `ProfileSection` and `PROFILE_SECTIONS` for the profile page (Konto, Darstellung, Sprache und Region,
|
|
242
|
-
Sicherheit, Verknüpfungen), title and hint on the left, content in a card on the right.
|
|
243
|
-
- More shared words in `LABELS` (Anmelden, Passwort vergessen?, Passwort ändern, Zwei-Faktor-Anmeldung, ...).
|
|
244
|
-
- New `auth.css` (imported by `index.css`). No visual change for apps that do not use the new components.
|
|
245
|
-
|
|
246
|
-
## 0.3.0
|
|
247
|
-
- New `@scalio-cloud/ui/react`: shared app header (`AppHeader` with status, language, theme, support and user menu
|
|
248
|
-
in a fixed order), `UserMenu`, `LanguageToggle`, `ThemeToggle`, `SupportButton`, `PageHeader`, `VersionTag`.
|
|
249
|
-
Only peer dependency is React (optional for apps that use just the tokens).
|
|
250
|
-
- New `shell.css` (imported by `index.css`): styles for these parts with `scalio-*` classes. No visual change for
|
|
251
|
-
apps that do not use the components.
|
|
252
|
-
|
|
253
|
-
## 0.2.0
|
|
254
|
-
- Dark mode colours from Scalio Rosetta: sidebar and other `bg-muted` areas are now darker than the page
|
|
255
|
-
(before: lighter grey), primary blue in dark mode `223 100% 45%` (before: brighter `223 85% 62%`), card,
|
|
256
|
-
border and input slightly adjusted. Light mode unchanged.
|
|
257
|
-
|
|
258
|
-
## 0.1.2
|
|
259
|
-
- Published via npm trusted publishing (GitHub Actions OIDC), no npm token. Content unchanged.
|
|
260
|
-
|
|
261
|
-
## 0.1.1
|
|
262
|
-
- Published publicly on npmjs.com instead of GitHub Packages (no token needed to install). Content unchanged.
|
|
263
|
-
|
|
264
|
-
## 0.1.0
|
|
265
|
-
- First version: Control Room tokens (light/dark, taken 1:1 from Scalio Nexus), Tailwind preset, self-hosted
|
|
266
|
-
fonts Chivo / IBM Plex Sans / JetBrains Mono. Compiled against Nexus it gives the same CSS as Nexus's own config.
|