import { useEffect, useMemo, useState, type ReactNode } from "react"; import { ActionToolbar, ActionBlockerHint, AdminSelectionList, DocumentationHelpLink, FieldLabel, LoadingFrame, PolicyLockedHint, PolicyPathHelp, PolicyRow, PolicySourcePath, PolicyTable, ToggleSwitch, normalizePolicySourcePathItems, type MailJmapTransportSettings, type NormalizedPolicySourcePathItem, type PolicySourcePathItem } from "@govoplan/core-webui"; import type { ApiSettings } from "../../types"; import { getMailProfilePolicy, mailProfilePatternKeys, mailProfilePolicyLimitKeys, updateMailProfilePolicy, type MailCredentialPolicy, type MailImapTestPayload, type MailProfilePatternKey, type MailProfilePatternRules, type MailProfilePolicy, type MailProfilePolicyLimitKey, type MailProfileScope, type MailServerProfile, type MailSmtpTestPayload } from "../../api/mail"; import { Button } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui"; import { DismissibleAlert } from "@govoplan/core-webui"; import { i18nMessage, usePlatformLanguage, useUnsavedDraftGuard } from "@govoplan/core-webui"; type MailProfilePolicyEditorProps = { settings: ApiSettings; scopeType: MailProfileScope; scopeId?: string | null; campaignId?: string | null; profiles: MailServerProfile[]; ownerUserId?: string | null; ownerGroupId?: string | null; canWrite: boolean; locked?: boolean; title?: string; description?: string; onSaved?: () => void | Promise; }; type PolicyFlagValue = "inherit" | "allow" | "deny"; export const MAIL_PROFILE_DOCUMENTATION = { topicId: "mail.profiles-and-policy", documentationType: "admin" } as const; const patternLabels: Record = { smtp_hosts: "i18n:govoplan-mail.smtp_hostnames.36eb51d8", imap_hosts: "i18n:govoplan-mail.imap_hostnames.ac9c1d78", jmap_hosts: "JMAP hostnames", envelope_senders: "i18n:govoplan-mail.envelope_senders.269065cd", from_headers: "i18n:govoplan-mail.from_headers.b3ea473b", recipient_domains: "i18n:govoplan-mail.recipient_domains.cb9b7b44" }; const blankPolicy: MailProfilePolicy = { allowed_profile_ids: [], allow_user_profiles: null, allow_group_profiles: null, allow_campaign_profiles: null, smtp_credentials: {}, imap_credentials: {}, whitelist: {}, blacklist: {}, allow_lower_level_limits: {} }; export function MailProfilePolicyEditor({ settings, scopeType, scopeId = null, campaignId = null, profiles, ownerUserId = null, ownerGroupId = null, canWrite, locked = false, title = "i18n:govoplan-mail.mail_profile_policy.f2ac4b92", description = "i18n:govoplan-mail.allowed_profiles_and_wildcard_rules_for_this_sco.0f82b3e4", onSaved }: MailProfilePolicyEditorProps) { const { translateText } = usePlatformLanguage(); const [policy, setPolicy] = useState(blankPolicy); const [effectivePolicy, setEffectivePolicy] = useState(null); const [parentPolicy, setParentPolicy] = useState(null); const [effectivePolicySources, setEffectivePolicySources] = useState([]); const [savedPolicyKey, setSavedPolicyKey] = useState(policyDraftKey(blankPolicy)); const [loading, setLoading] = useState(false); const [policyLoaded, setPolicyLoaded] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const [refreshWarning, setRefreshWarning] = useState(""); const requiresTarget = scopeType === "user" || scopeType === "group" || scopeType === "campaign"; const scopeReady = !requiresTarget || Boolean(scopeId); const policyDirty = scopeReady && policyDraftKey(policy) !== savedPolicyKey; useUnsavedDraftGuard({ dirty: policyDirty, onSave: savePolicy, onDiscard: () => setPolicy(JSON.parse(savedPolicyKey) as MailProfilePolicy) }); useEffect(() => {void loadPolicy();}, [settings.accessToken, settings.apiBaseUrl, settings.apiKey, scopeType, scopeId, campaignId]); async function loadPolicy() { setPolicyLoaded(false); setError(""); setSuccess(""); setRefreshWarning(""); if (!scopeReady) { setPolicy(blankPolicy); setSavedPolicyKey(policyDraftKey(blankPolicy)); setEffectivePolicy(null); setParentPolicy(null); setEffectivePolicySources([]); return; } setLoading(true); try { const response = await getMailProfilePolicy(settings, scopeType, scopeId, campaignId); const loadedPolicy = normalizePolicy(response.policy); setPolicy(loadedPolicy); setSavedPolicyKey(policyDraftKey(loadedPolicy)); setEffectivePolicy(response.effective_policy ? normalizePolicy(response.effective_policy) : null); setParentPolicy(response.parent_policy ? normalizePolicy(response.parent_policy) : null); setEffectivePolicySources(response.effective_policy_sources ?? []); setPolicyLoaded(true); } catch (err) { setPolicy(blankPolicy); setSavedPolicyKey(policyDraftKey(blankPolicy)); setEffectivePolicy(null); setParentPolicy(null); setEffectivePolicySources([]); setError(errorMessage(err)); } finally { setLoading(false); } } async function savePolicy(): Promise { if (!scopeReady || !canWrite || locked || loading || !policyLoaded || busy) return false; setBusy(true); setError(""); setSuccess(""); setRefreshWarning(""); try { const response = await updateMailProfilePolicy(settings, scopeType, normalizePolicyForSave(policy, parentPolicy, scopeType), scopeId); const savedPolicy = normalizePolicy(response.policy); setPolicy(savedPolicy); setSavedPolicyKey(policyDraftKey(savedPolicy)); setEffectivePolicy(response.effective_policy ? normalizePolicy(response.effective_policy) : null); setParentPolicy(response.parent_policy ? normalizePolicy(response.parent_policy) : null); setEffectivePolicySources(response.effective_policy_sources ?? []); setSuccess("i18n:govoplan-mail.mail_profile_policy_saved.666847bf"); try { await onSaved?.(); } catch (refreshError) { // The policy write has committed. A dependent refresh must never turn // this into a failed save or encourage replaying the accepted write. setRefreshWarning(i18nMessage("i18n:govoplan-mail.policy_saved_refresh_failed", { value0: errorMessage(refreshError) })); } return true; } catch (err) { setError(errorMessage(err)); return false; } finally { setBusy(false); } } const candidateProfiles = useMemo( () => profileCandidatesForPolicy(profiles, scopeType, scopeId, ownerUserId, ownerGroupId), [ownerGroupId, ownerUserId, profiles, scopeId, scopeType] ); const isSystem = scopeType === "system"; const displayPolicy = useMemo(() => isSystem ? concreteSystemPolicy(policy) : policy, [isSystem, policy]); const selectedProfileIds = new Set(policy.allowed_profile_ids ?? []); const disabled = locked || busy || loading || !policyLoaded || !canWrite || !scopeReady; const policySaveBlocker = mailPolicyDisabledReason(locked, canWrite, scopeReady, loading || !policyLoaded, busy, policyDirty); const parentAllowedProfileIds = parentPolicy?.allowed_profile_ids?.length ? new Set(parentPolicy.allowed_profile_ids) : null; const parentBlocksUserProfiles = parentPolicy?.allow_user_profiles === false; const parentBlocksGroupProfiles = parentPolicy?.allow_group_profiles === false; const parentBlocksCampaignProfiles = parentPolicy?.allow_campaign_profiles === false; const showAllowColumn = scopeType !== "campaign"; const showEffectiveColumn = !isSystem; const profileAllowListLocked = !parentAllowsMailLimit("allowed_profile_ids"); const blockedProfileDefinitions = [ parentBlocksUserProfiles ? "user" : "", parentBlocksGroupProfiles ? "group" : "", parentBlocksCampaignProfiles ? "i18n:govoplan-mail.campaign_local_settings.920ecb62" : ""]. filter(Boolean).join(", "); const effectivePolicyPath = effectivePolicySources.length > 0 ? effectivePolicySources : mailPolicySourcePath(scopeType); function patchPolicy(patch: Partial) { setPolicy((current) => normalizePolicy({ ...current, ...patch })); } function setFlag(key: "allow_user_profiles" | "allow_group_profiles" | "allow_campaign_profiles", value: PolicyFlagValue) { patchPolicy({ [key]: flagToBoolean(value) }); } function setPattern(kind: "whitelist" | "blacklist", key: MailProfilePatternKey, text: string) { const nextRules = { ...(policy[kind] ?? {}) }; const parsed = parsePatternList(text); if (parsed.length > 0) nextRules[key] = parsed;else delete nextRules[key]; patchPolicy({ [kind]: nextRules }); } function parentAllowsMailLimit(key: MailProfilePolicyLimitKey): boolean { return !parentPolicy || parentPolicy.allow_lower_level_limits?.[key] !== false; } function localAllowsMailLimit(key: MailProfilePolicyLimitKey): boolean { const localValue = policy.allow_lower_level_limits?.[key]; if (localValue !== undefined) return localValue && parentAllowsMailLimit(key); return parentAllowsMailLimit(key); } function setAllowLowerLevelLimit(key: MailProfilePolicyLimitKey, allowed: boolean) { patchPolicy({ allow_lower_level_limits: { ...(policy.allow_lower_level_limits ?? {}), [key]: allowed } }); } function lowerLevelLimitToggle(key: MailProfilePolicyLimitKey, label: ReactNode = "i18n:govoplan-mail.allow_override.ffa6e9a0"): ReactNode | undefined { if (!showAllowColumn) return undefined; const parentLocked = !parentAllowsMailLimit(key); return ( setAllowLowerLevelLimit(key, checked)} label={label} />); } return ( }>
{(locked || !canWrite || !scopeReady) && } {description &&

{description}

} {error && {error}} {success && {success}} {refreshWarning && {refreshWarning}}

