Files
Nexus/web/app/assets/SettingsPage-DCc8rJM6.js
T
Nexus Agent dfb0ea2d8f fix(frontend): 脚本库与推送调度对齐设计文档
修复 ScriptsPage 执行契约(script_id/command、历史过滤、长任务/凭据)及 SchedulesPage 缺失能力(target_path、next_run、push/script、cron/once、sync_mode);后端补迁移与 schedule_runner 传参。
2026-06-08 02:39:18 +08:00

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