From 2ed7ccd20dda1b8fcb8a2c796b5512138125ecfd Mon Sep 17 00:00:00 2001 From: liming <827182486@qq.com> Date: Sat, 15 Aug 2026 23:15:36 +0800 Subject: [PATCH 1/7] =?UTF-8?q?feat(web):=20=E9=85=8D=E7=BD=AE=E9=A1=B5?= =?UTF-8?q?=E7=94=9F=E6=88=90=E5=AE=A2=E6=88=B7=E7=AB=AF=E5=AE=89=E8=A3=85?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=EF=BC=8C=E6=94=AF=E6=8C=81=20Shell/Docker=20?= =?UTF-8?q?Compose/Docker=20Run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/css/app.css | 10 +++++ web/index.html | 5 ++- web/js/app.js | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 2 deletions(-) diff --git a/web/css/app.css b/web/css/app.css index bfd7e2ad..b3138efd 100644 --- a/web/css/app.css +++ b/web/css/app.css @@ -367,6 +367,16 @@ th[data-sort].sorted-desc:after{border-top-color:var(--accent);opacity:1} .config-form input[name=password],.config-form textarea[name=rule]{font-family:ui-monospace,SFMono-Regular,Menlo,monospace} .config-form button:disabled{opacity:.5;cursor:not-allowed} .config-editor-card{scroll-margin-top:70px} +.client-cmd{display:flex;flex-direction:column;gap:.6rem;border:1px dashed var(--border);border-radius:8px;background:var(--bg);padding:.75rem} +.client-cmd-head{display:flex;align-items:center;justify-content:space-between;gap:.5rem} +.client-cmd-head strong{font-size:13px} +.client-cmd-head .icon-text{width:auto;flex:none} +.client-cmd-addr{display:flex;flex-direction:column;gap:.3rem;color:var(--text-dim);font-size:12px} +.client-cmd-addr input{height:36px;border:1px solid var(--border);background:var(--bg-alt);color:var(--text);border-radius:6px;padding:0 .65rem;outline:none;font-family:ui-monospace,SFMono-Regular,Menlo,monospace} +.client-cmd-addr input:focus{border-color:var(--accent);box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 18%,transparent)} +.client-cmd-text{margin:0;padding:.6rem .7rem;border-radius:6px;background:var(--bg-alt);color:var(--text);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.55;white-space:pre-wrap;word-break:break-all;max-height:220px;overflow-y:auto} +.client-cmd-methods{width:100%} +.client-cmd-methods button{flex:1;padding:0 .4rem;white-space:nowrap} @media (min-width:981px){ .config-editor-card{position:sticky;top:70px;max-height:calc(100dvh - 86px);overflow-y:auto;scrollbar-gutter:stable} diff --git a/web/index.html b/web/index.html index fa844a5c..eea7ae25 100644 --- a/web/index.html +++ b/web/index.html @@ -6,7 +6,7 @@ 云监控 - +
@@ -189,6 +189,7 @@

新增节点

+ @@ -207,6 +208,6 @@ ServerStatus中文版 - + diff --git a/web/js/app.js b/web/js/app.js index b52ed9bf..a3895c09 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -28,6 +28,9 @@ const S = { enabled: false, connected: false, config: null, + agentAddr: '', + clientServer: '', + clientCmdMethod: 'shell', selectedType: 'servers', selectedIndex: -1, queries: { servers:'', monitors:'', sslcerts:'', watchdog:'' }, @@ -814,6 +817,7 @@ async function ensureAdminChecked(){ try{ const health = await api('/api/health', { auth:false }); S.admin.enabled = !!health.enabled; + S.admin.agentAddr = health.agent?.address || ''; setAdminStatus(health.enabled ? '管理 API 已启用,输入 token 后可编辑配置。' : '管理 API 未启用:请在容器环境变量设置 ADMIN_TOKEN。', health.enabled ? '' : 'err'); if(S.admin.enabled && S.admin.token) await loadConfig(); }catch(err){ @@ -919,6 +923,55 @@ function normalizeAdminConfig(config){ }); return normalized; } +function agentPort(){ + const addr = S.admin.agentAddr || ''; + const idx = addr.lastIndexOf(':'); + const port = idx >= 0 ? addr.slice(idx + 1) : addr; + return /^\d+$/.test(port) ? port : '35601'; +} +function defaultClientServer(){ + return window.location.hostname || '127.0.0.1'; +} +function currentClientServer(){ + if(!S.admin.clientServer) S.admin.clientServer = defaultClientServer(); + return S.admin.clientServer; +} +function shellSafe(v){ + const s = String(v ?? ''); + if(!s) return "''"; + return /^[A-Za-z0-9_@:./-]+$/.test(s) ? s : "'" + s.replace(/'/g, "'\\''") + "'"; +} +function clientInstallCommand(user, pass){ + const server = shellSafe(currentClientServer()); + const port = shellSafe(agentPort()); + const userS = shellSafe(user); + const passS = shellSafe(pass); + const method = S.admin.clientCmdMethod || 'shell'; + if(method === 'compose'){ + return [ + `wget -qO docker-compose-client.yml --header='Accept: application/vnd.github.raw' \\`, + ` 'https://api.github.com/repos/cppla/ServerStatus/contents/docker-compose-client.yml?ref=master'`, + `SERVER=${server} PORT=${port} USER=${userS} PASSWORD=${passS} \\`, + ` docker compose -f docker-compose-client.yml up -d --force-recreate`, + ].join('\n'); + } + if(method === 'run'){ + return [ + `docker run -d --restart=always --name=serverstatus-client \\`, + ` --network=host --pid=host \\`, + ` -e SERVER=${server} \\`, + ` -e PORT=${port} \\`, + ` -e USER=${userS} \\`, + ` -e PASSWORD=${passS} \\`, + ` cppla/serverstatus:client`, + ].join('\n'); + } + return [ + `wget -qO client-linux.py --header='Accept: application/vnd.github.raw' \\`, + ` 'https://api.github.com/repos/cppla/ServerStatus/contents/clients/client-linux.py?ref=master'`, + `nohup python3 client-linux.py SERVER=${server} PORT=${port} USER=${userS} PASSWORD=${passS} >/dev/null 2>&1 &` + ].join('\n'); +} function activeConfigDef(){ return CONFIG_TYPES[S.admin.selectedType] || CONFIG_TYPES.servers; } @@ -991,6 +1044,37 @@ function renderConfigEditor(item){ resetTrafficBtn.style.display = canResetTraffic ? '' : 'none'; resetTrafficBtn.disabled = !canResetTraffic || S.admin.saving; $('deleteConfigItemBtn').disabled = !editing; + renderClientCmd(); +} +function renderClientCmd(){ + const box = $('clientCmd'); + if(!box) return; + if(S.admin.selectedType !== 'servers'){ box.style.display = 'none'; return; } + box.style.display = ''; + box.innerHTML = [ + '
', + '客户端安装命令', + '', + '
', + '
', + '', + '', + '', + '
', + '', + '
'
+  ].join('');
+  $('clientCmdServer').value = currentClientServer();
+  document.querySelectorAll('#clientCmdMethods [data-method]').forEach(btn => btn.classList.toggle('active', btn.dataset.method === (S.admin.clientCmdMethod || 'shell')));
+  refreshClientCmd();
+}
+function refreshClientCmd(){
+  const textEl = $('clientCmdText');
+  if(!textEl) return;
+  const form = $('configForm').elements;
+  const user = form.username ? form.username.value.trim() : '';
+  const pass = form.password ? form.password.value : '';
+  textEl.textContent = clientInstallCommand(user, pass);
 }
 function fieldHTML(field, item){
   const value = item[field.name] ?? field.default ?? '';
@@ -1118,6 +1202,39 @@ function bindAdmin(){
   });
   $('resetConfigFormBtn').addEventListener('click', clearConfigForm);
   $('resetTrafficBtn').addEventListener('click', () => resetServerTraffic(S.admin.selectedIndex));
+  $('clientCmd').addEventListener('input', e => {
+    if(e.target.id === 'clientCmdServer'){
+      S.admin.clientServer = e.target.value.trim();
+      refreshClientCmd();
+    }
+  });
+  $('clientCmd').addEventListener('click', async e => {
+    const methodBtn = e.target.closest('#clientCmdMethods [data-method]');
+    if(methodBtn){
+      S.admin.clientCmdMethod = methodBtn.dataset.method;
+      document.querySelectorAll('#clientCmdMethods [data-method]').forEach(b => b.classList.toggle('active', b === methodBtn));
+      refreshClientCmd();
+      return;
+    }
+    const btn = e.target.closest('#copyClientCmdBtn');
+    if(!btn) return;
+    const text = $('clientCmdText')?.textContent || '';
+    if(!text) return;
+    try{
+      await navigator.clipboard.writeText(text);
+    }catch(_err){
+      const ta = document.createElement('textarea');
+      ta.value = text;
+      document.body.appendChild(ta);
+      ta.select();
+      document.execCommand('copy');
+      ta.remove();
+    }
+    const prev = btn.textContent;
+    btn.textContent = '已复制';
+    setTimeout(() => { btn.textContent = prev; }, 1200);
+  });
+  $('configForm').addEventListener('input', () => { if(S.admin.selectedType === 'servers') refreshClientCmd(); });
   $('adminReload').addEventListener('click', async () => {
     try{ await api('/api/reload', { method:'POST' }); setAdminStatus('配置重载已触发。', 'ok'); }
     catch(err){ setAdminStatus('重载失败:' + err.message, 'err'); }

From 3620186e44ad41d899473eaa3b04937ebdfaa88f Mon Sep 17 00:00:00 2001
From: liming <827182486@qq.com>
Date: Sun, 16 Aug 2026 01:45:56 +0800
Subject: [PATCH 2/7] =?UTF-8?q?feat(clients):=20=E6=96=B0=E5=A2=9E?=
 =?UTF-8?q?=E5=AE=A2=E6=88=B7=E7=AB=AF=20systemd=20=E5=AE=89=E8=A3=85?=
 =?UTF-8?q?=E8=84=9A=E6=9C=AC=EF=BC=8C=E9=85=8D=E7=BD=AE=E9=A1=B5=20Shell?=
 =?UTF-8?q?=20=E5=91=BD=E4=BB=A4=E6=94=B9=E4=B8=BA=E8=84=9A=E6=9C=AC?=
 =?UTF-8?q?=E5=AE=89=E8=A3=85?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .github/workflows/ci.yml |  1 +
 README.md                | 10 +++---
 clients/install.sh       | 77 ++++++++++++++++++++++++++++++++++++++++
 web/js/app.js            |  6 ++--
 4 files changed, 87 insertions(+), 7 deletions(-)
 create mode 100644 clients/install.sh

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 291d900a..1b29b025 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,6 +28,7 @@ jobs:
           python3 -m py_compile clients/client-linux.py clients/client-psutil.py
           python3 -m unittest clients/test_client_args.py
           sh -n clients/entrypoint.sh
+          bash -n clients/install.sh
           bash -n status.sh
           node --check web/js/app.js
 
diff --git a/README.md b/README.md
index c8aead11..7e8f3a36 100644
--- a/README.md
+++ b/README.md
@@ -63,12 +63,14 @@ docker run -d --restart=always --name=serverstatus-client \
 ```
 
 ```bash
-# Shell Run
-wget -qO client-linux.py --header='Accept: application/vnd.github.raw' \
-  'https://api.github.com/repos/cppla/ServerStatus/contents/clients/client-linux.py?ref=master'
-nohup python3 client-linux.py SERVER=127.0.0.1 USER=s01 PASSWORD=USER_DEFAULT_PASSWORD >/dev/null 2>&1 &
+# Shell(下载安装脚本,自动配置为 systemd 服务)
+wget -qO install.sh --header='Accept: application/vnd.github.raw' \
+  'https://api.github.com/repos/cppla/ServerStatus/contents/clients/install.sh?ref=master'
+bash install.sh SERVER=127.0.0.1 USER=s01 PASSWORD=USER_DEFAULT_PASSWORD
 ```
 
+安装脚本会下载 `service/status-client.service` 与 `clients/client-linux.py`,写入客户端配置并注册为 systemd 服务 `status-client`(需 root 或 sudo)。
+
 `USER` 是常见的宿主机环境变量名。如果没有显式传递或传递方式错误,Compose 可能会把系统中的 `$USER` 解析成本机用户名,而不是默认的 `s01`。推荐优先级:
 
 1. 运行命令显式传递 `USER=s01`
diff --git a/clients/install.sh b/clients/install.sh
new file mode 100644
index 00000000..250ee037
--- /dev/null
+++ b/clients/install.sh
@@ -0,0 +1,77 @@
+#!/usr/bin/env bash
+# ServerStatus 客户端一键安装脚本(systemd 方式)
+# 自动下载 status-client.service 与 client-linux.py,注册 systemd 服务并启动。
+# 用法: bash install.sh SERVER=服务器地址 [PORT=端口] [USER=用户名] [PASSWORD=密码]
+set -euo pipefail
+
+github_prefix="https://raw.githubusercontent.com/cppla/ServerStatus/master"
+
+client_file="/usr/local/ServerStatus/clients/client-linux.py"
+client_env="/usr/local/ServerStatus/clients/config.env"
+client_service="/usr/lib/systemd/system/status-client.service"
+client_override="/etc/systemd/system/status-client.service.d/override.conf"
+
+SERVER=""
+PORT="35601"
+USER=""
+PASSWORD=""
+
+for arg in "$@"; do
+  case "${arg}" in
+    SERVER=*) SERVER="${arg#SERVER=}" ;;
+    PORT=*) PORT="${arg#PORT=}" ;;
+    USER=*) USER="${arg#USER=}" ;;
+    PASSWORD=*) PASSWORD="${arg#PASSWORD=}" ;;
+    *) echo "错误: 未知参数 ${arg}" >&2; exit 1 ;;
+  esac
+done
+
+if [[ -z "${SERVER}" ]]; then
+  echo "错误: 缺少 SERVER 参数,用法: bash install.sh SERVER=服务器地址 [PORT=端口] [USER=用户名] [PASSWORD=密码]" >&2
+  exit 1
+fi
+
+if [[ -z "${USER}" ]]; then
+  echo "警告: USER 为空,客户端可能无法上报,请用 USER=用户名 指定" >&2
+fi
+
+if [[ "$(id -u)" -ne 0 ]]; then
+  if command -v sudo >/dev/null 2>&1; then
+    exec sudo bash "$0" "$@"
+  fi
+  echo "错误: 请使用 root 权限运行(如: sudo bash install.sh ...)" >&2
+  exit 1
+fi
+
+command -v wget >/dev/null 2>&1 || { echo "错误: 未找到 wget,请先安装 wget" >&2; exit 1; }
+command -v python3 >/dev/null 2>&1 || { echo "错误: 未找到 python3,请先安装 python3" >&2; exit 1; }
+command -v systemctl >/dev/null 2>&1 || { echo "错误: 未找到 systemctl,当前系统不支持 systemd" >&2; exit 1; }
+
+mkdir -p "$(dirname "${client_file}")"
+wget -qN --no-check-certificate "${github_prefix}/clients/client-linux.py" -O "${client_file}"
+wget -qN --no-check-certificate "${github_prefix}/service/status-client.service" -O "${client_service}"
+chmod +x "${client_file}"
+
+printf 'SERVER=%s\nPORT=%s\nUSER=%s\nPASSWORD=%s\n' "${SERVER}" "${PORT}" "${USER}" "${PASSWORD}" > "${client_env}"
+
+mkdir -p "$(dirname "${client_override}")"
+cat > "${client_override}" <<'EOF'
+[Service]
+EnvironmentFile=/usr/local/ServerStatus/clients/config.env
+EOF
+
+systemctl daemon-reload
+systemctl enable status-client >/dev/null 2>&1 || true
+if ! systemctl restart status-client; then
+  echo "错误: status-client 启动失败,最近日志:" >&2
+  journalctl -u status-client -n 20 --no-pager >&2 || true
+  exit 1
+fi
+
+echo "ServerStatus 客户端安装完成:"
+echo "  SERVER: ${SERVER}"
+echo "  PORT: ${PORT}"
+echo "  USER: ${USER}"
+echo "  配置: ${client_env}"
+echo "  查看状态: systemctl status status-client"
+echo "  查看日志: journalctl -u status-client -f"
diff --git a/web/js/app.js b/web/js/app.js
index a3895c09..be72e9f2 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -967,9 +967,9 @@ function clientInstallCommand(user, pass){
     ].join('\n');
   }
   return [
-    `wget -qO client-linux.py --header='Accept: application/vnd.github.raw' \\`,
-    `  'https://api.github.com/repos/cppla/ServerStatus/contents/clients/client-linux.py?ref=master'`,
-    `nohup python3 client-linux.py SERVER=${server} PORT=${port} USER=${userS} PASSWORD=${passS} >/dev/null 2>&1 &`
+    `wget -qO install.sh --header='Accept: application/vnd.github.raw' \\`,
+    `  'https://api.github.com/repos/cppla/ServerStatus/contents/clients/install.sh?ref=master'`,
+    `bash install.sh SERVER=${server} PORT=${port} USER=${userS} PASSWORD=${passS}`
   ].join('\n');
 }
 function activeConfigDef(){

From 45aa94cdb2ea7ed198e03481a02ab1a7e82ff7ac Mon Sep 17 00:00:00 2001
From: liming <827182486@qq.com>
Date: Sun, 16 Aug 2026 17:33:39 +0800
Subject: [PATCH 3/7] =?UTF-8?q?fix(clients):=20=E5=AE=89=E8=A3=85=E8=84=9A?=
 =?UTF-8?q?=E6=9C=AC=E4=B8=8B=E8=BD=BD=E6=BA=90=E6=94=B9=E4=B8=BA=20jumplo?=
 =?UTF-8?q?op/ServerStatus?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 clients/install.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clients/install.sh b/clients/install.sh
index 250ee037..c925f1f6 100644
--- a/clients/install.sh
+++ b/clients/install.sh
@@ -4,7 +4,7 @@
 # 用法: bash install.sh SERVER=服务器地址 [PORT=端口] [USER=用户名] [PASSWORD=密码]
 set -euo pipefail
 
-github_prefix="https://raw.githubusercontent.com/cppla/ServerStatus/master"
+github_prefix="https://raw.githubusercontent.com/jumploop/ServerStatus/master"
 
 client_file="/usr/local/ServerStatus/clients/client-linux.py"
 client_env="/usr/local/ServerStatus/clients/config.env"

From 53c35229c7161802e6b56dfaecf60aa821d35a62 Mon Sep 17 00:00:00 2001
From: liming <827182486@qq.com>
Date: Sun, 16 Aug 2026 18:56:01 +0800
Subject: [PATCH 4/7] =?UTF-8?q?fix(web):=20=E9=85=8D=E7=BD=AE=E9=A1=B5?=
 =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=B7=B2=E6=9C=89=E9=85=8D=E7=BD=AE=E6=97=B6?=
 =?UTF-8?q?=E4=BF=9D=E7=95=99=E5=8E=9F=E9=85=8D=E7=BD=AE=EF=BC=8C=E4=BB=85?=
 =?UTF-8?q?=E5=BA=94=E7=94=A8=E7=94=A8=E6=88=B7=E4=BF=AE=E6=94=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 web/js/app.js | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/web/js/app.js b/web/js/app.js
index be72e9f2..4564bb76 100644
--- a/web/js/app.js
+++ b/web/js/app.js
@@ -1038,7 +1038,7 @@ function renderConfigEditor(item){
   const current = item || {};
   $('configEditorTitle').textContent = `${editing ? '编辑' : '新增'}${def.label}`;
   $('configEditorHint').textContent = def.hint;
-  $('configFields').innerHTML = def.fields.map(field => fieldHTML(field, current)).join('');
+  $('configFields').innerHTML = def.fields.map(field => fieldHTML(field, current, !editing)).join('');
   const resetTrafficBtn = $('resetTrafficBtn');
   const canResetTraffic = editing && S.admin.selectedType === 'servers';
   resetTrafficBtn.style.display = canResetTraffic ? '' : 'none';
@@ -1076,8 +1076,8 @@ function refreshClientCmd(){
   const pass = form.password ? form.password.value : '';
   textEl.textContent = clientInstallCommand(user, pass);
 }
-function fieldHTML(field, item){
-  const value = item[field.name] ?? field.default ?? '';
+function fieldHTML(field, item, showDefault){
+  const value = item[field.name] ?? (showDefault ? (field.default ?? '') : '');
   if(field.type === 'checkbox'){
     return ``;
   }
@@ -1097,6 +1097,7 @@ function clearConfigForm(){
 }
 function formConfigItem(){
   const elements = $('configForm').elements;
+  const existing = S.admin.selectedIndex >= 0 ? configItems()[S.admin.selectedIndex] : null;
   const item = {};
   activeConfigDef().fields.forEach(field => {
     const el = elements[field.name];
@@ -1110,10 +1111,13 @@ function formConfigItem(){
       if(!Number.isFinite(value)) value = field.default ?? 0;
       if(field.min != null) value = Math.max(field.min, value);
       if(field.max != null) value = Math.min(field.max, value);
+      if(existing && !(field.name in existing) && el.value === '') return;
       item[field.name] = value;
       return;
     }
-    item[field.name] = field.keepRaw ? el.value : el.value.trim();
+    const value = field.keepRaw ? el.value : el.value.trim();
+    if(existing && !(field.name in existing) && value === '') return;
+    item[field.name] = value;
   });
   return item;
 }
@@ -1127,7 +1131,9 @@ async function saveConfigItem(key, index, item){
   S.admin.saving = true;
   S.suppressStatsReloadUntil = Date.now() + 8000;
   try{
-    const data = await api(configItemPath(key, index), { method: index >= 0 ? 'PUT' : 'POST', body: JSON.stringify(item) });
+    const original = index >= 0 ? configItems()[index] : null;
+    const body = original ? { ...original, ...item } : item;
+    const data = await api(configItemPath(key, index), { method: index >= 0 ? 'PUT' : 'POST', body: JSON.stringify(body) });
     if(data.config) S.admin.config = normalizeAdminConfig(data.config);
     S.suppressStatsReloadUntil = Date.now() + 8000;
     return data;

From 84e727b0a5e481296edfb975b505b9a8cefc183e Mon Sep 17 00:00:00 2001
From: liming <827182486@qq.com>
Date: Sun, 16 Aug 2026 19:57:20 +0800
Subject: [PATCH 5/7] =?UTF-8?q?fix(web):=20=E9=85=8D=E7=BD=AE=E9=A1=B5?=
 =?UTF-8?q?=E8=8A=82=E7=82=B9=E8=A1=A8=E5=8D=95=E7=A6=81=E7=94=A8=E6=B5=8F?=
 =?UTF-8?q?=E8=A7=88=E5=99=A8=E8=87=AA=E5=8A=A8=E5=A1=AB=E5=85=85=EF=BC=8C?=
 =?UTF-8?q?=E9=81=BF=E5=85=8D=E7=94=A8=E6=88=B7=E5=90=8D=E8=A2=AB=E6=94=B9?=
 =?UTF-8?q?=E4=B8=BA=E5=B7=B2=E4=BF=9D=E5=AD=98=E8=B4=A6=E5=8F=B7?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 web/index.html | 2 +-
 web/js/app.js  | 5 +++--
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/web/index.html b/web/index.html
index eea7ae25..2a45d8bb 100644
--- a/web/index.html
+++ b/web/index.html
@@ -180,7 +180,7 @@ 

新增节点

保存后会写入 config.json,并由服务热重载。

-
+
diff --git a/web/js/app.js b/web/js/app.js index 4564bb76..1fcc0b37 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -1085,10 +1085,11 @@ function fieldHTML(field, item, showDefault){ const min = field.min != null ? ` min="${field.min}"` : ''; const max = field.max != null ? (field.type === 'number' ? ` max="${field.max}"` : ` maxlength="${field.max}"`) : ''; const placeholder = field.placeholder ? ` placeholder="${esc(field.placeholder)}"` : ''; + const autocomplete = field.type === 'password' ? ' autocomplete="new-password"' : ' autocomplete="off"'; if(field.type === 'textarea'){ - return ``; + return ``; } - return ``; + return ``; } function clearConfigForm(){ S.admin.selectedIndex = -1; From e2f15fe548d6ffb4cf09f8da2b57ed75ffaa881d Mon Sep 17 00:00:00 2001 From: liming <827182486@qq.com> Date: Sun, 16 Aug 2026 20:46:26 +0800 Subject: [PATCH 6/7] =?UTF-8?q?fix(web):=20=E5=AE=A2=E6=88=B7=E7=AB=AF=20S?= =?UTF-8?q?hell=20=E5=AE=89=E8=A3=85=E5=91=BD=E4=BB=A4=20install.sh=20?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E6=BA=90=E6=94=B9=E4=B8=BA=20jumploop/Server?= =?UTF-8?q?Status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- web/js/app.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e8f3a36..d4ae4b7e 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ docker run -d --restart=always --name=serverstatus-client \ ```bash # Shell(下载安装脚本,自动配置为 systemd 服务) wget -qO install.sh --header='Accept: application/vnd.github.raw' \ - 'https://api.github.com/repos/cppla/ServerStatus/contents/clients/install.sh?ref=master' + 'https://api.github.com/repos/jumploop/ServerStatus/contents/clients/install.sh?ref=master' bash install.sh SERVER=127.0.0.1 USER=s01 PASSWORD=USER_DEFAULT_PASSWORD ``` diff --git a/web/js/app.js b/web/js/app.js index 1fcc0b37..d018250f 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -968,7 +968,7 @@ function clientInstallCommand(user, pass){ } return [ `wget -qO install.sh --header='Accept: application/vnd.github.raw' \\`, - ` 'https://api.github.com/repos/cppla/ServerStatus/contents/clients/install.sh?ref=master'`, + ` 'https://api.github.com/repos/jumploop/ServerStatus/contents/clients/install.sh?ref=master'`, `bash install.sh SERVER=${server} PORT=${port} USER=${userS} PASSWORD=${passS}` ].join('\n'); } From 18736402d5bb27d6a623a8d62014b9af92be5a2d Mon Sep 17 00:00:00 2001 From: liming <827182486@qq.com> Date: Sun, 16 Aug 2026 22:10:13 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat(web):=20=E6=96=B0=E5=A2=9E=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E6=97=B6=E9=9A=8F=E6=9C=BA=E7=94=9F=E6=88=90=20userna?= =?UTF-8?q?me/password?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/js/app.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/web/js/app.js b/web/js/app.js index d018250f..f1b7b899 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -849,12 +849,12 @@ const CONFIG_TYPES = { searchFields: ['name','username','location','type','host'], hint: '客户端登录使用 username/password,保存后服务会热重载并让客户端自动重连。', fields: [ - { name:'username', label:'用户名', required:true, max:120 }, + { name:'username', label:'用户名', required:true, max:120, random:8 }, { name:'name', label:'节点名', required:true, max:120 }, { name:'type', label:'虚拟化', required:true, max:120, placeholder:'kvm / xen / vmware' }, { name:'host', label:'主机名', required:true, max:120 }, { name:'location', label:'位置', required:true, max:120, placeholder:'🇨🇳 / 上海 / hk-01' }, - { name:'password', label:'密码', required:true, max:120, keepRaw:true }, + { name:'password', label:'密码', required:true, max:120, keepRaw:true, random:12 }, { name:'monthstart', label:'月初日', type:'number', min:1, max:28, default:1 }, { name:'disabled', label:'禁用节点', type:'checkbox' } ], @@ -1076,8 +1076,17 @@ function refreshClientCmd(){ const pass = form.password ? form.password.value : ''; textEl.textContent = clientInstallCommand(user, pass); } +function randomToken(len){ + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const bytes = new Uint8Array(len); + crypto.getRandomValues(bytes); + let out = ''; + for(let i = 0; i < len; i++) out += chars[bytes[i] % chars.length]; + return out; +} function fieldHTML(field, item, showDefault){ - const value = item[field.name] ?? (showDefault ? (field.default ?? '') : ''); + let value = item[field.name]; + if(value == null && showDefault) value = field.random ? randomToken(field.random) : (field.default ?? ''); if(field.type === 'checkbox'){ return ``; }