aws-runtime-bridge 1.9.122 → 1.9.125
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/README.md +23 -2
- package/dist/adapter/AcodeSdkAdapter.js +1 -1
- package/dist/adapter/types.js +1 -1
- package/dist/browser/browser-ws-bridge.js +3 -3
- package/dist/config.d.ts +22 -2
- package/dist/config.js +1 -1
- package/dist/index.js +1 -1
- package/dist/remote-desktop/index.js +2 -2
- package/dist/routes/dashboard.js +576 -17
- package/dist/routes/git.d.ts +28 -0
- package/dist/routes/git.js +32 -31
- package/dist/routes/pty.js +3 -3
- package/dist/routes/runtime-binding.js +1 -1
- package/dist/routes/terminal.js +24 -24
- 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.d.ts +2 -2
- package/dist/services/auto-register.js +1 -1
- package/dist/services/aws-client-agent-mcp.d.ts +2 -1
- package/dist/services/aws-client-agent-mcp.js +2 -2
- package/dist/services/cc-switch-sdk.js +1 -1
- package/dist/services/cli-commands.js +5 -5
- package/dist/services/fileSnapshotStore.js +1 -1
- package/dist/services/instance-ai-config-service.js +1 -1
- package/dist/services/instance-features.d.ts +2 -0
- package/dist/services/instance-features.js +1 -1
- package/dist/services/panel-auth.d.ts +3 -1
- package/dist/services/panel-auth.js +1 -1
- package/dist/services/startup-config-wizard.js +6 -6
- 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/services/update-notifier.js +2 -2
- package/dist/utils/config-write-queue.d.ts +3 -0
- package/dist/utils/config-write-queue.js +2 -0
- package/package/acode/dist/ask-user-question-tool.js +2 -2
- package/package/acode/dist/mcpToolNames.d.ts +2 -0
- package/package/acode/dist/mcpToolNames.js +1 -1
- package/package/acode/dist/meeting-debug-log.d.ts +1 -0
- package/package/acode/dist/meeting-debug-log.js +3 -0
- package/package/acode/dist/runtime.d.ts +2 -0
- package/package/acode/dist/runtime.js +12 -12
- package/package/aws-client-agent-mcp/dist/agent-client.d.ts +12 -0
- package/package/aws-client-agent-mcp/dist/agent-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/agent-client.test.js +179 -1
- package/package/aws-client-agent-mcp/dist/config.js +1 -1
- package/package/aws-client-agent-mcp/dist/http-client.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/http-client.js +1 -1
- package/package/aws-client-agent-mcp/dist/launch-config-tools.test.js +6 -2
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +21 -0
- package/package/aws-client-agent-mcp/dist/mcp-server.js +28 -28
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +604 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +10 -0
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +12 -10
- package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +38 -0
- package/package/aws-client-agent-mcp/dist/meeting-debug-log.d.ts +13 -0
- package/package/aws-client-agent-mcp/dist/meeting-debug-log.js +3 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +2 -0
- package/package/aws-client-agent-mcp/dist/message-buffer.js +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.test.js +60 -0
- package/package/aws-client-agent-mcp/dist/types.d.ts +11 -1
- package/package/aws-client-agent-mcp/dist/websocket-client.js +1 -1
- 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 I 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,getRuntimeHomeDir as se,getUserHomeDir as re}from"../config.js";import{runExclusiveConfigWrite as O}from"../utils/config-write-queue.js";import{sdkSessions as ae}from"./terminal.js";import{createSession as oe,getPanelCredentials as N,validateSession as U,destroySession as z,validateCredentials as F,hasConfiguredCredentials as B,isDefaultCredentials as ie,setPanelCredentials as de,getAllSessions as le,getActiveSessionCount as ce,checkLoginAllowed as ue,recordLoginFailure as ge,recordLoginSuccess as pe}from"../services/panel-auth.js";import{getRecentLogs as fe}from"../utils/logger.js";import{autoRegister as me,getRegistrationState as _,getAutoRegisterConfigFilePath as E}from"../services/auto-register.js";import{getAccessControlEntries as be,isValidIpEntry as he,parseAllowIps as ve,refreshAccessControlCache as ye}from"../services/access-control.js";const x=te("dashboard");let T=null;const xe=1e4;let L=null,P=null;const we=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,we);const V=ne();async function Se(){const t=await import("node:fs/promises"),n=(await import("node:path")).join(se(),".aws-bridge","lifecycle-state.json");try{const s=await t.readFile(n,"utf8"),i=JSON.parse(s),r=Array.isArray(i.operations)?i.operations:[];return r.length===0?null:(r.sort((a,o)=>new Date(o.updatedAt).getTime()-new Date(a.updatedAt).getTime()),r[0]||null)}catch{return null}}const g=$(),J="panel_sid";function Ie(t){const e={};if(!t)return e;for(const n of t.split(";")){const s=n.indexOf("=");if(s===-1)continue;const i=n.slice(0,s).trim(),r=n.slice(s+1).trim();i&&(e[i]=r)}return e}function A(t){return Ie(t.headers.cookie)[J]}function q(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=ue(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:i,password:r}=t.body||{};if(!i||!r){e.status(400).json({ok:!1,error:"username and password required"});return}if(!F(String(i),String(r))){ge(n),e.status(401).json({ok:!1,error:"invalid credentials"});return}pe(n);const a=oe(),o=!B();e.setHeader("Set-Cookie",q(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",q(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:ie(),configured:B(),username:n.username})}),g.post("/runtime/change-credentials",async(t,e)=>{if(!m(t,e))return;const{username:n,password:s,passwordConfirm:i,oldPassword:r}=t.body||{};if(s&&String(s).trim()){if(String(s)!==String(i)){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 de(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 W(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 ke=["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","projectId","roleName","workspacePath","registerAddr","registerIp","virtualIp","tunnel","preferSameLanIp","maxRetries","retryInterval"];function Ce(t){const e={};for(const n of ke)t[n]!==void 0&&(e[n]=t[n]);return e}function Be(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:W(t.userKey),userKeySet:!!String(t.userKey??"").trim(),connectionKeyMasked:W(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 i=String(s.connectionKey??"").trim()||void 0,r=G(s).map(a=>Be(a,i));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 d=new URL(l);if(!["http:","https:"].includes(d.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 h=String(o.originalServerUrl??"").trim();s.push({serverUrl:l,originalServerUrl:h&&h!==l?h:"",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 i=new Set;for(const r of s){const a=R(r.serverUrl);if(i.has(a)){e.status(400).json({ok:!1,error:"duplicate serverUrl in targets"});return}i.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(d=>[R(String(d.serverUrl??d.schedulerBaseUrl??"").trim()),f?Ce(d):d])),h=[];for(const d of s){let b;if(d.originalServerUrl){const w=R(d.originalServerUrl);b=p.get(w),b&&p.delete(w)}if(!b){const w=R(d.serverUrl);b=p.get(w),b&&p.delete(w)}const c=b?{...b}:{};if(c.serverUrl=d.serverUrl,c.tenantId=d.tenantId,c.instanceName=d.instanceName,c.registerAddr=d.registerAddr,d.tunnel===void 0?delete c.tunnel:c.tunnel=d.tunnel,d.userKey)c.userKey=d.userKey;else if(!String(c.userKey??"").trim())throw M(400,`userKey is required for ${d.serverUrl} (new target without inheritable userKey)`);d.connectionKey?c.connectionKey=d.connectionKey:!String(c.connectionKey??"").trim()&&o&&(c.connectionKey=o),h.push(c)}a.autoRegisterTargets=h;for(const d of["enabled","serverUrl","schedulerBaseUrl","tenantId","userKey","connectionKey","password","instanceName","instanceId","registerAddr","registerIp","virtualIp","tunnel"])delete a[d];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:be(),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=ve(n),i=s.filter(r=>!he(r));if(i.length>0){e.status(400).json({ok:!1,error:`invalid IP/CIDR entries: ${i.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"),ye()}),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:I.hostname(),platform:I.platform(),arch:I.arch(),uptime:Math.floor(process.uptime()),nodeVersion:process.version,bridgeVersion:V,configPath:E(),homeDir:re(),bindingStatus:n.status,paired:n.paired,instanceId:n.instanceId||s.instanceId,schedulerBaseUrl:n.schedulerBaseUrl,registered:s.registered,pairingCode:n.pairingCode,activeSessions:ce()})}),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=fe(s,r);e.json({ok:!0,logs:a})}),g.post("/runtime/re-register",async(t,e)=>{if(m(t,e))try{const n=await me();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=le().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 Ee(t){return new Promise((e,n)=>{const s=I.platform()==="win32",i=s?"taskkill":"kill",r=s?["/PID",String(t),"/F"]:["-9",String(t)];Y(i,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(),i=()=>{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=i();if(r.size===0)try{P={data:await s.discoverAllProcesses(),ts:Date.now()},r=i()}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 Ee(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<xe)return void e.json(T.data);try{const n=L?.data??null,s=P?.data??null;L||D();const[i,r,a]=await Promise.all([Promise.resolve(H()),Promise.resolve(_()),ee().catch(()=>[])]),l=C().getAllManagedProcesses(),f=Array.from(ae.entries()).map(([S,y])=>({sessionId:S,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},h=[];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 S=new Map(l.map(u=>[u.pid,u]));h=s.map(u=>{const k=S.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};h.sort((u,k)=>(y[u.managementStatus]??9)-(y[k.managementStatus]??9))}const d=new Set(f.map(S=>S.sessionId)),b=a.filter(S=>!d.has(S.sessionId)),c=i,w=await Se(),j={ok:!0,collectedAt:new Date().toISOString(),bridge:{version:V,hostname:I.hostname(),platform:I.platform(),arch:I.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:b,persistedCount:b.length,total:f.length+b.length},processes:{...p,total:p.managed+p.orphan+p.unknown,list:h,registryCount:l.length},lifecycleOperation:w};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(Pe)}),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(Te.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(Ae)});const Te=`<!DOCTYPE html>
|
|
2
2
|
<html lang="{{LANG}}">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
@@ -280,6 +280,61 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
280
280
|
th, td { padding: 7px 10px; }
|
|
281
281
|
.header h1 { font-size: 18px; }
|
|
282
282
|
}
|
|
283
|
+
|
|
284
|
+
/* ===== Scrollbar (\u4E3B\u9898\u9002\u914D) ===== */
|
|
285
|
+
/* \u9875\u9762\u7EA7\u6EDA\u52A8\u6761\uFF1Apill \u5F62\u6001\uFF08\u8FB9\u6846 + background-clip\uFF09\uFF0C\u4E0E\u6DF1\u8272\u80CC\u666F\u878D\u4E3A\u4E00\u4F53 */
|
|
286
|
+
html {
|
|
287
|
+
scrollbar-width: thin;
|
|
288
|
+
scrollbar-color: color-mix(in srgb, var(--accent) 30%, transparent) var(--bg);
|
|
289
|
+
}
|
|
290
|
+
html::-webkit-scrollbar {
|
|
291
|
+
width: 10px;
|
|
292
|
+
background: var(--bg);
|
|
293
|
+
}
|
|
294
|
+
html::-webkit-scrollbar-thumb {
|
|
295
|
+
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
296
|
+
border: 2px solid var(--bg);
|
|
297
|
+
border-radius: 5px;
|
|
298
|
+
background-clip: content-box;
|
|
299
|
+
}
|
|
300
|
+
html::-webkit-scrollbar-thumb:hover {
|
|
301
|
+
background: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
302
|
+
background-clip: content-box;
|
|
303
|
+
}
|
|
304
|
+
/* \u5185\u90E8\u6EDA\u52A8\u5BB9\u5668\uFF08\u8868\u683C\u3001\u8BBE\u7F6E\u9762\u677F\u3001\u65E5\u5FD7\u5217\u8868\uFF09\uFF1A6px \u900F\u660E\u8F68\u9053 + accent \u80F6\u56CA\u6ED1\u5757 */
|
|
305
|
+
.table-wrap,
|
|
306
|
+
.settings-content,
|
|
307
|
+
.s-log-list,
|
|
308
|
+
.s-table-wrap {
|
|
309
|
+
scrollbar-width: thin;
|
|
310
|
+
scrollbar-color: color-mix(in srgb, var(--accent) 30%, transparent) transparent;
|
|
311
|
+
}
|
|
312
|
+
.table-wrap::-webkit-scrollbar,
|
|
313
|
+
.settings-content::-webkit-scrollbar,
|
|
314
|
+
.s-log-list::-webkit-scrollbar,
|
|
315
|
+
.s-table-wrap::-webkit-scrollbar {
|
|
316
|
+
width: 6px;
|
|
317
|
+
height: 6px;
|
|
318
|
+
}
|
|
319
|
+
.table-wrap::-webkit-scrollbar-track,
|
|
320
|
+
.settings-content::-webkit-scrollbar-track,
|
|
321
|
+
.s-log-list::-webkit-scrollbar-track,
|
|
322
|
+
.s-table-wrap::-webkit-scrollbar-track {
|
|
323
|
+
background: transparent;
|
|
324
|
+
}
|
|
325
|
+
.table-wrap::-webkit-scrollbar-thumb,
|
|
326
|
+
.settings-content::-webkit-scrollbar-thumb,
|
|
327
|
+
.s-log-list::-webkit-scrollbar-thumb,
|
|
328
|
+
.s-table-wrap::-webkit-scrollbar-thumb {
|
|
329
|
+
background: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
330
|
+
border-radius: 3px;
|
|
331
|
+
}
|
|
332
|
+
.table-wrap::-webkit-scrollbar-thumb:hover,
|
|
333
|
+
.settings-content::-webkit-scrollbar-thumb:hover,
|
|
334
|
+
.s-log-list::-webkit-scrollbar-thumb:hover,
|
|
335
|
+
.s-table-wrap::-webkit-scrollbar-thumb:hover {
|
|
336
|
+
background: color-mix(in srgb, var(--accent) 50%, transparent);
|
|
337
|
+
}
|
|
283
338
|
</style>
|
|
284
339
|
</head>
|
|
285
340
|
<body>
|
|
@@ -295,6 +350,7 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
295
350
|
</span>
|
|
296
351
|
<span class="lang-btn" id="langBtn" onclick="toggleLang()">\u4E2D\u6587</span>
|
|
297
352
|
<button onclick="openSettings()" id="settingsBtn" title="Settings / \u8BBE\u7F6E">⚙</button>
|
|
353
|
+
<button onclick="doLogout()" id="logoutBtn" title="Logout / \u9000\u51FA\u767B\u5F55">⏻</button>
|
|
298
354
|
<button onclick="refresh()" id="refreshBtn">↻</button>
|
|
299
355
|
</div>
|
|
300
356
|
</div>
|
|
@@ -351,6 +407,7 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
351
407
|
<div class="settings-nav-item" data-page="node" onclick="switchSettingsPage('node')"><span id="navNode"></span></div>
|
|
352
408
|
<div class="settings-nav-item" data-page="logs" onclick="switchSettingsPage('logs')"><span id="navLogs"></span></div>
|
|
353
409
|
<div class="settings-nav-item" data-page="scheduler" onclick="switchSettingsPage('scheduler')"><span id="navScheduler"></span></div>
|
|
410
|
+
<div class="settings-nav-item" data-page="access" onclick="switchSettingsPage('access')"><span id="navAccess"></span></div>
|
|
354
411
|
<div class="settings-nav-item" data-page="maintenance" onclick="switchSettingsPage('maintenance')"><span id="navMaintenance"></span></div>
|
|
355
412
|
</div>
|
|
356
413
|
<div class="settings-content">
|
|
@@ -417,15 +474,83 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
417
474
|
<!-- Scheduler page -->
|
|
418
475
|
<div class="settings-page" id="pageScheduler">
|
|
419
476
|
<div class="s-field">
|
|
420
|
-
<label id="
|
|
421
|
-
<div class="s-
|
|
477
|
+
<label id="lblSchedTargets"></label>
|
|
478
|
+
<div class="s-target-list" id="schedTargetList"></div>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="s-actions s-actions-row" style="margin-bottom:18px">
|
|
481
|
+
<button class="s-btn s-btn-secondary" id="btnAddTarget" onclick="startAddTarget()"></button>
|
|
482
|
+
</div>
|
|
483
|
+
<div class="s-target-form" id="targetForm">
|
|
484
|
+
<div class="s-field">
|
|
485
|
+
<label id="lblSchedUrl"></label>
|
|
486
|
+
<input type="text" id="cfgServerUrl" placeholder="http://192.168.1.100:8080" autocomplete="off">
|
|
487
|
+
<span class="s-hint" id="hintSchedUrl"></span>
|
|
488
|
+
</div>
|
|
489
|
+
<div class="s-field">
|
|
490
|
+
<label id="lblSchedUserKey"></label>
|
|
491
|
+
<input type="password" id="cfgUserKey" autocomplete="new-password">
|
|
492
|
+
<span class="s-hint" id="hintSchedUserKey"></span>
|
|
493
|
+
</div>
|
|
494
|
+
<div class="s-field">
|
|
495
|
+
<label id="lblSchedTenantId"></label>
|
|
496
|
+
<input type="text" id="cfgTenantId" autocomplete="off">
|
|
497
|
+
<span class="s-hint" id="hintSchedTenantId"></span>
|
|
498
|
+
</div>
|
|
499
|
+
<div class="s-field">
|
|
500
|
+
<label id="lblSchedInstanceName"></label>
|
|
501
|
+
<input type="text" id="cfgInstanceName" autocomplete="off">
|
|
502
|
+
<span class="s-hint" id="hintSchedInstanceName"></span>
|
|
503
|
+
</div>
|
|
504
|
+
<div class="s-field">
|
|
505
|
+
<label id="lblSchedConnKey"></label>
|
|
506
|
+
<input type="password" id="cfgConnectionKey" autocomplete="new-password">
|
|
507
|
+
<span class="s-hint" id="hintSchedConnKey"></span>
|
|
508
|
+
</div>
|
|
509
|
+
<div class="s-field">
|
|
510
|
+
<label id="lblSchedRegisterAddr"></label>
|
|
511
|
+
<input type="text" id="cfgRegisterAddr" placeholder="192.168.1.25:18081" autocomplete="off">
|
|
512
|
+
<span class="s-hint" id="hintSchedRegisterAddr"></span>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="s-field">
|
|
515
|
+
<label id="lblSchedTunnel"></label>
|
|
516
|
+
<select id="cfgTunnel">
|
|
517
|
+
<option value="auto" id="optTunnelAuto"></option>
|
|
518
|
+
<option value="true" id="optTunnelOn"></option>
|
|
519
|
+
<option value="false" id="optTunnelOff"></option>
|
|
520
|
+
</select>
|
|
521
|
+
<span class="s-hint" id="hintSchedTunnel"></span>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="s-field">
|
|
524
|
+
<div class="s-info-row"><span class="s-info-label" id="lblSchedInstanceId"></span><span class="s-info-value" id="valSchedInstanceId">-</span></div>
|
|
525
|
+
<span class="s-hint" id="hintSchedInstanceId"></span>
|
|
526
|
+
</div>
|
|
527
|
+
<div class="s-msg" id="schedMsg"></div>
|
|
528
|
+
<div class="s-actions s-actions-row">
|
|
529
|
+
<button class="s-btn s-btn-primary" id="btnSaveServerConfig" onclick="saveServerConfig(false)"></button>
|
|
530
|
+
<button class="s-btn s-btn-danger" id="btnDeleteTarget" onclick="deleteTargetFromList()"></button>
|
|
531
|
+
</div>
|
|
422
532
|
</div>
|
|
423
533
|
<div class="s-field">
|
|
424
534
|
<p class="s-desc" id="descSched"></p>
|
|
425
535
|
</div>
|
|
426
|
-
<div class="s-
|
|
536
|
+
<div class="s-actions s-actions-row">
|
|
537
|
+
<button class="s-btn s-btn-danger" id="btnSaveRestart" onclick="saveServerConfigAndRestart()"></button>
|
|
538
|
+
<button class="s-btn s-btn-secondary" id="btnReRegister" onclick="doReRegister()"></button>
|
|
539
|
+
</div>
|
|
540
|
+
</div>
|
|
541
|
+
<!-- Access control page -->
|
|
542
|
+
<div class="settings-page" id="pageAccess">
|
|
543
|
+
<div class="s-field">
|
|
544
|
+
<label id="lblAccessIps"></label>
|
|
545
|
+
<textarea id="cfgAllowIps" rows="4" placeholder="0.0.0.0" autocomplete="off" spellcheck="false"></textarea>
|
|
546
|
+
<span class="s-hint" id="hintAccessIps"></span>
|
|
547
|
+
</div>
|
|
548
|
+
<div class="s-msg" id="accessMsg"></div>
|
|
427
549
|
<div class="s-actions">
|
|
428
|
-
<button class="s-btn s-btn-primary" id="
|
|
550
|
+
<button class="s-btn s-btn-primary" id="btnSaveAccessControl" onclick="saveAccessControl()"></button>
|
|
551
|
+
</div>
|
|
552
|
+
<div class="s-field" style="margin-top:18px">
|
|
553
|
+
<p class="s-desc" id="descAccess"></p>
|
|
429
554
|
</div>
|
|
430
555
|
</div>
|
|
431
556
|
<!-- Maintenance page -->
|
|
@@ -478,6 +603,21 @@ import{Router as F}from"express";import p from"node:os";import{execFile as _}fro
|
|
|
478
603
|
.s-field label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 6px; color: var(--text2); }
|
|
479
604
|
.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
605
|
.s-field input:focus { border-color: var(--accent); }
|
|
606
|
+
.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; }
|
|
607
|
+
.s-field select:focus { border-color: var(--accent); }
|
|
608
|
+
.s-field select option { background: var(--surface2); color: var(--text); }
|
|
609
|
+
.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; }
|
|
610
|
+
.s-field textarea:focus { border-color: var(--accent); }
|
|
611
|
+
.s-actions-row { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
612
|
+
.s-target-list { display: flex; flex-direction: column; gap: 8px; }
|
|
613
|
+
.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; }
|
|
614
|
+
.s-target-item.active { border-color: var(--accent); }
|
|
615
|
+
.s-target-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
616
|
+
.s-target-url { font-size: 14px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
617
|
+
.s-target-meta { font-size: 12px; opacity: 0.7; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
618
|
+
.s-target-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
619
|
+
.s-target-form { border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 18px; background: var(--surface); }
|
|
620
|
+
.s-target-empty { padding: 14px; text-align: center; font-size: 13px; opacity: 0.6; border: 1px dashed var(--border); border-radius: 6px; }
|
|
481
621
|
.s-hint { display: block; font-size: 11px; color: var(--text2); margin-top: 4px; }
|
|
482
622
|
.s-msg { padding: 8px 12px; border-radius: 6px; font-size: 13px; margin-bottom: 14px; display: none; }
|
|
483
623
|
.s-msg.error { display: block; background: rgba(240,84,84,0.12); color: var(--red); }
|
|
@@ -649,12 +789,53 @@ const T = {
|
|
|
649
789
|
logEmpty: "\u6682\u65E0\u65E5\u5FD7",
|
|
650
790
|
// \u8C03\u5EA6\u4E2D\u5FC3
|
|
651
791
|
schedTitle: "\u8C03\u5EA6\u4E2D\u5FC3",
|
|
652
|
-
|
|
792
|
+
schedTargets: "\u8C03\u5EA6\u4E2D\u5FC3\u5217\u8868\uFF08\u652F\u6301\u591A\u4E2A\uFF0C\u4FDD\u5B58\u540E\u5E76\u884C\u8FDE\u63A5\uFF09",
|
|
793
|
+
schedNoTargets: "\u5C1A\u672A\u914D\u7F6E\uFF0C\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\u6DFB\u52A0",
|
|
794
|
+
schedAddTarget: "\uFF0B \u6DFB\u52A0\u8C03\u5EA6\u4E2D\u5FC3",
|
|
795
|
+
schedEditTarget: "\u7F16\u8F91",
|
|
796
|
+
schedDeleteTarget: "\u5220\u9664",
|
|
797
|
+
schedConfirmDelete: "\u5220\u9664\u8BE5\u8C03\u5EA6\u4E2D\u5FC3\u914D\u7F6E\uFF1F",
|
|
798
|
+
schedFormNew: "\u65B0\u589E\u8C03\u5EA6\u4E2D\u5FC3",
|
|
799
|
+
schedUrl: "\u670D\u52A1\u5730\u5740 (serverUrl)",
|
|
800
|
+
schedUrlHint: "\u5FC5\u586B\uFF0C\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF1B\u5730\u5740\u53D8\u66F4\u65F6 API Key \u4F1A\u81EA\u52A8\u6CBF\u7528",
|
|
801
|
+
schedUserKey: "\u7528\u6237 API Key (userKey)",
|
|
802
|
+
schedUserKeyHint: "\u5FC5\u586B\uFF1B\u5DF2\u914D\u7F6E\u65F6\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u4E0D\u53D8",
|
|
803
|
+
schedTenantId: "\u79DF\u6237 ID\uFF08\u53EF\u9009\uFF09",
|
|
804
|
+
schedTenantIdHint: "\u7559\u7A7A\u7531 server \u6839\u636E userKey \u81EA\u52A8\u63A8\u5BFC",
|
|
805
|
+
schedInstanceName: "\u5B9E\u4F8B\u540D\u79F0\uFF08\u53EF\u9009\uFF09",
|
|
806
|
+
schedInstanceNameHint: "\u7559\u7A7A\u4F7F\u7528\u4E3B\u673A\u540D",
|
|
807
|
+
schedConnKey: "\u8FDE\u63A5\u5BC6\u94A5\uFF08\u53EF\u9009\uFF09",
|
|
808
|
+
schedConnKeyHint: "server \u8FDE\u63A5\u8BE5 bridge \u7684\u5BC6\u94A5\uFF1B\u5DF2\u914D\u7F6E\u65F6\u7559\u7A7A\u8868\u793A\u4FDD\u6301\u4E0D\u53D8",
|
|
809
|
+
schedRegisterAddr: "\u6CE8\u518C\u5730\u5740\uFF08\u53EF\u9009\uFF09",
|
|
810
|
+
schedRegisterAddrHint: "\u76F4\u8FDE\u6A21\u5F0F\u4E0B\u7684\u5730\u5740\uFF0C\u5982 192.168.1.25:18081\uFF1B\u6E05\u7A7A\u8868\u793A\u4E0D\u6307\u5B9A",
|
|
811
|
+
schedTunnel: "\u96A7\u9053\u6A21\u5F0F",
|
|
812
|
+
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",
|
|
813
|
+
schedTunnelAuto: "\u81EA\u52A8",
|
|
814
|
+
schedTunnelOn: "\u542F\u7528",
|
|
815
|
+
schedTunnelOff: "\u7981\u7528",
|
|
816
|
+
schedInstanceId: "\u5B9E\u4F8B ID",
|
|
817
|
+
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",
|
|
818
|
+
schedSave: "\u4FDD\u5B58\u914D\u7F6E",
|
|
819
|
+
schedSaving: "\u4FDD\u5B58\u4E2D...",
|
|
820
|
+
schedSaved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58",
|
|
821
|
+
schedSavedRegistering: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u6B63\u5728\u91CD\u65B0\u6CE8\u518C...",
|
|
822
|
+
schedSaveFail: "\u4FDD\u5B58\u5931\u8D25",
|
|
823
|
+
schedSaveAndRestart: "\u4FDD\u5B58\u5E76\u91CD\u542F",
|
|
824
|
+
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
825
|
schedReRegister: "\u91CD\u65B0\u6CE8\u518C",
|
|
654
826
|
schedReRegistering: "\u6CE8\u518C\u4E2D...",
|
|
655
827
|
schedReRegistered: "\u91CD\u65B0\u6CE8\u518C\u6210\u529F",
|
|
656
828
|
schedReRegisterFail: "\u91CD\u65B0\u6CE8\u518C\u5931\u8D25",
|
|
657
|
-
schedDesc: "\
|
|
829
|
+
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",
|
|
830
|
+
// \u8BBF\u95EE\u63A7\u5236
|
|
831
|
+
accTitle: "\u8BBF\u95EE\u63A7\u5236",
|
|
832
|
+
accAllowIps: "\u5141\u8BB8\u8BBF\u95EE\u7684 IP / \u7F51\u6BB5",
|
|
833
|
+
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",
|
|
834
|
+
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",
|
|
835
|
+
accSave: "\u4FDD\u5B58",
|
|
836
|
+
accSaving: "\u4FDD\u5B58\u4E2D...",
|
|
837
|
+
accSaved: "\u5DF2\u4FDD\u5B58\u5E76\u751F\u6548",
|
|
838
|
+
accSaveFail: "\u4FDD\u5B58\u5931\u8D25",
|
|
658
839
|
// \u72B6\u6001\u6587\u672C
|
|
659
840
|
stHealthy: "\u5065\u5EB7",
|
|
660
841
|
stError: "\u5F02\u5E38",
|
|
@@ -778,12 +959,53 @@ const T = {
|
|
|
778
959
|
logEmpty: "No logs yet",
|
|
779
960
|
// Scheduler
|
|
780
961
|
schedTitle: "Scheduler",
|
|
781
|
-
|
|
962
|
+
schedTargets: "Scheduler targets (multiple allowed, connected in parallel)",
|
|
963
|
+
schedNoTargets: "None configured. Click the button below to add one.",
|
|
964
|
+
schedAddTarget: "+ Add scheduler",
|
|
965
|
+
schedEditTarget: "Edit",
|
|
966
|
+
schedDeleteTarget: "Delete",
|
|
967
|
+
schedConfirmDelete: "Delete this scheduler configuration?",
|
|
968
|
+
schedFormNew: "New scheduler",
|
|
969
|
+
schedUrl: "Server URL (serverUrl)",
|
|
970
|
+
schedUrlHint: "Required, must start with http:// or https://; the API key is kept when the URL changes",
|
|
971
|
+
schedUserKey: "User API Key (userKey)",
|
|
972
|
+
schedUserKeyHint: "Required; leave blank to keep the configured value",
|
|
973
|
+
schedTenantId: "Tenant ID (optional)",
|
|
974
|
+
schedTenantIdHint: "Leave blank to auto-derive from userKey on the server",
|
|
975
|
+
schedInstanceName: "Instance name (optional)",
|
|
976
|
+
schedInstanceNameHint: "Leave blank to use the hostname",
|
|
977
|
+
schedConnKey: "Connection key (optional)",
|
|
978
|
+
schedConnKeyHint: "Key for the server to connect to this bridge; leave blank to keep the configured value",
|
|
979
|
+
schedRegisterAddr: "Register address (optional)",
|
|
980
|
+
schedRegisterAddrHint: "Address in direct mode, e.g. 192.168.1.25:18081; clear to unset",
|
|
981
|
+
schedTunnel: "Tunnel mode",
|
|
982
|
+
schedTunnelHint: "Auto: tunnel is enabled when no register address is set; On: reverse WebSocket connection to the server; Off: direct connection",
|
|
983
|
+
schedTunnelAuto: "Auto",
|
|
984
|
+
schedTunnelOn: "On",
|
|
985
|
+
schedTunnelOff: "Off",
|
|
986
|
+
schedInstanceId: "Instance ID",
|
|
987
|
+
schedInstanceIdHint: "Inherited automatically when editing the URL (same instance, new address); backfilled after a new target registers successfully",
|
|
988
|
+
schedSave: "Save config",
|
|
989
|
+
schedSaving: "Saving...",
|
|
990
|
+
schedSaved: "Configuration saved",
|
|
991
|
+
schedSavedRegistering: "Configuration saved, re-registering...",
|
|
992
|
+
schedSaveFail: "Failed to save",
|
|
993
|
+
schedSaveAndRestart: "Save & restart",
|
|
994
|
+
schedSaveRestartConfirm: "Save the configuration and restart the bridge? The service will be briefly interrupted; running agents are preserved.",
|
|
782
995
|
schedReRegister: "Re-register",
|
|
783
996
|
schedReRegistering: "Registering...",
|
|
784
997
|
schedReRegistered: "Re-registered successfully",
|
|
785
998
|
schedReRegisterFail: "Re-registration failed",
|
|
786
|
-
schedDesc: "Re-register
|
|
999
|
+
schedDesc: "After saving, click 'Re-register' to apply the new config immediately; some changes such as tunnel mode require a bridge restart.",
|
|
1000
|
+
// Access control
|
|
1001
|
+
accTitle: "Access Control",
|
|
1002
|
+
accAllowIps: "Allowed IPs / networks",
|
|
1003
|
+
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)",
|
|
1004
|
+
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.",
|
|
1005
|
+
accSave: "Save",
|
|
1006
|
+
accSaving: "Saving...",
|
|
1007
|
+
accSaved: "Saved and applied",
|
|
1008
|
+
accSaveFail: "Failed to save",
|
|
787
1009
|
// \u72B6\u6001\u6587\u672C
|
|
788
1010
|
stHealthy: "Healthy",
|
|
789
1011
|
stError: "Error",
|
|
@@ -1092,6 +1314,15 @@ function toggleUnknown() {
|
|
|
1092
1314
|
}
|
|
1093
1315
|
|
|
1094
1316
|
// \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
|
|
1317
|
+
// \u9000\u51FA\u767B\u5F55\uFF1A\u9500\u6BC1\u9762\u677F\u4F1A\u8BDD\u540E\u8DF3\u8F6C\u767B\u5F55\u9875
|
|
1318
|
+
function doLogout() {
|
|
1319
|
+
fetch("/runtime/logout", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
1320
|
+
.catch(function() {})
|
|
1321
|
+
.then(function() {
|
|
1322
|
+
location.href = "/login";
|
|
1323
|
+
});
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1095
1326
|
async function refresh() {
|
|
1096
1327
|
try {
|
|
1097
1328
|
const resp = await fetch("/runtime/dashboard");
|
|
@@ -1258,15 +1489,44 @@ function renderSettingsUI() {
|
|
|
1258
1489
|
document.getElementById("lblNodeActiveSessions").textContent = t("nodeActiveSessions");
|
|
1259
1490
|
document.getElementById("lblLogAutoRefresh").textContent = t("logAutoRefresh");
|
|
1260
1491
|
document.getElementById("logEmpty").textContent = t("logEmpty");
|
|
1492
|
+
document.getElementById("lblSchedTargets").textContent = t("schedTargets");
|
|
1493
|
+
document.getElementById("btnAddTarget").textContent = t("schedAddTarget");
|
|
1494
|
+
document.getElementById("btnDeleteTarget").textContent = t("schedDeleteTarget");
|
|
1261
1495
|
document.getElementById("lblSchedUrl").textContent = t("schedUrl");
|
|
1496
|
+
document.getElementById("hintSchedUrl").textContent = t("schedUrlHint");
|
|
1497
|
+
document.getElementById("lblSchedUserKey").textContent = t("schedUserKey");
|
|
1498
|
+
document.getElementById("hintSchedUserKey").textContent = t("schedUserKeyHint");
|
|
1499
|
+
document.getElementById("lblSchedTenantId").textContent = t("schedTenantId");
|
|
1500
|
+
document.getElementById("hintSchedTenantId").textContent = t("schedTenantIdHint");
|
|
1501
|
+
document.getElementById("lblSchedInstanceName").textContent = t("schedInstanceName");
|
|
1502
|
+
document.getElementById("hintSchedInstanceName").textContent = t("schedInstanceNameHint");
|
|
1503
|
+
document.getElementById("lblSchedConnKey").textContent = t("schedConnKey");
|
|
1504
|
+
document.getElementById("hintSchedConnKey").textContent = t("schedConnKeyHint");
|
|
1505
|
+
document.getElementById("lblSchedRegisterAddr").textContent = t("schedRegisterAddr");
|
|
1506
|
+
document.getElementById("hintSchedRegisterAddr").textContent = t("schedRegisterAddrHint");
|
|
1507
|
+
document.getElementById("lblSchedTunnel").textContent = t("schedTunnel");
|
|
1508
|
+
document.getElementById("hintSchedTunnel").textContent = t("schedTunnelHint");
|
|
1509
|
+
document.getElementById("optTunnelAuto").textContent = t("schedTunnelAuto");
|
|
1510
|
+
document.getElementById("optTunnelOn").textContent = t("schedTunnelOn");
|
|
1511
|
+
document.getElementById("optTunnelOff").textContent = t("schedTunnelOff");
|
|
1512
|
+
document.getElementById("lblSchedInstanceId").textContent = t("schedInstanceId");
|
|
1513
|
+
document.getElementById("hintSchedInstanceId").textContent = t("schedInstanceIdHint");
|
|
1514
|
+
document.getElementById("btnSaveServerConfig").textContent = t("schedSave");
|
|
1515
|
+
document.getElementById("btnSaveRestart").textContent = t("schedSaveAndRestart");
|
|
1262
1516
|
document.getElementById("descSched").textContent = t("schedDesc");
|
|
1263
1517
|
document.getElementById("btnReRegister").textContent = t("schedReRegister");
|
|
1518
|
+
document.getElementById("navAccess").textContent = t("accTitle");
|
|
1519
|
+
document.getElementById("lblAccessIps").textContent = t("accAllowIps");
|
|
1520
|
+
document.getElementById("hintAccessIps").textContent = t("accAllowIpsHint");
|
|
1521
|
+
document.getElementById("descAccess").textContent = t("accDesc");
|
|
1522
|
+
document.getElementById("btnSaveAccessControl").textContent = t("accSave");
|
|
1264
1523
|
}
|
|
1265
1524
|
function switchSettingsPage(page) {
|
|
1266
1525
|
document.querySelectorAll(".settings-nav-item").forEach(function(el) { el.classList.remove("active"); });
|
|
1267
1526
|
document.querySelectorAll(".settings-page").forEach(function(el) { el.classList.remove("active"); });
|
|
1268
1527
|
document.querySelector('.settings-nav-item[data-page="' + page + '"]').classList.add("active");
|
|
1269
1528
|
document.getElementById('page' + page.charAt(0).toUpperCase() + page.slice(1)).classList.add("active");
|
|
1529
|
+
if (page === "access") refreshAccessControl();
|
|
1270
1530
|
}
|
|
1271
1531
|
function openSettings() {
|
|
1272
1532
|
if (settingsLang !== lang) { renderSettingsUI(); settingsLang = lang; }
|
|
@@ -1428,6 +1688,267 @@ document.addEventListener("change", function(e) {
|
|
|
1428
1688
|
});
|
|
1429
1689
|
|
|
1430
1690
|
// \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
|
|
1691
|
+
// \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
|
|
1692
|
+
var schedTargets = [];
|
|
1693
|
+
var schedEditIndex = -1;
|
|
1694
|
+
|
|
1695
|
+
// \u6E32\u67D3\u76EE\u6807\u5217\u8868\uFF08\u5361\u7247\uFF1AURL + \u5143\u4FE1\u606F\uFF0C\u70B9\u51FB\u8FDB\u5165\u7F16\u8F91\uFF09
|
|
1696
|
+
function renderSchedTargetList() {
|
|
1697
|
+
var list = document.getElementById("schedTargetList");
|
|
1698
|
+
list.innerHTML = "";
|
|
1699
|
+
if (!schedTargets.length) {
|
|
1700
|
+
var empty = document.createElement("div");
|
|
1701
|
+
empty.className = "s-target-empty";
|
|
1702
|
+
empty.textContent = t("schedNoTargets");
|
|
1703
|
+
list.appendChild(empty);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
for (var i = 0; i < schedTargets.length; i++) {
|
|
1707
|
+
(function(idx) {
|
|
1708
|
+
var item = schedTargets[idx];
|
|
1709
|
+
var row = document.createElement("div");
|
|
1710
|
+
row.className = "s-target-item" + (idx === schedEditIndex ? " active" : "");
|
|
1711
|
+
var main = document.createElement("div");
|
|
1712
|
+
main.className = "s-target-main";
|
|
1713
|
+
var url = document.createElement("div");
|
|
1714
|
+
url.className = "s-target-url";
|
|
1715
|
+
url.textContent = item.serverUrl;
|
|
1716
|
+
var meta = document.createElement("div");
|
|
1717
|
+
meta.className = "s-target-meta";
|
|
1718
|
+
var parts = [];
|
|
1719
|
+
if (item.instanceName) parts.push(item.instanceName);
|
|
1720
|
+
if (item.instanceId) parts.push("ID: " + item.instanceId);
|
|
1721
|
+
if (item.tunnel === true) parts.push("tunnel");
|
|
1722
|
+
else if (item.tunnel === false && item.registerAddr) parts.push("direct");
|
|
1723
|
+
meta.textContent = parts.join(" \xB7 ");
|
|
1724
|
+
main.appendChild(url);
|
|
1725
|
+
main.appendChild(meta);
|
|
1726
|
+
var actions = document.createElement("div");
|
|
1727
|
+
actions.className = "s-target-actions";
|
|
1728
|
+
var editBtn = document.createElement("button");
|
|
1729
|
+
editBtn.className = "s-btn s-btn-sm s-btn-secondary";
|
|
1730
|
+
editBtn.textContent = t("schedEditTarget");
|
|
1731
|
+
editBtn.onclick = function(ev) { ev.stopPropagation(); loadTargetToForm(idx); };
|
|
1732
|
+
actions.appendChild(editBtn);
|
|
1733
|
+
row.appendChild(main);
|
|
1734
|
+
row.appendChild(actions);
|
|
1735
|
+
row.onclick = function() { loadTargetToForm(idx); };
|
|
1736
|
+
list.appendChild(row);
|
|
1737
|
+
})(i);
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// \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
|
|
1742
|
+
function loadTargetToForm(idx) {
|
|
1743
|
+
schedEditIndex = idx;
|
|
1744
|
+
var item = schedTargets[idx];
|
|
1745
|
+
document.getElementById("cfgServerUrl").value = item.serverUrl || "";
|
|
1746
|
+
document.getElementById("cfgTenantId").value = item.tenantId || "";
|
|
1747
|
+
document.getElementById("cfgInstanceName").value = item.instanceName || "";
|
|
1748
|
+
document.getElementById("cfgRegisterAddr").value = item.registerAddr || "";
|
|
1749
|
+
document.getElementById("cfgTunnel").value =
|
|
1750
|
+
item.tunnel === true ? "true" : (item.tunnel === false ? "false" : "auto");
|
|
1751
|
+
var uk = document.getElementById("cfgUserKey");
|
|
1752
|
+
var ck = document.getElementById("cfgConnectionKey");
|
|
1753
|
+
uk.value = "";
|
|
1754
|
+
ck.value = "";
|
|
1755
|
+
uk.placeholder = item.userKeyMasked || "";
|
|
1756
|
+
ck.placeholder = item.connectionKeyMasked || "";
|
|
1757
|
+
document.getElementById("valSchedInstanceId").textContent = item.instanceId || "-";
|
|
1758
|
+
document.getElementById("btnDeleteTarget").style.display = "";
|
|
1759
|
+
renderSchedTargetList();
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// \u8FDB\u5165\u65B0\u589E\u6A21\u5F0F\uFF1A\u6E05\u7A7A\u8868\u5355
|
|
1763
|
+
function startAddTarget() {
|
|
1764
|
+
schedEditIndex = -1;
|
|
1765
|
+
["cfgServerUrl", "cfgTenantId", "cfgInstanceName", "cfgRegisterAddr", "cfgUserKey", "cfgConnectionKey"].forEach(function(id) {
|
|
1766
|
+
document.getElementById(id).value = "";
|
|
1767
|
+
});
|
|
1768
|
+
document.getElementById("cfgUserKey").placeholder = "";
|
|
1769
|
+
document.getElementById("cfgConnectionKey").placeholder = "";
|
|
1770
|
+
document.getElementById("cfgTunnel").value = "auto";
|
|
1771
|
+
document.getElementById("valSchedInstanceId").textContent = "-";
|
|
1772
|
+
document.getElementById("btnDeleteTarget").style.display = "none";
|
|
1773
|
+
hideSchedMsg();
|
|
1774
|
+
renderSchedTargetList();
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function hideSchedMsg() {
|
|
1778
|
+
var msg = document.getElementById("schedMsg");
|
|
1779
|
+
msg.className = "s-msg";
|
|
1780
|
+
msg.style.display = "none";
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
function showSchedMsg(text, ok) {
|
|
1784
|
+
var msg = document.getElementById("schedMsg");
|
|
1785
|
+
msg.textContent = text;
|
|
1786
|
+
msg.className = "s-msg " + (ok ? "success" : "error");
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// \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
|
|
1790
|
+
function formToTarget() {
|
|
1791
|
+
return {
|
|
1792
|
+
serverUrl: document.getElementById("cfgServerUrl").value.trim(),
|
|
1793
|
+
// \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
|
|
1794
|
+
originalServerUrl: schedEditIndex >= 0 && schedTargets[schedEditIndex]
|
|
1795
|
+
? (schedTargets[schedEditIndex].serverUrl || "")
|
|
1796
|
+
: "",
|
|
1797
|
+
userKey: document.getElementById("cfgUserKey").value,
|
|
1798
|
+
tenantId: document.getElementById("cfgTenantId").value.trim(),
|
|
1799
|
+
instanceName: document.getElementById("cfgInstanceName").value.trim(),
|
|
1800
|
+
connectionKey: document.getElementById("cfgConnectionKey").value,
|
|
1801
|
+
registerAddr: document.getElementById("cfgRegisterAddr").value.trim(),
|
|
1802
|
+
tunnel: document.getElementById("cfgTunnel").value,
|
|
1803
|
+
};
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// \u52A0\u8F7D server \u8FDE\u63A5\u914D\u7F6E\u5217\u8868\u5230\u9762\u677F
|
|
1807
|
+
function refreshServerConfig() {
|
|
1808
|
+
fetch("/runtime/server-config").then(function(r) { return r.json(); }).then(function(d) {
|
|
1809
|
+
if (!d.ok || !Array.isArray(d.targets)) return;
|
|
1810
|
+
schedTargets = d.targets;
|
|
1811
|
+
if (schedEditIndex >= schedTargets.length) schedEditIndex = -1;
|
|
1812
|
+
if (schedEditIndex >= 0) loadTargetToForm(schedEditIndex);
|
|
1813
|
+
else startAddTarget();
|
|
1814
|
+
}).catch(function() {});
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
// \u5168\u91CF\u63D0\u4EA4 targets\uFF1BrestartAfter \u4E3A true \u65F6\u4FDD\u5B58\u6210\u529F\u540E\u91CD\u542F Bridge
|
|
1818
|
+
function saveServerConfig(restartAfter) {
|
|
1819
|
+
var form = formToTarget();
|
|
1820
|
+
if (!form.serverUrl) {
|
|
1821
|
+
showSchedMsg(t("schedSaveFail") + ": serverUrl", false);
|
|
1822
|
+
return Promise.resolve(false);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
// \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
|
|
1826
|
+
var list = schedTargets.map(function(item) {
|
|
1827
|
+
return {
|
|
1828
|
+
serverUrl: item.serverUrl,
|
|
1829
|
+
userKey: "",
|
|
1830
|
+
tenantId: item.tenantId || "",
|
|
1831
|
+
instanceName: item.instanceName || "",
|
|
1832
|
+
connectionKey: "",
|
|
1833
|
+
registerAddr: item.registerAddr || "",
|
|
1834
|
+
tunnel: item.tunnel === true ? "true" : (item.tunnel === false ? "false" : "auto"),
|
|
1835
|
+
};
|
|
1836
|
+
});
|
|
1837
|
+
if (schedEditIndex >= 0) list[schedEditIndex] = form;
|
|
1838
|
+
else list.push(form);
|
|
1839
|
+
|
|
1840
|
+
var btn = document.getElementById("btnSaveServerConfig");
|
|
1841
|
+
btn.disabled = true;
|
|
1842
|
+
btn.textContent = t("schedSaving");
|
|
1843
|
+
return fetch("/runtime/server-config", {
|
|
1844
|
+
method: "POST",
|
|
1845
|
+
headers: { "Content-Type": "application/json" },
|
|
1846
|
+
body: JSON.stringify({ targets: list }),
|
|
1847
|
+
})
|
|
1848
|
+
.then(function(r) { return r.json(); })
|
|
1849
|
+
.then(function(d) {
|
|
1850
|
+
if (d.ok) {
|
|
1851
|
+
// \u4FDD\u7559\u5F53\u524D\u7F16\u8F91\u4E0B\u6807\uFF0C\u4FDD\u5B58\u540E\u8868\u5355\u91CD\u65B0\u52A0\u8F7D\u63A9\u7801\u4E0E instanceId
|
|
1852
|
+
var keepIndex = schedEditIndex >= 0 ? schedEditIndex : schedTargets.length;
|
|
1853
|
+
refreshServerConfig();
|
|
1854
|
+
schedEditIndex = keepIndex;
|
|
1855
|
+
if (restartAfter) {
|
|
1856
|
+
showSchedMsg(t("schedSaved"), true);
|
|
1857
|
+
doRestartBridge();
|
|
1858
|
+
return true;
|
|
1859
|
+
}
|
|
1860
|
+
// \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
|
|
1861
|
+
// \u65E0\u9700\u91CD\u542F Bridge\uFF1B\u5B9E\u4F8B\u4FE1\u606F\u6309 instanceId \u5728\u670D\u52A1\u7AEF\u590D\u7528\uFF09
|
|
1862
|
+
showSchedMsg(t("schedSavedRegistering"), true);
|
|
1863
|
+
btn.textContent = t("schedReRegistering");
|
|
1864
|
+
return runReRegister().then(function(reg) {
|
|
1865
|
+
if (reg.ok) {
|
|
1866
|
+
showSchedMsg(t("schedReRegistered"), true);
|
|
1867
|
+
refreshNodeInfo();
|
|
1868
|
+
} else {
|
|
1869
|
+
showSchedMsg(t("schedReRegisterFail") + (reg.error ? ": " + reg.error : ""), false);
|
|
1870
|
+
}
|
|
1871
|
+
return reg.ok;
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
showSchedMsg(t("schedSaveFail") + (d.error ? ": " + d.error : ""), false);
|
|
1875
|
+
return false;
|
|
1876
|
+
})
|
|
1877
|
+
.catch(function() {
|
|
1878
|
+
showSchedMsg(t("schedSaveFail"), false);
|
|
1879
|
+
return false;
|
|
1880
|
+
})
|
|
1881
|
+
.then(function(ok) {
|
|
1882
|
+
btn.disabled = false;
|
|
1883
|
+
btn.textContent = t("schedSave");
|
|
1884
|
+
return ok;
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// \u4FDD\u5B58\u5E76\u91CD\u542F\uFF1A\u5148\u786E\u8BA4\uFF0C\u4FDD\u5B58\u6210\u529F\u540E\u7531 saveServerConfig \u89E6\u53D1\u91CD\u542F
|
|
1889
|
+
function saveServerConfigAndRestart() {
|
|
1890
|
+
if (!confirm(t("schedSaveRestartConfirm"))) return;
|
|
1891
|
+
saveServerConfig(true);
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// \u5220\u9664\u5F53\u524D\u7F16\u8F91\u7684 target\uFF1A\u4ECE\u5217\u8868\u79FB\u9664\u540E\u5168\u91CF\u63D0\u4EA4
|
|
1895
|
+
function deleteTargetFromList() {
|
|
1896
|
+
if (schedEditIndex < 0 || schedEditIndex >= schedTargets.length) return;
|
|
1897
|
+
if (!confirm(t("schedConfirmDelete"))) return;
|
|
1898
|
+
// \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
|
|
1899
|
+
var editIdx = schedEditIndex;
|
|
1900
|
+
var removed = schedTargets.splice(editIdx, 1)[0];
|
|
1901
|
+
var list = schedTargets.map(function(item) {
|
|
1902
|
+
return {
|
|
1903
|
+
serverUrl: item.serverUrl,
|
|
1904
|
+
userKey: "",
|
|
1905
|
+
tenantId: item.tenantId || "",
|
|
1906
|
+
instanceName: item.instanceName || "",
|
|
1907
|
+
connectionKey: "",
|
|
1908
|
+
registerAddr: item.registerAddr || "",
|
|
1909
|
+
tunnel: item.tunnel === true ? "true" : (item.tunnel === false ? "false" : "auto"),
|
|
1910
|
+
};
|
|
1911
|
+
});
|
|
1912
|
+
fetch("/runtime/server-config", {
|
|
1913
|
+
method: "POST",
|
|
1914
|
+
headers: { "Content-Type": "application/json" },
|
|
1915
|
+
body: JSON.stringify({ targets: list }),
|
|
1916
|
+
})
|
|
1917
|
+
.then(function(r) { return r.json(); })
|
|
1918
|
+
.then(function(d) {
|
|
1919
|
+
if (d.ok) {
|
|
1920
|
+
startAddTarget();
|
|
1921
|
+
refreshServerConfig();
|
|
1922
|
+
// \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
|
|
1923
|
+
showSchedMsg(t("schedSavedRegistering"), true);
|
|
1924
|
+
runReRegister().then(function(reg) {
|
|
1925
|
+
if (reg.ok) {
|
|
1926
|
+
showSchedMsg(t("schedReRegistered"), true);
|
|
1927
|
+
refreshNodeInfo();
|
|
1928
|
+
} else {
|
|
1929
|
+
showSchedMsg(t("schedReRegisterFail") + (reg.error ? ": " + reg.error : ""), false);
|
|
1930
|
+
}
|
|
1931
|
+
});
|
|
1932
|
+
} else {
|
|
1933
|
+
schedTargets.splice(editIdx, 0, removed);
|
|
1934
|
+
renderSchedTargetList();
|
|
1935
|
+
showSchedMsg(t("schedSaveFail") + (d.error ? ": " + d.error : ""), false);
|
|
1936
|
+
}
|
|
1937
|
+
})
|
|
1938
|
+
.catch(function() {
|
|
1939
|
+
schedTargets.splice(editIdx, 0, removed);
|
|
1940
|
+
renderSchedTargetList();
|
|
1941
|
+
showSchedMsg(t("schedSaveFail"), false);
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
// \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
|
|
1946
|
+
function runReRegister() {
|
|
1947
|
+
return fetch("/runtime/re-register", { method: "POST", headers: { "Content-Type": "application/json" } })
|
|
1948
|
+
.then(function(r) { return r.json(); })
|
|
1949
|
+
.catch(function() { return { ok: false }; });
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1431
1952
|
function doReRegister() {
|
|
1432
1953
|
var btn = document.getElementById("btnReRegister");
|
|
1433
1954
|
var msg = document.getElementById("schedMsg");
|
|
@@ -1435,8 +1956,7 @@ function doReRegister() {
|
|
|
1435
1956
|
msg.style.display = "none";
|
|
1436
1957
|
btn.disabled = true;
|
|
1437
1958
|
btn.textContent = t("schedReRegistering");
|
|
1438
|
-
|
|
1439
|
-
.then(function(r) { return r.json(); })
|
|
1959
|
+
runReRegister()
|
|
1440
1960
|
.then(function(d) {
|
|
1441
1961
|
if (d.ok) {
|
|
1442
1962
|
msg.textContent = t("schedReRegistered");
|
|
@@ -1447,13 +1967,52 @@ function doReRegister() {
|
|
|
1447
1967
|
msg.className = "s-msg error";
|
|
1448
1968
|
}
|
|
1449
1969
|
})
|
|
1970
|
+
.then(function() {
|
|
1971
|
+
btn.disabled = false;
|
|
1972
|
+
btn.textContent = t("schedReRegister");
|
|
1973
|
+
});
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
// \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
|
|
1977
|
+
// \u52A0\u8F7D\u5F53\u524D IP \u767D\u540D\u5355\u5230\u8868\u5355\uFF08\u7A7A\u5217\u8868 = \u9ED8\u8BA4\u5168\u653E\u884C\uFF09
|
|
1978
|
+
function refreshAccessControl() {
|
|
1979
|
+
fetch("/runtime/access-control").then(function(r) { return r.json(); }).then(function(d) {
|
|
1980
|
+
if (!d.ok) return;
|
|
1981
|
+
document.getElementById("cfgAllowIps").value = (d.allowIps || []).join("\\n");
|
|
1982
|
+
}).catch(function() {});
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// \u4FDD\u5B58\u767D\u540D\u5355\uFF08\u540E\u7AEF\u6821\u9A8C\u5408\u6CD5\u6027\uFF0C\u4FDD\u5B58\u540E\u7ACB\u5373\u751F\u6548\uFF09
|
|
1986
|
+
function saveAccessControl() {
|
|
1987
|
+
var msg = document.getElementById("accessMsg");
|
|
1988
|
+
var btn = document.getElementById("btnSaveAccessControl");
|
|
1989
|
+
msg.className = "s-msg";
|
|
1990
|
+
msg.style.display = "none";
|
|
1991
|
+
btn.disabled = true;
|
|
1992
|
+
btn.textContent = t("accSaving");
|
|
1993
|
+
fetch("/runtime/access-control", {
|
|
1994
|
+
method: "POST",
|
|
1995
|
+
headers: { "Content-Type": "application/json" },
|
|
1996
|
+
body: JSON.stringify({ allowIps: document.getElementById("cfgAllowIps").value }),
|
|
1997
|
+
})
|
|
1998
|
+
.then(function(r) { return r.json(); })
|
|
1999
|
+
.then(function(d) {
|
|
2000
|
+
if (d.ok) {
|
|
2001
|
+
msg.textContent = t("accSaved");
|
|
2002
|
+
msg.className = "s-msg success";
|
|
2003
|
+
document.getElementById("cfgAllowIps").value = (d.allowIps || []).join("\\n");
|
|
2004
|
+
} else {
|
|
2005
|
+
msg.textContent = d.error || t("accSaveFail");
|
|
2006
|
+
msg.className = "s-msg error";
|
|
2007
|
+
}
|
|
2008
|
+
})
|
|
1450
2009
|
.catch(function() {
|
|
1451
|
-
msg.textContent = t("
|
|
2010
|
+
msg.textContent = t("accSaveFail");
|
|
1452
2011
|
msg.className = "s-msg error";
|
|
1453
2012
|
})
|
|
1454
2013
|
.then(function() {
|
|
1455
2014
|
btn.disabled = false;
|
|
1456
|
-
btn.textContent = t("
|
|
2015
|
+
btn.textContent = t("accSave");
|
|
1457
2016
|
});
|
|
1458
2017
|
}
|
|
1459
2018
|
|
|
@@ -1498,11 +2057,11 @@ switchSettingsPage = function(page) {
|
|
|
1498
2057
|
if (nodeInfoTimer) { clearTimeout(nodeInfoTimer); nodeInfoTimer = null; }
|
|
1499
2058
|
if (page === "node") { refreshNodeInfo(); }
|
|
1500
2059
|
else if (page === "logs") { refreshLogs(); startLogAutoRefresh(); }
|
|
1501
|
-
else if (page === "scheduler") { refreshNodeInfo(); }
|
|
2060
|
+
else if (page === "scheduler") { refreshNodeInfo(); refreshServerConfig(); }
|
|
1502
2061
|
};
|
|
1503
2062
|
</script>
|
|
1504
2063
|
</body>
|
|
1505
|
-
</html>`,
|
|
2064
|
+
</html>`,Pe=`<!DOCTYPE html>
|
|
1506
2065
|
<html lang="zh-CN">
|
|
1507
2066
|
<head>
|
|
1508
2067
|
<meta charset="UTF-8">
|
|
@@ -1673,7 +2232,7 @@ document.getElementById("loginForm").addEventListener("keydown", function(e) {
|
|
|
1673
2232
|
});
|
|
1674
2233
|
</script>
|
|
1675
2234
|
</body>
|
|
1676
|
-
</html>`,
|
|
2235
|
+
</html>`,Ae=`<!DOCTYPE html>
|
|
1677
2236
|
<html lang="zh-CN">
|
|
1678
2237
|
<head>
|
|
1679
2238
|
<meta charset="UTF-8">
|
|
@@ -1960,5 +2519,5 @@ async function doSkip() {
|
|
|
1960
2519
|
document.getElementById("setupForm").addEventListener("submit", doSave);
|
|
1961
2520
|
</script>
|
|
1962
2521
|
</body>
|
|
1963
|
-
</html>`;export{
|
|
2522
|
+
</html>`;export{g as dashboardRouter};
|
|
1964
2523
|
|