block-proxy 0.1.12 → 0.1.13
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/.claude/settings.local.json +26 -1
- package/.claude/skills/build-client/skill.md +24 -0
- package/.claude/skills/release-client/skill.md +68 -0
- package/CLAUDE.md +69 -67
- package/Dockerfile +1 -1
- package/README.md +38 -24
- package/build/asset-manifest.json +6 -6
- package/build/index.html +1 -1
- package/build/static/css/main.3f317ce6.css +2 -0
- package/build/static/css/main.3f317ce6.css.map +1 -0
- package/build/static/js/{main.2247fb80.js → main.68f66be0.js} +3 -3
- package/build/static/js/main.68f66be0.js.map +1 -0
- package/client/app.py +312 -0
- package/client/build.sh +84 -0
- package/client/config.py +49 -0
- package/client/config_window.py +155 -0
- package/client/icons/app.icns +0 -0
- package/client/icons/app_example.png +0 -0
- package/client/icons/app_icon.png +0 -0
- package/client/icons/backup/app_example.png +0 -0
- package/client/icons/backup/christmas-sock_dark.png +0 -0
- package/client/icons/backup/christmas-sock_light.png +0 -0
- package/client/icons/backup/socks_on_G.png +0 -0
- package/client/icons/backup/socks_on_M.png +0 -0
- package/client/icons/christmas-sock_dark.png +0 -0
- package/client/icons/christmas-sock_light.png +0 -0
- package/client/icons/christmas-sock_light_bar.png +0 -0
- package/client/icons/socks_on_G.png +0 -0
- package/client/icons/socks_on_G_bar.png +0 -0
- package/client/icons/socks_on_M.png +0 -0
- package/client/icons/socks_on_M_bar.png +0 -0
- package/client/main.py +28 -0
- package/client/proxy_core.py +475 -0
- package/client/requirements.txt +3 -0
- package/client/scripts/download_xray.sh +30 -0
- package/client/setup.py +30 -0
- package/client/system_proxy.py +94 -0
- package/client/tests/__init__.py +0 -0
- package/client/tests/test_config.py +72 -0
- package/client/tests/test_system_proxy.py +69 -0
- package/client/watch-icons.js +31 -0
- package/config.json +28 -3
- package/docs/superpowers/plans/2026-05-27-blockproxyclient.md +1274 -0
- package/docs/superpowers/specs/2026-05-27-blockproxyclient-design.md +264 -0
- package/package.json +10 -4
- package/proxy/proxy.js +19 -15
- package/server/express.js +17 -1
- package/src/App.css +596 -276
- package/src/App.js +25 -22
- package/src/index.css +3 -4
- package/test/lib/mock-server.js +133 -0
- package/test/proxy-tests.js +708 -0
- package/test/run.js +330 -0
- package/build/static/css/main.8bfa3d5f.css +0 -2
- package/build/static/css/main.8bfa3d5f.css.map +0 -1
- package/build/static/js/main.2247fb80.js.map +0 -1
- package/hack-of-anyproxy/lib/requestHandler.js +0 -1060
- /package/build/static/js/{main.2247fb80.js.LICENSE.txt → main.68f66be0.js.LICENSE.txt} +0 -0
package/test/run.js
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const MockServer = require('./lib/mock-server');
|
|
6
|
+
const { runAllTests, checkProxyAccessible } = require('./proxy-tests');
|
|
7
|
+
|
|
8
|
+
// ── 配置 ──────────────────────────────────────────────────
|
|
9
|
+
|
|
10
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
11
|
+
const DEFAULT_CONFIG = {
|
|
12
|
+
httpProxy: { host: '127.0.0.1', port: 8001, username: 'lijing', password: 'lijing' },
|
|
13
|
+
socks5: { host: '127.0.0.1', port: 8002, username: 'lijing', password: 'lijing' },
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// ── CLI 参数解析 ──────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function parseArgs() {
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const opts = {
|
|
21
|
+
httpPort: 8001,
|
|
22
|
+
socks5Port: 8002,
|
|
23
|
+
httpUser: 'lijing',
|
|
24
|
+
httpPass: 'lijing',
|
|
25
|
+
skipExternal: false,
|
|
26
|
+
autoStart: false,
|
|
27
|
+
help: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < args.length; i++) {
|
|
31
|
+
const a = args[i];
|
|
32
|
+
if (a === '--http-port' || a === '-p') opts.httpPort = parseInt(args[++i], 10);
|
|
33
|
+
else if (a === '--socks5-port' || a === '-s') opts.socks5Port = parseInt(args[++i], 10);
|
|
34
|
+
else if (a === '--http-user' || a === '-u') opts.httpUser = args[++i];
|
|
35
|
+
else if (a === '--http-pass') opts.httpPass = args[++i];
|
|
36
|
+
else if (a === '--skip-external') opts.skipExternal = true;
|
|
37
|
+
else if (a === '--auto-start') opts.autoStart = true;
|
|
38
|
+
else if (a === '--help' || a === '-h') opts.help = true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return opts;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function printHelp() {
|
|
45
|
+
console.log(`
|
|
46
|
+
Block-Proxy 测试工具
|
|
47
|
+
|
|
48
|
+
用法: node test/run.js [选项]
|
|
49
|
+
|
|
50
|
+
选项:
|
|
51
|
+
-p, --http-port <port> HTTP 代理端口 (默认: 8001)
|
|
52
|
+
-s, --socks5-port <port> SOCKS5 代理端口 (默认: 8002)
|
|
53
|
+
-u, --http-user <user> 代理认证用户名 (默认: lijing)
|
|
54
|
+
--http-pass <pass> 代理认证密码 (默认: lijing)
|
|
55
|
+
--skip-external 跳过外部站点测试
|
|
56
|
+
--auto-start 自动启动代理服务 (如未运行)
|
|
57
|
+
-h, --help 显示帮助信息
|
|
58
|
+
`);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ── 输出工具 ──────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
const C = {
|
|
65
|
+
reset: '\x1b[0m',
|
|
66
|
+
bold: '\x1b[1m',
|
|
67
|
+
dim: '\x1b[2m',
|
|
68
|
+
green: '\x1b[32m',
|
|
69
|
+
red: '\x1b[31m',
|
|
70
|
+
yellow: '\x1b[33m',
|
|
71
|
+
cyan: '\x1b[36m',
|
|
72
|
+
gray: '\x1b[90m',
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function icon(passed) {
|
|
76
|
+
return passed ? `${C.green}✓${C.reset}` : `${C.red}✗${C.reset}`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function pad(str, len) {
|
|
80
|
+
return str.padEnd(len, ' ');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── 报告渲染 ──────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
function printReport(data) {
|
|
86
|
+
const { categories, summary } = data;
|
|
87
|
+
|
|
88
|
+
console.log('');
|
|
89
|
+
console.log(`${C.bold}${C.cyan}══════════════════════════════════════════════════════════${C.reset}`);
|
|
90
|
+
console.log(`${C.bold}${C.cyan} Block-Proxy 代理测试报告${C.reset}`);
|
|
91
|
+
console.log(`${C.bold}${C.cyan}══════════════════════════════════════════════════════════${C.reset}`);
|
|
92
|
+
console.log('');
|
|
93
|
+
|
|
94
|
+
let maxNameLen = 0;
|
|
95
|
+
for (const cat of categories) {
|
|
96
|
+
for (const r of cat.results) {
|
|
97
|
+
if (r.name.length > maxNameLen) maxNameLen = r.name.length;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
maxNameLen = Math.min(maxNameLen + 4, 70);
|
|
101
|
+
|
|
102
|
+
for (const cat of categories) {
|
|
103
|
+
const catIcon = icon(cat.passed);
|
|
104
|
+
console.log(`${C.bold}${catIcon} ${cat.category}${C.reset}`);
|
|
105
|
+
console.log(`${C.dim}${'─'.repeat(58)}${C.reset}`);
|
|
106
|
+
|
|
107
|
+
for (const r of cat.results) {
|
|
108
|
+
const statusIcon = icon(r.passed);
|
|
109
|
+
const name = pad(r.name, maxNameLen);
|
|
110
|
+
const dur = `${r.duration}ms`.padStart(8);
|
|
111
|
+
console.log(` ${statusIcon} ${C.gray}${name}${C.reset} ${dur} ${r.detail}`);
|
|
112
|
+
}
|
|
113
|
+
console.log('');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── 总结 ──
|
|
117
|
+
console.log(`${C.bold}${'─'.repeat(58)}${C.reset}`);
|
|
118
|
+
console.log(`${C.bold} 总结${C.reset}`);
|
|
119
|
+
console.log(`${C.bold}${'─'.repeat(58)}${C.reset}`);
|
|
120
|
+
|
|
121
|
+
const summaryIcon = summary.failed === 0 ? `${C.green}✓${C.reset}` : `${C.red}✗${C.reset}`;
|
|
122
|
+
console.log(` ${summaryIcon} 总测试项: ${summary.total} | 通过: ${summary.passed} | 失败: ${summary.failed}`);
|
|
123
|
+
console.log(` 分类通过: ${summary.categoriesPassed}/${summary.categoriesTotal}`);
|
|
124
|
+
|
|
125
|
+
// 关键指标
|
|
126
|
+
for (const cat of categories) {
|
|
127
|
+
if (cat.metrics) {
|
|
128
|
+
if (cat.metrics.p50 !== undefined) {
|
|
129
|
+
console.log(` ${C.dim}${cat.category}: P50=${cat.metrics.p50}ms P95=${cat.metrics.p95}ms P99=${cat.metrics.p99}ms${C.reset}`);
|
|
130
|
+
} else if (cat.metrics.qps !== undefined) {
|
|
131
|
+
console.log(` ${C.dim}${cat.category}: QPS=${cat.metrics.qps} 平均延迟=${cat.metrics.avgLatency}ms${C.reset}`);
|
|
132
|
+
} else if (cat.metrics.mbps !== undefined) {
|
|
133
|
+
console.log(` ${C.dim}${cat.category}: ${cat.metrics.mbps} MB/s${C.reset}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log('');
|
|
139
|
+
|
|
140
|
+
// ── 判定 ──
|
|
141
|
+
if (summary.failed === 0) {
|
|
142
|
+
console.log(`${C.green}${C.bold} ✓ 所有测试通过${C.reset}`);
|
|
143
|
+
} else {
|
|
144
|
+
console.log(`${C.red}${C.bold} ✗ 存在 ${summary.failed} 个失败项${C.reset}`);
|
|
145
|
+
}
|
|
146
|
+
console.log('');
|
|
147
|
+
|
|
148
|
+
return summary.failed === 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── 服务管理 ──────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
async function waitForPort(host, port, timeoutMs = 15000) {
|
|
154
|
+
const start = Date.now();
|
|
155
|
+
while (Date.now() - start < timeoutMs) {
|
|
156
|
+
const { accessible } = await checkProxyAccessible(host, port);
|
|
157
|
+
if (accessible) return true;
|
|
158
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let childProcesses = [];
|
|
164
|
+
|
|
165
|
+
async function startProxyService(name, scriptPath) {
|
|
166
|
+
return new Promise((resolve, reject) => {
|
|
167
|
+
const proc = spawn('node', [scriptPath], {
|
|
168
|
+
cwd: PROJECT_ROOT,
|
|
169
|
+
stdio: 'pipe',
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
childProcesses.push(proc);
|
|
173
|
+
|
|
174
|
+
let started = false;
|
|
175
|
+
proc.stdout.on('data', (data) => {
|
|
176
|
+
const text = data.toString();
|
|
177
|
+
if (!started && (text.includes('start') || text.includes('启动') || text.includes('listen'))) {
|
|
178
|
+
started = true;
|
|
179
|
+
resolve(proc);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
proc.stderr.on('data', (data) => {
|
|
184
|
+
const text = data.toString();
|
|
185
|
+
if (!started && (text.includes('start') || text.includes('启动') || text.includes('listen'))) {
|
|
186
|
+
started = true;
|
|
187
|
+
resolve(proc);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
proc.on('error', reject);
|
|
192
|
+
|
|
193
|
+
// timeout fallback
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
if (!started) {
|
|
196
|
+
started = true;
|
|
197
|
+
resolve(proc);
|
|
198
|
+
}
|
|
199
|
+
}, 3000);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cleanupChildProcesses() {
|
|
204
|
+
for (const proc of childProcesses) {
|
|
205
|
+
try { proc.kill('SIGTERM'); } catch (_) {}
|
|
206
|
+
}
|
|
207
|
+
childProcesses = [];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── 主流程 ────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
async function main() {
|
|
213
|
+
const opts = parseArgs();
|
|
214
|
+
if (opts.help) printHelp();
|
|
215
|
+
|
|
216
|
+
const httpProxyConfig = {
|
|
217
|
+
host: '127.0.0.1',
|
|
218
|
+
port: opts.httpPort,
|
|
219
|
+
username: opts.httpUser,
|
|
220
|
+
password: opts.httpPass,
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const socks5Config = {
|
|
224
|
+
host: '127.0.0.1',
|
|
225
|
+
port: opts.socks5Port,
|
|
226
|
+
username: opts.httpUser,
|
|
227
|
+
password: opts.httpPass,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
// ── 检查代理服务状态 ──
|
|
231
|
+
console.log(`${C.bold}检查代理服务...${C.reset}`);
|
|
232
|
+
|
|
233
|
+
const [httpStatus, socks5Status] = await Promise.all([
|
|
234
|
+
checkProxyAccessible(httpProxyConfig.host, httpProxyConfig.port, 'HTTP Proxy'),
|
|
235
|
+
checkProxyAccessible(socks5Config.host, socks5Config.port, 'SOCKS5 Proxy'),
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
console.log(` HTTP Proxy (${httpProxyConfig.port}): ${httpStatus.accessible ? `${C.green}运行中${C.reset}` : `${C.red}不可达${C.reset}`}`);
|
|
239
|
+
console.log(` SOCKS5 (${socks5Config.port}): ${socks5Status.accessible ? `${C.green}运行中${C.reset}` : `${C.red}不可达${C.reset}`}`);
|
|
240
|
+
|
|
241
|
+
if (!httpStatus.accessible && !socks5Status.accessible) {
|
|
242
|
+
if (opts.autoStart) {
|
|
243
|
+
console.log(`\n${C.yellow}代理服务未运行,自动启动...${C.reset}`);
|
|
244
|
+
|
|
245
|
+
startProxyService('proxy', path.join(PROJECT_ROOT, 'proxy/start.js'));
|
|
246
|
+
startProxyService('socks5', path.join(PROJECT_ROOT, 'socks5/start.js'));
|
|
247
|
+
|
|
248
|
+
const [httpReady, socks5Ready] = await Promise.all([
|
|
249
|
+
waitForPort(httpProxyConfig.host, httpProxyConfig.port),
|
|
250
|
+
waitForPort(socks5Config.host, socks5Config.port),
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
if (!httpReady) console.log(` ${C.red}HTTP Proxy 启动失败${C.reset}`);
|
|
254
|
+
else console.log(` HTTP Proxy ${C.green}已就绪${C.reset}`);
|
|
255
|
+
|
|
256
|
+
if (!socks5Ready) console.log(` ${C.red}SOCKS5 启动失败${C.reset}`);
|
|
257
|
+
else console.log(` SOCKS5 ${C.green}已就绪${C.reset}`);
|
|
258
|
+
|
|
259
|
+
if (!httpReady && !socks5Ready) {
|
|
260
|
+
console.log(`\n${C.red}无法启动代理服务,请手动启动后再运行测试。${C.reset}`);
|
|
261
|
+
console.log(`启动方式: npm run dev\n`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
} else {
|
|
265
|
+
console.log(`\n${C.red}代理服务未运行!请先启动代理服务:${C.reset}`);
|
|
266
|
+
console.log(` npm run dev # 启动全部服务`);
|
|
267
|
+
console.log(` npm run proxy # 仅 HTTP 代理`);
|
|
268
|
+
console.log(` npm run socks5 # 仅 SOCKS5`);
|
|
269
|
+
console.log(`\n或使用 --auto-start 自动启动\n`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const effectiveHttpProxy = httpStatus.accessible ? httpProxyConfig : null;
|
|
275
|
+
const effectiveSocks5 = socks5Status.accessible ? socks5Config : null;
|
|
276
|
+
|
|
277
|
+
// ── 启动 Mock Server ──
|
|
278
|
+
console.log(`\n${C.bold}启动 Mock Server...${C.reset}`);
|
|
279
|
+
const mockServer = new MockServer();
|
|
280
|
+
await mockServer.start();
|
|
281
|
+
console.log(` Mock Server: ${mockServer.baseUrl} ${C.green}就绪${C.reset}`);
|
|
282
|
+
|
|
283
|
+
// ── 运行测试 ──
|
|
284
|
+
console.log(`\n${C.bold}开始测试...${C.reset}\n`);
|
|
285
|
+
|
|
286
|
+
const startTime = Date.now();
|
|
287
|
+
let allPassed = false;
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
const results = await runAllTests({
|
|
291
|
+
mockBaseUrl: mockServer.baseUrl,
|
|
292
|
+
httpProxy: effectiveHttpProxy,
|
|
293
|
+
socks5: effectiveSocks5,
|
|
294
|
+
skipExternal: opts.skipExternal,
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const elapsed = Date.now() - startTime;
|
|
298
|
+
results._elapsed = elapsed;
|
|
299
|
+
|
|
300
|
+
allPassed = printReport(results);
|
|
301
|
+
console.log(`${C.dim} 总耗时: ${(elapsed / 1000).toFixed(1)}s${C.reset}\n`);
|
|
302
|
+
} catch (err) {
|
|
303
|
+
console.error(`${C.red}测试执行异常: ${err.message}${C.reset}`);
|
|
304
|
+
console.error(err.stack);
|
|
305
|
+
} finally {
|
|
306
|
+
// ── 清理 ──
|
|
307
|
+
await mockServer.stop();
|
|
308
|
+
cleanupChildProcesses();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
process.exit(allPassed ? 0 : 1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// 优雅退出
|
|
315
|
+
process.on('SIGINT', () => {
|
|
316
|
+
cleanupChildProcesses();
|
|
317
|
+
process.exit(130);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
process.on('SIGTERM', () => {
|
|
321
|
+
cleanupChildProcesses();
|
|
322
|
+
process.exit(143);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
main().catch((err) => {
|
|
326
|
+
console.error(`Fatal: ${err.message}`);
|
|
327
|
+
console.error(err.stack);
|
|
328
|
+
cleanupChildProcesses();
|
|
329
|
+
process.exit(1);
|
|
330
|
+
});
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.server-info p{margin:5px 0}.ip-list{list-style:none;margin:10px 0;padding:0}.ip-item{border-bottom:1px solid #eee;display:flex;padding:8px 0}.ip-item:last-child{border-bottom:none}.interface-name{color:#555;font-weight:700}.ip-address{background-color:#f8f9fa;border-radius:3px;flex:1 1;font-family:Courier New,monospace;padding:2px 6px}.toast{align-items:center;animation:toastSlideIn .3s ease-out;border-radius:4px;box-shadow:0 4px 12px #00000026;color:#fff;display:flex;font-weight:500;min-width:250px;padding:16px 20px;position:fixed;right:20px;top:20px;z-index:1000}@keyframes toastSlideIn{0%{opacity:0;transform:translateX(100%)}to{opacity:1;transform:translateX(0)}}.toast.success{background-color:#28a745;border-left:4px solid #1e7e34}.toast.error{background-color:#dc3545;border-left:4px solid #bd2130}.toast.info{background-color:#17a2b8;border-left:4px solid #117a8b}.toast-close{align-items:center;background:none;border:none;color:#fff;cursor:pointer;display:flex;font-size:20px;font-weight:700;height:20px;justify-content:center;margin-left:15px;padding:0;width:20px}.toast-close:hover{opacity:.7}.App{background-color:#f5f5f5;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;min-height:100vh;padding:20px;text-align:center}.config-container{background:#fff;border-radius:8px;box-shadow:0 2px 10px #0000001a;margin:0 auto;max-width:1220px;padding:30px;position:relative;text-align:left}.config-container h1{color:#333;margin-top:0;text-align:center}.config-section{background-color:#fafafa;border:1px solid #eee;border-radius:5px;margin-bottom:30px;padding:20px}.config-section h2{border-bottom:1px solid #eee;color:#555;margin-top:0;padding-bottom:10px}.host-input{display:flex;flex-direction:row;gap:10px;margin-bottom:15px}.host-input input[type=text]{border:1px solid #ddd;border-radius:4px;flex:1 1;font-size:14px;padding:10px}.host-input button{align-self:flex-start;background-color:#007bff;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:14px}.host-input button:hover{background-color:#0069d9}.time-inputs{align-items:center;display:flex;gap:10px}.time-inputs label{flex-direction:column;font-size:14px}hr.simple-line{background-color:#e3e3e3;border-width:0;height:1px}input[type=time]{background-color:#fff;border:1px solid #ddd;border-radius:3px;padding:4px}input[type=time]:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff40;outline:none}.host-list{list-style:none;margin:0;padding:0}.host-item{align-items:flex-start;border-bottom:1px solid #eee;display:flex;justify-content:space-between;padding:12px 0}.host-item:last-child{border-bottom:none}.host-info{align-items:center;display:flex;flex-grow:1;font-size:15px}span.host-text{align-self:center;flex-grow:1;font-size:14px}.time-controls{align-items:center;display:flex;gap:8px}.time-controls label{display:flex;flex-direction:column;font-size:12px}.remove-btn{align-self:center;background-color:#dc3545;border:none;border-radius:3px;color:#fff;cursor:pointer;font-size:12px;height:-webkit-fit-content;height:fit-content;margin-left:10px;min-width:23px;padding:5px 7px}.remove-btn:hover{background-color:#c82333}.setting-row{align-items:center;display:flex;gap:15px;margin-bottom:15px}.setting-row label{color:#555;flex-shrink:0;font-weight:700;text-align:right;width:220px}.setting-row input{flex:1 1}.setting-row input,.setting-row input[type=number]{border:1px solid #ddd;border-radius:4px;padding:8px}.setting-row input[type=number]{font-size:14px}.setting-row input[type=number]:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff40;outline:none}.actions{display:flex;justify-content:space-between}.restart-btn,.save-btn{border:none;border-radius:4px;cursor:pointer;flex:1 1;font-size:16px;font-weight:700;padding:12px 20px}.save-btn{background-color:#28a745;color:#fff}.save-btn:hover:not(:disabled){background-color:#218838}.restart-btn{background-color:#ffc107;color:#212529}.restart-btn:hover:not(:disabled){background-color:#e0a800}.restart-btn:disabled,.save-btn:disabled{background-color:#6c757d;cursor:not-allowed}button{border:none;border-radius:3px;cursor:pointer;padding:8px 12px;transition:background-color .2s}button:hover{opacity:.9}button:disabled{cursor:not-allowed;opacity:.6}.docker-info{color:#007bff;font-size:.9em}.host-ip-info{border-top:1px solid #eee;margin-top:15px;padding-top:10px}.host-ip-list{list-style:none;margin:10px 0;padding:0}.host-ip-item{display:flex;padding:5px 0}.method-name{color:#555;font-weight:700;width:150px}.host-ip-address{background-color:#e9ecef;border-radius:3px;flex:1 1;font-family:Courier New,monospace;padding:2px 6px}.weekday-btn,.weekday-controls{align-items:center;display:flex}.weekday-btn{background-color:#e5e5e5;border-radius:0;color:#515b63;cursor:pointer;font-size:12px;height:24px;justify-content:center;padding:0;width:24px}.weekday-btn:hover{background-color:#e9ecef;border-color:#adb5bd}.weekday-btn.active{background-color:#007bff;color:#fff}.weekday-btn:focus{box-shadow:0 0 0 2px #007bff40;outline:none}.mac-input{align-items:center;display:flex;gap:5px;margin:0 10px}.mac-input label{color:#555;font-size:12px}.mac-input input[type=text]{border:1px solid #ddd;border-radius:3px;font-size:12px;padding:4px;width:106px}.mac-input input[type=text]:focus{border-color:#007bff;box-shadow:0 0 0 2px #007bff40;outline:none}.table-right-blank{min-width:30px}.title-mac-input{font-size:15px;min-width:131px;text-align:center}.title-time-controls{font-size:15px;min-width:183px;text-align:center}.title-weedkey-controls{font-size:15px;min-width:106px}@media (max-width:500px){.config-container{margin:10px;padding:20px}.time-controls{align-items:flex-start;flex-direction:column;gap:5px}.host-item{gap:10px}.remove-btn{align-self:flex-start;margin-left:0;margin-top:10px}.mac-input{margin:5px 0}.mac-input input[type=text]{width:100px}}
|
|
2
|
-
/*# sourceMappingURL=main.8bfa3d5f.css.map*/
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"static/css/main.8bfa3d5f.css","mappings":"AAAA,KAKE,kCAAmC,CACnC,iCAAkC,CAJlC,mIAEY,CAHZ,QAMF,CAEA,KACE,uEAEF,CCPA,eACE,YACF,CAEA,SACE,eAAgB,CAEhB,aAAc,CADd,SAEF,CAEA,SAGE,4BAA6B,CAF7B,YAAa,CACb,aAEF,CAEA,oBACE,kBACF,CAEA,gBAEE,UAAW,CADX,eAEF,CAEA,YAGE,wBAAyB,CAEzB,iBAAkB,CAJlB,QAAO,CACP,iCAAqC,CAErC,eAEF,CAGA,OAWE,kBAAmB,CACnB,mCAAqC,CAPrC,iBAAkB,CAGlB,+BAA0C,CAF1C,UAAY,CAIZ,YAAa,CAHb,eAAgB,CAMhB,eAAgB,CAThB,iBAAkB,CAHlB,cAAe,CAEf,UAAW,CADX,QAAS,CAOT,YAKF,CAEA,wBACE,GAEE,SAAU,CADV,0BAEF,CACA,GAEE,SAAU,CADV,uBAEF,CACF,CAEA,eACE,wBAAyB,CACzB,6BACF,CAEA,aACE,wBAAyB,CACzB,6BACF,CAEA,YACE,wBAAyB,CACzB,6BACF,CAEA,aAYE,kBAAmB,CAXnB,eAAgB,CAChB,WAAY,CACZ,UAAY,CAIZ,cAAe,CAIf,YAAa,CAPb,cAAe,CACf,eAAiB,CAKjB,WAAY,CAGZ,sBAAuB,CAPvB,gBAAiB,CAEjB,SAAU,CACV,UAKF,CAEA,mBACE,UACF,CAGA,KAME,wBAAyB,CAHzB,mIAEY,CAEZ,gBAAiB,CALjB,YAAa,CADb,iBAOF,CAEA,kBAGE,eAAiB,CACjB,iBAAkB,CAClB,+BAAyC,CAHzC,aAAc,CADd,gBAAiB,CAKjB,YAAa,CAEb,iBAAkB,CADlB,eAEF,CAEA,qBAEE,UAAW,CACX,YAAa,CAFb,iBAGF,CAEA,gBAKE,wBAAyB,CAFzB,qBAAsB,CACtB,iBAAkB,CAHlB,kBAAmB,CACnB,YAIF,CAEA,mBAGE,4BAA6B,CAD7B,UAAW,CADX,YAAa,CAGb,mBACF,CAEA,YACE,YAAa,CAGb,kBAAmB,CADnB,QAAS,CADT,kBAGF,CAEA,6BAGE,qBAAsB,CACtB,iBAAkB,CAHlB,QAAO,CAIP,cAAe,CAHf,YAIF,CAEA,mBAOE,qBAAsB,CANtB,wBAAyB,CAEzB,WAAY,CACZ,iBAAkB,CAFlB,UAAY,CAGZ,cAAe,CACf,cAEF,CAEA,yBACE,wBACF,CAEA,aAGE,kBAAmB,CAFnB,YAAa,CACb,QAEF,CAEA,mBACE,qBAAsB,CACtB,cACF,CAEA,eAGE,wBAAuB,CADvB,cAAgB,CADhB,UAGF,CAGA,iBAIE,qBAAuB,CAFvB,qBAAsB,CACtB,iBAAkB,CAFlB,WAIF,CAEA,uBAEE,oBAAqB,CACrB,8BAA6C,CAF7C,YAGF,CAEA,WACE,eAAgB,CAEhB,QAAS,CADT,SAEF,CAEA,WAGE,sBAAuB,CAEvB,4BAA6B,CAJ7B,YAAa,CACb,6BAA8B,CAE9B,cAEF,CAEA,sBACE,kBACF,CAEA,WAGE,kBAAkB,CAFlB,YAAa,CACb,WAAY,CAEZ,cACF,CAEA,eAGE,iBAAkB,CADlB,WAAY,CADZ,cAGF,CAEA,eAGE,kBAAmB,CAFnB,YAAa,CACb,OAEF,CAEA,qBACE,YAAa,CACb,qBAAsB,CACtB,cACF,CAEA,YAUE,iBAAkB,CATlB,wBAAyB,CAEzB,WAAY,CAIZ,iBAAkB,CALlB,UAAY,CAIZ,cAAe,CAEf,cAAe,CACf,0BAAmB,CAAnB,kBAAmB,CAEnB,gBAAiB,CANjB,cAAe,CADf,eAQF,CAEA,kBACE,wBACF,CAEA,aAEE,kBAAmB,CADnB,YAAa,CAGb,QAAS,CADT,kBAEF,CAEA,mBAIE,UAAW,CACX,aAAc,CAFd,eAAiB,CADjB,gBAAiB,CADjB,WAKF,CAEA,mBACE,QAIF,CAEA,mDAJE,qBAAsB,CACtB,iBAAkB,CAFlB,WAUF,CALA,gCAIE,cACF,CAEA,sCAEE,oBAAqB,CACrB,8BAA6C,CAF7C,YAGF,CAEA,SACE,YAAa,CACb,6BACF,CAEA,uBAGE,WAAY,CACZ,iBAAkB,CAClB,cAAe,CAJf,QAAO,CAKP,cAAe,CACf,eAAiB,CALjB,iBAMF,CAEA,UACE,wBAAyB,CACzB,UACF,CAEA,+BACE,wBACF,CAEA,aACE,wBAAyB,CACzB,aACF,CAEA,kCACE,wBACF,CAEA,yCACE,wBAAyB,CACzB,kBACF,CAEA,OAEE,WAAY,CACZ,iBAAkB,CAClB,cAAe,CAHf,gBAAiB,CAIjB,+BACF,CAEA,aACE,UACF,CAEA,gBAEE,kBAAmB,CADnB,UAEF,CAGA,aACE,aAAc,CACd,cACF,CAEA,cAGE,yBAA0B,CAF1B,eAAgB,CAChB,gBAEF,CAEA,cACE,eAAgB,CAEhB,aAAc,CADd,SAEF,CAEA,cACE,YAAa,CACb,aACF,CAEA,aAGE,UAAW,CAFX,eAAiB,CACjB,WAEF,CAEA,iBAGE,wBAAyB,CAEzB,iBAAkB,CAJlB,QAAO,CACP,iCAAqC,CAErC,eAEF,CASA,+BAHE,kBAAmB,CAFnB,YAkBF,CAbA,aAKE,wBAAyB,CAGzB,eAAkB,CAFlB,aAAc,CAGd,cAAe,CAFf,cAAe,CALf,WAAY,CAUZ,sBAAuB,CATvB,SAAU,CAFV,UAYF,CAEA,mBACE,wBAAyB,CACzB,oBACF,CAEA,oBACE,wBAAyB,CACzB,UACF,CAEA,mBAEE,8BAA6C,CAD7C,YAEF,CAGA,WAEE,kBAAmB,CADnB,YAAa,CAGb,OAAQ,CADR,aAEF,CAEA,iBAEE,UAAW,CADX,cAEF,CAEA,4BAGE,qBAAsB,CACtB,iBAAkB,CAClB,cAAe,CAHf,WAAY,CADZ,WAKF,CAEA,kCAEE,oBAAqB,CACrB,8BAA6C,CAF7C,YAGF,CAEA,mBACE,cACF,CAEA,iBACE,cAAc,CAEd,eAAe,CADf,iBAEF,CACA,qBACE,cAAc,CAEd,eAAe,CADf,iBAEF,CACA,wBACE,cAAc,CACd,eACF,CAGA,yBACE,kBACE,WAAY,CACZ,YACF,CAEA,eAEE,sBAAuB,CADvB,qBAAsB,CAEtB,OACF,CAEA,WACE,QACF,CAEA,YACE,qBAAsB,CACtB,aAAc,CACd,eACF,CAEA,WACE,YACF,CAEA,4BACE,WACF,CACF","sources":["index.css","App.css"],"sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n","/* 服务器信息样式 */\n.server-info {\n /*padding: 10px 0;*/\n}\n\n.server-info p {\n margin: 5px 0;\n}\n\n.ip-list {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n}\n\n.ip-item {\n display: flex;\n padding: 8px 0;\n border-bottom: 1px solid #eee;\n}\n\n.ip-item:last-child {\n border-bottom: none;\n}\n\n.interface-name {\n font-weight: bold;\n color: #555;\n}\n\n.ip-address {\n flex: 1;\n font-family: 'Courier New', monospace;\n background-color: #f8f9fa;\n padding: 2px 6px;\n border-radius: 3px;\n}\n\n/* Toast 样式 */\n.toast {\n position: fixed;\n top: 20px;\n right: 20px;\n padding: 16px 20px;\n border-radius: 4px;\n color: white;\n font-weight: 500;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n display: flex;\n align-items: center;\n animation: toastSlideIn 0.3s ease-out;\n min-width: 250px;\n}\n\n@keyframes toastSlideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n.toast.success {\n background-color: #28a745;\n border-left: 4px solid #1e7e34;\n}\n\n.toast.error {\n background-color: #dc3545;\n border-left: 4px solid #bd2130;\n}\n\n.toast.info {\n background-color: #17a2b8;\n border-left: 4px solid #117a8b;\n}\n\n.toast-close {\n background: none;\n border: none;\n color: white;\n font-size: 20px;\n font-weight: bold;\n margin-left: 15px;\n cursor: pointer;\n padding: 0;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.toast-close:hover {\n opacity: 0.7;\n}\n\n/* 其他原有样式保持不变 */\n.App {\n text-align: center;\n padding: 20px;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n background-color: #f5f5f5;\n min-height: 100vh;\n}\n\n.config-container {\n max-width: 1220px;\n margin: 0 auto;\n background: white;\n border-radius: 8px;\n box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);\n padding: 30px;\n text-align: left;\n position: relative;\n}\n\n.config-container h1 {\n text-align: center;\n color: #333;\n margin-top: 0;\n}\n\n.config-section {\n margin-bottom: 30px;\n padding: 20px;\n border: 1px solid #eee;\n border-radius: 5px;\n background-color: #fafafa;\n}\n\n.config-section h2 {\n margin-top: 0;\n color: #555;\n border-bottom: 1px solid #eee;\n padding-bottom: 10px;\n}\n\n.host-input {\n display: flex;\n margin-bottom: 15px;\n gap: 10px;\n flex-direction: row;\n}\n\n.host-input input[type=\"text\"] {\n flex: 1;\n padding: 10px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n}\n\n.host-input button {\n background-color: #007bff;\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 14px;\n align-self: flex-start;\n}\n\n.host-input button:hover {\n background-color: #0069d9;\n}\n\n.time-inputs {\n display: flex;\n gap: 10px;\n align-items: center;\n}\n\n.time-inputs label {\n flex-direction: column;\n font-size: 14px;\n}\n\nhr.simple-line {\n height:1px;\n border-width:0px;\n background-color:#e3e3e3\n}\n\n/* 时间控件样式 */\ninput[type=\"time\"] {\n padding: 4px;\n border: 1px solid #ddd;\n border-radius: 3px;\n background-color: white;\n}\n\ninput[type=\"time\"]:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);\n}\n\n.host-list {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n\n.host-item {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n padding: 12px 0px;\n border-bottom: 1px solid #eee;\n}\n\n.host-item:last-child {\n border-bottom: none;\n}\n\n.host-info {\n display: flex;\n flex-grow: 1;\n align-items:center;\n font-size:15px;\n}\n\nspan.host-text {\n font-size: 14px;\n flex-grow: 1;\n align-self: center; /* 垂直居中 */\n}\n\n.time-controls {\n display: flex;\n gap: 8px;\n align-items: center;\n}\n\n.time-controls label {\n display: flex;\n flex-direction: column;\n font-size: 12px;\n}\n\n.remove-btn {\n background-color: #dc3545;\n color: white;\n border: none;\n padding: 5px 7px;\n min-width: 23px;\n cursor: pointer;\n border-radius: 3px;\n font-size: 12px;\n height: fit-content;\n align-self: center;\n margin-left: 10px;\n}\n\n.remove-btn:hover {\n background-color: #c82333;\n}\n\n.setting-row {\n display: flex;\n align-items: center;\n margin-bottom: 15px;\n gap: 15px;\n}\n\n.setting-row label {\n width: 220px; /* 固定宽度,足够容纳最长的标签 */\n text-align: right;\n font-weight: bold;\n color: #555;\n flex-shrink: 0; /* 防止被压缩 */\n}\n\n.setting-row input {\n flex: 1;\n padding: 8px;\n border: 1px solid #ddd;\n border-radius: 4px;\n}\n\n.setting-row input[type=\"number\"] {\n padding: 8px;\n border: 1px solid #ddd;\n border-radius: 4px;\n font-size: 14px;\n}\n\n.setting-row input[type=\"number\"]:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);\n}\n\n.actions {\n display: flex;\n justify-content: space-between;\n}\n\n.save-btn, .restart-btn {\n flex: 1;\n padding: 12px 20px;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n font-size: 16px;\n font-weight: bold;\n}\n\n.save-btn {\n background-color: #28a745;\n color: white;\n}\n\n.save-btn:hover:not(:disabled) {\n background-color: #218838;\n}\n\n.restart-btn {\n background-color: #ffc107;\n color: #212529;\n}\n\n.restart-btn:hover:not(:disabled) {\n background-color: #e0a800;\n}\n\n.save-btn:disabled, .restart-btn:disabled {\n background-color: #6c757d;\n cursor: not-allowed;\n}\n\nbutton {\n padding: 8px 12px;\n border: none;\n border-radius: 3px;\n cursor: pointer;\n transition: background-color 0.2s;\n}\n\nbutton:hover {\n opacity: 0.9;\n}\n\nbutton:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n/* Docker信息样式 */\n.docker-info {\n color: #007bff;\n font-size: 0.9em;\n}\n\n.host-ip-info {\n margin-top: 15px;\n padding-top: 10px;\n border-top: 1px solid #eee;\n}\n\n.host-ip-list {\n list-style: none;\n padding: 0;\n margin: 10px 0;\n}\n\n.host-ip-item {\n display: flex;\n padding: 5px 0;\n}\n\n.method-name {\n font-weight: bold;\n width: 150px;\n color: #555;\n}\n\n.host-ip-address {\n flex: 1;\n font-family: 'Courier New', monospace;\n background-color: #e9ecef;\n padding: 2px 6px;\n border-radius: 3px;\n}\n\n/* 星期几控件样式 */\n.weekday-controls {\n display: flex;\n /*flex-wrap: wrap;*/\n align-items: center;\n}\n\n.weekday-btn {\n width: 24px;\n height: 24px;\n padding: 0;\n /*border: 1px solid #ddd;*/\n background-color: #e5e5e5;\n color: #515b63;\n font-size: 12px;\n border-radius: 0px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.weekday-btn:hover {\n background-color: #e9ecef;\n border-color: #adb5bd;\n}\n\n.weekday-btn.active {\n background-color: #007bff;\n color: white;\n}\n\n.weekday-btn:focus {\n outline: none;\n box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);\n}\n\n/* MAC地址输入框样式 */\n.mac-input {\n display: flex;\n align-items: center;\n margin: 0 10px;\n gap: 5px;\n}\n\n.mac-input label {\n font-size: 12px;\n color: #555;\n}\n\n.mac-input input[type=\"text\"] {\n width: 106px;\n padding: 4px;\n border: 1px solid #ddd;\n border-radius: 3px;\n font-size: 12px;\n}\n\n.mac-input input[type=\"text\"]:focus {\n outline: none;\n border-color: #007bff;\n box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);\n}\n\n.table-right-blank {\n min-width:30px;\n}\n\n.title-mac-input {\n font-size:15px;\n text-align:center;\n min-width:131px;\n}\n.title-time-controls {\n font-size:15px;\n text-align:center;\n min-width:183px;\n}\n.title-weedkey-controls {\n font-size:15px;\n min-width:106px;\n}\n\n/* 响应式设计 */\n@media (max-width: 500px) {\n .config-container {\n margin: 10px;\n padding: 20px;\n }\n \n .time-controls {\n flex-direction: column;\n align-items: flex-start;\n gap: 5px;\n }\n\n .host-item {\n gap: 10px;\n }\n \n .remove-btn {\n align-self: flex-start;\n margin-left: 0;\n margin-top: 10px;\n }\n\n .mac-input {\n margin: 5px 0;\n }\n \n .mac-input input[type=\"text\"] {\n width: 100px;\n }\n}\n\n"],"names":[],"sourceRoot":""}
|