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