Explorar o código

feat:系统初始化页面改造

韩洋 hai 3 semanas
pai
achega
f6979f4135

+ 2 - 1
default/rsbuild.config.ts

@@ -14,7 +14,8 @@ export default defineConfig(({ envMode }) => {
     process.env.VITE_REACT_APP_SERVER_URL ||
     env.rawPublicVars.VITE_REACT_APP_SERVER_URL ||
     // 'http://192.168.100.26:3000',
-    'http://47.117.92.63:8081'
+    // 'http://47.117.92.63:8081',
+    'http://192.168.100.184:3000'
 
   const isProd = envMode === 'production'
   const devProxy = Object.fromEntries(

+ 12 - 1
default/src/features/setup/api.ts

@@ -42,11 +42,22 @@ export function buildSetupPayload(
   values: SetupFormValues,
   rootInitialized: boolean
 ) {
-  const { usageMode, ...rest } = values
+  const { usageMode, resource, role, ...rest } = values
 
   const basePayload = {
     SelfUseModeEnabled: usageMode === 'self',
     DemoSiteEnabled: usageMode === 'demo',
+    QuotaForNewUser: resource.quotaForNewUser,
+    PreConsumedQuota: resource.preConsumedQuota,
+    QuotaPerUnit: resource.quotaPerUnit,
+    EnabledModules: resource.enabledModules,
+    DefaultRoles: role.roles.map((r) => ({
+      key: r.key,
+      name: r.name,
+      description: r.description,
+      is_locked: r.isLocked,
+      permission_codes: Object.values(r.permissions).flat(),
+    })),
   }
 
   if (rootInitialized) {

+ 62 - 35
default/src/features/setup/components/complete-step.tsx

@@ -19,9 +19,9 @@ For commercial licensing, please contact support@quantumnous.com
 import { CheckCircle2 } from 'lucide-react'
 import { useTranslation } from 'react-i18next'
 
-import { StatusBadge } from '@/components/status-badge'
 import { Separator } from '@/components/ui/separator'
 
+import { DEFAULT_ROLES, PERMISSION_MODULES } from '../constants'
 import type { SetupFormValues, SetupStatus } from '../types'
 
 interface CompleteStepProps {
@@ -29,26 +29,10 @@ interface CompleteStepProps {
   values: SetupFormValues
 }
 
-const USAGE_MODE_LABEL_KEYS: Record<SetupFormValues['usageMode'], string> = {
-  external: 'External operations mode',
-  self: 'Personal use mode',
-  demo: 'Demo site mode',
-}
-
-const DATABASE_VARIANT: Record<
-  string,
-  'info' | 'success' | 'warning' | 'neutral'
-> = {
-  sqlite: 'warning',
-  mysql: 'success',
-  postgres: 'success',
-}
-
 export function CompleteStep({ status, values }: CompleteStepProps) {
-  const { t } = useTranslation()
-  const usageLabelKey = USAGE_MODE_LABEL_KEYS[values.usageMode]
-  const dbType = status?.database_type ?? 'Unknown'
-  const databaseVariant = DATABASE_VARIANT[dbType.toLowerCase()] ?? 'neutral'
+  const { t, i18n } = useTranslation()
+  const isZh = i18n.language.startsWith('zh')
+  const resource = values.resource
 
   return (
     <div className='flex flex-col items-center gap-6 text-center'>
@@ -70,15 +54,12 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
         <dl className='grid gap-6'>
           <div className='space-y-1.5'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
-              {t('Database')}
+              {t('Administrator account')}
             </dt>
-            <dd className='flex flex-wrap items-center gap-2'>
-              <span className='text-sm font-semibold'>{dbType}</span>
-              <StatusBadge
-                label={dbType}
-                variant={databaseVariant}
-                copyable={false}
-              />
+            <dd className='text-sm font-semibold'>
+              {status?.root_init
+                ? t('Existing account will be reused')
+                : values.username || t('Not set yet')}
             </dd>
           </div>
 
@@ -86,12 +67,28 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
 
           <div className='space-y-1.5'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
-              {t('Administrator account')}
+              {t('System resource configuration')}
             </dt>
-            <dd className='text-sm font-semibold'>
-              {status?.root_init
-                ? t('Existing account will be reused')
-                : values.username || t('Not set yet')}
+            <dd className='text-sm'>
+              <dl className='grid gap-1.5'>
+                <div className='flex flex-col gap-1.5'>
+                  <dt className='text-muted-foreground'>
+                    {t('Enabled Modules')}
+                  </dt>
+                  <dd className='flex flex-wrap gap-1.5'>
+                    {PERMISSION_MODULES.filter((m) =>
+                      resource.enabledModules.includes(m.moduleCode)
+                    ).map((m) => (
+                      <span
+                        key={m.moduleCode}
+                        className='bg-muted inline-flex rounded px-2 py-0.5 text-xs font-medium'
+                      >
+                        {m.moduleName}
+                      </span>
+                    ))}
+                  </dd>
+                </div>
+              </dl>
             </dd>
           </div>
 
@@ -99,9 +96,39 @@ export function CompleteStep({ status, values }: CompleteStepProps) {
 
           <div className='space-y-1.5'>
             <dt className='text-muted-foreground text-xs font-medium tracking-wide uppercase'>
-              {t('Usage mode')}
+              {t('System role configuration')}
             </dt>
-            <dd className='text-sm font-semibold'>{t(usageLabelKey)}</dd>
+            <dd className='text-sm'>
+              <dl className='grid gap-3'>
+                {values.role.roles.map((role, idx) => {
+                  const defaultRole = DEFAULT_ROLES[idx]
+                  const permCount = Object.values(role.permissions).reduce(
+                    (sum, arr) => sum + arr.length,
+                    0
+                  )
+                  return (
+                    <div
+                      key={role.key}
+                      className='flex items-center justify-between gap-4'
+                    >
+                      <dt className='text-muted-foreground'>
+                        {isZh
+                          ? defaultRole?.name ?? role.name
+                          : defaultRole?.nameEn ?? role.name}
+                        <span className='text-muted-foreground/60 ml-1 text-xs'>
+                          ({isZh
+                            ? defaultRole?.description ?? role.description
+                            : defaultRole?.descriptionEn ?? role.description})
+                        </span>
+                      </dt>
+                      <dd className='text-muted-foreground text-xs'>
+                        {t('{{count}} permissions', { count: permCount })}
+                      </dd>
+                    </div>
+                  )
+                })}
+              </dl>
+            </dd>
           </div>
         </dl>
       </div>

+ 105 - 0
default/src/features/setup/components/resource-step.tsx

@@ -0,0 +1,105 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+import { Lock } from 'lucide-react'
+import type { UseFormReturn } from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
+
+import {
+  FormDescription,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+} from '@/components/ui/form'
+import { cn } from '@/lib/utils'
+
+import { PERMISSION_MODULES } from '../constants'
+import type { SetupFormValues } from '../types'
+
+interface ResourceStepProps {
+  form: UseFormReturn<SetupFormValues>
+}
+
+export function ResourceStep({ form }: ResourceStepProps) {
+  const { t } = useTranslation()
+
+  const toggleModule = (code: string, checked: boolean) => {
+    const current = form.getValues('resource.enabledModules')
+    const next = checked
+      ? [...current, code]
+      : current.filter((c) => c !== code)
+    form.setValue('resource.enabledModules', next, { shouldDirty: true })
+  }
+
+  return (
+    <div className='space-y-6'>
+      {/* --- Module checkboxes --- */}
+      <FormField
+        control={form.control}
+        name='resource.enabledModules'
+        render={({ field }) => (
+          <FormItem>
+            <FormLabel>{t('Menu Modules')}</FormLabel>
+            <FormDescription>
+              {t('Select which modules are visible to users.')}
+            </FormDescription>
+            <div className='grid gap-2 sm:grid-cols-2 lg:grid-cols-3'>
+              {PERMISSION_MODULES.map((mod) => {
+                const checked = field.value.includes(mod.moduleCode)
+                const disabled = mod.isOnlyOpenForRoot
+                return (
+                  <label
+                    key={mod.moduleCode}
+                    className={cn(
+                      'hover:bg-muted/65 flex items-center gap-2.5 rounded-lg border px-3 py-2.5 text-sm transition-colors',
+                      checked
+                        ? 'border-primary/45 bg-primary/5'
+                        : 'border-transparent bg-background/60',
+                      disabled && 'pointer-events-none opacity-70'
+                    )}
+                  >
+                    <input
+                      type='checkbox'
+                      checked={checked}
+                      disabled={disabled}
+                      onChange={(e) =>
+                        toggleModule(mod.moduleCode, e.target.checked)
+                      }
+                      className='size-4 rounded border-input accent-primary'
+                    />
+                    <span className='flex-1'>
+                      <span className='font-medium'>{mod.moduleName}</span>
+                      <span className='text-muted-foreground ml-1.5 text-xs'>
+                        {mod.moduleLabel}
+                      </span>
+                    </span>
+                    {disabled && (
+                      <Lock className='text-muted-foreground/60 size-3 shrink-0' />
+                    )}
+                  </label>
+                )
+              })}
+            </div>
+            <FormMessage />
+          </FormItem>
+        )}
+      />
+    </div>
+  )
+}

+ 308 - 0
default/src/features/setup/components/role-step.tsx

@@ -0,0 +1,308 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+import { Lock, Plus, Trash2 } from 'lucide-react'
+import { useState } from 'react'
+import type { UseFormReturn } from 'react-hook-form'
+import { useTranslation } from 'react-i18next'
+
+import {
+  Accordion,
+  AccordionContent,
+  AccordionItem,
+  AccordionTrigger,
+} from '@/components/ui/accordion'
+import { Button } from '@/components/ui/button'
+import { Input } from '@/components/ui/input'
+import { Switch } from '@/components/ui/switch'
+import { cn } from '@/lib/utils'
+
+import {
+  DEFAULT_ROLES,
+  PERMISSION_TREE,
+  type PermissionModuleItem,
+} from '../constants'
+import type { SetupFormValues } from '../types'
+
+interface RoleStepProps {
+  form: UseFormReturn<SetupFormValues>
+}
+
+/** Keys of the three built-in roles that cannot be deleted. */
+const BUILTIN_KEYS = new Set(DEFAULT_ROLES.map((r) => r.key))
+
+export function RoleStep({ form }: RoleStepProps) {
+  const { t, i18n } = useTranslation()
+  const isZh = i18n.language.startsWith('zh')
+  const roles = form.watch('role.roles')
+  const enabledModules = form.watch('resource.enabledModules')
+  const [expandedKeys, setExpandedKeys] = useState<string[]>(['root'])
+
+  /** Only show permission modules that are enabled in the resource step. */
+  const visibleModules = PERMISSION_TREE.filter((mod) =>
+    enabledModules.includes(mod.moduleCode)
+  )
+
+  /** Toggle all children of a module on/off for a specific role. */
+  const toggleModuleAll = (
+    roleIndex: number,
+    mod: PermissionModuleItem,
+    checked: boolean
+  ) => {
+    const current = form.getValues(`role.roles.${roleIndex}.permissions`)
+    form.setValue(
+      `role.roles.${roleIndex}.permissions`,
+      {
+        ...current,
+        [mod.moduleCode]: checked
+          ? mod.children.map((c) => c.frontPermCode)
+          : [],
+      },
+      { shouldDirty: true }
+    )
+  }
+
+  /** Toggle a single child permission for a specific role. */
+  const toggleChild = (
+    roleIndex: number,
+    moduleCode: string,
+    childCode: string,
+    checked: boolean
+  ) => {
+    const current = form.getValues(`role.roles.${roleIndex}.permissions`)
+    const existing = current[moduleCode] ?? []
+    const next = checked
+      ? [...existing, childCode]
+      : existing.filter((c) => c !== childCode)
+    form.setValue(
+      `role.roles.${roleIndex}.permissions`,
+      { ...current, [moduleCode]: next },
+      { shouldDirty: true }
+    )
+  }
+
+  /** Add a new custom role with an empty permission set. */
+  const addRole = () => {
+    const current = form.getValues('role.roles')
+    const newKey = `custom-${Date.now()}`
+    form.setValue(
+      'role.roles',
+      [
+        ...current,
+        {
+          key: newKey,
+          name: '',
+          description: '',
+          isLocked: false,
+          permissions: {},
+        },
+      ],
+      { shouldDirty: true }
+    )
+    setExpandedKeys((prev) => [...prev, newKey])
+  }
+
+  /** Remove a custom role by index. */
+  const removeRole = (roleIndex: number) => {
+    const current = form.getValues('role.roles')
+    const removed = current[roleIndex]
+    form.setValue(
+      'role.roles',
+      current.filter((_, i) => i !== roleIndex),
+      { shouldDirty: true }
+    )
+    setExpandedKeys((prev) => prev.filter((k) => k !== removed.key))
+  }
+
+  return (
+    <div className='space-y-2'>
+      <Accordion
+        value={expandedKeys}
+        onValueChange={setExpandedKeys}
+        className='gap-2'
+      >
+        {roles.map((role, roleIndex) => {
+          const defaultRole = DEFAULT_ROLES[roleIndex]
+          const isBuiltin = BUILTIN_KEYS.has(role.key)
+          const isLocked = role.isLocked
+          const rolePermissions = role.permissions ?? {}
+
+          return (
+            <AccordionItem
+              key={role.key}
+              value={role.key}
+              className='bg-card rounded-lg border px-4'
+            >
+              <AccordionTrigger>
+                <div className='flex items-center gap-2'>
+                  <span className='text-sm font-semibold'>
+                    {isBuiltin
+                      ? (isZh ? defaultRole.name : defaultRole.nameEn)
+                      : role.name || t('New Role')
+                    }
+                  </span>
+                  <span className='text-muted-foreground text-xs'>
+                    {isBuiltin
+                      ? (isZh
+                        ? defaultRole.description
+                        : defaultRole.descriptionEn)
+                      : role.description
+                    }
+                  </span>
+                  {isLocked && (
+                    <Lock className='text-muted-foreground/60 size-3' />
+                  )}
+                </div>
+              </AccordionTrigger>
+              <AccordionContent>
+                {/* Editable name/description for custom roles */}
+                {!isBuiltin && (
+                  <div className='mb-3 grid gap-2 sm:grid-cols-2'>
+                    <Input
+                      placeholder={t('Role Name')}
+                      value={role.name}
+                      onChange={(e) =>
+                        form.setValue(
+                          `role.roles.${roleIndex}.name`,
+                          e.target.value,
+                          { shouldDirty: true }
+                        )
+                      }
+                    />
+                    <Input
+                      placeholder={t('Role Description')}
+                      value={role.description}
+                      onChange={(e) =>
+                        form.setValue(
+                          `role.roles.${roleIndex}.description`,
+                          e.target.value,
+                          { shouldDirty: true }
+                        )
+                      }
+                    />
+                  </div>
+                )}
+
+                <div className='grid gap-2 sm:grid-cols-2 lg:grid-cols-3'>
+                  {visibleModules.map((mod) => {
+                    const selected = rolePermissions[mod.moduleCode] ?? []
+                    const allChildCodes = mod.children.map(
+                      (c) => c.frontPermCode
+                    )
+                    const isAllSelected =
+                      allChildCodes.length > 0 &&
+                      allChildCodes.every((c) => selected.includes(c))
+
+                    return (
+                      <div
+                        key={mod.moduleCode}
+                        className={cn(
+                          'rounded-md border p-2.5',
+                          isAllSelected
+                            ? 'border-primary/40 bg-primary/5'
+                            : 'border-border'
+                        )}
+                      >
+                        <div className='mb-2 flex items-center justify-between gap-2'>
+                          <span className='text-xs font-medium'>
+                            {isZh ? mod.moduleName : mod.moduleLabel}
+                          </span>
+                          <Switch
+                            checked={isAllSelected}
+                            disabled={isLocked}
+                            onCheckedChange={(checked) =>
+                              toggleModuleAll(roleIndex, mod, checked)
+                            }
+                            className='scale-75'
+                          />
+                        </div>
+                        <div className='flex flex-wrap gap-1.5'>
+                          {mod.children.map((child) => {
+                            const checked = selected.includes(
+                              child.frontPermCode
+                            )
+                            return (
+                              <label
+                                key={child.frontPermCode}
+                                className={cn(
+                                  'inline-flex cursor-pointer items-center gap-1 rounded px-1.5 py-0.5 text-xs transition-colors',
+                                  checked
+                                    ? 'bg-primary/10 text-primary'
+                                    : 'text-muted-foreground',
+                                  isLocked && 'pointer-events-none'
+                                )}
+                              >
+                                <input
+                                  type='checkbox'
+                                  checked={checked}
+                                  disabled={isLocked}
+                                  onChange={(e) =>
+                                    toggleChild(
+                                      roleIndex,
+                                      mod.moduleCode,
+                                      child.frontPermCode,
+                                      e.target.checked
+                                    )
+                                  }
+                                  className='size-3 rounded border-input accent-primary'
+                                />
+                                {isZh
+                                  ? child.permName
+                                  : child.permLabel}
+                              </label>
+                            )
+                          })}
+                        </div>
+                      </div>
+                    )
+                  })}
+                </div>
+
+                {/* Delete button for custom roles */}
+                {!isBuiltin && (
+                  <div className='mt-3 flex justify-end'>
+                    <Button
+                      type='button'
+                      variant='ghost'
+                      size='sm'
+                      onClick={() => removeRole(roleIndex)}
+                    >
+                      <Trash2 className='mr-1 size-3.5' />
+                      {t('Delete Role')}
+                    </Button>
+                  </div>
+                )}
+              </AccordionContent>
+            </AccordionItem>
+          )
+        })}
+      </Accordion>
+
+      <Button
+        type='button'
+        variant='outline'
+        size='sm'
+        onClick={addRole}
+        className='w-full border-dashed'
+      >
+        <Plus className='mr-1 size-4' />
+        {t('Add Role')}
+      </Button>
+    </div>
+  )
+}

+ 300 - 0
default/src/features/setup/constants.ts

@@ -0,0 +1,300 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+/* ------------------------------------------------------------------ */
+/*  Module-level permission data (flat list, for resource step)        */
+/* ------------------------------------------------------------------ */
+
+/** A selectable menu module shown on the resource configuration step. */
+export interface PermissionModule {
+  /** Module name used as the checkbox label (already localized in zh). */
+  moduleName: string
+  /** Module code, e.g. `route.dashboard.view`. */
+  moduleCode: string
+  /** English label for the module. */
+  moduleLabel: string
+  /** When 1 the module is root-only: checked & disabled by default. */
+  isOnlyOpenForRoot: boolean
+}
+
+/**
+ * Static list of permission modules sourced from
+ * `docs/permission-identifiers.json`.
+ */
+export const PERMISSION_MODULES: PermissionModule[] = [
+  { moduleName: '数据看板', moduleCode: 'route.dashboard.view', moduleLabel: 'Dashboard', isOnlyOpenForRoot: false },
+  { moduleName: '配额管理', moduleCode: 'route.quota.view', moduleLabel: 'Quota Management', isOnlyOpenForRoot: false },
+  { moduleName: '渠道管理', moduleCode: 'route.channel.view', moduleLabel: 'Provider Channels', isOnlyOpenForRoot: true },
+  { moduleName: '模型管理', moduleCode: 'route.models.view', moduleLabel: 'Model Management', isOnlyOpenForRoot: true },
+  { moduleName: '模型定价', moduleCode: 'route.model-pricing.view', moduleLabel: 'Model Pricing', isOnlyOpenForRoot: true },
+  { moduleName: '分组管理', moduleCode: 'route.groups.view', moduleLabel: 'Group Management', isOnlyOpenForRoot: true },
+  { moduleName: '企业列表', moduleCode: 'route.tenant.view', moduleLabel: 'Tenants', isOnlyOpenForRoot: false },
+  { moduleName: '个人资料', moduleCode: 'route.profile.view', moduleLabel: 'Profile', isOnlyOpenForRoot: false },
+  { moduleName: 'API密钥', moduleCode: 'route.keys.view', moduleLabel: 'API Keys', isOnlyOpenForRoot: false },
+  { moduleName: '模型列表', moduleCode: 'route.pricing.view', moduleLabel: 'Models', isOnlyOpenForRoot: false },
+  { moduleName: '调用日志', moduleCode: 'route.usage-logs.view', moduleLabel: 'API Logs', isOnlyOpenForRoot: false },
+  { moduleName: '审计日志', moduleCode: 'route.audit-logs.view', moduleLabel: 'Audit Logs', isOnlyOpenForRoot: false },
+  { moduleName: '用户管理', moduleCode: 'route.user.view', moduleLabel: 'Users', isOnlyOpenForRoot: false },
+  { moduleName: '角色管理', moduleCode: 'route.role.view', moduleLabel: 'Roles & Permissions', isOnlyOpenForRoot: false },
+  { moduleName: '系统信息', moduleCode: 'route.settings.view', moduleLabel: 'System Information', isOnlyOpenForRoot: true },
+]
+
+/** Default enabled module codes: all modules enabled by default. */
+export const DEFAULT_ENABLED_MODULE_CODES = PERMISSION_MODULES.map(
+  (m) => m.moduleCode
+)
+
+/* ------------------------------------------------------------------ */
+/*  Permission tree with children (for role step)                      */
+/* ------------------------------------------------------------------ */
+
+/** A button-level permission inside a module. */
+export interface PermissionChildItem {
+  permName: string
+  frontPermCode: string
+  permLabel: string
+}
+
+/** A permission module with its child (button) permissions. */
+export interface PermissionModuleItem extends PermissionModule {
+  forRoleTemplateType: number
+  children: PermissionChildItem[]
+}
+
+/**
+ * Full permission tree sourced from `docs/permission-identifiers.json`,
+ * including button-level children.
+ */
+export const PERMISSION_TREE: PermissionModuleItem[] = [
+  {
+    moduleName: '数据看板', moduleCode: 'route.dashboard.view', moduleLabel: 'Dashboard',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'dashboard.view', permLabel: 'View Dashboard' }],
+  },
+  {
+    moduleName: '配额管理', moduleCode: 'route.quota.view', moduleLabel: 'Quota Management',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'quota.view', permLabel: 'View Quota Management' }],
+  },
+  {
+    moduleName: '渠道管理', moduleCode: 'route.channel.view', moduleLabel: 'Provider Channels',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'channel.view', permLabel: 'View Provider Channels' },
+      { permName: '新增', frontPermCode: 'channel.create', permLabel: 'Create Provider Channel' },
+      { permName: '编辑', frontPermCode: 'channel.update', permLabel: 'Update Provider Channel' },
+      { permName: '删除', frontPermCode: 'channel.delete', permLabel: 'Delete Provider Channel' },
+      { permName: '禁用', frontPermCode: 'channel.disable', permLabel: 'Disable Provider Channel' },
+    ],
+  },
+  {
+    moduleName: '模型管理', moduleCode: 'route.models.view', moduleLabel: 'Model Management',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'models.view', permLabel: 'View Model Management' },
+      { permName: '编辑', frontPermCode: 'models.update', permLabel: 'Update Model' },
+      { permName: '禁用', frontPermCode: 'models.disable', permLabel: 'Disable Model' },
+    ],
+  },
+  {
+    moduleName: '模型定价', moduleCode: 'route.model-pricing.view', moduleLabel: 'Model Pricing',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'model-pricing.view', permLabel: 'View Model Pricing' },
+      { permName: '新增', frontPermCode: 'model-pricing.add', permLabel: 'Add Model Pricing' },
+      { permName: '编辑', frontPermCode: 'model-pricing.edit', permLabel: 'Edit Model Pricing' },
+      { permName: '删除', frontPermCode: 'model-pricing.delete', permLabel: 'Delete Model Pricing' },
+    ],
+  },
+  {
+    moduleName: '分组管理', moduleCode: 'route.groups.view', moduleLabel: 'Group Management',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'groups.view', permLabel: 'View Group Management' },
+      { permName: '新增', frontPermCode: 'groups.create', permLabel: 'Create Group' },
+      { permName: '编辑', frontPermCode: 'groups.update', permLabel: 'Update Group' },
+      { permName: '删除', frontPermCode: 'groups.delete', permLabel: 'Delete Group' },
+    ],
+  },
+  {
+    moduleName: '企业列表', moduleCode: 'route.tenant.view', moduleLabel: 'Tenants',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'tenant.view', permLabel: 'View Tenants' },
+      { permName: '新增', frontPermCode: 'tenant.create', permLabel: 'Create Tenant' },
+      { permName: '编辑', frontPermCode: 'tenant.update', permLabel: 'Update Tenant' },
+      { permName: '禁用', frontPermCode: 'tenant.disable', permLabel: 'Disable Tenant' },
+    ],
+  },
+  {
+    moduleName: '个人资料', moduleCode: 'route.profile.view', moduleLabel: 'Profile',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'profile.view', permLabel: 'View Profile' }],
+  },
+  {
+    moduleName: 'API密钥', moduleCode: 'route.keys.view', moduleLabel: 'API Keys',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看', frontPermCode: 'keys.view', permLabel: 'View API Keys' },
+      { permName: '新增', frontPermCode: 'keys.create', permLabel: 'Create API Key' },
+      { permName: '编辑', frontPermCode: 'keys.update', permLabel: 'Update API Key' },
+      { permName: '删除', frontPermCode: 'keys.delete', permLabel: 'Delete API Key' },
+      { permName: '禁用', frontPermCode: 'keys.disable', permLabel: 'Disable API Key' },
+    ],
+  },
+  {
+    moduleName: '模型列表', moduleCode: 'route.pricing.view', moduleLabel: 'Models',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [{ permName: '查看', frontPermCode: 'pricing.view', permLabel: 'View Models' }],
+  },
+  {
+    moduleName: '调用日志', moduleCode: 'route.usage-logs.view', moduleLabel: 'API Logs',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看(仅自己)', frontPermCode: 'usage-logs.view', permLabel: 'View API Logs' },
+      { permName: '查看(全部数据)', frontPermCode: 'usage-logs.all', permLabel: 'View All API Logs' },
+    ],
+  },
+  {
+    moduleName: '审计日志', moduleCode: 'route.audit-logs.view', moduleLabel: 'Audit Logs',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 10,
+    children: [
+      { permName: '查看(仅自己)', frontPermCode: 'audit-logs.view', permLabel: 'View Audit Logs' },
+      { permName: '查看(全部数据)', frontPermCode: 'audit-logs.all', permLabel: 'View All Audit Logs' },
+    ],
+  },
+  {
+    moduleName: '用户管理', moduleCode: 'route.user.view', moduleLabel: 'Users',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 20,
+    children: [
+      { permName: '查看', frontPermCode: 'user.view', permLabel: 'View Users' },
+      { permName: '新增', frontPermCode: 'user.create', permLabel: 'Create User' },
+      { permName: '编辑', frontPermCode: 'user.update', permLabel: 'Update User' },
+      { permName: '删除', frontPermCode: 'user.delete', permLabel: 'Delete User' },
+      { permName: '禁用', frontPermCode: 'user.disable', permLabel: 'Disable User' },
+    ],
+  },
+  {
+    moduleName: '角色管理', moduleCode: 'route.role.view', moduleLabel: 'Roles & Permissions',
+    isOnlyOpenForRoot: false, forRoleTemplateType: 30,
+    children: [
+      { permName: '查看', frontPermCode: 'role.view', permLabel: 'View Roles & Permissions' },
+      { permName: '新增', frontPermCode: 'role.create', permLabel: 'Create Role' },
+      { permName: '编辑', frontPermCode: 'role.update', permLabel: 'Update Role' },
+      { permName: '删除', frontPermCode: 'role.delete', permLabel: 'Delete Role' },
+    ],
+  },
+  {
+    moduleName: '系统信息', moduleCode: 'route.settings.view', moduleLabel: 'System Information',
+    isOnlyOpenForRoot: true, forRoleTemplateType: 20,
+    children: [{ permName: '查看', frontPermCode: 'settings.view', permLabel: 'View System Information' }],
+  },
+]
+
+/* ------------------------------------------------------------------ */
+/*  Default roles                                                      */
+/* ------------------------------------------------------------------ */
+
+/** A default role displayed on the role configuration step. */
+export interface DefaultRoleConfig {
+  /** Stable identifier for the role. */
+  key: string
+  /** Display name (Chinese). */
+  name: string
+  /** Display name (English). */
+  nameEn: string
+  /** Description (Chinese). */
+  description: string
+  /** Description (English). */
+  descriptionEn: string
+  /** When true the role's permissions cannot be modified. */
+  isLocked: boolean
+  /**
+   * Default permission map: moduleCode -> array of frontPermCode values
+   * for the enabled children.
+   */
+  permissions: Record<string, string[]>
+}
+
+/** Build a permission map where every module has all its children enabled. */
+function allPermissions(modules: PermissionModuleItem[]): Record<string, string[]> {
+  const result: Record<string, string[]> = {}
+  for (const mod of modules) {
+    result[mod.moduleCode] = mod.children.map((c) => c.frontPermCode)
+  }
+  return result
+}
+
+/** Build a view-only permission map for the given modules. */
+function viewOnlyPermissions(modules: PermissionModuleItem[]): Record<string, string[]> {
+  const result: Record<string, string[]> = {}
+  for (const mod of modules) {
+    const viewChildren = mod.children.filter(
+      (c) => c.permName === '查看' || c.permName.startsWith('查看')
+    )
+    if (viewChildren.length > 0) {
+      result[mod.moduleCode] = viewChildren.map((c) => c.frontPermCode)
+    }
+  }
+  return result
+}
+
+/** Admin-level modules: forRoleTemplateType 10 + 20 + 30. */
+const ADMIN_MODULES = PERMISSION_TREE.filter(
+  (m) => m.forRoleTemplateType <= 30
+)
+
+/** User-level modules: forRoleTemplateType 10 only. */
+const USER_MODULES = PERMISSION_TREE.filter(
+  (m) => m.forRoleTemplateType === 10
+)
+
+/**
+ * Three default roles pre-configured with sensible permission sets.
+ * Root is locked; admin and common-user are editable.
+ */
+export const DEFAULT_ROLES: DefaultRoleConfig[] = [
+  {
+    key: 'root',
+    name: '超级管理员',
+    nameEn: 'Super Admin',
+    description: '拥有系统全部权限,不可修改',
+    descriptionEn: 'Full system access, locked',
+    isLocked: true,
+    permissions: allPermissions(PERMISSION_TREE),
+  },
+  {
+    key: 'admin',
+    name: '管理员',
+    nameEn: 'Admin',
+    description: '拥有管理模块的全部权限',
+    descriptionEn: 'Full management module access',
+    isLocked: false,
+    permissions: allPermissions(ADMIN_MODULES),
+  },
+  {
+    key: 'common',
+    name: '普通用户',
+    nameEn: 'Common User',
+    description: '仅拥有基础查看权限',
+    descriptionEn: 'Basic view-only access',
+    isLocked: false,
+    permissions: viewOnlyPermissions(USER_MODULES),
+  },
+]

