import { useEffect, useMemo, useState, type ReactNode } from "react"; import { AdminSelectionList, ConnectionTree, FieldLabel, LoadingFrame, MailServerSettingsPanel, PolicyLockedHint, PolicyPathHelp, PolicyRow, PolicySourcePath, PolicyTable, StatusBadge, TableActionGroup, ToggleSwitch, hasMailImapSettings, mailImapSettingsPayload, mailServerSecurityOptions, mailSmtpSettingsPayload, mailTextOrNull, mailTransportCredentialsPayload, mergeDeltaRows, normalizeMailServerSecurity, normalizePolicySourcePathItems, useDeltaWatermarks, type ConnectionTreeColumn, type MailServerConnectionTestResult, type MailServerCredentialSettings, type MailServerImapSettings, type MailServerSmtpSettings, type NormalizedPolicySourcePathItem, type PolicySourcePathItem } from "@govoplan/core-webui"; import { Link2, Pencil, Plus, Trash2, Unlink } from "lucide-react"; import type { ApiSettings } from "../../types"; import { bindMailServerCredential, createMailServerCredential, createMailServerEndpoint, createMailServerProfile, deactivateMailServerEndpoint, deactivateMailServerProfile, fetchMailSettingsDelta, getMailProfilePolicy, mailProfilePatternKeys, mailProfilePolicyLimitKeys, listAvailableMailCredentials, updateMailProfilePolicy, testImapSettings, testMailProfileImap, testMailProfileSmtp, testSmtpSettings, unlinkMailServerCredential, updateMailServerCredential, updateMailServerEndpoint, updateMailServerProfile, type MailCredentialEnvelope, type MailCredentialPolicy, type MailImapTestPayload, type MailProfilePatternKey, type MailProfilePatternRules, type MailProfilePolicy, type MailProfilePolicyLimitKey, type MailProfileScope, type MailSecurity, type MailServerEndpoint, type MailServerProfile, type MailServerProfilePayload, type MailServerProfileUpdatePayload, type MailSmtpTestPayload } from "../../api/mail"; import { validateMailPolicy } from "./mailPolicyValidation"; import { Button } from "@govoplan/core-webui"; import { Card } from "@govoplan/core-webui"; import { ConfirmDialog } from "@govoplan/core-webui"; import { Dialog } from "@govoplan/core-webui"; import { DismissibleAlert } from "@govoplan/core-webui"; import { FormField, i18nMessage, useUnsavedDraftGuard } from "@govoplan/core-webui"; import { mailProfileEditTargetInitialSection, mailProfileEditTargetPanelMode, mailProfileEditTargetShowsProfileFields, mailProfileEditTargetShowsSettingsPanel, mailProfileEditTargetVisibleSections, mailProfileCreateCredentialsPayload, type MailProfileEditTarget, type MailProfileProtocol } from "./mailProfileEditorModel"; export type MailProfileTargetOption = { id: string; label: string; secondary?: string | null; }; type ProfileDraft = { name: string; slug: string; description: string; isActive: boolean; inheritToLowerScopes: boolean; serverName: string; serverIsDefault: boolean; serverIsActive: boolean; serverInheritToLowerScopes: boolean; credentialName: string; credentialDescription: string; credentialAllowedModules: string; credentialAllowedServerRefs: string; credentialInheritToLowerScopes: boolean; credentialIsDefault: boolean; smtpHost: string; smtpPort: string; smtpSecurity: MailSecurity; smtpUsername: string; smtpPassword: string; smtpTimeout: string; imapHost: string; imapPort: string; imapSecurity: MailSecurity; imapUsername: string; imapPassword: string; imapSentFolder: string; imapTimeout: string; }; type MailProfileScopeManagerProps = { settings: ApiSettings; scopeType: MailProfileScope; scopeId?: string | null; targetOptions?: MailProfileTargetOption[]; targetLabel?: string; profileTitle?: string; policyTitle?: string; canWriteProfiles: boolean; canManageCredentials: boolean; canWritePolicy: boolean; }; 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 EditingProfile = MailServerProfile | "new" | null; type PolicyFlagValue = "inherit" | "allow" | "deny"; type MailProfileTreeRow = {kind: "profile";id: string;profile: MailServerProfile;} | {kind: "server";id: string;profile: MailServerProfile;protocol: MailProfileProtocol;server: MailServerEndpoint;} | {kind: "credential";id: string;profile: MailServerProfile;protocol: MailProfileProtocol;server: MailServerEndpoint;credential: MailCredentialEnvelope;}; type PendingHierarchyRemoval = | {kind: "server";profile: MailServerProfile;server: MailServerEndpoint;} | {kind: "credential";profile: MailServerProfile;server: MailServerEndpoint;credential: MailCredentialEnvelope;} | null; const securityOptions = mailServerSecurityOptions as readonly MailSecurity[]; const patternLabels: Record = { smtp_hosts: "i18n:govoplan-mail.smtp_hostnames.36eb51d8", imap_hosts: "i18n:govoplan-mail.imap_hostnames.ac9c1d78", 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 MailProfileScopeManager({ settings, scopeType, scopeId = null, targetOptions = [], targetLabel = "i18n:govoplan-mail.target.61ad50a9", profileTitle = "i18n:govoplan-mail.mail_server_profiles.b1726682", policyTitle = "i18n:govoplan-mail.mail_profile_policy.f2ac4b92", canWriteProfiles, canManageCredentials, canWritePolicy }: MailProfileScopeManagerProps) { const [profiles, setProfiles] = useState([]); const [selectedTargetId, setSelectedTargetId] = useState(scopeId || targetOptions[0]?.id || ""); const [editing, setEditing] = useState(null); const [editingTarget, setEditingTarget] = useState({ kind: "create" }); const [deactivating, setDeactivating] = useState(null); const [pendingHierarchyRemoval, setPendingHierarchyRemoval] = useState(null); const [availableCredentials, setAvailableCredentials] = useState([]); const [reuseCredentialId, setReuseCredentialId] = useState(""); const [savedReuseCredentialId, setSavedReuseCredentialId] = useState(""); const [draft, setDraft] = useState(emptyProfileDraft()); const [savedProfileDraftKey, setSavedProfileDraftKey] = useState(profileDraftKey(emptyProfileDraft())); const [loading, setLoading] = useState(true); const [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const [profileEffectivePolicy, setProfileEffectivePolicy] = useState(null); const { getDeltaWatermark, setDeltaWatermark, resetDeltaWatermark } = useDeltaWatermarks(); const requiresTarget = scopeType === "user" || scopeType === "group" || scopeType === "campaign"; const targetSelectionRequired = requiresTarget && !scopeId; const hasSelectableTarget = targetOptions.length > 0; const activeScopeId = scopeId || selectedTargetId || null; const deltaKey = `mail:settings:${scopeType}:${activeScopeId ?? ""}`; const scopeReady = !requiresTarget || Boolean(activeScopeId); const targetEmptyText = i18nMessage("i18n:govoplan-mail.no_value_available", { value0: targetPluralLabel(scopeType, targetLabel) }); const profileDirty = editing !== null && ( profileDraftKey(draft) !== savedProfileDraftKey || reuseCredentialId !== savedReuseCredentialId ); useUnsavedDraftGuard({ dirty: profileDirty, onSave: saveProfile, onDiscard: closeProfileEditor }); useEffect(() => { if (scopeId) { setSelectedTargetId(scopeId); return; } if (targetOptions.length > 0 && !targetOptions.some((option) => option.id === selectedTargetId)) { setSelectedTargetId(targetOptions[0].id); return; } if (targetOptions.length === 0 && selectedTargetId) setSelectedTargetId(""); }, [scopeId, selectedTargetId, targetOptions]); useEffect(() => { resetDeltaWatermark(deltaKey); void loadProfiles(true); }, [deltaKey, scopeReady, settings.accessToken, settings.apiBaseUrl, settings.apiKey, resetDeltaWatermark]); async function loadProfiles(forceFull = false) { setLoading(true); setError(""); if (!scopeReady) { setProfiles([]); setProfileEffectivePolicy(null); resetDeltaWatermark(deltaKey); setLoading(false); return; } try { let nextProfiles = forceFull ? [] : profiles; let nextEffectivePolicy = forceFull ? null : profileEffectivePolicy; let nextWatermark = forceFull ? null : getDeltaWatermark(deltaKey); let hasMore = false; do { const response = await fetchMailSettingsDelta(settings, { scope_type: scopeType, scope_id: activeScopeId, include_inactive: true, since: nextWatermark }); nextProfiles = response.full ? response.profiles : mergeDeltaRows(nextProfiles, response.profiles, response.deleted, (profile) => profile.id, { deletedResourceType: "mail_profile", sort: sortMailProfiles }); if (response.policy) { nextEffectivePolicy = response.policy.effective_policy ?? null; } nextWatermark = response.watermark ?? null; hasMore = response.has_more; } while (hasMore); setProfiles(nextProfiles); setProfileEffectivePolicy(nextEffectivePolicy); setDeltaWatermark(deltaKey, nextWatermark); } catch (err) { setProfiles([]); setProfileEffectivePolicy(null); resetDeltaWatermark(deltaKey); setError(errorMessage(err)); } finally { setLoading(false); } } const scopedProfiles = useMemo( () => profiles.filter((profile) => profileBelongsToEditableScope(profile, scopeType, activeScopeId)), [activeScopeId, profiles, scopeType] ); function openCreate() { const nextDraft = emptyProfileDraft(); setDraft(nextDraft); setSavedProfileDraftKey(profileDraftKey(nextDraft)); setAvailableCredentials([]); setReuseCredentialId(""); setSavedReuseCredentialId(""); setEditingTarget({ kind: "create" }); setEditing("new"); setError(""); setSuccess(""); } function openEdit(profile: MailServerProfile, target: MailProfileEditTarget = { kind: "profile" }) { const nextDraft = profileToDraft(profile, target); setDraft(nextDraft); setSavedProfileDraftKey(profileDraftKey(nextDraft)); setAvailableCredentials([]); setReuseCredentialId(""); setSavedReuseCredentialId(""); setEditingTarget(target); setEditing(profile); setError(""); setSuccess(""); if (target.kind === "credentials" && target.serverId && !target.credentialId) { void loadReusableCredentials(profile.id, target.serverId); } } function openCreateServer(profile: MailServerProfile, protocol: MailProfileProtocol) { openEdit(profile, { kind: "server", protocol }); } function openCreateCredential(profile: MailServerProfile, server: MailServerEndpoint) { openEdit(profile, { kind: "credentials", protocol: server.protocol, serverId: server.id }); } async function loadReusableCredentials(profileId: string, serverId: string) { try { setAvailableCredentials(await listAvailableMailCredentials(settings, profileId, serverId)); } catch (err) { setAvailableCredentials([]); setError(errorMessage(err)); } } function closeProfileEditor() { setEditing(null); const nextDraft = emptyProfileDraft(); setDraft(nextDraft); setSavedProfileDraftKey(profileDraftKey(nextDraft)); setAvailableCredentials([]); setReuseCredentialId(""); setSavedReuseCredentialId(""); setEditingTarget({ kind: "create" }); } async function saveProfile(): Promise { if (!editing || !scopeReady) return false; setBusy(true); setError(""); setSuccess(""); try { if (editing === "new") { const payload = createProfilePayload(draft, scopeType, activeScopeId); const created = await createMailServerProfile(settings, payload); setSuccess(i18nMessage("i18n:govoplan-mail.profile_value_created.2a088d8d", { value0: created.name })); } else if (editingTarget.kind === "server") { const serverPayload = { name: draft.serverName.trim(), config: editingTarget.protocol === "smtp" ? smtpServerPayload(draft) : imapServerPayload(draft), inherit_to_lower_scopes: draft.serverInheritToLowerScopes, is_default: draft.serverIsDefault, is_active: draft.serverIsActive }; if (editingTarget.serverId) { await updateMailServerEndpoint(settings, editing.id, editingTarget.serverId, serverPayload); } else { await createMailServerEndpoint(settings, editing.id, { protocol: editingTarget.protocol, ...serverPayload }); } setSuccess(`${draft.serverName.trim()} saved`); } else if (editingTarget.kind === "credentials" && editingTarget.serverId) { if (!editingTarget.credentialId && reuseCredentialId) { const reused = availableCredentials.find((credential) => credential.id === reuseCredentialId); await bindMailServerCredential( settings, editing.id, editingTarget.serverId, reuseCredentialId, draft.credentialIsDefault ); setSuccess(`${reused?.name || "Credential"} linked`); } else { const payload = credentialPayload(draft, editingTarget.protocol, Boolean(editingTarget.credentialId)); if (editingTarget.credentialId) { await updateMailServerCredential( settings, editing.id, editingTarget.serverId, editingTarget.credentialId, payload ); } else { await createMailServerCredential(settings, editing.id, editingTarget.serverId, payload); } setSuccess(`${draft.credentialName.trim()} saved`); } } else { const payload = updateProfilePayload(draft); const updated = await updateMailServerProfile(settings, editing.id, payload); setSuccess(i18nMessage("i18n:govoplan-mail.profile_value_updated.fdbad0ea", { value0: updated.name })); } closeProfileEditor(); await loadProfiles(); return true; } catch (err) { setError(errorMessage(err)); return false; } finally { setBusy(false); } } async function deactivateProfile() { if (!deactivating) return; setBusy(true); setError(""); setSuccess(""); try { await deactivateMailServerProfile(settings, deactivating.id); setSuccess(i18nMessage("i18n:govoplan-mail.profile_value_deactivated.fa7fcc1a", { value0: deactivating.name })); setDeactivating(null); await loadProfiles(); } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } async function removeHierarchyItem() { if (!pendingHierarchyRemoval) return; setBusy(true); setError(""); setSuccess(""); try { if (pendingHierarchyRemoval.kind === "server") { await deactivateMailServerEndpoint( settings, pendingHierarchyRemoval.profile.id, pendingHierarchyRemoval.server.id ); setSuccess(`${pendingHierarchyRemoval.server.name} deactivated`); } else { await unlinkMailServerCredential( settings, pendingHierarchyRemoval.profile.id, pendingHierarchyRemoval.server.id, pendingHierarchyRemoval.credential.id ); setSuccess(`${pendingHierarchyRemoval.credential.name} unlinked`); } setPendingHierarchyRemoval(null); await loadProfiles(); } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } const treeRows = useMemo( () => scopedProfiles.map((profile) => ({ kind: "profile", id: `profile:${profile.id}`, profile })), [scopedProfiles] ); const columns = useMemo[]>(() => [ { id: "profile", header: "i18n:govoplan-mail.server_credential.7fb9a24e", width: "minmax(260px, 1.4fr)", render: (row) => row.kind === "profile" ?
{row.profile.name}
{row.profile.slug} · {scopeLabel(row.profile)}
: row.kind === "server" ? : }, { id: "transport", header: "i18n:govoplan-mail.transport.c10d76c9", width: "minmax(220px, 1fr)", render: (row) => row.kind === "profile" ? {transportLabel(row.profile.smtp)}{row.profile.imap ? i18nMessage("i18n:govoplan-mail.value.48afe802", { value0: transportLabel(row.profile.imap) }) : ""} : row.kind === "server" ? : {String(row.credential.public_data?.username || "No username")} }, { id: "policy", header: "i18n:govoplan-mail.policy.bb9cf141", width: "minmax(150px, 0.8fr)", render: (row) => row.kind === "profile" ? scopeLabel(row.profile) : row.kind === "server" ? (row.server.inherit_to_lower_scopes ? "Inherited by lower scopes" : "Current scope only") : credentialAvailabilityLabel(row.credential) }, { id: "status", header: "i18n:govoplan-mail.status.bae7d5be", width: "110px", render: (row) => row.kind === "profile" ? : row.kind === "server" ? : }], [profileEffectivePolicy]); function mailProfileChildren(row: MailProfileTreeRow): MailProfileTreeRow[] { if (row.kind === "profile") { return (row.profile.servers ?? []).map((server) => ({ kind: "server", id: `server:${server.id}`, profile: row.profile, protocol: server.protocol, server })); } if (row.kind === "server") { return row.server.credentials.map((credential) => ({ kind: "credential", id: `credential:${row.server.id}:${credential.id}`, profile: row.profile, protocol: row.protocol, server: row.server, credential })); } return []; } function renderMailProfileActions(row: MailProfileTreeRow) { if (row.kind === "server") { const label = `Edit ${row.protocol.toUpperCase()} server`; return , disabled: !canWriteProfiles || !canManageCredentials || busy, onClick: () => openCreateCredential(row.profile, row.server) }, { id: "edit-server", label, icon: , disabled: !canWriteProfiles || busy, onClick: () => openEdit(row.profile, { kind: "server", protocol: row.protocol, serverId: row.server.id }) }, { id: "deactivate-server", label: `Deactivate ${row.server.name}`, icon: , variant: "danger", applicable: row.server.is_active, disabled: !canWriteProfiles || busy, onClick: () => setPendingHierarchyRemoval({ kind: "server", profile: row.profile, server: row.server }) } ]} />; } if (row.kind === "credential") { return , disabled: !canWriteProfiles || !canManageCredentials || busy, onClick: () => openEdit(row.profile, { kind: "credentials", protocol: row.protocol, serverId: row.server.id, credentialId: row.credential.id }) }, { id: "unlink-credentials", label: `Unlink ${row.credential.name}`, icon: , variant: "danger", disabled: !canWriteProfiles || !canManageCredentials || busy, onClick: () => setPendingHierarchyRemoval({ kind: "credential", profile: row.profile, server: row.server, credential: row.credential }) } ]} />; } const deactivationDeletesCredentials = Boolean( row.profile.smtp_password_configured || row.profile.imap_password_configured ); return , disabled: !canWriteProfiles || busy, onClick: () => openEdit(row.profile) }, { id: "add-smtp", label: "Add SMTP server", icon: , disabled: !canWriteProfiles || busy, onClick: () => openCreateServer(row.profile, "smtp") }, { id: "add-imap", label: "Add IMAP server", icon: , disabled: !canWriteProfiles || busy, onClick: () => openCreateServer(row.profile, "imap") }, { id: "deactivate", label: i18nMessage("i18n:govoplan-mail.deactivate_value.a276a667", { value0: row.profile.name }), icon: , variant: "danger", applicable: row.profile.is_active, disabled: !canWriteProfiles || busy || (deactivationDeletesCredentials && !canManageCredentials), onClick: () => setDeactivating(row.profile) } ]} />; } return (
{targetSelectionRequired &&
} {scopeReady && <>
}> row.id} getChildren={mailProfileChildren} renderActions={renderMailProfileActions} emptyText="i18n:govoplan-mail.no_profiles_in_this_scope.302b21d8" /> } {error && {error}} {success && {success}} !busy && closeProfileEditor()} className="admin-dialog admin-dialog-wide mail-profile-dialog" closeDisabled={busy} footer={<>}> setDeactivating(null)} onConfirm={() => void deactivateProfile()} /> setPendingHierarchyRemoval(null)} onConfirm={() => void removeHierarchyItem()} /> ); } 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 [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 [busy, setBusy] = useState(false); const [error, setError] = useState(""); const [success, setSuccess] = useState(""); const [profileEffectivePolicy, setProfileEffectivePolicy] = useState(null); 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() { setError(""); setSuccess(""); 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 ?? []); } catch (err) { setPolicy(blankPolicy); setSavedPolicyKey(policyDraftKey(blankPolicy)); setEffectivePolicy(null); setParentPolicy(null); setEffectivePolicySources([]); setError(errorMessage(err)); } finally { setLoading(false); } } async function savePolicy(): Promise { if (!scopeReady) return false; setBusy(true); setError(""); setSuccess(""); 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"); await onSaved?.(); 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 || !canWrite || !scopeReady; 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 ( }>
{description &&

{description}

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

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.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(i18nMessage("i18n:govoplan-mail.whitelist_value.d4cc3755", { value0: key }) as MailProfilePolicyLimitKey, "i18n:govoplan-mail.whitelist.53c2ad30")} {lowerLevelLimitToggle(i18nMessage("i18n:govoplan-mail.blacklist_value.556334d0", { value0: 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 ProfileForm({ settings, draft, setDraft, editing, busy, canWrite, canManageCredentials, effectivePolicy, editTarget, availableCredentials, reuseCredentialId, setReuseCredentialId }: { settings: ApiSettings; draft: ProfileDraft; setDraft: (draft: ProfileDraft) => void; editing: EditingProfile; busy: boolean; canWrite: boolean; canManageCredentials: boolean; effectivePolicy?: MailProfilePolicy | null; editTarget: MailProfileEditTarget; availableCredentials: MailCredentialEnvelope[]; reuseCredentialId: string; setReuseCredentialId: (credentialId: string) => void; }) { const [smtpTestResult, setSmtpTestResult] = useState(null); const [imapTestResult, setImapTestResult] = useState(null); const [mailActionState, setMailActionState] = useState<"smtp" | "imap" | null>(null); const disabled = busy || !canWrite; const credentialDisabled = disabled || !canManageCredentials; const existingProfile = editing !== "new" ? editing : null; const initialSection = mailProfileEditTargetInitialSection(editTarget); const settingsPanelMode = mailProfileEditTargetPanelMode(editTarget); const visibleSections = mailProfileEditTargetVisibleSections(editTarget); const showProfileFields = mailProfileEditTargetShowsProfileFields(editTarget); const showSettingsPanel = mailProfileEditTargetShowsSettingsPanel(editTarget); const draftHasImap = hasDraftImapSettings(draft); const selectedServerId = editTarget.kind === "server" || editTarget.kind === "credentials" ? editTarget.serverId : undefined; const selectedCredentialId = editTarget.kind === "credentials" ? editTarget.credentialId : undefined; const selectedCredential = selectedCredentialId && existingProfile ? existingProfile.servers?.flatMap((server) => server.credentials).find((credential) => credential.id === selectedCredentialId) : null; const useSavedSmtpTest = Boolean( existingProfile && (editTarget.kind === "server" || editTarget.kind === "credentials") && editTarget.protocol === "smtp" && selectedServerId && !draft.smtpPassword ); const useSavedImapTest = Boolean( existingProfile && (editTarget.kind === "server" || editTarget.kind === "credentials") && editTarget.protocol === "imap" && selectedServerId && !draft.imapPassword ); const creatingCredential = editTarget.kind === "credentials" && !editTarget.credentialId; const creatingNewCredential = creatingCredential && !reuseCredentialId; const policyMessages = useMemo(() => validateMailPolicy(effectivePolicy, { smtpHost: draft.smtpHost, imapHost: draft.imapHost }), [draft.imapHost, draft.smtpHost, effectivePolicy]); useEffect(() => { setSmtpTestResult(null); setImapTestResult(null); setMailActionState(null); }, [editing, editTarget]); function patchSmtpSettings(patch: Partial) { setDraft({ ...draft, smtpHost: patch.host !== undefined ? String(patch.host ?? "") : draft.smtpHost, smtpPort: patch.port !== undefined ? String(patch.port ?? "") : draft.smtpPort, smtpSecurity: patch.security !== undefined ? readSecurity(String(patch.security || "starttls"), "starttls") : draft.smtpSecurity, smtpTimeout: patch.timeout_seconds !== undefined ? String(patch.timeout_seconds ?? "") : draft.smtpTimeout }); } function patchImapSettings(patch: Partial) { setDraft({ ...draft, imapHost: patch.host !== undefined ? String(patch.host ?? "") : draft.imapHost, imapPort: patch.port !== undefined ? String(patch.port ?? "") : draft.imapPort, imapSecurity: patch.security !== undefined ? readSecurity(String(patch.security || "tls"), "tls") : draft.imapSecurity, imapSentFolder: patch.sent_folder !== undefined ? String(patch.sent_folder ?? "") : draft.imapSentFolder, imapTimeout: patch.timeout_seconds !== undefined ? String(patch.timeout_seconds ?? "") : draft.imapTimeout }); } function patchSmtpCredentials(patch: Partial) { setDraft({ ...draft, smtpUsername: patch.username !== undefined ? String(patch.username ?? "") : draft.smtpUsername, smtpPassword: patch.password !== undefined ? String(patch.password ?? "") : draft.smtpPassword }); } function patchImapCredentials(patch: Partial) { setDraft({ ...draft, imapUsername: patch.username !== undefined ? String(patch.username ?? "") : draft.imapUsername, imapPassword: patch.password !== undefined ? String(patch.password ?? "") : draft.imapPassword }); } async function runSmtpTest() { setMailActionState("smtp"); setSmtpTestResult(null); try { setSmtpTestResult(useSavedSmtpTest && existingProfile ? await testMailProfileSmtp(settings, existingProfile.id, selectedServerId, selectedCredentialId) : await testSmtpSettings(settings, rawSmtpPayload(draft, false))); } catch (err) { setSmtpTestResult({ ok: false, protocol: "smtp", message: errorMessage(err), details: {} }); } finally { setMailActionState(null); } } async function runImapTest() { if (!draftHasImap) return; setMailActionState("imap"); setImapTestResult(null); try { setImapTestResult(useSavedImapTest && existingProfile ? await testMailProfileImap(settings, existingProfile.id, selectedServerId, selectedCredentialId) : await testImapSettings(settings, rawImapPayload(draft, false))); } catch (err) { setImapTestResult({ ok: false, protocol: "imap", message: errorMessage(err), details: {} }); } finally { setMailActionState(null); } } return (
{showProfileFields &&
setDraft({ ...draft, name: event.target.value })} /> setDraft({ ...draft, slug: event.target.value })} placeholder="i18n:govoplan-mail.generated_from_name.33d69a91" />