@fudanda/admin-antd 2.20261107.1-beta.1 → 2.20261109.1-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/chunks/ApiTokensPage-w3JGRNQC.js +2 -0
  2. package/dist/chunks/ApiTokensPage-w3JGRNQC.js.map +1 -0
  3. package/dist/chunks/{App-BF96TQ_D.js → App-nh6_qOo8.js} +5 -5
  4. package/dist/chunks/{App-BF96TQ_D.js.map → App-nh6_qOo8.js.map} +1 -1
  5. package/dist/chunks/{AppModal-CLkMJ-96.js → AppModal-CEouaU03.js} +2 -2
  6. package/dist/chunks/{AppModal-CLkMJ-96.js.map → AppModal-CEouaU03.js.map} +1 -1
  7. package/dist/chunks/{ArcBaseAdmin--xQxd4tg.js → ArcBaseAdmin-CX7fQz4x.js} +2 -2
  8. package/dist/chunks/{ArcBaseAdmin--xQxd4tg.js.map → ArcBaseAdmin-CX7fQz4x.js.map} +1 -1
  9. package/dist/chunks/{DepartmentsPage-D6YVegPU.js → DepartmentsPage-BdD9VfDI.js} +2 -2
  10. package/dist/chunks/{DepartmentsPage-D6YVegPU.js.map → DepartmentsPage-BdD9VfDI.js.map} +1 -1
  11. package/dist/chunks/{DictsPage-uV3Uukwc.js → DictsPage-fxBdV1-3.js} +2 -2
  12. package/dist/chunks/{DictsPage-uV3Uukwc.js.map → DictsPage-fxBdV1-3.js.map} +1 -1
  13. package/dist/chunks/{EntityContextRuntime-DbjyzCtY.js → EntityContextRuntime-CMhoj6qe.js} +2 -2
  14. package/dist/chunks/{EntityContextRuntime-DbjyzCtY.js.map → EntityContextRuntime-CMhoj6qe.js.map} +1 -1
  15. package/dist/chunks/EntityContextRuntime-Dc89ZSnM.js +1 -0
  16. package/dist/chunks/{ExportButton-e2ybxuHb.js → ExportButton-DGxB2tcu.js} +2 -2
  17. package/dist/chunks/{ExportButton-e2ybxuHb.js.map → ExportButton-DGxB2tcu.js.map} +1 -1
  18. package/dist/chunks/{FileViewerPreviewPanel-BhI3pTi8.js → FileViewerPreviewPanel-BMZ7FdKV.js} +3 -3
  19. package/dist/chunks/{FileViewerPreviewPanel-BhI3pTi8.js.map → FileViewerPreviewPanel-BMZ7FdKV.js.map} +1 -1
  20. package/dist/chunks/{FilesPage-BI35mStl.js → FilesPage-DrgWELKy.js} +3 -3
  21. package/dist/chunks/{FilesPage-BI35mStl.js.map → FilesPage-DrgWELKy.js.map} +1 -1
  22. package/dist/chunks/IdentitySecurityPage-BjTKvr-O.js +9 -0
  23. package/dist/chunks/IdentitySecurityPage-BjTKvr-O.js.map +1 -0
  24. package/dist/chunks/{LogStatsScaffold-m0vVUXPh.js → LogStatsScaffold-DaB0JeAN.js} +2 -2
  25. package/dist/chunks/{LogStatsScaffold-m0vVUXPh.js.map → LogStatsScaffold-DaB0JeAN.js.map} +1 -1
  26. package/dist/chunks/{LoginLogsPage-BzBUR4Oo.js → LoginLogsPage-VmYC7i6q.js} +2 -2
  27. package/dist/chunks/{LoginLogsPage-BzBUR4Oo.js.map → LoginLogsPage-VmYC7i6q.js.map} +1 -1
  28. package/dist/chunks/{LoginLogsTable-BweUEjQX.js → LoginLogsTable-CULcc_e8.js} +2 -2
  29. package/dist/chunks/{LoginLogsTable-BweUEjQX.js.map → LoginLogsTable-CULcc_e8.js.map} +1 -1
  30. package/dist/chunks/{MarkdownPreviewPanel-C1OKSLmh.js → MarkdownPreviewPanel-CBcPc7Wv.js} +2 -2
  31. package/dist/chunks/{MarkdownPreviewPanel-C1OKSLmh.js.map → MarkdownPreviewPanel-CBcPc7Wv.js.map} +1 -1
  32. package/dist/chunks/{MasterDetailLayout-KVizcSNO.js → MasterDetailLayout-ByIBpmkZ.js} +2 -2
  33. package/dist/chunks/{MasterDetailLayout-KVizcSNO.js.map → MasterDetailLayout-ByIBpmkZ.js.map} +1 -1
  34. package/dist/chunks/{McpSettingsPage-Dhf0CbDN.js → McpSettingsPage-C5V_rj2V.js} +2 -2
  35. package/dist/chunks/{McpSettingsPage-Dhf0CbDN.js.map → McpSettingsPage-C5V_rj2V.js.map} +1 -1
  36. package/dist/chunks/{MemberAssignmentSheet-TYpnyA0z.js → MemberAssignmentSheet-DL0fg1Yf.js} +2 -2
  37. package/dist/chunks/{MemberAssignmentSheet-TYpnyA0z.js.map → MemberAssignmentSheet-DL0fg1Yf.js.map} +1 -1
  38. package/dist/chunks/{MenuCommandPalette-BcvpUfK7.js → MenuCommandPalette-C2PvK3Gh.js} +2 -2
  39. package/dist/chunks/{MenuCommandPalette-BcvpUfK7.js.map → MenuCommandPalette-C2PvK3Gh.js.map} +1 -1
  40. package/dist/chunks/{MenusPage-Dd7OLQYT.js → MenusPage-Bytd1HlW.js} +2 -2
  41. package/dist/chunks/{MenusPage-Dd7OLQYT.js.map → MenusPage-Bytd1HlW.js.map} +1 -1
  42. package/dist/chunks/{MonacoPreviewPanel-BW1j3ZTF.js → MonacoPreviewPanel-BZrNIcFZ.js} +2 -2
  43. package/dist/chunks/{MonacoPreviewPanel-BW1j3ZTF.js.map → MonacoPreviewPanel-BZrNIcFZ.js.map} +1 -1
  44. package/dist/chunks/{NavListPanel-Iz1RCkVw.js → NavListPanel-zwUDIYSP.js} +2 -2
  45. package/dist/chunks/{NavListPanel-Iz1RCkVw.js.map → NavListPanel-zwUDIYSP.js.map} +1 -1
  46. package/dist/chunks/{OperationLogsPage-Dt22tU4I.js → OperationLogsPage-D2BQdGNp.js} +2 -2
  47. package/dist/chunks/{OperationLogsPage-Dt22tU4I.js.map → OperationLogsPage-D2BQdGNp.js.map} +1 -1
  48. package/dist/chunks/{OperationLogsTable-B23Ry-2v.js → OperationLogsTable-DaaON4QR.js} +3 -3
  49. package/dist/chunks/{OperationLogsTable-B23Ry-2v.js.map → OperationLogsTable-DaaON4QR.js.map} +1 -1
  50. package/dist/chunks/{PDFPreviewPanel-kBPz5dQr.js → PDFPreviewPanel-CMmnWBKW.js} +3 -3
  51. package/dist/chunks/{PDFPreviewPanel-kBPz5dQr.js.map → PDFPreviewPanel-CMmnWBKW.js.map} +1 -1
  52. package/dist/chunks/{PositionsPage-BIKYQDB5.js → PositionsPage-CBZITssT.js} +2 -2
  53. package/dist/chunks/{PositionsPage-BIKYQDB5.js.map → PositionsPage-CBZITssT.js.map} +1 -1
  54. package/dist/chunks/{PreferencesSheetBody-D276rrp4.js → PreferencesSheetBody-B0poTPEA.js} +2 -2
  55. package/dist/chunks/{PreferencesSheetBody-D276rrp4.js.map → PreferencesSheetBody-B0poTPEA.js.map} +1 -1
  56. package/dist/chunks/{ProfilePage-DLftC6KZ.js → ProfilePage-DLhacdWt.js} +2 -2
  57. package/dist/chunks/{ProfilePage-DLftC6KZ.js.map → ProfilePage-DLhacdWt.js.map} +1 -1
  58. package/dist/chunks/{ProjectInfoPage-DkuBLC8t.js → ProjectInfoPage-CJpGxpu5.js} +2 -2
  59. package/dist/chunks/{ProjectInfoPage-DkuBLC8t.js.map → ProjectInfoPage-CJpGxpu5.js.map} +1 -1
  60. package/dist/chunks/{RelationPanel-B4BeO0Bz.js → RelationPanel-BsbfEDo2.js} +2 -2
  61. package/dist/chunks/{RelationPanel-B4BeO0Bz.js.map → RelationPanel-BsbfEDo2.js.map} +1 -1
  62. package/dist/chunks/{RolesPage-BhltU2Qm.js → RolesPage-46dzdiah.js} +2 -2
  63. package/dist/chunks/{RolesPage-BhltU2Qm.js.map → RolesPage-46dzdiah.js.map} +1 -1
  64. package/dist/chunks/{SessionConflictModal-3R8jdYkI.js → SessionConflictModal-Bm38eExL.js} +2 -2
  65. package/dist/chunks/{SessionConflictModal-3R8jdYkI.js.map → SessionConflictModal-Bm38eExL.js.map} +1 -1
  66. package/dist/chunks/SettingModuleDetail-BjxUJeH5.js +17 -0
  67. package/dist/chunks/SettingModuleDetail-BjxUJeH5.js.map +1 -0
  68. package/dist/chunks/{SettingsPage-DMzeDrnc.js → SettingsPage-C32Vuizm.js} +2 -2
  69. package/dist/chunks/{SettingsPage-DMzeDrnc.js.map → SettingsPage-C32Vuizm.js.map} +1 -1
  70. package/dist/chunks/{StatCard-C0xSZyiS.js → StatCard-CRPfkSBY.js} +2 -2
  71. package/dist/chunks/{StatCard-C0xSZyiS.js.map → StatCard-CRPfkSBY.js.map} +1 -1
  72. package/dist/chunks/{UserGroupsPage-DTJdszHl.js → UserGroupsPage-08G5MdGm.js} +2 -2
  73. package/dist/chunks/{UserGroupsPage-DTJdszHl.js.map → UserGroupsPage-08G5MdGm.js.map} +1 -1
  74. package/dist/chunks/{UserLogsPanels-z4UhQtgu.js → UserLogsPanels-nbIrMym9.js} +2 -2
  75. package/dist/chunks/{UserLogsPanels-z4UhQtgu.js.map → UserLogsPanels-nbIrMym9.js.map} +1 -1
  76. package/dist/chunks/{UserTransferSelect-BJNUXMhl.js → UserTransferSelect-C8betaWG.js} +2 -2
  77. package/dist/chunks/{UserTransferSelect-BJNUXMhl.js.map → UserTransferSelect-C8betaWG.js.map} +1 -1
  78. package/dist/chunks/{UsersPage--p68X2Yr.js → UsersPage-BjTiNEqF.js} +2 -2
  79. package/dist/chunks/{UsersPage--p68X2Yr.js.map → UsersPage-BjTiNEqF.js.map} +1 -1
  80. package/dist/chunks/{browser-BKLM0ThC-3Xar5rZw.js → browser-BKLM0ThC-Cp7Gi4-F.js} +1 -1
  81. package/dist/chunks/{code-P0HJS_q1.js → code-LswxAr98.js} +1 -1
  82. package/dist/chunks/{direct-engine-BYEscsGg.js → direct-engine-Dv_6v4sx.js} +1 -1
  83. package/dist/chunks/{html-B__1Vr_C.js → html-BokjxlLb.js} +1 -1
  84. package/dist/chunks/{line-DzLRZVEq.js → line-Bqh6atHH.js} +1 -1
  85. package/dist/chunks/{lrc-kf-TilFB.js → lrc-T5e1svZG.js} +1 -1
  86. package/dist/chunks/{markdown-xIo-vnKK.js → markdown-BpWokN1s.js} +1 -1
  87. package/dist/chunks/{patch-BEaX-xYV.js → patch-BYVX5C9G.js} +1 -1
  88. package/dist/chunks/{worker-engine-FCdIQLzN.js → worker-engine-CtUAeI4G.js} +1 -1
  89. package/dist/core/hooks/queries/settings.d.ts +6 -0
  90. package/dist/core/hooks/queries/settings.d.ts.map +1 -1
  91. package/dist/core/hooks/queries/settings.js +34 -25
  92. package/dist/core/hooks/queries/settings.js.map +1 -1
  93. package/dist/core/hooks/useAuth.d.ts +2 -0
  94. package/dist/core/hooks/useAuth.d.ts.map +1 -1
  95. package/dist/core/hooks/useAuth.js.map +1 -1
  96. package/dist/core/index.js +7 -7
  97. package/dist/core/lib/go-auth-api.d.ts +1 -1
  98. package/dist/core/lib/go-auth-api.d.ts.map +1 -1
  99. package/dist/core/lib/go-auth-api.js +12 -8
  100. package/dist/core/lib/go-auth-api.js.map +1 -1
  101. package/dist/core/lib/native-operations.d.ts +85 -4
  102. package/dist/core/lib/native-operations.d.ts.map +1 -1
  103. package/dist/core/lib/native-operations.js +1 -0
  104. package/dist/core/lib/native-operations.js.map +1 -1
  105. package/dist/core/providers/GoAuthProvider.d.ts.map +1 -1
  106. package/dist/core/providers/GoAuthProvider.js +191 -142
  107. package/dist/core/providers/GoAuthProvider.js.map +1 -1
  108. package/dist/core/session-renewal.d.ts +15 -0
  109. package/dist/core/session-renewal.d.ts.map +1 -0
  110. package/dist/core/session-renewal.js +95 -0
  111. package/dist/core/session-renewal.js.map +1 -0
  112. package/dist/core/session-transition.d.ts +2 -0
  113. package/dist/core/session-transition.d.ts.map +1 -1
  114. package/dist/core/session-transition.js +20 -7
  115. package/dist/core/session-transition.js.map +1 -1
  116. package/dist/core/session.d.ts +4 -0
  117. package/dist/core/session.d.ts.map +1 -1
  118. package/dist/core/session.js +88 -61
  119. package/dist/core/session.js.map +1 -1
  120. package/dist/index.js +1 -1
  121. package/dist/{src-C8_bzBi4.js → src-D6UlLiVN.js} +2 -2
  122. package/dist/{src-C8_bzBi4.js.map → src-D6UlLiVN.js.map} +1 -1
  123. package/package.json +9 -9
  124. package/dist/chunks/ApiTokensPage-DiRMgViD.js +0 -2
  125. package/dist/chunks/ApiTokensPage-DiRMgViD.js.map +0 -1
  126. package/dist/chunks/EntityContextRuntime-BOJySc1D.js +0 -1
  127. package/dist/chunks/IdentitySecurityPage-DW5ybVL4.js +0 -9
  128. package/dist/chunks/IdentitySecurityPage-DW5ybVL4.js.map +0 -1
  129. package/dist/chunks/SettingModuleDetail-Dppk9An1.js +0 -17
  130. package/dist/chunks/SettingModuleDetail-Dppk9An1.js.map +0 -1
