aws-runtime-bridge 1.9.122 → 1.9.124
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/browser-ws-bridge.js +3 -3
- package/dist/index.js +1 -1
- package/dist/remote-desktop/index.js +2 -2
- package/dist/routes/dashboard.js +521 -17
- package/dist/routes/pty.js +3 -3
- package/dist/routes/runtime-binding.js +1 -1
- package/dist/services/access-control.d.ts +18 -0
- package/dist/services/access-control.js +5 -0
- package/dist/services/agent-process-manager.js +1 -1
- package/dist/services/auto-register.js +1 -1
- package/dist/services/panel-auth.d.ts +1 -1
- package/dist/services/panel-auth.js +1 -1
- package/dist/services/system-metrics.js +1 -1
- package/dist/services/tunnel-client.d.ts +4 -0
- package/dist/services/tunnel-client.js +1 -1
- package/dist/utils/config-write-queue.d.ts +3 -0
- package/dist/utils/config-write-queue.js +2 -0
- package/package.json +1 -1
package/dist/routes/dashboard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{Router as F}from"express";import p from"node:os";import{execFile as _}from"node:child_process";import{getRuntimeBindingPublicState as T,getRuntimePairingCode as V}from"../services/runtime-binding.js";import{getAgentProcessManager as y}from"../services/agent-process-manager.js";import{collectSystemMetrics as K}from"../services/system-metrics.js";import{loadPersistedSessions as W}from"../services/terminal-persistence.js";import{createLogger as G}from"../utils/logger.js";import{getBridgeVersion as q}from"../config.js";import{sdkSessions as J}from"./terminal.js";import{createSession as Y,getPanelCredentials as P,validateSession as E,destroySession as L,validateCredentials as U,hasConfiguredCredentials as x,isDefaultCredentials as $,setPanelCredentials as Q,getAllSessions as X,getActiveSessionCount as Z,checkLoginAllowed as ee,recordLoginFailure as te,recordLoginSuccess as ne}from"../services/panel-auth.js";import{getRecentLogs as se}from"../utils/logger.js";import{autoRegister as ae,getRegistrationState as D,getAutoRegisterConfigFilePath as oe}from"../services/auto-register.js";const w=G("dashboard");let k=null;const re=1e4;let R=null,S=null;const ie=3e4;async function N(){K().then(n=>{R={data:n,ts:Date.now()}}).catch(()=>{}),y().discoverAllProcesses().then(n=>{S={data:n,ts:Date.now()}}).catch(()=>{}),y().cleanupStaleRegistry().catch(()=>{})}N(),setInterval(N,ie);const M=q();async function de(){const n=await import("node:fs/promises"),e=await import("node:path"),t=await import("node:os"),s=e.join(String(process.env.AWS_RUNTIME_HOME_DIR||t.homedir()).trim()||t.homedir(),".aws-bridge","lifecycle-state.json");try{const i=await n.readFile(s,"utf8"),a=JSON.parse(i),o=Array.isArray(a.operations)?a.operations:[];return o.length===0?null:(o.sort((l,c)=>new Date(c.updatedAt).getTime()-new Date(l.updatedAt).getTime()),o[0]||null)}catch{return null}}const d=F(),z="panel_sid";function le(n){const e={};if(!n)return e;for(const t of n.split(";")){const s=t.indexOf("=");if(s===-1)continue;const i=t.slice(0,s).trim(),a=t.slice(s+1).trim();i&&(e[i]=a)}return e}function I(n){return le(n.headers.cookie)[z]}function H(n,e,t){const s=n.secure||n.headers["x-forwarded-proto"]==="https";return`${z}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${t}`}function g(n,e){const t=I(n);return t&&E(t)?!0:n.xhr||n.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}d.post("/runtime/login",(n,e)=>{const t=n.socket.remoteAddress||"unknown",s=ee(t);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:i,password:a}=n.body||{};if(!i||!a){e.status(400).json({ok:!1,error:"username and password required"});return}if(!U(String(i),String(a))){te(t),e.status(401).json({ok:!1,error:"invalid credentials"});return}ne(t);const o=Y(),l=!x();e.setHeader("Set-Cookie",H(n,o,86400)),e.json({ok:!0,needSetup:l})}),d.post("/runtime/logout",(n,e)=>{const t=I(n);t&&L(t),e.setHeader("Set-Cookie",H(n,"",0)),e.json({ok:!0})}),d.get("/runtime/credentials-status",(n,e)=>{if(!g(n,e))return;const t=P();e.json({ok:!0,isDefault:$(),configured:x(),username:t.username})}),d.post("/runtime/change-credentials",(n,e)=>{if(!g(n,e))return;const{username:t,password:s,passwordConfirm:i,oldPassword:a}=n.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(x()){if(!a){e.status(400).json({ok:!1,error:"old password required"});return}const l=P();if(!U(l.username,String(a))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!t||!String(t).trim()){e.status(400).json({ok:!1,error:"username required"});return}const o=s&&String(s).trim()?String(s).trim():P().password;Q(String(t).trim(),o),e.json({ok:!0})}),d.get("/runtime/node-info",(n,e)=>{if(!g(n,e))return;const t=T(),s=D();e.json({ok:!0,hostname:p.hostname(),platform:p.platform(),arch:p.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:M,configPath:oe(),homeDir:process.env.AWS_RUNTIME_HOME_DIR||p.homedir(),bindingStatus:t.status,paired:t.paired,instanceId:t.instanceId||s.instanceId,schedulerBaseUrl:t.schedulerBaseUrl,registered:s.registered,pairingCode:t.pairingCode,activeSessions:Z()})}),d.get("/runtime/logs",(n,e)=>{if(!g(n,e))return;const t=n.query.level||void 0,s=Math.min(Number(n.query.count)||200,500),a=t&&["debug","info","warn","error"].includes(t)?t:void 0,o=se(s,a);e.json({ok:!0,logs:o})}),d.post("/runtime/re-register",async(n,e)=>{if(g(n,e))try{const t=await ae();e.json({ok:t})}catch(t){const s=t;e.status(500).json({ok:!1,error:s.message})}}),d.get("/runtime/admin/sessions",(n,e)=>{if(!g(n,e))return;const t=X().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:t})}),d.post("/runtime/admin/sessions/:token/destroy",(n,e)=>{g(n,e)&&(L(n.params.token),e.json({ok:!0}))});function ce(n){return new Promise((e,t)=>{const s=p.platform()==="win32",i=s?"taskkill":"kill",a=s?["/PID",String(n),"/F"]:["-9",String(n)];_(i,a,{timeout:5e3},o=>o?t(o):e())})}d.post("/runtime/kill-process",async(n,e)=>{if(!g(n,e))return;const t=Number(n.body.pid);if(!t||!Number.isInteger(t)||t<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=y(),i=()=>{const o=new Set,l=S?.data??[];for(const c of l)typeof c.pid=="number"&&o.add(c.pid);for(const c of s.getAllManagedProcesses())typeof c.pid=="number"&&o.add(c.pid);return o};let a=i();if(a.size===0)try{S={data:await s.discoverAllProcesses(),ts:Date.now()},a=i()}catch{}if(!a.has(t)){w.warn("Rejected kill-process for unrecognized PID",{pid:t}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await ce(t),w.info("Killed process",{pid:t}),e.json({ok:!0})}catch(o){const l=o;w.warn("Failed to kill process",{pid:t,error:l.message}),e.status(500).json({ok:!1,error:l.message})}}),d.get("/runtime/dashboard",async(n,e)=>{if(g(n,e)){if(k&&Date.now()-k.ts<re)return void e.json(k.data);try{const t=R?.data??null,s=S?.data??null;R||N();const[i,a,o]=await Promise.all([Promise.resolve(T()),Promise.resolve(D()),W().catch(()=>[])]),c=y().getAllManagedProcesses(),v=Array.from(J.entries()).map(([m,u])=>({sessionId:m,agentId:u.agentId,workspacePath:u.workspacePath,runtimeStatus:u.currentRuntimeStatus||"running",runtimeActionType:u.currentRuntimeActionType,runtimeActionLabel:u.currentRuntimeActionLabel,providerId:u.providerId}));let f={managed:0,orphan:0,unknown:0},C=[];if(s){f={managed:s.filter(r=>r.managementStatus==="managed").length,orphan:s.filter(r=>r.managementStatus==="orphan").length,unknown:s.filter(r=>r.managementStatus==="unknown").length};const m=new Map(c.map(r=>[r.pid,r]));C=s.map(r=>{const h=m.get(r.pid);return{agentId:r.agentId||h?.agentId,pid:r.pid,managementStatus:r.managementStatus,sessionId:h?.sessionId,state:h?.state,command:r.command,cwd:r.cwd,startedAt:r.startedAt}});const u={managed:0,orphan:1,unknown:2};C.sort((r,h)=>(u[r.managementStatus]??9)-(u[h.managementStatus]??9))}const j=new Set(v.map(m=>m.sessionId)),B=o.filter(m=>!j.has(m.sessionId)),b=i,O=await de(),A={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:M,hostname:p.hostname(),platform:p.platform(),arch:p.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:b.status,paired:b.paired,instanceId:b.instanceId,userId:b.userId,schedulerBaseUrl:b.schedulerBaseUrl,pairingCode:V()},registration:{registered:a.registered,instanceId:a.instanceId,lastAttempt:a.lastAttempt?.toISOString(),error:a.error},metrics:t,sessions:{active:v,activeCount:v.length,persisted:B,persistedCount:B.length,total:v.length+B.length},processes:{...f,total:f.managed+f.orphan+f.unknown,list:C,registryCount:c.length},lifecycleOperation:O};k={data:A,ts:Date.now()},e.json(A)}catch(t){const s=t;w.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),d.get("/login",(n,e)=>{e.type("text/html; charset=utf-8"),e.send(ge)}),d.get("/",(n,e)=>{const t=I(n);if(!t||!E(t)){e.redirect("/login");return}if(!x()){e.redirect("/setup");return}const s=n.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(ue.replace("{{LANG}}",s))}),d.get("/setup",(n,e)=>{const t=I(n);if(!t||!E(t)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(pe)});const ue=`<!DOCTYPE html>
|
|
1
|
+
import{Router as $}from"express";import v from"node:fs";import K from"node:path";import w from"node:os";import{execFile as Y}from"node:child_process";import{getRuntimeBindingPublicState as H,getRuntimePairingCode as Q,normalizeSchedulerBaseUrl as X}from"../services/runtime-binding.js";import{getAgentProcessManager as C}from"../services/agent-process-manager.js";import{collectSystemMetrics as Z}from"../services/system-metrics.js";import{loadPersistedSessions as ee}from"../services/terminal-persistence.js";import{createLogger as te}from"../utils/logger.js";import{getBridgeVersion as ne}from"../config.js";import{runExclusiveConfigWrite as O}from"../utils/config-write-queue.js";import{sdkSessions as se}from"./terminal.js";import{createSession as re,getPanelCredentials as N,validateSession as U,destroySession as z,validateCredentials as F,hasConfiguredCredentials as B,isDefaultCredentials as ae,setPanelCredentials as oe,getAllSessions as de,getActiveSessionCount as ie,checkLoginAllowed as le,recordLoginFailure as ce,recordLoginSuccess as ue}from"../services/panel-auth.js";import{getRecentLogs as ge}from"../utils/logger.js";import{autoRegister as pe,getRegistrationState as _,getAutoRegisterConfigFilePath as E}from"../services/auto-register.js";import{getAccessControlEntries as fe,isValidIpEntry as me,parseAllowIps as he,refreshAccessControlCache as be}from"../services/access-control.js";const x=te("dashboard");let T=null;const ve=1e4;let L=null,P=null;const ye=3e4;async function D(){Z().then(t=>{L={data:t,ts:Date.now()}}).catch(()=>{}),C().discoverAllProcesses().then(t=>{P={data:t,ts:Date.now()}}).catch(()=>{}),C().cleanupStaleRegistry().catch(()=>{})}D(),setInterval(D,ye);const V=ne();async function xe(){const t=await import("node:fs/promises"),e=await import("node:path"),n=await import("node:os"),s=e.join(String(process.env.AWS_RUNTIME_HOME_DIR||n.homedir()).trim()||n.homedir(),".aws-bridge","lifecycle-state.json");try{const d=await t.readFile(s,"utf8"),r=JSON.parse(d),a=Array.isArray(r.operations)?r.operations:[];return a.length===0?null:(a.sort((o,l)=>new Date(l.updatedAt).getTime()-new Date(o.updatedAt).getTime()),a[0]||null)}catch{return null}}const g=$(),J="panel_sid";function we(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const d=n.slice(0,s).trim(),r=n.slice(s+1).trim();d&&(e[d]=r)}return e}function A(t){return we(t.headers.cookie)[J]}function W(t,e,n){const s=t.secure||t.headers["x-forwarded-proto"]==="https";return`${J}=${e}; HttpOnly; SameSite=Lax; Path=/${s?"; Secure":""}; Max-Age=${n}`}function m(t,e){const n=A(t);return n&&U(n)?!0:t.xhr||t.headers.accept?.includes("application/json")?(e.status(401).json({ok:!1,error:"unauthorized",needLogin:!0}),!1):(e.redirect("/login"),!1)}g.post("/runtime/login",(t,e)=>{const n=t.socket.remoteAddress||"unknown",s=le(n);if(!s.allowed){e.status(429).set("Retry-After",String(s.retryAfterSec)).json({ok:!1,error:"too many failed login attempts, try again later",retryAfterSec:s.retryAfterSec});return}const{username:d,password:r}=t.body||{};if(!d||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!F(String(d),String(r))){ce(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}ue(n);const a=re(),o=!B();e.setHeader("Set-Cookie",W(t,a,86400)),e.json({ok:!0,needSetup:o})}),g.post("/runtime/logout",(t,e)=>{const n=A(t);n&&z(n),e.setHeader("Set-Cookie",W(t,"",0)),e.json({ok:!0})}),g.get("/runtime/credentials-status",(t,e)=>{if(!m(t,e))return;const n=N();e.json({ok:!0,isDefault:ae(),configured:B(),username:n.username})}),g.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:d,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(d)){e.status(400).json({ok:!1,error:"passwords do not match"});return}if(B()){if(!r){e.status(400).json({ok:!1,error:"old password required"});return}const o=N();if(!F(o.username,String(r))){e.status(403).json({ok:!1,error:"old password is incorrect"});return}}}if(!n||!String(n).trim()){e.status(400).json({ok:!1,error:"username required"});return}const a=s&&String(s).trim()?String(s).trim():N().password;try{await oe(String(n).trim(),a),e.json({ok:!0})}catch(o){const l=o;x.error("Failed to change panel credentials:",l.message),e.status(500).json({ok:!1,error:l.message})}});function q(t){const e=typeof t=="string"?t.trim():"";if(e)return e.length<8?"\u2022\u2022\u2022\u2022":"\u2022\u2022\u2022\u2022"+e.slice(-4)}function G(t){const e=Array.isArray(t.autoRegisterTargets)?t.autoRegisterTargets.filter(s=>!!(s&&typeof s=="object"&&!Array.isArray(s))):[];return e.length>0?e:["serverUrl","schedulerBaseUrl","userKey"].some(s=>!!String(t[s]??"").trim())?[t]:[]}const Se=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ie(t){const e={};for(const n of Se)t[n]!==void 0&&(e[n]=t[n]);return e}function ke(t,e){const n=String(t.connectionKey??"").trim()||String(e??"").trim();return{serverUrl:String(t.serverUrl??t.schedulerBaseUrl??"").trim(),tenantId:String(t.tenantId??"").trim(),instanceName:String(t.instanceName??"").trim(),instanceId:String(t.instanceId??"").trim()||void 0,registerAddr:String(t.registerAddr??"").trim(),tunnel:typeof t.tunnel=="boolean"?t.tunnel:void 0,userKeyMasked:q(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:q(n),connectionKeySet:!!n}}function R(t){return X(t)??String(t||"").trim()}g.get("/runtime/server-config",(t,e)=>{if(!m(t,e))return;const n=E();let s={};if(v.existsSync(n))try{s=JSON.parse(v.readFileSync(n,"utf-8"))}catch{e.status(500).json({ok:!1,error:"config.json is not valid JSON; fix it manually first"});return}const d=String(s.connectionKey??"").trim()||void 0,r=G(s).map(a=>ke(a,d));e.json({ok:!0,targets:r})});function M(t,e){const n=new Error(e);return n.status=t,n}g.post("/runtime/server-config",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).targets;if(!Array.isArray(n)){e.status(400).json({ok:!1,error:"targets array is required"});return}const s=[];for(let r=0;r<n.length;r++){const a=n[r];if(!a||typeof a!="object"||Array.isArray(a)){e.status(400).json({ok:!1,error:`targets[${r}] must be an object`});return}const o=a,l=String(o.serverUrl??o.schedulerBaseUrl??"").trim();if(!l){e.status(400).json({ok:!1,error:`targets[${r}].serverUrl is required`});return}try{const i=new URL(l);if(!["http:","https:"].includes(i.protocol))throw new Error("protocol must be http or https")}catch{e.status(400).json({ok:!1,error:`targets[${r}].serverUrl must be a valid http(s) URL`});return}const f=o.tunnel;let p;if(f==="auto"||f===""||f==null)p=void 0;else if(f===!0||f==="true")p=!0;else if(f===!1||f==="false")p=!1;else{e.status(400).json({ok:!1,error:`targets[${r}].tunnel must be auto, true or false`});return}const b=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:b&&b!==l?b:"",userKey:String(o.userKey??"").trim(),tenantId:String(o.tenantId??"").trim(),instanceName:String(o.instanceName??"").trim(),connectionKey:String(o.connectionKey??"").trim(),registerAddr:String(o.registerAddr??"").trim(),tunnel:p})}const d=new Set;for(const r of s){const a=R(r.serverUrl);if(d.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}d.add(a)}try{const r=E();await O(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=String(a.connectionKey??"").trim(),l=G(a),f=l.length>0&&l[0]===a,p=new Map(l.map(i=>[R(String(i.serverUrl??i.schedulerBaseUrl??"").trim()),f?Ie(i):i])),b=[];for(const i of s){let h;if(i.originalServerUrl){const S=R(i.originalServerUrl);h=p.get(S),h&&p.delete(S)}if(!h){const S=R(i.serverUrl);h=p.get(S),h&&p.delete(S)}const c=h?{...h}:{};if(c.serverUrl=i.serverUrl,c.tenantId=i.tenantId,c.instanceName=i.instanceName,c.registerAddr=i.registerAddr,i.tunnel===void 0?delete c.tunnel:c.tunnel=i.tunnel,i.userKey)c.userKey=i.userKey;else if(!String(c.userKey??"").trim())throw M(400,`userKey is required for ${i.serverUrl} (new target without inheritable userKey)`);i.connectionKey?c.connectionKey=i.connectionKey:!String(c.connectionKey??"").trim()&&o&&(c.connectionKey=o),b.push(c)}a.autoRegisterTargets=b;for(const i of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[i];v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8")}),x.info("Server connection config updated via panel",{configPath:r,targetCount:s.length}),e.json({ok:!0})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save server connection config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/access-control",(t,e)=>{m(t,e)&&e.json({ok:!0,allowIps:fe(),defaultAllowIps:["0.0.0.0"]})}),g.post("/runtime/access-control",async(t,e)=>{if(!m(t,e))return;const n=(t.body||{}).allowIps;if(typeof n!="string"&&!Array.isArray(n)){e.status(400).json({ok:!1,error:"allowIps must be a string or an array"});return}const s=he(n),d=s.filter(r=>!me(r));if(d.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${d.join(", ")}`});return}try{const r=E();await O(r,()=>{let a={};if(v.existsSync(r))try{a=JSON.parse(v.readFileSync(r,"utf-8"))}catch{throw M(500,"config.json is not valid JSON; fix it manually first")}const o=a.accessControl&&typeof a.accessControl=="object"&&!Array.isArray(a.accessControl)?{...a.accessControl}:{};o.allowIps=s,a.accessControl=o,v.mkdirSync(K.dirname(r),{recursive:!0}),v.writeFileSync(r,JSON.stringify(a,null,2),"utf-8"),be()}),x.info("Access control config updated via panel",{configPath:r,entryCount:s.length}),e.json({ok:!0,allowIps:s})}catch(r){const a=r,o=Number(a.status)||500;o>=500&&x.error("Failed to save access control config:",a.message),e.status(o).json({ok:!1,error:a.message})}}),g.get("/runtime/node-info",(t,e)=>{if(!m(t,e))return;const n=H(),s=_();e.json({ok:!0,hostname:w.hostname(),platform:w.platform(),arch:w.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:E(),homeDir:process.env.AWS_RUNTIME_HOME_DIR||w.homedir(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:ie()})}),g.get("/runtime/logs",(t,e)=>{if(!m(t,e))return;const n=t.query.level||void 0,s=Math.min(Number(t.query.count)||200,500),r=n&&["debug","info","warn","error"].includes(n)?n:void 0,a=ge(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await pe();e.json({ok:n})}catch(n){const s=n;e.status(500).json({ok:!1,error:s.message})}}),g.get("/runtime/admin/sessions",(t,e)=>{if(!m(t,e))return;const n=de().map(s=>({token:s.token.slice(0,8)+"...",createdAt:new Date(s.createdAt).toISOString(),lastUsedAt:new Date(s.lastUsedAt).toISOString(),age:Math.floor((Date.now()-s.createdAt)/1e3)}));e.json({ok:!0,sessions:n})}),g.post("/runtime/admin/sessions/:token/destroy",(t,e)=>{m(t,e)&&(z(t.params.token),e.json({ok:!0}))});function Ce(t){return new Promise((e,n)=>{const s=w.platform()==="win32",d=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];Y(d,r,{timeout:5e3},a=>a?n(a):e())})}g.post("/runtime/kill-process",async(t,e)=>{if(!m(t,e))return;const n=Number(t.body.pid);if(!n||!Number.isInteger(n)||n<=0){e.status(400).json({ok:!1,error:"Invalid PID"});return}const s=C(),d=()=>{const a=new Set,o=P?.data??[];for(const l of o)typeof l.pid=="number"&&a.add(l.pid);for(const l of s.getAllManagedProcesses())typeof l.pid=="number"&&a.add(l.pid);return a};let r=d();if(r.size===0)try{P={data:await s.discoverAllProcesses(),ts:Date.now()},r=d()}catch{}if(!r.has(n)){x.warn("Rejected kill-process for unrecognized PID",{pid:n}),e.status(404).json({ok:!1,error:"PID is not a bridge-discovered or registered agent process"});return}try{await Ce(n),x.info("Killed process",{pid:n}),e.json({ok:!0})}catch(a){const o=a;x.warn("Failed to kill process",{pid:n,error:o.message}),e.status(500).json({ok:!1,error:o.message})}}),g.get("/runtime/dashboard",async(t,e)=>{if(m(t,e)){if(T&&Date.now()-T.ts<ve)return void e.json(T.data);try{const n=L?.data??null,s=P?.data??null;L||D();const[d,r,a]=await Promise.all([Promise.resolve(H()),Promise.resolve(_()),ee().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(se.entries()).map(([I,y])=>({sessionId:I,agentId:y.agentId,workspacePath:y.workspacePath,runtimeStatus:y.currentRuntimeStatus||"running",runtimeActionType:y.currentRuntimeActionType,runtimeActionLabel:y.currentRuntimeActionLabel,providerId:y.providerId}));let p={managed:0,orphan:0,unknown:0},b=[];if(s){p={managed:s.filter(u=>u.managementStatus==="managed").length,orphan:s.filter(u=>u.managementStatus==="orphan").length,unknown:s.filter(u=>u.managementStatus==="unknown").length};const I=new Map(l.map(u=>[u.pid,u]));b=s.map(u=>{const k=I.get(u.pid);return{agentId:u.agentId||k?.agentId,pid:u.pid,managementStatus:u.managementStatus,sessionId:k?.sessionId,state:k?.state,command:u.command,cwd:u.cwd,startedAt:u.startedAt}});const y={managed:0,orphan:1,unknown:2};b.sort((u,k)=>(y[u.managementStatus]??9)-(y[k.managementStatus]??9))}const i=new Set(f.map(I=>I.sessionId)),h=a.filter(I=>!i.has(I.sessionId)),c=d,S=await xe(),j={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:V,hostname:w.hostname(),platform:w.platform(),arch:w.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version},binding:{status:c.status,paired:c.paired,instanceId:c.instanceId,userId:c.userId,schedulerBaseUrl:c.schedulerBaseUrl,pairingCode:Q()},registration:{registered:r.registered,instanceId:r.instanceId,lastAttempt:r.lastAttempt?.toISOString(),error:r.error},metrics:n,sessions:{active:f,activeCount:f.length,persisted:h,persistedCount:h.length,total:f.length+h.length},processes:{...p,total:p.managed+p.orphan+p.unknown,list:b,registryCount:l.length},lifecycleOperation:S};T={data:j,ts:Date.now()},e.json(j)}catch(n){const s=n;x.error("Dashboard aggregation failed:",s.message),e.status(500).json({ok:!1,error:s.message})}}}),g.get("/login",(t,e)=>{e.type("text/html; charset=utf-8"),e.send(Ee)}),g.get("/",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}if(!B()){e.redirect("/setup");return}const s=t.query.lang==="en"?"en":"zh";e.type("text/html; charset=utf-8"),e.send(Be.replace("{{LANG}}",s))}),g.get("/setup",(t,e)=>{const n=A(t);if(!n||!U(n)){e.redirect("/login");return}e.type("text/html; charset=utf-8"),e.send(Te)});const Be=`<!DOCTYPE html>
|
|
2
2
|
<html lang="{{LANG}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
@@ -295,6 +295,7 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
295
295
|
</span>
|
|
296
296
|
<span class="lang-btn" id="langBtn" onclick="toggleLang()">\u4E2D\u6587</span>
|
|
297
297
|
<button onclick="openSettings()" id="settingsBtn" title="Settings / \u8BBE\u7F6E">⚙</button>
|
|
298
|
+
<button onclick="doLogout()" id="logoutBtn" title="Logout / \u9000\u51FA\u767B\u5F55">⏻</button>
|
|
298
299
|
<button onclick="refresh()" id="refreshBtn">↻</button>
|
|
299
300
|
</div>
|
|
300
301
|
</div>
|
|
@@ -351,6 +352,7 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
351
352
|
<div class="settings-nav-item" data-page="node" onclick="switchSettingsPage('node')"><span id="navNode"></span></div>
|
|
352
353
|
<div class="settings-nav-item" data-page="logs" onclick="switchSettingsPage('logs')"><span id="navLogs"></span></div>
|
|
353
354
|
<div class="settings-nav-item" data-page="scheduler" onclick="switchSettingsPage('scheduler')"><span id="navScheduler"></span></div>
|
|
355
|
+
<div class="settings-nav-item" data-page="access" onclick="switchSettingsPage('access')"><span id="navAccess"></span></div>
|
|
354
356
|
<div class="settings-nav-item" data-page="maintenance" onclick="switchSettingsPage('maintenance')"><span id="navMaintenance"></span></div>
|
|
355
357
|
</div>
|
|
356
358
|
<div class="settings-content">
|
|
@@ -417,15 +419,83 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
417
419
|
<!-- Scheduler page -->
|
|
418
420
|
<div class="settings-page" id="pageScheduler">
|
|
419
421
|
<div class="s-field">
|
|
420
|
-
<label id="
|
|
421
|
-
<div class="s-
|
|
422
|
+
<label id="lblSchedTargets"></label>
|
|
423
|
+
<div class="s-target-list" id="schedTargetList"></div>
|
|
424
|
+
</div>
|
|
425
|
+
<div class="s-actions s-actions-row" style="margin-bottom:18px">
|
|
426
|
+
<button class="s-btn s-btn-secondary" id="btnAddTarget" onclick="startAddTarget()"></button>
|
|
427
|
+
</div>
|
|
428
|
+
<div class="s-target-form" id="targetForm">
|
|
429
|
+
<div class="s-field">
|
|
430
|
+
<label id="lblSchedUrl"></label>
|
|
431
|
+
<input type="text" id="cfgServerUrl" placeholder="http://192.168.1.100:8080" autocomplete="off">
|
|
432
|
+
<span class="s-hint" id="hintSchedUrl"></span>
|
|
433
|
+
</div>
|
|
434
|
+
<div class="s-field">
|
|
435
|
+
<label id="lblSchedUserKey"></label>
|
|
436
|
+
<input type="password" id="cfgUserKey" autocomplete="new-password">
|
|
437
|
+
<span class="s-hint" id="hintSchedUserKey"></span>
|
|
438
|
+
</div>
|
|
439
|
+
<div class="s-field">
|
|
440
|
+
<label id="lblSchedTenantId"></label>
|
|
441
|
+
<input type="text" id="cfgTenantId" autocomplete="off">
|
|
442
|
+
<span class="s-hint" id="hintSchedTenantId"></span>
|
|
443
|
+
</div>
|
|
444
|
+
<div class="s-field">
|
|
445
|
+
<label id="lblSchedInstanceName"></label>
|
|
446
|
+
<input type="text" id="cfgInstanceName" autocomplete="off">
|
|
447
|
+
<span class="s-hint" id="hintSchedInstanceName"></span>
|
|
448
|
+
</div>
|
|
449
|
+
<div class="s-field">
|
|
450
|
+
<label id="lblSchedConnKey"></label>
|
|
451
|
+
<input type="password" id="cfgConnectionKey" autocomplete="new-password">
|
|
452
|
+
<span class="s-hint" id="hintSchedConnKey"></span>
|
|
453
|
+
</div>
|
|
454
|
+
<div class="s-field">
|
|
455
|
+
<label id="lblSchedRegisterAddr"></label>
|
|
456
|
+
<input type="text" id="cfgRegisterAddr" placeholder="192.168.1.25:18081" autocomplete="off">
|
|
457
|
+
<span class="s-hint" id="hintSchedRegisterAddr"></span>
|
|
458
|
+
</div>
|
|
459
|
+
<div class="s-field">
|
|
460
|
+
<label id="lblSchedTunnel"></label>
|
|
461
|
+
<select id="cfgTunnel">
|
|
462
|
+
<option value="auto" id="optTunnelAuto"></option>
|
|
463
|
+
<option value="true" id="optTunnelOn"></option>
|
|
464
|
+
<option value="false" id="optTunnelOff"></option>
|
|
465
|
+
</select>
|
|
466
|
+
<span class="s-hint" id="hintSchedTunnel"></span>
|
|
467
|
+
</div>
|
|
468
|
+
<div class="s-field">
|
|
469
|
+
<div class="s-info-row"><span class="s-info-label" id="lblSchedInstanceId"></span><span class="s-info-value" id="valSchedInstanceId">-</span></div>
|
|
470
|
+
<span class="s-hint" id="hintSchedInstanceId"></span>
|
|
471
|
+
</div>
|
|
472
|
+
<div class="s-msg" id="schedMsg"></div>
|
|
473
|
+
<div class="s-actions s-actions-row">
|
|
474
|
+
<button class="s-btn s-btn-primary" id="btnSaveServerConfig" onclick="saveServerConfig(false)"></button>
|
|
475
|
+
<button class="s-btn s-btn-danger" id="btnDeleteTarget" onclick="deleteTargetFromList()"></button>
|
|
476
|
+
</div>
|
|
422
477
|
</div>
|
|
423
478
|
<div class="s-field">
|
|
424
479
|
<p class="s-desc" id="descSched"></p>
|
|
425
480
|
</div>
|
|
426
|
-
<div class="s-
|
|
481
|
+
<div class="s-actions s-actions-row">
|
|
482
|
+
<button class="s-btn s-btn-danger" id="btnSaveRestart" onclick="saveServerConfigAndRestart()"></button>
|
|
483
|
+
<button class="s-btn s-btn-secondary" id="btnReRegister" onclick="doReRegister()"></button>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
<!-- Access control page -->
|
|
487
|
+
<div class="settings-page" id="pageAccess">
|
|
488
|
+
<div class="s-field">
|
|
489
|
+
<label id="lblAccessIps"></label>
|
|
490
|
+
<textarea id="cfgAllowIps" rows="4" placeholder="0.0.0.0" autocomplete="off" spellcheck="false"></textarea>
|
|
491
|
+
<span class="s-hint" id="hintAccessIps"></span>
|
|
492
|
+
</div>
|
|
493
|
+
<div class="s-msg" id="accessMsg"></div>
|
|
427
494
|
<div class="s-actions">
|
|
428
|
-
<button class="s-btn s-btn-primary" id="
|
|
495
|
+
<button class="s-btn s-btn-primary" id="btnSaveAccessControl" onclick="saveAccessControl()"></button>
|
|
496
|
+
</div>
|
|
497
|
+
<div class="s-field" style="margin-top:18px">
|
|
498
|
+
<p class="s-desc" id="descAccess"></p>
|
|
429
499
|
</div>
|
|
430
500
|
</div>
|
|
431
501
|
<!-- Maintenance page -->
|
|
@@ -478,6 +548,21 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
478
548
|
.s-field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: var(--text2); }
|
|
479
549
|
.s-field input { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; outline: none; }
|
|
480
550
|
.s-field input:focus { border-color: var(--accent); }
|
|
551
|
+
.s-field select { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; outline: none; cursor: pointer; }
|
|
552
|
+
.s-field select:focus { border-color: var(--accent); }
|
|
553
|
+
.s-field select option { background: var(--surface2); color: var(--text); }
|
|
554
|
+
.s-field textarea { width: 100%; padding: 9px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); font-size: 14px; font-family: "Cascadia Code", "Fira Code", Consolas, monospace; outline: none; resize: vertical; }
|
|
555
|
+
.s-field textarea:focus { border-color: var(--accent); }
|
|
556
|
+
.s-actions-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
557
|
+
.s-target-list { display: flex; flex-direction: column; gap: 8px; }
|
|
558
|
+
.s-target-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; background: var(--surface2); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; }
|
|
559
|
+
.s-target-item.active { border-color: var(--accent); }
|
|
560
|
+
.s-target-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
561
|
+
.s-target-url { font-size: 14px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
562
|
+
.s-target-meta { font-size: 12px; opacity: 0.7; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
563
|
+
.s-target-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
564
|
+
.s-target-form { border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 18px; background: var(--surface); }
|
|
565
|
+
.s-target-empty { padding: 14px; text-align: center; font-size: 13px; opacity: 0.6; border: 1px dashed var(--border); border-radius: 6px; }
|
|
481
566
|
.s-hint { display: block; font-size: 11px; color: var(--text2); margin-top: 4px; }
|
|
482
567
|
.s-msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; display: none; }
|
|
483
568
|
.s-msg.error { display: block; background: rgba(240,84,84,0.12); color: var(--red); }
|
|
@@ -649,12 +734,53 @@ const T = {
|
|
|
649
734
|
logEmpty: "\u6682\u65E0\u65E5\u5FD7",
|
|
650
735
|
// \u8C03\u5EA6\u4E2D\u5FC3
|
|
651
736
|
schedTitle: "\u8C03\u5EA6\u4E2D\u5FC3",
|
|
652
|
-
|
|
737
|
+
schedTargets: "\u8C03\u5EA6\u4E2D\u5FC3\u5217\u8868\uFF08\u652F\u6301\u591A\u4E2A\uFF0C\u4FDD\u5B58\u540E\u5E76\u884C\u8FDE\u63A5\uFF09",
|
|
738
|
+
schedNoTargets: "\u5C1A\u672A\u914D\u7F6E\uFF0C\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u6DFB\u52A0",
|
|
739
|
+
schedAddTarget: "\uFF0B \u6DFB\u52A0\u8C03\u5EA6\u4E2D\u5FC3",
|
|
740
|
+
schedEditTarget: "\u7F16\u8F91",
|
|
741
|
+
schedDeleteTarget: "\u5220\u9664",
|
|
742
|
+
schedConfirmDelete: "\u5220\u9664\u8BE5\u8C03\u5EA6\u4E2D\u5FC3\u914D\u7F6E\uFF1F",
|
|
743
|
+
schedFormNew: "\u65B0\u589E\u8C03\u5EA6\u4E2D\u5FC3",
|
|
744
|
+
schedUrl: "\u670D\u52A1\u5730\u5740 (serverUrl)",
|
|
745
|
+
schedUrlHint: "\u5FC5\u586B\uFF0C\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF1B\u5730\u5740\u53D8\u66F4\u65F6 API Key \u4F1A\u81EA\u52A8\u6CBF\u7528",
|
|
746
|
+
schedUserKey: "\u7528\u6237 API Key (userKey)",
|
|
747
|
+
schedUserKeyHint: "\u5FC5\u586B\uFF1B\u5DF2\u914D\u7F6E\u65F6\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u4E0D\u53D8",
|
|
748
|
+
schedTenantId: "\u79DF\u6237 ID\uFF08\u53EF\u9009\uFF09",
|
|
749
|
+
schedTenantIdHint: "\u7559\u7A7A\u7531 server \u6839\u636E userKey \u81EA\u52A8\u63A8\u5BFC",
|
|
750
|
+
schedInstanceName: "\u5B9E\u4F8B\u540D\u79F0\uFF08\u53EF\u9009\uFF09",
|
|
751
|
+
schedInstanceNameHint: "\u7559\u7A7A\u4F7F\u7528\u4E3B\u673A\u540D",
|
|
752
|
+
schedConnKey: "\u8FDE\u63A5\u5BC6\u94A5\uFF08\u53EF\u9009\uFF09",
|
|
753
|
+
schedConnKeyHint: "server \u8FDE\u63A5\u8BE5 bridge \u7684\u5BC6\u94A5\uFF1B\u5DF2\u914D\u7F6E\u65F6\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u4E0D\u53D8",
|
|
754
|
+
schedRegisterAddr: "\u6CE8\u518C\u5730\u5740\uFF08\u53EF\u9009\uFF09",
|
|
755
|
+
schedRegisterAddrHint: "\u76F4\u8FDE\u6A21\u5F0F\u4E0B\u7684\u5730\u5740\uFF0C\u5982 192.168.1.25:18081\uFF1B\u6E05\u7A7A\u8868\u793A\u4E0D\u6307\u5B9A",
|
|
756
|
+
schedTunnel: "\u96A7\u9053\u6A21\u5F0F",
|
|
757
|
+
schedTunnelHint: "\u81EA\u52A8\uFF1A\u672A\u914D\u7F6E\u6CE8\u518C\u5730\u5740\u65F6\u81EA\u52A8\u542F\u7528\u96A7\u9053\uFF1B\u542F\u7528\uFF1A\u7ECF WebSocket \u53CD\u5411\u8FDE\u63A5 server\uFF1B\u7981\u7528\uFF1A\u76F4\u8FDE",
|
|
758
|
+
schedTunnelAuto: "\u81EA\u52A8",
|
|
759
|
+
schedTunnelOn: "\u542F\u7528",
|
|
760
|
+
schedTunnelOff: "\u7981\u7528",
|
|
761
|
+
schedInstanceId: "\u5B9E\u4F8B ID",
|
|
762
|
+
schedInstanceIdHint: "\u7F16\u8F91\u6539\u5740\u65F6\u81EA\u52A8\u7EE7\u627F\uFF08\u540C\u4E00\u5B9E\u4F8B\u6362\u5730\u5740\uFF09\uFF0C\u65B0\u589E\u76EE\u6807\u6CE8\u518C\u6210\u529F\u540E\u81EA\u52A8\u56DE\u586B",
|
|
763
|
+
schedSave: "\u4FDD\u5B58\u914D\u7F6E",
|
|
764
|
+
schedSaving: "\u4FDD\u5B58\u4E2D...",
|
|
765
|
+
schedSaved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58",
|
|
766
|
+
schedSavedRegistering: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u6B63\u5728\u91CD\u65B0\u6CE8\u518C...",
|
|
767
|
+
schedSaveFail: "\u4FDD\u5B58\u5931\u8D25",
|
|
768
|
+
schedSaveAndRestart: "\u4FDD\u5B58\u5E76\u91CD\u542F",
|
|
769
|
+
schedSaveRestartConfirm: "\u4FDD\u5B58\u914D\u7F6E\u5E76\u91CD\u542F Bridge\uFF1F\u91CD\u542F\u671F\u95F4\u670D\u52A1\u4F1A\u77ED\u6682\u4E2D\u65AD\uFF0C\u8FD0\u884C\u4E2D\u7684 Agent \u4F1A\u88AB\u4FDD\u7559\u3002",
|
|
653
770
|
schedReRegister: "\u91CD\u65B0\u6CE8\u518C",
|
|
654
771
|
schedReRegistering: "\u6CE8\u518C\u4E2D...",
|
|
655
772
|
schedReRegistered: "\u91CD\u65B0\u6CE8\u518C\u6210\u529F",
|
|
656
773
|
schedReRegisterFail: "\u91CD\u65B0\u6CE8\u518C\u5931\u8D25",
|
|
657
|
-
schedDesc: "\
|
|
774
|
+
schedDesc: "\u4FDD\u5B58\u540E\u53EF\u70B9\u51FB\u201C\u91CD\u65B0\u6CE8\u518C\u201D\u7ACB\u5373\u5E94\u7528\u65B0\u914D\u7F6E\uFF1B\u96A7\u9053\u6A21\u5F0F\u7B49\u90E8\u5206\u53D8\u66F4\u9700\u91CD\u542F Bridge \u751F\u6548\u3002",
|
|
775
|
+
// \u8BBF\u95EE\u63A7\u5236
|
|
776
|
+
accTitle: "\u8BBF\u95EE\u63A7\u5236",
|
|
777
|
+
accAllowIps: "\u5141\u8BB8\u8BBF\u95EE\u7684 IP / \u7F51\u6BB5",
|
|
778
|
+
accAllowIpsHint: "\u591A\u4E2A\u7528\u9017\u53F7\u6216\u6362\u884C\u5206\u9694\uFF0C\u652F\u6301\u5355\u4E2A IP \u4E0E CIDR \u7F51\u6BB5\uFF08\u5982 192.168.1.0/24\uFF09\uFF1B\u586B 0.0.0.0 \u6216 * \u8868\u793A\u5BF9\u6240\u6709\u4EBA\u5F00\u653E\uFF1B\u7559\u7A7A\u9ED8\u8BA4\u5373 0.0.0.0 \u5168\u5F00\u653E",
|
|
779
|
+
accDesc: "\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF0C\u65E0\u9700\u91CD\u542F\u3002\u672C\u673A\uFF08127.0.0.1\uFF09\u59CB\u7EC8\u5141\u8BB8\u8BBF\u95EE\uFF1B\u76F4\u8FDE\u6A21\u5F0F\u7684\u8C03\u5EA6\u4E2D\u5FC3\u82E5\u6765\u81EA\u5916\u90E8\u5730\u5740\uFF0C\u9700\u5C06\u5176 IP \u52A0\u5165\u767D\u540D\u5355\uFF0C\u5426\u5219\u4F1A\u88AB\u62D2\u7EDD\u3002",
|
|
780
|
+
accSave: "\u4FDD\u5B58",
|
|
781
|
+
accSaving: "\u4FDD\u5B58\u4E2D...",
|
|
782
|
+
accSaved: "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
|
|
783
|
+
accSaveFail: "\u4FDD\u5B58\u5931\u8D25",
|
|
658
784
|
// \u72B6\u6001\u6587\u672C
|
|
659
785
|
stHealthy: "\u5065\u5EB7",
|
|
660
786
|
stError: "\u5F02\u5E38",
|
|
@@ -778,12 +904,53 @@ const T = {
|
|
|
778
904
|
logEmpty: "No logs yet",
|
|
779
905
|
// Scheduler
|
|
780
906
|
schedTitle: "Scheduler",
|
|
781
|
-
|
|
907
|
+
schedTargets: "Scheduler targets (multiple allowed, connected in parallel)",
|
|
908
|
+
schedNoTargets: "None configured. Click the button below to add one.",
|
|
909
|
+
schedAddTarget: "+ Add scheduler",
|
|
910
|
+
schedEditTarget: "Edit",
|
|
911
|
+
schedDeleteTarget: "Delete",
|
|
912
|
+
schedConfirmDelete: "Delete this scheduler configuration?",
|
|
913
|
+
schedFormNew: "New scheduler",
|
|
914
|
+
schedUrl: "Server URL (serverUrl)",
|
|
915
|
+
schedUrlHint: "Required, must start with http:// or https://; the API key is kept when the URL changes",
|
|
916
|
+
schedUserKey: "User API Key (userKey)",
|
|
917
|
+
schedUserKeyHint: "Required; leave blank to keep the configured value",
|
|
918
|
+
schedTenantId: "Tenant ID (optional)",
|
|
919
|
+
schedTenantIdHint: "Leave blank to auto-derive from userKey on the server",
|
|
920
|
+
schedInstanceName: "Instance name (optional)",
|
|
921
|
+
schedInstanceNameHint: "Leave blank to use the hostname",
|
|
922
|
+
schedConnKey: "Connection key (optional)",
|
|
923
|
+
schedConnKeyHint: "Key for the server to connect to this bridge; leave blank to keep the configured value",
|
|
924
|
+
schedRegisterAddr: "Register address (optional)",
|
|
925
|
+
schedRegisterAddrHint: "Address in direct mode, e.g. 192.168.1.25:18081; clear to unset",
|
|
926
|
+
schedTunnel: "Tunnel mode",
|
|
927
|
+
schedTunnelHint: "Auto: tunnel is enabled when no register address is set; On: reverse WebSocket connection to the server; Off: direct connection",
|
|
928
|
+
schedTunnelAuto: "Auto",
|
|
929
|
+
schedTunnelOn: "On",
|
|
930
|
+
schedTunnelOff: "Off",
|
|
931
|
+
schedInstanceId: "Instance ID",
|
|
932
|
+
schedInstanceIdHint: "Inherited automatically when editing the URL (same instance, new address); backfilled after a new target registers successfully",
|
|
933
|
+
schedSave: "Save config",
|
|
934
|
+
schedSaving: "Saving...",
|
|
935
|
+
schedSaved: "Configuration saved",
|
|
936
|
+
schedSavedRegistering: "Configuration saved, re-registering...",
|
|
937
|
+
schedSaveFail: "Failed to save",
|
|
938
|
+
schedSaveAndRestart: "Save & restart",
|
|
939
|
+
schedSaveRestartConfirm: "Save the configuration and restart the bridge? The service will be briefly interrupted; running agents are preserved.",
|
|
782
940
|
schedReRegister: "Re-register",
|
|
783
941
|
schedReRegistering: "Registering...",
|
|
784
942
|
schedReRegistered: "Re-registered successfully",
|
|
785
943
|
schedReRegisterFail: "Re-registration failed",
|
|
786
|
-
schedDesc: "Re-register
|
|
944
|
+
schedDesc: "After saving, click 'Re-register' to apply the new config immediately; some changes such as tunnel mode require a bridge restart.",
|
|
945
|
+
// Access control
|
|
946
|
+
accTitle: "Access Control",
|
|
947
|
+
accAllowIps: "Allowed IPs / networks",
|
|
948
|
+
accAllowIpsHint: "Separate entries with commas or new lines; single IPs and CIDR blocks (e.g. 192.168.1.0/24) are supported; 0.0.0.0 or * allows everyone; empty defaults to 0.0.0.0 (allow all)",
|
|
949
|
+
accDesc: "Takes effect immediately after saving, no restart needed. Loopback (127.0.0.1) is always allowed; if a direct-mode scheduler connects from an external address, add its IP to the list or it will be rejected.",
|
|
950
|
+
accSave: "Save",
|
|
951
|
+
accSaving: "Saving...",
|
|
952
|
+
accSaved: "Saved and applied",
|
|
953
|
+
accSaveFail: "Failed to save",
|
|
787
954
|
// \u72B6\u6001\u6587\u672C
|
|
788
955
|
stHealthy: "Healthy",
|
|
789
956
|
stError: "Error",
|
|
@@ -1092,6 +1259,15 @@ function toggleUnknown() {
|
|
|
1092
1259
|
}
|
|
1093
1260
|
|
|
1094
1261
|
// \u2500\u2500 \u8BF7\u6C42\u4E0E\u8F6E\u8BE2 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1262
|
+
// \u9000\u51FA\u767B\u5F55\uFF1A\u9500\u6BC1\u9762\u677F\u4F1A\u8BDD\u540E\u8DF3\u8F6C\u767B\u5F55\u9875
|
|
1263
|
+
function doLogout() {
|
|
1264
|
+
fetch("/runtime/logout", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
1265
|
+
.catch(function() {})
|
|
1266
|
+
.then(function() {
|
|
1267
|
+
location.href = "/login";
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1095
1271
|
async function refresh() {
|
|
1096
1272
|
try {
|
|
1097
1273
|
const resp = await fetch("/runtime/dashboard");
|
|
@@ -1258,15 +1434,44 @@ function renderSettingsUI() {
|
|
|
1258
1434
|
document.getElementById("lblNodeActiveSessions").textContent = t("nodeActiveSessions");
|
|
1259
1435
|
document.getElementById("lblLogAutoRefresh").textContent = t("logAutoRefresh");
|
|
1260
1436
|
document.getElementById("logEmpty").textContent = t("logEmpty");
|
|
1437
|
+
document.getElementById("lblSchedTargets").textContent = t("schedTargets");
|
|
1438
|
+
document.getElementById("btnAddTarget").textContent = t("schedAddTarget");
|
|
1439
|
+
document.getElementById("btnDeleteTarget").textContent = t("schedDeleteTarget");
|
|
1261
1440
|
document.getElementById("lblSchedUrl").textContent = t("schedUrl");
|
|
1441
|
+
document.getElementById("hintSchedUrl").textContent = t("schedUrlHint");
|
|
1442
|
+
document.getElementById("lblSchedUserKey").textContent = t("schedUserKey");
|
|
1443
|
+
document.getElementById("hintSchedUserKey").textContent = t("schedUserKeyHint");
|
|
1444
|
+
document.getElementById("lblSchedTenantId").textContent = t("schedTenantId");
|
|
1445
|
+
document.getElementById("hintSchedTenantId").textContent = t("schedTenantIdHint");
|
|
1446
|
+
document.getElementById("lblSchedInstanceName").textContent = t("schedInstanceName");
|
|
1447
|
+
document.getElementById("hintSchedInstanceName").textContent = t("schedInstanceNameHint");
|
|
1448
|
+
document.getElementById("lblSchedConnKey").textContent = t("schedConnKey");
|
|
1449
|
+
document.getElementById("hintSchedConnKey").textContent = t("schedConnKeyHint");
|
|
1450
|
+
document.getElementById("lblSchedRegisterAddr").textContent = t("schedRegisterAddr");
|
|
1451
|
+
document.getElementById("hintSchedRegisterAddr").textContent = t("schedRegisterAddrHint");
|
|
1452
|
+
document.getElementById("lblSchedTunnel").textContent = t("schedTunnel");
|
|
1453
|
+
document.getElementById("hintSchedTunnel").textContent = t("schedTunnelHint");
|
|
1454
|
+
document.getElementById("optTunnelAuto").textContent = t("schedTunnelAuto");
|
|
1455
|
+
document.getElementById("optTunnelOn").textContent = t("schedTunnelOn");
|
|
1456
|
+
document.getElementById("optTunnelOff").textContent = t("schedTunnelOff");
|
|
1457
|
+
document.getElementById("lblSchedInstanceId").textContent = t("schedInstanceId");
|
|
1458
|
+
document.getElementById("hintSchedInstanceId").textContent = t("schedInstanceIdHint");
|
|
1459
|
+
document.getElementById("btnSaveServerConfig").textContent = t("schedSave");
|
|
1460
|
+
document.getElementById("btnSaveRestart").textContent = t("schedSaveAndRestart");
|
|
1262
1461
|
document.getElementById("descSched").textContent = t("schedDesc");
|
|
1263
1462
|
document.getElementById("btnReRegister").textContent = t("schedReRegister");
|
|
1463
|
+
document.getElementById("navAccess").textContent = t("accTitle");
|
|
1464
|
+
document.getElementById("lblAccessIps").textContent = t("accAllowIps");
|
|
1465
|
+
document.getElementById("hintAccessIps").textContent = t("accAllowIpsHint");
|
|
1466
|
+
document.getElementById("descAccess").textContent = t("accDesc");
|
|
1467
|
+
document.getElementById("btnSaveAccessControl").textContent = t("accSave");
|
|
1264
1468
|
}
|
|
1265
1469
|
function switchSettingsPage(page) {
|
|
1266
1470
|
document.querySelectorAll(".settings-nav-item").forEach(function(el) { el.classList.remove("active"); });
|
|
1267
1471
|
document.querySelectorAll(".settings-page").forEach(function(el) { el.classList.remove("active"); });
|
|
1268
1472
|
document.querySelector('.settings-nav-item[data-page="' + page + '"]').classList.add("active");
|
|
1269
1473
|
document.getElementById('page' + page.charAt(0).toUpperCase() + page.slice(1)).classList.add("active");
|
|
1474
|
+
if (page === "access") refreshAccessControl();
|
|
1270
1475
|
}
|
|
1271
1476
|
function openSettings() {
|
|
1272
1477
|
if (settingsLang !== lang) { renderSettingsUI(); settingsLang = lang; }
|
|
@@ -1428,6 +1633,267 @@ document.addEventListener("change", function(e) {
|
|
|
1428
1633
|
});
|
|
1429
1634
|
|
|
1430
1635
|
// \u2500\u2500 Scheduler \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1636
|
+
// \u591A\u8C03\u5EA6\u4E2D\u5FC3\u7BA1\u7406\uFF1AschedTargets \u7F13\u5B58\u5F53\u524D\u5217\u8868\uFF08GET \u89C6\u56FE\uFF09\uFF0CschedEditIndex \u4E3A\u8868\u5355\u6B63\u5728\u7F16\u8F91\u7684\u4E0B\u6807\uFF08-1 = \u65B0\u589E\uFF09
|
|
1637
|
+
var schedTargets = [];
|
|
1638
|
+
var schedEditIndex = -1;
|
|
1639
|
+
|
|
1640
|
+
// \u6E32\u67D3\u76EE\u6807\u5217\u8868\uFF08\u5361\u7247\uFF1AURL + \u5143\u4FE1\u606F\uFF0C\u70B9\u51FB\u8FDB\u5165\u7F16\u8F91\uFF09
|
|
1641
|
+
function renderSchedTargetList() {
|
|
1642
|
+
var list = document.getElementById("schedTargetList");
|
|
1643
|
+
list.innerHTML = "";
|
|
1644
|
+
if (!schedTargets.length) {
|
|
1645
|
+
var empty = document.createElement("div");
|
|
1646
|
+
empty.className = "s-target-empty";
|
|
1647
|
+
empty.textContent = t("schedNoTargets");
|
|
1648
|
+
list.appendChild(empty);
|
|
1649
|
+
return;
|
|
1650
|
+
}
|
|
1651
|
+
for (var i = 0; i < schedTargets.length; i++) {
|
|
1652
|
+
(function(idx) {
|
|
1653
|
+
var item = schedTargets[idx];
|
|
1654
|
+
var row = document.createElement("div");
|
|
1655
|
+
row.className = "s-target-item" + (idx === schedEditIndex ? " active" : "");
|
|
1656
|
+
var main = document.createElement("div");
|
|
1657
|
+
main.className = "s-target-main";
|
|
1658
|
+
var url = document.createElement("div");
|
|
1659
|
+
url.className = "s-target-url";
|
|
1660
|
+
url.textContent = item.serverUrl;
|
|
1661
|
+
var meta = document.createElement("div");
|
|
1662
|
+
meta.className = "s-target-meta";
|
|
1663
|
+
var parts = [];
|
|
1664
|
+
if (item.instanceName) parts.push(item.instanceName);
|
|
1665
|
+
if (item.instanceId) parts.push("ID: " + item.instanceId);
|
|
1666
|
+
if (item.tunnel === true) parts.push("tunnel");
|
|
1667
|
+
else if (item.tunnel === false && item.registerAddr) parts.push("direct");
|
|
1668
|
+
meta.textContent = parts.join(" \xB7 ");
|
|
1669
|
+
main.appendChild(url);
|
|
1670
|
+
main.appendChild(meta);
|
|
1671
|
+
var actions = document.createElement("div");
|
|
1672
|
+
actions.className = "s-target-actions";
|
|
1673
|
+
var editBtn = document.createElement("button");
|
|
1674
|
+
editBtn.className = "s-btn s-btn-sm s-btn-secondary";
|
|
1675
|
+
editBtn.textContent = t("schedEditTarget");
|
|
1676
|
+
editBtn.onclick = function(ev) { ev.stopPropagation(); loadTargetToForm(idx); };
|
|
1677
|
+
actions.appendChild(editBtn);
|
|
1678
|
+
row.appendChild(main);
|
|
1679
|
+
row.appendChild(actions);
|
|
1680
|
+
row.onclick = function() { loadTargetToForm(idx); };
|
|
1681
|
+
list.appendChild(row);
|
|
1682
|
+
})(i);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// \u5C06\u6307\u5B9A target \u8F7D\u5165\u8868\u5355\uFF08\u7F16\u8F91\u6A21\u5F0F\uFF09\uFF1A\u5BC6\u94A5\u7559\u7A7A=\u4FDD\u6301\u4E0D\u53D8\uFF08placeholder \u663E\u793A\u63A9\u7801\uFF09
|
|
1687
|
+
function loadTargetToForm(idx) {
|
|
1688
|
+
schedEditIndex = idx;
|
|
1689
|
+
var item = schedTargets[idx];
|
|
1690
|
+
document.getElementById("cfgServerUrl").value = item.serverUrl || "";
|
|
1691
|
+
document.getElementById("cfgTenantId").value = item.tenantId || "";
|
|
1692
|
+
document.getElementById("cfgInstanceName").value = item.instanceName || "";
|
|
1693
|
+
document.getElementById("cfgRegisterAddr").value = item.registerAddr || "";
|
|
1694
|
+
document.getElementById("cfgTunnel").value =
|
|
1695
|
+
item.tunnel === true ? "true" : (item.tunnel === false ? "false" : "auto");
|
|
1696
|
+
var uk = document.getElementById("cfgUserKey");
|
|
1697
|
+
var ck = document.getElementById("cfgConnectionKey");
|
|
1698
|
+
uk.value = "";
|
|
1699
|
+
ck.value = "";
|
|
1700
|
+
uk.placeholder = item.userKeyMasked || "";
|
|
1701
|
+
ck.placeholder = item.connectionKeyMasked || "";
|
|
1702
|
+
document.getElementById("valSchedInstanceId").textContent = item.instanceId || "-";
|
|
1703
|
+
document.getElementById("btnDeleteTarget").style.display = "";
|
|
1704
|
+
renderSchedTargetList();
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
// \u8FDB\u5165\u65B0\u589E\u6A21\u5F0F\uFF1A\u6E05\u7A7A\u8868\u5355
|
|
1708
|
+
function startAddTarget() {
|
|
1709
|
+
schedEditIndex = -1;
|
|
1710
|
+
["cfgServerUrl", "cfgTenantId", "cfgInstanceName", "cfgRegisterAddr", "cfgUserKey", "cfgConnectionKey"].forEach(function(id) {
|
|
1711
|
+
document.getElementById(id).value = "";
|
|
1712
|
+
});
|
|
1713
|
+
document.getElementById("cfgUserKey").placeholder = "";
|
|
1714
|
+
document.getElementById("cfgConnectionKey").placeholder = "";
|
|
1715
|
+
document.getElementById("cfgTunnel").value = "auto";
|
|
1716
|
+
document.getElementById("valSchedInstanceId").textContent = "-";
|
|
1717
|
+
document.getElementById("btnDeleteTarget").style.display = "none";
|
|
1718
|
+
hideSchedMsg();
|
|
1719
|
+
renderSchedTargetList();
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
function hideSchedMsg() {
|
|
1723
|
+
var msg = document.getElementById("schedMsg");
|
|
1724
|
+
msg.className = "s-msg";
|
|
1725
|
+
msg.style.display = "none";
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function showSchedMsg(text, ok) {
|
|
1729
|
+
var msg = document.getElementById("schedMsg");
|
|
1730
|
+
msg.textContent = text;
|
|
1731
|
+
msg.className = "s-msg " + (ok ? "success" : "error");
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// \u8868\u5355 \u2192 target \u5BF9\u8C61\uFF08\u5BC6\u94A5\u539F\u6837\u643A\u5E26\uFF0C\u7559\u7A7A\u7531\u540E\u7AEF\u6309 originalServerUrl/serverUrl \u7EE7\u627F\uFF09
|
|
1735
|
+
function formToTarget() {
|
|
1736
|
+
return {
|
|
1737
|
+
serverUrl: document.getElementById("cfgServerUrl").value.trim(),
|
|
1738
|
+
// \u7F16\u8F91\u6A21\u5F0F\u643A\u5E26\u539F\u5730\u5740\uFF1A\u5730\u5740\u53D8\u66F4\u65F6\u540E\u7AEF\u6309\u539F\u5730\u5740\u7EE7\u627F instanceId/\u5BC6\u94A5\uFF08\u540C\u4E00\u5B9E\u4F8B\u6362\u5730\u5740\uFF0C\u65E0\u9700\u91CD\u65B0\u6CE8\u518C\uFF09
|
|
1739
|
+
originalServerUrl: schedEditIndex >= 0 && schedTargets[schedEditIndex]
|
|
1740
|
+
? (schedTargets[schedEditIndex].serverUrl || "")
|
|
1741
|
+
: "",
|
|
1742
|
+
userKey: document.getElementById("cfgUserKey").value,
|
|
1743
|
+
tenantId: document.getElementById("cfgTenantId").value.trim(),
|
|
1744
|
+
instanceName: document.getElementById("cfgInstanceName").value.trim(),
|
|
1745
|
+
connectionKey: document.getElementById("cfgConnectionKey").value,
|
|
1746
|
+
registerAddr: document.getElementById("cfgRegisterAddr").value.trim(),
|
|
1747
|
+
tunnel: document.getElementById("cfgTunnel").value,
|
|
1748
|
+
};
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// \u52A0\u8F7D server \u8FDE\u63A5\u914D\u7F6E\u5217\u8868\u5230\u9762\u677F
|
|
1752
|
+
function refreshServerConfig() {
|
|
1753
|
+
fetch("/runtime/server-config").then(function(r) { return r.json(); }).then(function(d) {
|
|
1754
|
+
if (!d.ok || !Array.isArray(d.targets)) return;
|
|
1755
|
+
schedTargets = d.targets;
|
|
1756
|
+
if (schedEditIndex >= schedTargets.length) schedEditIndex = -1;
|
|
1757
|
+
if (schedEditIndex >= 0) loadTargetToForm(schedEditIndex);
|
|
1758
|
+
else startAddTarget();
|
|
1759
|
+
}).catch(function() {});
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// \u5168\u91CF\u63D0\u4EA4 targets\uFF1BrestartAfter \u4E3A true \u65F6\u4FDD\u5B58\u6210\u529F\u540E\u91CD\u542F Bridge
|
|
1763
|
+
function saveServerConfig(restartAfter) {
|
|
1764
|
+
var form = formToTarget();
|
|
1765
|
+
if (!form.serverUrl) {
|
|
1766
|
+
showSchedMsg(t("schedSaveFail") + ": serverUrl", false);
|
|
1767
|
+
return Promise.resolve(false);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// \u7EC4\u88C5\u5168\u91CF\u5217\u8868\uFF1A\u7F16\u8F91\u6A21\u5F0F\u66FF\u6362\u5BF9\u5E94\u9879\uFF0C\u65B0\u589E\u6A21\u5F0F\u8FFD\u52A0\uFF1B\u5176\u4F59\u9879\u4ECE\u7F13\u5B58\u89C6\u56FE\u539F\u6837\u5E26\u4E0A\uFF08\u5BC6\u94A5\u7559\u7A7A\u7531\u540E\u7AEF\u7EE7\u627F\uFF09
|
|
1771
|
+
var list = schedTargets.map(function(item) {
|
|
1772
|
+
return {
|
|
1773
|
+
serverUrl: item.serverUrl,
|
|
1774
|
+
userKey: "",
|
|
1775
|
+
tenantId: item.tenantId || "",
|
|
1776
|
+
instanceName: item.instanceName || "",
|
|
1777
|
+
connectionKey: "",
|
|
1778
|
+
registerAddr: item.registerAddr || "",
|
|
1779
|
+
tunnel: item.tunnel === true ? "true" : (item.tunnel === false ? "false" : "auto"),
|
|
1780
|
+
};
|
|
1781
|
+
});
|
|
1782
|
+
if (schedEditIndex >= 0) list[schedEditIndex] = form;
|
|
1783
|
+
else list.push(form);
|
|
1784
|
+
|
|
1785
|
+
var btn = document.getElementById("btnSaveServerConfig");
|
|
1786
|
+
btn.disabled = true;
|
|
1787
|
+
btn.textContent = t("schedSaving");
|
|
1788
|
+
return fetch("/runtime/server-config", {
|
|
1789
|
+
method: "POST",
|
|
1790
|
+
headers: { "Content-Type": "application/json" },
|
|
1791
|
+
body: JSON.stringify({ targets: list }),
|
|
1792
|
+
})
|
|
1793
|
+
.then(function(r) { return r.json(); })
|
|
1794
|
+
.then(function(d) {
|
|
1795
|
+
if (d.ok) {
|
|
1796
|
+
// \u4FDD\u7559\u5F53\u524D\u7F16\u8F91\u4E0B\u6807\uFF0C\u4FDD\u5B58\u540E\u8868\u5355\u91CD\u65B0\u52A0\u8F7D\u63A9\u7801\u4E0E instanceId
|
|
1797
|
+
var keepIndex = schedEditIndex >= 0 ? schedEditIndex : schedTargets.length;
|
|
1798
|
+
refreshServerConfig();
|
|
1799
|
+
schedEditIndex = keepIndex;
|
|
1800
|
+
if (restartAfter) {
|
|
1801
|
+
showSchedMsg(t("schedSaved"), true);
|
|
1802
|
+
doRestartBridge();
|
|
1803
|
+
return true;
|
|
1804
|
+
}
|
|
1805
|
+
// \u70ED\u5207\u6362\uFF1A\u4FDD\u5B58\u540E\u7ACB\u5373\u6309\u65B0\u914D\u7F6E\u91CD\u65B0\u6CE8\u518C\uFF08\u7F16\u8F91\u5730\u5740/\u65B0\u589E target \u5747\u5373\u65F6\u751F\u6548\uFF0C
|
|
1806
|
+
// \u65E0\u9700\u91CD\u542F Bridge\uFF1B\u5B9E\u4F8B\u4FE1\u606F\u6309 instanceId \u5728\u670D\u52A1\u7AEF\u590D\u7528\uFF09
|
|
1807
|
+
showSchedMsg(t("schedSavedRegistering"), true);
|
|
1808
|
+
btn.textContent = t("schedReRegistering");
|
|
1809
|
+
return runReRegister().then(function(reg) {
|
|
1810
|
+
if (reg.ok) {
|
|
1811
|
+
showSchedMsg(t("schedReRegistered"), true);
|
|
1812
|
+
refreshNodeInfo();
|
|
1813
|
+
} else {
|
|
1814
|
+
showSchedMsg(t("schedReRegisterFail") + (reg.error ? ": " + reg.error : ""), false);
|
|
1815
|
+
}
|
|
1816
|
+
return reg.ok;
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
showSchedMsg(t("schedSaveFail") + (d.error ? ": " + d.error : ""), false);
|
|
1820
|
+
return false;
|
|
1821
|
+
})
|
|
1822
|
+
.catch(function() {
|
|
1823
|
+
showSchedMsg(t("schedSaveFail"), false);
|
|
1824
|
+
return false;
|
|
1825
|
+
})
|
|
1826
|
+
.then(function(ok) {
|
|
1827
|
+
btn.disabled = false;
|
|
1828
|
+
btn.textContent = t("schedSave");
|
|
1829
|
+
return ok;
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// \u4FDD\u5B58\u5E76\u91CD\u542F\uFF1A\u5148\u786E\u8BA4\uFF0C\u4FDD\u5B58\u6210\u529F\u540E\u7531 saveServerConfig \u89E6\u53D1\u91CD\u542F
|
|
1834
|
+
function saveServerConfigAndRestart() {
|
|
1835
|
+
if (!confirm(t("schedSaveRestartConfirm"))) return;
|
|
1836
|
+
saveServerConfig(true);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// \u5220\u9664\u5F53\u524D\u7F16\u8F91\u7684 target\uFF1A\u4ECE\u5217\u8868\u79FB\u9664\u540E\u5168\u91CF\u63D0\u4EA4
|
|
1840
|
+
function deleteTargetFromList() {
|
|
1841
|
+
if (schedEditIndex < 0 || schedEditIndex >= schedTargets.length) return;
|
|
1842
|
+
if (!confirm(t("schedConfirmDelete"))) return;
|
|
1843
|
+
// \u6355\u83B7\u53D1\u8D77\u65F6\u7684\u4E0B\u6807\uFF1A\u8BF7\u6C42\u671F\u95F4\u7528\u6237\u53EF\u80FD\u5207\u6362\u7F16\u8F91\u9879\uFF0C\u6062\u590D\u65F6\u9700\u7528\u539F\u4E0B\u6807\u63D2\u56DE\u539F\u4F4D
|
|
1844
|
+
var editIdx = schedEditIndex;
|
|
1845
|
+
var removed = schedTargets.splice(editIdx, 1)[0];
|
|
1846
|
+
var list = schedTargets.map(function(item) {
|
|
1847
|
+
return {
|
|
1848
|
+
serverUrl: item.serverUrl,
|
|
1849
|
+
userKey: "",
|
|
1850
|
+
tenantId: item.tenantId || "",
|
|
1851
|
+
instanceName: item.instanceName || "",
|
|
1852
|
+
connectionKey: "",
|
|
1853
|
+
registerAddr: item.registerAddr || "",
|
|
1854
|
+
tunnel: item.tunnel === true ? "true" : (item.tunnel === false ? "false" : "auto"),
|
|
1855
|
+
};
|
|
1856
|
+
});
|
|
1857
|
+
fetch("/runtime/server-config", {
|
|
1858
|
+
method: "POST",
|
|
1859
|
+
headers: { "Content-Type": "application/json" },
|
|
1860
|
+
body: JSON.stringify({ targets: list }),
|
|
1861
|
+
})
|
|
1862
|
+
.then(function(r) { return r.json(); })
|
|
1863
|
+
.then(function(d) {
|
|
1864
|
+
if (d.ok) {
|
|
1865
|
+
startAddTarget();
|
|
1866
|
+
refreshServerConfig();
|
|
1867
|
+
// \u70ED\u5207\u6362\uFF1A\u5220\u9664\u540E\u7ACB\u5373\u91CD\u65B0\u6CE8\u518C\uFF0C\u505C\u6389\u5DF2\u79FB\u9664\u5730\u5740\u7684\u6CE8\u518C\u4E0E\u96A7\u9053
|
|
1868
|
+
showSchedMsg(t("schedSavedRegistering"), true);
|
|
1869
|
+
runReRegister().then(function(reg) {
|
|
1870
|
+
if (reg.ok) {
|
|
1871
|
+
showSchedMsg(t("schedReRegistered"), true);
|
|
1872
|
+
refreshNodeInfo();
|
|
1873
|
+
} else {
|
|
1874
|
+
showSchedMsg(t("schedReRegisterFail") + (reg.error ? ": " + reg.error : ""), false);
|
|
1875
|
+
}
|
|
1876
|
+
});
|
|
1877
|
+
} else {
|
|
1878
|
+
schedTargets.splice(editIdx, 0, removed);
|
|
1879
|
+
renderSchedTargetList();
|
|
1880
|
+
showSchedMsg(t("schedSaveFail") + (d.error ? ": " + d.error : ""), false);
|
|
1881
|
+
}
|
|
1882
|
+
})
|
|
1883
|
+
.catch(function() {
|
|
1884
|
+
schedTargets.splice(editIdx, 0, removed);
|
|
1885
|
+
renderSchedTargetList();
|
|
1886
|
+
showSchedMsg(t("schedSaveFail"), false);
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// \u53D1\u8D77\u91CD\u65B0\u6CE8\u518C\u8BF7\u6C42\uFF08\u4E0D\u7BA1\u7406\u6309\u94AE\u72B6\u6001\uFF0C\u4F9B\u624B\u52A8\u6309\u94AE\u4E0E\u4FDD\u5B58\u540E\u7684\u70ED\u5207\u6362\u94FE\u5171\u7528\uFF09
|
|
1891
|
+
function runReRegister() {
|
|
1892
|
+
return fetch("/runtime/re-register", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
1893
|
+
.then(function(r) { return r.json(); })
|
|
1894
|
+
.catch(function() { return { ok: false }; });
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1431
1897
|
function doReRegister() {
|
|
1432
1898
|
var btn = document.getElementById("btnReRegister");
|
|
1433
1899
|
var msg = document.getElementById("schedMsg");
|
|
@@ -1435,8 +1901,7 @@ function doReRegister() {
|
|
|
1435
1901
|
msg.style.display = "none";
|
|
1436
1902
|
btn.disabled = true;
|
|
1437
1903
|
btn.textContent = t("schedReRegistering");
|
|
1438
|
-
|
|
1439
|
-
.then(function(r) { return r.json(); })
|
|
1904
|
+
runReRegister()
|
|
1440
1905
|
.then(function(d) {
|
|
1441
1906
|
if (d.ok) {
|
|
1442
1907
|
msg.textContent = t("schedReRegistered");
|
|
@@ -1447,13 +1912,52 @@ function doReRegister() {
|
|
|
1447
1912
|
msg.className = "s-msg error";
|
|
1448
1913
|
}
|
|
1449
1914
|
})
|
|
1915
|
+
.then(function() {
|
|
1916
|
+
btn.disabled = false;
|
|
1917
|
+
btn.textContent = t("schedReRegister");
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// \u2500\u2500 Access Control \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1922
|
+
// \u52A0\u8F7D\u5F53\u524D IP \u767D\u540D\u5355\u5230\u8868\u5355\uFF08\u7A7A\u5217\u8868 = \u9ED8\u8BA4\u5168\u653E\u884C\uFF09
|
|
1923
|
+
function refreshAccessControl() {
|
|
1924
|
+
fetch("/runtime/access-control").then(function(r) { return r.json(); }).then(function(d) {
|
|
1925
|
+
if (!d.ok) return;
|
|
1926
|
+
document.getElementById("cfgAllowIps").value = (d.allowIps || []).join("\\n");
|
|
1927
|
+
}).catch(function() {});
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// \u4FDD\u5B58\u767D\u540D\u5355\uFF08\u540E\u7AEF\u6821\u9A8C\u5408\u6CD5\u6027\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF09
|
|
1931
|
+
function saveAccessControl() {
|
|
1932
|
+
var msg = document.getElementById("accessMsg");
|
|
1933
|
+
var btn = document.getElementById("btnSaveAccessControl");
|
|
1934
|
+
msg.className = "s-msg";
|
|
1935
|
+
msg.style.display = "none";
|
|
1936
|
+
btn.disabled = true;
|
|
1937
|
+
btn.textContent = t("accSaving");
|
|
1938
|
+
fetch("/runtime/access-control", {
|
|
1939
|
+
method: "POST",
|
|
1940
|
+
headers: { "Content-Type": "application/json" },
|
|
1941
|
+
body: JSON.stringify({ allowIps: document.getElementById("cfgAllowIps").value }),
|
|
1942
|
+
})
|
|
1943
|
+
.then(function(r) { return r.json(); })
|
|
1944
|
+
.then(function(d) {
|
|
1945
|
+
if (d.ok) {
|
|
1946
|
+
msg.textContent = t("accSaved");
|
|
1947
|
+
msg.className = "s-msg success";
|
|
1948
|
+
document.getElementById("cfgAllowIps").value = (d.allowIps || []).join("\\n");
|
|
1949
|
+
} else {
|
|
1950
|
+
msg.textContent = d.error || t("accSaveFail");
|
|
1951
|
+
msg.className = "s-msg error";
|
|
1952
|
+
}
|
|
1953
|
+
})
|
|
1450
1954
|
.catch(function() {
|
|
1451
|
-
msg.textContent = t("
|
|
1955
|
+
msg.textContent = t("accSaveFail");
|
|
1452
1956
|
msg.className = "s-msg error";
|
|
1453
1957
|
})
|
|
1454
1958
|
.then(function() {
|
|
1455
1959
|
btn.disabled = false;
|
|
1456
|
-
btn.textContent = t("
|
|
1960
|
+
btn.textContent = t("accSave");
|
|
1457
1961
|
});
|
|
1458
1962
|
}
|
|
1459
1963
|
|
|
@@ -1498,11 +2002,11 @@ switchSettingsPage = function(page) {
|
|
|
1498
2002
|
if (nodeInfoTimer) { clearTimeout(nodeInfoTimer); nodeInfoTimer = null; }
|
|
1499
2003
|
if (page === "node") { refreshNodeInfo(); }
|
|
1500
2004
|
else if (page === "logs") { refreshLogs(); startLogAutoRefresh(); }
|
|
1501
|
-
else if (page === "scheduler") { refreshNodeInfo(); }
|
|
2005
|
+
else if (page === "scheduler") { refreshNodeInfo(); refreshServerConfig(); }
|
|
1502
2006
|
};
|
|
1503
2007
|
</script>
|
|
1504
2008
|
</body>
|
|
1505
|
-
</html>`,
|
|
2009
|
+
</html>`,Ee=`<!DOCTYPE html>
|
|
1506
2010
|
<html lang="zh-CN">
|
|
1507
2011
|
<head>
|
|
1508
2012
|
<meta charset="UTF-8">
|
|
@@ -1673,7 +2177,7 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
|
|
|
1673
2177
|
});
|
|
1674
2178
|
</script>
|
|
1675
2179
|
</body>
|
|
1676
|
-
</html>`,
|
|
2180
|
+
</html>`,Te=`<!DOCTYPE html>
|
|
1677
2181
|
<html lang="zh-CN">
|
|
1678
2182
|
<head>
|
|
1679
2183
|
<meta charset="UTF-8">
|
|
@@ -1960,5 +2464,5 @@ async function doSkip() {
|
|
|
1960
2464
|
document.getElementById("setupForm").addEventListener("submit", doSave);
|
|
1961
2465
|
</script>
|
|
1962
2466
|
</body>
|
|
1963
|
-
</html>`;export{
|
|
2467
|
+
</html>`;export{g as dashboardRouter};
|
|
1964
2468
|
|