prince1604 commited on
Commit
1937b23
·
1 Parent(s): 21eb86b

Add simple dashboard for progress tracking

Browse files
Files changed (2) hide show
  1. api.py +19 -2
  2. static/index.html +338 -0
api.py CHANGED
@@ -1,6 +1,7 @@
1
  from fastapi import FastAPI, BackgroundTasks, Query
2
  from fastapi.middleware.cors import CORSMiddleware as CORS
3
- from fastapi.responses import JSONResponse
 
4
  from pydantic import BaseModel
5
  from uuid import uuid4
6
  from typing import Dict, Any, List, Optional
@@ -21,6 +22,16 @@ logger = logging.getLogger("API")
21
 
22
  app = FastAPI(title="Antigravity SEO Scaler API")
23
 
 
 
 
 
 
 
 
 
 
 
24
  app.add_middleware(
25
  CORS,
26
  allow_origins=["*"],
@@ -29,6 +40,11 @@ app.add_middleware(
29
  allow_headers=["*"],
30
  )
31
 
 
 
 
 
 
32
  # In-memory Job Store
33
  JOBS: Dict[str, Dict[str, Any]] = {}
34
  REPORT_FILE = 'seo_report.json'
@@ -175,7 +191,8 @@ def run_scan_job(job_id: str, domain: str, limit: int, is_auto: bool = False):
175
 
176
  @app.get("/")
177
  def home():
178
- return "Antigravity API (FastAPI) is Running. Use /docs for Swagger UI."
 
179
 
180
  @app.get("/health")
181
  def health_check():
 
1
  from fastapi import FastAPI, BackgroundTasks, Query
2
  from fastapi.middleware.cors import CORSMiddleware as CORS
3
+ from fastapi.responses import JSONResponse, FileResponse
4
+ from fastapi.staticfiles import StaticFiles
5
  from pydantic import BaseModel
6
  from uuid import uuid4
7
  from typing import Dict, Any, List, Optional
 
22
 
23
  app = FastAPI(title="Antigravity SEO Scaler API")
24
 
25
+ app.add_middleware(
26
+ CORS,
27
+ allow_origins=["*"],
28
+ allow_credentials=True,
29
+ allow_methods=["*"],
30
+ allow_headers=["*"],
31
+ from fastapi.staticfiles import StaticFiles
32
+
33
+ # ...
34
+
35
  app.add_middleware(
36
  CORS,
37
  allow_origins=["*"],
 
40
  allow_headers=["*"],
41
  )
42
 
43
+ # Mount Static Files
44
+ if not os.path.exists("static"):
45
+ os.makedirs("static")
46
+ app.mount("/static", StaticFiles(directory="static"), name="static")
47
+
48
  # In-memory Job Store
49
  JOBS: Dict[str, Dict[str, Any]] = {}
50
  REPORT_FILE = 'seo_report.json'
 
191
 
192
  @app.get("/")
193
  def home():
194
+ # Serve the static UI by default
195
+ return FileResponse("static/index.html")
196
 
197
  @app.get("/health")
198
  def health_check():
static/index.html ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Antigravity SEO Scanner</title>
7
+ <style>
8
+ :root {
9
+ --primary: #6366f1;
10
+ --primary-dark: #4f46e5;
11
+ --bg: #0f172a;
12
+ --surface: #1e293b;
13
+ --text: #f8fafc;
14
+ --text-muted: #94a3b8;
15
+ --success: #22c55e;
16
+ --error: #ef4444;
17
+ }
18
+
19
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', system-ui, sans-serif; }
20
+
21
+ body {
22
+ background-color: var(--bg);
23
+ color: var(--text);
24
+ display: flex;
25
+ flex-direction: column;
26
+ align-items: center;
27
+ min-height: 100vh;
28
+ padding: 2rem;
29
+ }
30
+
31
+ .container {
32
+ width: 100%;
33
+ max-width: 800px;
34
+ }
35
+
36
+ header {
37
+ margin-bottom: 2rem;
38
+ text-align: center;
39
+ }
40
+
41
+ h1 {
42
+ font-size: 2.5rem;
43
+ font-weight: 800;
44
+ background: linear-gradient(135deg, #818cf8, #c084fc);
45
+ -webkit-background-clip: text;
46
+ -webkit-text-fill-color: transparent;
47
+ margin-bottom: 0.5rem;
48
+ }
49
+
50
+ .card {
51
+ background: var(--surface);
52
+ border-radius: 1rem;
53
+ padding: 2rem;
54
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
55
+ border: 1px solid rgba(255,255,255,0.05);
56
+ margin-bottom: 1.5rem;
57
+ }
58
+
59
+ .input-group {
60
+ display: flex;
61
+ gap: 1rem;
62
+ margin-bottom: 1rem;
63
+ }
64
+
65
+ input {
66
+ flex: 1;
67
+ padding: 0.75rem 1rem;
68
+ border-radius: 0.5rem;
69
+ border: 1px solid #334155;
70
+ background: #0f172a;
71
+ color: white;
72
+ font-size: 1rem;
73
+ outline: none;
74
+ transition: all 0.2s;
75
+ }
76
+
77
+ input:focus {
78
+ border-color: var(--primary);
79
+ box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.2);
80
+ }
81
+
82
+ button {
83
+ padding: 0.75rem 1.5rem;
84
+ background: var(--primary);
85
+ color: white;
86
+ border: none;
87
+ border-radius: 0.5rem;
88
+ font-weight: 600;
89
+ cursor: pointer;
90
+ transition: all 0.2s;
91
+ }
92
+
93
+ button:hover {
94
+ background: var(--primary-dark);
95
+ transform: translateY(-1px);
96
+ }
97
+
98
+ button:disabled {
99
+ opacity: 0.5;
100
+ cursor: not-allowed;
101
+ }
102
+
103
+ /* Progress Section */
104
+ #progress-section {
105
+ display: none;
106
+ }
107
+
108
+ .progress-container {
109
+ margin-top: 1rem;
110
+ }
111
+
112
+ .progress-labels {
113
+ display: flex;
114
+ justify-content: space-between;
115
+ margin-bottom: 0.5rem;
116
+ font-size: 0.875rem;
117
+ color: var(--text-muted);
118
+ }
119
+
120
+ .progress-bar-bg {
121
+ width: 100%;
122
+ height: 8px;
123
+ background: #334155;
124
+ border-radius: 4px;
125
+ overflow: hidden;
126
+ }
127
+
128
+ .progress-bar {
129
+ height: 100%;
130
+ background: linear-gradient(90deg, var(--primary), #a855f7);
131
+ width: 0%;
132
+ transition: width 0.3s ease;
133
+ }
134
+
135
+ .stats-grid {
136
+ display: grid;
137
+ grid-template-columns: repeat(3, 1fr);
138
+ gap: 1rem;
139
+ margin-top: 1.5rem;
140
+ }
141
+
142
+ .stat-box {
143
+ background: rgba(255,255,255,0.03);
144
+ padding: 1rem;
145
+ border-radius: 0.5rem;
146
+ text-align: center;
147
+ }
148
+
149
+ .stat-value {
150
+ font-size: 1.5rem;
151
+ font-weight: 700;
152
+ margin-bottom: 0.25rem;
153
+ }
154
+
155
+ .stat-label {
156
+ font-size: 0.75rem;
157
+ text-transform: uppercase;
158
+ letter-spacing: 0.05em;
159
+ color: var(--text-muted);
160
+ }
161
+
162
+ .status-msg {
163
+ margin-top: 1.5rem;
164
+ padding: 1rem;
165
+ background: rgba(99, 102, 241, 0.1);
166
+ border-left: 3px solid var(--primary);
167
+ color: #c7d2fe;
168
+ font-family: monospace;
169
+ font-size: 0.9rem;
170
+ }
171
+
172
+ /* Results Section */
173
+ #result-section {
174
+ display: none;
175
+ }
176
+
177
+ pre {
178
+ background: #0f172a;
179
+ padding: 1rem;
180
+ border-radius: 0.5rem;
181
+ overflow-x: auto;
182
+ color: #a5b4fc;
183
+ font-size: 0.85rem;
184
+ }
185
+
186
+ .json-key { color: #9cdcfe; }
187
+ .json-string { color: #ce9178; }
188
+ .json-number { color: #b5cea8; }
189
+ .json-boolean { color: #569cd6; }
190
+
191
+ </style>
192
+ </head>
193
+ <body>
194
+
195
+ <div class="container">
196
+ <header>
197
+ <h1>SEO Scaler</h1>
198
+ <p style="color: var(--text-muted);">Antigravity Async Scanner Interface</p>
199
+ </header>
200
+
201
+ <div class="card">
202
+ <div class="input-group">
203
+ <input type="text" id="domainInput" placeholder="Enter Domain (e.g. https://example.com)" value="https://example.com">
204
+ <input type="number" id="limitInput" placeholder="Limit" value="25" style="max-width: 100px;">
205
+ <button id="scanBtn" onclick="startScan()">Start Scan</button>
206
+ </div>
207
+ </div>
208
+
209
+ <!-- Progress Card -->
210
+ <div id="progress-section" class="card">
211
+ <div class="progress-labels">
212
+ <span id="status-badge">Initializing...</span>
213
+ <span id="percent-badge">0%</span>
214
+ </div>
215
+ <div class="progress-bar-bg">
216
+ <div id="progress-fill" class="progress-bar"></div>
217
+ </div>
218
+
219
+ <div class="stats-grid">
220
+ <div class="stat-box">
221
+ <div id="pages-stat" class="stat-value">0</div>
222
+ <div class="stat-label">Pages Scanned</div>
223
+ </div>
224
+ <div class="stat-box">
225
+ <div id="images-stat" class="stat-value">0</div>
226
+ <div class="stat-label">Images Found</div>
227
+ </div>
228
+ <div class="stat-box">
229
+ <div id="time-stat" class="stat-value">0s</div>
230
+ <div class="stat-label">Elapsed Time</div>
231
+ </div>
232
+ </div>
233
+
234
+ <div id="log-msg" class="status-msg">
235
+ Waiting to start...
236
+ </div>
237
+ </div>
238
+
239
+ <!-- Result Card -->
240
+ <div id="result-section" class="card">
241
+ <h3 style="margin-bottom: 1rem;">Scan Results</h3>
242
+ <pre id="json-output"></pre>
243
+ </div>
244
+ </div>
245
+
246
+ <script>
247
+ let jobId = null;
248
+ let pollInterval = null;
249
+
250
+ async function startScan() {
251
+ const domain = document.getElementById('domainInput').value;
252
+ const limit = parseInt(document.getElementById('limitInput').value);
253
+
254
+ if (!domain) return alert("Please enter a domain");
255
+
256
+ // UI Reset
257
+ document.getElementById('scanBtn').disabled = true;
258
+ document.getElementById('progress-section').style.display = 'block';
259
+ document.getElementById('result-section').style.display = 'none';
260
+
261
+ try {
262
+ // 1. Start Job
263
+ const res = await fetch('/api/scan/start', {
264
+ method: 'POST',
265
+ headers: { 'Content-Type': 'application/json' },
266
+ body: JSON.stringify({ domain, limit })
267
+ });
268
+ const data = await res.json();
269
+ jobId = data.job_id;
270
+
271
+ // 2. Start Polling
272
+ pollInterval = setInterval(pollProgress, 1000);
273
+
274
+ } catch (e) {
275
+ alert("Error starting scan: " + e);
276
+ document.getElementById('scanBtn').disabled = false;
277
+ }
278
+ }
279
+
280
+ async function pollProgress() {
281
+ if (!jobId) return;
282
+
283
+ try {
284
+ const res = await fetch(`/api/scan/progress/${jobId}`);
285
+ const data = await res.json();
286
+
287
+ // Update UI
288
+ document.getElementById('percent-badge').innerText = data.percent + '%';
289
+ document.getElementById('progress-fill').style.width = data.percent + '%';
290
+ document.getElementById('status-badge').innerText = data.status.toUpperCase();
291
+
292
+ document.getElementById('pages-stat').innerText = data.pages_scanned;
293
+ document.getElementById('images-stat').innerText = data.images_found;
294
+ document.getElementById('time-stat').innerText = data.elapsed_seconds + 's';
295
+ document.getElementById('log-msg').innerText = "> " + data.message;
296
+
297
+ if (data.status === 'done' || data.status === 'error') {
298
+ clearInterval(pollInterval);
299
+ document.getElementById('scanBtn').disabled = false;
300
+
301
+ if (data.status === 'done') {
302
+ fetchResult();
303
+ } else {
304
+ document.getElementById('log-msg').innerHTML += '<br><strong style="color:var(--error)">ERROR: ' + data.error + '</strong>';
305
+ }
306
+ }
307
+
308
+ } catch (e) {
309
+ console.error("Poll error", e);
310
+ }
311
+ }
312
+
313
+ async function fetchResult() {
314
+ const res = await fetch(`/api/scan/result/${jobId}`);
315
+ const data = await res.json();
316
+
317
+ document.getElementById('result-section').style.display = 'block';
318
+ document.getElementById('json-output').innerText = JSON.stringify(data, null, 2);
319
+ }
320
+
321
+ // Auto-fill from query params
322
+ window.onload = () => {
323
+ const urlParams = new URLSearchParams(window.location.search);
324
+ const domain = urlParams.get('domain');
325
+ const limit = urlParams.get('limit');
326
+
327
+ if (domain) {
328
+ document.getElementById('domainInput').value = domain;
329
+ if (limit) document.getElementById('limitInput').value = limit;
330
+
331
+ // Auto start
332
+ startScan();
333
+ }
334
+ }
335
+ </script>
336
+
337
+ </body>
338
+ </html>