openclaw-ops-zh / app.py
qqqtry's picture
init: zh ops toolkit
241a0f2
Raw
History Blame Contribute Delete
7.4 kB
import json
import re
import textwrap
from datetime import datetime
import gradio as gr
def _now() -> str:
return datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
SENSITIVE_KEYS = {
"apikey",
"token",
"password",
"secret",
"authorization",
"bearer",
"access_token",
"refresh_token",
}
def _looks_sensitive_key(k: str) -> bool:
lk = k.lower()
return any(s in lk for s in SENSITIVE_KEYS)
def _mask_value(v):
if v is None:
return v
if isinstance(v, (int, float, bool)):
return v
s = str(v).strip()
if not s:
return s
if len(s) <= 8:
return "***"
return s[:2] + "***" + s[-2:]
def redact(obj):
if isinstance(obj, dict):
out = {}
for k, v in obj.items():
if _looks_sensitive_key(str(k)):
out[k] = _mask_value(v)
else:
out[k] = redact(v)
return out
if isinstance(obj, list):
return [redact(x) for x in obj]
return obj
def explain_config(raw: str):
raw = (raw or "").strip()
if not raw:
return "请粘贴 openclaw.json 内容。", "", ""
try:
cfg = json.loads(raw)
except Exception as e:
return f"JSON 解析失败:{e}", "", ""
red = redact(cfg)
red_json = json.dumps(red, ensure_ascii=False, indent=2)
tips = [f"体检时间:{_now()}"]
def has(path):
cur = cfg
for p in path:
if not isinstance(cur, dict) or p not in cur:
return False
cur = cur[p]
return True
if has(["gateway", "auth", "token"]) and has(["gateway", "auth", "password"]) and not has(
["gateway", "auth", "mode"]
):
tips.append(
"风险:同时配置了 gateway.auth.token 和 gateway.auth.password,但未显式设置 gateway.auth.mode;可能导致鉴权行为不符合预期。"
)
tips.append("建议:明确写 gateway.auth.mode(按你的实际需求选 token/password/both)。")
talk = cfg.get("talk", {}) if isinstance(cfg, dict) else {}
if isinstance(talk, dict) and "silenceTimeoutMs" in talk:
tips.append(f"Talk:silenceTimeoutMs={talk.get('silenceTimeoutMs')}(可控制静音多久后自动发送)。")
provider_hits = []
def walk(o, prefix=""):
if isinstance(o, dict):
for k, v in o.items():
np = f"{prefix}.{k}" if prefix else str(k)
if str(k).lower() in ("baseurl", "endpoint", "url") and isinstance(v, str):
provider_hits.append((np, v))
walk(v, np)
elif isinstance(o, list):
for i, x in enumerate(o):
walk(x, f"{prefix}[{i}]")
walk(cfg)
if provider_hits:
tips.append("检测到可能的接口地址(原样显示;注意不要公开你的 key):")
for p, v in provider_hits[:10]:
tips.append(f"- {p} = {v}")
if len(provider_hits) > 10:
tips.append(f"- ... 共 {len(provider_hits)} 条(仅展示前 10)")
cmds = textwrap.dedent(
"""
# 常用排障命令(复制到终端执行)
openclaw status
openclaw gateway status
openclaw gateway probe
openclaw logs --follow
openclaw doctor
openclaw channels status --probe
"""
).strip()
report = "\n".join(f"- {t}" for t in tips)
return report, red_json, cmds
def explain_logs(raw: str):
raw = (raw or "").strip()
if not raw:
return "请粘贴日志片段。", ""
lines = raw.splitlines()
score = {"auth": 0, "rpc": 0, "channel": 0, "provider": 0, "other": 0}
patterns = {
"auth": [r"401", r"unauthorized", r"forbidden", r"auth", r"token"],
"rpc": [r"rpc", r"timeout", r"ws://", r"websocket"],
"channel": [r"telegram", r"signal", r"discord", r"channel"],
"provider": [r"openai", r"zhipu", r"siliconflow", r"baseurl", r"api"],
}
for ln in lines[:2000]:
low = ln.lower()
matched = False
for k, ps in patterns.items():
if any(re.search(p, low) for p in ps):
score[k] += 1
matched = True
if not matched:
score["other"] += 1
kind = sorted(score.items(), key=lambda x: x[1], reverse=True)[0][0]
advice = [f"体检时间:{_now()}", f"粗分类:{kind}(仅基于关键词,供快速定位)"]
next_cmds = ["openclaw status", "openclaw gateway probe"]
if kind == "auth":
advice.append("关注点:鉴权配置/allowedOrigins/上游 token。")
next_cmds += ["openclaw channels status --probe"]
elif kind == "rpc":
advice.append("关注点:gateway 重启后的短暂恢复窗口、网络、RPC 超时。")
next_cmds += ["openclaw gateway status", "openclaw logs --follow"]
elif kind == "channel":
advice.append("关注点:通道连接/回调、provider 配置是否影响发送。")
next_cmds += ["openclaw channels status --probe", "openclaw logs --follow"]
elif kind == "provider":
advice.append("关注点:baseUrl、API key、模型名、限流/401。")
next_cmds += ["openclaw logs --follow"]
else:
advice.append("关注点:先用 openclaw doctor + logs 定位模块。")
next_cmds += ["openclaw doctor", "openclaw logs --follow"]
return "\n".join(f"- {x}" for x in advice), "\n".join(next_cmds)
with gr.Blocks(title="OpenClaw 运维工具站(中文)") as demo:
gr.Markdown(
"""
# OpenClaw 运维工具站(中文)
- **配置体检**:粘贴 `openclaw.json` → 输出中文风险提示 + 脱敏后的配置
- **日志体检**:粘贴日志片段 → 快速归类 + 给出下一步排障命令
说明:本工具会对疑似敏感字段做脱敏显示,但**请不要在公开场合粘贴真实密钥**。
""".strip()
)
with gr.Tab("配置体检"):
cfg_in = gr.Textbox(
label="粘贴 openclaw.json(完整 JSON)",
lines=16,
placeholder="{\n \"gateway\": { ... }\n}\n",
)
btn = gr.Button("开始体检")
report = gr.Textbox(label="中文结论", lines=10)
redacted = gr.Textbox(label="脱敏后的配置(可分享)", lines=16)
cmds = gr.Textbox(label="下一步命令(复制执行)", lines=8)
btn.click(explain_config, inputs=[cfg_in], outputs=[report, redacted, cmds])
with gr.Tab("日志体检"):
log_in = gr.Textbox(label="粘贴日志片段", lines=16)
btn2 = gr.Button("开始体检")
report2 = gr.Textbox(label="中文结论", lines=10)
cmds2 = gr.Textbox(label="下一步命令(复制执行)", lines=8)
btn2.click(explain_logs, inputs=[log_in], outputs=[report2, cmds2])
gr.Markdown(
"""
### 你可以怎么用它
1) 先跑 `openclaw status`,把关键报错贴到“日志体检”。
2) 想检查配置,就把 `openclaw.json` 全文贴到“配置体检”。
### 后续计划
- 配置项中文解释更细
- 常见错误库(按版本/通道/Provider)
- 一键生成备份/恢复脚本(脱敏)
""".strip()
)
demo.queue()
def main():
demo.launch(server_name="0.0.0.0", server_port=7860)
if __name__ == "__main__":
main()