Aqso commited on
Commit
3cfd884
·
verified ·
1 Parent(s): c2d999d

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -739
index.html DELETED
@@ -1,739 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="id">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Anichin Scraper — Dashboard</title>
7
- <link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Mono:wght@400;500&family=DM+Sans:wght@300;400;500;700&display=swap" rel="stylesheet" />
8
- <style>
9
- /* ── RESET & BASE ── */
10
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
11
-
12
- :root {
13
- --bg: #0a0a0f;
14
- --surface: #111118;
15
- --surface2: #16161f;
16
- --border: #1e1e2e;
17
- --accent: #e64040;
18
- --accent2: #ff7043;
19
- --green: #4ade80;
20
- --yellow: #fbbf24;
21
- --text: #e2e2ef;
22
- --muted: #6b6b8a;
23
- --font-display: 'Bebas Neue', sans-serif;
24
- --font-mono: 'DM Mono', monospace;
25
- --font-body: 'DM Sans', sans-serif;
26
- }
27
-
28
- body {
29
- background: var(--bg);
30
- color: var(--text);
31
- font-family: var(--font-body);
32
- min-height: 100vh;
33
- overflow-x: hidden;
34
- }
35
-
36
- /* ── NOISE OVERLAY ── */
37
- body::before {
38
- content: '';
39
- position: fixed;
40
- inset: 0;
41
- background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noise)' opacity='0.03'/%3E%3C/svg%3E");
42
- pointer-events: none;
43
- z-index: 9999;
44
- opacity: 0.4;
45
- }
46
-
47
- /* ── SIDEBAR ── */
48
- .sidebar {
49
- position: fixed;
50
- left: 0; top: 0; bottom: 0;
51
- width: 240px;
52
- background: var(--surface);
53
- border-right: 1px solid var(--border);
54
- display: flex;
55
- flex-direction: column;
56
- padding: 0;
57
- z-index: 100;
58
- }
59
-
60
- .sidebar-logo {
61
- padding: 28px 24px 20px;
62
- border-bottom: 1px solid var(--border);
63
- }
64
-
65
- .sidebar-logo h1 {
66
- font-family: var(--font-display);
67
- font-size: 28px;
68
- letter-spacing: 2px;
69
- color: var(--accent);
70
- line-height: 1;
71
- }
72
-
73
- .sidebar-logo p {
74
- font-size: 10px;
75
- color: var(--muted);
76
- font-family: var(--font-mono);
77
- margin-top: 4px;
78
- letter-spacing: 1px;
79
- text-transform: uppercase;
80
- }
81
-
82
- .status-pill {
83
- display: inline-flex;
84
- align-items: center;
85
- gap: 6px;
86
- margin-top: 12px;
87
- padding: 4px 10px;
88
- border-radius: 20px;
89
- font-family: var(--font-mono);
90
- font-size: 10px;
91
- background: #1a1a2a;
92
- border: 1px solid var(--border);
93
- }
94
-
95
- .status-dot {
96
- width: 6px; height: 6px;
97
- border-radius: 50%;
98
- background: var(--green);
99
- }
100
-
101
- .status-dot.running {
102
- background: var(--yellow);
103
- animation: pulse 1s infinite;
104
- }
105
-
106
- @keyframes pulse {
107
- 0%, 100% { opacity: 1; }
108
- 50% { opacity: 0.3; }
109
- }
110
-
111
- .nav {
112
- flex: 1;
113
- padding: 16px 12px;
114
- }
115
-
116
- .nav-item {
117
- display: flex;
118
- align-items: center;
119
- gap: 10px;
120
- padding: 10px 12px;
121
- border-radius: 8px;
122
- cursor: pointer;
123
- font-size: 13px;
124
- font-weight: 500;
125
- color: var(--muted);
126
- transition: all 0.15s;
127
- margin-bottom: 2px;
128
- }
129
-
130
- .nav-item:hover { background: var(--surface2); color: var(--text); }
131
- .nav-item.active { background: rgba(230, 64, 64, 0.1); color: var(--accent); border-left: 2px solid var(--accent); }
132
-
133
- .nav-icon { font-size: 16px; width: 20px; text-align: center; }
134
-
135
- /* ── MAIN CONTENT ── */
136
- .main {
137
- margin-left: 240px;
138
- min-height: 100vh;
139
- padding: 32px;
140
- }
141
-
142
- /* ── HEADER ── */
143
- .page-header {
144
- display: flex;
145
- align-items: flex-start;
146
- justify-content: space-between;
147
- margin-bottom: 32px;
148
- }
149
-
150
- .page-title {
151
- font-family: var(--font-display);
152
- font-size: 48px;
153
- letter-spacing: 3px;
154
- line-height: 1;
155
- background: linear-gradient(135deg, var(--text) 0%, var(--muted) 100%);
156
- -webkit-background-clip: text;
157
- -webkit-text-fill-color: transparent;
158
- }
159
-
160
- .page-sub {
161
- font-family: var(--font-mono);
162
- font-size: 11px;
163
- color: var(--muted);
164
- margin-top: 6px;
165
- letter-spacing: 1px;
166
- }
167
-
168
- /* ── STAT CARDS ── */
169
- .stats-grid {
170
- display: grid;
171
- grid-template-columns: repeat(4, 1fr);
172
- gap: 16px;
173
- margin-bottom: 28px;
174
- }
175
-
176
- .stat-card {
177
- background: var(--surface);
178
- border: 1px solid var(--border);
179
- border-radius: 12px;
180
- padding: 20px;
181
- position: relative;
182
- overflow: hidden;
183
- }
184
-
185
- .stat-card::after {
186
- content: '';
187
- position: absolute;
188
- top: 0; left: 0; right: 0;
189
- height: 2px;
190
- background: linear-gradient(90deg, var(--accent), var(--accent2));
191
- }
192
-
193
- .stat-label {
194
- font-family: var(--font-mono);
195
- font-size: 10px;
196
- color: var(--muted);
197
- text-transform: uppercase;
198
- letter-spacing: 1px;
199
- }
200
-
201
- .stat-value {
202
- font-family: var(--font-display);
203
- font-size: 38px;
204
- letter-spacing: 2px;
205
- margin-top: 8px;
206
- color: var(--text);
207
- }
208
-
209
- /* ── PANELS ── */
210
- .panel {
211
- background: var(--surface);
212
- border: 1px solid var(--border);
213
- border-radius: 12px;
214
- margin-bottom: 20px;
215
- }
216
-
217
- .panel-header {
218
- padding: 16px 20px;
219
- border-bottom: 1px solid var(--border);
220
- display: flex;
221
- align-items: center;
222
- justify-content: space-between;
223
- }
224
-
225
- .panel-title {
226
- font-family: var(--font-mono);
227
- font-size: 12px;
228
- color: var(--muted);
229
- text-transform: uppercase;
230
- letter-spacing: 2px;
231
- }
232
-
233
- .panel-body { padding: 20px; }
234
-
235
- /* ── SCRAPE CONTROLS ── */
236
- .controls-grid {
237
- display: grid;
238
- grid-template-columns: 1fr 1fr;
239
- gap: 16px;
240
- }
241
-
242
- .control-card {
243
- background: var(--surface2);
244
- border: 1px solid var(--border);
245
- border-radius: 10px;
246
- padding: 20px;
247
- }
248
-
249
- .control-card h3 {
250
- font-size: 14px;
251
- font-weight: 600;
252
- margin-bottom: 6px;
253
- }
254
-
255
- .control-card p {
256
- font-size: 12px;
257
- color: var(--muted);
258
- margin-bottom: 16px;
259
- line-height: 1.5;
260
- }
261
-
262
- .input-row {
263
- display: flex;
264
- gap: 10px;
265
- margin-bottom: 12px;
266
- }
267
-
268
- input[type=text], input[type=number], input[type=url] {
269
- flex: 1;
270
- background: var(--bg);
271
- border: 1px solid var(--border);
272
- border-radius: 6px;
273
- padding: 8px 12px;
274
- color: var(--text);
275
- font-family: var(--font-mono);
276
- font-size: 12px;
277
- outline: none;
278
- transition: border 0.2s;
279
- }
280
-
281
- input:focus { border-color: var(--accent); }
282
-
283
- /* ── BUTTONS ── */
284
- .btn {
285
- padding: 9px 18px;
286
- border-radius: 6px;
287
- font-family: var(--font-mono);
288
- font-size: 12px;
289
- font-weight: 500;
290
- cursor: pointer;
291
- border: none;
292
- transition: all 0.15s;
293
- white-space: nowrap;
294
- }
295
-
296
- .btn-primary {
297
- background: var(--accent);
298
- color: white;
299
- }
300
-
301
- .btn-primary:hover { background: #c0392b; }
302
-
303
- .btn-secondary {
304
- background: transparent;
305
- border: 1px solid var(--border);
306
- color: var(--muted);
307
- }
308
-
309
- .btn-secondary:hover { border-color: var(--text); color: var(--text); }
310
-
311
- .btn-green {
312
- background: rgba(74, 222, 128, 0.1);
313
- border: 1px solid rgba(74, 222, 128, 0.3);
314
- color: var(--green);
315
- }
316
-
317
- .btn-green:hover { background: rgba(74, 222, 128, 0.2); }
318
-
319
- .btn:disabled { opacity: 0.4; cursor: not-allowed; }
320
-
321
- /* ── LOG TERMINAL ── */
322
- .terminal {
323
- background: #060609;
324
- border: 1px solid var(--border);
325
- border-radius: 8px;
326
- padding: 16px;
327
- height: 220px;
328
- overflow-y: auto;
329
- font-family: var(--font-mono);
330
- font-size: 11px;
331
- line-height: 1.8;
332
- }
333
-
334
- .terminal::-webkit-scrollbar { width: 4px; }
335
- .terminal::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
336
-
337
- .log-entry { color: var(--muted); }
338
- .log-entry.info { color: var(--text); }
339
- .log-entry.success { color: var(--green); }
340
- .log-entry.error { color: var(--accent); }
341
- .log-entry.warn { color: var(--yellow); }
342
-
343
- .log-time {
344
- color: var(--border);
345
- margin-right: 8px;
346
- }
347
-
348
- /* ── ANIME TABLE ── */
349
- .anime-grid {
350
- display: grid;
351
- grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
352
- gap: 14px;
353
- }
354
-
355
- .anime-card {
356
- background: var(--surface2);
357
- border: 1px solid var(--border);
358
- border-radius: 8px;
359
- overflow: hidden;
360
- transition: transform 0.15s, border-color 0.15s;
361
- cursor: pointer;
362
- }
363
-
364
- .anime-card:hover {
365
- transform: translateY(-2px);
366
- border-color: var(--accent);
367
- }
368
-
369
- .anime-thumb {
370
- width: 100%;
371
- aspect-ratio: 2/3;
372
- object-fit: cover;
373
- background: var(--border);
374
- display: block;
375
- }
376
-
377
- .anime-thumb-placeholder {
378
- width: 100%;
379
- aspect-ratio: 2/3;
380
- background: linear-gradient(135deg, #1a1a2e, #16213e);
381
- display: flex;
382
- align-items: center;
383
- justify-content: center;
384
- font-size: 32px;
385
- }
386
-
387
- .anime-info {
388
- padding: 10px;
389
- }
390
-
391
- .anime-title {
392
- font-size: 11px;
393
- font-weight: 600;
394
- line-height: 1.3;
395
- margin-bottom: 4px;
396
- display: -webkit-box;
397
- -webkit-line-clamp: 2;
398
- -webkit-box-orient: vertical;
399
- overflow: hidden;
400
- }
401
-
402
- .anime-meta {
403
- display: flex;
404
- gap: 4px;
405
- }
406
-
407
- .badge {
408
- font-family: var(--font-mono);
409
- font-size: 9px;
410
- padding: 2px 5px;
411
- border-radius: 3px;
412
- background: var(--border);
413
- color: var(--muted);
414
- }
415
-
416
- .badge.ongoing { background: rgba(74, 222, 128, 0.1); color: var(--green); }
417
- .badge.completed { background: rgba(96, 165, 250, 0.1); color: #60a5fa; }
418
-
419
- /* ── PROGRESS BAR ── */
420
- .progress-wrap {
421
- background: var(--border);
422
- border-radius: 99px;
423
- height: 4px;
424
- margin-top: 12px;
425
- overflow: hidden;
426
- }
427
-
428
- .progress-bar {
429
- height: 100%;
430
- background: linear-gradient(90deg, var(--accent), var(--accent2));
431
- border-radius: 99px;
432
- transition: width 0.5s ease;
433
- }
434
-
435
- /* ── TABS ── */
436
- .tabs {
437
- display: flex;
438
- gap: 2px;
439
- margin-bottom: 20px;
440
- background: var(--surface2);
441
- border-radius: 8px;
442
- padding: 4px;
443
- width: fit-content;
444
- }
445
-
446
- .tab {
447
- padding: 7px 16px;
448
- border-radius: 6px;
449
- font-family: var(--font-mono);
450
- font-size: 11px;
451
- cursor: pointer;
452
- color: var(--muted);
453
- transition: all 0.15s;
454
- }
455
-
456
- .tab.active {
457
- background: var(--surface);
458
- color: var(--text);
459
- }
460
-
461
- /* ── SEARCH ── */
462
- .search-bar {
463
- display: flex;
464
- gap: 10px;
465
- margin-bottom: 20px;
466
- }
467
-
468
- /* ── VIEWS ── */
469
- .view { display: none; }
470
- .view.active { display: block; }
471
-
472
- /* ── TOAST ── */
473
- #toast {
474
- position: fixed;
475
- bottom: 24px;
476
- right: 24px;
477
- padding: 12px 20px;
478
- border-radius: 8px;
479
- font-family: var(--font-mono);
480
- font-size: 12px;
481
- background: var(--surface);
482
- border: 1px solid var(--border);
483
- color: var(--text);
484
- transform: translateY(80px);
485
- opacity: 0;
486
- transition: all 0.3s;
487
- z-index: 9999;
488
- }
489
-
490
- #toast.show { transform: translateY(0); opacity: 1; }
491
- #toast.success { border-color: var(--green); color: var(--green); }
492
- #toast.error { border-color: var(--accent); color: var(--accent); }
493
-
494
- /* ── EMPTY STATE ── */
495
- .empty {
496
- text-align: center;
497
- padding: 60px 20px;
498
- color: var(--muted);
499
- }
500
-
501
- .empty-icon { font-size: 48px; margin-bottom: 16px; }
502
- .empty h3 { font-size: 16px; margin-bottom: 8px; color: var(--text); }
503
- .empty p { font-size: 12px; line-height: 1.5; }
504
-
505
- /* ── PAGINATION ── */
506
- .pagination {
507
- display: flex;
508
- align-items: center;
509
- justify-content: center;
510
- gap: 8px;
511
- margin-top: 24px;
512
- }
513
-
514
- /* ── RESPONSIVE ── */
515
- @media (max-width: 900px) {
516
- .sidebar { width: 60px; }
517
- .sidebar-logo h1, .sidebar-logo p, .status-pill, .nav-item span { display: none; }
518
- .main { margin-left: 60px; }
519
- .stats-grid { grid-template-columns: 1fr 1fr; }
520
- }
521
- </style>
522
- </head>
523
- <body>
524
-
525
- <!-- ── SIDEBAR ── -->
526
- <aside class="sidebar">
527
- <div class="sidebar-logo">
528
- <h1>ANICHIN</h1>
529
- <p>Scraper Dashboard</p>
530
- <div class="status-pill">
531
- <span class="status-dot" id="statusDot"></span>
532
- <span id="statusText">Ready</span>
533
- </div>
534
- </div>
535
-
536
- <nav class="nav">
537
- <div class="nav-item active" onclick="switchView('scraper')">
538
- <span class="nav-icon">⚡</span>
539
- <span>Scraper</span>
540
- </div>
541
- <div class="nav-item" onclick="switchView('library')">
542
- <span class="nav-icon">🗃️</span>
543
- <span>Library</span>
544
- </div>
545
- <div class="nav-item" onclick="switchView('logs')">
546
- <span class="nav-icon">📋</span>
547
- <span>Logs</span>
548
- </div>
549
- </nav>
550
- </aside>
551
-
552
- <!-- ── MAIN ── -->
553
- <main class="main">
554
-
555
- <!-- ── VIEW: SCRAPER ── -->
556
- <div class="view active" id="view-scraper">
557
- <div class="page-header">
558
- <div>
559
- <div class="page-title">SCRAPER</div>
560
- <div class="page-sub">// anichin.cafe → firebase firestore</div>
561
- </div>
562
- <button class="btn btn-secondary" onclick="refreshStats()">⟳ Refresh Stats</button>
563
- </div>
564
-
565
- <!-- Stats -->
566
- <div class="stats-grid">
567
- <div class="stat-card">
568
- <div class="stat-label">Total Anime</div>
569
- <div class="stat-value" id="statTotal">—</div>
570
- </div>
571
- <div class="stat-card">
572
- <div class="stat-label">Ongoing</div>
573
- <div class="stat-value" id="statOngoing">—</div>
574
- </div>
575
- <div class="stat-card">
576
- <div class="stat-label">Last Scrape</div>
577
- <div class="stat-value" style="font-size:16px; margin-top:16px;" id="statLastRun">Never</div>
578
- </div>
579
- <div class="stat-card">
580
- <div class="stat-label">Status</div>
581
- <div class="stat-value" style="font-size:22px; margin-top:12px;" id="statStatus">IDLE</div>
582
- </div>
583
- </div>
584
-
585
- <!-- Controls -->
586
- <div class="panel">
587
- <div class="panel-header">
588
- <span class="panel-title">// Scrape Controls</span>
589
- <span style="font-family:var(--font-mono);font-size:10px;color:var(--muted)" id="progressLabel"></span>
590
- </div>
591
- <div class="panel-body">
592
- <div class="progress-wrap" id="progressWrap" style="display:none">
593
- <div class="progress-bar" id="progressBar" style="width:0%"></div>
594
- </div>
595
-
596
- <div class="controls-grid" style="margin-top:16px">
597
- <!-- Full Scrape -->
598
- <div class="control-card">
599
- <h3>🚀 Full Scrape</h3>
600
- <p>Scrape semua anime dari halaman list + detail lengkap. Cocok buat pertama kali.</p>
601
- <div class="input-row">
602
- <input type="number" id="pagesInput" value="10" min="1" max="100" placeholder="Jumlah halaman" />
603
- </div>
604
- <button class="btn btn-primary" id="btnFullScrape" onclick="startFullScrape()">
605
- ▶ Mulai Full Scrape
606
- </button>
607
- </div>
608
-
609
- <!-- Incremental Update -->
610
- <div class="control-card">
611
- <h3>🔄 Incremental Update</h3>
612
- <p>Cek episode baru untuk anime yang masih ongoing. Ringan dan cepet.</p>
613
- <p style="margin-bottom:8px;color:var(--yellow)">⏰ Auto-run setiap hari 06:00 WIB</p>
614
- <button class="btn btn-green" id="btnUpdate" onclick="startUpdate()">
615
- ↻ Update Sekarang
616
- </button>
617
- </div>
618
-
619
- <!-- Single Anime -->
620
- <div class="control-card">
621
- <h3>🎯 Scrape Anime Tertentu</h3>
622
- <p>Input URL anime dari anichin.cafe untuk scrape satu anime aja.</p>
623
- <div class="input-row">
624
- <input type="url" id="singleUrl" placeholder="https://anichin.cafe/anime/..." />
625
- </div>
626
- <button class="btn btn-secondary" onclick="scrapeSingle()">Scrape</button>
627
- </div>
628
-
629
- <!-- Search Scrape -->
630
- <div class="control-card">
631
- <h3>🔍 Search & Scrape</h3>
632
- <p>Cari anime berdasarkan judul dan scrape hasilnya ke database.</p>
633
- <div class="input-row">
634
- <input type="text" id="searchQuery" placeholder="Nama anime..." />
635
- </div>
636
- <button class="btn btn-secondary" onclick="searchScrape()">Cari & Scrape</button>
637
- </div>
638
- </div>
639
- </div>
640
- </div>
641
-
642
- <!-- Live Log -->
643
- <div class="panel">
644
- <div class="panel-header">
645
- <span class="panel-title">// Live Log</span>
646
- <button class="btn btn-secondary" onclick="clearLog()" style="padding:4px 10px;font-size:10px">Clear</button>
647
- </div>
648
- <div class="panel-body" style="padding:12px">
649
- <div class="terminal" id="terminal">
650
- <div class="log-entry info">
651
- <span class="log-time">--:--:--</span>
652
- Anichin Scraper siap. Tekan tombol scrape untuk mulai.
653
- </div>
654
- </div>
655
- </div>
656
- </div>
657
- </div>
658
-
659
- <!-- ── VIEW: LIBRARY ── -->
660
- <div class="view" id="view-library">
661
- <div class="page-header">
662
- <div>
663
- <div class="page-title">LIBRARY</div>
664
- <div class="page-sub">// anime database dari firebase</div>
665
- </div>
666
- </div>
667
-
668
- <div class="search-bar">
669
- <input type="text" id="libSearch" placeholder="🔍 Cari anime..." style="max-width:300px" oninput="debounceSearch()" />
670
- <select id="libStatus" onchange="loadLibrary()" style="background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:8px 12px;color:var(--text);font-family:var(--font-mono);font-size:12px;outline:none;">
671
- <option value="">Semua Status</option>
672
- <option value="Ongoing">Ongoing</option>
673
- <option value="Completed">Completed</option>
674
- </select>
675
- </div>
676
-
677
- <div class="anime-grid" id="animeGrid">
678
- <div class="empty" style="grid-column:1/-1">
679
- <div class="empty-icon">📦</div>
680
- <h3>Library Kosong</h3>
681
- <p>Belum ada anime. Jalankan scraper dulu!</p>
682
- </div>
683
- </div>
684
-
685
- <div class="pagination" id="pagination"></div>
686
- </div>
687
-
688
- <!-- ── VIEW: LOGS ── -->
689
- <div class="view" id="view-logs">
690
- <div class="page-header">
691
- <div>
692
- <div class="page-title">LOGS</div>
693
- <div class="page-sub">// riwayat scraping activity</div>
694
- </div>
695
- <button class="btn btn-secondary" onclick="loadLogs()">⟳ Refresh</button>
696
- </div>
697
-
698
- <div class="panel">
699
- <div class="panel-body" style="padding:12px">
700
- <div class="terminal" id="fullLog" style="height:500px"></div>
701
- </div>
702
- </div>
703
- </div>
704
-
705
- </main>
706
-
707
- <!-- Toast -->
708
- <div id="toast"></div>
709
-
710
- <script>
711
- // ── STATE ──────────────────────────────────────────────────────────────────
712
- let currentPage = 1;
713
- let pollInterval = null;
714
- let searchTimeout = null;
715
-
716
- const API = window.location.origin + '/api';
717
-
718
- // ── INIT ──────────────────────────────────────────────────────────────────
719
- document.addEventListener('DOMContentLoaded', () => {
720
- refreshStats();
721
- startPolling();
722
- });
723
-
724
- // ── VIEWS ─────────────────────────────────────────────────────────────────
725
- function switchView(name) {
726
- document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
727
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
728
-
729
- document.getElementById(`view-${name}`).classList.add('active');
730
- event.currentTarget.classList.add('active');
731
-
732
- if (name === 'library') { currentPage = 1; loadLibrary(); }
733
- if (name === 'logs') loadLogs();
734
- }
735
-
736
- // ── STATS ─────────────────────────────────────────────────────────────────
737
- async function refreshStats() {
738
- try {
739
- const res = await fetch(