feat: link mailbox self-service setup guidance
This commit is contained in:
@@ -183,6 +183,10 @@ const exactTranslations: Record<string, Translation> = {
|
|||||||
"前往个人中心": { "zh-TW": "前往個人中心", en: "Go to profile" },
|
"前往个人中心": { "zh-TW": "前往個人中心", en: "Go to profile" },
|
||||||
"请前往邮箱管理,创建、申请或联系管理员分配邮箱。": { "zh-TW": "請前往信箱管理,建立、申請或聯絡管理員分配信箱。", en: "Open mailbox management to create, request, or ask an administrator to assign a mailbox." },
|
"请前往邮箱管理,创建、申请或联系管理员分配邮箱。": { "zh-TW": "請前往信箱管理,建立、申請或聯絡管理員分配信箱。", en: "Open mailbox management to create, request, or ask an administrator to assign a mailbox." },
|
||||||
"前往邮箱管理": { "zh-TW": "前往信箱管理", en: "Go to mailbox management" },
|
"前往邮箱管理": { "zh-TW": "前往信箱管理", en: "Go to mailbox management" },
|
||||||
|
"提示:尚未选择开放域名。请在“后台管理 → 系统设置 → 邮件”中至少勾选一个已启用域名。": { "zh-TW": "提示:尚未選擇開放網域。請在「後台管理 → 系統設定 → 郵件」中至少勾選一個已啟用網域。", en: "No domain is open for mailbox requests. Open Admin → System settings → Mail and select at least one active domain." },
|
||||||
|
"提示:账号自助申请邮箱未开启。请在“后台管理 → 系统设置 → 邮件”中开启,并勾选开放域名。": { "zh-TW": "提示:帳號自助申請信箱尚未開啟。請在「後台管理 → 系統設定 → 郵件」中開啟,並勾選開放網域。", en: "Mailbox self-service is disabled. Enable it under Admin → System settings → Mail, then select the available domains." },
|
||||||
|
"提示:当前账号暂不可创建新邮箱,请联系管理员开启账号自助申请邮箱。": { "zh-TW": "提示:目前帳號暫時無法建立新信箱,請聯絡管理員開啟帳號自助申請信箱。", en: "This account cannot create a mailbox. Ask an administrator to enable mailbox self-service." },
|
||||||
|
"前往设置": { "zh-TW": "前往設定", en: "Open settings" },
|
||||||
"无邮箱前台权限": { "zh-TW": "無信箱前台權限", en: "No mailbox access" },
|
"无邮箱前台权限": { "zh-TW": "無信箱前台權限", en: "No mailbox access" },
|
||||||
"当前账号未开启邮箱前台访问权限。": { "zh-TW": "目前帳號未開啟信箱前台存取權限。", en: "Mailbox access is not enabled for this account." },
|
"当前账号未开启邮箱前台访问权限。": { "zh-TW": "目前帳號未開啟信箱前台存取權限。", en: "Mailbox access is not enabled for this account." },
|
||||||
"无邮件查看权限": { "zh-TW": "無郵件檢視權限", en: "No mail read permission" },
|
"无邮件查看权限": { "zh-TW": "無郵件檢視權限", en: "No mail read permission" },
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { hasAnyPermission, hasPermission } from "@/lib/permissions"
|
|||||||
import type { PermissionKey } from "@/lib/api-types"
|
import type { PermissionKey } from "@/lib/api-types"
|
||||||
|
|
||||||
type Section = "overview" | "users" | "permissionGroups" | "domains" | "mailboxes" | "aliases" | "messages" | "sendAudit" | "settings"
|
type Section = "overview" | "users" | "permissionGroups" | "domains" | "mailboxes" | "aliases" | "messages" | "sendAudit" | "settings"
|
||||||
|
type SettingsTab = "base" | "smtp" | "storage" | "mail" | "externalImap" | "templates" | "security" | "about"
|
||||||
type PendingConfirm = { title: string; description?: string; confirmText: string; onConfirm: () => void }
|
type PendingConfirm = { title: string; description?: string; confirmText: string; onConfirm: () => void }
|
||||||
|
|
||||||
const sectionMeta: Record<Section, { label: string; frontLabel: string; description: string }> = {
|
const sectionMeta: Record<Section, { label: string; frontLabel: string; description: string }> = {
|
||||||
@@ -132,7 +133,7 @@ export function AdminPage() {
|
|||||||
{section === "aliases" && <AliasesSection aliases={aliasItems} domains={domainItems} />}
|
{section === "aliases" && <AliasesSection aliases={aliasItems} domains={domainItems} />}
|
||||||
{section === "messages" && <AdminMessagesSection mailboxes={mailboxItems} systemAdmin={user?.role === "admin"} />}
|
{section === "messages" && <AdminMessagesSection mailboxes={mailboxItems} systemAdmin={user?.role === "admin"} />}
|
||||||
{section === "sendAudit" && <AdminSendAuditSection mailboxes={mailboxItems} />}
|
{section === "sendAudit" && <AdminSendAuditSection mailboxes={mailboxItems} />}
|
||||||
{section === "settings" && <SystemSettingsSection settings={settings.data} domains={domainItems} />}
|
{section === "settings" && <SystemSettingsSection settings={settings.data} domains={domainItems} initialTab={params.get("settingsTab")} />}
|
||||||
</main>
|
</main>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
)
|
)
|
||||||
@@ -1018,7 +1019,7 @@ function AdminSendAuditSection({ mailboxes }: { mailboxes: MailboxType[] }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function SystemSettingsSection({ settings, domains }: { settings?: SystemSettings; domains: Domain[] }) {
|
function SystemSettingsSection({ settings, domains, initialTab }: { settings?: SystemSettings; domains: Domain[]; initialTab?: string | null }) {
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
const user = me.data?.user
|
const user = me.data?.user
|
||||||
const qc = useQueryClient()
|
const qc = useQueryClient()
|
||||||
@@ -1030,7 +1031,8 @@ function SystemSettingsSection({ settings, domains }: { settings?: SystemSetting
|
|||||||
const canUpdateTemplates = hasPermission(user, "admin.templates.update")
|
const canUpdateTemplates = hasPermission(user, "admin.templates.update")
|
||||||
const canResetTemplates = hasPermission(user, "admin.templates.reset")
|
const canResetTemplates = hasPermission(user, "admin.templates.reset")
|
||||||
const templates = useQuery({ queryKey: ["admin", "mail-templates"], queryFn: api.mailTemplates, enabled: canViewTemplates })
|
const templates = useQuery({ queryKey: ["admin", "mail-templates"], queryFn: api.mailTemplates, enabled: canViewTemplates })
|
||||||
const [settingsTab, setSettingsTab] = React.useState<"base" | "smtp" | "storage" | "mail" | "externalImap" | "templates" | "security" | "about">("base")
|
const requestedTab = initialTab as SettingsTab | undefined
|
||||||
|
const [settingsTab, setSettingsTab] = React.useState<SettingsTab>(() => requestedTab && ["base", "smtp", "storage", "mail", "externalImap", "templates", "security", "about"].includes(requestedTab) ? requestedTab : "base")
|
||||||
const maildirHealth = useQuery({ queryKey: ["admin", "maildir-sync", "health"], queryFn: api.maildirSyncHealth, enabled: canSettingsView && settingsTab === "storage" })
|
const maildirHealth = useQuery({ queryKey: ["admin", "maildir-sync", "health"], queryFn: api.maildirSyncHealth, enabled: canSettingsView && settingsTab === "storage" })
|
||||||
const [smtpRequireTls, setSmtpRequireTls] = React.useState(false)
|
const [smtpRequireTls, setSmtpRequireTls] = React.useState(false)
|
||||||
const [allowInsecureHttp, setAllowInsecureHttp] = React.useState(true)
|
const [allowInsecureHttp, setAllowInsecureHttp] = React.useState(true)
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export function ProfilePage() {
|
|||||||
const canManageBlocked = hasPermission(user, "mail.blocked_senders.manage")
|
const canManageBlocked = hasPermission(user, "mail.blocked_senders.manage")
|
||||||
const canViewStats = hasPermission(user, "mail.stats.view")
|
const canViewStats = hasPermission(user, "mail.stats.view")
|
||||||
const canApplyMailbox = hasPermission(user, "mail.mailboxes.apply")
|
const canApplyMailbox = hasPermission(user, "mail.mailboxes.apply")
|
||||||
|
const canConfigureMailboxApply = hasPermission(user, "admin.settings.update")
|
||||||
const visibleTabKeys = tabKeys.filter((key) => {
|
const visibleTabKeys = tabKeys.filter((key) => {
|
||||||
if (key === "profile") return true
|
if (key === "profile") return true
|
||||||
if (key === "mailboxes") return canAccessMail || canApplyMailbox
|
if (key === "mailboxes") return canAccessMail || canApplyMailbox
|
||||||
@@ -453,6 +454,7 @@ export function ProfilePage() {
|
|||||||
mailboxes={canAccessMail ? mailboxes.data?.items || [] : []}
|
mailboxes={canAccessMail ? mailboxes.data?.items || [] : []}
|
||||||
applyOptions={mailboxApplyOptions.data}
|
applyOptions={mailboxApplyOptions.data}
|
||||||
applyPending={applyMailbox.isPending}
|
applyPending={applyMailbox.isPending}
|
||||||
|
canConfigureApply={canConfigureMailboxApply}
|
||||||
selectedMailboxId={mailboxId}
|
selectedMailboxId={mailboxId}
|
||||||
externalImapEnabled={externalImapEnabled}
|
externalImapEnabled={externalImapEnabled}
|
||||||
externalAccounts={externalImapAccounts.data?.items || []}
|
externalAccounts={externalImapAccounts.data?.items || []}
|
||||||
@@ -464,6 +466,7 @@ export function ProfilePage() {
|
|||||||
onSelect={setMailboxId}
|
onSelect={setMailboxId}
|
||||||
onOpen={(id) => { if (!canAccessMail) return; setMailboxId(id); navigate("/") }}
|
onOpen={(id) => { if (!canAccessMail) return; setMailboxId(id); navigate("/") }}
|
||||||
onApply={(payload) => applyMailbox.mutateAsync(payload).then(() => undefined)}
|
onApply={(payload) => applyMailbox.mutateAsync(payload).then(() => undefined)}
|
||||||
|
onConfigureApply={() => navigate("/admin?section=settings&settingsTab=mail")}
|
||||||
onCreateExternal={(payload) => createExternalImap.mutate(payload)}
|
onCreateExternal={(payload) => createExternalImap.mutate(payload)}
|
||||||
onStartExternalOAuth={(provider, payload) => startExternalOAuth.mutate({ provider, ...payload })}
|
onStartExternalOAuth={(provider, payload) => startExternalOAuth.mutate({ provider, ...payload })}
|
||||||
onUpdateExternal={(id, payload) => updateExternalImap.mutate({ id, payload })}
|
onUpdateExternal={(id, payload) => updateExternalImap.mutate({ id, payload })}
|
||||||
@@ -1000,6 +1003,7 @@ function MailboxManagement({
|
|||||||
mailboxes,
|
mailboxes,
|
||||||
applyOptions,
|
applyOptions,
|
||||||
applyPending,
|
applyPending,
|
||||||
|
canConfigureApply,
|
||||||
selectedMailboxId,
|
selectedMailboxId,
|
||||||
externalImapEnabled,
|
externalImapEnabled,
|
||||||
externalAccounts,
|
externalAccounts,
|
||||||
@@ -1011,6 +1015,7 @@ function MailboxManagement({
|
|||||||
onSelect,
|
onSelect,
|
||||||
onOpen,
|
onOpen,
|
||||||
onApply,
|
onApply,
|
||||||
|
onConfigureApply,
|
||||||
onCreateExternal,
|
onCreateExternal,
|
||||||
onStartExternalOAuth,
|
onStartExternalOAuth,
|
||||||
onUpdateExternal,
|
onUpdateExternal,
|
||||||
@@ -1022,6 +1027,7 @@ function MailboxManagement({
|
|||||||
mailboxes: Mailbox[]
|
mailboxes: Mailbox[]
|
||||||
applyOptions?: MailboxApplyOptions
|
applyOptions?: MailboxApplyOptions
|
||||||
applyPending: boolean
|
applyPending: boolean
|
||||||
|
canConfigureApply: boolean
|
||||||
selectedMailboxId: string
|
selectedMailboxId: string
|
||||||
externalImapEnabled: boolean
|
externalImapEnabled: boolean
|
||||||
externalAccounts: ExternalImapAccount[]
|
externalAccounts: ExternalImapAccount[]
|
||||||
@@ -1033,6 +1039,7 @@ function MailboxManagement({
|
|||||||
onSelect: (id: string) => void
|
onSelect: (id: string) => void
|
||||||
onOpen: (id: string) => void
|
onOpen: (id: string) => void
|
||||||
onApply: (payload: { domainId: string; localPart: string; displayName: string }) => Promise<void>
|
onApply: (payload: { domainId: string; localPart: string; displayName: string }) => Promise<void>
|
||||||
|
onConfigureApply: () => void
|
||||||
onCreateExternal: (payload: ExternalImapAccountPayload) => void
|
onCreateExternal: (payload: ExternalImapAccountPayload) => void
|
||||||
onStartExternalOAuth: (provider: ExternalImapOAuthProvider, payload: { mailboxId: string; email: string; storageMode: ExternalImapStorageMode }) => void
|
onStartExternalOAuth: (provider: ExternalImapOAuthProvider, payload: { mailboxId: string; email: string; storageMode: ExternalImapStorageMode }) => void
|
||||||
onUpdateExternal: (id: string, payload: ExternalImapAccountPayload) => void
|
onUpdateExternal: (id: string, payload: ExternalImapAccountPayload) => void
|
||||||
@@ -1208,9 +1215,16 @@ function MailboxManagement({
|
|||||||
</select>
|
</select>
|
||||||
<Button className="h-[42px] px-0" disabled={!canApply || applyPending || !selectedDomain || !localPart.trim()}>{applyPending ? "创建中" : "创建"}</Button>
|
<Button className="h-[42px] px-0" disabled={!canApply || applyPending || !selectedDomain || !localPart.trim()}>{applyPending ? "创建中" : "创建"}</Button>
|
||||||
</form>
|
</form>
|
||||||
<p className="mt-4 text-sm text-muted-foreground">
|
<div className="mt-4 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground">
|
||||||
{canApply ? "提示:邮箱数量受账号配额限制,管理员可在后台为单个账号调整可创建数量。" : "提示:当前账号暂不可创建新邮箱。"}
|
<span>{canApply
|
||||||
</p>
|
? "提示:邮箱数量受账号配额限制,管理员可在后台为单个账号调整可创建数量。"
|
||||||
|
: canConfigureApply && applyOptions?.enabled
|
||||||
|
? "提示:尚未选择开放域名。请在“后台管理 → 系统设置 → 邮件”中至少勾选一个已启用域名。"
|
||||||
|
: canConfigureApply
|
||||||
|
? "提示:账号自助申请邮箱未开启。请在“后台管理 → 系统设置 → 邮件”中开启,并勾选开放域名。"
|
||||||
|
: "提示:当前账号暂不可创建新邮箱,请联系管理员开启账号自助申请邮箱。"}</span>
|
||||||
|
{!canApply && canConfigureApply && <Button type="button" variant="link" className="h-auto p-0 text-sm" onClick={onConfigureApply}>前往设置</Button>}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section className="rounded-lg border bg-card">
|
<section className="rounded-lg border bg-card">
|
||||||
|
|||||||
Reference in New Issue
Block a user