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