i18n:govoplan-mail.profile_allow_list.507dfe6c

{lowerLevelLimitToggle("allowed_profile_ids")}

{selectedProfileIds.size === 0 ? "i18n:govoplan-mail.no_local_profile_allow_list_is_set.31072e39" : i18nMessage("i18n:govoplan-mail.value_profile_s_allowed_by_this_scope.6fe9ba44", { value0: selectedProfileIds.size })}

({ id: profile.id, label: profile.name, description: `${scopeLabel(profile)} ยท ${transportLabel(profile.smtp)}`, disabled: disabled || profileAllowListLocked || Boolean(parentAllowedProfileIds && !parentAllowedProfileIds.has(profile.id) && !selectedProfileIds.has(profile.id)) }))} selected={[...selectedProfileIds]} onChange={(allowedProfileIds) => patchPolicy({ allowed_profile_ids: [...allowedProfileIds].sort() })} emptyText="i18n:govoplan-mail.no_profiles_are_visible_for_this_policy_scope.1ec7bd85" /> {parentAllowedProfileIds &&

i18n:govoplan-mail.an_ancestor_allow_list_limits_selectable_profile.499ec179 {parentAllowedProfileIds.size} i18n:govoplan-mail.profile_s.742e9200

}

i18n:govoplan-mail.lower_level_mail_definitions.d39a0a1d

