feat: link mailbox self-service setup guidance

This commit is contained in:
zxyszx
2026-08-04 00:03:11 +08:00
parent 1ce75ef241
commit 1af4b7250e
4 changed files with 27 additions and 7 deletions
+1 -1
View File
@@ -1 +1 @@
1.2.6
1.2.7
+4
View File
@@ -183,6 +183,10 @@ const exactTranslations: Record<string, Translation> = {
"前往个人中心": { "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: "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: "Mailbox access is not enabled for this account." },
"无邮件查看权限": { "zh-TW": "無郵件檢視權限", en: "No mail read permission" },
+5 -3
View File
@@ -27,6 +27,7 @@ import { hasAnyPermission, hasPermission } from "@/lib/permissions"
import type { PermissionKey } from "@/lib/api-types"
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 }
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 === "messages" && <AdminMessagesSection mailboxes={mailboxItems} systemAdmin={user?.role === "admin"} />}
{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>
</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 user = me.data?.user
const qc = useQueryClient()
@@ -1030,7 +1031,8 @@ function SystemSettingsSection({ settings, domains }: { settings?: SystemSetting
const canUpdateTemplates = hasPermission(user, "admin.templates.update")
const canResetTemplates = hasPermission(user, "admin.templates.reset")
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 [smtpRequireTls, setSmtpRequireTls] = React.useState(false)
const [allowInsecureHttp, setAllowInsecureHttp] = React.useState(true)
+17 -3
View File
@@ -82,6 +82,7 @@ export function ProfilePage() {
const canManageBlocked = hasPermission(user, "mail.blocked_senders.manage")
const canViewStats = hasPermission(user, "mail.stats.view")
const canApplyMailbox = hasPermission(user, "mail.mailboxes.apply")
const canConfigureMailboxApply = hasPermission(user, "admin.settings.update")
const visibleTabKeys = tabKeys.filter((key) => {
if (key === "profile") return true
if (key === "mailboxes") return canAccessMail || canApplyMailbox
@@ -453,6 +454,7 @@ export function ProfilePage() {
mailboxes={canAccessMail ? mailboxes.data?.items || [] : []}
applyOptions={mailboxApplyOptions.data}
applyPending={applyMailbox.isPending}
canConfigureApply={canConfigureMailboxApply}
selectedMailboxId={mailboxId}
externalImapEnabled={externalImapEnabled}
externalAccounts={externalImapAccounts.data?.items || []}
@@ -464,6 +466,7 @@ export function ProfilePage() {
onSelect={setMailboxId}
onOpen={(id) => { if (!canAccessMail) return; setMailboxId(id); navigate("/") }}
onApply={(payload) => applyMailbox.mutateAsync(payload).then(() => undefined)}
onConfigureApply={() => navigate("/admin?section=settings&settingsTab=mail")}
onCreateExternal={(payload) => createExternalImap.mutate(payload)}
onStartExternalOAuth={(provider, payload) => startExternalOAuth.mutate({ provider, ...payload })}
onUpdateExternal={(id, payload) => updateExternalImap.mutate({ id, payload })}
@@ -1000,6 +1003,7 @@ function MailboxManagement({
mailboxes,
applyOptions,
applyPending,
canConfigureApply,
selectedMailboxId,
externalImapEnabled,
externalAccounts,
@@ -1011,6 +1015,7 @@ function MailboxManagement({
onSelect,
onOpen,
onApply,
onConfigureApply,
onCreateExternal,
onStartExternalOAuth,
onUpdateExternal,
@@ -1022,6 +1027,7 @@ function MailboxManagement({
mailboxes: Mailbox[]
applyOptions?: MailboxApplyOptions
applyPending: boolean
canConfigureApply: boolean
selectedMailboxId: string
externalImapEnabled: boolean
externalAccounts: ExternalImapAccount[]
@@ -1033,6 +1039,7 @@ function MailboxManagement({
onSelect: (id: string) => void
onOpen: (id: string) => void
onApply: (payload: { domainId: string; localPart: string; displayName: string }) => Promise<void>
onConfigureApply: () => void
onCreateExternal: (payload: ExternalImapAccountPayload) => void
onStartExternalOAuth: (provider: ExternalImapOAuthProvider, payload: { mailboxId: string; email: string; storageMode: ExternalImapStorageMode }) => void
onUpdateExternal: (id: string, payload: ExternalImapAccountPayload) => void
@@ -1208,9 +1215,16 @@ function MailboxManagement({
</select>
<Button className="h-[42px] px-0" disabled={!canApply || applyPending || !selectedDomain || !localPart.trim()}>{applyPending ? "创建中" : "创建"}</Button>
</form>
<p className="mt-4 text-sm text-muted-foreground">
{canApply ? "提示:邮箱数量受账号配额限制,管理员可在后台为单个账号调整可创建数量。" : "提示:当前账号暂不可创建新邮箱。"}
</p>
<div className="mt-4 flex flex-wrap items-center gap-x-2 gap-y-1 text-sm text-muted-foreground">
<span>{canApply
? "提示:邮箱数量受账号配额限制,管理员可在后台为单个账号调整可创建数量。"
: 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 className="rounded-lg border bg-card">