@@ -1,2 +0,0 @@
1
- import{n as e}from"./rolldown-runtime-Bruge11p.js";import{E as t,It as n,M as r,O as i,R as a,Rt as o,T as s,U as c,f as l,m as u,r as d,t as f,vt as p,yt as m,zt as h}from"./clipboard-dpqHdCyy.js";import{i as g,n as _}from"./feedback-B1UL2ETz.js";import{n as v,t as ee}from"./circle-check-big-BacE3WpZ.js";import{n as y,t as b}from"./plus-C_Qg8uc3.js";import{n as te,t as x}from"./useCrudOperationColumn-CPAzNGXp.js";import{n as ne}from"./listTableProps-BAghXGb4.js";import{i as S,n as C,r as w}from"./table-columns-CvT6OHd2.js";import{a as T,o as re,r as E,t as D}from"./EditFormModal-DVQksS9R.js";import{n as O,t as k}from"./ConfigurableTable-8sYmeul2.js";import{useEffect as A,useState as j}from"react";import{Fragment as M,jsx as N,jsxs as P}from"react/jsx-runtime";import{abortSubmit as F}from"@fudanda/admin-antd/core/lib/abort-submit";import{useLocation as I}from"react-router-dom";import{usePagination as L}from"@fudanda/admin-antd/core/hooks/usePagination";import{useAuth as R}from"@fudanda/admin-antd/core/hooks/useAuth";import{TOKEN_PERMISSION_KIND_LABELS as z,tokenPermissionGroups as B,tokenPermissionPreset as V,tokenPermissionPreview as H,useApiKeyPermissions as U,useApiTokenDetail as W,useApiTokenSecret as G,useDeleteApiToken as K,useProfileApiTokens as q,useSaveApiToken as J}from"@fudanda/admin-antd/core/hooks/queries/profile";function Y({permissions:e}){let t=H(e);return/* @__PURE__ */ P(`div`,{className:`token-permission-preview`,children:[/* @__PURE__ */ P(`p`,{role:`status`,children:[`已选 `,new Set(e).size,` 项权限 · 内置 MCP 工具 `,t.tools.length,` 项 · 已知 API 接口 `,t.operations.length,` 项`]}),/* @__PURE__ */ N(`p`,{children:`实际访问仍受账号当前权限、数据范围和宿主已开放接口限制。这些权限也可用于普通 API,MCP 查询预设不限制 Token 的接入方式。`}),t.tools.length>0&&/* @__PURE__ */ N(`div`,{className:`token-permission-tools`,children:t.tools.map(e=>/* @__PURE__ */ P(i,{children:[e.label,` `,/* @__PURE__ */ N(`code`,{children:e.name})]},e.name))}),t.operations.length>0&&/* @__PURE__ */ P(`details`,{children:[/* @__PURE__ */ N(`summary`,{children:`查看已知 API 接口`}),/* @__PURE__ */ N(`ul`,{children:t.operations.map(e=>/* @__PURE__ */ N(`li`,{children:/* @__PURE__ */ P(`code`,{children:[e.method,` `,e.path]})},e.id))})]}),t.extensions.length>0&&/* @__PURE__ */ P(`p`,{children:[`扩展权限的具体能力请核对宿主说明:`,t.extensions.join(`、`),`。`]})]})}function ie({permissions:e}){return/* @__PURE__ */ P(M,{children:[B(e).map(e=>/* @__PURE__ */ P(`section`,{className:`token-permission-group`,children:[/* @__PURE__ */ N(`h4`,{children:e.label}),e.items.map(e=>/* @__PURE__ */ P(`div`,{className:`token-permission-row`,children:[/* @__PURE__ */ P(`span`,{className:`token-permission-label`,children:[e.label,/* @__PURE__ */ N(`code`,{children:e.code})]}),/* @__PURE__ */ N(i,{className:`token-permission-kind`,children:z[e.kind]})]},e.code))]},e.label)),/* @__PURE__ */ N(Y,{permissions:e})]})}function ae({permissions:e,onDetails:t}){let n=B(e);return/* @__PURE__ */ P(`div`,{className:`token-permission-summary`,children:[/* @__PURE__ */ N(`span`,{children:e.length?`${new Set(e).size} 项 · ${n.slice(0,3).map(e=>e.label).join(`、`)}${n.length>3?`等`:``}`:`未授予权限`}),/* @__PURE__ */ N(u,{theme:`borderless`,size:`small`,onClick:t,children:`查看范围`})]})}function oe({value:e=[],onChange:t,permissions:n,disabled:r}){let[a,c]=j(``),d=B(n,a),f=new Set(e),p=e=>t?.(V(n,e)),m=(n,r)=>t?.(r?[.../* @__PURE__ */ new Set([...e,n])]:e.filter(e=>e!==n));return/* @__PURE__ */ P(`div`,{className:`token-permission-picker`,children:[/* @__PURE__ */ P(s,{wrap:!0,children:[/* @__PURE__ */ N(u,{size:`small`,disabled:r||!V(n,`mcp`).length,onClick:()=>p(`mcp`),children:`MCP 查询`}),/* @__PURE__ */ N(u,{size:`small`,disabled:r||!V(n,`read`).length,onClick:()=>p(`read`),children:`API 只读`}),/* @__PURE__ */ N(u,{size:`small`,disabled:r||!e.length,onClick:()=>t?.([]),children:`清空选择`})]}),/* @__PURE__ */ N(`p`,{className:`token-permission-hint`,children:`快捷选择会替换当前勾选;也可逐项自定义。只读按已知接口判断,未知扩展权限需手动选择。`}),/* @__PURE__ */ N(h,{"aria-label":`搜索权限`,placeholder:`搜索模块、中文名称或权限代码`,value:a,onChange:c,showClear:!0,disabled:r}),/* @__PURE__ */ P(`div`,{className:`token-permission-options`,"aria-label":`可授予权限`,children:[d.map(e=>/* @__PURE__ */ P(`fieldset`,{className:`token-permission-group`,children:[/* @__PURE__ */ N(`legend`,{children:e.label}),e.items.map(e=>/* @__PURE__ */ P(`div`,{className:`token-permission-row`,children:[/* @__PURE__ */ N(o,{disabled:r,checked:f.has(e.code),onChange:t=>m(e.code,!!t.target.checked),children:/* @__PURE__ */ P(`span`,{className:`token-permission-label`,children:[e.label,/* @__PURE__ */ N(`code`,{children:e.code})]})}),/* @__PURE__ */ N(i,{className:`token-permission-kind`,children:z[e.kind]})]},e.code))]},e.label)),!d.length&&/* @__PURE__ */ N(`p`,{children:n.length?`没有匹配的权限,已选项仍然保留。`:`当前没有可授予的权限。`})]}),e.some(e=>!n.includes(e))&&/* @__PURE__ */ N(l,{type:`warning`,description:`部分已选权限当前不可授予,请清空并重新选择。`}),/* @__PURE__ */ N(Y,{permissions:e})]})}var X;function Z(){return(Z=e((()=>{r(),X=c(oe)})))()}function se(){let{user:e}=R(),n=e.id,r=I().pathname===`/system/api-tokens`,[i,o]=j(null),s=G(n,r),c=s.value,[d,m]=j(!1),h=q(r),v=L({resetKey:n}),y=U(r),x=J(n,r),S=K(),T=re({save:{mutateAsync:({id:e,values:t})=>x.mutateAsync({id:e,values:t}),isPending:x.isPending},useDetail:W,toValues:e=>({name:e.name,expiresAt:`expiresAt`in e?e.expiresAt:void 0,permissions:`permissions`in e?e.permissions:[]}),beforeSave:e=>{let t=e.name.trim();return t||(g.error(`请填写 Token 名称`),F(`validation`)),e.permissions?.length||(g.error(`请选择 API Key 权限`),F(`validation`)),{name:t,...e.expiresAt?{expiresAt:new Date(e.expiresAt).toISOString()}:{},permissions:e.permissions}},defaults:{name:``,permissions:[]},successMessage:({isEdit:e})=>e?`Token 已更新`:null,onSaved:e=>{e&&`token`in e&&(m(!1),s.showCreated(e))}}),E=T.close;A(()=>{E(),o(null),m(!1)},[r,n,E]);async function O(e){await S.mutateAsync({params:{id:e}}),g.success(`Token 已撤销`)}function M(e){f(e).then(e=>{e?(m(!0),setTimeout(()=>m(!1),2e3)):g.error(`复制失败,请手动复制`)})}let z=te({edit:e=>T.openEdit(e),remove:e=>O(e.id),title:`确定要撤销该 Token 吗?`,content:`撤销后将无法恢复,使用该 Token 的调用将立即失效。`,successMessage:null,removeLabel:`撤销`,width:200,extra:e=>[{key:`view`,label:`查看 Key`,disabled:!e.canReveal,onClick:()=>{m(!1),s.reveal(e.id)}}]});return/* @__PURE__ */ P(`div`,{className:`page-container page-container--stretch`,children:[/* @__PURE__ */ P(Q,{type:`tertiary`,size:`small`,style:{display:`block`},children:[`Token 可通过 `,/* @__PURE__ */ N(`code`,{children:`Authorization: Bearer YOUR_TOKEN`}),` 头访问 API,每人最多 20 个。`]}),/* @__PURE__ */ N(k,{...ne(h,{pagination:v.buildPagination}),headerTitle:`API Token`,toolBarRender:()=>[/* @__PURE__ */ N(u,{type:`primary`,icon:/* @__PURE__ */ N(b,{size:14}),onClick:T.openCreate,children:`新建 Token`},`create`)],columns:[{title:`名称`,dataIndex:`name`,minWidth:150,render:e=>/* @__PURE__ */ N(Q,{strong:!0,children:e})},{title:`权限范围`,dataIndex:`permissions`,width:260,render:(e,t)=>/* @__PURE__ */ N(ae,{permissions:e,onDetails:()=>o(t)})},{title:`Token 前缀`,dataIndex:`tokenPrefix`,width:180,render:(e,t)=>/* @__PURE__ */ P(`div`,{children:[/* @__PURE__ */ N(`code`,{style:{fontSize:12},children:e}),!t.canReveal&&/* @__PURE__ */ N(`div`,{children:/* @__PURE__ */ N(Q,{type:`tertiary`,size:`small`,children:`仅存摘要,无法查看原文`})})]})},w(`最后使用`,`lastUsedAt`,{empty:`未使用`}),w(`过期时间`,`expiresAt`,{empty:`永久有效`}),C,z]}),r&&/* @__PURE__ */ P(D,{modal:T,title:T.isEdit?`编辑 API Token`:`新建 API Token`,okText:T.isEdit?`保存`:`创建`,cancelText:`取消`,width:740,centered:!0,formProps:{labelPosition:`top`},okButtonProps:{...T.modalProps.okButtonProps,disabled:T.detailLoading||y.isPending||!!y.error},children:[/* @__PURE__ */ N(a.Input,{field:`name`,maxLength:64,label:`名称`,placeholder:`如:本地开发、CI/CD 环境`,rules:[{required:!0,message:`请填写 Token 名称`}],style:{width:`100%`}}),y.error&&/* @__PURE__ */ N(l,{type:`danger`,description:/* @__PURE__ */ P(`span`,{children:[`权限范围加载失败。`,/* @__PURE__ */ N(u,{theme:`borderless`,onClick:()=>void y.refetch(),children:`重试`})]})}),/* @__PURE__ */ N(X,{field:`permissions`,label:`权限范围`,permissions:y.data??[],disabled:y.isPending||!!y.error,rules:[{validator:(e,t)=>Array.isArray(t)&&t.length>0,message:`请选择至少一项权限`}],extraText:`Token 权限不会超过账号当前权限;权限撤销、停用和改密后立即失效。`}),/* @__PURE__ */ N(a.DatePicker,{field:`expiresAt`,label:`过期时间`,type:`dateTime`,placeholder:`不填则永久有效`,style:{width:`100%`},disabledDate:e=>!!e&&e<new Date((/* @__PURE__ */ new Date()).setHours(0,0,0,0))})]}),/* @__PURE__ */ N(_,{title:i?`Token 权限范围 · ${i.name}`:`Token 权限范围`,visible:r&&!!i,onCancel:()=>o(null),footer:/* @__PURE__ */ N(u,{onClick:()=>o(null),children:`关闭`}),width:740,centered:!0,children:i&&/* @__PURE__ */ N(ie,{permissions:i.permissions})}),/* @__PURE__ */ P(_,{title:s.created?`Token 创建成功`:`查看 API Token`,visible:r&&(!!c||s.pending),closeOnEsc:!0,onCancel:s.close,footer:/* @__PURE__ */ N(u,{type:`primary`,onClick:s.close,children:`关闭`}),width:520,centered:!0,children:[/* @__PURE__ */ N(Q,{type:`warning`,style:{display:`block`,marginBottom:12,padding:`8px 12px`,background:`var(--arc-antd-color-warning-light-default)`,borderRadius:`var(--arc-antd-border-radius-medium)`},children:c?.canReveal?`请妥善保管此 Token,可在列表中再次查看或复制。`:`请立即复制并安全保存此 Token,关闭后将无法再次查看完整内容。`}),s.pending&&/* @__PURE__ */ N(t,{}),c&&/* @__PURE__ */ P(`div`,{className:`token-display`,children:[/* @__PURE__ */ N(`code`,{style:{flex:1,wordBreak:`break-all`,fontSize:12,lineHeight:1.6},children:c.token}),/* @__PURE__ */ N(u,{icon:N(d?ee:p,{size:14}),type:d?`tertiary`:`primary`,onClick:()=>M(c.token),style:{flexShrink:0},children:d?`已复制`:`复制`})]})]})]})}var Q;function $(){return($=e((()=>{y(),m(),v(),r(),O(),E(),x(),T(),d(),S(),Z(),{Text:Q}=n})))()}$();export{se as default};
2
- //# sourceMappingURL=ApiTokensPage-DiRMgViD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"ApiTokensPage-DiRMgViD.js","mappings":"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","names":["TokenPermissionPreview","preview","tokenPermissionPreview","permissions","Tag","tool","operation","TokenPermissionDetails","_Fragment","tokenPermissionGroups","group","item","TOKEN_PERMISSION_KIND_LABELS","TokenPermissionSummary","groups","Button","onDetails","TokenPermissionPicker","keyword","setKeyword","useState","selected","value","selectPreset","onChange","tokenPermissionPreset","preset","toggle","checked","code","Space","disabled","Input","Checkbox","event","Banner","withField","ApiTokensPage","useAuth","ownerId","user","active","useLocation","scopeToken","setScopeToken","tokenSecret","useApiTokenSecret","createdToken","tokenCopied","setTokenCopied","apiTokensQuery","useProfileApiTokens","tokenPagination","usePagination","keyPermissionsQuery","useApiKeyPermissions","saveTokenMutation","useSaveApiToken","deleteTokenMutation","useDeleteApiToken","tokenModal","useEditModal","useApiTokenDetail","record","name","values","Toast","abortSubmit","isEdit","saved","closeEditor","useEffect","handleDeleteToken","copyToken","copyText","token","ok","tokenOperationColumn","useCrudOperationColumn","Text","ConfigurableTable","listTableProps","Plus","v","dateTimeColumn","createdAtColumn","EditFormModal","Form","FormTokenPermissionPicker","date","Modal","Spin","jsx","CheckCircle","Copy","Typography"],"ignoreList":[],"sources":["../../src/pages/system/api-tokens/TokenPermissionPicker.tsx","../../src/pages/system/api-tokens/ApiTokensPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Banner, Button, Checkbox, Input, Space, Tag, withField } from '@/ui';\nimport {\n tokenPermissionGroups, tokenPermissionPreset,\n tokenPermissionPreview, TOKEN_PERMISSION_KIND_LABELS,\n} from '@fudanda/react-core/hooks/queries/profile';\n\ninterface PickerProps {\n value?: string[];\n onChange?: (value: string[]) => void;\n permissions: readonly string[];\n disabled?: boolean;\n}\n\nexport function TokenPermissionPreview({ permissions }: { readonly permissions: readonly string[] }) {\n const preview = tokenPermissionPreview(permissions);\n return <div className=\"token-permission-preview\">\n <p role=\"status\">已选 {new Set(permissions).size} 项权限 · 内置 MCP 工具 {preview.tools.length} 项 · 已知 API 接口 {preview.operations.length} 项</p>\n <p>实际访问仍受账号当前权限、数据范围和宿主已开放接口限制。这些权限也可用于普通 API,MCP 查询预设不限制 Token 的接入方式。</p>\n {preview.tools.length > 0 && <div className=\"token-permission-tools\">{preview.tools.map(tool => <Tag key={tool.name}>{tool.label} <code>{tool.name}</code></Tag>)}</div>}\n {preview.operations.length > 0 && <details><summary>查看已知 API 接口</summary><ul>{preview.operations.map(operation => <li key={operation.id}><code>{operation.method} {operation.path}</code></li>)}</ul></details>}\n {preview.extensions.length > 0 && <p>扩展权限的具体能力请核对宿主说明:{preview.extensions.join('、')}。</p>}\n </div>;\n}\n\nexport function TokenPermissionDetails({ permissions }: { readonly permissions: readonly string[] }) {\n return <>\n {tokenPermissionGroups(permissions).map(group => <section className=\"token-permission-group\" key={group.label}>\n <h4>{group.label}</h4>\n {group.items.map(item => <div className=\"token-permission-row\" key={item.code}>\n <span className=\"token-permission-label\">{item.label}<code>{item.code}</code></span>\n <Tag className=\"token-permission-kind\">{TOKEN_PERMISSION_KIND_LABELS[item.kind]}</Tag>\n </div>)}\n </section>)}\n <TokenPermissionPreview permissions={permissions}/>\n </>;\n}\n\nexport function TokenPermissionSummary({ permissions, onDetails }: {\n readonly permissions: readonly string[];\n readonly onDetails: () => void;\n}) {\n const groups = tokenPermissionGroups(permissions);\n return <div className=\"token-permission-summary\">\n <span>{permissions.length ? `${new Set(permissions).size} 项 · ${groups.slice(0, 3).map(group => group.label).join('、')}${groups.length > 3 ? '等' : ''}` : '未授予权限'}</span>\n <Button theme=\"borderless\" size=\"small\" onClick={onDetails}>查看范围</Button>\n </div>;\n}\n\nexport function TokenPermissionPicker({ value = [], onChange, permissions, disabled }: Readonly<PickerProps>) {\n const [keyword, setKeyword] = useState('');\n const groups = tokenPermissionGroups(permissions, keyword);\n const selected = new Set(value);\n const selectPreset = (preset: 'mcp' | 'read') => onChange?.(tokenPermissionPreset(permissions, preset));\n const toggle = (code: string, checked: boolean) => onChange?.(checked ? [...new Set([...value, code])] : value.filter(item => item !== code));\n return <div className=\"token-permission-picker\">\n <Space wrap>\n <Button size=\"small\" disabled={disabled || !tokenPermissionPreset(permissions, 'mcp').length} onClick={() => selectPreset('mcp')}>MCP 查询</Button>\n <Button size=\"small\" disabled={disabled || !tokenPermissionPreset(permissions, 'read').length} onClick={() => selectPreset('read')}>API 只读</Button>\n <Button size=\"small\" disabled={disabled || !value.length} onClick={() => onChange?.([])}>清空选择</Button>\n </Space>\n <p className=\"token-permission-hint\">快捷选择会替换当前勾选;也可逐项自定义。只读按已知接口判断,未知扩展权限需手动选择。</p>\n <Input aria-label=\"搜索权限\" placeholder=\"搜索模块、中文名称或权限代码\" value={keyword} onChange={setKeyword} showClear disabled={disabled}/>\n <div className=\"token-permission-options\" aria-label=\"可授予权限\">\n {groups.map(group => <fieldset className=\"token-permission-group\" key={group.label}>\n <legend>{group.label}</legend>\n {group.items.map(item => <div className=\"token-permission-row\" key={item.code}>\n <Checkbox disabled={disabled} checked={selected.has(item.code)} onChange={event => toggle(item.code, Boolean(event.target.checked))}>\n <span className=\"token-permission-label\">{item.label}<code>{item.code}</code></span>\n </Checkbox>\n <Tag className=\"token-permission-kind\">{TOKEN_PERMISSION_KIND_LABELS[item.kind]}</Tag>\n </div>)}\n </fieldset>)}\n {!groups.length && <p>{permissions.length ? '没有匹配的权限,已选项仍然保留。' : '当前没有可授予的权限。'}</p>}\n </div>\n {value.some(code => !permissions.includes(code)) && <Banner type=\"warning\" description=\"部分已选权限当前不可授予,请清空并重新选择。\"/>}\n <TokenPermissionPreview permissions={value}/>\n </div>;\n}\n\nexport const FormTokenPermissionPicker = withField(TokenPermissionPicker);\n","import { useEffect, useState } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { Plus, Copy, CheckCircle } from 'lucide-react';\nimport type { UserApiToken, UserApiTokenCreated } from '@fudanda/shared/identity';\nimport { useAuth } from '@fudanda/react-core/hooks/useAuth';\nimport { usePagination } from '@fudanda/react-core/hooks/usePagination';\nimport { abortSubmit } from '@fudanda/react-core/lib/abort-submit';\nimport { useSaveApiToken, useApiTokenDetail, useApiTokenSecret, useDeleteApiToken, useProfileApiTokens, useApiKeyPermissions } from '@fudanda/react-core/hooks/queries/profile';\nimport { Form, Button, Typography, Toast, Spin, Modal, Banner } from '@/ui';\nimport ConfigurableTable from '@/components/ConfigurableTable';\nimport { EditFormModal } from '@/components/EditFormModal';\nimport { listTableProps, useCrudOperationColumn } from '@/components/list-page';\nimport { useEditModal } from '@/hooks/useEditModal';\nimport { copyText } from '@/utils/clipboard';\nimport { createdAtColumn, dateTimeColumn } from '@/utils/table-columns';\nimport { FormTokenPermissionPicker, TokenPermissionDetails, TokenPermissionSummary } from './TokenPermissionPicker';\nimport './ApiTokensPage.css';\n\nconst { Text } = Typography;\n\nexport default function ApiTokensPage() {\n const { user } = useAuth();\n const ownerId = user!.id;\n const active = useLocation().pathname === '/system/api-tokens';\n const [scopeToken, setScopeToken] = useState<UserApiToken | null>(null);\n const tokenSecret = useApiTokenSecret(ownerId, active);\n const createdToken = tokenSecret.value;\n const [tokenCopied, setTokenCopied] = useState(false);\n const apiTokensQuery = useProfileApiTokens(active);\n const tokenPagination = usePagination({ resetKey: ownerId });\n const keyPermissionsQuery = useApiKeyPermissions(active);\n const saveTokenMutation = useSaveApiToken(ownerId, active);\n const deleteTokenMutation = useDeleteApiToken();\n // Secret responses stay in the open dialog, outside shared query caches.\n const tokenModal = useEditModal<UserApiToken | UserApiTokenCreated, {\n name: string;\n expiresAt?: Date | string | null;\n permissions?: string[];\n }, {\n name: string;\n expiresAt?: string;\n permissions: string[];\n }>({\n save: {\n mutateAsync: ({ id, values }) => saveTokenMutation.mutateAsync({ id, values }),\n isPending: saveTokenMutation.isPending,\n },\n useDetail: useApiTokenDetail,\n toValues: (record) => ({ name: record.name, expiresAt: 'expiresAt' in record ? record.expiresAt : undefined, permissions: 'permissions' in record ? record.permissions : [] }),\n beforeSave: (values) => {\n const name = values.name.trim();\n if (!name) {\n Toast.error('请填写 Token 名称');\n abortSubmit('validation');\n }\n if (!values.permissions?.length) {\n Toast.error('请选择 API Key 权限');\n abortSubmit('validation');\n }\n return { name, ...(values.expiresAt ? { expiresAt: new Date(values.expiresAt).toISOString() } : {}), ...({ permissions: values.permissions }) };\n },\n defaults: { name: '', permissions: [] },\n successMessage: ({ isEdit }) => isEdit ? 'Token 已更新' : null,\n onSaved: (saved) => { if (saved && 'token' in saved) { setTokenCopied(false); tokenSecret.showCreated(saved); } },\n });\n const closeEditor = tokenModal.close;\n useEffect(() => {\n closeEditor();\n setScopeToken(null);\n setTokenCopied(false);\n }, [active, ownerId, closeEditor]);\n async function handleDeleteToken(id: number) {\n await deleteTokenMutation.mutateAsync({ params: { id } });\n Toast.success('Token 已撤销');\n }\n function copyToken(token: string) {\n void copyText(token).then((ok) => {\n if (!ok) {\n Toast.error('复制失败,请手动复制');\n return;\n }\n setTokenCopied(true);\n setTimeout(() => setTokenCopied(false), 2000);\n });\n }\n const tokenOperationColumn = useCrudOperationColumn<UserApiToken>({\n edit: (record) => tokenModal.openEdit(record),\n remove: (record) => handleDeleteToken(record.id),\n title: '确定要撤销该 Token 吗?',\n content: '撤销后将无法恢复,使用该 Token 的调用将立即失效。',\n successMessage: null,\n removeLabel: '撤销',\n width: 200,\n extra: (record) => [{ key: 'view', label: '查看 Key', disabled: !record.canReveal,\n onClick: () => { setTokenCopied(false); void tokenSecret.reveal(record.id); } }],\n });\n return <div className=\"page-container page-container--stretch\">\n <Text type=\"tertiary\" size=\"small\" style={{ display: 'block' }}>\n Token 可通过 <code>Authorization: Bearer YOUR_TOKEN</code> 头访问 API,每人最多 20 个。\n </Text>\n <ConfigurableTable<UserApiToken> {...listTableProps(apiTokensQuery, { pagination: tokenPagination.buildPagination })}\n headerTitle=\"API Token\"\n toolBarRender={() => [<Button key=\"create\" type=\"primary\" icon={<Plus size={14}/>} onClick={tokenModal.openCreate}>新建 Token</Button>]}\n columns={[\n { title: '名称', dataIndex: 'name', minWidth: 150, render: (v: string) => <Text strong>{v}</Text> },\n { title: '权限范围', dataIndex: 'permissions', width: 260, render: (permissions: string[], token: UserApiToken) => <TokenPermissionSummary permissions={permissions} onDetails={() => setScopeToken(token)}/> },\n { title: 'Token 前缀', dataIndex: 'tokenPrefix', width: 180, render: (v: string, record: UserApiToken) => <div><code style={{ fontSize: 12 }}>{v}</code>{!record.canReveal && <div><Text type=\"tertiary\" size=\"small\">仅存摘要,无法查看原文</Text></div>}</div> },\n dateTimeColumn<NonNullable<typeof apiTokensQuery.data>[number]>('最后使用', 'lastUsedAt', { empty: '未使用' }),\n dateTimeColumn<NonNullable<typeof apiTokensQuery.data>[number]>('过期时间', 'expiresAt', { empty: '永久有效' }),\n createdAtColumn,\n tokenOperationColumn,\n ]}/>\n {/* ── 新建 Token Modal ──────────────────────────────────────────────────────────────── */}\n {active && <EditFormModal modal={tokenModal} title={tokenModal.isEdit ? '编辑 API Token' : '新建 API Token'} okText={tokenModal.isEdit ? '保存' : '创建'} cancelText=\"取消\" width={740} centered formProps={{ labelPosition: 'top' }} okButtonProps={{ ...tokenModal.modalProps.okButtonProps, disabled: tokenModal.detailLoading || keyPermissionsQuery.isPending || Boolean(keyPermissionsQuery.error) }}>\n <Form.Input field=\"name\" maxLength={64} label=\"名称\" placeholder=\"如:本地开发、CI/CD 环境\" rules={[{ required: true, message: '请填写 Token 名称' }]} style={{ width: '100%' }}/>\n {keyPermissionsQuery.error && <Banner type=\"danger\" description={<span>权限范围加载失败。<Button theme=\"borderless\" onClick={() => void keyPermissionsQuery.refetch()}>重试</Button></span>}/>}\n <FormTokenPermissionPicker field=\"permissions\" label=\"权限范围\" permissions={keyPermissionsQuery.data ?? []} disabled={keyPermissionsQuery.isPending || Boolean(keyPermissionsQuery.error)} rules={[{ validator: (_rule, value) => Array.isArray(value) && value.length > 0, message: '请选择至少一项权限' }]} extraText=\"Token 权限不会超过账号当前权限;权限撤销、停用和改密后立即失效。\"/>\n <Form.DatePicker field=\"expiresAt\" label=\"过期时间\" type=\"dateTime\" placeholder=\"不填则永久有效\" style={{ width: '100%' }} disabledDate={(date) => !!date && date < new Date(new Date().setHours(0, 0, 0, 0))}/>\n </EditFormModal>}\n\n <Modal title={scopeToken ? `Token 权限范围 · ${scopeToken.name}` : 'Token 权限范围'} visible={active && !!scopeToken} onCancel={() => setScopeToken(null)} footer={<Button onClick={() => setScopeToken(null)}>关闭</Button>} width={740} centered>\n {scopeToken && <TokenPermissionDetails permissions={scopeToken.permissions}/>}\n </Modal>\n\n {/* ── 创建成功展示完整 Token ──────────────────────────────────────────── */}\n <Modal title={tokenSecret.created ? 'Token 创建成功' : '查看 API Token'} visible={active && (!!createdToken || tokenSecret.pending)} closeOnEsc onCancel={tokenSecret.close} footer={<Button type=\"primary\" onClick={tokenSecret.close}>关闭</Button>} width={520} centered>\n <Text type=\"warning\" style={{ display: 'block', marginBottom: 12, padding: '8px 12px', background: 'var(--arc-antd-color-warning-light-default)', borderRadius: 'var(--arc-antd-border-radius-medium)' }}>\n {createdToken?.canReveal ? '请妥善保管此 Token,可在列表中再次查看或复制。' : '请立即复制并安全保存此 Token,关闭后将无法再次查看完整内容。'}\n </Text>\n {tokenSecret.pending && <Spin />}\n {createdToken && (<div className=\"token-display\">\n <code style={{ flex: 1, wordBreak: 'break-all', fontSize: 12, lineHeight: 1.6 }}>{createdToken.token}</code>\n <Button icon={tokenCopied ? <CheckCircle size={14}/> : <Copy size={14}/>} type={tokenCopied ? 'tertiary' : 'primary'} onClick={() => copyToken(createdToken.token)} style={{ flexShrink: 0 }}>\n {tokenCopied ? '已复制' : '复制'}\n </Button>\n </div>)}\n </Modal>\n </div>;\n}\n"]}
@@ -1 +0,0 @@
1
- import{n as e,r as t,t as n}from"./EntityContextRuntime-DbjyzCtY.js";t();export{n as EntityContextSheet,e as default};
@@ -1,9 +0,0 @@
1
- import{n as e}from"./rolldown-runtime-Bruge11p.js";import{H as t,Ht as n,M as r,R as i,T as a,V as o,Vt as s,b as c,bt as l,f as u,m as d,xt as f}from"./clipboard-dpqHdCyy.js";import{i as p}from"./feedback-B1UL2ETz.js";import{n as m,t as h}from"./MasterDetailLayout-KVizcSNO.js";import{i as g,s as _}from"./toolbar-controls-CrM8hKZg.js";import{n as v,t as ee}from"./info-CMEL1G7n.js";import{n as y,t as b}from"./monitor-DjHEaSak.js";import{n as x,t as S}from"./save-CSrPWJxC.js";import{n as C,t as te}from"./shield-Cf8xmgtJ.js";import{n as ne,r as re}from"./NavListPanel-Iz1RCkVw.js";import{i as ie,n as ae,r as w,t as T}from"./useSectionSelection-DBggfIoz.js";import{useEffect as E,useRef as D,useState as O}from"react";import{Fragment as k,jsx as A,jsxs as j}from"react/jsx-runtime";import{GoaError as M}from"@fudanda/client";import{usePermission as N}from"@fudanda/admin-antd/core/hooks/usePermission";import{useSaveSettings as P,useSettings as F}from"@fudanda/admin-antd/core/hooks/queries/settings";import{SESSION_CONCURRENCY_SCOPES as I,SESSION_CONCURRENCY_SCOPE_LABELS as L,SESSION_EXCEED_ACTIONS as R,SESSION_EXCEED_ACTION_LABELS as z}from"@fudanda/client/identity";import{formatSessionPolicyHint as B}from"@fudanda/client/settings";import{ApiIdentitySecuritySettingsSchema as V}from"@fudanda/client/api/settings";import{foundationSettingDefaults as H}from"@fudanda/client/settings/foundation";var U,W;function G(){return(G=e((()=>{
2
- /**
3
- * @license lucide-react v1.67.0 - ISC
4
- *
5
- * This source code is licensed under the ISC license.
6
- * See the LICENSE file in the root directory of this source tree.
7
- */
8
- f(),U={name:`key`,size:24,node:[[`path`,{d:`m2 21 9.6-9.6`,key:`9l79m3`}],[`path`,{d:`m7.5 15.5 2.3 2.3a1 1 0 0 1 0 1.4l-2.1 2.1a1 1 0 0 1-1.4 0L4 19`,key:`fw8biw`}],[`circle`,{cx:`15.5`,cy:`7.5`,r:`5.5`,key:`4wxmhb`}]]},U.node,W=l(U)})))()}function K(e){return e<=0?`unlimited`:e===1?`single`:`limited`}function q(){let e=o(),{values:r}=t(),a=r?.session,c={maxSessions:a?.maxSessions??0,scope:a?.scope??`global`,exceedAction:a?.exceedAction??`kick-oldest`},l=K(c.maxSessions),u=l===`unlimited`,d=B(c),f=t=>{let n=t===`unlimited`?0:t===`single`?1:Math.max(c.maxSessions,J);e.setValue(`session.maxSessions`,n)};return/* @__PURE__ */ j(k,{children:[/* @__PURE__ */ A(i.Slot,{label:`同时在线`,children:/* @__PURE__ */ j(`div`,{className:`session-policy-limit`,children:[/* @__PURE__ */ j(n,{value:l,onChange:e=>f(e.target.value),"aria-label":`同时在线上限`,children:[/* @__PURE__ */ A(s,{value:`unlimited`,children:`不限制`}),/* @__PURE__ */ A(s,{value:`single`,children:`仅一处登录`}),/* @__PURE__ */ A(s,{value:`limited`,children:`最多 N 处`})]}),/* @__PURE__ */ A(i.InputNumber,{field:`session.maxSessions`,noLabel:!0,min:l===`limited`?2:0,max:20,style:{width:120},fieldStyle:{padding:0},fieldClassName:l===`limited`?void 0:`session-policy-limit-hidden`,suffix:`处`,"aria-label":`同时在线上限(处)`})]})}),/* @__PURE__ */ A(i.Select,{field:`session.scope`,label:`统计范围`,disabled:u,style:{width:260},optionList:I.map(e=>({value:e,label:L[e]})),extraText:`按终端分别计算时,网页、移动审批、桌面端各占一份名额`}),/* @__PURE__ */ A(i.RadioGroup,{field:`session.exceedAction`,label:`超限处理`,disabled:u,options:R.map(e=>({value:e,label:z[e]})),extraText:`拒绝新登录时,用户可在登录页选择下线其它设备后继续登录`}),/* @__PURE__ */ A(i.Slot,{label:` `,children:/* @__PURE__ */ j(`div`,{className:`session-policy-preview`,role:`status`,children:[/* @__PURE__ */ A(ee,{size:14,"aria-hidden":!0}),/* @__PURE__ */ A(`span`,{children:d})]})})]})}var J;function Y(){return(Y=e((()=>{r(),v(),J=3})))()}function oe(){let{hasPermission:e}=N(),t=e(`system:identity-security:manage`),n=ae(Z,`password`),r=n.active,o=D(null),[s,l]=O(Q),f=F(`identitySecurity`,t),m=P(`identitySecurity`);E(()=>{f.data&&l(V.parse(f.data.effective))},[f.data]);async function _(){if(!o.current||!f.data)return;let e;try{e=await o.current?.validate()}catch{let e=o.current.getFieldsError().find(({errors:e})=>e.length>0)?.name[0];e===`password`?n.select(`password`):e===`loginChallenge`?n.select(`login-challenge`):e===`session`&&n.select(`session`);return}try{let t=await m.mutateAsync({body:{version:f.data?.version??0,data:e}});l(V.parse(t.effective)),p.success(`身份安全策略已保存`)}catch(e){e instanceof M&&e.status===409&&f.refetch()}}return t?/* @__PURE__ */ A(`div`,{className:`page-container page-container--stretch`,children:/* @__PURE__ */ A(h,{defaultSize:260,minSize:200,maxSize:400,persistKey:`identity-security`,style:{flex:1,minHeight:0,overflow:`hidden`},showDetail:r!==null,onBack:n.back,onResponsiveChange:n.responsiveChange,master:/* @__PURE__ */ A(ne,{title:`安全模块`,rawBody:!0,children:/* @__PURE__ */ A(w,{sections:X,active:r,onSelect:n.select})}),detail:/* @__PURE__ */ j(k,{children:[/* @__PURE__ */ A(h.Header,{extra:/* @__PURE__ */ j(a,{children:[/* @__PURE__ */ A(d,{type:`primary`,icon:/* @__PURE__ */ A(S,{size:14}),loading:m.isPending,disabled:!f.data||f.isFetching,onClick:_,children:`保存`}),/* @__PURE__ */ A(g,{onClick:()=>void f.refetch(),loading:f.isFetching,children:`重载`})]}),children:/* @__PURE__ */ A(`strong`,{children:X.find(e=>e.key===r)?.title??`身份安全`})}),/* @__PURE__ */ j(h.Body,{padding:24,children:[f.error&&/* @__PURE__ */ A(u,{type:`danger`,description:`身份安全策略加载失败,请重载后重试。`}),/* @__PURE__ */ j(i,{initValues:s,getFormApi:e=>{o.current=e},disabled:!f.data||m.isPending,labelPosition:`left`,labelWidth:150,style:{maxWidth:760},children:[/* @__PURE__ */ j(`section`,{hidden:r!==`password`,children:[/* @__PURE__ */ A(i.InputNumber,{field:`password.minLength`,label:`最小长度`,min:6,max:64}),/* @__PURE__ */ A(i.Switch,{field:`password.requireUppercase`,label:`必须包含大写`}),/* @__PURE__ */ A(i.Switch,{field:`password.requireSpecialChar`,label:`必须包含特殊字符`}),/* @__PURE__ */ A(i.Switch,{field:`password.expiryEnabled`,label:`启用密码过期`}),/* @__PURE__ */ A(i.InputNumber,{field:`password.expiryDays`,label:`过期天数`,min:1,max:3650})]}),/* @__PURE__ */ j(`section`,{hidden:r!==`login-challenge`,children:[/* @__PURE__ */ A(i.InputNumber,{field:`loginChallenge.maxAttemptsPerSource`,label:`单来源失败阈值`,min:1,max:1e3,extraText:`同一账号 + 同一 IP 的失败次数,达到后该来源需先过验证码`}),/* @__PURE__ */ A(i.InputNumber,{field:`loginChallenge.sourceLimit`,label:`多来源失败阈值`,min:1,max:100,extraText:`窗口内失败来源 IP 数,达到后该账号所有来源都需验证码(防分布式猜解)`}),/* @__PURE__ */ A(i.InputNumber,{field:`loginChallenge.windowMinutes`,label:`计数窗口(分钟)`,min:1,max:1440})]}),/* @__PURE__ */ A(`section`,{hidden:r!==`session`,children:/* @__PURE__ */ A(q,{})})]},JSON.stringify(s))]})]})})}):/* @__PURE__ */ A(c,{description:`无访问权限`})}var X,Z,Q;function $(){return($=e((()=>{r(),G(),y(),x(),C(),m(),re(),ie(),T(),_(),Y(),X=[{key:`password`,title:`密码策略`,description:`密码强度与过期规则`,icon:W},{key:`login-challenge`,title:`登录失败防护`,description:`失败阈值与验证码防护`,icon:te},{key:`session`,title:`会话并发`,description:`同时在线设备与冲突策略`,icon:b}],Z=X.map(e=>e.key),Q=H.identitySecurity})))()}$();export{oe as default};
9
- //# sourceMappingURL=IdentitySecurityPage-DW5ybVL4.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"IdentitySecurityPage-DW5ybVL4.js","mappings":";;;;;;;IASM,EAAa,CACjB,KAAM,MACN,KAAM,GACN,KAAM,CACJ,CAAC,OAAQ,CAAE,EAAG,gBAAiB,IAAK,QAAS,CAAC,EAC9C,CACE,OACA,CAAE,EAAG,kEAAmE,IAAK,QAAS,CACxF,EACA,CAAC,SAAU,CAAE,GAAI,OAAQ,GAAI,MAAO,EAAG,MAAO,IAAK,QAAS,CAAC,CAC/D,CACF,EACAA,EAAW,KACL,EAAMC,EAAiBD,CAAU,C,OCfvC,SAASE,EAAO,EAAgC,CAG5C,OAFIC,GAAe,EACR,YACJA,IAAgB,EAAI,SAAW,SAC1C,CAKA,SAAgBC,GAAsB,CAClC,IAAMC,EAAUC,EAAW,EACrB,CAAE,UAAWC,EAEhB,EACGC,EAAQC,GAAQ,QAChBC,EAAoC,CACtC,YAAaF,GAAO,aAAe,EACnC,MAAOA,GAAO,OAAS,SACvB,aAAcA,GAAO,cAAgB,aACzC,EACMG,EAAOT,EAAOQ,EAAQ,WAAW,EACjCE,EAAYD,IAAS,YACrBE,EAAUC,EAAwBJ,CAAO,EACzCK,EAAc,GAAoB,CACpC,IAAMC,EAAQC,IAAS,YAAc,EAAIA,IAAS,SAAW,EAAI,KAAK,IAAIP,EAAQ,YAAaQ,CAAe,EAC9Gb,EAAQ,SAAS,sBAAuBW,CAAK,CACjD,EACA,MAAQ,kBAAAG,EAAA,WACN,kBAACC,EAAK,KAAN,CAAW,MAAM,OACf,2BAAC,MAAD,CAAK,UAAU,uBAAf,UACE,kBAACC,EAAD,CAAY,MAAOV,EAAM,SAAW,GAAMI,EAAW,EAAE,OAAO,KAAkB,EAAG,aAAW,SAA9F,UACE,kBAACO,EAAD,CAAO,MAAM,YAAY,cAAU,GACnC,kBAACA,EAAD,CAAO,MAAM,SAAS,gBAAY,GAClC,kBAACA,EAAD,CAAO,MAAM,UAAU,iBAAa,EAC1B,CAEZ,qBAACF,EAAK,YAAN,CAAkB,MAAM,sBAAsB,WAAQ,IAAKT,IAAS,UAAY,EAAI,EAAG,IAAK,GAAI,MAAO,CAAE,MAAO,GAAI,EAAG,WAAY,CAAE,QAAS,CAAE,EAAG,eAAgBA,IAAS,UAAY,OAAY,8BAA+B,OAAO,IAAI,aAAW,WAAY,EAClQ,C,EACI,GACX,kBAACS,EAAK,OAAN,CAAa,MAAM,gBAAgB,MAAM,OAAO,SAAUR,EAAW,MAAO,CAAE,MAAO,GAAI,EAAG,WAAYW,EAA2B,IAAK,IAAW,CAAE,QAAO,MAAOC,EAAiCR,EAAO,EAAE,EAAG,UAAU,4BAA6B,GACvP,kBAACI,EAAK,WAAN,CAAiB,MAAM,uBAAuB,MAAM,OAAO,SAAUR,EAAW,QAASa,EAAuB,IAAK,IAAW,CAAE,QAAO,MAAOC,EAA6BV,EAAO,EAAE,EAAG,UAAU,6BAA8B,GACjO,kBAACI,EAAK,KAAN,CAAW,MAAM,IACf,2BAAC,MAAD,CAAK,UAAU,yBAAyB,KAAK,SAA7C,UACE,kBAACO,GAAD,CAAM,KAAM,GAAI,gBAAY,GAC5B,kBAAC,OAAD,UAAOd,CAAc,EAClB,C,EACI,EACX,GACN,C,4CAjDM,EAAkB,C,OCmBxB,SAAwBe,IAAuB,CAC3C,GAAM,CAAE,iBAAkBC,EAAc,EAClCC,EAAkBC,EAAc,iCAAiC,EACjEC,EAAaC,GAAoBC,EAAc,UAAU,EACzDC,EAAgBH,EAAW,OAE3B3B,EAAU+B,EAAuB,IAAI,EACrC,CAACC,EAAQC,GAAaC,EAAmCC,CAAa,EAEtEC,EAAcC,EAAY,mBAAoBZ,CAAe,EAC7Da,EAAqBC,EAAgB,kBAAkB,EAC7DC,MAAgB,CACRJ,EAAY,MACZH,EAAUQ,EAA+B,MAAML,EAAY,KAAK,SAAS,CAAC,CAClF,EAAG,CAACA,EAAY,IAAI,CAAC,EACrB,eAAeM,GAAmB,CAC9B,GAAI,CAAC1C,EAAQ,SAAW,CAACoC,EAAY,KAAM,OAC3C,IAAIhC,EACJ,GAAI,CACA,EAAS,MAAMJ,EAAQ,SAAS,SAAS,CAC7C,MACM,CACF,IAAM2C,EAAQ3C,EAAQ,QAAQ,eAAe,CAAC,CAAC,MAAM,CAAE,YAAa4C,EAAO,OAAS,CAAC,CAAC,EAAE,KAAK,GACzFD,IAAU,WAAYhB,EAAW,OAAO,UAAU,EAC7CgB,IAAU,iBAAkBhB,EAAW,OAAO,iBAAiB,EAC/DgB,IAAU,WAAWhB,EAAW,OAAO,SAAS,EACzD,MACJ,CACA,GAAI,CACA,IAAMkB,EAAQ,MAAMP,EAAmB,YAAY,CAAE,KAAM,CAAE,QAASF,EAAY,MAAM,SAAW,EAAG,KAAMhC,CAAO,CAAE,CAAC,EACtH6B,EAAUQ,EAA+B,MAAMI,EAAM,SAAS,CAAC,EAC/DC,EAAM,QAAQ,WAAW,CAC7B,OACOC,EAAK,CAEJA,aAAeC,GAAYD,EAAI,SAAW,KAC1CX,EAAiB,QAAQ,CACjC,CACJ,CAGA,OAFKX,EAEG,kBAAC,MAAD,CAAK,UAAU,yCACrB,2BAACwB,EAAD,CAAoB,YAAa,IAAK,QAAS,IAAK,QAAS,IAAK,WAAW,oBAC3E,MAAO,CAAE,KAAM,EAAG,UAAW,EAAG,SAAU,QAAS,EAAG,WAAYnB,IAAkB,KACpF,OAAQH,EAAW,KAAM,mBAAoBA,EAAW,iBACxD,OAAQ,kBAACuB,GAAD,CAAc,MAAM,OAAO,WACjC,2BAACC,EAAD,CAAmB,SAAUC,EAAU,OAAQtB,EAAe,SAAUH,EAAW,MAAS,EAChF,GACd,OAAQ,kBAAAb,EAAA,WACN,kBAACmC,EAAmB,OAApB,CAA2B,MAAO,kBAACI,EAAD,WAChC,kBAACC,EAAD,CAAQ,KAAK,UAAU,KAAM,kBAACC,EAAD,CAAM,KAAM,EAAI,GAAG,QAASjB,EAAmB,UAAW,SAAU,CAACF,EAAY,MAAQA,EAAY,WAAY,QAASM,EAAkB,aAAU,GACnL,kBAACc,EAAD,CAAe,YAAe,KAAKpB,EAAY,QAAQ,EAAG,QAASA,EAAY,WAAY,aAAiB,EACvG,IACL,2BAAC,SAAD,UAASgB,EAAS,KAAK,GAAWK,EAAQ,MAAQ3B,CAAa,CAAC,EAAE,OAAS,MAAe,EACjE,GAC3B,kBAACmB,EAAmB,KAApB,CAAyB,QAAS,GAAlC,UACGb,EAAY,OAAS,kBAACsB,EAAD,CAAQ,KAAK,SAAS,YAAY,oBAAsB,GAC9E,kBAAC3C,EAAD,CAAmC,WAAYiB,EAAQ,WAAa,GAAQ,CAAE,EAAQ,QAAU2B,CAAK,EAAG,SAAU,CAACvB,EAAY,MAAQE,EAAmB,UAAW,cAAc,OAAO,WAAY,IAAK,MAAO,CAAE,SAAU,GAAI,EAAlO,UACE,kBAAC,UAAD,CAAS,OAAQR,IAAkB,WAAnC,UACA,kBAACf,EAAK,YAAN,CAAkB,MAAM,qBAAqB,MAAM,OAAO,IAAK,EAAG,IAAK,EAAI,GAC3E,kBAACA,EAAK,OAAN,CAAa,MAAM,4BAA4B,MAAM,QAAS,GAC9D,kBAACA,EAAK,OAAN,CAAa,MAAM,8BAA8B,MAAM,UAAW,GAClE,kBAACA,EAAK,OAAN,CAAa,MAAM,yBAAyB,MAAM,QAAS,GAC3D,kBAACA,EAAK,YAAN,CAAkB,MAAM,sBAAsB,MAAM,OAAO,IAAK,EAAG,IAAK,IAAM,EACrE,C,GAGT,kBAAC,UAAD,CAAS,OAAQe,IAAkB,kBAAnC,UACA,kBAACf,EAAK,YAAN,CAAkB,MAAM,sCAAsC,MAAM,UAAU,IAAK,EAAG,IAAK,IAAM,UAAU,iCAAkC,GAC7I,kBAACA,EAAK,YAAN,CAAkB,MAAM,6BAA6B,MAAM,UAAU,IAAK,EAAG,IAAK,IAAK,UAAU,sCAAuC,GACxI,kBAACA,EAAK,YAAN,CAAkB,MAAM,+BAA+B,MAAM,WAAW,IAAK,EAAG,IAAK,IAAM,EAClF,C,GACT,kBAAC,UAAD,CAAS,OAAQe,IAAkB,UACnC,2BAAC/B,EAAD,CAAsB,EACb,EACL,CAlBK,OAAK,UAAUiC,CAAM,CAkB1B,CACiB,CACzB,KAAI,EACL,GAtCM,kBAAC4B,EAAD,CAAO,YAAY,OAAQ,EAuC1C,C,sFAvFM,EAAW,CACb,CAAE,IAAK,WAAY,MAAO,OAAQ,YAAa,YAAa,KAAMC,CAAI,EACtE,CAAE,IAAK,kBAAmB,MAAO,SAAU,YAAa,aAAc,KAAMC,EAAO,EACnF,CAAE,IAAK,UAAW,MAAO,OAAQ,YAAa,cAAe,KAAMC,CAAQ,CAC/E,EACM,EAAeX,EAAS,IAAI,GAAWK,EAAQ,GAAG,EAElD,EAA0CO,EAA0B,gB","names":["__iconData","createLucideIcon","modeOf","maxSessions","SessionPolicyFields","formApi","useFormApi","useFormState","draft","values","session","mode","unlimited","preview","formatSessionPolicyHint","changeMode","value","next","DEFAULT_LIMITED","_Fragment","Form","RadioGroup","Radio","SESSION_CONCURRENCY_SCOPES","SESSION_CONCURRENCY_SCOPE_LABELS","SESSION_EXCEED_ACTIONS","SESSION_EXCEED_ACTION_LABELS","Info","IdentitySecurityPage","usePermission","canManagePolicy","hasPermission","navigation","useSectionSelection","SECTION_KEYS","activeSection","useRef","policy","setPolicy","useState","defaultPolicy","policyQuery","useSettings","savePolicyMutation","useSaveSettings","useEffect","identitySecuritySettingsSchema","handleSavePolicy","field","errors","saved","Toast","err","GoaError","MasterDetailLayout","NavListPanel","SectionNavigation","SECTIONS","Space","Button","Save","RefreshButton","section","Banner","api","Empty","Key","Shield","Monitor","foundationSettingDefaults"],"ignoreList":[0],"sources":["../../../../node_modules/lucide-react/dist/esm/icons/key.mjs","../../src/pages/system/identity-security/SessionPolicyFields.tsx","../../src/pages/system/identity-security/IdentitySecurityPage.tsx"],"sourcesContent":[null,"import { Form, RadioGroup, Radio, useFormApi, useFormState } from \"@/ui\";\nimport { Info } from 'lucide-react';\nimport { SESSION_CONCURRENCY_SCOPE_LABELS, SESSION_CONCURRENCY_SCOPES, SESSION_EXCEED_ACTION_LABELS, SESSION_EXCEED_ACTIONS } from '@fudanda/shared/identity';\nimport { formatSessionPolicyHint, type SessionConcurrencyPolicy } from '@fudanda/shared/settings';\ntype LimitMode = 'unlimited' | 'single' | 'limited';\n/** 「最多 N 处」的默认起点:从单处切过来时给一个有意义的 N,避免落在 0 / 1 上又变回别的模式 */\nconst DEFAULT_LIMITED = 3;\nfunction modeOf(maxSessions: number): LimitMode {\n if (maxSessions <= 0)\n return 'unlimited';\n return maxSessions === 1 ? 'single' : 'limited';\n}\n/**\n * 身份安全 → 会话并发。渲染在 <Form> 内:同时在线用单选表达「不限 / 单处 / 最多 N 处」,\n * 落到同一个 session.maxSessions 字段;不限制时范围与超限处理置灰。\n */\nexport function SessionPolicyFields() {\n const formApi = useFormApi();\n const { values } = useFormState<{\n session?: Partial<SessionConcurrencyPolicy>;\n }>();\n const draft = values?.session;\n const session: SessionConcurrencyPolicy = {\n maxSessions: draft?.maxSessions ?? 0,\n scope: draft?.scope ?? 'global',\n exceedAction: draft?.exceedAction ?? 'kick-oldest',\n };\n const mode = modeOf(session.maxSessions);\n const unlimited = mode === 'unlimited';\n const preview = formatSessionPolicyHint(session);\n const changeMode = (next: LimitMode) => {\n const value = next === 'unlimited' ? 0 : next === 'single' ? 1 : Math.max(session.maxSessions, DEFAULT_LIMITED);\n formApi.setValue('session.maxSessions', value);\n };\n return (<>\n <Form.Slot label=\"同时在线\">\n <div className=\"session-policy-limit\">\n <RadioGroup value={mode} onChange={(e) => changeMode(e.target.value as LimitMode)} aria-label=\"同时在线上限\">\n <Radio value=\"unlimited\">不限制</Radio>\n <Radio value=\"single\">仅一处登录</Radio>\n <Radio value=\"limited\">最多 N 处</Radio>\n </RadioGroup>\n {/* 常驻挂载:Semi Field 卸载会连带移除表单值,切回「不限 / 单处」时 maxSessions 必须仍在提交体里;非 N 处模式仅隐藏 */}\n <Form.InputNumber field=\"session.maxSessions\" noLabel min={mode === 'limited' ? 2 : 0} max={20} style={{ width: 120 }} fieldStyle={{ padding: 0 }} fieldClassName={mode === 'limited' ? undefined : 'session-policy-limit-hidden'} suffix=\"处\" aria-label=\"同时在线上限(处)\"/>\n </div>\n </Form.Slot>\n <Form.Select field=\"session.scope\" label=\"统计范围\" disabled={unlimited} style={{ width: 260 }} optionList={SESSION_CONCURRENCY_SCOPES.map((value) => ({ value, label: SESSION_CONCURRENCY_SCOPE_LABELS[value] }))} extraText=\"按终端分别计算时,网页、移动审批、桌面端各占一份名额\"/>\n <Form.RadioGroup field=\"session.exceedAction\" label=\"超限处理\" disabled={unlimited} options={SESSION_EXCEED_ACTIONS.map((value) => ({ value, label: SESSION_EXCEED_ACTION_LABELS[value] }))} extraText=\"拒绝新登录时,用户可在登录页选择下线其它设备后继续登录\"/>\n <Form.Slot label=\" \">\n <div className=\"session-policy-preview\" role=\"status\">\n <Info size={14} aria-hidden/>\n <span>{preview}</span>\n </div>\n </Form.Slot>\n </>);\n}\n","import { useEffect, useRef, useState } from 'react';\nimport { Banner, Button, Empty, Form, Space, Toast } from \"@/ui\";\nimport type { FormApi } from \"@/ui\";\nimport { Key, Monitor, Save, Shield } from 'lucide-react';\nimport { ApiIdentitySecuritySettingsSchema as identitySecuritySettingsSchema, type ApiIdentitySecuritySettings as IdentitySecuritySettings } from '@fudanda/shared/api/settings';\nimport { foundationSettingDefaults } from '@fudanda/shared/settings/foundation';\nimport { MasterDetailLayout } from '@/components/MasterDetailLayout';\nimport { NavListPanel } from '@/components/NavListPanel';\nimport { SectionNavigation } from '@/components/SectionNavigation';\nimport { useSectionSelection } from '@/hooks/useSectionSelection';\n\nimport { usePermission } from \"@fudanda/react-core/hooks/usePermission\";\nimport { useSaveSettings, useSettings } from \"@fudanda/react-core/hooks/queries/settings\";\nimport { GoaError } from '@fudanda/client';\nimport { RefreshButton } from \"@/components/toolbar-controls\";\nimport { SessionPolicyFields } from \"@/pages/system/identity-security/SessionPolicyFields\";\nimport \"@/pages/system/identity-security/IdentitySecurityPage.css\";\nconst SECTIONS = [\n { key: 'password', title: '密码策略', description: '密码强度与过期规则', icon: Key },\n { key: 'login-challenge', title: '登录失败防护', description: '失败阈值与验证码防护', icon: Shield },\n { key: 'session', title: '会话并发', description: '同时在线设备与冲突策略', icon: Monitor },\n] as const;\nconst SECTION_KEYS = SECTIONS.map(section => section.key);\n// 默认值以 shared schema 为唯一真相(通用设置页与服务端同源)\nconst defaultPolicy: IdentitySecuritySettings = foundationSettingDefaults.identitySecurity;\nexport default function IdentitySecurityPage() {\n const { hasPermission } = usePermission();\n const canManagePolicy = hasPermission('system:identity-security:manage');\n const navigation = useSectionSelection(SECTION_KEYS, 'password');\n const activeSection = navigation.active;\n // useEditModal 例外:页面级全局配置表单(身份安全策略),保存后不关闭;表单 key 跟随策略值重挂载\n const formApi = useRef<FormApi | null>(null);\n const [policy, setPolicy] = useState<IdentitySecuritySettings>(defaultPolicy);\n // 页面级全局配置表单(无弹窗、保存后不关闭),不走 useEditModal;策略由运行时设置 identitySecurity 模块承载\n const policyQuery = useSettings('identitySecurity', canManagePolicy);\n const savePolicyMutation = useSaveSettings('identitySecurity');\n useEffect(() => {\n if (policyQuery.data)\n setPolicy(identitySecuritySettingsSchema.parse(policyQuery.data.effective));\n }, [policyQuery.data]);\n async function handleSavePolicy() {\n if (!formApi.current || !policyQuery.data) return;\n let values: IdentitySecuritySettings;\n try {\n values = await formApi.current?.validate() as IdentitySecuritySettings;\n }\n catch {\n const field = formApi.current.getFieldsError().find(({ errors }) => errors.length > 0)?.name[0];\n if (field === 'password') navigation.select('password');\n else if (field === 'loginChallenge') navigation.select('login-challenge');\n else if (field === 'session') navigation.select('session');\n return;\n }\n try {\n const saved = await savePolicyMutation.mutateAsync({ body: { version: policyQuery.data?.version ?? 0, data: values } });\n setPolicy(identitySecuritySettingsSchema.parse(saved.effective));\n Toast.success('身份安全策略已保存');\n }\n catch (err) {\n // 409 = 他人已修改:请求层已提示,重载最新值供比对后再保存\n if (err instanceof GoaError && err.status === 409)\n void policyQuery.refetch();\n }\n }\n if (!canManagePolicy)\n return <Empty description=\"无访问权限\"/>;\n return (<div className=\"page-container page-container--stretch\">\n <MasterDetailLayout defaultSize={260} minSize={200} maxSize={400} persistKey=\"identity-security\"\n style={{ flex: 1, minHeight: 0, overflow: 'hidden' }} showDetail={activeSection !== null}\n onBack={navigation.back} onResponsiveChange={navigation.responsiveChange}\n master={<NavListPanel title=\"安全模块\" rawBody>\n <SectionNavigation sections={SECTIONS} active={activeSection} onSelect={navigation.select} />\n </NavListPanel>}\n detail={<>\n <MasterDetailLayout.Header extra={<Space>\n <Button type=\"primary\" icon={<Save size={14}/>} loading={savePolicyMutation.isPending} disabled={!policyQuery.data || policyQuery.isFetching} onClick={handleSavePolicy}>保存</Button>\n <RefreshButton onClick={() => void policyQuery.refetch()} loading={policyQuery.isFetching}>重载</RefreshButton>\n </Space>}>\n <strong>{SECTIONS.find(section => section.key === activeSection)?.title ?? '身份安全'}</strong>\n </MasterDetailLayout.Header>\n <MasterDetailLayout.Body padding={24}>\n {policyQuery.error && <Banner type=\"danger\" description=\"身份安全策略加载失败,请重载后重试。\" />}\n <Form key={JSON.stringify(policy)} initValues={policy} getFormApi={(api) => { formApi.current = api; }} disabled={!policyQuery.data || savePolicyMutation.isPending} labelPosition=\"left\" labelWidth={150} style={{ maxWidth: 760 }}>\n <section hidden={activeSection !== 'password'}>\n <Form.InputNumber field=\"password.minLength\" label=\"最小长度\" min={6} max={64}/>\n <Form.Switch field=\"password.requireUppercase\" label=\"必须包含大写\"/>\n <Form.Switch field=\"password.requireSpecialChar\" label=\"必须包含特殊字符\"/>\n <Form.Switch field=\"password.expiryEnabled\" label=\"启用密码过期\"/>\n <Form.InputNumber field=\"password.expiryDays\" label=\"过期天数\" min={1} max={3650}/>\n </section>\n\n {/* 登录失败不再锁定账号:达到阈值只要求验证码,真正的用户凭正确密码 + 验证码仍可登录 */}\n <section hidden={activeSection !== 'login-challenge'}>\n <Form.InputNumber field=\"loginChallenge.maxAttemptsPerSource\" label=\"单来源失败阈值\" min={1} max={1000} extraText=\"同一账号 + 同一 IP 的失败次数,达到后该来源需先过验证码\"/>\n <Form.InputNumber field=\"loginChallenge.sourceLimit\" label=\"多来源失败阈值\" min={1} max={100} extraText=\"窗口内失败来源 IP 数,达到后该账号所有来源都需验证码(防分布式猜解)\"/>\n <Form.InputNumber field=\"loginChallenge.windowMinutes\" label=\"计数窗口(分钟)\" min={1} max={1440}/>\n </section>\n <section hidden={activeSection !== 'session'}>\n <SessionPolicyFields />\n </section>\n </Form>\n </MasterDetailLayout.Body>\n </>} />\n </div>);\n}\n"]}
@@ -1,17 +0,0 @@
1
- import{n as e}from"./rolldown-runtime-Bruge11p.js";import{A as t,Bt as n,E as r,It as i,M as a,N as o,O as s,Rt as c,T as l,Ut as u,Wt as d,b as f,bt as p,dt as m,f as h,g,it as _,j as v,m as y,n as b,r as x,rt as ee,st as te,ut as ne,xt as S,zt as C}from"./clipboard-dpqHdCyy.js";import{i as w}from"./feedback-B1UL2ETz.js";import{n as T,t as re}from"./MasterDetailLayout-KVizcSNO.js";import{a as ie,i as ae,s as E}from"./toolbar-controls-CrM8hKZg.js";import{n as oe,t as se}from"./save-CSrPWJxC.js";import{a as ce,i as le,n as ue,o as de,r as fe,s as pe,t as me}from"./schemas-CVYuqWs9.js";import{n as he,t as ge}from"./rendered-slot-G6nvF0nR.js";import{n as _e,t as ve}from"./SearchToolbar-KtXD_Wgr.js";import{n as ye,t as be}from"./webmcp-control-BR0YABIY.js";import{a as xe,o as Se}from"./App-BF96TQ_D.js";import{i as Ce,r as we}from"./ArcBaseAdmin--xQxd4tg.js";import{a as D,i as O,o as k,r as A}from"./SettingRow-Bh2bBjdL.js";import{Fragment as Te,useEffect as Ee,useMemo as j,useRef as De,useState as Oe}from"react";import{Fragment as M,jsx as N,jsxs as P}from"react/jsx-runtime";import{Alert as ke,Button as Ae,Space as je,Switch as Me,Typography as F}from"antd";import{GoaError as Ne}from"@fudanda/client";import{goTransport as I}from"@fudanda/admin-antd/core/lib/go-transport";import{Link as Pe}from"react-router-dom";import{settingsContract as Fe,settingsWriteOperations as Ie}from"@fudanda/admin-antd/core/lib/native-operations";import{usePermission as Le}from"@fudanda/admin-antd/core/hooks/usePermission";import{useRenderedSlot as Re}from"@fudanda/admin-antd/core/hooks/useRenderedSlot";import{useListSearch as ze}from"@fudanda/admin-antd/core/hooks/useListSearch";import{useCurrentUserMenuTree as Be,useMenuTree as Ve}from"@fudanda/admin-antd/core/hooks/queries/menus";import{settingsKeys as He,useSaveSettings as Ue,useSettings as We}from"@fudanda/admin-antd/core/hooks/queries/settings";import{THEME_COLOR_PRESETS as Ge}from"@fudanda/admin-antd/core/lib/theme-color";import{textMatches as Ke}from"@fudanda/admin-antd/core/utils/pinyin";import{usePinyinReady as qe}from"@fudanda/admin-antd/core/hooks/usePinyinReady";import{canOverridePreference as L,defaultPreferences as Je,describePreferenceCondition as R,getPreferenceValue as z,isPreferenceApplicable as Ye,preferenceDefinitions as B,preferenceGroups as V,resolvePreferences as Xe,setPreferenceValue as H}from"@fudanda/client/preferences";import{CAPTCHA_COMPLEXITY_LABELS as Ze,SETTINGS_MODULES as Qe}from"@fudanda/client/settings";import{foundationSettingSchemas as $e}from"@fudanda/client/settings/foundation";import{mcpTools as et}from"@fudanda/client/api/mcp-tools";var U,W;function G(){return(G=e((()=>{
2
- /**
3
- * @license lucide-react v1.67.0 - ISC
4
- *
5
- * This source code is licensed under the ISC license.
6
- * See the LICENSE file in the root directory of this source tree.
7
- */
8
- S(),U={name:`external-link`,size:24,node:[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]},U.node,W=p(U)})))()}var K,tt;function nt(){return(nt=e((()=>{
9
- /**
10
- * @license lucide-react v1.67.0 - ISC
11
- *
12
- * This source code is licensed under the ISC license.
13
- * See the LICENSE file in the root directory of this source tree.
14
- */
15
- S(),K={name:`image-plus`,size:24,node:[[`path`,{d:`M16 5h6`,key:`1vod17`}],[`path`,{d:`M19 2v6`,key:`4bpg5p`}],[`path`,{d:`M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5`,key:`1ue2ih`}],[`path`,{d:`m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21`,key:`1xmnt7`}],[`circle`,{cx:`9`,cy:`9`,r:`2`,key:`af1f0g`}]]},K.node,tt=p(K)})))()}function rt({primary:e,filters:t,onRefresh:n,refreshing:r,onReset:i,actions:a,mobileActions:o,extra:s,filterTitle:c,actionTitle:l,className:u}){let d=n?/* @__PURE__ */ N(ae,{onClick:n,loading:r}):null,f=Re(),p=q(a)||q(s)?/* @__PURE__ */ P(M,{children:[/* @__PURE__ */ N(ge,{ref:f.probeRef,children:a}),s]}):void 0;return/* @__PURE__ */ N(ve,{className:u,primary:/* @__PURE__ */ P(M,{children:[e,t,d,i?/* @__PURE__ */ N(ie,{onClick:i}):null]}),actions:p,mobilePrimary:/* @__PURE__ */ P(M,{children:[e,d]}),mobileFilters:q(t)?t:void 0,mobileActions:o??(f.rendered?a:!1),filterTitle:c,actionTitle:l,onFilterReset:i})}var q;function it(){return(it=e((()=>{_e(),E(),he(),q=e=>!!e})))()}var at;function ot(){return(ot=e((()=>{at={captchaEnabled:{title:`登录验证码`,description:`开启后登录必须输入图形验证码`},captchaComplexity:{title:`验证码复杂度`,description:`仅在开启登录验证码后生效`},uploadValidateType:{title:`校验真实文件类型`,description:`基于 magic bytes 识别,防止伪造 MIME type 绕过校验`},uploadAllowedTypes:{title:`允许的 MIME 类型`,description:`支持通配符(如 image/*);填 * 或 */* 放行全部类型`},uploadMaxSizeMb:{title:`单文件大小上限(MB)`,description:`0 表示不限制;超过的上传(含分片)将被拒绝`},chunkThresholdMb:{title:`分片上传阈值(MB)`,description:`超过该大小的文件走分片上传 + 断点续传,不超过则单请求上传;受反向代理请求体上限约束`},chunkSizeMb:{title:`分片大小(MB)`,description:`服务端裁定的分片基线,不低于对象存储要求的最小分片;文件过大时自动上调以不超过片数上限`},watermark:{title:`水印`},"watermark.enabled":{title:`页面水印`,description:`开启后全站叠加水印(防截图泄漏)`},"watermark.content":{title:`水印文本`,description:`留空则显示当前用户名`},"watermark.fontSize":{title:`字体大小(px)`},"watermark.opacity":{title:`透明度(1-100)`},preferencesEntryEnabled:{title:`偏好设置入口`,description:`显示在用户头像下拉菜单,关闭后隐藏“偏好设置”(含锁屏密码设置)`}}})))()}function J(e){return(e.def.checks??[]).map(e=>e._zod.def)}function Y(e){if(e instanceof fe)return Y(e.unwrap());if(e instanceof me)return Object.values(e.enum).filter(e=>typeof e==`string`);if(e instanceof le){let t=[...e.values];return t.every(e=>typeof e==`string`)?t:void 0}if(e instanceof de){let t=e.options.map(e=>Y(e));return t.every(e=>e!==void 0)?t.flat():void 0}if(e instanceof ue){let t=Y(e.def.left),n=Y(e.def.right);return t&&n?t.filter(e=>n.includes(e)):t??n}}function X(e){let t=e,n={},r=!1;for(;;){let e=t.meta();if(e&&(e.title||e.description)&&!n.title&&(n={title:e.title,description:e.description}),t instanceof fe){let e=t.unwrap();if(!(e instanceof ce))return{meta:n,leaf:{kind:`unknown`}};t=e;continue}let i=t.def;if(i.type===`nullable`&&(r=!0),(i.type==="default"||i.type===`prefault`||i.type===`optional`||i.type===`nullable`)&&i.innerType)t=i.innerType;else break}let i=t.def,a=Y(t);if(a?.length)return{meta:n,leaf:{kind:`enum`,options:a}};switch(i.type){case`boolean`:return{meta:n,leaf:{kind:`boolean`}};case`number`:{let e=J(t),i=e.find(e=>e.check===`greater_than`),a=e.find(e=>e.check===`less_than`),o=e.some(e=>e.check===`number_format`)||t.def.format===`safeint`,s=(e,t)=>e?.value===void 0?void 0:e.inclusive===!1&&o?e.value+t:e.value;return{meta:n,leaf:{kind:`number`,int:o,min:s(i,1),max:s(a,-1),nullable:r}}}case`enum`:return{meta:n,leaf:{kind:`enum`,options:Object.values(i.entries??{})}};case`string`:return{meta:n,leaf:{kind:`string`,maxLength:J(t).find(e=>e.check===`max_length`)?.maximum}};case`array`:{let e=i.element?X(i.element):null;return e?.leaf.kind===`string`||e?.leaf.kind===`enum`?{meta:n,leaf:{kind:`string-array`,maxItems:J(t).find(e=>e.check===`max_length`)?.maximum}}:{meta:n,leaf:{kind:`unknown`}}}case`object`:return{meta:n,leaf:{kind:`object`,shape:t.shape}};default:return{meta:n,leaf:{kind:`unknown`}}}}function st(e,t){return JSON.stringify(e)===JSON.stringify(t)}function ct(e,t,n){if(t.length===1)return{...e,[t[0]]:n};let[r,...i]=t,a=e[r]??{};return{...e,[r]:ct(a,i,n)}}function lt(e,t){return t.reduce((e,t)=>e&&typeof e==`object`?e[t]:void 0,e)}function ut({schema:e,value:r,inheritedValue:i,onChange:a,disabled:o,allowRevert:s=!0,enumLabels:c,excludeFields:l,fieldLabels:f}){let p=(e,t)=>a(ct(r,e,t)),m=(e,a)=>{let l=e.join(`.`),m=lt(r,e),h=i?lt(i,e):void 0,g=i!==void 0&&!st(m,h),_=s&&g&&!o?/* @__PURE__ */ N(y,{size:`small`,theme:`borderless`,onClick:()=>p(e,h),children:`恢复继承`}):null,v=a.meta.title??f?.[l]?.title??e[e.length-1],b;switch(a.leaf.kind){case`boolean`:b=/* @__PURE__ */ N(d,{checked:!!m,disabled:o,onChange:t=>p(e,t)});break;case`number`:{let{min:t,max:r,int:i,nullable:s}=a.leaf;b=/* @__PURE__ */ N(n,{style:{width:160},min:t,max:r,precision:i?0:void 0,disabled:o,value:typeof m==`number`?m:void 0,placeholder:s?`留空 = 未设置`:void 0,onChange:t=>{typeof t==`number`&&Number.isFinite(t)?p(e,i?Math.trunc(t):t):s&&p(e,null)}});break}case`enum`:b=/* @__PURE__ */ N(u,{style:{width:200},disabled:o,value:typeof m==`string`?m:void 0,optionList:a.leaf.options.map(e=>({value:e,label:c?.[l]?.[e]??e})),onChange:t=>p(e,t)});break;case`string`:b=/* @__PURE__ */ N(C,{style:{width:280},disabled:o,maxLength:a.leaf.maxLength,value:typeof m==`string`?m:``,onChange:t=>p(e,t)});break;case`string-array`:b=/* @__PURE__ */ N(t,{style:{width:360,maxWidth:`100%`},disabled:o,max:a.leaf.maxItems,value:Array.isArray(m)?m:[],allowDuplicates:!1,separator:[`,`,` `,`
16
- `],addOnBlur:!0,placeholder:`输入后按回车添加`,onChange:t=>p(e,t)});break;default:b=/* @__PURE__ */ N(`span`,{style:{color:`var(--arc-antd-color-text-2)`},children:`该字段类型暂不支持在通用设置页编辑`})}return/* @__PURE__ */ N(O,{title:v,description:a.meta.description??f?.[l]?.description,control:b,overridden:g,extra:_},l)},h=(e,t)=>{let n=Object.entries(e).filter(([e])=>!l?.includes([...t,e].join(`.`))),r=n.filter(([,e])=>X(e).leaf.kind!==`object`),i=n.filter(([,e])=>X(e).leaf.kind===`object`);return/* @__PURE__ */ P(M,{children:[r.map(([e,n],r)=>/* @__PURE__ */ P(Te,{children:[r>0?/* @__PURE__ */ N(A,{}):null,m([...t,e],X(n))]},e)),i.map(([e,n])=>{let r=X(n);return r.leaf.kind===`object`?/* @__PURE__ */ N(D,{title:r.meta.title??f?.[[...t,e].join(`.`)]?.title??e,description:r.meta.description??f?.[[...t,e].join(`.`)]?.description,children:h(r.leaf.shape,[...t,e])},e):null})]})},g=X(e);return/* @__PURE__ */ N(`div`,{children:g.leaf.kind===`object`?h(g.leaf.shape,[]):null})}function dt(){return(dt=e((()=>{a(),pe(),k()})))()}function ft(e){return j(()=>{let t=[],n=(e,r)=>{for(let i of e){if(i.type===`menu`&&i.path&&i.status===`enabled`&&i.visible){let e=i.isExternal&&i.embed?`/embed/${i.id}`:i.path;t.push({id:i.id,title:i.title,path:e,icon:i.icon,breadcrumb:r})}i.children?.length&&n(i.children,i.type===`directory`?[...r,i.title]:r)}};return n(e,[]),t},[e])}function pt(){return(pt=e((()=>{})))()}function Z(e,t){return!L(t,e)||z(e.defaults,t)!==z(Je,t)}function mt({value:e,disabled:t,onChange:n,label:r}){let i=Ge.find(t=>t.key===e),a=i?.light.primary??e,o=Ge.map(e=>({value:e.key,label:e.name}));return i||o.push({value:e,label:e}),/* @__PURE__ */ P(`div`,{className:`preference-policy-editor__color`,children:[/* @__PURE__ */ N(u,{"aria-label":r,value:e,optionList:o,disabled:t,onChange:e=>{typeof e==`string`&&n(e)}}),/* @__PURE__ */ N(g,{alpha:!1,usePopover:!0,value:g.colorStringToValue(a),onChange:e=>{t||n(e.hex)},children:/* @__PURE__ */ N(`button`,{type:`button`,className:`preference-policy-editor__swatch`,disabled:t,"aria-label":`自定义主题色`,title:`自定义主题色`,style:{backgroundColor:a}})})]})}function ht({value:e,onChange:t,disabled:r}){let{hasPermission:a}=Le(),o=a(`system:menu:list`),s=Ve({enabled:o}),p=Be(),m=ft(s.data??p.data??[]),{draftParams:g,bind:b,setField:x,handleReset:te}=ze({defaults:gt,listKey:He.module(`ui`),refetchOnSearch:!1}),S=j(()=>{let t=[{value:`/`,label:`首页(默认)`}],n=/* @__PURE__ */ new Set([`/`]);for(let e of m)!e.path.startsWith(`/`)||e.path.startsWith(`//`)||n.has(e.path)||(n.add(e.path),t.push({value:e.path,label:[...e.breadcrumb,e.title].join(` / `)}));let r=e.defaults.homePath;return n.has(r)||t.push({value:r,label:`${r}(当前已配置)`}),t},[m,e.defaults.homePath]),w=qe(),T=j(()=>{let t=g.keyword.trim();return B.filter(n=>g.group&&n.group!==g.group||g.changedOnly&&!Z(e,n.path)||g.lockedOnly&&L(n.path,e)?!1:!t||[n.label,n.path,n.description,V.find(e=>e.id===n.group)?.label,n.applicableWhen&&R(n.applicableWhen)].filter(Boolean).some(e=>Ke(String(e),t)))},[g,e,w]),re=B.filter(t=>Z(e,t.path)).length,ie=B.filter(t=>!L(t.path,e)).length,ae=Xe(e,{});function E(n,r){t({...e,defaults:H(e.defaults,n,r)})}function oe(n){t({defaults:H(e.defaults,n,z(Je,n)),allowUserOverride:H(e.allowUserOverride,n,!0)})}function se(t){let{path:i,kind:a}=t,c=z(e.defaults,i),l=`${t.label}默认值`;return a===`boolean`?/* @__PURE__ */ N(u,{"aria-label":l,value:String(c),optionList:vt,disabled:r,onChange:e=>E(i,e===`true`)}):a===`number`?/* @__PURE__ */ N(n,{"aria-label":l,value:typeof c==`number`?c:void 0,min:t.min,max:t.max,step:t.step??1,precision:t.step&&t.step<1?void 0:0,suffix:t.unit,disabled:r,onChange:e=>{typeof e==`number`&&Number.isFinite(e)&&E(i,e)}}):a===`color`?/* @__PURE__ */ N(mt,{value:String(c),disabled:r,label:l,onChange:e=>E(i,e)}):a===`home-path`?/* @__PURE__ */ N(u,{"aria-label":l,value:String(c),optionList:S,filter:!0,disabled:r,loading:o?s.isFetching:p.isFetching,onChange:e=>{typeof e==`string`&&E(i,e)}}):a===`select`?/* @__PURE__ */ N(u,{"aria-label":l,value:typeof c==`number`?c:String(c),optionList:[...t.options??[]],disabled:r,onChange:e=>{(typeof e==`string`||typeof e==`number`)&&E(i,e)}}):/* @__PURE__ */ N(C,{"aria-label":l,value:String(c),disabled:r,onChange:e=>E(i,e)})}function ce(e){let t=Ye(e.path,ae);return/* @__PURE__ */ P(`span`,{className:`preference-policy-editor__description`,children:[e.description?/* @__PURE__ */ N(`span`,{children:e.description}):null,e.applicableWhen?/* @__PURE__ */ P(`span`,{children:[`适用条件:`,R(e.applicableWhen)]}):null,t?null:/* @__PURE__ */ N(`span`,{className:`preference-policy-editor__hint`,children:`当前系统默认组合不满足条件,满足条件后生效。`}),e.path===`homePath`?/* @__PURE__ */ P(`span`,{children:[`用户无权访问指定页面时回到可访问的首页。`,o?``:`当前仅列出你可访问的页面。`]}):null]})}return/* @__PURE__ */ N(`div`,{className:`preference-policy-editor`,children:/* @__PURE__ */ P(D,{title:`偏好策略`,description:`设置每项偏好的系统默认值,并决定用户是否可以修改。`,children:[/* @__PURE__ */ N(h,{type:`info`,closeIcon:null,className:`preference-policy-editor__intro`,description:`禁止用户修改的项目会在个人偏好中隐藏并使用系统值;子项是否显示取决于父项的生效值。更改将在保存后统一生效。`}),/* @__PURE__ */ N(rt,{primary:/* @__PURE__ */ N(_,{placeholder:`搜索偏好名称 / 说明`,...b(`keyword`)}),filters:/* @__PURE__ */ P(M,{children:[/* @__PURE__ */ N(ee,{placeholder:`全部分组`,items:_t,...b(`group`)}),/* @__PURE__ */ N(c,{checked:g.changedOnly,onChange:e=>x(`changedOnly`)(!!e.target.checked),children:`仅看已调整`}),/* @__PURE__ */ N(c,{checked:g.lockedOnly,onChange:e=>x(`lockedOnly`)(!!e.target.checked),children:`仅看禁止修改`})]}),onReset:te}),/* @__PURE__ */ P(i.Text,{type:`tertiary`,size:`small`,children:[`共 `,B.length,` 项 · 已调整 `,re,` 项 · 禁止修改 `,ie,` 项 · 当前显示 `,T.length,` 项`]}),T.length===0?/* @__PURE__ */ N(f,{description:`没有符合筛选条件的偏好`}):null,V.map(n=>{let i=T.filter(e=>e.group===n.id);return i.length===0?null:/* @__PURE__ */ P(D,{title:n.label,children:[/* @__PURE__ */ P(`div`,{className:`preference-policy-editor__head`,"aria-hidden":`true`,children:[/* @__PURE__ */ N(`span`,{children:`偏好设置`}),/* @__PURE__ */ P(`div`,{className:`preference-policy-editor__controls`,children:[/* @__PURE__ */ N(`span`,{children:`系统默认值`}),/* @__PURE__ */ N(`span`,{children:`用户可修改`}),/* @__PURE__ */ N(`span`,{})]})]}),i.map((n,i)=>/* @__PURE__ */ P(Te,{children:[i>0?/* @__PURE__ */ N(A,{}):null,/* @__PURE__ */ N(`div`,{className:`preference-policy-editor__row`,"data-preference-path":n.path,children:/* @__PURE__ */ N(O,{title:n.label,description:ce(n),style:{flexWrap:`wrap`},control:/* @__PURE__ */ P(`div`,{className:`preference-policy-editor__controls`,children:[/* @__PURE__ */ P(`div`,{className:`preference-policy-editor__value`,children:[/* @__PURE__ */ N(`span`,{className:`preference-policy-editor__mobile-label`,children:`系统默认值`}),se(n)]}),/* @__PURE__ */ P(`div`,{className:`preference-policy-editor__override`,children:[/* @__PURE__ */ N(`span`,{className:`preference-policy-editor__mobile-label`,children:`用户可修改`}),/* @__PURE__ */ N(d,{"aria-label":`${n.label}用户可修改`,disabled:r,checked:L(n.path,e),onChange:r=>t({...e,allowUserOverride:H(e.allowUserOverride,n.path,r)})})]}),/* @__PURE__ */ N(v,{content:`恢复内置默认值并允许用户修改`,children:/* @__PURE__ */ N(l,{children:/* @__PURE__ */ N(y,{theme:`borderless`,size:`small`,"aria-label":`恢复${n.label}内置策略`,icon:/* @__PURE__ */ N(ne,{size:14}),disabled:r||!Z(e,n.path),onClick:()=>oe(n.path)})})})]})})})]},n.path))]},n.id)})]})})}var gt,_t,vt;function Q(){return(Q=e((()=>{a(),m(),it(),te(),k(),pt(),gt={keyword:``,group:void 0,changedOnly:!1,lockedOnly:!1},_t=V.map(e=>({value:e.id,label:e.label})),vt=[{value:`true`,label:`开启`},{value:`false`,label:`关闭`}]})))()}function yt({value:e,disabled:t,uploading:n,onChange:r,onUploadingChange:i}){let a=Ce(),s=De(null);return Ee(()=>()=>{s.current?.abort()},[]),/* @__PURE__ */ P(M,{children:[/* @__PURE__ */ N(O,{title:/* @__PURE__ */ N(`label`,{htmlFor:`website-name`,children:`网站名称`}),description:`应用于登录页、管理台和浏览器标题。留空时沿用宿主默认名称。`,control:/* @__PURE__ */ N(C,{id:`website-name`,style:{width:280},maxLength:80,showClear:!0,disabled:t,value:e.name,placeholder:a?.name||`沿用宿主默认名称`,onChange:e=>r({name:e})})}),/* @__PURE__ */ N(A,{}),/* @__PURE__ */ N(O,{title:`网站 Logo`,description:`支持 PNG、JPEG,最大 2 MB,尺寸不超过 4096 × 4096。保存后生效。`,control:/* @__PURE__ */ P(`div`,{style:{display:`flex`,flexDirection:`column`,alignItems:`center`,gap:12},children:[/* @__PURE__ */ N(`div`,{style:{width:96,height:96,padding:12,border:`1px solid var(--arc-antd-color-border)`,borderRadius:`var(--semi-border-radius-medium)`,display:`grid`,placeItems:`center`},children:e.logoUrl?/* @__PURE__ */ N(`img`,{src:I.resolveURL(e.logoUrl),alt:`网站 Logo 预览`,style:{width:`100%`,height:`100%`,objectFit:`contain`}}):/* @__PURE__ */ N(xe,{size:64,hostDefault:!0})}),/* @__PURE__ */ N(o,{accept:`image/png,image/jpeg`,showUploadList:!1,disabled:t||n,customRequest:e=>{let t=new AbortController;return s.current?.abort(),s.current=t,i(!0),I.upload(Fe.uploadLogo,{body:{file:e.file}},{signal:t.signal}).then(n=>{t.signal.aborted||(r({logoUrl:n.logoUrl}),e.onSuccess?.(n))}).catch(n=>{if(t.signal.aborted)return;let r=n instanceof Error?n:/* @__PURE__ */ Error(`Logo 上传失败`);w.error(r.message),e.onError?.(r)}).finally(()=>{s.current===t&&!t.signal.aborted&&(s.current=null,i(!1))}),{abort:()=>t.abort()}},beforeUpload:({file:e})=>Number(e.size)>2097152||![`image/png`,`image/jpeg`].includes(e.fileInstance?.type??``)?(w.error(`请选择不超过 2 MB 的 PNG 或 JPEG 图片`),!1):!0,children:/* @__PURE__ */ N(y,{icon:/* @__PURE__ */ N(tt,{size:14}),disabled:t||n,loading:n,children:e.logoUrl?`更换 Logo`:`上传 Logo`})}),e.logoUrl?/* @__PURE__ */ N(y,{size:`small`,theme:`borderless`,disabled:t||n,onClick:()=>r({logoUrl:``}),children:`恢复默认 Logo`}):/* @__PURE__ */ N(`span`,{style:{color:`var(--arc-antd-color-text-2)`,fontSize:12},children:`使用宿主默认 Logo`})]})})]})}function bt(){return(bt=e((()=>{a(),nt(),k(),Se(),we()})))()}function xt({value:e,disabled:t,onChange:n}){let r=ye(),i=new URL(I.resolveURL(`/api/v1/mcp`),window.location.origin).href,a=JSON.stringify({url:i,headers:{Authorization:`Bearer <API_TOKEN>`}},null,2);return/* @__PURE__ */ P(M,{children:[/* @__PURE__ */ N(O,{title:`启用 MCP`,description:`向外部客户端提供只读工具。保存后立即生效,关闭后拒绝新的 MCP 请求。`,control:/* @__PURE__ */ N(Me,{"aria-label":`启用 MCP`,checked:e.enabled,disabled:t,onChange:e=>n({enabled:e})})}),/* @__PURE__ */ N(A,{}),/* @__PURE__ */ N(O,{title:`启用 WebMCP`,description:`已登录且未锁屏的管理台自动加载浏览器工具,沿用当前账号权限,删除操作仍需确认。保存后同步到已打开的页面。`,control:/* @__PURE__ */ N(Me,{"aria-label":`启用 WebMCP`,checked:e.webMcpEnabled,disabled:t,onChange:e=>n({webMcpEnabled:e})})}),e.webMcpEnabled&&r&&!r.configured?/* @__PURE__ */ N(ke,{type:`warning`,title:`当前宿主尚未接入 WebMCP`,description:`请先在宿主装配可选的 WebMCP 入口,接入后跟随此系统开关。`}):null,r?.status===`error`?/* @__PURE__ */ N(ke,{type:`error`,title:`WebMCP 加载失败`,description:`请检查管理台资源并刷新页面重试。`}):null,/* @__PURE__ */ N(F.Title,{level:5,children:`MCP 连接`}),/* @__PURE__ */ P(je,{wrap:!0,style:{marginBottom:12},children:[/* @__PURE__ */ N(F.Text,{code:!0,style:{overflowWrap:`anywhere`},children:i}),/* @__PURE__ */ N(Ae,{onClick:()=>void b(i),children:`复制地址`})]}),/* @__PURE__ */ P(F.Paragraph,{children:[`客户端选择 Streamable HTTP,通过 Authorization 请求头携带 API Token。前往`,/* @__PURE__ */ N(Pe,{to:`/system/api-tokens`,children:`AI 与集成 → API Token`}),`创建和管理凭据;可用工具由账号权限与 Token 范围共同决定。`]}),/* @__PURE__ */ N(`pre`,{style:{padding:16,whiteSpace:`pre-wrap`,overflowWrap:`anywhere`,background:`var(--arc-antd-color-fill-0)`,borderRadius:`var(--semi-border-radius-medium)`},children:a}),/* @__PURE__ */ N(Ae,{onClick:()=>void b(a),children:`复制连接示例`}),/* @__PURE__ */ N(F.Title,{level:5,children:`只读工具`}),/* @__PURE__ */ N(`ul`,{children:et.map(e=>/* @__PURE__ */ P(`li`,{children:[/* @__PURE__ */ N(F.Text,{children:e.title}),` `,/* @__PURE__ */ N(F.Text,{type:`secondary`,children:e.name})]},e.name))}),/* @__PURE__ */ N(F.Paragraph,{type:`secondary`,children:`MCP 与 WebMCP 独立控制。宿主已配置桌面 Relay 时,桥接跟随 WebMCP 开关启停;连接状态以客户端实际列出工具为准。`})]})}function St(){return(St=e((()=>{k(),x(),be()})))()}function Ct({module:e,meta:t}){let n=Qe[e],i=We(e),a=Ue(e),o=i.data,[c,l]=Oe(null),[u,d]=Oe(!1);Ee(()=>{l(null),d(!1)},[o?.version]);let f=j(()=>c??o?.effective??null,[c,o]),p=c!==null&&JSON.stringify(c)!==JSON.stringify(o?.effective),m=e===`ui`&&f?f:null,g=e===`ui`&&o?o.effective:null,_=m&&g?B.filter(({path:e})=>z(m.preferences.defaults,e)!==z(g.preferences.defaults,e)||z(m.preferences.allowUserOverride,e)!==z(g.preferences.allowUserOverride,e)):[],v=_.filter(({path:e})=>m&&!z(m.preferences.allowUserOverride,e)).length;async function b(){if(!o||!f||!t.canWrite)return;let n=e===`website`?{...f,name:f.name.trim()}:f,r=Ie[e].input.safeParse({body:{version:o.version,data:n}});if(r.success)try{await a.mutateAsync(r.data),l(null),w.success(`设置已保存`)}catch(e){e instanceof Ne&&e.status===409&&(l(null),i.refetch())}else{let e=r.error.issues[0];w.error(`${e.path.join(`.`)||`表单`}:${e.message}`)}}return/* @__PURE__ */ N(re.Body,{padding:24,children:/* @__PURE__ */ P(`div`,{style:{maxWidth:e===`ui`?1040:760},children:[/* @__PURE__ */ P(`div`,{style:{display:`flex`,alignItems:`flex-start`,justifyContent:`space-between`,gap:16,marginBottom:8},children:[/* @__PURE__ */ P(`div`,{children:[/* @__PURE__ */ N(wt,{heading:5,style:{margin:0},children:n.title}),/* @__PURE__ */ N($,{type:`tertiary`,children:n.description})]}),!t.page&&t.canWrite?/* @__PURE__ */ N(y,{theme:`solid`,icon:/* @__PURE__ */ N(se,{size:14}),loading:a.isPending,disabled:!p||u,onClick:()=>void b(),children:`保存`}):null]}),/* @__PURE__ */ P(`div`,{style:{display:`flex`,gap:8,flexWrap:`wrap`,marginBottom:12},children:[/* @__PURE__ */ N(s,{color:`grey`,children:`系统级`}),o?/* @__PURE__ */ P(s,{children:[`版本 `,o.version]}):null,t.overriddenCount>0?/* @__PURE__ */ P(s,{color:`orange`,children:[t.overriddenCount,` 项覆盖`]}):null,o?.updatedAt?/* @__PURE__ */ P($,{type:`tertiary`,size:`small`,children:[`最后修改 `,o.updatedAt]}):null]}),t.page?/* @__PURE__ */ N(h,{type:`info`,closeIcon:null,description:/* @__PURE__ */ P(`span`,{children:[`该模块有专用设置页面,请前往`,/* @__PURE__ */ P(Pe,{to:t.page,style:{margin:`0 4px`,display:`inline-flex`,alignItems:`center`,gap:4},children:[n.title,/* @__PURE__ */ N(W,{size:12})]}),`修改。`]})}):null,!t.page&&!t.canWrite?/* @__PURE__ */ N(h,{type:`warning`,closeIcon:null,description:t.scope===`platform`?`平台级设置仅平台管理员可修改,当前为只读。`:`当前账号没有修改权限,当前为只读。`}):null,_.length>0?/* @__PURE__ */ N(h,{type:`info`,closeIcon:null,description:`待保存 ${_.length} 项偏好策略,其中 ${v} 项将统一使用系统值;允许修改的项目保留用户已有选择。`}):null,i.error?/* @__PURE__ */ N(h,{type:`danger`,closeIcon:null,description:/* @__PURE__ */ P(`span`,{children:[`设置加载失败。`,/* @__PURE__ */ N(y,{theme:`borderless`,onClick:()=>void i.refetch(),children:`重试`})]})}):null,/* @__PURE__ */ N(r,{spinning:i.isLoading,children:o&&f?/* @__PURE__ */ P(M,{children:[e===`website`?/* @__PURE__ */ N(yt,{value:f,disabled:!!t.page||!t.canWrite||a.isPending,uploading:u,onUploadingChange:d,onChange:e=>l(t=>({...o.effective,...t,...e}))},o.version):e===`mcp`?/* @__PURE__ */ N(xt,{value:f,disabled:!t.canWrite||a.isPending,onChange:e=>l({...f,...e})}):/* @__PURE__ */ N(ut,{schema:$e[e],excludeFields:e===`ui`?[`preferences`]:void 0,fieldLabels:at,value:f,inheritedValue:o.inherited,onChange:e=>l({...f,...e}),disabled:!!t.page||!t.canWrite,enumLabels:Tt}),m?/* @__PURE__ */ N(ht,{value:m.preferences,onChange:e=>l({...f,preferences:e}),disabled:!t.canWrite}):null]}):null})]})})}var $,wt,Tt;function Et(){return(Et=e((()=>{a(),G(),oe(),T(),ot(),dt(),Q(),bt(),St(),{Text:$,Title:wt}=i,Tt={captchaComplexity:Ze}})))()}export{G as i,Et as n,W as r,Ct as t};
17
- //# sourceMappingURL=SettingModuleDetail-Dppk9An1.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"SettingModuleDetail-Dppk9An1.js","mappings":"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","names":["__iconData","__iconData$1","createLucideIcon","InstantFilterToolbar","refresh","onRefresh","RefreshButton","refreshing","actionsSlot","useRenderedSlot","desktopActions","present","actions","extra","_Fragment","SlotProbe","SearchToolbar","primary","filters","onReset","ResetButton","mobileActions","node","checksOf","schema","c","enumValues","z.ZodLazy","z.ZodEnum","value","z.ZodLiteral","values","z.ZodUnion","options","option","z.ZodIntersection","left","right","describe","current","field","meta","nullable","m","unwrapped","z.ZodType","def","checks","gt","lt","int","bound","step","element","sameValue","a","b","setPath","path","doc","head","rest","child","getPath","acc","seg","SchemaForm","update","onChange","next","renderLeaf","key","inherited","inheritedValue","overridden","revert","allowRevert","disabled","Button","title","info","fieldLabels","control","Switch","v","InputNumber","Select","enumLabels","Input","TagInput","SettingRow","renderShape","entries","shape","name","excludeFields","leaves","groups","Fragment","index","SettingDivider","SettingSection","root","useFlatMenus","useMemo","result","walk","nodes","parents","menuTree","isAdjusted","canOverridePreference","policy","getPreferenceValue","defaultPreferences","PolicyColorInput","preset","THEME_COLOR_PRESETS","entry","color","label","ColorPicker","PreferencePolicyEditor","usePermission","canReadAllMenus","hasPermission","useMenuTree","currentMenus","useCurrentUserMenuTree","flatMenus","useListSearch","INITIAL_FILTERS","settingsKeys","homeOptions","seen","menu","configured","pinyinReady","usePinyinReady","rows","keyword","draftParams","preferenceDefinitions","definition","preferenceGroups","group","describePreferenceCondition","textMatches","adjustedCount","lockedCount","defaultValues","resolvePreferences","updateDefault","setPreferenceValue","resetField","renderControl","kind","BOOLEAN_OPTIONS","renderDescription","applicable","isPreferenceApplicable","Banner","KeywordInput","bind","FilterSelect","GROUP_OPTIONS","Checkbox","setField","event","handleReset","Typography","Empty","fields","Tooltip","Space","RotateCcw","WebsiteSettingsForm","hostBrand","useHostBrand","request","useRef","useEffect","goTransport","AppLogo","Upload","uploading","controller","onUploadingChange","settingsContract","problem","error","Toast","file","ImagePlus","McpSettingsForm","webmcp","useWebMcpControl","endpoint","example","Alert","copyTextWithToast","Link","mcpTools","tool","SettingModuleDetail","SETTINGS_MODULES","module","query","useSettings","save","useSaveSettings","envelope","draft","setDraft","useState","uploadingLogo","setUploadingLogo","dirty","uiValue","savedUi","changedPreferences","forcedChanges","handleSave","data","parsed","settingsWriteOperations","err","GoaError","issue","MasterDetailLayout","Title","Text","Save","Tag","ExternalLink","Spin","previous","patch","foundationSettingSchemas","settingFieldLabels","ENUM_LABELS","CAPTCHA_COMPLEXITY_LABELS"],"ignoreList":[0,1],"sources":["../../../../node_modules/lucide-react/dist/esm/icons/external-link.mjs","../../../../node_modules/lucide-react/dist/esm/icons/image-plus.mjs","../../src/components/list-page/InstantFilterToolbar.tsx","../../src/pages/system/settings/field-labels.ts","../../src/components/settings/SchemaForm.tsx","../../src/layouts/admin/useMenuDerived.ts","../../src/pages/system/settings/PreferencePolicyEditor.tsx","../../src/pages/system/settings/WebsiteSettingsForm.tsx","../../src/pages/system/settings/McpSettingsForm.tsx","../../src/pages/system/settings/SettingModuleDetail.tsx"],"sourcesContent":[null,null,"import type { ReactNode } from 'react';\nimport { SearchToolbar } from \"@/components/SearchToolbar\";\nimport { RefreshButton, ResetButton } from \"@/components/toolbar-controls\";\nimport { SlotProbe } from \"@/components/rendered-slot\";\nimport { useRenderedSlot } from \"@fudanda/react-core/hooks/useRenderedSlot\";\nexport interface InstantFilterToolbarProps {\n /** 主区控件(关键字输入 / 主机或站点等作用域切换),桌面与移动端主区都展示 */\n primary?: ReactNode;\n /** 选中即生效的筛选项:桌面端内联,移动端收进抽屉(抽屉没有「查询」页脚,改值即筛) */\n filters?: ReactNode;\n /** 重新拉取数据;传入即渲染 `RefreshButton`(桌面跟在筛选项后,移动端留在主区) */\n onRefresh?: () => void;\n refreshing?: boolean;\n /** 清空筛选条件;传入即渲染 `ResetButton`,移动端出现在筛选抽屉页脚 */\n onReset?: () => void;\n /** 新增 / 清理等操作:桌面端跟在刷新之后,移动端收进「更多操作」菜单 */\n actions?: ReactNode;\n /** 覆盖移动端更多菜单内容;缺省与 `actions` 相同 */\n mobileActions?: ReactNode;\n /** 只在桌面端展示的说明文字(如「共 N 个监听端口」「规则保存后立即热更新」) */\n extra?: ReactNode;\n filterTitle?: ReactNode;\n actionTitle?: string;\n className?: string;\n}\nconst present = (node: ReactNode) => Boolean(node);\n/**\n * 「边输边筛 + 刷新」型工具栏:进程 / 服务 / 容器 / 端口这类一次取全量、在客户端即时过滤、没有「查询」按钮语义的页面。\n * 与 `ListSearchToolbar` 一样把桌面 / 移动排布写在一处——\n * 桌面:主区 → 筛选项 → 刷新 → 重置 → 操作 → 说明;移动:主区 + 刷新,筛选项进抽屉,操作进更多菜单。\n * 页面不再为 `primary` / `mobilePrimary` 各写一份同样的控件。\n */\nexport function InstantFilterToolbar({ primary, filters, onRefresh, refreshing, onReset, actions, mobileActions, extra, filterTitle, actionTitle, className, }: InstantFilterToolbarProps) {\n const refresh = onRefresh ? <RefreshButton onClick={onRefresh} loading={refreshing}/> : null;\n const actionsSlot = useRenderedSlot();\n const desktopActions = present(actions) || present(extra)\n ? <><SlotProbe ref={actionsSlot.probeRef}>{actions}</SlotProbe>{extra}</>\n : undefined;\n return (<SearchToolbar className={className} primary={(<>\n {primary}\n {filters}\n {refresh}\n {onReset ? <ResetButton onClick={onReset}/> : null}\n </>)} actions={desktopActions} mobilePrimary={(<>\n {primary}\n {refresh}\n </>)} mobileFilters={present(filters) ? filters : undefined}\n // SearchToolbar 对 mobileActions 缺省回落到 actions;说明文字不进更多菜单,且只有操作真的渲染出元素才复用\n mobileActions={mobileActions ?? (actionsSlot.rendered ? actions : false)} filterTitle={filterTitle} actionTitle={actionTitle} onFilterReset={onReset}/>);\n}\n","/** Presentation text for the generated settings fields; validation stays in Goa schemas. */\nexport const settingFieldLabels = {\n captchaEnabled: { title: '登录验证码', description: '开启后登录必须输入图形验证码' },\n captchaComplexity: { title: '验证码复杂度', description: '仅在开启登录验证码后生效' },\n uploadValidateType: { title: '校验真实文件类型', description: '基于 magic bytes 识别,防止伪造 MIME type 绕过校验' },\n uploadAllowedTypes: { title: '允许的 MIME 类型', description: '支持通配符(如 image/*);填 * 或 */* 放行全部类型' },\n uploadMaxSizeMb: { title: '单文件大小上限(MB)', description: '0 表示不限制;超过的上传(含分片)将被拒绝' },\n chunkThresholdMb: { title: '分片上传阈值(MB)', description: '超过该大小的文件走分片上传 + 断点续传,不超过则单请求上传;受反向代理请求体上限约束' },\n chunkSizeMb: { title: '分片大小(MB)', description: '服务端裁定的分片基线,不低于对象存储要求的最小分片;文件过大时自动上调以不超过片数上限' },\n watermark: { title: '水印' },\n 'watermark.enabled': { title: '页面水印', description: '开启后全站叠加水印(防截图泄漏)' },\n 'watermark.content': { title: '水印文本', description: '留空则显示当前用户名' },\n 'watermark.fontSize': { title: '字体大小(px)' },\n 'watermark.opacity': { title: '透明度(1-100)' },\n preferencesEntryEnabled: { title: '偏好设置入口', description: '显示在用户头像下拉菜单,关闭后隐藏“偏好设置”(含锁屏密码设置)' },\n} as const;\n","import { Fragment, type ReactNode } from 'react';\nimport { Button, Input, InputNumber, Select, Switch, TagInput } from \"@/ui\";\nimport * as z from 'zod';\nimport { SettingDivider, SettingRow, SettingSection } from \"@/components/settings/SettingRow\";\n/**\n * 由 Zod 读取 schema 驱动的设置表单:布尔 → Switch、数值 → InputNumber(min / max 取自 schema 约束)、\n * 枚举 → Select、字符串 → Input、字符串数组 → TagInput、嵌套对象 → 分组。\n * 标签 / 说明取字段 `.meta({ title, description })`,新增设置项只改 shared schema,页面零改动。\n *\n * 受控组件:`value` 为完整生效文档,`onChange` 回传替换后的完整文档;`inheritedValue` 用于「已覆盖」标记与「恢复继承」。\n */\nexport interface SchemaFormProps {\n readonly schema: z.ZodType;\n readonly excludeFields?: readonly string[];\n readonly fieldLabels?: Readonly<Record<string, FieldMeta>>;\n readonly value: Record<string, unknown>;\n readonly inheritedValue?: Record<string, unknown>;\n readonly onChange: (next: Record<string, unknown>) => void;\n readonly disabled?: boolean;\n /** 是否显示「恢复继承」操作(租户作用域 / 平台作用域相对默认值均适用) */\n readonly allowRevert?: boolean;\n /** 枚举字段的展示文案:键为字段路径(a.b),值为 value → label;缺省显示原始值 */\n readonly enumLabels?: Record<string, Record<string, string>>;\n}\ninterface FieldMeta {\n title?: string;\n description?: string;\n}\ntype LeafKind = {\n kind: 'boolean';\n} | {\n kind: 'number';\n int: boolean;\n min?: number;\n max?: number;\n nullable: boolean;\n} | {\n kind: 'enum';\n options: string[];\n} | {\n kind: 'string';\n maxLength?: number;\n} | {\n kind: 'string-array';\n maxItems?: number;\n} | {\n kind: 'object';\n shape: Record<string, z.ZodType>;\n} | {\n kind: 'unknown';\n};\ninterface FieldInfo {\n meta: FieldMeta;\n leaf: LeafKind;\n}\ntype Check = {\n check: string;\n value?: number;\n inclusive?: boolean;\n maximum?: number;\n minimum?: number;\n};\nfunction checksOf(schema: z.ZodType): Check[] {\n const checks = (schema.def as {\n checks?: Array<{\n _zod: {\n def: Check;\n };\n }>;\n }).checks ?? [];\n return checks.map((c) => c._zod.def);\n}\nfunction enumValues(schema: z.core.$ZodType): string[] | undefined {\n if (schema instanceof z.ZodLazy) return enumValues(schema.unwrap());\n if (schema instanceof z.ZodEnum) return Object.values(schema.enum).filter((value): value is string => typeof value === 'string');\n if (schema instanceof z.ZodLiteral) {\n const values = [...schema.values];\n return values.every(value => typeof value === 'string') ? values : undefined;\n }\n if (schema instanceof z.ZodUnion) {\n const options = schema.options.map(option => enumValues(option));\n return options.every((option): option is string[] => option !== undefined) ? options.flat() : undefined;\n }\n if (schema instanceof z.ZodIntersection) {\n const left = enumValues(schema.def.left), right = enumValues(schema.def.right);\n return left && right ? left.filter(value => right.includes(value)) : left ?? right;\n }\n return undefined;\n}\n/** 剥掉 default / prefault / optional / nullable 包装;沿途收集第一份 meta 与 nullable 标记 */\nfunction describe(field: z.ZodType): FieldInfo {\n let current = field;\n let meta: FieldMeta = {};\n let nullable = false;\n for (;;) {\n const m = current.meta() as FieldMeta | undefined;\n if (m && (m.title || m.description) && !meta.title)\n meta = { title: m.title, description: m.description };\n if (current instanceof z.ZodLazy) {\n const unwrapped = current.unwrap();\n if (!(unwrapped instanceof z.ZodType)) return { meta, leaf: { kind: 'unknown' } };\n current = unwrapped; continue;\n }\n const def = current.def as {\n type: string;\n innerType?: z.ZodType;\n };\n if (def.type === 'nullable')\n nullable = true;\n if ((def.type === 'default' || def.type === 'prefault' || def.type === 'optional' || def.type === 'nullable') && def.innerType) {\n current = def.innerType;\n continue;\n }\n break;\n }\n const def = current.def as {\n type: string;\n entries?: Record<string, string>;\n element?: z.ZodType;\n };\n const options = enumValues(current);\n if (options?.length) return { meta, leaf: { kind: 'enum', options } };\n switch (def.type) {\n case 'boolean':\n return { meta, leaf: { kind: 'boolean' } };\n case 'number': {\n const checks = checksOf(current);\n const gt = checks.find((c) => c.check === 'greater_than');\n const lt = checks.find((c) => c.check === 'less_than');\n const int = checks.some((c) => c.check === 'number_format') || (current.def as {\n format?: string;\n }).format === 'safeint';\n // 开区间约束(positive() / negative())只能在整数上精确折算成闭区间\n const bound = (c: Check | undefined, step: number) => c?.value === undefined ? undefined : c.inclusive === false && int ? c.value + step : c.value;\n return { meta, leaf: { kind: 'number', int, min: bound(gt, 1), max: bound(lt, -1), nullable } };\n }\n case 'enum':\n return { meta, leaf: { kind: 'enum', options: Object.values(def.entries ?? {}) } };\n case 'string':\n return { meta, leaf: { kind: 'string', maxLength: checksOf(current).find((c) => c.check === 'max_length')?.maximum } };\n case 'array': {\n const element = def.element ? describe(def.element) : null;\n if (element?.leaf.kind === 'string' || element?.leaf.kind === 'enum') {\n return { meta, leaf: { kind: 'string-array', maxItems: checksOf(current).find((c) => c.check === 'max_length')?.maximum } };\n }\n return { meta, leaf: { kind: 'unknown' } };\n }\n case 'object':\n return { meta, leaf: { kind: 'object', shape: (current as z.ZodObject).shape as Record<string, z.ZodType> } };\n default:\n return { meta, leaf: { kind: 'unknown' } };\n }\n}\nfunction sameValue(a: unknown, b: unknown): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\nfunction setPath(doc: Record<string, unknown>, path: string[], value: unknown): Record<string, unknown> {\n if (path.length === 1)\n return { ...doc, [path[0]]: value };\n const [head, ...rest] = path;\n const child = (doc[head] ?? {}) as Record<string, unknown>;\n return { ...doc, [head]: setPath(child, rest, value) };\n}\nfunction getPath(doc: Record<string, unknown> | undefined, path: string[]): unknown {\n return path.reduce<unknown>((acc, seg) => (acc && typeof acc === 'object' ? (acc as Record<string, unknown>)[seg] : undefined), doc);\n}\nexport function SchemaForm({ schema, value, inheritedValue, onChange, disabled, allowRevert = true, enumLabels, excludeFields, fieldLabels }: SchemaFormProps) {\n const update = (path: string[], next: unknown) => onChange(setPath(value, path, next));\n const renderLeaf = (path: string[], info: FieldInfo): ReactNode => {\n const key = path.join('.');\n const current = getPath(value, path);\n const inherited = inheritedValue ? getPath(inheritedValue, path) : undefined;\n const overridden = inheritedValue !== undefined && !sameValue(current, inherited);\n const revert = allowRevert && overridden && !disabled\n ? <Button size=\"small\" theme=\"borderless\" onClick={() => update(path, inherited)}>恢复继承</Button>\n : null;\n const title = info.meta.title ?? fieldLabels?.[key]?.title ?? path[path.length - 1];\n let control: ReactNode;\n switch (info.leaf.kind) {\n case 'boolean':\n control = <Switch checked={Boolean(current)} disabled={disabled} onChange={(v) => update(path, v)}/>;\n break;\n case 'number': {\n const { min, max, int, nullable } = info.leaf;\n control = (<InputNumber style={{ width: 160 }} min={min} max={max} precision={int ? 0 : undefined} disabled={disabled} value={typeof current === 'number' ? current : undefined} placeholder={nullable ? '留空 = 未设置' : undefined} onChange={(v) => {\n if (typeof v === 'number' && Number.isFinite(v))\n update(path, int ? Math.trunc(v) : v);\n else if (nullable)\n update(path, null);\n }}/>);\n break;\n }\n case 'enum':\n control = (<Select style={{ width: 200 }} disabled={disabled} value={typeof current === 'string' ? current : undefined} optionList={info.leaf.options.map((option) => ({ value: option, label: enumLabels?.[key]?.[option] ?? option }))} onChange={(v) => update(path, v)}/>);\n break;\n case 'string':\n control = (<Input style={{ width: 280 }} disabled={disabled} maxLength={info.leaf.maxLength} value={typeof current === 'string' ? current : ''} onChange={(v) => update(path, v)}/>);\n break;\n case 'string-array':\n control = (<TagInput style={{ width: 360, maxWidth: '100%' }} disabled={disabled} max={info.leaf.maxItems} value={Array.isArray(current) ? (current as string[]) : []} allowDuplicates={false} separator={[',', ' ', '\\n']} addOnBlur placeholder=\"输入后按回车添加\" onChange={(v) => update(path, v)}/>);\n break;\n default:\n control = <span style={{ color: 'var(--arc-antd-color-text-2)' }}>该字段类型暂不支持在通用设置页编辑</span>;\n }\n return <SettingRow key={key} title={title} description={info.meta.description ?? fieldLabels?.[key]?.description} control={control} overridden={overridden} extra={revert}/>;\n };\n const renderShape = (shape: Record<string, z.ZodType>, path: string[]): ReactNode => {\n const entries = Object.entries(shape).filter(([name]) => !excludeFields?.includes([...path, name].join('.')));\n const leaves = entries.filter(([, field]) => describe(field).leaf.kind !== 'object');\n const groups = entries.filter(([, field]) => describe(field).leaf.kind === 'object');\n return (<>\n {leaves.map(([name, field], index) => (<Fragment key={name}>\n {index > 0 ? <SettingDivider /> : null}\n {renderLeaf([...path, name], describe(field))}\n </Fragment>))}\n {groups.map(([name, field]) => {\n const info = describe(field);\n if (info.leaf.kind !== 'object')\n return null;\n return (<SettingSection key={name} title={info.meta.title ?? fieldLabels?.[[...path, name].join('.')]?.title ?? name} description={info.meta.description ?? fieldLabels?.[[...path, name].join('.')]?.description}>\n {renderShape(info.leaf.shape, [...path, name])}\n </SettingSection>);\n })}\n </>);\n };\n const root = describe(schema);\n return <div>{root.leaf.kind === 'object' ? renderShape(root.leaf.shape, []) : null}</div>;\n}\n","import { useCallback, useEffect, useMemo } from 'react';\nimport type { Menu } from '@fudanda/shared/identity';\nimport type { FlatMenuItem } from \"@/components/MenuSearchInput\";\nimport type { NavLayout } from \"@fudanda/react-core/hooks/usePreferences\";\nimport { useLucideIconsReady } from \"@/utils/icons\";\nimport { menuToNavItem, findAncestorKeys, findBreadcrumbs, type NavItem, type BreadcrumbData } from \"@/layouts/admin/utils\";\nexport function useFlatMenus(menuTree: Menu[]) {\n const flatMenus = useMemo<FlatMenuItem[]>(() => {\n const result: FlatMenuItem[] = [];\n const walk = (nodes: Menu[], parents: string[]) => {\n for (const node of nodes) {\n if (node.type === 'menu' && node.path && node.status === 'enabled' && node.visible) {\n // 外链内嵌菜单的实际访问路径是内部路由 /embed/{id}(页签/搜索/收藏按此匹配)\n const path = node.isExternal && node.embed ? `/embed/${node.id}` : node.path;\n result.push({ id: node.id, title: node.title, path, icon: node.icon, breadcrumb: parents });\n }\n if (node.children?.length)\n walk(node.children, node.type === 'directory' ? [...parents, node.title] : parents);\n }\n };\n walk(menuTree, []);\n return result;\n }, [menuTree]);\n return flatMenus;\n}\nexport function useBreadcrumbData(menuTree: Menu[], pathname: string, breadcrumbShowHome: boolean | undefined) {\n const currentSectionKeys = useMemo(() => findAncestorKeys(menuTree, pathname), [menuTree, pathname]);\n const breadcrumbs = useMemo(() => findBreadcrumbs(menuTree, pathname), [menuTree, pathname]);\n const displayBreadcrumbs = useMemo<BreadcrumbData[]>(() => {\n if ((breadcrumbShowHome ?? true) && pathname !== '/') {\n // 找到首页菜单的图标(findBreadcrumbs 不包含首页)\n const findHomeIcon = (nodes: Menu[]): string | undefined => {\n for (const node of nodes) {\n if (!node.visible || node.type === 'button')\n continue;\n if (node.type === 'directory' && node.children?.length) {\n const icon = findHomeIcon(node.children);\n if (icon)\n return icon;\n }\n else if (node.path === '/') {\n return node.icon ?? undefined;\n }\n }\n return undefined;\n };\n return [{ title: '首页', path: '/', icon: findHomeIcon(menuTree) }, ...breadcrumbs];\n }\n return breadcrumbs;\n }, [breadcrumbs, breadcrumbShowHome, pathname, menuTree]);\n return { currentSectionKeys, displayBreadcrumbs };\n}\nexport function useNavItems(menuTree: Menu[], chatUnreadCount: number) {\n const iconsReady = useLucideIconsReady();\n // 深层转换(递归遍历整棵菜单树 + 为每个节点创建图标元素)只依赖菜单本身。\n // 聊天未读数每来一条消息就变,若与此处合并会导致整棵导航树被重建。\n const baseNavItems = useMemo(() => menuTree.map(menuToNavItem).filter((item): item is NavItem => item !== null),\n // iconsReady: 图标注册表异步加载完成后重建 nav 项以补齐菜单图标\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [menuTree, iconsReady]);\n // 未读徽标只影响顶层 /chat 一项,浅层重建即可;无未读时直接透传保持引用不变\n const navItems = useMemo(() => {\n if (chatUnreadCount <= 0)\n return baseNavItems;\n if (!baseNavItems.some((item) => item.itemKey === '/chat'))\n return baseNavItems;\n return baseNavItems.map((item) => (item.itemKey === '/chat'\n ? { ...item, badge: { count: chatUnreadCount, overflowCount: 99 } }\n : item));\n }, [baseNavItems, chatUnreadCount]);\n return navItems;\n}\nexport function useMenuMaps(menuTree: Menu[]) {\n const pathMetaMap = useMemo(() => {\n const map = new Map<string, {\n title: string;\n icon?: string;\n }>([\n ['/search', { title: '搜索中心', icon: 'Search' }],\n ]);\n function traverse(nodes: Menu[]) {\n for (const node of nodes) {\n if (node.path && node.title)\n map.set(node.path, { title: node.title, icon: node.icon ?? undefined });\n if (node.children)\n traverse(node.children);\n }\n }\n traverse(menuTree);\n return map;\n }, [menuTree]);\n /**\n * 路径 → 菜单元信息。\n *\n * 带动态参数的页面(`/workflow/designer/1`)在菜单中只登记了父路径,需前缀回退;\n * 且必须取**最长**前缀——`/workflow/forms/designer/5` 同时匹配「表单库」`/workflow/forms`\n * 与「表单设计」`/workflow/forms/designer`,按遍历顺序取首个会命中父级菜单。\n */\n const resolveMeta = useCallback((pathname: string) => {\n const exact = pathMetaMap.get(pathname);\n if (exact)\n return exact;\n let best: {\n title: string;\n icon?: string;\n } | undefined;\n let bestLength = -1;\n for (const [path, meta] of pathMetaMap) {\n if (path.length > bestLength && pathname.startsWith(`${path}/`)) {\n best = meta;\n bestLength = path.length;\n }\n }\n return best;\n }, [pathMetaMap]);\n const resolveTitle = useCallback((pathname: string) => resolveMeta(pathname)?.title ?? pathname, [resolveMeta]);\n /** 页签图标:与标题同一套匹配规则,否则动态参数页面会缺图标 */\n const resolveIcon = useCallback((pathname: string) => resolveMeta(pathname)?.icon, [resolveMeta]);\n return { resolveTitle, resolveIcon };\n}\nexport function useAutoTopKey(navLayout: NavLayout, navItems: NavItem[], pathname: string, setManualTopKey: (key: string | null) => void) {\n const autoTopKey = useMemo(() => {\n if (navLayout !== 'mixed' && navLayout !== 'double')\n return null;\n function contains(items: NavItem[], path: string): boolean {\n return items.some((item) => item.itemKey === path || (item.items ? contains(item.items, path) : false));\n }\n for (const item of navItems) {\n if (contains([item], pathname))\n return item.itemKey;\n }\n return navItems[0]?.itemKey ?? null;\n }, [navLayout, navItems, pathname]);\n useEffect(() => {\n if ((navLayout === 'mixed' || navLayout === 'double') && autoTopKey)\n setManualTopKey(autoTopKey);\n }, [navLayout, autoTopKey, setManualTopKey]);\n return autoTopKey;\n}\nexport function useMixedNavItems(navLayout: NavLayout, navItems: NavItem[], effectiveTopKey: string | null) {\n const mixedTopNavItems = useMemo(\n // 保留 badge 字段:mixed 顶栏由 TopNavWithOverflow 渲染徽标(如聊天未读)\n () => navItems.map(({ itemKey, text, icon, isExternal, badge }) => ({ itemKey, text, icon, isExternal, badge })), [navItems]);\n const mixedSidebarItems = useMemo(() => {\n if (navLayout !== 'mixed')\n return [];\n const top = navItems.find((i) => i.itemKey === effectiveTopKey);\n return top?.items ?? [];\n }, [navLayout, navItems, effectiveTopKey]);\n const doubleSubItems = useMemo(() => {\n if (navLayout !== 'double')\n return [];\n const top = navItems.find((i) => i.itemKey === effectiveTopKey);\n return top?.items ?? [];\n }, [navLayout, navItems, effectiveTopKey]);\n return { mixedTopNavItems, mixedSidebarItems, doubleSubItems };\n}\n// 页面缓存白名单:菜单开启 keepAlive 的路径(外链内嵌菜单取内部路由 /embed/{id})\nexport function useKeepAlivePaths(menuTree: Menu[]) {\n const keepAlivePaths = useMemo(() => {\n const result = new Set<string>();\n const walk = (nodes: Menu[]) => {\n for (const node of nodes) {\n if (node.type === 'menu' && node.keepAlive && node.path && node.status === 'enabled') {\n if (node.isExternal) {\n if (node.embed)\n result.add(`/embed/${node.id}`);\n }\n else {\n result.add(node.path);\n }\n }\n if (node.children?.length)\n walk(node.children);\n }\n };\n walk(menuTree);\n return result;\n }, [menuTree]);\n return keepAlivePaths;\n}\n","import { Fragment, useMemo, type ReactNode } from 'react';\nimport { Banner, Button, Checkbox, ColorPicker, Empty, Input, InputNumber, Select, Space, Switch, Tooltip, Typography } from \"@/ui\";\nimport { RotateCcw } from 'lucide-react';\nimport { canOverridePreference, defaultPreferences, describePreferenceCondition, getPreferenceValue, isPreferenceApplicable, preferenceDefinitions, preferenceGroups, resolvePreferences, setPreferenceValue, type PreferenceDefinition, type PreferenceGroup, type PreferencePath, type PreferencePolicy } from '@fudanda/shared/preferences';\nimport { InstantFilterToolbar } from \"@/components/list-page/InstantFilterToolbar\";\nimport { FilterSelect, KeywordInput } from \"@/components/search-filters\";\nimport { SettingDivider, SettingRow, SettingSection } from \"@/components/settings/SettingRow\";\nimport { useCurrentUserMenuTree, useMenuTree } from \"@fudanda/react-core/hooks/queries/menus\";\nimport { settingsKeys } from \"@fudanda/react-core/hooks/queries/settings\";\nimport { useListSearch } from \"@fudanda/react-core/hooks/useListSearch\";\nimport { usePinyinReady } from \"@fudanda/react-core/hooks/usePinyinReady\";\nimport { textMatches } from \"@fudanda/react-core/utils/pinyin\";\nimport { usePermission } from \"@fudanda/react-core/hooks/usePermission\";\nimport { useFlatMenus } from \"@/layouts/admin/useMenuDerived\";\nimport { THEME_COLOR_PRESETS } from \"@fudanda/react-core/lib/theme-color\";\nimport \"@/pages/system/settings/preference-policy-editor.css\";\ninterface PreferencePolicyEditorProps {\n readonly value: PreferencePolicy;\n readonly onChange: (next: PreferencePolicy) => void;\n readonly disabled?: boolean;\n}\ninterface PolicyFilters {\n keyword: string;\n group?: PreferenceGroup;\n changedOnly: boolean;\n lockedOnly: boolean;\n}\nconst INITIAL_FILTERS: PolicyFilters = { keyword: '', group: undefined, changedOnly: false, lockedOnly: false };\nconst GROUP_OPTIONS = preferenceGroups.map((group) => ({ value: group.id, label: group.label }));\nconst BOOLEAN_OPTIONS = [{ value: 'true', label: '开启' }, { value: 'false', label: '关闭' }];\nfunction isAdjusted(policy: PreferencePolicy, path: PreferencePath): boolean {\n return !canOverridePreference(path, policy)\n || getPreferenceValue(policy.defaults, path) !== getPreferenceValue(defaultPreferences, path);\n}\nfunction PolicyColorInput({ value, disabled, onChange, label }: {\n readonly value: string;\n readonly disabled?: boolean;\n readonly onChange: (next: string) => void;\n readonly label: string;\n}) {\n const preset = THEME_COLOR_PRESETS.find((entry) => entry.key === value);\n const color = preset?.light.primary ?? value;\n const options = THEME_COLOR_PRESETS.map((entry) => ({ value: entry.key, label: entry.name }));\n if (!preset)\n options.push({ value, label: value });\n return (<div className=\"preference-policy-editor__color\">\n <Select aria-label={label} value={value} optionList={options} disabled={disabled} onChange={(next) => {\n if (typeof next === 'string')\n onChange(next);\n }}/>\n <ColorPicker alpha={false} usePopover value={ColorPicker.colorStringToValue(color)} onChange={(next) => {\n if (!disabled)\n onChange(next.hex);\n }}>\n <button type=\"button\" className=\"preference-policy-editor__swatch\" disabled={disabled} aria-label=\"自定义主题色\" title=\"自定义主题色\" style={{ backgroundColor: color }}/>\n </ColorPicker>\n </div>);\n}\n/** 系统偏好草稿编辑器。所有字段始终可配置,适用条件只解释用户端何时生效。 */\nexport function PreferencePolicyEditor({ value, onChange, disabled }: PreferencePolicyEditorProps) {\n const { hasPermission } = usePermission();\n const canReadAllMenus = hasPermission('system:menu:list');\n const menuTree = useMenuTree({ enabled: canReadAllMenus });\n const currentMenus = useCurrentUserMenuTree();\n const flatMenus = useFlatMenus(menuTree.data ?? currentMenus.data ?? []);\n const { draftParams, bind, setField, handleReset } = useListSearch<PolicyFilters>({\n defaults: INITIAL_FILTERS,\n listKey: settingsKeys.module('ui'),\n refetchOnSearch: false,\n });\n const homeOptions = useMemo(() => {\n const options = [{ value: '/', label: '首页(默认)' }];\n const seen = new Set(['/']);\n for (const menu of flatMenus) {\n // 默认首页只接受站内路由;外链内嵌菜单已由 useFlatMenus 转为 /embed/{id}。\n if (!menu.path.startsWith('/') || menu.path.startsWith('//') || seen.has(menu.path))\n continue;\n seen.add(menu.path);\n options.push({ value: menu.path, label: [...menu.breadcrumb, menu.title].join(' / ') });\n }\n const configured = value.defaults.homePath;\n if (!seen.has(configured))\n options.push({ value: configured, label: `${configured}(当前已配置)` });\n return options;\n }, [flatMenus, value.defaults.homePath]);\n // 拼音词典就绪后重算 rows 过滤,补上已输入关键字的拼音命中\n const pinyinReady = usePinyinReady();\n const rows = useMemo(() => {\n void pinyinReady;\n const keyword = draftParams.keyword.trim();\n return preferenceDefinitions.filter((definition) => {\n if (draftParams.group && definition.group !== draftParams.group)\n return false;\n if (draftParams.changedOnly && !isAdjusted(value, definition.path))\n return false;\n if (draftParams.lockedOnly && canOverridePreference(definition.path, value))\n return false;\n if (!keyword)\n return true;\n // 名称 / 路径 / 说明 / 分组 / 适用条件:子串 + 拼音首字母 / 全拼\n return [\n definition.label, definition.path, definition.description,\n preferenceGroups.find((group) => group.id === definition.group)?.label,\n definition.applicableWhen && describePreferenceCondition(definition.applicableWhen),\n ].filter(Boolean).some((field) => textMatches(String(field), keyword));\n });\n }, [draftParams, value, pinyinReady]);\n const adjustedCount = preferenceDefinitions.filter((definition) => isAdjusted(value, definition.path)).length;\n const lockedCount = preferenceDefinitions.filter((definition) => !canOverridePreference(definition.path, value)).length;\n const defaultValues = resolvePreferences(value, {});\n function updateDefault(path: PreferencePath, next: string | number | boolean) {\n onChange({ ...value, defaults: setPreferenceValue(value.defaults, path, next) });\n }\n function resetField(path: PreferencePath) {\n onChange({\n defaults: setPreferenceValue(value.defaults, path, getPreferenceValue(defaultPreferences, path)),\n allowUserOverride: setPreferenceValue(value.allowUserOverride, path, true),\n });\n }\n function renderControl(definition: PreferenceDefinition): ReactNode {\n const { path, kind } = definition;\n const current = getPreferenceValue(value.defaults, path);\n const label = `${definition.label}默认值`;\n if (kind === 'boolean') {\n return <Select aria-label={label} value={String(current)} optionList={BOOLEAN_OPTIONS} disabled={disabled} onChange={(next) => updateDefault(path, next === 'true')}/>;\n }\n if (kind === 'number') {\n return <InputNumber aria-label={label} value={typeof current === 'number' ? current : undefined} min={definition.min} max={definition.max} step={definition.step ?? 1} precision={definition.step && definition.step < 1 ? undefined : 0} suffix={definition.unit} disabled={disabled} onChange={(next) => {\n if (typeof next === 'number' && Number.isFinite(next))\n updateDefault(path, next);\n }}/>;\n }\n if (kind === 'color') {\n return <PolicyColorInput value={String(current)} disabled={disabled} label={label} onChange={(next) => updateDefault(path, next)}/>;\n }\n if (kind === 'home-path') {\n return <Select aria-label={label} value={String(current)} optionList={homeOptions} filter disabled={disabled} loading={canReadAllMenus ? menuTree.isFetching : currentMenus.isFetching} onChange={(next) => {\n if (typeof next === 'string')\n updateDefault(path, next);\n }}/>;\n }\n if (kind === 'select') {\n return <Select<string | number> aria-label={label} value={typeof current === 'number' ? current : String(current)} optionList={[...(definition.options ?? [])]} disabled={disabled} onChange={(next) => {\n if (typeof next === 'string' || typeof next === 'number')\n updateDefault(path, next);\n }}/>;\n }\n return <Input aria-label={label} value={String(current)} disabled={disabled} onChange={(next) => updateDefault(path, next)}/>;\n }\n function renderDescription(definition: PreferenceDefinition): ReactNode {\n const applicable = isPreferenceApplicable(definition.path, defaultValues);\n return (<span className=\"preference-policy-editor__description\">\n {definition.description ? <span>{definition.description}</span> : null}\n {definition.applicableWhen ? <span>适用条件:{describePreferenceCondition(definition.applicableWhen)}</span> : null}\n {!applicable ? <span className=\"preference-policy-editor__hint\">当前系统默认组合不满足条件,满足条件后生效。</span> : null}\n {definition.path === 'homePath' ? <span>用户无权访问指定页面时回到可访问的首页。{!canReadAllMenus ? '当前仅列出你可访问的页面。' : ''}</span> : null}\n </span>);\n }\n return (<div className=\"preference-policy-editor\">\n <SettingSection title=\"偏好策略\" description=\"设置每项偏好的系统默认值,并决定用户是否可以修改。\">\n <Banner type=\"info\" closeIcon={null} className=\"preference-policy-editor__intro\" description=\"禁止用户修改的项目会在个人偏好中隐藏并使用系统值;子项是否显示取决于父项的生效值。更改将在保存后统一生效。\"/>\n <InstantFilterToolbar primary={<KeywordInput placeholder=\"搜索偏好名称 / 说明\" {...bind('keyword')}/>} filters={(<>\n <FilterSelect placeholder=\"全部分组\" items={GROUP_OPTIONS} {...bind('group')}/>\n <Checkbox checked={draftParams.changedOnly} onChange={(event) => setField('changedOnly')(Boolean(event.target.checked))}>仅看已调整</Checkbox>\n <Checkbox checked={draftParams.lockedOnly} onChange={(event) => setField('lockedOnly')(Boolean(event.target.checked))}>仅看禁止修改</Checkbox>\n </>)} onReset={handleReset}/>\n <Typography.Text type=\"tertiary\" size=\"small\">共 {preferenceDefinitions.length} 项 · 已调整 {adjustedCount} 项 · 禁止修改 {lockedCount} 项 · 当前显示 {rows.length} 项</Typography.Text>\n {rows.length === 0 ? <Empty description=\"没有符合筛选条件的偏好\"/> : null}\n {preferenceGroups.map((group) => {\n const fields = rows.filter((definition) => definition.group === group.id);\n if (fields.length === 0)\n return null;\n return (<SettingSection key={group.id} title={group.label}>\n <div className=\"preference-policy-editor__head\" aria-hidden=\"true\">\n <span>偏好设置</span>\n <div className=\"preference-policy-editor__controls\"><span>系统默认值</span><span>用户可修改</span><span /></div>\n </div>\n {fields.map((definition, index) => (<Fragment key={definition.path}>\n {index > 0 ? <SettingDivider /> : null}\n <div className=\"preference-policy-editor__row\" data-preference-path={definition.path}>\n <SettingRow title={definition.label} description={renderDescription(definition)} style={{ flexWrap: 'wrap' }} control={(<div className=\"preference-policy-editor__controls\">\n <div className=\"preference-policy-editor__value\">\n <span className=\"preference-policy-editor__mobile-label\">系统默认值</span>\n {renderControl(definition)}\n </div>\n <div className=\"preference-policy-editor__override\">\n <span className=\"preference-policy-editor__mobile-label\">用户可修改</span>\n <Switch aria-label={`${definition.label}用户可修改`} disabled={disabled} checked={canOverridePreference(definition.path, value)} onChange={(next) => onChange({ ...value, allowUserOverride: setPreferenceValue(value.allowUserOverride, definition.path, next) })}/>\n </div>\n <Tooltip content=\"恢复内置默认值并允许用户修改\">\n <Space>\n <Button theme=\"borderless\" size=\"small\" aria-label={`恢复${definition.label}内置策略`} icon={<RotateCcw size={14}/>} disabled={disabled || !isAdjusted(value, definition.path)} onClick={() => resetField(definition.path)}/>\n </Space>\n </Tooltip>\n </div>)}/>\n </div>\n </Fragment>))}\n </SettingSection>);\n })}\n </SettingSection>\n </div>);\n}\n","import { useEffect, useRef, type ComponentProps } from 'react';\nimport type { ApiWebsiteSettings } from '@fudanda/shared/api/settings';\nimport { settingsContract } from '@fudanda/react-core/lib/native-operations';\nimport { goTransport } from '@fudanda/react-core/lib/go-transport';\nimport { Button, Input, Toast, Upload } from '@/ui';\nimport { ImagePlus } from 'lucide-react';\nimport { SettingDivider, SettingRow } from '@/components/settings/SettingRow';\nimport AppLogo from '@/components/AppLogo';\nimport { useHostBrand } from '@/admin/host-brand';\n\nexport function WebsiteSettingsForm({ value, disabled, uploading, onChange, onUploadingChange }: {\n value: ApiWebsiteSettings;\n disabled: boolean;\n uploading: boolean;\n onChange: (patch: Partial<ApiWebsiteSettings>) => void;\n onUploadingChange: (value: boolean) => void;\n}) {\n const hostBrand = useHostBrand();\n const request = useRef<AbortController | null>(null);\n useEffect(() => () => { request.current?.abort(); }, []);\n const uploadLogo: NonNullable<ComponentProps<typeof Upload>['customRequest']> = options => {\n const controller = new AbortController();\n request.current?.abort();\n request.current = controller;\n onUploadingChange(true);\n void goTransport.upload(settingsContract.uploadLogo, { body: { file: options.file as File } }, { signal: controller.signal })\n .then(result => {\n if (controller.signal.aborted) return;\n onChange({ logoUrl: result.logoUrl });\n options.onSuccess?.(result);\n })\n .catch((error: unknown) => {\n if (controller.signal.aborted) return;\n const problem = error instanceof Error ? error : new Error('Logo 上传失败');\n Toast.error(problem.message);\n options.onError?.(problem);\n })\n .finally(() => {\n if (request.current === controller && !controller.signal.aborted) {\n request.current = null;\n onUploadingChange(false);\n }\n });\n return { abort: () => controller.abort() };\n };\n return <>\n <SettingRow title={<label htmlFor=\"website-name\">网站名称</label>} description=\"应用于登录页、管理台和浏览器标题。留空时沿用宿主默认名称。\" control={<Input id=\"website-name\" style={{ width: 280 }} maxLength={80} showClear disabled={disabled} value={value.name} placeholder={hostBrand?.name || '沿用宿主默认名称'} onChange={name => onChange({ name })} />} />\n <SettingDivider />\n <SettingRow title=\"网站 Logo\" description=\"支持 PNG、JPEG,最大 2 MB,尺寸不超过 4096 × 4096。保存后生效。\" control={<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}>\n <div style={{ width: 96, height: 96, padding: 12, border: '1px solid var(--arc-antd-color-border)', borderRadius: 'var(--semi-border-radius-medium)', display: 'grid', placeItems: 'center' }}>\n {value.logoUrl ? <img src={goTransport.resolveURL(value.logoUrl)} alt=\"网站 Logo 预览\" style={{ width: '100%', height: '100%', objectFit: 'contain' }} /> : <AppLogo size={64} hostDefault />}\n </div>\n <Upload accept=\"image/png,image/jpeg\" showUploadList={false} disabled={disabled || uploading} customRequest={uploadLogo} beforeUpload={({ file }) => {\n if (Number(file.size) > 2 * 1024 * 1024 || !['image/png', 'image/jpeg'].includes(file.fileInstance?.type ?? '')) {\n Toast.error('请选择不超过 2 MB 的 PNG 或 JPEG 图片');\n return false;\n }\n return true;\n }}><Button icon={<ImagePlus size={14}/>} disabled={disabled || uploading} loading={uploading}>{value.logoUrl ? '更换 Logo' : '上传 Logo'}</Button></Upload>\n {value.logoUrl ? <Button size=\"small\" theme=\"borderless\" disabled={disabled || uploading} onClick={() => onChange({ logoUrl: '' })}>恢复默认 Logo</Button> : <span style={{ color: 'var(--arc-antd-color-text-2)', fontSize: 12 }}>使用宿主默认 Logo</span>}\n </div>} />\n </>;\n}\n","import { Alert, Button, Space, Switch, Typography } from 'antd';\nimport { Link } from 'react-router-dom';\nimport type { ApiMCPSettings } from '@fudanda/shared/api/settings';\nimport { mcpTools } from '@fudanda/shared/api/mcp-tools';\nimport { goTransport } from '@fudanda/react-core/lib/go-transport';\nimport { SettingDivider, SettingRow } from '@/components/settings/SettingRow';\nimport { copyTextWithToast } from '@/utils/clipboard';\nimport { useWebMcpControl } from '@/webmcp-control';\n\nexport function McpSettingsForm({ value, disabled, onChange }: {\n value: ApiMCPSettings;\n disabled: boolean;\n onChange: (patch: Partial<ApiMCPSettings>) => void;\n}) {\n const webmcp = useWebMcpControl();\n const endpoint = new URL(goTransport.resolveURL('/api/v1/mcp'), window.location.origin).href;\n const example = JSON.stringify({ url: endpoint, headers: { Authorization: 'Bearer <API_TOKEN>' } }, null, 2);\n return <>\n <SettingRow title=\"启用 MCP\" description=\"向外部客户端提供只读工具。保存后立即生效,关闭后拒绝新的 MCP 请求。\"\n control={<Switch aria-label=\"启用 MCP\" checked={value.enabled} disabled={disabled} onChange={enabled => onChange({ enabled })} />} />\n <SettingDivider />\n <SettingRow title=\"启用 WebMCP\" description=\"已登录且未锁屏的管理台自动加载浏览器工具,沿用当前账号权限,删除操作仍需确认。保存后同步到已打开的页面。\"\n control={<Switch aria-label=\"启用 WebMCP\" checked={value.webMcpEnabled} disabled={disabled} onChange={webMcpEnabled => onChange({ webMcpEnabled })} />} />\n {value.webMcpEnabled && webmcp && !webmcp.configured ? <Alert type=\"warning\" title=\"当前宿主尚未接入 WebMCP\" description=\"请先在宿主装配可选的 WebMCP 入口,接入后跟随此系统开关。\" /> : null}\n {webmcp?.status === 'error' ? <Alert type=\"error\" title=\"WebMCP 加载失败\" description=\"请检查管理台资源并刷新页面重试。\" /> : null}\n <Typography.Title level={5}>MCP 连接</Typography.Title>\n <Space wrap style={{ marginBottom: 12 }}>\n <Typography.Text code style={{ overflowWrap: 'anywhere' }}>{endpoint}</Typography.Text>\n <Button onClick={() => void copyTextWithToast(endpoint)}>复制地址</Button>\n </Space>\n <Typography.Paragraph>客户端选择 Streamable HTTP,通过 Authorization 请求头携带 API Token。前往<Link to=\"/system/api-tokens\">AI 与集成 → API Token</Link>创建和管理凭据;可用工具由账号权限与 Token 范围共同决定。</Typography.Paragraph>\n <pre style={{ padding: 16, whiteSpace: 'pre-wrap', overflowWrap: 'anywhere', background: 'var(--arc-antd-color-fill-0)', borderRadius: 'var(--semi-border-radius-medium)' }}>{example}</pre>\n <Button onClick={() => void copyTextWithToast(example)}>复制连接示例</Button>\n <Typography.Title level={5}>只读工具</Typography.Title>\n <ul>{mcpTools.map(tool => <li key={tool.name}><Typography.Text>{tool.title}</Typography.Text> <Typography.Text type=\"secondary\">{tool.name}</Typography.Text></li>)}</ul>\n <Typography.Paragraph type=\"secondary\">MCP 与 WebMCP 独立控制。宿主已配置桌面 Relay 时,桥接跟随 WebMCP 开关启停;连接状态以客户端实际列出工具为准。</Typography.Paragraph>\n </>;\n}\n","import type { ApiSettingModule, ApiUISettings, ApiWebsiteSettings, ApiMCPSettings } from '@fudanda/shared/api/settings';\nimport { settingsWriteOperations } from '@fudanda/react-core/lib/native-operations';\nimport type { SettingsModuleKey } from '@fudanda/react-core/hooks/queries/settings';\nimport { foundationSettingSchemas } from '@fudanda/shared/settings/foundation';\nimport { useEffect, useMemo, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { Banner, Button, Spin, Tag, Toast, Typography } from '@/ui';\nimport { ExternalLink, Save } from 'lucide-react';\nimport { CAPTCHA_COMPLEXITY_LABELS, SETTINGS_MODULES } from '@fudanda/shared/settings';\nimport { getPreferenceValue, preferenceDefinitions } from '@fudanda/shared/preferences';\nimport { MasterDetailLayout } from '@/components/MasterDetailLayout';\nimport { settingFieldLabels } from './field-labels';\nimport { SchemaForm } from '@/components/settings/SchemaForm';\nimport { useSaveSettings, useSettings } from '@fudanda/react-core/hooks/queries/settings';\nimport { GoaError } from '@fudanda/client';\nimport { PreferencePolicyEditor } from './PreferencePolicyEditor';\nimport { WebsiteSettingsForm } from './WebsiteSettingsForm';\nimport { McpSettingsForm } from './McpSettingsForm';\n\nconst { Text, Title } = Typography;\nconst ENUM_LABELS: Record<string, Record<string, string>> = { captchaComplexity: CAPTCHA_COMPLEXITY_LABELS };\n\n/** Shared full-document editor for the general settings list and dedicated pages. */\nexport function SettingModuleDetail({ module, meta }: {\n readonly module: SettingsModuleKey;\n readonly meta: ApiSettingModule;\n}) {\n const def = SETTINGS_MODULES[module];\n const query = useSettings(module);\n const save = useSaveSettings(module);\n const envelope = query.data;\n const [draft, setDraft] = useState<Record<string, unknown> | null>(null);\n const [uploadingLogo, setUploadingLogo] = useState(false);\n useEffect(() => { setDraft(null); setUploadingLogo(false); }, [envelope?.version]);\n const value = useMemo(() => (draft ?? (envelope?.effective as Record<string, unknown> | undefined) ?? null), [draft, envelope]);\n const dirty = draft !== null && JSON.stringify(draft) !== JSON.stringify(envelope?.effective);\n const uiValue = module === 'ui' && value ? value as ApiUISettings : null;\n const savedUi = module === 'ui' && envelope ? envelope.effective as ApiUISettings : null;\n const changedPreferences = uiValue && savedUi ? preferenceDefinitions.filter(({ path }) => getPreferenceValue(uiValue.preferences.defaults, path) !== getPreferenceValue(savedUi.preferences.defaults, path)\n || getPreferenceValue(uiValue.preferences.allowUserOverride, path) !== getPreferenceValue(savedUi.preferences.allowUserOverride, path)) : [];\n const forcedChanges = changedPreferences.filter(({ path }) => uiValue && !getPreferenceValue(uiValue.preferences.allowUserOverride, path)).length;\n async function handleSave() {\n if (!envelope || !value || !meta.canWrite)\n return;\n const data = module === 'website' ? { ...value, name: (value.name as string).trim() } : value;\n const parsed = settingsWriteOperations[module].input.safeParse({ body: { version: envelope.version, data } });\n if (!parsed.success) {\n const issue = parsed.error.issues[0];\n Toast.error(`${issue.path.join('.') || '表单'}:${issue.message}`);\n return;\n }\n try {\n await save.mutateAsync(parsed.data);\n setDraft(null);\n Toast.success('设置已保存');\n }\n catch (err) {\n if (err instanceof GoaError && err.status === 409) {\n setDraft(null);\n void query.refetch();\n }\n }\n }\n return (<MasterDetailLayout.Body padding={24}>\n <div style={{ maxWidth: module === 'ui' ? 1040 : 760 }}>\n <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, marginBottom: 8 }}>\n <div>\n <Title heading={5} style={{ margin: 0 }}>{def.title}</Title>\n <Text type=\"tertiary\">{def.description}</Text>\n </div>\n {!meta.page && meta.canWrite ? (<Button theme=\"solid\" icon={<Save size={14}/>} loading={save.isPending} disabled={!dirty || uploadingLogo} onClick={() => void handleSave()}>保存</Button>) : null}\n </div>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap', marginBottom: 12 }}>\n <Tag color=\"grey\">系统级</Tag>\n {envelope ? <Tag>版本 {envelope.version}</Tag> : null}\n {meta.overriddenCount > 0 ? <Tag color=\"orange\">{meta.overriddenCount} 项覆盖</Tag> : null}\n {envelope?.updatedAt ? <Text type=\"tertiary\" size=\"small\">最后修改 {envelope.updatedAt}</Text> : null}\n </div>\n {meta.page ? (<Banner type=\"info\" closeIcon={null} description={<span>该模块有专用设置页面,请前往<Link to={meta.page} style={{ margin: '0 4px', display: 'inline-flex', alignItems: 'center', gap: 4 }}>{def.title}<ExternalLink size={12}/></Link>修改。</span>}/>) : null}\n {!meta.page && !meta.canWrite ? (<Banner type=\"warning\" closeIcon={null} description={meta.scope === 'platform' ? '平台级设置仅平台管理员可修改,当前为只读。' : '当前账号没有修改权限,当前为只读。'}/>) : null}\n {changedPreferences.length > 0 ? <Banner type=\"info\" closeIcon={null} description={`待保存 ${changedPreferences.length} 项偏好策略,其中 ${forcedChanges} 项将统一使用系统值;允许修改的项目保留用户已有选择。`}/> : null}\n {query.error ? <Banner type=\"danger\" closeIcon={null} description={<span>设置加载失败。<Button theme=\"borderless\" onClick={() => void query.refetch()}>重试</Button></span>} /> : null}\n <Spin spinning={query.isLoading}>\n {envelope && value ? (<>\n {module === 'website' ? <WebsiteSettingsForm key={envelope.version} value={value as ApiWebsiteSettings} disabled={Boolean(meta.page) || !meta.canWrite || save.isPending} uploading={uploadingLogo} onUploadingChange={setUploadingLogo} onChange={patch => setDraft(previous => ({ ...envelope.effective, ...previous, ...patch }))} />\n : module === 'mcp' ? <McpSettingsForm value={value as ApiMCPSettings} disabled={!meta.canWrite || save.isPending} onChange={patch => setDraft({ ...value, ...patch })} />\n : <SchemaForm schema={foundationSettingSchemas[module]} excludeFields={module === 'ui' ? ['preferences'] : undefined} fieldLabels={settingFieldLabels} value={value} inheritedValue={envelope.inherited as Record<string, unknown>} onChange={next => setDraft({ ...value, ...next })} disabled={Boolean(meta.page) || !meta.canWrite} enumLabels={ENUM_LABELS}/>}\n {uiValue ? <PreferencePolicyEditor value={uiValue.preferences} onChange={preferences => setDraft({ ...value, preferences })} disabled={!meta.canWrite}/> : null}\n </>) : null}\n </Spin>\n </div>\n </MasterDetailLayout.Body>);\n}\n"]}