Baskar2005 commited on
Commit
029016a
·
verified ·
1 Parent(s): 701bf65

Upload index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +226 -0
templates/index.html ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>InstaSpy - Bulk Scraper SaaS</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://unpkg.com/lucide@latest"></script>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
+
11
+ <script>
12
+ tailwind.config = {
13
+ theme: {
14
+ extend: {
15
+ fontFamily: { sans: ['Inter', 'sans-serif'] },
16
+ animation: { 'fade-in': 'fadeIn 0.5s ease-out forwards' },
17
+ keyframes: {
18
+ fadeIn: {
19
+ '0%': { opacity: '0', transform: 'translateY(10px)' },
20
+ '100%': { opacity: '1', transform: 'translateY(0)' },
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ </script>
27
+
28
+ <style>
29
+ body { background-color: #020617; color: #e2e8f0; }
30
+ .glass-panel {
31
+ background: rgba(15, 23, 42, 0.6);
32
+ backdrop-filter: blur(12px);
33
+ border: 1px solid rgba(148, 163, 184, 0.1);
34
+ }
35
+ .gradient-text {
36
+ background: linear-gradient(135deg, #f472b6 0%, #a855f7 100%);
37
+ -webkit-background-clip: text;
38
+ -webkit-text-fill-color: transparent;
39
+ }
40
+ </style>
41
+ </head>
42
+ <body class="min-h-screen flex flex-col font-sans selection:bg-pink-500 selection:text-white">
43
+
44
+ <nav class="border-b border-slate-800 bg-slate-950/80 sticky top-0 z-50 backdrop-blur-md">
45
+ <div class="max-w-7xl mx-auto px-6 h-16 flex items-center justify-between">
46
+ <div class="flex items-center gap-2">
47
+ <div class="bg-gradient-to-br from-pink-500 to-purple-600 p-1.5 rounded-lg shadow-lg shadow-purple-500/20">
48
+ <i data-lucide="instagram" class="w-5 h-5 text-white"></i>
49
+ </div>
50
+ <span class="font-bold text-xl tracking-tight text-white">InstaSpy<span class="text-pink-500">.io</span></span>
51
+ </div>
52
+ </div>
53
+ </nav>
54
+
55
+ <main class="flex-grow max-w-7xl mx-auto px-6 py-12 w-full space-y-12">
56
+ <div class="text-center space-y-4 animate-fade-in">
57
+ <h1 class="text-4xl md:text-5xl font-extrabold tracking-tight text-white">
58
+ Instagram <span class="gradient-text">Bulk Data Scraper</span>
59
+ </h1>
60
+ <p class="text-slate-400 max-w-2xl mx-auto text-lg leading-relaxed">
61
+ Extract <span class="text-white font-medium">Views, Likes, and Followers</span> from Reels, Posts, and Profiles instantly.
62
+ </p>
63
+ </div>
64
+
65
+ <div class="glass-panel rounded-2xl p-1.5 shadow-2xl shadow-purple-900/10 animate-fade-in" style="animation-delay: 0.1s;">
66
+ <div class="bg-[#0f172a] rounded-xl p-4">
67
+ <div class="flex justify-between mb-2">
68
+ <label class="text-xs font-semibold uppercase tracking-wider text-slate-500">Target URLs</label>
69
+ <button class="text-xs text-pink-400 hover:text-pink-300 transition" onclick="pasteExample()">Paste Example Links</button>
70
+ </div>
71
+ <textarea id="urlInput" placeholder="Paste Instagram links here (separated by commas or new lines)..." class="w-full h-48 bg-transparent text-slate-300 placeholder:text-slate-600 focus:outline-none resize-none font-mono text-sm leading-6 custom-scrollbar"></textarea>
72
+ </div>
73
+
74
+ <div class="flex justify-between items-center px-4 py-3 bg-slate-900/50 rounded-b-xl border-t border-slate-800">
75
+ <span id="linkCount" class="text-xs text-slate-500 font-mono">0 Links detected</span>
76
+ <button id="scrapeBtn" onclick="startScrape()" class="flex items-center gap-2 px-6 py-2.5 rounded-lg font-semibold text-sm transition-all bg-gradient-to-r from-pink-600 to-purple-600 hover:from-pink-500 hover:to-purple-500 text-white shadow-lg shadow-pink-900/20 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed">
77
+ <i data-lucide="play" class="w-4 h-4 fill-current"></i> Start Extraction
78
+ </button>
79
+ </div>
80
+ </div>
81
+
82
+ <div id="resultsArea" class="hidden space-y-6 animate-fade-in">
83
+ <div class="border border-slate-800 rounded-xl overflow-hidden bg-slate-900/40 backdrop-blur-sm">
84
+ <div class="flex items-center justify-between p-4 border-b border-slate-800 bg-slate-900/60">
85
+ <h3 class="font-semibold text-white flex items-center gap-2 text-sm">
86
+ <i data-lucide="database" class="w-4 h-4 text-slate-400"></i> Extracted Data
87
+ </h3>
88
+ <button onclick="downloadCSV()" class="flex items-center gap-2 px-3 py-1.5 bg-slate-800 hover:bg-slate-700 hover:text-white text-xs font-medium rounded-lg text-slate-300 transition border border-slate-700">
89
+ <i data-lucide="download" class="w-3.5 h-3.5"></i> Export Excel
90
+ </button>
91
+ </div>
92
+
93
+ <div class="overflow-x-auto">
94
+ <table class="w-full text-sm text-left">
95
+ <thead class="text-xs text-slate-400 uppercase bg-slate-900/80 border-b border-slate-800">
96
+ <tr>
97
+ <th class="px-6 py-3 font-medium">Type</th>
98
+ <th class="px-6 py-3 font-medium">Source URL</th>
99
+ <th class="px-6 py-3 font-medium">Author</th>
100
+ <th class="px-6 py-3 font-medium text-right">Followers</th>
101
+ <th class="px-6 py-3 font-medium text-right">Likes</th>
102
+ <th class="px-6 py-3 font-medium text-right">Views</th>
103
+ <th class="px-6 py-3 font-medium text-center">Status</th>
104
+ </tr>
105
+ </thead>
106
+ <tbody id="tableBody" class="divide-y divide-slate-800/50"></tbody>
107
+ </table>
108
+ </div>
109
+ </div>
110
+ </div>
111
+ </main>
112
+
113
+ <script>
114
+ lucide.createIcons();
115
+ const urlInput = document.getElementById('urlInput');
116
+ const scrapeBtn = document.getElementById('scrapeBtn');
117
+ const resultsArea = document.getElementById('resultsArea');
118
+ const tableBody = document.getElementById('tableBody');
119
+
120
+ urlInput.addEventListener('input', () => {
121
+ const lines = urlInput.value.split('\n').filter(line => line.trim() !== '');
122
+ document.getElementById('linkCount').innerText = `${lines.length} Links detected`;
123
+ });
124
+
125
+ function pasteExample() {
126
+ urlInput.value = `https://www.instagram.com/reel/DTQEBbwEs_K/\nhttps://www.instagram.com/devs_clicks12/\nhttps://www.instagram.com/reel/DTSetANkglg/`;
127
+ urlInput.dispatchEvent(new Event('input'));
128
+ }
129
+
130
+ async function startScrape() {
131
+ const rawInput = urlInput.value.trim();
132
+ // Split by comma OR newline, then filter empty strings
133
+ const urls = rawInput.split(/[\n,]+/).map(u => u.trim()).filter(u => u !== '');
134
+
135
+ if (urls.length === 0) { alert("Please enter at least one URL"); return; }
136
+
137
+ scrapeBtn.disabled = true;
138
+ scrapeBtn.innerHTML = `<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i> Processing...`;
139
+ lucide.createIcons();
140
+
141
+ try {
142
+ // CALL PYTHON BACKEND
143
+ const response = await fetch('/api/scrape', {
144
+ method: 'POST',
145
+ headers: { 'Content-Type': 'application/json' },
146
+ body: JSON.stringify({ urls: urls })
147
+ });
148
+
149
+ const data = await response.json();
150
+ renderTable(data);
151
+ resultsArea.classList.remove('hidden');
152
+ resultsArea.scrollIntoView({ behavior: 'smooth' });
153
+
154
+ } catch (error) {
155
+ alert("Error connecting to server. Is app.py running?");
156
+ console.error(error);
157
+ }
158
+
159
+ scrapeBtn.disabled = false;
160
+ scrapeBtn.innerHTML = `<i data-lucide="play" class="w-4 h-4 fill-current"></i> Start Extraction`;
161
+ lucide.createIcons();
162
+ }
163
+
164
+ function renderTable(data) {
165
+ tableBody.innerHTML = '';
166
+ data.forEach(row => {
167
+ const tr = document.createElement('tr');
168
+ tr.className = "hover:bg-slate-800/40 transition-colors group";
169
+
170
+ const typeColor = row.type === 'REEL' ? 'text-pink-400 bg-pink-400/10 border-pink-400/20'
171
+ : row.type === 'PROFILE' ? 'text-purple-400 bg-purple-400/10 border-purple-400/20'
172
+ : 'text-blue-400 bg-blue-400/10 border-blue-400/20';
173
+
174
+ const statusColor = row.status === 'Success' ? 'text-emerald-400 bg-emerald-400/10' : 'text-amber-400 bg-amber-400/10';
175
+
176
+ // Truncate URL for display
177
+ const shortUrl = row.url.length > 30 ? row.url.substring(0, 30) + "..." : row.url;
178
+
179
+ tr.innerHTML = `
180
+ <td class="px-6 py-4"><span class="px-2 py-1 rounded text-[10px] font-bold border uppercase ${typeColor}">${row.type}</span></td>
181
+
182
+ <td class="px-6 py-4">
183
+ <a href="${row.url}" target="_blank" class="text-xs text-slate-500 hover:text-pink-400 hover:underline transition font-mono flex items-center gap-1" title="${row.url}">
184
+ ${shortUrl}
185
+ <i data-lucide="external-link" class="w-3 h-3"></i>
186
+ </a>
187
+ </td>
188
+
189
+ <td class="px-6 py-4 font-medium text-white">@${row.author || 'N/A'}</td>
190
+ <td class="px-6 py-4 text-right text-slate-300 font-mono">${row.followers}</td>
191
+ <td class="px-6 py-4 text-right text-slate-300 font-mono">${row.likes}</td>
192
+ <td class="px-6 py-4 text-right text-white font-bold font-mono">${row.views}</td>
193
+ <td class="px-6 py-4 text-center">
194
+ <span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium ${statusColor}">
195
+ <span class="w-1.5 h-1.5 rounded-full ${row.status === 'Success' ? 'bg-emerald-400' : 'bg-amber-400'}"></span>
196
+ ${row.status}
197
+ </span>
198
+ </td>
199
+ `;
200
+ tableBody.appendChild(tr);
201
+ });
202
+ lucide.createIcons();
203
+ }
204
+
205
+ function downloadCSV() {
206
+ const rows = Array.from(document.querySelectorAll("table tr"));
207
+ const csvContent = rows.map(row => {
208
+ const cells = Array.from(row.querySelectorAll("th, td"));
209
+ return cells.map(cell => {
210
+ // Check if cell has a link (for the URL column)
211
+ const link = cell.querySelector("a");
212
+ const text = link ? link.getAttribute("href") : cell.innerText;
213
+ return `"${text.replace(/(\r\n|\n|\r)/gm, " ").trim()}"`;
214
+ }).join(",");
215
+ }).join("\n");
216
+
217
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
218
+ const url = URL.createObjectURL(blob);
219
+ const link = document.createElement("a");
220
+ link.href = url;
221
+ link.download = "instagram_export.csv";
222
+ link.click();
223
+ }
224
+ </script>
225
+ </body>
226
+ </html>