setFlag("allow_user_profiles", value)} />} effective={showEffectiveColumn ? effectiveBooleanLabel(effectivePolicy?.allow_user_profiles, effectivePolicy) : undefined} allowControl={showAllowColumn ?
{lowerLevelLimitToggle("allow_user_profiles")}
: undefined} effectiveHelp={showEffectiveColumn ? mailBooleanPolicyPathHelp("allow_user_profiles", effectivePolicyPath) : undefined} /> setFlag("allow_group_profiles", value)} />} effective={showEffectiveColumn ? effectiveBooleanLabel(effectivePolicy?.allow_group_profiles, effectivePolicy) : undefined} allowControl={showAllowColumn ?
{lowerLevelLimitToggle("allow_group_profiles")}
: undefined} effectiveHelp={showEffectiveColumn ? mailBooleanPolicyPathHelp("allow_group_profiles", effectivePolicyPath) : undefined} /> setFlag("allow_campaign_profiles", value)} />} effective={showEffectiveColumn ? effectiveBooleanLabel(effectivePolicy?.allow_campaign_profiles, effectivePolicy) : undefined} allowControl={showAllowColumn ?
{lowerLevelLimitToggle("allow_campaign_profiles")}
: undefined} effectiveHelp={showEffectiveColumn ? mailBooleanPolicyPathHelp("allow_campaign_profiles", effectivePolicyPath) : undefined} />
{blockedProfileDefinitions && i18n:govoplan-mail.explicit_allow_is_unavailable_for.8d05fd4a {blockedProfileDefinitions} i18n:govoplan-mail.because_an_ancestor_policy_blocks_those_definiti.5de3e30d}

