feat: govern tenant appearance overrides

This commit is contained in:
2026-08-20 10:50:55 +02:00
parent c5119ab868
commit c558621550
6 changed files with 103 additions and 9 deletions
+5 -1
View File
@@ -50,6 +50,9 @@ export type TenantSettingsItem = {
system_appearance_palette_locked: boolean;
effective_appearance_palette: UserUiPalette;
effective_appearance_source: "tenant" | "system" | "tenant_lock" | "system_lock";
appearance_custom_overrides_allowed: boolean | null;
system_appearance_custom_overrides_allowed: boolean;
effective_appearance_custom_overrides_allowed: boolean;
settings: Record<string, unknown>;
};
@@ -61,7 +64,7 @@ export type TenantSettingsDeltaSections = Partial<{
"available_languages" | "system_enabled_language_codes" | "enabled_language_codes"
>;
navigation: TenantSettingsItem["navigation"];
appearance: Pick<TenantSettingsItem, "appearance_palette" | "appearance_palette_locked" | "system_appearance_palette" | "system_appearance_palette_locked" | "effective_appearance_palette" | "effective_appearance_source">;
appearance: Pick<TenantSettingsItem, "appearance_palette" | "appearance_palette_locked" | "system_appearance_palette" | "system_appearance_palette_locked" | "effective_appearance_palette" | "effective_appearance_source" | "appearance_custom_overrides_allowed" | "system_appearance_custom_overrides_allowed" | "effective_appearance_custom_overrides_allowed">;
settings: TenantSettingsItem["settings"];
}>;
@@ -161,6 +164,7 @@ export function updateTenantSettings(
navigation?: NavigationPreferences | null;
appearance_palette?: UserUiPalette | null;
appearance_palette_locked?: boolean;
appearance_custom_overrides_allowed?: boolean | null;
}
): Promise<TenantSettingsItem> {
return apiFetch(settings, "/api/v1/admin/tenant/settings", {
@@ -51,7 +51,10 @@ const fallback: TenantSettingsItem = {
system_appearance_palette: "default",
system_appearance_palette_locked: false,
effective_appearance_palette: "default",
effective_appearance_source: "system"
effective_appearance_source: "system",
appearance_custom_overrides_allowed: null,
system_appearance_custom_overrides_allowed: false,
effective_appearance_custom_overrides_allowed: false
};
export default function TenantSettingsPanel({
@@ -76,6 +79,11 @@ export default function TenantSettingsPanel({
const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks();
const defaultLocaleOptions = localeOptions(draft.default_locale, draft.enabled_language_codes);
const dirty = tenantSettingsDraftKey(draft) !== tenantSettingsDraftKey(savedDraft);
const customOverridesEffectivelyAllowed =
draft.system_appearance_custom_overrides_allowed
&& draft.appearance_custom_overrides_allowed !== false
&& !draft.system_appearance_palette_locked
&& !draft.appearance_palette_locked;
const saveDisabledReason = tenantMutationDisabledReason({
busy,
permitted: canWrite,
@@ -127,7 +135,8 @@ export default function TenantSettingsPanel({
enabled_language_codes: draft.enabled_language_codes,
navigation: draft.navigation,
appearance_palette: draft.appearance_palette,
appearance_palette_locked: draft.appearance_palette_locked
appearance_palette_locked: draft.appearance_palette_locked,
appearance_custom_overrides_allowed: draft.appearance_custom_overrides_allowed
});
setDraft(saved);
setSavedDraft(saved);
@@ -230,10 +239,36 @@ export default function TenantSettingsPanel({
help={!canWritePolicy ? "i18n:govoplan-tenancy.appearance_lock_policy_permission" : draft.system_appearance_palette_locked ? "i18n:govoplan-tenancy.system_palette_is_locked" : undefined}
label="i18n:govoplan-tenancy.lock_tenant_palette"
/>
<FormField
label="i18n:govoplan-tenancy.custom_overrides_policy"
help={!canWritePolicy ? "i18n:govoplan-tenancy.appearance_policy_permission" : "i18n:govoplan-tenancy.custom_overrides_policy_help"}
>
<select
value={draft.appearance_custom_overrides_allowed === null ? "inherit" : draft.appearance_custom_overrides_allowed ? "allow" : "block"}
disabled={!canWrite || !canWritePolicy || busy}
onChange={(event) => {
const appearance_custom_overrides_allowed = event.target.value === "inherit" ? null : event.target.value === "allow";
setDraft({
...draft,
appearance_custom_overrides_allowed,
effective_appearance_custom_overrides_allowed:
draft.system_appearance_custom_overrides_allowed
&& appearance_custom_overrides_allowed !== false
&& !draft.system_appearance_palette_locked
&& !draft.appearance_palette_locked
});
}}
>
<option value="inherit">i18n:govoplan-tenancy.inherit_system_policy</option>
<option value="allow" disabled={!draft.system_appearance_custom_overrides_allowed}>i18n:govoplan-tenancy.allow_for_tenant_users</option>
<option value="block">i18n:govoplan-tenancy.block_for_tenant_users</option>
</select>
</FormField>
<AppearancePalettePreview palette={draft.system_appearance_palette_locked ? draft.system_appearance_palette : draft.appearance_palette ?? draft.system_appearance_palette} />
<DescriptionList variant="inline">
<div><dt>i18n:govoplan-tenancy.effective_source</dt><dd>{draft.system_appearance_palette_locked ? "i18n:govoplan-tenancy.system_lock" : draft.appearance_palette ? "i18n:govoplan-tenancy.tenant_default" : "i18n:govoplan-tenancy.system_default"}</dd></div>
<div><dt>i18n:govoplan-tenancy.user_override</dt><dd>{draft.system_appearance_palette_locked || draft.appearance_palette_locked ? "i18n:govoplan-tenancy.blocked_by_policy" : "i18n:govoplan-tenancy.allowed"}</dd></div>
<div><dt>i18n:govoplan-tenancy.advanced_color_overrides</dt><dd>{customOverridesEffectivelyAllowed ? "i18n:govoplan-tenancy.allowed" : "i18n:govoplan-tenancy.blocked_by_policy"}</dd></div>
</DescriptionList>
</Card>
</div>
@@ -255,7 +290,8 @@ function tenantSettingsDraftKey(item: TenantSettingsItem): string {
enabled_language_codes: item.enabled_language_codes,
navigation: item.navigation,
appearance_palette: item.appearance_palette,
appearance_palette_locked: item.appearance_palette_locked
appearance_palette_locked: item.appearance_palette_locked,
appearance_custom_overrides_allowed: item.appearance_custom_overrides_allowed
});
}
+14
View File
@@ -6,6 +6,13 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-tenancy.tenant_palette_default": "Tenant palette default",
"i18n:govoplan-tenancy.tenant_palette_default_help": "Inherit the system palette or select the default for this tenant.",
"i18n:govoplan-tenancy.appearance_lock_policy_permission": "Policy-write permission is required to change this lock.",
"i18n:govoplan-tenancy.custom_overrides_policy": "Personal color override policy",
"i18n:govoplan-tenancy.custom_overrides_policy_help": "Inherit the system decision, or explicitly allow or block the validated advanced editor for this tenant.",
"i18n:govoplan-tenancy.appearance_policy_permission": "Policy-write permission is required to change this appearance policy.",
"i18n:govoplan-tenancy.inherit_system_policy": "Inherit system policy",
"i18n:govoplan-tenancy.allow_for_tenant_users": "Allow for tenant users",
"i18n:govoplan-tenancy.block_for_tenant_users": "Block for tenant users",
"i18n:govoplan-tenancy.advanced_color_overrides": "Advanced color overrides",
"i18n:govoplan-tenancy.system_palette_is_locked": "The system palette is locked and takes precedence.",
"i18n:govoplan-tenancy.lock_tenant_palette": "Lock the tenant palette",
"i18n:govoplan-tenancy.effective_source": "Effective source",
@@ -118,6 +125,13 @@ export const generatedTranslations: PlatformTranslations = {
"i18n:govoplan-tenancy.tenant_palette_default": "Mandantenstandard für die Farbpalette",
"i18n:govoplan-tenancy.tenant_palette_default_help": "Systempalette übernehmen oder einen Standard für diesen Mandanten auswählen.",
"i18n:govoplan-tenancy.appearance_lock_policy_permission": "Zum Ändern dieser Sperre ist das Recht zum Bearbeiten von Richtlinien erforderlich.",
"i18n:govoplan-tenancy.custom_overrides_policy": "Richtlinie für persönliche Farbanpassungen",
"i18n:govoplan-tenancy.custom_overrides_policy_help": "Die Systementscheidung übernehmen oder den geprüften erweiterten Editor für diesen Mandanten ausdrücklich zulassen oder sperren.",
"i18n:govoplan-tenancy.appearance_policy_permission": "Zum Ändern dieser Darstellungsrichtlinie ist das Recht zum Bearbeiten von Richtlinien erforderlich.",
"i18n:govoplan-tenancy.inherit_system_policy": "Systemrichtlinie übernehmen",
"i18n:govoplan-tenancy.allow_for_tenant_users": "Für Mandantenbenutzer zulassen",
"i18n:govoplan-tenancy.block_for_tenant_users": "Für Mandantenbenutzer sperren",
"i18n:govoplan-tenancy.advanced_color_overrides": "Erweiterte Farbanpassungen",
"i18n:govoplan-tenancy.system_palette_is_locked": "Die Systempalette ist verbindlich und hat Vorrang.",
"i18n:govoplan-tenancy.lock_tenant_palette": "Mandantenpalette verbindlich festlegen",
"i18n:govoplan-tenancy.effective_source": "Wirksame Quelle",