Files
Nexus/web/app/assets/SettingsPage-D1T1Ggix.js
T
Nexus Agent dabfc128b3 feat: 浏览器 RDP、推送提示音与子机指标等多项功能
- 3389远程页 + guacd 侧车 WebSocket 桥(无 RDP 会话审计)
- 文件推送完成独立提示音,至少一台成功即播放
- 子机列表展开 CPU/内存/磁盘 sparkline 与指标采样落库
- 终端全量服务器列表、连接中状态;告警 Telegram 公网 IP
- SSH 密钥凭据 UI 简化;子机搜索未设路径筛选

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 01:37:32 +08:00

4 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import{n as e,t}from"./useSnackbar-CnnJE332.js";import{n,t as r}from"./VRow-w_jxb14C.js";import{Cr as i,Dt as a,Er as o,Et as ee,I as s,K as te,Or as ne,Ot as re,Q as c,Rr as l,Sr as u,Tr as d,Tt as ie,Ur as f,Vr as p,Xt as ae,Zr as m,_ as h,br as g,di as _,en as v,g as oe,gr as y,h as se,hi as b,hr as ce,ii as x,l as le,m as ue,v as S,wt as C,xr as w,y as T,yr as E}from"./index--ZLWJhtp.js";import{t as D}from"./usePageActivateRefresh-CJkUcCiK.js";import{S as O,_ as k,b as A,t as j,v as M}from"./VTextField-BHVsCc73.js";import{t as N}from"./VSelect-fmsU8QCW.js";import{t as P}from"./VDialog-BCdcmy4p.js";import{n as F,r as I,t as L}from"./validation-BMzYTJL4.js";import{t as de}from"./VForm-C7ctDLNB.js";import{n as fe,t as pe}from"./VTextarea-8gALE-W1.js";import{n as me,r as he}from"./useTerminalQuickCommands-DY0WyOwM.js";var R={key:1,class:`text-medium-emphasis text-body-2 mb-4`},ge={class:`text-caption text-medium-emphasis mr-2`,style:{"min-width":`1.5rem`}},z={class:`d-flex flex-wrap ga-2`},_e=ne({__name:`TerminalQuickCommandsSettings`,setup(e){let n=t(),{commands:r,loading:a,load:ee,create:ne,update:re,remove:ie,reorder:ae,customCommands:v,builtinCommands:ce}=me(),C=x(!1),E=x(null),D=x(``),N=x(``),F=x(!1),I=x(!1),L=x(!1),de=x(null),fe=x(``);function pe(){E.value=null,D.value=``,N.value=``,C.value=!0}function _e(e){E.value=e.id,D.value=e.name,N.value=e.cmd.replace(/\r$/,``),C.value=!0}async function ve(){if(!D.value.trim()||!N.value.trim()){n(`名称和命令不能为空`,`warning`);return}let e=N.value.trim();e.endsWith(`\r`)||(e+=`\r`),F.value=!0;try{E.value?await re(E.value,D.value,e):await ne(D.value,e),C.value=!1,n(`已保存`)}catch(e){n(e instanceof Error?e.message:`保存失败`,`error`)}finally{F.value=!1}}function ye(e){de.value=e.id,fe.value=e.name,L.value=!0}async function be(){if(de.value){F.value=!0;try{await ie(de.value),L.value=!1,n(`已删除`)}catch(e){n(e instanceof Error?e.message:`删除失败`,`error`)}finally{F.value=!1}}}async function xe(e){let t=v();if(e<=0)return;let n=t.map(e=>e.id),r=n[e-1];n[e-1]=n[e],n[e]=r,await Ce(n)}async function Se(e){let t=v();if(e>=t.length-1)return;let n=t.map(e=>e.id),r=n[e+1];n[e+1]=n[e],n[e]=r,await Ce(n)}async function Ce(e){I.value=!0;try{await ae(e)}catch(e){n(e instanceof Error?e.message:`排序失败`,`error`)}finally{I.value=!1}}return l(()=>{ee().catch(e=>n(e.message,`error`))}),(e,t)=>(p(),w(k,{id:`terminal-quick-commands`,elevation:`0`,border:``,rounded:`lg`,class:`mb-4`},{default:m(()=>[o(A,{class:`d-flex align-center flex-wrap ga-2`},{default:m(()=>[o(c,{class:`mr-1`},{default:m(()=>[...t[6]||=[d(`mdi-flash`,-1)]]),_:1}),t[8]||=d(` 终端快捷命令 `,-1),o(h),o(s,{size:`small`,color:`primary`,variant:`flat`,"prepend-icon":`mdi-plus`,onClick:pe},{default:m(()=>[...t[7]||=[d(` 添加命令 `,-1)]]),_:1})]),_:1}),o(S),o(M,null,{default:m(()=>[t[9]||=g(`div`,{class:`text-body-2 text-medium-emphasis mb-4`},` 自定义命令显示在终端栏最前(优先级最高)。终端页仅可点击执行;在此添加、编辑、删除与调整顺序。 `,-1),_(a)?(p(),w(te,{key:0,indeterminate:``,color:`primary`,class:`mb-3`})):u(``,!0),_(v)().length===0&&!_(a)?(p(),i(`div`,R,` 暂无自定义命令,点击「添加命令」创建。 `)):u(``,!0),_(v)().length?(p(),w(le,{key:2,density:`compact`,class:`mb-4 pa-0`},{default:m(()=>[(p(!0),i(y,null,f(_(v)(),(e,t)=>(p(),w(ue,{key:e.id,border:``,rounded:`lg`,class:`mb-2`},{prepend:m(()=>[g(`span`,ge,b(t+1),1)]),append:m(()=>[o(s,{icon:`mdi-arrow-up`,size:`x-small`,variant:`text`,disabled:t===0||I.value,onClick:e=>xe(t)},null,8,[`disabled`,`onClick`]),o(s,{icon:`mdi-arrow-down`,size:`x-small`,variant:`text`,disabled:t===_(v)().length-1||I.value,onClick:e=>Se(t)},null,8,[`disabled`,`onClick`]),o(s,{icon:`mdi-pencil`,size:`x-small`,variant:`text`,onClick:t=>_e(e)},null,8,[`onClick`]),o(s,{icon:`mdi-delete`,size:`x-small`,variant:`text`,color:`error`,onClick:t=>ye(e)},null,8,[`onClick`])]),default:m(()=>[o(se,null,{default:m(()=>[d(b(e.name),1)]),_:2},1024),o(oe,{class:`font-mono text-truncate`},{default:m(()=>[d(b(e.cmd),1)]),_:2},1024)]),_:2},1024))),128))]),_:1})):u(``,!0),t[10]||=g(`div`,{class:`text-subtitle-2 mb-2`},`系统内置(不可编辑)`,-1),g(`div`,z,[(p(!0),i(y,null,f(_(ce)(),e=>(p(),w(T,{key:e.id,size:`small`,variant:`outlined`,label:``},{default:m(()=>[d(b(e.name),1)]),_:2},1024))),128))])]),_:1}),o(P,{modelValue:C.value,"onUpdate:modelValue":t[3]||=e=>C.value=e,"max-width":`520`},{default:m(()=>[o(k,{border:``},{default:m(()=>[o(A,null,{default:m(()=>[d(b(E.value?`编辑命令`:`添加快捷命令`),1)]),_:1}),o(M,null,{default:m(()=>[o(j,{modelValue:D.value,"onUpdate:modelValue":t[0]||=e=>D.value=e,label:`显示名称`,variant:`outlined`,density:`compact`,class:`mb-3`},null,8,[`modelValue`]),t[11]||=g(`div`,{class:`text-body-2 text-medium-emphasis mb-2`},`命令内容(发送时自动追加回车)`,-1),o(he,{modelValue:N.value,"onUpdate:modelValue":t[1]||=e=>N.value=e,multiline:``,rows:4,placeholder:`systemctl status nginx`},null,8,[`modelValue`])]),_:1}),o(O,null,{default:m(()=>[o(h),o(s,{variant:`text`,onClick:t[2]||=e=>C.value=!1},{default:m(()=>[...t[12]||=[d(`取消`,-1)]]),_:1}),o(s,{color:`primary`,variant:`flat`,loading:F.value,onClick:ve},{default:m(()=>[...t[13]||=[d(`保存`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1},8,[`modelValue`]),o(P,{modelValue:L.value,"onUpdate:modelValue":t[5]||=e=>L.value=e,"max-width":`400`},{default:m(()=>[o(k,{border:``},{default:m(()=>[o(A,null,{default:m(()=>[...t[14]||=[d(`删除命令`,-1)]]),_:1}),o(M,null,{default:m(()=>[d(`确定删除「`+b(fe.value)+`」?`,1)]),_:1}),o(O,null,{default:m(()=>[o(h),o(s,{variant:`text`,onClick:t[4]||=e=>L.value=!1},{default:m(()=>[...t[15]||=[d(`取消`,-1)]]),_:1}),o(s,{color:`error`,variant:`flat`,loading:F.value,onClick:be},{default:m(()=>[...t[16]||=[d(`删除`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1},8,[`modelValue`])]),_:1}))}}),ve=[{key:`notify_alert_cpu`,label:`CPU 超阈值告警`,desc:`CPU 超阈值或恢复时推送;关闭则该类告警与恢复均不通知`,group:`服务器资源`},{key:`notify_alert_mem`,label:`内存超阈值告警`,desc:`内存超阈值或恢复时推送;关闭则该类告警与恢复均不通知`,group:`服务器资源`},{key:`notify_alert_disk`,label:`磁盘超阈值告警`,desc:`磁盘超阈值或恢复时推送;关闭则该类告警与恢复均不通知`,group:`服务器资源`},{key:`notify_recovery`,label:`资源恢复正常`,desc:`CPU/内存/磁盘回落正常时推送(须总开关开启且对应资源开关未关)`,group:`服务器资源`},{key:`notify_alert_offline`,label:`子机离线告警`,desc:`Agent 心跳丢失、子机由在线变为离线时推送一次`,group:`服务器`},{key:`notify_time_drift`,label:`时钟偏差严重 (≥60s)`,desc:`子机与主机时间偏差过大影响 TOTP/cron`,group:`服务器`},{key:`notify_system_redis`,label:`Redis 连接异常 / 恢复`,desc:`Redis 不可达或恢复时推送`,group:`系统`},{key:`notify_system_mysql`,label:`MySQL 连接异常 / 恢复`,desc:`MySQL 不可达或恢复时推送`,group:`系统`},{key:`notify_restart`,label:`Nexus 后端重启`,desc:`Python 服务崩溃重启成功/失败时推送`,group:`系统`},{key:`notify_script_complete`,label:`脚本执行完成`,desc:`脚本批量执行结束时推送汇总与看板链接(不含失败明细)`,group:`运维`}],ye={class:`d-flex flex-wrap ga-2 mb-4`},be={class:`text-caption text-medium-emphasis font-weight-medium mt-2 mb-1`},xe={class:`d-flex flex-wrap align-center ga-2 mb-3`},Se={class:`d-flex flex-wrap ga-2`},Ce={key:1,class:`text-caption text-medium-emphasis mt-2 mb-0`},we={class:`d-flex align-center mb-3`},Te={key:0,class:`text-center mb-3`},Ee=[`src`],De={key:0,class:`d-flex align-center justify-center ga-2 mt-2`},Oe={class:`text-caption pa-1 bg-grey-lighten-4 rounded`,style:{"user-select":`all`}},ke={key:1},Ae={key:1,class:`d-flex ga-2 align-center`},je={class:`d-flex align-center ga-2`},Me={class:`d-flex flex-wrap ga-2 mb-3`},Ne={key:0,class:`mb-4`},Pe={class:`text-caption text-medium-emphasis mb-2`},Fe={key:0},Ie={class:`d-flex flex-wrap ga-1`},Le={key:1,class:`text-caption text-medium-emphasis mb-4`},Re={class:`text-caption text-medium-emphasis`},ze={class:`text-body-2 mb-2`},Be={class:`d-flex flex-wrap ga-1 mb-2`,style:{"max-height":`240px`,"overflow-y":`auto`}},Ve={key:0,class:`text-body-2 text-medium-emphasis`},B=ne({name:`SettingsPage`,__name:`SettingsPage`,setup(ne){let l=t(),{saveToServer:oe,previewSound:se}=ie(),{saveToServer:me,previewSound:he}=C(),R=ae(),ge=a,z=x(ee),B=x(!1),V=x(ee),H=x(!1),He=x(!1),U=x({system_name:``,system_title:``,cpu_alert_threshold:80,mem_alert_threshold:80,disk_alert_threshold:80,db_pool_size:10,db_max_overflow:20,telegram_bot_token:``,telegram_chat_id:``}),W=x(``),G=x(!1),Ue=x(!1),We=x(null),Ge=x(!1),Ke=x(!1),K=x(!1),qe=x([]),q=x(!1),Je=x(!1),Ye=x({}),Xe=x(null),J=x(null),Ze=x(!1),Qe=x(!1),$e=E(()=>{let e={};for(let t of ve)e[t.group]||(e[t.group]=[]),e[t.group].push(t);return e});function et(e){let t=String(e??`true`).trim().toLowerCase();return![`false`,`0`,`no`,`off`].includes(t)}let Y=x({current:``,new_pw:``,confirm:``,totp_code:``}),tt=x(!1),nt=x(``),rt=x(``),it=x(``),at=x(!1),ot=x(!1),st=x(``),ct=x(``),lt=x(!1),X=x(!1),ut=x(``),dt=x(!1),ft=x(!1),pt=x(!1),Z=x(``),mt=x(``),Q=x([]),ht=x(null),gt=x(0),_t=x(!1),vt=x(!1),$=x(!1),yt=x([]),bt=E(()=>Q.value.slice(0,24));async function xt(e=!1){e||(He.value=!0);try{let e=await v.get(`/settings/`),t=Array.isArray(e)?e:[],n=Object.keys(U.value);for(let e of t){let t=e.key;if(t===`telegram_bot_token`){G.value=!!e.value_set,W.value=``;continue}if(t===`script_exec_complete_sound`){z.value=re(e.value);continue}if(t===`push_complete_sound`){V.value=re(e.value);continue}if(t&&ve.some(e=>e.key===t)){Ye.value[t]=et(e.value);continue}if(t&&n.includes(t)){let n=e.value;typeof n==`string`&&/^\d+(\.\d+)?$/.test(n)?U.value[t]=Number(n):U.value[t]=n}}}catch{l(`加载设置失败`,`error`)}finally{e||(He.value=!1)}}async function St(){try{let e=await v.get(`/settings/ip-allowlist`);pt.value=e.enabled||!1,Z.value=e.subscription_url||``,mt.value=(e.manual_ips||[]).join(`
`),Q.value=e.subscription_ips||[],ht.value=e.last_refresh||null,gt.value=e.total_count??new Set([...e.manual_ips||[],...e.subscription_ips||[]]).size}catch{l(`加载白名单失败`,`error`)}}async function Ct(){let e=We.value;if(e){let{valid:t}=await e.validate();if(!t)return}Ue.value=!0;try{let e=Object.entries(U.value).filter(([e])=>[`system_name`,`system_title`,`cpu_alert_threshold`,`mem_alert_threshold`,`disk_alert_threshold`,`db_pool_size`,`db_max_overflow`].includes(e));for(let[t,n]of e)await v.put(`/settings/${t}`,{value:String(n??``)});l(`设置已保存`)}catch(e){l(e instanceof Error?e.message:`保存失败`,`error`)}finally{Ue.value=!1}}async function wt(e){let t=W.value.trim(),n=String(U.value.telegram_chat_id||``).trim();return e?.requireToken&&!t&&!G.value?(l(`请先填写 Bot Token`,`error`),!1):e?.requireChatId&&!n?(l(`请先填写或检测 Chat ID`,`error`),!1):(t&&(await v.put(`/settings/telegram_bot_token`,{value:t}),G.value=!0,W.value=``,q.value=!1),n&&await v.put(`/settings/telegram_chat_id`,{value:n}),!0)}async function Tt(e=!1){e||(Ze.value=!0);try{J.value=await v.get(`/settings/ops-patrol/status`)}catch{J.value=null}finally{e||(Ze.value=!1)}}async function Et(){Qe.value=!0;try{if(!await wt({requireToken:!0,requireChatId:!0}))return;let e=await v.post(`/settings/ops-patrol/sync-telegram`,{});J.value={...J.value||{},telegram_layer3_ready:e.telegram_layer3_ready,telegram_env_synced:e.env_status.env_synced,env_status:e.env_status,telegram_app_configured:!0},l(`巡检 Telegram 已同步至 .env`),await Tt()}catch(e){l(e instanceof Error?e.message:`同步失败`,`error`)}finally{Qe.value=!1}}async function Dt(){Je.value=!0;try{let e=W.value.trim(),t=String(U.value.telegram_chat_id||``).trim();if(!e&&!t&&!G.value){l(`请先填写 Bot Token 或 Chat ID`,`warning`);return}await wt(),await Tt(),l(`Telegram 配置已保存`)}catch(e){l(e instanceof Error?e.message:`保存失败`,`error`)}finally{Je.value=!1}}async function Ot(e){if(!e||B.value)return;let t=z.value;z.value=e,B.value=!0;try{await oe(e),l(`提示音设置已保存`)}catch(e){z.value=t,l(e instanceof Error?e.message:`保存失败`,`error`)}finally{B.value=!1}}function kt(){se(z.value)}async function At(e){if(!e||H.value)return;let t=V.value;V.value=e,H.value=!0;try{await me(e),l(`推送提示音设置已保存`)}catch(e){V.value=t,l(e instanceof Error?e.message:`保存失败`,`error`)}finally{H.value=!1}}function jt(){he(V.value)}async function Mt(e,t){let n=Ye.value[e];Ye.value[e]=t,Xe.value=e;try{await v.put(`/settings/${e}`,{value:t?`true`:`false`})}catch(t){Ye.value[e]=n,l(t instanceof Error?t.message:`保存失败,已撤销`,`error`)}finally{Xe.value=null}}async function Nt(){Ge.value=!0;try{if(!await wt({requireToken:!0,requireChatId:!0}))return;await v.post(`/settings/telegram/test`),l(`测试消息已发送`)}catch(e){l(e instanceof Error?e.message:`发送失败`,`error`)}finally{Ge.value=!1}}async function Pt(){Ke.value=!0;try{if(!await wt({requireToken:!0}))return;if(qe.value=(await v.get(`/settings/telegram/chats`)).chats||[],!qe.value.length){l(`未检测到对话,请先向 Bot 发送一条消息后再试`,`warning`);return}K.value=!0}catch(e){l(e instanceof Error?e.message:`检测失败`,`error`)}finally{Ke.value=!1}}async function Ft(e){let t=String(e.id);U.value.telegram_chat_id=t,K.value=!1;try{await v.put(`/settings/telegram_chat_id`,{value:t}),l(`已选择 Chat ID: ${t}`)}catch(e){l(e instanceof Error?e.message:`保存 Chat ID 失败`,`error`)}}async function It(){if(Y.value.new_pw!==Y.value.confirm){l(`两次密码不一致`,`error`);return}tt.value=!0;try{let e={current_password:Y.value.current,new_password:Y.value.new_pw};R.admin?.totp_enabled&&Y.value.totp_code&&(e.totp_code=Y.value.totp_code),await v.put(`/auth/password`,e),Y.value={current:``,new_pw:``,confirm:``,totp_code:``},l(`密码已修改`)}catch(e){l(e instanceof Error?e.message:`修改失败`,`error`)}finally{tt.value=!1}}async function Lt(){try{let e=await v.post(`/auth/totp/setup`);nt.value=e.qr_code,rt.value=e.secret||``}catch(e){l(e instanceof Error?e.message:`生成失败`,`error`)}}async function Rt(){at.value=!0;try{await v.post(`/auth/totp/enable`,{code:it.value}),await R.fetchProfile(),nt.value=``,it.value=``,l(`TOTP 已启用`)}catch(e){l(e.message||`启用失败`,`error`)}finally{at.value=!1}}function zt(){ot.value=!0}async function Bt(){lt.value=!0;try{await v.post(`/auth/totp/disable`,{admin_id:R.admin?.id,current_password:st.value,totp_code:ct.value}),await R.fetchProfile(),ot.value=!1,st.value=``,ct.value=``,l(`TOTP 已禁用`)}catch(e){l(e.message||`禁用失败`,`error`)}finally{lt.value=!1}}function Vt(){rt.value&&(navigator.clipboard.writeText(rt.value),l(`密钥已复制`))}async function Ht(){if(X.value){X.value=!1;return}dt.value=!0;try{ut.value=(await v.post(`/settings/api-key/reveal`,{})).value,X.value=!0}catch(e){l(e instanceof Error?e.message:`获取 API Key 失败`,`error`)}finally{dt.value=!1}}async function Ut(){if(q.value){q.value=!1,G.value&&(W.value=``);return}if(W.value.trim()){q.value=!0;return}if(!G.value){q.value=!0;return}ft.value=!0;try{W.value=(await v.post(`/settings/telegram/reveal-token`,{})).value,q.value=!0}catch(e){l(e instanceof Error?e.message:`获取 Bot Token 失败`,`error`)}finally{ft.value=!1}}function Wt(){if(!X.value||!ut.value){l(`请先点击眼睛图标显示 API Key`,`warning`);return}navigator.clipboard.writeText(ut.value),l(`已复制到剪贴板`)}async function Gt(){try{await v.post(`/settings/ip-allowlist/toggle?enabled=${pt.value?`true`:`false`}`)}catch(e){l(e instanceof Error?e.message:`操作失败`,`error`)}}async function Kt(){let e=Z.value.trim();if(!e){l(`请先填写订阅 URL`,`error`);return}vt.value=!0;try{if(yt.value=(await v.post(`/settings/ip-allowlist/parse-subscription`,{url:e})).hosts||[],!yt.value.length){l(`未能解析出任何节点`,`warning`);return}$.value=!0}catch(e){l(e instanceof Error?e.message:`解析失败`,`error`)}finally{vt.value=!1}}async function qt(){_t.value=!0;try{let e={manual_ips:mt.value.split(`
`).map(e=>e.trim()).filter(Boolean),subscription_url:Z.value.trim(),enabled:pt.value};await v.post(`/settings/ip-allowlist`,e),l(`白名单已保存`),$.value=!1,await St()}catch(e){l(e instanceof Error?e.message:`保存失败`,`error`)}finally{_t.value=!1}}async function Jt(e=!1){await Promise.all([xt(e),St(),Tt(e),R.fetchProfile()])}return D(e=>Jt(e)),(t,a)=>(p(),w(e,{fluid:``,class:`pa-6`},{default:m(()=>[o(r,null,{default:m(()=>[o(n,{cols:`12`,lg:`6`},{default:m(()=>[o(k,{elevation:`0`,border:``,rounded:`lg`,class:`mb-4`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[28]||=[d(`mdi-cog`,-1)]]),_:1}),a[29]||=d(` 系统设置 `,-1)]),_:1}),He.value?(p(),w(te,{key:0,indeterminate:``,color:`primary`})):u(``,!0),o(S),o(de,{ref_key:`systemSettingsFormRef`,ref:We,onSubmit:ce(Ct,[`prevent`])},{default:m(()=>[o(M,null,{default:m(()=>[o(j,{modelValue:U.value.system_name,"onUpdate:modelValue":a[0]||=e=>U.value.system_name=e,label:`系统名称`,variant:`outlined`,density:`compact`,class:`mb-3`,rules:[_(I)(`系统名称`)]},null,8,[`modelValue`,`rules`]),o(j,{modelValue:U.value.system_title,"onUpdate:modelValue":a[1]||=e=>U.value.system_title=e,label:`系统标题`,variant:`outlined`,density:`compact`,class:`mb-3`,rules:[_(I)(`系统标题`)]},null,8,[`modelValue`,`rules`]),o(S,{class:`my-4`}),a[30]||=g(`div`,{class:`text-subtitle-2 mb-3`},`告警阈值`,-1),o(r,{dense:``},{default:m(()=>[o(n,{cols:`4`},{default:m(()=>[o(j,{modelValue:U.value.cpu_alert_threshold,"onUpdate:modelValue":a[2]||=e=>U.value.cpu_alert_threshold=e,label:`CPU %`,variant:`outlined`,density:`compact`,type:`number`,rules:[_(I)(`阈值`),_(F)(1,100,`阈值`)]},null,8,[`modelValue`,`rules`])]),_:1}),o(n,{cols:`4`},{default:m(()=>[o(j,{modelValue:U.value.mem_alert_threshold,"onUpdate:modelValue":a[3]||=e=>U.value.mem_alert_threshold=e,label:`内存 %`,variant:`outlined`,density:`compact`,type:`number`,rules:[_(I)(`阈值`),_(F)(1,100,`阈值`)]},null,8,[`modelValue`,`rules`])]),_:1}),o(n,{cols:`4`},{default:m(()=>[o(j,{modelValue:U.value.disk_alert_threshold,"onUpdate:modelValue":a[4]||=e=>U.value.disk_alert_threshold=e,label:`磁盘 %`,variant:`outlined`,density:`compact`,type:`number`,rules:[_(I)(`阈值`),_(F)(1,100,`阈值`)]},null,8,[`modelValue`,`rules`])]),_:1})]),_:1}),o(S,{class:`my-4`}),a[31]||=g(`div`,{class:`text-subtitle-2 mb-3`},`连接池`,-1),o(r,{dense:``},{default:m(()=>[o(n,{cols:`6`},{default:m(()=>[o(j,{modelValue:U.value.db_pool_size,"onUpdate:modelValue":a[5]||=e=>U.value.db_pool_size=e,label:`连接池大小`,variant:`outlined`,density:`compact`,type:`number`,rules:[_(I)(`连接池大小`),_(F)(1,1e3,`连接池大小`)]},null,8,[`modelValue`,`rules`])]),_:1}),o(n,{cols:`6`},{default:m(()=>[o(j,{modelValue:U.value.db_max_overflow,"onUpdate:modelValue":a[6]||=e=>U.value.db_max_overflow=e,label:`溢出连接数`,variant:`outlined`,density:`compact`,type:`number`,rules:[_(I)(`溢出连接数`),_(F)(1,1e3,`溢出连接数`)]},null,8,[`modelValue`,`rules`])]),_:1})]),_:1})]),_:1}),o(O,{class:`px-4 pb-4`},{default:m(()=>[o(h),o(s,{type:`submit`,color:`primary`,variant:`flat`,loading:Ue.value},{default:m(()=>[...a[32]||=[d(`保存设置`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1},512)]),_:1}),o(_e),o(k,{elevation:`0`,border:``,rounded:`lg`,class:`mb-4`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[33]||=[d(`mdi-bell-ring-outline`,-1)]]),_:1}),a[34]||=d(` 界面提示 `,-1)]),_:1}),o(S),o(M,null,{default:m(()=>[a[37]||=g(`p`,{class:`text-caption text-medium-emphasis mb-3`},` 脚本批量执行全部结束时,在浏览器播放提示音(仅当前登录的管理员会话,与 Telegram 推送无关)。 `,-1),o(N,{modelValue:z.value,"onUpdate:modelValue":[a[7]||=e=>z.value=e,Ot],items:_(ge),"item-title":`title`,"item-value":`value`,label:`脚本执行完成提示音`,variant:`outlined`,density:`compact`,"hide-details":``,class:`mb-3`,loading:B.value,disabled:B.value},null,8,[`modelValue`,`items`,`loading`,`disabled`]),o(s,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-volume-high`,disabled:z.value===`off`||B.value,onClick:kt},{default:m(()=>[...a[35]||=[d(` 试听 `,-1)]]),_:1},8,[`disabled`]),o(S,{class:`my-4`}),a[38]||=g(`p`,{class:`text-caption text-medium-emphasis mb-3`},` 文件推送批次结束且至少一台成功时,在浏览器播放提示音(与脚本提示音独立配置)。 `,-1),o(N,{modelValue:V.value,"onUpdate:modelValue":[a[8]||=e=>V.value=e,At],items:_(ge),"item-title":`title`,"item-value":`value`,label:`文件推送完成提示音`,variant:`outlined`,density:`compact`,"hide-details":``,class:`mb-3`,loading:H.value,disabled:H.value},null,8,[`modelValue`,`items`,`loading`,`disabled`]),o(s,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-volume-high`,disabled:V.value===`off`||H.value,onClick:jt},{default:m(()=>[...a[36]||=[d(` 试听 `,-1)]]),_:1},8,[`disabled`])]),_:1})]),_:1}),o(k,{elevation:`0`,border:``,rounded:`lg`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[39]||=[d(`mdi-send`,-1)]]),_:1}),a[40]||=d(` Telegram 推送 `,-1)]),_:1}),o(S),o(M,null,{default:m(()=>[o(j,{modelValue:W.value,"onUpdate:modelValue":a[9]||=e=>W.value=e,label:`Bot Token`,variant:`outlined`,density:`compact`,class:`mb-3`,placeholder:G.value?`已配置(留空保持不变)`:`输入 Bot Token`,type:q.value?`text`:`password`,"append-inner-icon":q.value?`mdi-eye-off`:`mdi-eye`,"onClick:appendInner":Ut},null,8,[`modelValue`,`placeholder`,`type`,`append-inner-icon`]),G.value&&!W.value?(p(),w(T,{key:0,size:`small`,color:`success`,variant:`tonal`,class:`mb-3`},{default:m(()=>[...a[41]||=[d(`Token 已配置`,-1)]]),_:1})):u(``,!0),o(j,{modelValue:U.value.telegram_chat_id,"onUpdate:modelValue":a[10]||=e=>U.value.telegram_chat_id=e,label:`Chat ID`,variant:`outlined`,density:`compact`,class:`mb-3`},null,8,[`modelValue`]),a[49]||=g(`p`,{class:`text-caption text-medium-emphasis mb-3`},` 在 Telegram 向 Bot 发送任意消息后,可点击下方按钮自动检测 Chat ID。 `,-1),g(`div`,ye,[o(s,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-radar`,onClick:Pt,loading:Ke.value},{default:m(()=>[...a[42]||=[d(`检测 Chat ID`,-1)]]),_:1},8,[`loading`]),o(s,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-message-text`,onClick:Nt,loading:Ge.value},{default:m(()=>[...a[43]||=[d(`发送测试消息`,-1)]]),_:1},8,[`loading`]),o(s,{size:`small`,variant:`tonal`,color:`primary`,"prepend-icon":`mdi-content-save`,onClick:Dt,loading:Je.value},{default:m(()=>[...a[44]||=[d(`保存 Telegram 配置`,-1)]]),_:1},8,[`loading`])]),o(S,{class:`my-3`}),a[50]||=g(`div`,{class:`text-subtitle-2 mb-1`},`告警通知项目`,-1),a[51]||=g(`p`,{class:`text-caption text-medium-emphasis mb-3`},` 关闭后该类告警仍记录日志,但不发送 Telegram 消息。Bot Token 未配置时全部静默。 `,-1),(p(!0),i(y,null,f($e.value,(e,t)=>(p(),i(y,{key:t},[g(`div`,be,b(t),1),(p(!0),i(y,null,f(e,e=>(p(),w(fe,{key:e.key,"model-value":Ye.value[e.key]??!0,label:e.label,hint:e.desc,"persistent-hint":``,color:`primary`,density:`compact`,"hide-details":`auto`,class:`mb-1`,disabled:Xe.value===e.key,"onUpdate:modelValue":t=>Mt(e.key,!!t)},null,8,[`model-value`,`label`,`hint`,`disabled`,`onUpdate:modelValue`]))),128))],64))),128)),o(S,{class:`my-4`}),a[52]||=g(`div`,{class:`text-subtitle-2 mb-1`},`Layer 3 宿主机巡检`,-1),a[53]||=g(`p`,{class:`text-caption text-medium-emphasis mb-3`},[d(` 每分钟由宿主机 cron 执行 `),g(`code`,null,`health_monitor.sh`),d(`:连续 3 次 /health 失败则自动重启并推送 Telegram。 配置保存后会自动同步至 .env;亦可手动同步。未同步时脚本会从数据库读取 Telegram 配置。 `)],-1),g(`div`,xe,[o(T,{size:`small`,color:J.value?.telegram_layer3_ready?`success`:`warning`,variant:`tonal`},{default:m(()=>[d(b(J.value?.telegram_layer3_ready?`巡检 Telegram 就绪`:`请先配置 Telegram`),1)]),_:1},8,[`color`]),J.value?(p(),w(T,{key:0,size:`small`,color:J.value.telegram_env_synced?`success`:`grey`,variant:`tonal`},{default:m(()=>[d(b(J.value.telegram_env_synced?`.env 已同步`:`.env 未同步(可用 DB 回退)`),1)]),_:1},8,[`color`])):u(``,!0)]),g(`div`,Se,[o(s,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-sync`,onClick:Et,loading:Qe.value,disabled:!J.value?.telegram_app_configured},{default:m(()=>[...a[45]||=[d(` 同步巡检 Telegram `,-1)]]),_:1},8,[`loading`,`disabled`]),o(s,{size:`small`,variant:`text`,"prepend-icon":`mdi-refresh`,onClick:Tt,loading:Ze.value},{default:m(()=>[...a[46]||=[d(` 刷新状态 `,-1)]]),_:1},8,[`loading`])]),J.value?(p(),i(`p`,Ce,[a[47]||=d(` 宿主机安装 cron`,-1),g(`code`,null,b(J.value.cron_install_command),1),a[48]||=d(` · 日志:`,-1),g(`code`,null,b(J.value.health_log_path),1)])):u(``,!0)]),_:1})]),_:1})]),_:1}),o(n,{cols:`12`,lg:`6`},{default:m(()=>[o(k,{elevation:`0`,border:``,rounded:`lg`,class:`mb-4`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[54]||=[d(`mdi-lock`,-1)]]),_:1}),a[55]||=d(` 修改密码 `,-1)]),_:1}),o(S),o(M,null,{default:m(()=>[o(j,{modelValue:Y.value.current,"onUpdate:modelValue":a[11]||=e=>Y.value.current=e,label:`当前密码`,variant:`outlined`,density:`compact`,type:`password`,class:`mb-3`,rules:[_(I)(`密码`)]},null,8,[`modelValue`,`rules`]),o(j,{modelValue:Y.value.new_pw,"onUpdate:modelValue":a[12]||=e=>Y.value.new_pw=e,label:`新密码`,variant:`outlined`,density:`compact`,type:`password`,class:`mb-3`,rules:[_(I)(`密码`)]},null,8,[`modelValue`,`rules`]),o(j,{modelValue:Y.value.confirm,"onUpdate:modelValue":a[13]||=e=>Y.value.confirm=e,label:`确认新密码`,variant:`outlined`,density:`compact`,type:`password`,class:`mb-3`,rules:[_(I)(`确认密码`),_(L)(()=>Y.value.new_pw)]},null,8,[`modelValue`,`rules`]),_(R).admin?.totp_enabled?(p(),w(j,{key:0,modelValue:Y.value.totp_code,"onUpdate:modelValue":a[14]||=e=>Y.value.totp_code=e,label:`TOTP 验证码`,variant:`outlined`,density:`compact`,type:`text`,inputmode:`numeric`,maxlength:`6`,class:`mb-3`,rules:_(R).admin?.totp_enabled?[_(I)(`验证码`)]:[]},null,8,[`modelValue`,`rules`])):u(``,!0)]),_:1}),o(O,{class:`px-4 pb-4`},{default:m(()=>[o(h),o(s,{color:`primary`,variant:`flat`,onClick:It,loading:tt.value},{default:m(()=>[...a[56]||=[d(`修改密码`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1}),o(k,{elevation:`0`,border:``,rounded:`lg`,class:`mb-4`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[57]||=[d(`mdi-shield-key`,-1)]]),_:1}),a[58]||=d(` 双因素认证 (TOTP) `,-1)]),_:1}),o(S),o(M,null,{default:m(()=>[g(`div`,we,[a[59]||=g(`span`,{class:`text-body-2`},`状态:`,-1),o(T,{color:_(R).admin?.totp_enabled?`success`:`grey`,size:`small`,variant:`tonal`,label:``,class:`ml-2`},{default:m(()=>[d(b(_(R).admin?.totp_enabled?`已启用`:`未启用`),1)]),_:1},8,[`color`])]),nt.value?(p(),i(`div`,Te,[g(`img`,{src:nt.value,alt:`TOTP QR`,style:{"max-width":`200px`}},null,8,Ee),rt.value?(p(),i(`div`,De,[g(`code`,Oe,b(rt.value),1),o(s,{size:`x-small`,variant:`text`,icon:`mdi-content-copy`,onClick:Vt})])):u(``,!0),a[60]||=g(`div`,{class:`text-caption text-medium-emphasis mt-1`},`用认证器 App 扫描二维码或手动输入密钥`,-1)])):u(``,!0),_(R).admin?.totp_enabled?(p(),w(s,{key:2,size:`small`,variant:`tonal`,color:`error`,"prepend-icon":`mdi-shield-off`,onClick:zt},{default:m(()=>[...a[63]||=[d(`禁用 TOTP`,-1)]]),_:1})):(p(),i(`div`,ke,[nt.value?(p(),i(`div`,Ae,[o(j,{modelValue:it.value,"onUpdate:modelValue":a[15]||=e=>it.value=e,label:`验证码`,variant:`outlined`,density:`compact`,style:{"max-width":`160px`}},null,8,[`modelValue`]),o(s,{color:`primary`,variant:`flat`,size:`small`,onClick:Rt,loading:at.value},{default:m(()=>[...a[62]||=[d(`启用`,-1)]]),_:1},8,[`loading`])])):(p(),w(s,{key:0,size:`small`,variant:`tonal`,"prepend-icon":`mdi-qrcode`,onClick:Lt},{default:m(()=>[...a[61]||=[d(`生成二维码`,-1)]]),_:1}))]))]),_:1})]),_:1}),o(k,{elevation:`0`,border:``,rounded:`lg`,class:`mb-4`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[64]||=[d(`mdi-key`,-1)]]),_:1}),a[65]||=d(` API Key `,-1)]),_:1}),o(S),o(M,null,{default:m(()=>[g(`div`,je,[o(j,{"model-value":X.value?ut.value:`••••••••`,label:`API Key`,variant:`outlined`,density:`compact`,readonly:``,type:X.value?`text`:`password`,style:{"max-width":`320px`}},null,8,[`model-value`,`type`]),o(s,{variant:`text`,size:`small`,icon:X.value?`mdi-eye-off`:`mdi-eye`,onClick:Ht,loading:dt.value},null,8,[`icon`,`loading`]),o(s,{variant:`text`,size:`small`,icon:`mdi-content-copy`,onClick:Wt})])]),_:1})]),_:1}),o(k,{elevation:`0`,border:``,rounded:`lg`},{default:m(()=>[o(A,{class:`d-flex align-center`},{default:m(()=>[o(c,{class:`mr-2`},{default:m(()=>[...a[66]||=[d(`mdi-ip-network`,-1)]]),_:1}),a[67]||=d(` 登录 IP 白名单 `,-1)]),_:1}),o(S),o(M,null,{default:m(()=>[o(fe,{modelValue:pt.value,"onUpdate:modelValue":[a[16]||=e=>pt.value=e,Gt],label:`启用 IP 白名单(仅白名单 IP 可登录)`,color:`primary`,density:`compact`,"hide-details":``,class:`mb-4`},null,8,[`modelValue`]),a[70]||=g(`div`,{class:`text-subtitle-2 mb-2`},`代理订阅`,-1),a[71]||=g(`p`,{class:`text-caption text-medium-emphasis mb-2`},` 填写 SS/VMess/VLESS 等订阅链接,保存后每 2 小时自动刷新节点 IP 并合并进白名单。 `,-1),o(j,{modelValue:Z.value,"onUpdate:modelValue":a[17]||=e=>Z.value=e,label:`订阅 URL`,variant:`outlined`,density:`compact`,class:`mb-2`,placeholder:`https://example.com/sub?token=...`},null,8,[`modelValue`]),g(`div`,Me,[o(s,{size:`small`,variant:`tonal`,"prepend-icon":`mdi-cloud-download`,onClick:Kt,loading:vt.value},{default:m(()=>[...a[68]||=[d(`解析预览`,-1)]]),_:1},8,[`loading`]),o(s,{size:`small`,variant:`tonal`,color:`primary`,"prepend-icon":`mdi-content-save`,onClick:qt,loading:_t.value},{default:m(()=>[...a[69]||=[d(`保存白名单`,-1)]]),_:1},8,[`loading`])]),Q.value.length?(p(),i(`div`,Ne,[g(`div`,Pe,[d(` 订阅节点 `+b(Q.value.length)+` 个 `,1),ht.value?(p(),i(`span`,Fe,` · 上次刷新 `+b(ht.value),1)):u(``,!0)]),g(`div`,Ie,[(p(!0),i(y,null,f(bt.value,e=>(p(),w(T,{key:e,size:`x-small`,variant:`tonal`,label:``},{default:m(()=>[d(b(e),1)]),_:2},1024))),128)),Q.value.length>bt.value.length?(p(),w(T,{key:0,size:`x-small`,variant:`text`,label:``},{default:m(()=>[d(` +`+b(Q.value.length-bt.value.length)+` 更多 `,1)]),_:1})):u(``,!0)])])):Z.value?(p(),i(`div`,Le,b(ht.value?`上次刷新 ${ht.value},暂无节点`:`等待首次刷新…保存订阅 URL 后将自动拉取`),1)):u(``,!0),o(S,{class:`my-3`}),a[72]||=g(`div`,{class:`text-subtitle-2 mb-2`},`手动 IP`,-1),o(pe,{modelValue:mt.value,"onUpdate:modelValue":a[18]||=e=>mt.value=e,label:`手动 IP / CIDR(每行一个)`,variant:`outlined`,density:`compact`,rows:`4`,class:`mb-2`,placeholder:`1.2.3.4
10.0.0.0/24`},null,8,[`modelValue`]),g(`div`,Re,` 手动 IP 与订阅节点合并生效;共 `+b(gt.value)+` 条(去重后)。 `,1)]),_:1})]),_:1})]),_:1})]),_:1}),o(P,{modelValue:$.value,"onUpdate:modelValue":a[21]||=e=>$.value=e,"max-width":`520`},{default:m(()=>[o(k,{border:``},{default:m(()=>[o(A,null,{default:m(()=>[...a[73]||=[d(`订阅解析预览`,-1)]]),_:1}),o(M,null,{default:m(()=>[g(`div`,ze,[a[74]||=d(`共解析出 `,-1),g(`strong`,null,b(yt.value.length),1),a[75]||=d(` 个节点 IP / 域名:`,-1)]),g(`div`,Be,[(p(!0),i(y,null,f(yt.value,e=>(p(),w(T,{key:e,size:`small`,variant:`tonal`,label:``},{default:m(()=>[d(b(e),1)]),_:2},1024))),128))]),a[76]||=g(`div`,{class:`text-caption text-medium-emphasis`},`点击「保存白名单」后才会写入订阅 URL 并触发后台刷新。`,-1)]),_:1}),o(O,null,{default:m(()=>[o(h),o(s,{variant:`text`,onClick:a[19]||=e=>$.value=!1},{default:m(()=>[...a[77]||=[d(`关闭`,-1)]]),_:1}),o(s,{color:`primary`,variant:`flat`,onClick:a[20]||=e=>{$.value=!1,qt()}},{default:m(()=>[...a[78]||=[d(`保存白名单`,-1)]]),_:1})]),_:1})]),_:1})]),_:1},8,[`modelValue`]),o(P,{modelValue:K.value,"onUpdate:modelValue":a[23]||=e=>K.value=e,"max-width":`480`},{default:m(()=>[o(k,{border:``},{default:m(()=>[o(A,null,{default:m(()=>[...a[79]||=[d(`选择 Chat ID`,-1)]]),_:1}),o(M,null,{default:m(()=>[qe.value.length?(p(),w(le,{key:1,density:`compact`,lines:`two`},{default:m(()=>[(p(!0),i(y,null,f(qe.value,e=>(p(),w(ue,{key:e.id,title:e.title||e.username||`Chat ${e.id}`,subtitle:`${e.type}${e.username?` · @`+e.username:``} · ID ${e.id}`,onClick:t=>Ft(e)},null,8,[`title`,`subtitle`,`onClick`]))),128))]),_:1})):(p(),i(`div`,Ve,` 未检测到对话。请先在 Telegram 向 Bot 发送一条消息,然后重新检测。 `))]),_:1}),o(O,null,{default:m(()=>[o(h),o(s,{variant:`text`,onClick:a[22]||=e=>K.value=!1},{default:m(()=>[...a[80]||=[d(`关闭`,-1)]]),_:1})]),_:1})]),_:1})]),_:1},8,[`modelValue`]),o(P,{modelValue:ot.value,"onUpdate:modelValue":a[27]||=e=>ot.value=e,"max-width":`400`},{default:m(()=>[o(k,{border:``},{default:m(()=>[o(A,null,{default:m(()=>[...a[81]||=[d(`禁用 TOTP`,-1)]]),_:1}),o(M,null,{default:m(()=>[o(j,{modelValue:st.value,"onUpdate:modelValue":a[24]||=e=>st.value=e,label:`当前密码`,variant:`outlined`,density:`compact`,type:`password`,class:`mb-2`,rules:[_(I)()]},null,8,[`modelValue`,`rules`]),o(j,{modelValue:ct.value,"onUpdate:modelValue":a[25]||=e=>ct.value=e,label:`验证码`,variant:`outlined`,density:`compact`,rules:[_(I)()]},null,8,[`modelValue`,`rules`])]),_:1}),o(O,null,{default:m(()=>[o(h),o(s,{variant:`text`,onClick:a[26]||=e=>ot.value=!1},{default:m(()=>[...a[82]||=[d(`取消`,-1)]]),_:1}),o(s,{color:`error`,variant:`flat`,onClick:Bt,loading:lt.value},{default:m(()=>[...a[83]||=[d(`禁用`,-1)]]),_:1},8,[`loading`])]),_:1})]),_:1})]),_:1},8,[`modelValue`])]),_:1}))}});export{B as default};