qqqtry commited on
Commit
241a0f2
·
1 Parent(s): 7510769

init: zh ops toolkit

Browse files
Files changed (3) hide show
  1. README.md +13 -6
  2. app.py +231 -0
  3. requirements.txt +1 -0
README.md CHANGED
@@ -1,12 +1,19 @@
1
  ---
2
- title: Openclaw Ops Zh
3
- emoji: 👀
4
- colorFrom: pink
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 6.9.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
1
  ---
2
+ title: OpenClaw 运维工具站(中文)
3
+ emoji: 🧰
4
+ colorFrom: blue
5
+ colorTo: gray
6
  sdk: gradio
7
+ sdk_version: 4.44.0
8
  app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ # OpenClaw 运维工具站(中文)
13
+
14
+ 一个手机端也能用的 OpenClaw 快速体检工具:
15
+
16
+ - **配置体检**:粘贴 `openclaw.json` → 输出中文风险提示 + 脱敏后的配置(可分享)
17
+ - **日志体检**:粘贴日志片段 → 快速归类 + 给出下一步排障命令
18
+
19
+ > 注意:工具会对疑似敏感字段脱敏,但仍建议不要在公开场合粘贴真实密钥。
app.py ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import re
3
+ import textwrap
4
+ from datetime import datetime
5
+
6
+ import gradio as gr
7
+
8
+
9
+ def _now() -> str:
10
+ return datetime.utcnow().strftime("%Y-%m-%d %H:%M UTC")
11
+
12
+
13
+ SENSITIVE_KEYS = {
14
+ "apikey",
15
+ "token",
16
+ "password",
17
+ "secret",
18
+ "authorization",
19
+ "bearer",
20
+ "access_token",
21
+ "refresh_token",
22
+ }
23
+
24
+
25
+ def _looks_sensitive_key(k: str) -> bool:
26
+ lk = k.lower()
27
+ return any(s in lk for s in SENSITIVE_KEYS)
28
+
29
+
30
+ def _mask_value(v):
31
+ if v is None:
32
+ return v
33
+ if isinstance(v, (int, float, bool)):
34
+ return v
35
+ s = str(v).strip()
36
+ if not s:
37
+ return s
38
+ if len(s) <= 8:
39
+ return "***"
40
+ return s[:2] + "***" + s[-2:]
41
+
42
+
43
+ def redact(obj):
44
+ if isinstance(obj, dict):
45
+ out = {}
46
+ for k, v in obj.items():
47
+ if _looks_sensitive_key(str(k)):
48
+ out[k] = _mask_value(v)
49
+ else:
50
+ out[k] = redact(v)
51
+ return out
52
+ if isinstance(obj, list):
53
+ return [redact(x) for x in obj]
54
+ return obj
55
+
56
+
57
+ def explain_config(raw: str):
58
+ raw = (raw or "").strip()
59
+ if not raw:
60
+ return "请粘贴 openclaw.json 内容。", "", ""
61
+
62
+ try:
63
+ cfg = json.loads(raw)
64
+ except Exception as e:
65
+ return f"JSON 解析失败:{e}", "", ""
66
+
67
+ red = redact(cfg)
68
+ red_json = json.dumps(red, ensure_ascii=False, indent=2)
69
+
70
+ tips = [f"体检时间:{_now()}"]
71
+
72
+ def has(path):
73
+ cur = cfg
74
+ for p in path:
75
+ if not isinstance(cur, dict) or p not in cur:
76
+ return False
77
+ cur = cur[p]
78
+ return True
79
+
80
+ if has(["gateway", "auth", "token"]) and has(["gateway", "auth", "password"]) and not has(
81
+ ["gateway", "auth", "mode"]
82
+ ):
83
+ tips.append(
84
+ "风险:同时配置了 gateway.auth.token 和 gateway.auth.password,但未显式设置 gateway.auth.mode;可能导致鉴权行为不符合预期。"
85
+ )
86
+ tips.append("建议:明确写 gateway.auth.mode(按你的实际需求选 token/password/both)。")
87
+
88
+ talk = cfg.get("talk", {}) if isinstance(cfg, dict) else {}
89
+ if isinstance(talk, dict) and "silenceTimeoutMs" in talk:
90
+ tips.append(f"Talk:silenceTimeoutMs={talk.get('silenceTimeoutMs')}(可控制静音多久后自动发送)。")
91
+
92
+ provider_hits = []
93
+
94
+ def walk(o, prefix=""):
95
+ if isinstance(o, dict):
96
+ for k, v in o.items():
97
+ np = f"{prefix}.{k}" if prefix else str(k)
98
+ if str(k).lower() in ("baseurl", "endpoint", "url") and isinstance(v, str):
99
+ provider_hits.append((np, v))
100
+ walk(v, np)
101
+ elif isinstance(o, list):
102
+ for i, x in enumerate(o):
103
+ walk(x, f"{prefix}[{i}]")
104
+
105
+ walk(cfg)
106
+ if provider_hits:
107
+ tips.append("检测到可能的接口地址(原样显示;注意不要公开你的 key):")
108
+ for p, v in provider_hits[:10]:
109
+ tips.append(f"- {p} = {v}")
110
+ if len(provider_hits) > 10:
111
+ tips.append(f"- ... 共 {len(provider_hits)} 条(仅展示前 10)")
112
+
113
+ cmds = textwrap.dedent(
114
+ """
115
+ # 常用排障命令(复制到终端执行)
116
+ openclaw status
117
+ openclaw gateway status
118
+ openclaw gateway probe
119
+ openclaw logs --follow
120
+ openclaw doctor
121
+ openclaw channels status --probe
122
+ """
123
+ ).strip()
124
+
125
+ report = "\n".join(f"- {t}" for t in tips)
126
+ return report, red_json, cmds
127
+
128
+
129
+ def explain_logs(raw: str):
130
+ raw = (raw or "").strip()
131
+ if not raw:
132
+ return "请粘贴日志片段。", ""
133
+
134
+ lines = raw.splitlines()
135
+ score = {"auth": 0, "rpc": 0, "channel": 0, "provider": 0, "other": 0}
136
+
137
+ patterns = {
138
+ "auth": [r"401", r"unauthorized", r"forbidden", r"auth", r"token"],
139
+ "rpc": [r"rpc", r"timeout", r"ws://", r"websocket"],
140
+ "channel": [r"telegram", r"signal", r"discord", r"channel"],
141
+ "provider": [r"openai", r"zhipu", r"siliconflow", r"baseurl", r"api"],
142
+ }
143
+
144
+ for ln in lines[:2000]:
145
+ low = ln.lower()
146
+ matched = False
147
+ for k, ps in patterns.items():
148
+ if any(re.search(p, low) for p in ps):
149
+ score[k] += 1
150
+ matched = True
151
+ if not matched:
152
+ score["other"] += 1
153
+
154
+ kind = sorted(score.items(), key=lambda x: x[1], reverse=True)[0][0]
155
+
156
+ advice = [f"体检时间:{_now()}", f"粗分类:{kind}(仅基于关键词,供快速定位)"]
157
+ next_cmds = ["openclaw status", "openclaw gateway probe"]
158
+
159
+ if kind == "auth":
160
+ advice.append("关注点:鉴权配置/allowedOrigins/上游 token。")
161
+ next_cmds += ["openclaw channels status --probe"]
162
+ elif kind == "rpc":
163
+ advice.append("关注点:gateway 重启后的短暂恢复窗口、网络、RPC 超时。")
164
+ next_cmds += ["openclaw gateway status", "openclaw logs --follow"]
165
+ elif kind == "channel":
166
+ advice.append("关注点:通道连接/回调、provider 配置是否影响发送。")
167
+ next_cmds += ["openclaw channels status --probe", "openclaw logs --follow"]
168
+ elif kind == "provider":
169
+ advice.append("关注点:baseUrl、API key、模型名、限流/401。")
170
+ next_cmds += ["openclaw logs --follow"]
171
+ else:
172
+ advice.append("关注点:先用 openclaw doctor + logs 定位模块。")
173
+ next_cmds += ["openclaw doctor", "openclaw logs --follow"]
174
+
175
+ return "\n".join(f"- {x}" for x in advice), "\n".join(next_cmds)
176
+
177
+
178
+ with gr.Blocks(title="OpenClaw 运维工具站(中文)") as demo:
179
+ gr.Markdown(
180
+ """
181
+ # OpenClaw 运维工具站(中文)
182
+
183
+ - **配置体检**:粘贴 `openclaw.json` → 输出中文风险提示 + 脱敏后的配置
184
+ - **日志体检**:粘贴日志片段 → 快速归类 + 给出下一步排障命令
185
+
186
+ 说明:本工具会对疑似敏感字段做脱敏显示,但**请不要在公开场合粘贴真实密钥**。
187
+ """.strip()
188
+ )
189
+
190
+ with gr.Tab("配置体检"):
191
+ cfg_in = gr.Textbox(
192
+ label="粘贴 openclaw.json(完整 JSON)",
193
+ lines=16,
194
+ placeholder="{\n \"gateway\": { ... }\n}\n",
195
+ )
196
+ btn = gr.Button("开始体检")
197
+ report = gr.Textbox(label="中文结论", lines=10)
198
+ redacted = gr.Textbox(label="脱敏后的配置(可分享)", lines=16)
199
+ cmds = gr.Textbox(label="下一步命令(复制执行)", lines=8)
200
+ btn.click(explain_config, inputs=[cfg_in], outputs=[report, redacted, cmds])
201
+
202
+ with gr.Tab("日志体检"):
203
+ log_in = gr.Textbox(label="粘贴日志片段", lines=16)
204
+ btn2 = gr.Button("开始体检")
205
+ report2 = gr.Textbox(label="中文结论", lines=10)
206
+ cmds2 = gr.Textbox(label="下一步命令(复制执行)", lines=8)
207
+ btn2.click(explain_logs, inputs=[log_in], outputs=[report2, cmds2])
208
+
209
+ gr.Markdown(
210
+ """
211
+ ### 你可以怎么用它
212
+ 1) 先跑 `openclaw status`,把关键报错贴到“日志体检”。
213
+ 2) 想检查配置,就把 `openclaw.json` 全文贴到“配置体检”。
214
+
215
+ ### 后续计划
216
+ - 配置项中文解释更细
217
+ - 常见错误库(按版本/通道/Provider)
218
+ - 一键生成备份/恢复脚本(脱敏)
219
+ """.strip()
220
+ )
221
+
222
+
223
+ demo.queue()
224
+
225
+
226
+ def main():
227
+ demo.launch(server_name="0.0.0.0", server_port=7860)
228
+
229
+
230
+ if __name__ == "__main__":
231
+ main()
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio==4.44.0