+ 31 - 51
default/src/features/setup/setup-wizard.tsx

@@ -42,23 +42,24 @@ import { cn } from '@/lib/utils'
 import { buildSetupPayload, getSetupStatus, submitSetup } from './api'
 import { AdminStep } from './components/admin-step'
 import { CompleteStep } from './components/complete-step'
-import { DatabaseStep } from './components/database-step'
+import { ResourceStep } from './components/resource-step'
+import { RoleStep } from './components/role-step'
 import { StepNavigation } from './components/step-navigation'
-import { UsageModeStep } from './components/usage-mode-step'
+import { DEFAULT_ENABLED_MODULE_CODES, DEFAULT_ROLES } from './constants'
 import type { SetupFormValues, SetupStatus } from './types'
 
 const STEPS = [
-  {
-    titleKey: 'Database check',
-    descriptionKey: 'Verify your database connection',
-  },
   {
     titleKey: 'Administrator account',
     descriptionKey: 'Create credentials for the root user',
   },
   {
-    titleKey: 'Usage mode',
-    descriptionKey: 'Choose how the platform will operate',
+    titleKey: 'System resource configuration',
+    descriptionKey: 'Configure default resource limits',
+  },
+  {
+    titleKey: 'System role configuration',
+    descriptionKey: 'Set up system default roles',
   },
   {
     titleKey: 'Review & initialize',
@@ -71,6 +72,21 @@ const DEFAULT_FORM_VALUES: SetupFormValues = {
   password: '',
   confirmPassword: '',
   usageMode: 'external',
+  resource: {
+    quotaForNewUser: 0,
+    preConsumedQuota: 0,
+    quotaPerUnit: 500000,
+    enabledModules: DEFAULT_ENABLED_MODULE_CODES,
+  },
+  role: {
+    roles: DEFAULT_ROLES.map((r) => ({
+      key: r.key,
+      name: r.name,
+      description: r.description,
+      isLocked: r.isLocked,
+      permissions: { ...r.permissions },
+    })),
+  },
 }
 
 export function SetupWizard() {
@@ -139,29 +155,8 @@ export function SetupWizard() {
 
     setSetupStatus(status)
     setCurrentStep(0)
-
-    // Pre-fill usage mode if backend echoes it
-    if (status.SelfUseModeEnabled) {
-      form.setValue('usageMode', 'self', {
-        shouldDirty: false,
-        shouldTouch: false,
-        shouldValidate: false,
-      })
-    } else if (status.DemoSiteEnabled) {
-      form.setValue('usageMode', 'demo', {
-        shouldDirty: false,
-        shouldTouch: false,
-        shouldValidate: false,
-      })
-    } else {
-      form.setValue('usageMode', 'external', {
-        shouldDirty: false,
-        shouldTouch: false,
-        shouldValidate: false,
-      })
-    }
     // eslint-disable-next-line react-hooks/exhaustive-deps
-  }, [statusResponse, navigate, form])
+  }, [statusResponse, navigate])
 
   useEffect(() => {
     if (!setupStatus) return
@@ -188,9 +183,6 @@ export function SetupWizard() {
 
   const currentStepComponent = useMemo(() => {
     if (currentStep === 0) {
-      return <DatabaseStep status={setupStatus} />
-    }
-    if (currentStep === 1) {
       return (
         <AdminStep
           form={form}
@@ -198,8 +190,11 @@ export function SetupWizard() {
         />
       )
     }
+    if (currentStep === 1) {
+      return <ResourceStep form={form} />
+    }
     if (currentStep === 2) {
-      return <UsageModeStep form={form} />
+      return <RoleStep form={form} />
     }
     return <CompleteStep status={setupStatus} values={watchedValues} />
   }, [currentStep, setupStatus, form, watchedValues])
@@ -241,22 +236,8 @@ export function SetupWizard() {
     return true
   }
 
-  const validateUsageModeStep = () => {
-    const usageMode = form.getValues('usageMode')
-    if (!usageMode) {
-      form.setError('usageMode', {
-        type: 'manual',
-        message: t('Select a usage mode to continue'),
-      })
-      toast.error(t('Select a usage mode to continue'))
-      return false
-    }
-    return true
-  }
-
   const handleNextStep = () => {
-    if (currentStep === 1 && !validateAdminStep()) return
-    if (currentStep === 2 && !validateUsageModeStep()) return
+    if (currentStep === 0 && !validateAdminStep()) return
 
     setCurrentStep((step) => Math.min(step + 1, STEPS.length - 1))
   }
@@ -267,8 +248,7 @@ export function SetupWizard() {
 
   const handleSubmit = async () => {
     const adminValid = validateAdminStep()
-    const usageValid = validateUsageModeStep()
-    if (!adminValid || !usageValid) return
+    if (!adminValid) return
 
     const payload = buildSetupPayload(
       form.getValues(),

+ 34 - 0
default/src/features/setup/types.ts

@@ -27,11 +27,45 @@ export interface SetupStatus {
   DemoSiteEnabled?: boolean
 }
 
+/** Fields collected on the "System resource configuration" step. */
+export interface ResourceConfigValues {
+  /** Default quota granted to newly registered users. */
+  quotaForNewUser: number
+  /** Quota pre-consumed before a request is billed. */
+  preConsumedQuota: number
+  /** How many quota units equal one unit of display currency. */
+  quotaPerUnit: number
+  /** Enabled menu module codes selected on this step. */
+  enabledModules: string[]
+}
+
+/** A single role's form values on the role configuration step. */
+export interface RoleFormValues {
+  /** Stable key (root / admin / common). */
+  key: string
+  /** Role display name. */
+  name: string
+  /** Role description. */
+  description: string
+  /** Whether the role is locked (permissions cannot be modified). */
+  isLocked: boolean
+  /** moduleCode -> array of enabled frontPermCode values. */
+  permissions: Record<string, string[]>
+}
+
+/** Fields collected on the "System role configuration" step. */
+export interface RoleConfigValues {
+  /** The three default roles with their permission sets. */
+  roles: RoleFormValues[]
+}
+
 export interface SetupFormValues {
   username: string
   password: string
   confirmPassword: string
   usageMode: SetupUsageMode
+  resource: ResourceConfigValues
+  role: RoleConfigValues
 }
 
 export interface SetupResponse {

+ 22 - 0
default/src/i18n/locales/en.json

@@ -68,6 +68,7 @@
     "{{count}} models": "{{count}} models",
     "{{count}} months ago": "{{count}} months ago",
     "{{count}} override": "{{count}} override",
+    "{{count}} permissions": "{{count}} permissions",
     "{{count}} selected targets available for bulk copy.": "{{count}} selected targets available for bulk copy.",
     "{{count}} tiers": "{{count}} tiers",
     "{{count}} Uptime Kuma groups will be removed from the list.": "{{count}} Uptime Kuma groups will be removed from the list.",
@@ -225,6 +226,7 @@
     "Add ratio override": "Add ratio override",
     "Add route": "Add route",
     "Add Row": "Add Row",
+    "Add Role": "Add Role",
     "Add Rule": "Add Rule",
     "Add rule group": "Add rule group",
     "Add rules for a user group": "Add rules for a user group",
@@ -347,6 +349,7 @@
     "Allow upstream callbacks": "Allow upstream callbacks",
     "Allow users to check in daily for random quota rewards": "Allow users to check in daily for random quota rewards",
     "Allow users to enter promo codes": "Allow users to enter promo codes",
+    "Allow User Registration": "Allow User Registration",
     "Allow users to log in with password": "Allow users to log in with password",
     "Allow users to register and sign in with Passkey (WebAuthn)": "Allow users to register and sign in with Passkey (WebAuthn)",
     "Allow users to sign in with Discord": "Allow users to sign in with Discord",
@@ -1034,6 +1037,7 @@
     "Confirm password": "Confirm password",
     "Confirm Payment": "Confirm Payment",
     "Confirm Selection": "Confirm Selection",
+    "Configure default resource limits": "Configure default resource limits",
     "Confirm settings and finish setup": "Confirm settings and finish setup",
     "confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
     "Confirm Unbind": "Confirm Unbind",
@@ -1291,6 +1295,8 @@
     "Default Collapse Sidebar": "Default Collapse Sidebar",
     "Default consumption chart": "Default consumption chart",
     "Default Max Tokens": "Default Max Tokens",
+    "Default Quota for New Users": "Default Quota for New Users",
+    "Default Role Name": "Default Role Name",
     "Default model call chart": "Default model call chart",
     "Default range": "Default range",
     "Default Responses API version, if empty, will use the API version above": "Default Responses API version, if empty, will use the API version above",
@@ -1538,6 +1544,7 @@
     "e.g., v2.1": "e.g., v2.1",
     "Each backup code can only be used once.": "Each backup code can only be used once.",
     "Each item must be an object with a single key-value pair.": "Each item must be an object with a single key-value pair.",
+    "e.g. Common User": "e.g. Common User",
     "Each item must have exactly one key-value pair.": "Each item must have exactly one key-value pair.",
     "Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Each line represents one keyword. Leave blank to disable the list but keep the switch states.",
     "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.": "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.",
@@ -1625,6 +1632,7 @@
     "Enable SSL/TLS": "Enable SSL/TLS",
     "Enable SSRF Protection": "Enable SSRF Protection",
     "Enable STARTTLS": "Enable STARTTLS",
+    "Enable self-service sign-up for new users.": "Enable self-service sign-up for new users.",
     "Enable streaming mode for the test request.": "Enable streaming mode for the test request.",
     "Enable Telegram OAuth": "Enable Telegram OAuth",
     "Enable test mode for Creem payments": "Enable test mode for Creem payments",
@@ -1641,6 +1649,7 @@
     "Enabled all channels with tag: {{tag}}": "Enabled all channels with tag: {{tag}}",
     "Enabled channels with tag {{tag}}": "Enabled channels with tag {{tag}}",
     "Enabled Status": "Enabled Status",
+    "Enabled Modules": "Enabled Modules",
     "Enabling...": "Enabling...",
     "Encourages introducing new topics": "Encourages introducing new topics",
     "Encourages new topics": "Encourages new topics",
@@ -2293,6 +2302,7 @@
     "How to reset my quota?": "How to reset my quota?",
     "How to select keys: random or sequential polling": "How to select keys: random or sequential polling",
     "How will you use the platform?": "How will you use the platform?",
+    "How many quota units equal one unit of display currency.": "How many quota units equal one unit of display currency.",
     "https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
     "https://api.example.com": "https://api.example.com",
     "https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
@@ -2675,6 +2685,7 @@
     "Memory Threshold (%)": "Memory Threshold (%)",
     "Merchant ID": "Merchant ID",
     "Merchant ID is required": "Merchant ID is required",
+    "Menu Modules": "Menu Modules",
     "Merge into Other": "Merge into Other",
     "Message Priority": "Message Priority",
     "Metadata": "Metadata",
@@ -2886,6 +2897,7 @@
     "New password": "New password",
     "New Password": "New Password",
     "New password must be different from current password": "New password must be different from current password",
+    "New Role": "New Role",
     "New User Quota": "New User Quota",
     "New version available: {{version}}": "New version available: {{version}}",
     "NewAPI": "NewAPI",
@@ -3495,6 +3507,7 @@
     "Powerful API Management Platform": "Powerful API Management Platform",
     "Pre-Consume for Free Models": "Pre-Consume for Free Models",
     "Pre-consumed": "Pre-consumed",
+    "Pre-consumed Quota": "Pre-consumed Quota",
     "Pre-Consumed Quota": "Pre-Consumed Quota",
     "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.": "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.",
     "Preferences": "Preferences",
@@ -3646,9 +3659,11 @@
     "Quota": "Quota",
     "Quota ({{currency}})": "Quota ({{currency}})",
     "Quota adjusted successfully": "Quota adjusted successfully",
+    "Quota automatically granted to each new user upon registration.": "Quota automatically granted to each new user upon registration.",
     "Quota clamped": "Quota clamped",
     "Quota consumed before charging users": "Quota consumed before charging users",
     "Quota Distribution": "Quota Distribution",
+    "Quota deducted before a request is processed to prevent overuse.": "Quota deducted before a request is processed to prevent overuse.",
     "Quota given to invited users": "Quota given to invited users",
     "Quota given to invited users ({{formattedQuota}})": "Quota given to invited users ({{formattedQuota}})",
     "Quota given to users who invite others": "Quota given to users who invite others",
@@ -3657,6 +3672,7 @@
     "Quota must be a positive number": "Quota must be a positive number",
     "Quota must be zero or greater": "Quota must be zero or greater",
     "Quota Per Unit": "Quota Per Unit",
+    "Quota per Unit": "Quota per Unit",
     "Quota reminder (tokens)": "Quota reminder (tokens)",
     "Quota Reset": "Quota Reset",
     "Quota saturation protection triggered": "Quota saturation protection triggered",
@@ -3857,6 +3873,7 @@
     "Requests will be forwarded to this worker. Trailing slashes are removed automatically.": "Requests will be forwarded to this worker. Trailing slashes are removed automatically.",
     "Requests:": "Requests:",
     "Require email verification for new accounts": "Require email verification for new accounts",
+    "Require email verification when users register.": "Require email verification when users register.",
     "Require job success before follow-up actions": "Require job success before follow-up actions",
     "Require login to view models": "Require login to view models",
     "Require login to view rankings": "Require login to view rankings",
@@ -4165,6 +4182,7 @@
     "Select time granularity": "Select time granularity",
     "Select type": "Select type",
     "Select vendor": "Select vendor",
+    "Select which modules are visible to users.": "Select which modules are visible to users.",
     "Select user groups and models for this account": "Select user groups and models for this account",
     "Selectable groups": "Selectable groups",
     "selected": "selected",
@@ -4216,6 +4234,7 @@
     "Set the language used across the interface": "Set the language used across the interface",
     "Set the user's role (cannot be Root)": "Set the user's role (cannot be Root)",
     "Set these values in the provider application before enabling login.": "Set these values in the provider application before enabling login.",
+    "Set up system default roles": "Set up system default roles",
     "Setting Key": "Setting Key",
     "Setting saved": "Setting saved",
     "Setting up 2FA...": "Setting up 2FA...",
@@ -4470,6 +4489,8 @@
     "System settings": "System settings",
     "System Settings": "System Settings",
     "System setup wizard": "System setup wizard",
+    "System resource configuration": "System resource configuration",
+    "System role configuration": "System role configuration",
     "System task records": "System task records",
     "System Tasks": "System Tasks",
     "System Version": "System Version",
@@ -4562,6 +4583,7 @@
     "The requested chat preset does not exist or has been removed.": "The requested chat preset does not exist or has been removed.",
     "The reset request stays disabled until a credit is available.": "The reset request stays disabled until a credit is available.",
     "The setup wizard will use this database during initialization.": "The setup wizard will use this database during initialization.",
+    "The role automatically assigned to newly registered users.": "The role automatically assigned to newly registered users.",
     "The site is not available at the moment.": "The site is not available at the moment.",
     "The slug is appended to the URL:": "The slug is appended to the URL:",
     "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.": "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.",

+ 22 - 0
default/src/i18n/locales/zh.json

@@ -68,6 +68,7 @@
     "{{count}} models": "{{count}} 个模型",
     "{{count}} months ago": "{{count}} 个月前",
     "{{count}} override": "{{count}} 个覆盖",
+    "{{count}} permissions": "{{count}} 个权限",
     "{{count}} selected targets available for bulk copy.": "已选择 {{count}} 个目标,可用于批量复制。",
     "{{count}} tiers": "{{count}} 档",
     "{{count}} Uptime Kuma groups will be removed from the list.": "将从列表中移除 {{count}} 个 Uptime Kuma 分组。",
@@ -225,6 +226,7 @@
     "Add ratio override": "添加倍率覆盖",
     "Add route": "添加路由",
     "Add Row": "添加行",
+    "Add Role": "添加角色",
     "Add Rule": "添加规则",
     "Add rule group": "新增规则组",
     "Add rules for a user group": "为用户分组添加规则",
@@ -350,6 +352,7 @@
     "Allow upstream callbacks": "允许上游回调",
     "Allow users to check in daily for random quota rewards": "允许用户每日签到获取随机额度奖励",
     "Allow users to enter promo codes": "允许用户输入促销代码",
+    "Allow User Registration": "允许用户注册",
     "Allow users to log in with password": "允许用户使用密码登录",
     "Allow users to register and sign in with Passkey (WebAuthn)": "允许用户使用通行密钥 (WebAuthn) 注册和登录",
     "Allow users to sign in with Discord": "允许用户使用 Discord 登录",
@@ -1037,6 +1040,7 @@
     "Confirm password": "确认密码",
     "Confirm Payment": "确认付款",
     "Confirm Selection": "确认选择",
+    "Configure default resource limits": "配置默认资源限制",
     "Confirm settings and finish setup": "确认设置并完成安装",
     "confirm that I bear legal responsibility arising from deployment": "确认承担因部署",
     "Confirm Unbind": "确认解绑",
@@ -1294,6 +1298,8 @@
     "Default Collapse Sidebar": "默认折叠侧边栏",
     "Default consumption chart": "默认消耗分布图",
     "Default Max Tokens": "默认最大 Token 数",
+    "Default Quota for New Users": "新用户默认配额",
+    "Default Role Name": "默认角色名称",
     "Default model call chart": "默认模型调用图",
     "Default range": "默认范围",
     "Default Responses API version, if empty, will use the API version above": "默认响应 API 版本,如果为空,将使用上面的 API 版本",
@@ -1541,6 +1547,7 @@
     "e.g., v2.1": "例如,v2.1",
     "Each backup code can only be used once.": "每个备份代码只能使用一次。",
     "Each item must be an object with a single key-value pair.": "每个条目必须是包含单个键值对的对象。",
+    "e.g. Common User": "例如:普通用户",
     "Each item must have exactly one key-value pair.": "每个条目必须恰好包含一个键值对。",
     "Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "每行代表一个关键词。留空以禁用列表,但保留开关状态。",
     "Each matrix cell is one rule: users of this row group pay this ratio when billed as this column group. In JSON the row is the outer key and the column is the inner key.": "矩阵的每个单元格是一条规则:该行用户分组的用户按该列分组计费时使用此倍率。在 JSON 中行是外层键,列是内层键。",
@@ -1628,6 +1635,7 @@
     "Enable SSL/TLS": "启用 SSL/TLS",
     "Enable SSRF Protection": "启用 SSRF 保护",
     "Enable STARTTLS": "启用 STARTTLS",
+    "Enable self-service sign-up for new users.": "允许新用户自助注册。",
     "Enable streaming mode for the test request.": "为测试请求启用流式模式。",
     "Enable Telegram OAuth": "启用 Telegram OAuth",
     "Enable test mode for Creem payments": "启用 Creem 支付测试模式",
@@ -1644,6 +1652,7 @@
     "Enabled all channels with tag: {{tag}}": "已启用标签「{{tag}}」下的所有渠道",
     "Enabled channels with tag {{tag}}": "启用标签为 {{tag}} 的渠道",
     "Enabled Status": "启用状态",
+    "Enabled Modules": "已启用模块",
     "Enabling...": "正在启用...",
     "Encourages introducing new topics": "鼓励引入新话题",
     "Encourages new topics": "鼓励讨论新话题",
@@ -2295,6 +2304,7 @@
     "How to reset my quota?": "如何重置我的配额?",
     "How to select keys: random or sequential polling": "密钥选择方式:随机或顺序轮询",
     "How will you use the platform?": "您将如何使用本平台?",
+    "How many quota units equal one unit of display currency.": "多少配额单位等于一个显示货币单位。",
     "https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
     "https://api.example.com": "https://api.example.com",
     "https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
@@ -2677,6 +2687,7 @@
     "Memory Threshold (%)": "内存阈值 (%)",
     "Merchant ID": "商户 ID",
     "Merchant ID is required": "商户 ID 为必填项",
+    "Menu Modules": "菜单模块",
     "Merge into Other": "合并为其他",
     "Message Priority": "消息优先级",
     "Metadata": "元信息",
@@ -2888,6 +2899,7 @@
     "New password": "新密码",
     "New Password": "新密码",
     "New password must be different from current password": "新密码必须与当前密码不同",
+    "New Role": "新角色",
     "New User Quota": "新用户配额",
     "New version available: {{version}}": "有新版本可用:{{version}}",
     "NewAPI": "NewAPI",
@@ -3498,6 +3510,7 @@
     "Powerful API Management Platform": "强大的 API 管理平台",
     "Pre-Consume for Free Models": "免费模型预消耗",
     "Pre-consumed": "预扣费",
+    "Pre-consumed Quota": "预扣配额",
     "Pre-Consumed Quota": "预消耗配额",
     "Preference saved as {{pref}}, but no active subscription. Wallet will be used automatically.": "已保存偏好为{{pref}},当前无生效订阅,将自动使用钱包",
     "Preferences": "偏好设置",
@@ -3649,8 +3662,10 @@
     "Quota": "额度",
     "Quota ({{currency}})": "额度 ({{currency}})",
     "Quota adjusted successfully": "调整额度成功",
+    "Quota automatically granted to each new user upon registration.": "每位新用户注册时自动获得的配额。",
     "Quota clamped": "额度已钳制",
     "Quota consumed before charging users": "向用户收费前消耗的配额",
+    "Quota deducted before a request is processed to prevent overuse.": "在请求处理前扣除的配额,以防止超额使用。",
     "Quota Distribution": "消耗分布",
     "Quota given to invited users": "授予被邀请用户的配额",
     "Quota given to invited users ({{formattedQuota}})": "授予被邀请用户的配额({{formattedQuota}})",
@@ -3660,6 +3675,7 @@
     "Quota must be a positive number": "配额必须是正数",
     "Quota must be zero or greater": "额度不能为负数",
     "Quota Per Unit": "每单位配额",
+    "Quota per Unit": "每单位配额",
     "Quota reminder (tokens)": "配额提醒(token)",
     "Quota Reset": "额度重置",
     "Quota saturation protection triggered": "额度饱和保护已触发",
@@ -3860,6 +3876,7 @@
     "Requests will be forwarded to this worker. Trailing slashes are removed automatically.": "请求将被转发到此 Worker。末尾的斜杠会自动移除。",
     "Requests:": "请求:",
     "Require email verification for new accounts": "要求新账户验证邮箱",
+    "Require email verification when users register.": "用户注册时要求验证邮箱。",
     "Require job success before follow-up actions": "在后续操作前要求任务成功",
     "Require login to view models": "要求登录才能查看模型",
     "Require login to view rankings": "要求登录才能查看排行榜",
@@ -4168,6 +4185,7 @@
     "Select time granularity": "选择时间粒度",
     "Select type": "选择类型",
     "Select vendor": "选择供应商",
+    "Select which modules are visible to users.": "选择对用户可见的模块。",
     "Select user groups and models for this account": "选择此账户的用户分组和模型",
     "Selectable groups": "可选分组",
     "selected": "已选择",
@@ -4219,6 +4237,7 @@
     "Set the language used across the interface": "设置界面显示语言",
     "Set the user's role (cannot be Root)": "设置用户角色(不能是 Root)",
     "Set these values in the provider application before enabling login.": "启用登录前,请先在提供商应用中设置这些值。",
+    "Set up system default roles": "设置系统默认角色",
     "Setting Key": "配置项",
     "Setting saved": "设置已保存",
     "Setting up 2FA...": "正在设置 2FA...",
@@ -4473,6 +4492,8 @@
     "System settings": "系统设置",
     "System Settings": "系统设置",
     "System setup wizard": "系统设置向导",
+    "System resource configuration": "系统资源配置",
+    "System role configuration": "系统角色配置",
     "System task records": "系统任务记录",
     "System Tasks": "系统任务",
     "System Version": "系统版本",
@@ -4565,6 +4586,7 @@
     "The requested chat preset does not exist or has been removed.": "请求的聊天预设不存在或已被删除。",
     "The reset request stays disabled until a credit is available.": "没有可用次数时,重置请求会保持禁用。",
     "The setup wizard will use this database during initialization.": "设置向导将在初始化过程中使用此数据库。",
+    "The role automatically assigned to newly registered users.": "自动分配给新注册用户的角色。",
     "The site is not available at the moment.": "该站点目前不可用。",
     "The slug is appended to the URL:": "别名将附加到 URL:",
     "The sync will fetch missing models and vendors from the selected source. Existing records are updated only when you approve conflicts.": "同步将从选定的源获取缺失的模型和供应商。仅在您批准冲突时才会更新现有记录。",