i18n:govoplan-mail.credential_selection_policy

i18n:govoplan-mail.credential_selection_policy_help

{(["smtp", "imap"] as const).map((protocol) => { const key = `${protocol}_credentials` as const; const limitKey = `${key}.inherit` as MailProfilePolicyLimitKey; const parentLocked = !parentAllowsMailLimit(limitKey); const local = displayPolicy[key]?.inherit; return patchPolicy({ [key]: { inherit: event.target.value === "inherit" ? null : event.target.value === "profile" } })}> {(!isSystem || parentLocked) && } } effective={showEffectiveColumn ? effectivePolicy ? credentialSelectionLabel(effectivePolicy[key]?.inherit) : "i18n:govoplan-mail.loading.b04ba49f" : undefined} effectiveHelp={showEffectiveColumn ? : undefined} allowControl={showAllowColumn ?
{lowerLevelLimitToggle(limitKey)}
: undefined} />; })}
{(["smtp_credentials.inherit", "imap_credentials.inherit"] as const).some((key) => !parentAllowsMailLimit(key)) && i18n:govoplan-mail.credential_policy_parent_locked}

i18n:govoplan-mail.wildcard_rules.54fb3fc0

i18n:govoplan-mail.policy_target.a19dcee9 i18n:govoplan-mail.whitelist.53c2ad30 i18n:govoplan-mail.blacklist.7b2dd04c {showAllowColumn && i18n:govoplan-mail.lower_levels.940821ee}
{mailProfilePatternKeys.map((key) =>
{patternLabels[key]}
setPattern("whitelist", key, text)} /> setPattern("blacklist", key, text)} /> {showAllowColumn &&
{lowerLevelLimitToggle(`whitelist.${key}` as MailProfilePolicyLimitKey, "i18n:govoplan-mail.whitelist.53c2ad30")} {lowerLevelLimitToggle(`blacklist.${key}` as MailProfilePolicyLimitKey, "i18n:govoplan-mail.blacklist.7b2dd04c")}
}
)}
{showEffectiveColumn && effectivePolicy &&

i18n:govoplan-mail.policy_path.1ba91ee5

i18n:govoplan-mail.effective_values_are_shown_in_the_table_rows_abo.b27b900d

}
); } function PolicyFlagControl({ value, disabled, includeInherit = true, inheritOnly = false, allowDisabled = false, onChange }: {value: PolicyFlagValue;disabled: boolean;includeInherit?: boolean;inheritOnly?: boolean;allowDisabled?: boolean;onChange: (value: PolicyFlagValue) => void;}) { const selectedValue = inheritOnly ? "inherit" : value; return ( ); } function PatternTextareaControl({ value, disabled, onChange }: {value: string;disabled: boolean;onChange: (value: string) => void;}) { return