node-red-contrib-fox-admin 1.0.0

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.
@@ -0,0 +1,324 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Fox Control | 控制台</title>
7
+ <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MCA2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJGYWVsaW5rIEljb24iPg0KICA8c3R5bGU+DQogICAgOnJvb3Qgew0KICAgICAgLS1jLTMwMDogIzkzQzVGRDsNCiAgICAgIC0tYy00MDA6ICM2MEE1RkE7DQogICAgICAtLWMtNTAwOiAjM0I4MkY2Ow0KICAgICAgLS1jLTYwMDogIzI1NjNFQjsNCiAgICAgIC0tYy03MDA6ICMxRDRFRDg7DQogICAgICAtLWMtODAwOiAjMUU0MEFGOw0KICAgIH0NCiAgPC9zdHlsZT4NCiAgDQogIDxnIGlkPSJmb3gtbG9nby12MyI+DQogICAgICA8IS0tID09PSBDRU5URVIgU1RSVUNUVVJFID09PSAtLT4NCiAgICAgIDwhLS0gTm9zZSBCcmlkZ2UgKERpYW1vbmQpIC0tPg0KICAgICAgPHBhdGggZD0iTSAzMCAxNSBMIDM2IDI1IEwgMzAgMzggTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICANCiAgICAgIDwhLS0gU25vdXQgVGlwIChTaGFycCBWKSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMjQgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNjAwKSIgLz4gPCEtLSBMZWZ0IFNoYWRvdyAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMzYgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4gPCEtLSBSaWdodCBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFQVJTIChMYXJnZSAmIEFsZXJ0KSA9PT0gLS0+DQogICAgICA8IS0tIExlZnQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyMCAyMCBMIDI2IDE1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4NCiAgICAgIDxwYXRoIGQ9Ik0gMjYgMTUgTCAyMCAyMCBMIDI0IDI1IEwgMzAgMTUgWiIgZmlsbD0idmFyKC0tYy00MDApIiAvPiA8IS0tIElubmVyIGNvbm5lY3Rpb24gLS0+DQogICAgICANCiAgICAgIDwhLS0gUmlnaHQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA1MiAyIEwgNDAgMjAgTCAzNCAxNSBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDM0IDE1IEwgNDAgMjAgTCAzNiAyNSBMIDMwIDE1IFoiIGZpbGw9InZhcigtLWMtMzAwKSIgLz4gPCEtLSBJbm5lciBjb25uZWN0aW9uIC0tPg0KDQogICAgICA8IS0tID09PSBDSEVFS1MgKFRoZSAiRm94IiBTaGFwZSkgPT09IC0tPg0KICAgICAgPCEtLSBMZWZ0IENoZWVrIChXaWRlc3QgUG9pbnQpIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyIDIwIEwgMjQgMjUgTCAyMCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDIgMjAgTCAzMCA1NSBMIDI0IDI1IFoiIGZpbGw9InZhcigtLWMtNzAwKSIgLz4gPCEtLSBTaGFycCBKYXdsaW5lIFNoYWRvdyAtLT4NCiAgICAgIA0KICAgICAgPCEtLSBSaWdodCBDaGVlayAoV2lkZXN0IFBvaW50KSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gNTIgMiBMIDU4IDIwIEwgMzYgMjUgTCA0MCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDU4IDIwIEwgMzAgNTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTUwMCkiIC8+IDwhLS0gU2hhcnAgSmF3bGluZSBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFWUVTIChJbnRlZ3JhdGVkIERlcHRoKSA9PT0gLS0+DQogICAgICA8cGF0aCBkPSJNIDIwIDIwIEwgMjYgMTUgTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTgwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDQwIDIwIEwgMzQgMTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTcwMCkiIC8+DQogIDwvZz4NCjwvc3ZnPg==" type="image/x-icon">
8
+ <style>
9
+ :root {
10
+ --primary: #ed1c24;
11
+ --primary-rgb: 237, 28, 36;
12
+ --success: #52c41a;
13
+ --warning: #faad14;
14
+ --error: #ff4d4f;
15
+ --text-main: #ffffff;
16
+ --text-muted: rgba(255, 255, 255, 0.7);
17
+ --bg-gradient: linear-gradient(135deg, #1e2a38 0%, #2c3e50 100%);
18
+ --card-bg: rgba(255, 255, 255, 0.08);
19
+ --card-border: rgba(255, 255, 255, 0.15);
20
+ }
21
+
22
+ * { box-sizing: border-box; outline: none; margin: 0; padding: 0; }
23
+
24
+ body {
25
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
26
+ background: var(--bg-gradient);
27
+ color: var(--text-main);
28
+ min-height: 100vh;
29
+ }
30
+
31
+ .sidebar {
32
+ position: fixed; left: 0; top: 0;
33
+ width: 250px; height: 100vh;
34
+ background: rgba(0, 0, 0, 0.3);
35
+ backdrop-filter: blur(20px);
36
+ border-right: 1px solid var(--card-border);
37
+ padding: 30px 20px;
38
+ z-index: 100;
39
+ }
40
+
41
+ .logo {
42
+ margin-bottom: 40px; display: flex; align-items: center; justify-content: center;
43
+ }
44
+
45
+ .logo-img {
46
+ width: 40px;
47
+ height: 40px;
48
+ }
49
+
50
+ .nav-item {
51
+ display: flex; align-items: center; gap: 12px;
52
+ padding: 14px 16px;
53
+ margin-bottom: 8px;
54
+ border-radius: 12px;
55
+ cursor: pointer;
56
+ transition: all 0.3s ease;
57
+ color: var(--text-muted);
58
+ text-decoration: none;
59
+ }
60
+
61
+ .nav-item:hover, .nav-item.active {
62
+ background: var(--primary);
63
+ color: white;
64
+ box-shadow: 0 4px 15px rgba(var(--primary-rgb), 0.3);
65
+ }
66
+
67
+ .nav-icon { font-size: 20px; }
68
+
69
+ .main-content {
70
+ margin-left: 250px;
71
+ padding: 30px;
72
+ }
73
+
74
+ .header {
75
+ display: flex; justify-content: space-between; align-items: center;
76
+ margin-bottom: 30px;
77
+ }
78
+
79
+ .header h1 { font-size: 28px; font-weight: 700; }
80
+
81
+ .logout-btn {
82
+ background: rgba(255, 77, 79, 0.2);
83
+ color: var(--error);
84
+ border: 1px solid var(--error);
85
+ padding: 10px 20px;
86
+ border-radius: 8px;
87
+ cursor: pointer;
88
+ transition: all 0.3s;
89
+ }
90
+
91
+ .logout-btn:hover {
92
+ background: var(--error);
93
+ color: white;
94
+ }
95
+
96
+ .stats-grid {
97
+ display: grid;
98
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
99
+ gap: 20px;
100
+ margin-bottom: 30px;
101
+ }
102
+
103
+ .stat-card {
104
+ background: var(--card-bg);
105
+ backdrop-filter: blur(20px);
106
+ border: 1px solid var(--card-border);
107
+ border-radius: 16px;
108
+ padding: 24px;
109
+ transition: all 0.3s;
110
+ }
111
+
112
+ .stat-card:hover {
113
+ transform: translateY(-5px);
114
+ box-shadow: 0 15px 40px rgba(0, 0, 0, 0.3);
115
+ }
116
+
117
+ .stat-icon {
118
+ width: 50px; height: 50px;
119
+ border-radius: 12px;
120
+ display: flex; align-items: center; justify-content: center;
121
+ font-size: 24px;
122
+ margin-bottom: 15px;
123
+ }
124
+
125
+ .stat-icon.system { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
126
+ .stat-icon.cpu { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); }
127
+ .stat-icon.memory { background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); }
128
+ .stat-icon.network { background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); }
129
+
130
+ .stat-label { font-size: 14px; color: var(--text-muted); margin-bottom: 8px; }
131
+ .stat-value { font-size: 28px; font-weight: 700; margin-bottom: 5px; }
132
+ .stat-desc { font-size: 13px; color: var(--text-muted); }
133
+
134
+ .system-info {
135
+ background: var(--card-bg);
136
+ backdrop-filter: blur(20px);
137
+ border: 1px solid var(--card-border);
138
+ border-radius: 16px;
139
+ padding: 24px;
140
+ }
141
+
142
+ .info-row {
143
+ display: flex; justify-content: space-between;
144
+ padding: 12px 0;
145
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
146
+ }
147
+
148
+ .info-row:last-child { border-bottom: none; }
149
+
150
+ .info-label { color: var(--text-muted); }
151
+ .info-value { font-weight: 600; }
152
+
153
+ .loading { text-align: center; padding: 40px; color: var(--text-muted); }
154
+ </style>
155
+ </head>
156
+ <body>
157
+ <div class="sidebar">
158
+ <div class="logo">
159
+ <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MCA2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJGYWVsaW5rIEljb24iPg0KICA8c3R5bGU+DQogICAgOnJvb3Qgew0KICAgICAgLS1jLTMwMDogIzkzQzVGRDsNCiAgICAgIC0tYy00MDA6ICM2MEE1RkE7DQogICAgICAtLWMtNTAwOiAjM0I4MkY2Ow0KICAgICAgLS1jLTYwMDogIzI1NjNFQjsNCiAgICAgIC0tYy03MDA6ICMxRDRFRDg7DQogICAgICAtLWMtODAwOiAjMUU0MEFGOw0KICAgIH0NCiAgPC9zdHlsZT4NCiAgDQogIDxnIGlkPSJmb3gtbG9nby12MyI+DQogICAgICA8IS0tID09PSBDRU5URVIgU1RSVUNUVVJFID09PSAtLT4NCiAgICAgIDwhLS0gTm9zZSBCcmlkZ2UgKERpYW1vbmQpIC0tPg0KICAgICAgPHBhdGggZD0iTSAzMCAxNSBMIDM2IDI1IEwgMzAgMzggTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICANCiAgICAgIDwhLS0gU25vdXQgVGlwIChTaGFycCBWKSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMjQgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNjAwKSIgLz4gPCEtLSBMZWZ0IFNoYWRvdyAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMzYgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4gPCEtLSBSaWdodCBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFQVJTIChMYXJnZSAmIEFsZXJ0KSA9PT0gLS0+DQogICAgICA8IS0tIExlZnQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyMCAyMCBMIDI2IDE1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4NCiAgICAgIDxwYXRoIGQ9Ik0gMjYgMTUgTCAyMCAyMCBMIDI0IDI1IEwgMzAgMTUgWiIgZmlsbD0idmFyKC0tYy00MDApIiAvPiA8IS0tIElubmVyIGNvbm5lY3Rpb24gLS0+DQogICAgICANCiAgICAgIDwhLS0gUmlnaHQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA1MiAyIEwgNDAgMjAgTCAzNCAxNSBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDM0IDE1IEwgNDAgMjAgTCAzNiAyNSBMIDMwIDE1IFoiIGZpbGw9InZhcigtLWMtMzAwKSIgLz4gPCEtLSBJbm5lciBjb25uZWN0aW9uIC0tPg0KDQogICAgICA8IS0tID09PSBDSEVFS1MgKFRoZSAiRm94IiBTaGFwZSkgPT09IC0tPg0KICAgICAgPCEtLSBMZWZ0IENoZWVrIChXaWRlc3QgUG9pbnQpIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyIDIwIEwgMjQgMjUgTCAyMCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDIgMjAgTCAzMCA1NSBMIDI0IDI1IFoiIGZpbGw9InZhcigtLWMtNzAwKSIgLz4gPCEtLSBTaGFycCBKYXdsaW5lIFNoYWRvdyAtLT4NCiAgICAgIA0KICAgICAgPCEtLSBSaWdodCBDaGVlayAoV2lkZXN0IFBvaW50KSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gNTIgMiBMIDU4IDIwIEwgMzYgMjUgTCA0MCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDU4IDIwIEwgMzAgNTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTUwMCkiIC8+IDwhLS0gU2hhcnAgSmF3bGluZSBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFWUVTIChJbnRlZ3JhdGVkIERlcHRoKSA9PT0gLS0+DQogICAgICA8cGF0aCBkPSJNIDIwIDIwIEwgMjYgMTUgTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTgwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDQwIDIwIEwgMzQgMTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTcwMCkiIC8+DQogIDwvZz4NCjwvc3ZnPg==" class="logo-img" alt="Fox Logo">
160
+ </div>
161
+ <a href="foxcontroladmin_dashboard" class="nav-item active">
162
+ <span class="nav-icon">📊</span>
163
+ <span>控制台</span>
164
+ </a>
165
+ <a href="foxcontroladmin_network" class="nav-item">
166
+ <span class="nav-icon">🌐</span>
167
+ <span>网络管理</span>
168
+ </a>
169
+ <a href="foxcontroladmin_firewall" class="nav-item">
170
+ <span class="nav-icon">🔒</span>
171
+ <span>防火墙</span>
172
+ </a>
173
+ <a href="foxcontroladmin_deploy" class="nav-item">
174
+ <span class="nav-icon">🚀</span>
175
+ <span>站点部署</span>
176
+ </a>
177
+ <a href="../foxcontrol_edit" class="nav-item" target="_blank">
178
+ <span class="nav-icon">⚡</span>
179
+ <span>流程编辑器</span>
180
+ </a>
181
+ </div>
182
+
183
+ <div class="main-content">
184
+ <div class="header">
185
+ <h1>控制台</h1>
186
+ <button class="logout-btn" onclick="logout()">退出登录</button>
187
+ </div>
188
+
189
+ <div id="loading" class="loading">加载中...</div>
190
+
191
+ <div id="content" style="display: none;">
192
+ <div class="stats-grid">
193
+ <div class="stat-card">
194
+ <div class="stat-icon system">💻</div>
195
+ <div class="stat-label">系统</div>
196
+ <div class="stat-value" id="system-name">-</div>
197
+ <div class="stat-desc" id="system-version">-</div>
198
+ </div>
199
+
200
+ <div class="stat-card">
201
+ <div class="stat-icon cpu">⚡</div>
202
+ <div class="stat-label">CPU</div>
203
+ <div class="stat-value" id="cpu-cores">-</div>
204
+ <div class="stat-desc" id="cpu-brand">-</div>
205
+ </div>
206
+
207
+ <div class="stat-card">
208
+ <div class="stat-icon memory">🧠</div>
209
+ <div class="stat-label">内存</div>
210
+ <div class="stat-value" id="memory-usage">-</div>
211
+ <div class="stat-desc" id="memory-total">-</div>
212
+ </div>
213
+
214
+ <div class="stat-card">
215
+ <div class="stat-icon network">🌐</div>
216
+ <div class="stat-label">网络</div>
217
+ <div class="stat-value" id="network-status">-</div>
218
+ <div class="stat-desc" id="hostname">-</div>
219
+ </div>
220
+ </div>
221
+
222
+ <div class="system-info">
223
+ <h3 style="margin-bottom: 20px;">系统详细信息</h3>
224
+ <div class="info-row">
225
+ <span class="info-label">主机名</span>
226
+ <span class="info-value" id="info-hostname">-</span>
227
+ </div>
228
+ <div class="info-row">
229
+ <span class="info-label">操作系统</span>
230
+ <span class="info-value" id="info-os">-</span>
231
+ </div>
232
+ <div class="info-row">
233
+ <span class="info-label">版本</span>
234
+ <span class="info-value" id="info-version">-</span>
235
+ </div>
236
+ <div class="info-row">
237
+ <span class="info-label">架构</span>
238
+ <span class="info-value" id="info-arch">-</span>
239
+ </div>
240
+ <div class="info-row">
241
+ <span class="info-label">内核</span>
242
+ <span class="info-value" id="info-kernel">-</span>
243
+ </div>
244
+ <div class="info-row">
245
+ <span class="info-label">CPU</span>
246
+ <span class="info-value" id="info-cpu">-</span>
247
+ </div>
248
+ <div class="info-row">
249
+ <span class="info-label">CPU速度</span>
250
+ <span class="info-value" id="info-cpu-speed">-</span>
251
+ </div>
252
+ <div class="info-row">
253
+ <span class="info-label">内存</span>
254
+ <span class="info-value" id="info-memory">-</span>
255
+ </div>
256
+ <div class="info-row">
257
+ <span class="info-label">内存使用率</span>
258
+ <span class="info-value" id="info-memory-usage">-</span>
259
+ </div>
260
+ </div>
261
+ </div>
262
+ </div>
263
+
264
+ <script>
265
+ async function loadSystemInfo() {
266
+ try {
267
+ const res = await fetch('/foxcontrol_api/admin/system/info');
268
+ const data = await res.json();
269
+
270
+ if (data.status === 'success') {
271
+ const { system, cpu, memory } = data.data;
272
+
273
+ document.getElementById('system-name').textContent = system.os;
274
+ document.getElementById('system-version').textContent = system.version;
275
+ document.getElementById('cpu-cores').textContent = cpu.cores + ' 核心';
276
+ document.getElementById('cpu-brand').textContent = cpu.brand;
277
+ document.getElementById('memory-usage').textContent = memory.usage + '%';
278
+ document.getElementById('memory-total').textContent = memory.total + ' GB';
279
+ document.getElementById('network-status').textContent = '在线';
280
+ document.getElementById('hostname').textContent = system.hostname;
281
+
282
+ document.getElementById('info-hostname').textContent = system.hostname;
283
+ document.getElementById('info-os').textContent = system.os;
284
+ document.getElementById('info-version').textContent = system.version;
285
+ document.getElementById('info-arch').textContent = system.arch;
286
+ document.getElementById('info-kernel').textContent = system.kernel;
287
+ document.getElementById('info-cpu').textContent = `${cpu.brand} (${cpu.cores} 核心)`;
288
+ document.getElementById('info-cpu-speed').textContent = `${cpu.speed} GHz`;
289
+ document.getElementById('info-memory').textContent = `${memory.total} GB`;
290
+ document.getElementById('info-memory-usage').textContent = `${memory.usage}%`;
291
+
292
+ document.getElementById('loading').style.display = 'none';
293
+ document.getElementById('content').style.display = 'block';
294
+ }
295
+ } catch (err) {
296
+ document.getElementById('loading').textContent = '加载失败: ' + err.message;
297
+ }
298
+ }
299
+
300
+ async function logout() {
301
+ if (confirm('确定要退出登录吗?')) {
302
+ await fetch('/foxcontrol_api/admin/logout', { method: 'POST' });
303
+ window.location.href = '..';
304
+ }
305
+ }
306
+
307
+ async function checkAuth() {
308
+ try {
309
+ const res = await fetch('/foxcontrol_api/admin/check-auth');
310
+ const data = await res.json();
311
+ if (data.status === 'error' || !data.authenticated) {
312
+ window.location.href = '/foxadmin';
313
+ }
314
+ } catch (err) {
315
+ console.error('[Fox Admin] 登录状态检查失败:', err);
316
+ }
317
+ }
318
+
319
+ loadSystemInfo();
320
+ checkAuth();
321
+ setInterval(loadSystemInfo, 30000);
322
+ </script>
323
+ </body>
324
+ </html>
@@ -0,0 +1,349 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Fox Control | 站点部署</title>
7
+ <link rel="icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MCA2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJGYWVsaW5rIEljb24iPg0KICA8c3R5bGU+DQogICAgOnJvb3Qgew0KICAgICAgLS1jLTMwMDogIzkzQzVGRDsNCiAgICAgIC0tYy00MDA6ICM2MEE1RkE7DQogICAgICAtLWMtNTAwOiAjM0I4MkY2Ow0KICAgICAgLS1jLTYwMDogIzI1NjNFQjsNCiAgICAgIC0tYy03MDA6ICMxRDRFRDg7DQogICAgICAtLWMtODAwOiAjMUU0MEFGOw0KICAgIH0NCiAgPC9zdHlsZT4NCiAgDQogIDxnIGlkPSJmb3gtbG9nby12MyI+DQogICAgICA8IS0tID09PSBDRU5URVIgU1RSVUNUVVJFID09PSAtLT4NCiAgICAgIDwhLS0gTm9zZSBCcmlkZ2UgKERpYW1vbmQpIC0tPg0KICAgICAgPHBhdGggZD0iTSAzMCAxNSBMIDM2IDI1IEwgMzAgMzggTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICANCiAgICAgIDwhLS0gU25vdXQgVGlwIChTaGFycCBWKSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMjQgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNjAwKSIgLz4gPCEtLSBMZWZ0IFNoYWRvdyAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMzYgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4gPCEtLSBSaWdodCBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFQVJTIChMYXJnZSAmIEFsZXJ0KSA9PT0gLS0+DQogICAgICA8IS0tIExlZnQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyMCAyMCBMIDI2IDE1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4NCiAgICAgIDxwYXRoIGQ9Ik0gMjYgMTUgTCAyMCAyMCBMIDI0IDI1IEwgMzAgMTUgWiIgZmlsbD0idmFyKC0tYy00MDApIiAvPiA8IS0tIElubmVyIGNvbm5lY3Rpb24gLS0+DQogICAgICANCiAgICAgIDwhLS0gUmlnaHQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA1MiAyIEwgNDAgMjAgTCAzNCAxNSBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDM0IDE1IEwgNDAgMjAgTCAzNiAyNSBMIDMwIDE1IFoiIGZpbGw9InZhcigtLWMtMzAwKSIgLz4gPCEtLSBJbm5lciBjb25uZWN0aW9uIC0tPg0KDQogICAgICA8IS0tID09PSBDSEVFS1MgKFRoZSAiRm94IiBTaGFwZSkgPT09IC0tPg0KICAgICAgPCEtLSBMZWZ0IENoZWVrIChXaWRlc3QgUG9pbnQpIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyIDIwIEwgMjQgMjUgTCAyMCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDIgMjAgTCAzMCA1NSBMIDI0IDI1IFoiIGZpbGw9InZhcigtLWMtNzAwKSIgLz4gPCEtLSBTaGFycCBKYXdsaW5lIFNoYWRvdyAtLT4NCiAgICAgIA0KICAgICAgPCEtLSBSaWdodCBDaGVlayAoV2lkZXN0IFBvaW50KSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gNTIgMiBMIDU4IDIwIEwgMzYgMjUgTCA0MCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDU4IDIwIEwgMzAgNTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTUwMCkiIC8+IDwhLS0gU2hhcnAgSmF3bGluZSBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFWUVTIChJbnRlZ3JhdGVkIERlcHRoKSA9PT0gLS0+DQogICAgICA8cGF0aCBkPSJNIDIwIDIwIEwgMjYgMTUgTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTgwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDQwIDIwIEwgMzQgMTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTcwMCkiIC8+DQogIDwvZz4NCjwvc3ZnPg==" type="image/x-icon">
8
+ <style>
9
+ /* 核心变量同步自 device.html */
10
+ :root {
11
+ --primary: #ed1c24;
12
+ --primary-rgb: 237, 28, 36;
13
+ --success: #52c41a;
14
+ --warning: #faad14;
15
+ --error: #ff4d4f;
16
+ --text-main: #ffffff;
17
+ --text-muted: rgba(255, 255, 255, 0.7);
18
+ --bg-gradient: linear-gradient(135deg, #1e2a38 0%, #2c3e50 100%);
19
+ --card-bg: rgba(255, 255, 255, 0.08);
20
+ --card-border: rgba(255, 255, 255, 0.15);
21
+ }
22
+
23
+ * { box-sizing: border-box; outline: none; margin: 0; padding: 0; }
24
+
25
+ body {
26
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
27
+ background: var(--bg-gradient);
28
+ color: var(--text-main);
29
+ min-height: 100vh;
30
+ }
31
+
32
+ /* --- Sidebar 同步 --- */
33
+ .sidebar {
34
+ position: fixed; left: 0; top: 0;
35
+ width: 250px; height: 100vh;
36
+ background: rgba(0, 0, 0, 0.3);
37
+ backdrop-filter: blur(20px);
38
+ border-right: 1px solid var(--card-border);
39
+ padding: 30px 20px;
40
+ z-index: 100;
41
+ }
42
+
43
+ .logo {
44
+ margin-bottom: 40px; display: flex; align-items: center; justify-content: center;
45
+ }
46
+
47
+ .logo-img { width: 40px; height: 40px; }
48
+
49
+ .nav-item {
50
+ display: flex; align-items: center; gap: 12px;
51
+ padding: 14px 16px;
52
+ margin-bottom: 8px;
53
+ border-radius: 12px;
54
+ cursor: pointer;
55
+ transition: all 0.3s ease;
56
+ color: var(--text-muted);
57
+ text-decoration: none;
58
+ }
59
+
60
+ .nav-item:hover, .nav-item.active {
61
+ background: var(--primary);
62
+ color: white;
63
+ box-shadow: 0 4px 15px rgba(var(--primary-rgb), 0.3);
64
+ }
65
+
66
+ .nav-icon { font-size: 20px; }
67
+
68
+ /* --- Main Content 同步 --- */
69
+ .main-content {
70
+ margin-left: 250px;
71
+ padding: 30px;
72
+ }
73
+
74
+ .header {
75
+ display: flex; justify-content: space-between; align-items: center;
76
+ margin-bottom: 30px;
77
+ }
78
+
79
+ .header h1 { font-size: 28px; font-weight: 700; }
80
+
81
+ .logout-btn {
82
+ background: rgba(255, 77, 79, 0.2);
83
+ color: var(--error);
84
+ border: 1px solid var(--error);
85
+ padding: 10px 20px;
86
+ border-radius: 8px;
87
+ cursor: pointer;
88
+ transition: all 0.3s;
89
+ }
90
+
91
+ .logout-btn:hover {
92
+ background: var(--error);
93
+ color: white;
94
+ }
95
+
96
+ /* --- Card 样式同步 --- */
97
+ .card {
98
+ background: var(--card-bg);
99
+ backdrop-filter: blur(20px);
100
+ border: 1px solid var(--card-border);
101
+ border-radius: 16px;
102
+ padding: 24px;
103
+ margin-bottom: 20px;
104
+ }
105
+
106
+ .card h3 { margin-bottom: 20px; font-size: 18px; }
107
+
108
+ /* --- Upload Section 优化 --- */
109
+ .upload-section {
110
+ border: 2px dashed var(--card-border);
111
+ border-radius: 12px;
112
+ padding: 40px 20px;
113
+ text-align: center;
114
+ cursor: pointer;
115
+ transition: all 0.3s ease;
116
+ background: rgba(255, 255, 255, 0.03);
117
+ margin-bottom: 20px;
118
+ }
119
+
120
+ .upload-section:hover {
121
+ border-color: var(--primary);
122
+ background: rgba(255, 255, 255, 0.05);
123
+ }
124
+
125
+ .upload-icon {
126
+ font-size: 40px;
127
+ margin-bottom: 15px;
128
+ display: block;
129
+ }
130
+
131
+ #file-info-box {
132
+ display: none;
133
+ background: rgba(237, 28, 36, 0.1);
134
+ border: 1px solid rgba(237, 28, 36, 0.3);
135
+ border-radius: 10px;
136
+ padding: 15px;
137
+ margin-bottom: 20px;
138
+ justify-content: space-between;
139
+ align-items: center;
140
+ }
141
+
142
+ .file-name { font-weight: 600; color: #ffccc7; }
143
+ .remove-file { color: var(--error); cursor: pointer; font-weight: bold; }
144
+
145
+ /* --- Button 样式同步 --- */
146
+ .btn-deploy {
147
+ width: 100%;
148
+ padding: 14px;
149
+ background: var(--primary);
150
+ color: white;
151
+ border: none;
152
+ border-radius: 10px;
153
+ font-size: 16px;
154
+ font-weight: 600;
155
+ cursor: pointer;
156
+ transition: all 0.3s ease;
157
+ box-shadow: 0 4px 15px rgba(var(--primary-rgb), 0.3);
158
+ }
159
+
160
+ .btn-deploy:hover {
161
+ transform: translateY(-2px);
162
+ box-shadow: 0 6px 20px rgba(var(--primary-rgb), 0.4);
163
+ }
164
+
165
+ .btn-deploy:disabled {
166
+ opacity: 0.5;
167
+ cursor: not-allowed;
168
+ transform: none;
169
+ }
170
+
171
+ /* --- Progress Bar --- */
172
+ .progress-container { margin-top: 20px; display: none; }
173
+
174
+ .progress-track {
175
+ height: 8px;
176
+ background: rgba(255, 255, 255, 0.1);
177
+ border-radius: 4px;
178
+ overflow: hidden;
179
+ }
180
+
181
+ .progress-fill {
182
+ height: 100%;
183
+ width: 0%;
184
+ background: var(--primary);
185
+ transition: width 0.3s;
186
+ }
187
+
188
+ .status-text {
189
+ margin-top: 12px;
190
+ font-size: 14px;
191
+ text-align: center;
192
+ color: var(--text-muted);
193
+ }
194
+
195
+ input[type="file"] { display: none; }
196
+
197
+ .success-link {
198
+ color: var(--success);
199
+ text-decoration: underline;
200
+ margin-left: 5px;
201
+ }
202
+ </style>
203
+ </head>
204
+ <body>
205
+ <div class="sidebar">
206
+ <div class="logo">
207
+ <img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MCA2MCIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJGYWVsaW5rIEljb24iPg0KICA8c3R5bGU+DQogICAgOnJvb3Qgew0KICAgICAgLS1jLTMwMDogIzkzQzVGRDsNCiAgICAgIC0tYy00MDA6ICM2MEE1RkE7DQogICAgICAtLWMtNTAwOiAjM0I4MkY2Ow0KICAgICAgLS1jLTYwMDogIzI1NjNFQjsNCiAgICAgIC0tYy03MDA6ICMxRDRFRDg7DQogICAgICAtLWMtODAwOiAjMUU0MEFGOw0KICAgIH0NCiAgPC9zdHlsZT4NCiAgDQogIDxnIGlkPSJmb3gtbG9nby12MyI+DQogICAgICA8IS0tID09PSBDRU5URVIgU1RSVUNUVVJFID09PSAtLT4NCiAgICAgIDwhLS0gTm9zZSBCcmlkZ2UgKERpYW1vbmQpIC0tPg0KICAgICAgPHBhdGggZD0iTSAzMCAxNSBMIDM2IDI1IEwgMzAgMzggTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICANCiAgICAgIDwhLS0gU25vdXQgVGlwIChTaGFycCBWKSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMjQgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNjAwKSIgLz4gPCEtLSBMZWZ0IFNoYWRvdyAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gMzYgMjUgTCAzMCAzOCBMIDMwIDU1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4gPCEtLSBSaWdodCBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFQVJTIChMYXJnZSAmIEFsZXJ0KSA9PT0gLS0+DQogICAgICA8IS0tIExlZnQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyMCAyMCBMIDI2IDE1IFoiIGZpbGw9InZhcigtLWMtNTAwKSIgLz4NCiAgICAgIDxwYXRoIGQ9Ik0gMjYgMTUgTCAyMCAyMCBMIDI0IDI1IEwgMzAgMTUgWiIgZmlsbD0idmFyKC0tYy00MDApIiAvPiA8IS0tIElubmVyIGNvbm5lY3Rpb24gLS0+DQogICAgICANCiAgICAgIDwhLS0gUmlnaHQgRWFyIC0tPg0KICAgICAgPHBhdGggZD0iTSA1MiAyIEwgNDAgMjAgTCAzNCAxNSBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDM0IDE1IEwgNDAgMjAgTCAzNiAyNSBMIDMwIDE1IFoiIGZpbGw9InZhcigtLWMtMzAwKSIgLz4gPCEtLSBJbm5lciBjb25uZWN0aW9uIC0tPg0KDQogICAgICA8IS0tID09PSBDSEVFS1MgKFRoZSAiRm94IiBTaGFwZSkgPT09IC0tPg0KICAgICAgPCEtLSBMZWZ0IENoZWVrIChXaWRlc3QgUG9pbnQpIC0tPg0KICAgICAgPHBhdGggZD0iTSA4IDIgTCAyIDIwIEwgMjQgMjUgTCAyMCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTQwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDIgMjAgTCAzMCA1NSBMIDI0IDI1IFoiIGZpbGw9InZhcigtLWMtNzAwKSIgLz4gPCEtLSBTaGFycCBKYXdsaW5lIFNoYWRvdyAtLT4NCiAgICAgIA0KICAgICAgPCEtLSBSaWdodCBDaGVlayAoV2lkZXN0IFBvaW50KSAtLT4NCiAgICAgIDxwYXRoIGQ9Ik0gNTIgMiBMIDU4IDIwIEwgMzYgMjUgTCA0MCAyMCBaIiBmaWxsPSJ2YXIoLS1jLTMwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDU4IDIwIEwgMzAgNTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTUwMCkiIC8+IDwhLS0gU2hhcnAgSmF3bGluZSBCYXNlIC0tPg0KICAgICAgDQogICAgICA8IS0tID09PSBFWUVTIChJbnRlZ3JhdGVkIERlcHRoKSA9PT0gLS0+DQogICAgICA8cGF0aCBkPSJNIDIwIDIwIEwgMjYgMTUgTCAyNCAyNSBaIiBmaWxsPSJ2YXIoLS1jLTgwMCkiIC8+DQogICAgICA8cGF0aCBkPSJNIDQwIDIwIEwgMzQgMTUgTCAzNiAyNSBaIiBmaWxsPSJ2YXIoLS1jLTcwMCkiIC8+DQogIDwvZz4NCjwvc3ZnPg==" class="logo-img" alt="Fox Logo">
208
+ </div>
209
+ <a href="foxcontroladmin_dashboard" class="nav-item">
210
+ <span class="nav-icon">📊</span>
211
+ <span>控制台</span>
212
+ </a>
213
+ <a href="foxcontroladmin_network" class="nav-item">
214
+ <span class="nav-icon">🌐</span>
215
+ <span>网络管理</span>
216
+ </a>
217
+ <a href="foxcontroladmin_firewall" class="nav-item">
218
+ <span class="nav-icon">🔒</span>
219
+ <span>防火墙</span>
220
+ </a>
221
+ <a href="foxcontroladmin_deploy" class="nav-item active">
222
+ <span class="nav-icon">🚀</span>
223
+ <span>站点部署</span>
224
+ </a>
225
+ <a href="../foxcontrol_edit" class="nav-item" target="_blank">
226
+ <span class="nav-icon">⚡</span>
227
+ <span>流程编辑器</span>
228
+ </a>
229
+ </div>
230
+
231
+ <div class="main-content">
232
+ <div class="header">
233
+ <h1>站点部署</h1>
234
+ <button class="logout-btn" onclick="logout()">退出登录</button>
235
+ </div>
236
+
237
+ <div class="card">
238
+ <h3>🚀 生产环境自动部署</h3>
239
+
240
+ <div class="upload-section" id="drop-zone" onclick="document.getElementById('zipFile').click()">
241
+ <span class="upload-icon">📁</span>
242
+ <p>点击或将 ZIP 压缩包拖拽到此处</p>
243
+ <p style="font-size: 13px; color: var(--text-muted); margin-top: 8px;">仅支持 .zip 格式文件</p>
244
+ </div>
245
+
246
+ <div id="file-info-box">
247
+ <span class="file-name" id="selected-file-name"></span>
248
+ <span class="remove-file" onclick="cancelFile(event)">移除</span>
249
+ </div>
250
+
251
+ <input type="file" id="zipFile" accept=".zip" onchange="handleFile(this)">
252
+
253
+ <button class="btn-deploy" id="deployBtn" onclick="doDeploy()">开始部署</button>
254
+
255
+ <div class="progress-container" id="prog-container">
256
+ <div class="progress-track"><div class="progress-fill" id="prog-fill"></div></div>
257
+ <div class="status-text" id="status-text">准备中...</div>
258
+ </div>
259
+ </div>
260
+ </div>
261
+
262
+ <script>
263
+ // --- 逻辑部分完全保持不变 ---
264
+ const zipFile = document.getElementById('zipFile');
265
+ const infoBox = document.getElementById('file-info-box');
266
+ const dropZone = document.getElementById('drop-zone');
267
+ const fileNameDisp = document.getElementById('selected-file-name');
268
+ const btn = document.getElementById('deployBtn');
269
+ const progContainer = document.getElementById('prog-container');
270
+ const progFill = document.getElementById('prog-fill');
271
+ const statusText = document.getElementById('status-text');
272
+
273
+ function handleFile(input) {
274
+ const file = input.files[0];
275
+ if (file) {
276
+ if (!file.name.endsWith('.zip')) {
277
+ alert('请选择 ZIP 文件');
278
+ cancelFile();
279
+ return;
280
+ }
281
+ fileNameDisp.textContent = file.name;
282
+ infoBox.style.display = 'flex';
283
+ dropZone.style.display = 'none';
284
+ }
285
+ }
286
+
287
+ function cancelFile(e) {
288
+ if(e) e.stopPropagation();
289
+ zipFile.value = '';
290
+ infoBox.style.display = 'none';
291
+ dropZone.style.display = 'block';
292
+ progContainer.style.display = 'none';
293
+ }
294
+
295
+ async function doDeploy() {
296
+ if (!zipFile.files[0]) return alert('请先选择文件');
297
+
298
+ btn.disabled = true;
299
+ progContainer.style.display = 'block';
300
+ statusText.textContent = '正在上传部署包...';
301
+
302
+ let p = 0;
303
+ const timer = setInterval(() => {
304
+ if (p < 90) { p += (95 - p) * 0.1; progFill.style.width = p + '%'; }
305
+ }, 200);
306
+
307
+ const formData = new FormData();
308
+ formData.append('site_zip', zipFile.files[0]);
309
+
310
+ try {
311
+ const res = await fetch('/foxcontrol_api/admin/deploy/upload', { method: 'POST', body: formData });
312
+ const data = await res.json();
313
+ clearInterval(timer);
314
+ progFill.style.width = '100%';
315
+
316
+ if (data.status === 'success') {
317
+ statusText.style.color = '#52c41a';
318
+ statusText.innerHTML = `✅ 部署成功! <a href="${data.url}" target="_blank" class="success-link">查看站点</a>`;
319
+ } else {
320
+ throw new Error(data.message);
321
+ }
322
+ } catch (err) {
323
+ clearInterval(timer);
324
+ statusText.style.color = '#ff4d4f';
325
+ statusText.textContent = '❌ 部署失败: ' + err.message;
326
+ } finally {
327
+ btn.disabled = false;
328
+ }
329
+ }
330
+
331
+ dropZone.ondragover = (e) => { e.preventDefault(); dropZone.style.borderColor = 'var(--primary)'; };
332
+ dropZone.ondragleave = () => { dropZone.style.borderColor = 'var(--card-border)'; };
333
+ dropZone.ondrop = (e) => {
334
+ e.preventDefault();
335
+ if (e.dataTransfer.files[0]) {
336
+ zipFile.files = e.dataTransfer.files;
337
+ handleFile(zipFile);
338
+ }
339
+ };
340
+
341
+ async function logout() {
342
+ if (confirm('确定退出?')) {
343
+ await fetch('/foxcontrol_api/admin/logout', { method: 'POST' });
344
+ window.location.href = '/foxadmin';
345
+ }
346
+ }
347
+ </script>
348
+ </body>
349
+ </html>