feat: use usernames for administrator accounts
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
Docker Release / Check web and api (push) Waiting to run
Docker Release / Resolve release tag (push) Blocked by required conditions
Docker Release / Build and publish all-in-one (push) Blocked by required conditions
Docker Release / Build and publish api (push) Blocked by required conditions
Docker Release / Build and publish web (push) Blocked by required conditions
Docker Release / Build and publish dovecot (push) Blocked by required conditions
Docker Release / Build and publish postfix (push) Blocked by required conditions
Docker Release / Build and publish rspamd (push) Blocked by required conditions
Docker Release / Create GitHub release (push) Blocked by required conditions
This commit is contained in:
@@ -164,7 +164,7 @@ export const api = {
|
||||
defaultPermissionLimits: () => request<PermissionLimits>("/api/admin/permission-limits/defaults"),
|
||||
deletePermissionGroup: (id: string) => request<{ ok: boolean }>(`/api/admin/permission-groups/${id}`, { method: "DELETE" }),
|
||||
createUser: (payload: { loginName: string; displayName: string; role: "admin" | "user"; password: string; disabled: boolean; mailboxLimitOverride?: number; permissionGroupIds?: string[] }) => request<AdminUser>("/api/admin/users", { method: "POST", body: JSON.stringify(payload) }),
|
||||
updateUser: (id: string, payload: { displayName: string; role: "admin" | "user"; disabled: boolean; mailboxLimitOverride?: number; permissionGroupIds?: string[] }) => request<AdminUser>(`/api/admin/users/${id}`, { method: "POST", body: JSON.stringify(payload) }),
|
||||
updateUser: (id: string, payload: { loginName?: string; displayName: string; role: "admin" | "user"; disabled: boolean; mailboxLimitOverride?: number; permissionGroupIds?: string[] }) => request<AdminUser>(`/api/admin/users/${id}`, { method: "POST", body: JSON.stringify(payload) }),
|
||||
resetUserPassword: (id: string, password: string) => request<{ ok: boolean }>(`/api/admin/users/${id}/password`, { method: "POST", body: JSON.stringify({ password }) }),
|
||||
deleteUser: (id: string) => request<{ ok: boolean }>(`/api/admin/users/${id}`, { method: "DELETE" }),
|
||||
domains: () => request<ListResponse<Domain>>("/api/admin/domains"),
|
||||
|
||||
@@ -1896,6 +1896,7 @@ function EditUserDialog({ user, permissionGroups, open, onOpenChange }: { user:
|
||||
}, [user, open])
|
||||
const mut = useMutation({
|
||||
mutationFn: (form: FormData) => api.updateUser(user.id, {
|
||||
loginName: String(form.get("loginName") || ""),
|
||||
displayName: String(form.get("displayName") || ""),
|
||||
role,
|
||||
disabled: disabled === "disabled",
|
||||
@@ -1910,7 +1911,7 @@ function EditUserDialog({ user, permissionGroups, open, onOpenChange }: { user:
|
||||
<DialogContent>
|
||||
<DialogHeader><DialogTitle>编辑账号</DialogTitle></DialogHeader>
|
||||
<form className="space-y-4" onSubmit={(e) => { e.preventDefault(); mut.mutate(new FormData(e.currentTarget)) }}>
|
||||
<Field name="loginName" label="登录名" value={accountLoginName(user)} readOnly />
|
||||
<Field name="loginName" label="登录名" defaultValue={accountLoginName(user)} type="text" autoComplete="off" />
|
||||
<Field name="displayName" label="显示名称" defaultValue={user.displayName} />
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<SelectField label="身份" value={role} onValueChange={(value) => setRole(value as "admin" | "user")} items={[["user", "普通用户"], ["admin", "管理员"]]} disabled={user.protected} />
|
||||
|
||||
@@ -340,7 +340,7 @@ export function ProfilePage() {
|
||||
const sidebarContent = (
|
||||
<div className="flex h-full w-[256px] shrink-0 flex-col border-r border-border bg-card">
|
||||
<div className="h-[64px] border-b">
|
||||
<AccountHeader name={user.displayName || selectedMailbox?.address || "NewSzxcn"} email={user.email || selectedMailbox?.address} darkMode={darkMode} onToggleTheme={() => setDarkMode((v) => !v)} onBack={() => navigate("/")} />
|
||||
<AccountHeader name={user.displayName || selectedMailbox?.address || "NewSzxcn"} email={user.loginName || user.email || selectedMailbox?.address} darkMode={darkMode} onToggleTheme={() => setDarkMode((v) => !v)} onBack={() => navigate("/")} />
|
||||
</div>
|
||||
<nav className="min-h-0 flex-1 overflow-y-auto p-2">
|
||||
<div className="px-2 pb-2 pt-2 text-xs font-semibold text-muted-foreground">管理</div>
|
||||
@@ -556,7 +556,7 @@ function StatsRangeTabs({ rangeDays, onRangeChange }: { rangeDays: number; onRan
|
||||
|
||||
type AccountSettingsSectionProps = {
|
||||
activeTab: AccountSettingsTab
|
||||
user: { id: string; email: string; displayName: string; role: string; disabled: boolean; twoFactorEnabled: boolean; createdAt: string; limits?: PermissionLimits }
|
||||
user: { id: string; loginName?: string; email: string; displayName: string; role: string; disabled: boolean; twoFactorEnabled: boolean; createdAt: string; limits?: PermissionLimits }
|
||||
profile: { mutate: (form: FormData) => void; isPending: boolean }
|
||||
password: { mutate: (form: FormData) => void; isPending: boolean }
|
||||
passwordFormRef: React.RefObject<HTMLFormElement>
|
||||
@@ -658,7 +658,7 @@ function SettingsCard({ title, subtitle, action, children, className, contentCla
|
||||
}
|
||||
|
||||
function AccountTabSection({ user, stats, selectedMailbox, mailboxes, onOpenCleanup }: { user: AccountSettingsSectionProps["user"]; profile: AccountSettingsSectionProps["profile"]; stats?: MailStats; showStats: boolean; displayMode: DisplayMode; onDisplayModeChange: (mode: DisplayMode) => void; selectedMailbox?: Mailbox; mailboxes: Mailbox[]; onOpenCleanup: () => void }) {
|
||||
const accountName = cleanAccountName(user.displayName || user.email, user.email)
|
||||
const accountName = user.loginName || user.email
|
||||
const quotaBytes = stats?.quotaBytes || (selectedMailbox?.quotaMb ? selectedMailbox.quotaMb * 1024 * 1024 : 0)
|
||||
const storageBytes = stats?.storageBytes || 0
|
||||
const quotaPct = quotaBytes > 0 ? Math.min(100, Math.round((storageBytes / quotaBytes) * 100)) : 0
|
||||
@@ -973,7 +973,7 @@ function SecuritySettingsSection({ user, password, passwordFormRef, twoFactorFor
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="flex size-10 items-center justify-center rounded-full bg-emerald-100 text-emerald-700"><ShieldCheck className="h-5 w-5" /></div>
|
||||
<div>
|
||||
<div className="font-semibold">{user.email}</div>
|
||||
<div className="font-semibold">{user.loginName || user.email}</div>
|
||||
<div className="text-sm text-muted-foreground">上次登录:刚刚</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1258,7 +1258,7 @@ function writeLocalLogs(key: string, value: MailboxActionLog[]) {
|
||||
try { window.localStorage.setItem(key, JSON.stringify(value.slice(0, 50))) } catch {}
|
||||
}
|
||||
|
||||
function ProfileOverview({ user, profile, password, passwordFormRef, stats, showStats, displayMode, onDisplayModeChange, twoFactorFormRef, setupTwoFactor, enableTwoFactor, disableTwoFactor, onCopy }: { user: { email: string; displayName: string; role: string; disabled: boolean; twoFactorEnabled: boolean; createdAt: string; limits?: PermissionLimits }; profile: { mutate: (form: FormData) => void; isPending: boolean }; password: { mutate: (form: FormData) => void; isPending: boolean }; passwordFormRef: React.RefObject<HTMLFormElement>; stats?: MailStats; showStats: boolean; displayMode: DisplayMode; onDisplayModeChange: (mode: DisplayMode) => void; twoFactorFormRef: React.RefObject<HTMLFormElement>; setupTwoFactor: { data?: { secret: string; otpauthUrl: string }; mutate: () => void; reset: () => void; isPending: boolean }; enableTwoFactor: { mutate: (form: FormData) => void; isPending: boolean }; disableTwoFactor: { mutate: (form: FormData) => void; isPending: boolean }; onCopy: (text: string) => void }) {
|
||||
function ProfileOverview({ user, profile, password, passwordFormRef, stats, showStats, displayMode, onDisplayModeChange, twoFactorFormRef, setupTwoFactor, enableTwoFactor, disableTwoFactor, onCopy }: { user: { loginName?: string; email: string; displayName: string; role: string; disabled: boolean; twoFactorEnabled: boolean; createdAt: string; limits?: PermissionLimits }; profile: { mutate: (form: FormData) => void; isPending: boolean }; password: { mutate: (form: FormData) => void; isPending: boolean }; passwordFormRef: React.RefObject<HTMLFormElement>; stats?: MailStats; showStats: boolean; displayMode: DisplayMode; onDisplayModeChange: (mode: DisplayMode) => void; twoFactorFormRef: React.RefObject<HTMLFormElement>; setupTwoFactor: { data?: { secret: string; otpauthUrl: string }; mutate: () => void; reset: () => void; isPending: boolean }; enableTwoFactor: { mutate: (form: FormData) => void; isPending: boolean }; disableTwoFactor: { mutate: (form: FormData) => void; isPending: boolean }; onCopy: (text: string) => void }) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
@@ -1287,7 +1287,7 @@ function ProfileOverview({ user, profile, password, passwordFormRef, stats, show
|
||||
<form className="space-y-4" onSubmit={(e) => { e.preventDefault(); profile.mutate(new FormData(e.currentTarget)) }}>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Field label="用户名">
|
||||
<Input value={user.email} readOnly />
|
||||
<Input value={user.loginName || user.email} readOnly />
|
||||
</Field>
|
||||
<Field label="显示名称">
|
||||
<Input name="displayName" defaultValue={user.displayName} required />
|
||||
|
||||
Reference in New Issue
Block a user