dabfc128b3
- 3389远程页 + guacd 侧车 WebSocket 桥(无 RDP 会话审计) - 文件推送完成独立提示音,至少一台成功即播放 - 子机列表展开 CPU/内存/磁盘 sparkline 与指标采样落库 - 终端全量服务器列表、连接中状态;告警 Telegram 公网 IP - SSH 密钥凭据 UI 简化;子机搜索未设路径筛选 Co-authored-by: Cursor <cursoragent@cursor.com>
4 lines
34 KiB
JavaScript
4 lines
34 KiB
JavaScript
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}; |