Teep002 commited on
Commit
dda9f5e
·
verified ·
1 Parent(s): 363e325

Make the left column fixed not scrollable and make the right column scrollable to be able to scroll - Follow Up Deployment

Browse files
Files changed (1) hide show
  1. index.html +222 -780
index.html CHANGED
@@ -3,819 +3,261 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Security Scanner | Comprehensive Threat Analysis</title>
 
 
7
  <style>
8
- :root {
9
- --bg-color: #1a1a1a;
10
- --text-color: #e0e0e0;
11
- --card-bg: #2d2d2d;
12
- --border-color: #444;
13
- --primary-color: #3498db;
14
- --primary-hover: #2980b9;
15
- --secondary-text: #b0b0b0;
16
  }
17
-
18
- .light-mode {
19
- --bg-color: #f5f7fa;
20
- --text-color: #333;
21
- --card-bg: white;
22
- --border-color: #e1e4e8;
23
- --secondary-text: #7f8c8d;
24
- }
25
-
26
- body {
27
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
28
- line-height: 1.6;
29
- margin: 0;
30
- padding: 0;
31
- background-color: var(--bg-color);
32
- color: var(--text-color);
33
- transition: background-color 0.3s, color 0.3s;
34
- }
35
-
36
- .container {
37
- max-width: 800px;
38
- margin: 0 auto;
39
- padding: 20px;
40
- position: relative;
41
- }
42
-
43
- header {
44
- text-align: center;
45
- margin-bottom: 30px;
46
- padding: 20px 0;
47
- border-bottom: 1px solid var(--border-color);
48
- position: relative;
49
- }
50
-
51
- h1 {
52
- color: var(--primary-color);
53
- margin-bottom: 10px;
54
- }
55
-
56
- .description {
57
- color: var(--secondary-text);
58
- margin-bottom: 20px;
59
- }
60
-
61
- .scanner-form {
62
- background: var(--card-bg);
63
- padding: 25px;
64
- border-radius: 8px;
65
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
66
- margin-bottom: 30px;
67
  }
68
-
69
- .form-group {
70
- margin-bottom: 20px;
71
- }
72
-
73
- label {
74
- display: block;
75
- margin-bottom: 8px;
76
- font-weight: 600;
77
  }
78
-
79
- input[type="text"] {
80
- width: 100%;
81
- padding: 10px;
82
- border: 1px solid #ddd;
83
- border-radius: 4px;
84
- font-size: 16px;
85
  }
86
-
87
- .radio-group {
88
- display: flex;
89
- gap: 20px;
90
- margin-bottom: 15px;
91
- justify-content: center;
92
- flex-wrap: wrap;
93
- }
94
-
95
- .radio-option {
96
- display: flex;
97
- align-items: center;
98
- }
99
-
100
- .radio-option input {
101
- margin-right: 8px;
102
- }
103
-
104
- button {
105
- background-color: #3498db;
106
- color: white;
107
- border: none;
108
- padding: 12px 20px;
109
- border-radius: 4px;
110
- cursor: pointer;
111
- font-size: 16px;
112
- font-weight: 600;
113
- transition: background-color 0.3s;
114
- width: 100%;
115
- }
116
-
117
- button:hover {
118
- background-color: #2980b9;
119
  }
120
-
121
- button:disabled {
122
- background-color: #95a5a6;
123
- cursor: not-allowed;
124
- }
125
-
126
- .results {
127
- background: white;
128
- padding: 25px;
129
- border-radius: 8px;
130
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
131
- display: none;
132
- }
133
-
134
- .result-item {
135
- margin-bottom: 15px;
136
- padding-bottom: 15px;
137
- border-bottom: 1px solid #eee;
138
- }
139
-
140
- .result-item:last-child {
141
- border-bottom: none;
142
- }
143
-
144
- .result-title {
145
- font-weight: 600;
146
- color: #2c3e50;
147
- margin-bottom: 5px;
148
- }
149
-
150
- .loading {
151
- text-align: center;
152
- display: none;
153
- margin: 20px 0;
154
- }
155
-
156
- .spinner {
157
- border: 4px solid rgba(0, 0, 0, 0.1);
158
- border-radius: 50%;
159
- border-top: 4px solid #3498db;
160
- width: 30px;
161
- height: 30px;
162
- animation: spin 1s linear infinite;
163
- margin: 0 auto 10px;
164
- }
165
-
166
- @keyframes spin {
167
  0% { transform: rotate(0deg); }
168
  100% { transform: rotate(360deg); }
169
  }
170
-
171
- .error-message {
172
- color: #e74c3c;
173
- margin-top: 5px;
174
- font-size: 14px;
175
- display: none;
176
- }
177
-
178
- footer {
179
- text-align: center;
180
- margin-top: 40px;
181
- padding: 20px 0;
182
- color: #7f8c8d;
183
- font-size: 14px;
184
- }
185
-
186
- .hash-info {
187
- font-size: 13px;
188
- color: #7f8c8d;
189
- margin-top: 5px;
190
- }
191
-
192
- /* Modal Styles */
193
- .modal {
194
- display: none;
195
- position: fixed;
196
- z-index: 1;
197
- left: 0;
198
- top: 0;
199
- width: 100%;
200
- height: 100%;
201
- overflow: auto;
202
- background-color: rgba(0,0,0,0.4);
203
- }
204
-
205
- .modal-content {
206
- background-color: #fefefe;
207
- margin: 15% auto;
208
- padding: 20px;
209
- border-radius: 8px;
210
- width: 80%;
211
- max-width: 500px;
212
- box-shadow: 0 4px 8px rgba(0,0,0,0.1);
213
- }
214
-
215
- .modal-header {
216
- display: flex;
217
- justify-content: space-between;
218
- align-items: center;
219
- margin-bottom: 15px;
220
- }
221
-
222
- .modal-title {
223
- color: #e74c3c;
224
- font-weight: bold;
225
- font-size: 20px;
226
- margin: 0;
227
- }
228
-
229
- .close-modal {
230
- color: #aaa;
231
- font-size: 28px;
232
- font-weight: bold;
233
- cursor: pointer;
234
- }
235
-
236
- .close-modal:hover {
237
- color: #333;
238
- }
239
-
240
- .modal-body {
241
- margin-bottom: 20px;
242
- }
243
-
244
- .modal-footer {
245
- text-align: right;
246
- }
247
-
248
- .modal-button {
249
- background-color: #3498db;
250
- color: white;
251
- border: none;
252
- padding: 8px 16px;
253
- border-radius: 4px;
254
- cursor: pointer;
255
- }
256
-
257
- .progress-text {
258
- margin-top: 10px;
259
- font-style: italic;
260
- color: #7f8c8d;
261
- }
262
-
263
- .result-text {
264
- white-space: pre-wrap;
265
- word-wrap: break-word;
266
- background-color: #f8f9fa;
267
- padding: 10px;
268
- border-radius: 4px;
269
- font-family: monospace;
270
- }
271
-
272
- /* Circular Progress Indicators */
273
- .score-container {
274
- display: flex;
275
- flex-wrap: wrap;
276
- gap: 20px;
277
- margin-top: 15px;
278
- }
279
-
280
- .score-item {
281
- display: flex;
282
- flex-direction: column;
283
- align-items: center;
284
- }
285
-
286
- .circular-progress {
287
- position: relative;
288
- width: 80px;
289
- height: 80px;
290
- border-radius: 50%;
291
- display: flex;
292
- align-items: center;
293
- justify-content: center;
294
- margin-bottom: 8px;
295
- }
296
-
297
- .circular-progress::before {
298
- content: "";
299
- position: absolute;
300
- width: 70px;
301
- height: 70px;
302
- border-radius: 50%;
303
- background-color: white;
304
- }
305
-
306
- .progress-value {
307
- position: relative;
308
- font-weight: bold;
309
- font-size: 18px;
310
- }
311
-
312
- .score-label {
313
- font-size: 14px;
314
- font-weight: 600;
315
- }
316
-
317
- .malicious {
318
- background: conic-gradient(#e74c3c var(--progress), #f5b7b1 0deg);
319
- }
320
-
321
- .suspicious {
322
- background: conic-gradient(#f39c12 var(--progress), #fad7a0 0deg);
323
- }
324
-
325
- .harmless {
326
- background: conic-gradient(#2ecc71 var(--progress), #abebc6 0deg);
327
- }
328
-
329
- .undetected {
330
- background: conic-gradient(#95a5a6 var(--progress), #d5dbdb 0deg);
331
  }
332
  </style>
333
  </head>
334
- <body>
335
- <div class="container">
336
- <header>
337
- <h1>Security Scanner</h1>
338
- <p class="description">Comprehensive threat analysis for domains, IPs, and file hashes</p>
339
- <button id="theme-toggle" style="position: absolute; top: 20px; right: 20px; padding: 5px 10px; background: var(--primary-color); color: white; border: none; border-radius: 4px; cursor: pointer;">Light Mode</button>
340
  </header>
341
-
342
- <div class="scanner-form">
343
- <div class="radio-group">
344
- <div class="radio-option">
345
- <input type="radio" id="url-check" name="check-type" value="url" checked>
346
- <label for="url-check">URL Check</label>
347
- </div>
348
- <div class="radio-option">
349
- <input type="radio" id="domain-check" name="check-type" value="domain">
350
- <label for="domain-check">Domain Analysis</label>
351
- </div>
352
- <div class="radio-option">
353
- <input type="radio" id="ip-check" name="check-type" value="ip">
354
- <label for="ip-check">IP Reputation</label>
355
- </div>
356
- <div class="radio-option">
357
- <input type="radio" id="hash-check" name="check-type" value="hash">
358
- <label for="hash-check">File Hash Check</label>
359
- </div>
360
- </div>
361
-
362
- <div class="form-group" id="url-ip-domain-group">
363
- <label for="target" id="target-label">Enter domain to scan (example.com)</label>
364
- <input type="text" id="target" placeholder="example.com">
365
- <div class="error-message" id="target-error"></div>
366
- </div>
367
-
368
- <div class="form-group" id="hash-group" style="display: none;">
369
- <label for="hash-input">Enter file hash (MD5, SHA-1, SHA-256, etc.)</label>
370
- <input type="text" id="hash-input" placeholder="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855">
371
- <div class="error-message" id="hash-error"></div>
372
- <p class="hash-info">Enter the cryptographic hash of a file to check its reputation</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
373
  </div>
374
 
375
- <button id="scan-button" disabled>Scan Now</button>
376
- </div>
377
-
378
- <div class="loading" id="loading">
379
- <div class="spinner"></div>
380
- <p>Analyzing, please wait...</p>
381
- <p class="progress-text" id="progress-text">Waiting for the analysis report</p>
382
- </div>
383
-
384
- <div class="results" id="results">
385
- <h2>Scan Results</h2>
386
- <div class="result-item">
387
- <div class="result-title">Threat Scores:</div>
388
- <div class="score-container" id="score-container" style="justify-content: center;">
389
- <!-- Scores will be inserted here dynamically -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
390
  </div>
391
  </div>
392
- <div class="result-item">
393
- <div class="result-title">Target:</div>
394
- <div id="result-target"></div>
395
- </div>
396
- <div class="result-item">
397
- <div class="result-title">Type:</div>
398
- <div id="result-type"></div>
399
- </div>
400
- <div class="result-item">
401
- <div class="result-title">Details:</div>
402
- <div class="result-text" id="result-details"></div>
403
- </div>
404
- <div class="result-item">
405
- <div class="result-title">Last Updated:</div>
406
- <div id="result-date"></div>
407
- </div>
408
- </div>
409
- </div>
410
-
411
- <!-- Error Modal -->
412
- <div id="error-modal" class="modal">
413
- <div class="modal-content">
414
- <div class="modal-header">
415
- <h3 class="modal-title">Error</h3>
416
- <span class="close-modal">&times;</span>
417
- </div>
418
- <div class="modal-body" id="modal-error-message">
419
- An error occurred while processing your request.
420
- </div>
421
- <div class="modal-footer">
422
- <button class="modal-button" id="close-modal-btn">OK</button>
423
- </div>
424
  </div>
 
 
 
 
425
  </div>
426
-
427
- <footer>
428
- <p>Security Scanner System | Powered by n8n automation</p>
429
- <div class="scan-stats" style="margin-top: 10px; font-size: 14px; color: var(--secondary-text);">
430
- <span>Total Scans: <span id="total-scans">0</span></span> |
431
- <span>URL Checks: <span id="url-scans">0</span></span> |
432
- <span>Domain Analysis: <span id="domain-scans">0</span></span> |
433
- <span>IP Reputation: <span id="ip-scans">0</span></span> |
434
- <span>File Hash: <span id="hash-scans">0</span></span>
435
- </div>
436
- </footer>
437
-
438
  <script>
439
  document.addEventListener('DOMContentLoaded', function() {
440
- // Theme toggle functionality
441
- const themeToggle = document.getElementById('theme-toggle');
442
- themeToggle.addEventListener('click', function() {
443
- document.body.classList.toggle('light-mode');
444
- themeToggle.textContent = document.body.classList.contains('light-mode') ? 'Dark Mode' : 'Light Mode';
445
- localStorage.setItem('theme', document.body.classList.contains('light-mode') ? 'light' : 'dark');
446
- });
447
-
448
- // Set initial theme from localStorage
449
- if (localStorage.getItem('theme') === 'light') {
450
- document.body.classList.add('light-mode');
451
- themeToggle.textContent = 'Dark Mode';
452
- }
453
-
454
- // Scan counters
455
- let scanCounters = {
456
- total: 0,
457
- url: 0,
458
- domain: 0,
459
- ip: 0,
460
- hash: 0
461
- };
462
-
463
- // Load counters from localStorage if available
464
- const savedCounters = localStorage.getItem('scanCounters');
465
- if (savedCounters) {
466
- scanCounters = JSON.parse(savedCounters);
467
- updateCounterDisplay();
468
- }
469
- // DOM Elements
470
- const urlRadio = document.getElementById('url-check');
471
- const domainRadio = document.getElementById('domain-check');
472
- const ipRadio = document.getElementById('ip-check');
473
- const hashRadio = document.getElementById('hash-check');
474
- const targetLabel = document.getElementById('target-label');
475
- const targetInput = document.getElementById('target');
476
- const urlIpDomainGroup = document.getElementById('url-ip-domain-group');
477
- const hashGroup = document.getElementById('hash-group');
478
- const hashInput = document.getElementById('hash-input');
479
- const scanButton = document.getElementById('scan-button');
480
- const loadingDiv = document.getElementById('loading');
481
- const progressText = document.getElementById('progress-text');
482
- const resultsDiv = document.getElementById('results');
483
- const scoreContainer = document.getElementById('score-container');
484
- const targetError = document.getElementById('target-error');
485
- const hashError = document.getElementById('hash-error');
486
 
487
- // Modal elements
488
- const errorModal = document.getElementById('error-modal');
489
- const modalErrorMessage = document.getElementById('modal-error-message');
490
- const closeModalBtn = document.getElementById('close-modal-btn');
491
- const closeModalSpan = document.querySelector('.close-modal');
492
 
493
- // Validation patterns
494
- const DOMAIN_REGEX = /^(?!:\/\/)(?:[a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$/;
495
- const URL_REGEX = /^(https?:\/\/)?(www\.)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/[^\s]*)?$/;
496
- const PUBLIC_IP_REGEX = /^(?!0)(?!10\.|127\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
497
- const HASH_REGEX = /^[a-fA-F0-9]{32,128}$/;
498
-
499
- // Event listeners for radio buttons
500
- urlRadio.addEventListener('change', updateFormFields);
501
- domainRadio.addEventListener('change', updateFormFields);
502
- ipRadio.addEventListener('change', updateFormFields);
503
- hashRadio.addEventListener('change', updateFormFields);
504
-
505
- function updateFormFields() {
506
- if(urlRadio.checked) {
507
- urlIpDomainGroup.style.display = 'block';
508
- hashGroup.style.display = 'none';
509
- targetLabel.textContent = 'Enter URL to scan (example.com/path)';
510
- targetInput.placeholder = 'example.com/path';
511
- targetInput.value = '';
512
- hashInput.value = '';
513
- }
514
- else if(domainRadio.checked) {
515
- urlIpDomainGroup.style.display = 'block';
516
- hashGroup.style.display = 'none';
517
- targetLabel.textContent = 'Enter domain to analyze (example.com)';
518
- targetInput.placeholder = 'example.com';
519
- targetInput.value = '';
520
- hashInput.value = '';
521
- }
522
- else if(ipRadio.checked) {
523
- urlIpDomainGroup.style.display = 'block';
524
- hashGroup.style.display = 'none';
525
- targetLabel.textContent = 'Enter public IP address to check';
526
- targetInput.placeholder = '8.8.8.8';
527
- targetInput.value = '';
528
- hashInput.value = '';
529
- }
530
- else if(hashRadio.checked) {
531
- urlIpDomainGroup.style.display = 'none';
532
- hashGroup.style.display = 'block';
533
- targetInput.value = '';
534
- hashInput.value = '';
535
- }
536
 
537
- scanButton.disabled = true;
538
- clearErrors();
539
- }
540
-
541
- // Input validation
542
- targetInput.addEventListener('input', validateInput);
543
- hashInput.addEventListener('input', validateInput);
544
-
545
- // Modal event listeners
546
- closeModalBtn.addEventListener('click', function() {
547
- errorModal.style.display = 'none';
548
- });
549
-
550
- closeModalSpan.addEventListener('click', function() {
551
- errorModal.style.display = 'none';
552
- });
553
-
554
- window.addEventListener('click', function(event) {
555
- if (event.target === errorModal) {
556
- errorModal.style.display = 'none';
557
- }
558
- });
559
-
560
- function validateInput() {
561
- clearErrors();
562
- let isValid = false;
563
 
564
- if(urlRadio.checked) {
565
- // URL validation
566
- if(targetInput.value.trim() === '') {
567
- showError(targetError, 'Please enter a URL');
568
- } else if(!URL_REGEX.test(targetInput.value.trim())) {
569
- showError(targetError, 'Please enter a valid URL (example.com/path) without http/https/www');
570
- } else {
571
- isValid = true;
572
- }
573
- }
574
- else if(domainRadio.checked) {
575
- // Domain validation (strict)
576
- if(targetInput.value.trim() === '') {
577
- showError(targetError, 'Please enter a domain');
578
- } else if(!DOMAIN_REGEX.test(targetInput.value.trim())) {
579
- showError(targetError, 'Please enter a valid domain (example.com) without http/https/www or paths');
580
- } else {
581
- isValid = true;
582
- }
583
- }
584
- else if(ipRadio.checked) {
585
- // Public IP validation
586
- const ip = targetInput.value.trim();
587
- if(ip === '') {
588
- showError(targetError, 'Please enter an IP address');
589
- } else if(!PUBLIC_IP_REGEX.test(ip)) {
590
- showError(targetError, 'Please enter a valid public IPv4 address (e.g. 8.8.8.8)');
591
- } else if(isPrivateIP(ip)) {
592
- showError(targetError, 'This is a private IP address. Please enter a public IP.');
593
- } else {
594
- isValid = true;
595
- }
596
- }
597
- else if(hashRadio.checked) {
598
- // Hash validation
599
- if(hashInput.value.trim() === '') {
600
- showError(hashError, 'Please enter a file hash');
601
- } else if(!HASH_REGEX.test(hashInput.value.trim())) {
602
- showError(hashError, 'Please enter a valid hash (MD5, SHA-1, SHA-256, etc.)');
603
- } else {
604
- isValid = true;
605
- }
606
- }
607
-
608
- scanButton.disabled = !isValid;
609
- return isValid;
610
- }
611
-
612
- function isPrivateIP(ip) {
613
- // Check against standard private IP ranges
614
- const ipParts = ip.split('.').map(Number);
615
-
616
- // 10.0.0.0/8
617
- if(ipParts[0] === 10) return true;
618
-
619
- // 172.16.0.0/12
620
- if(ipParts[0] === 172 && ipParts[1] >= 16 && ipParts[1] <= 31) return true;
621
-
622
- // 192.168.0.0/16
623
- if(ipParts[0] === 192 && ipParts[1] === 168) return true;
624
-
625
- // 127.0.0.0/8
626
- if(ipParts[0] === 127) return true;
627
-
628
- // 169.254.0.0/16 (link-local)
629
- if(ipParts[0] === 169 && ipParts[1] === 254) return true;
630
-
631
- return false;
632
- }
633
-
634
- function showError(element, message) {
635
- element.textContent = message;
636
- element.style.display = 'block';
637
- }
638
-
639
- function clearErrors() {
640
- targetError.style.display = 'none';
641
- hashError.style.display = 'none';
642
- }
643
-
644
- function showModalError(message) {
645
- modalErrorMessage.textContent = message;
646
- errorModal.style.display = 'block';
647
-
648
- // Ensure modal is visible in dark/light mode
649
- const modalContent = document.querySelector('.modal-content');
650
- if (document.body.classList.contains('light-mode')) {
651
- modalContent.style.backgroundColor = 'white';
652
- modalContent.style.color = '#333';
653
- } else {
654
- modalContent.style.backgroundColor = '#2d2d2d';
655
- modalContent.style.color = '#e0e0e0';
656
- }
657
- }
658
-
659
- function createScoreIndicator(type, score) {
660
- const percentage = Math.min(100, Math.max(0, score));
661
- const progress = (percentage / 100) * 360;
662
-
663
- const scoreItem = document.createElement('div');
664
- scoreItem.className = 'score-item';
665
-
666
- const progressDiv = document.createElement('div');
667
- progressDiv.className = `circular-progress ${type}`;
668
- progressDiv.style.setProperty('--progress', `${progress}deg`);
669
-
670
- const progressValue = document.createElement('div');
671
- progressValue.className = 'progress-value';
672
- progressValue.textContent = `${percentage}%`;
673
-
674
- const scoreLabel = document.createElement('div');
675
- scoreLabel.className = 'score-label';
676
- scoreLabel.textContent = type.charAt(0).toUpperCase() + type.slice(1);
677
-
678
- progressDiv.appendChild(progressValue);
679
- scoreItem.appendChild(progressDiv);
680
- scoreItem.appendChild(scoreLabel);
681
 
682
- return scoreItem;
683
- }
684
-
685
- function parseScoresFromResponse(text) {
686
- // Extract scores from the text response
687
- const scores = {
688
- malicious: 0,
689
- suspicious: 0,
690
- harmless: 0,
691
- undetected: 0
692
- };
693
-
694
- // More robust score parsing that looks for various formats
695
- const scorePatterns = [
696
- // Matches "Malicious: 75%" or "Malicious Score: 75"
697
- /(malicious)[\s:]*(\d+)/i,
698
- /(suspicious)[\s:]*(\d+)/i,
699
- /(harmless)[\s:]*(\d+)/i,
700
- /(undetected)[\s:]*(\d+)/i,
701
- // Matches "75% malicious" or "75 malicious"
702
- /(\d+)[%\s]*(malicious)/i,
703
- /(\d+)[%\s]*(suspicious)/i,
704
- /(\d+)[%\s]*(harmless)/i,
705
- /(\d+)[%\s]*(undetected)/i
706
- ];
707
-
708
- for (const pattern of scorePatterns) {
709
- const matches = text.matchAll(pattern);
710
- for (const match of matches) {
711
- let type, value;
712
- if (match[2] && isNaN(match[2])) {
713
- // Pattern like "75 malicious"
714
- value = parseInt(match[1]);
715
- type = match[2].toLowerCase();
716
- } else {
717
- // Pattern like "malicious: 75"
718
- type = match[1].toLowerCase();
719
- value = parseInt(match[2]);
720
- }
721
 
722
- if (type in scores) {
723
- scores[type] = Math.min(100, Math.max(0, value));
724
- }
 
 
725
  }
 
 
 
 
726
  }
727
-
728
- return scores;
729
- }
730
-
731
- // Handle form submission
732
- scanButton.addEventListener('click', function() {
733
- if(!validateInput()) return;
734
-
735
- // Update counters
736
- const checkType = document.querySelector('input[name="check-type"]:checked').value;
737
- scanCounters.total++;
738
- scanCounters[checkType]++;
739
- localStorage.setItem('scanCounters', JSON.stringify(scanCounters));
740
- updateCounterDisplay();
741
-
742
- // Show loading with persistent message, hide results
743
- loadingDiv.style.display = 'block';
744
- progressText.textContent = 'Waiting for the analysis report...';
745
- resultsDiv.style.display = 'none';
746
- scoreContainer.innerHTML = ''; // Clear previous scores
747
- let data = { type: checkType };
748
-
749
- if(checkType === 'hash') {
750
- data.target = hashInput.value.trim();
751
- } else {
752
- data.target = targetInput.value.trim();
753
- }
754
-
755
- sendToWebhook(data);
756
  });
757
 
758
- function sendToWebhook(data) {
759
- // Start timeout timer to show we're still waiting
760
- const timeoutTimer = setTimeout(() => {
761
- progressText.textContent = 'Still processing... this may take a moment';
762
- }, 5000);
763
-
764
- fetch('https://workflow.porifyx.com/webhook/150f8de3-d60c-4110-9ac8-22031a8cf6bb', {
765
- method: 'POST',
766
- headers: {
767
- 'Content-Type': 'application/json'
768
- },
769
- body: JSON.stringify(data)
770
- })
771
- .then(response => {
772
- clearTimeout(timeoutTimer);
773
-
774
- if (!response.ok) {
775
- return response.text().then(text => {
776
- throw new Error(text || `HTTP error! status: ${response.status}`);
777
- });
778
- }
779
- return response.text(); // Get response as text
780
- })
781
- .then(textResponse => {
782
- // Hide loading, show results
783
- loadingDiv.style.display = 'none';
784
- resultsDiv.style.display = 'block';
785
-
786
- // Display results
787
- document.getElementById('result-target').textContent = data.target || 'N/A';
788
- document.getElementById('result-type').textContent = data.type ? data.type.toUpperCase() : 'N/A';
789
- document.getElementById('result-details').textContent = textResponse;
790
- document.getElementById('result-date').textContent = new Date().toLocaleString();
791
-
792
- // Parse and display scores - always show all indicators even if 0
793
- const scores = parseScoresFromResponse(textResponse);
794
- scoreContainer.innerHTML = '';
795
-
796
- // Always show all score indicators
797
- scoreContainer.appendChild(createScoreIndicator('malicious', scores.malicious));
798
- scoreContainer.appendChild(createScoreIndicator('suspicious', scores.suspicious));
799
- scoreContainer.appendChild(createScoreIndicator('harmless', scores.harmless));
800
- scoreContainer.appendChild(createScoreIndicator('undetected', scores.undetected));
801
-
802
- // Display the raw response for debugging
803
- console.log('Webhook response:', textResponse);
804
- })
805
- .catch(error => {
806
- clearTimeout(timeoutTimer);
807
- console.error('Error:', error);
808
- loadingDiv.style.display = 'none';
809
- showModalError(error.message || 'An error occurred while scanning. Please try again.');
810
- });
811
  }
812
 
813
- function updateCounterDisplay() {
814
- document.getElementById('total-scans').textContent = scanCounters.total;
815
- document.getElementById('url-scans').textContent = scanCounters.url;
816
- document.getElementById('domain-scans').textContent = scanCounters.domain;
817
- document.getElementById('ip-scans').textContent = scanCounters.ip;
818
- document.getElementById('hash-scans').textContent = scanCounters.hash;
 
 
 
 
 
 
 
 
 
 
819
  }
820
  });
821
  </script>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Newsletter Automation</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
+ .gradient-bg {
11
+ background: linear-gradient(135deg, #6B73FF 0%, #000DFF 100%);
 
 
 
 
 
 
12
  }
13
+ .input-focus:focus {
14
+ box-shadow: 0 0 0 3px rgba(107, 115, 255, 0.3);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
+ .response-area {
17
+ min-height: 400px;
18
+ transition: all 0.3s ease;
 
 
 
 
 
 
19
  }
20
+ .loader {
21
+ border-top-color: #6B73FF;
22
+ -webkit-animation: spinner 1.5s linear infinite;
23
+ animation: spinner 1.5s linear infinite;
 
 
 
24
  }
25
+ @-webkit-keyframes spinner {
26
+ 0% { -webkit-transform: rotate(0deg); }
27
+ 100% { -webkit-transform: rotate(360deg); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
+ @keyframes spinner {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  0% { transform: rotate(0deg); }
31
  100% { transform: rotate(360deg); }
32
  }
33
+ .newsletter-counter {
34
+ font-family: 'Courier New', monospace;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
  </style>
37
  </head>
38
+ <body class="bg-gray-50 min-h-screen">
39
+ <div class="container mx-auto px-4 py-8">
40
+ <header class="mb-10 text-center">
41
+ <h1 class="text-4xl font-bold text-gray-800 mb-2">Newsletter Automation</h1>
42
+ <p class="text-gray-600 max-w-2xl mx-auto">Generate professional newsletters in seconds with AI. Just fill in the details and let our system do the rest!</p>
 
43
  </header>
44
+
45
+ <div class="flex flex-col lg:flex-row gap-8 relative">
46
+ <!-- Left Column - Form -->
47
+ <div class="w-full lg:w-1/2 bg-white rounded-xl shadow-md p-6 lg:sticky lg:top-8 lg:h-[calc(100vh-4rem)]">
48
+ <h2 class="text-2xl font-semibold text-gray-800 mb-6">Create Your Newsletter</h2>
49
+
50
+ <form id="newsletterForm" class="space-y-6">
51
+ <div>
52
+ <label for="topic" class="block text-sm font-medium text-gray-700 mb-1">Topic *</label>
53
+ <input
54
+ type="text"
55
+ id="topic"
56
+ name="topic"
57
+ required
58
+ placeholder="What's your newsletter about?"
59
+ class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 input-focus transition duration-200"
60
+ >
61
+ </div>
62
+
63
+ <div>
64
+ <label for="tone" class="block text-sm font-medium text-gray-700 mb-1">Tone *</label>
65
+ <select
66
+ id="tone"
67
+ name="tone"
68
+ required
69
+ class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 input-focus transition duration-200"
70
+ >
71
+ <option value="" disabled selected>Select a tone for your newsletter</option>
72
+ <option value="Professional">Professional</option>
73
+ <option value="Casual">Casual</option>
74
+ <option value="Friendly">Friendly</option>
75
+ <option value="Authoritative">Authoritative</option>
76
+ <option value="Inspirational">Inspirational</option>
77
+ <option value="Humorous">Humorous</option>
78
+ <option value="Technical">Technical</option>
79
+ </select>
80
+ </div>
81
+
82
+ <div>
83
+ <label for="audience" class="block text-sm font-medium text-gray-700 mb-1">Target Audience *</label>
84
+ <input
85
+ type="text"
86
+ id="audience"
87
+ name="audience"
88
+ required
89
+ placeholder="Who is your target audience?"
90
+ class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 input-focus transition duration-200"
91
+ >
92
+ </div>
93
+
94
+ <div>
95
+ <label for="email" class="block text-sm font-medium text-gray-700 mb-1">Your Email *</label>
96
+ <input
97
+ type="email"
98
+ id="email"
99
+ name="email"
100
+ required
101
+ placeholder="Where should we send the newsletter?"
102
+ class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 input-focus transition duration-200"
103
+ >
104
+ </div>
105
+
106
+ <div class="pt-4">
107
+ <button
108
+ type="submit"
109
+ class="w-full gradient-bg text-white py-3 px-6 rounded-lg font-medium hover:opacity-90 transition duration-200 flex items-center justify-center"
110
+ >
111
+ <i class="fas fa-paper-plane mr-2"></i> Generate Newsletter
112
+ </button>
113
+ </div>
114
+
115
+ <div class="mt-8 pt-6 border-t border-gray-200">
116
+ <div class="flex items-center justify-between">
117
+ <span class="text-sm font-medium text-gray-600">Newsletters Generated:</span>
118
+ <span id="counter" class="text-xl font-bold text-blue-600 newsletter-counter">70</span>
119
+ </div>
120
+ </div>
121
+ </form>
122
  </div>
123
 
124
+ <!-- Right Column - Response -->
125
+ <div class="w-full lg:w-1/2 lg:overflow-y-auto">
126
+ <div class="bg-white rounded-xl shadow-md p-6 min-h-full">
127
+ <h2 class="text-2xl font-semibold text-gray-800 mb-6">Your Newsletter</h2>
128
+
129
+ <div id="responseArea" class="response-area bg-gray-50 rounded-lg p-4 border border-gray-200">
130
+ <div id="emptyState" class="flex flex-col items-center justify-center h-full text-center py-12">
131
+ <i class="fas fa-newspaper text-4xl text-gray-300 mb-4"></i>
132
+ <h3 class="text-lg font-medium text-gray-500">Your newsletter will appear here</h3>
133
+ <p class="text-gray-400 mt-1 max-w-md">Fill out the form and click "Generate Newsletter" to create your custom content.</p>
134
+ </div>
135
+
136
+ <div id="loadingState" class="hidden flex-col items-center justify-center h-full text-center py-12">
137
+ <div class="loader ease-linear rounded-full border-4 border-t-4 border-gray-200 h-12 w-12 mb-4"></div>
138
+ <h3 class="text-lg font-medium text-gray-700">Generating your newsletter...</h3>
139
+ <p class="text-gray-500 mt-1">This usually takes about 10-15 seconds.</p>
140
+ </div>
141
+
142
+ <div id="responseContent" class="hidden">
143
+ <div id="successAlert" class="bg-green-50 border-l-4 border-green-500 p-4 mb-4 rounded">
144
+ <div class="flex items-center">
145
+ <div class="flex-shrink-0">
146
+ <i class="fas fa-check-circle text-green-500"></i>
147
+ </div>
148
+ <div class="ml-3">
149
+ <p class="text-sm text-green-700">
150
+ A copy of this newsletter has been sent to your email address!
151
+ </p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+
156
+ <div id="newsletterContent" class="prose max-w-none">
157
+ <!-- Newsletter content will be inserted here -->
158
+ </div>
159
+ </div>
160
+ </div>
161
  </div>
162
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  </div>
164
+
165
+ <footer class="mt-16 text-center text-gray-500 text-sm">
166
+ <p>Designed by Jamiu Sosanya | <a href="mailto:sosanyajamiu@gmail.com" class="text-blue-600 hover:underline">sosanyajamiu@gmail.com</a></p>
167
+ </footer>
168
  </div>
169
+
 
 
 
 
 
 
 
 
 
 
 
170
  <script>
171
  document.addEventListener('DOMContentLoaded', function() {
172
+ const form = document.getElementById('newsletterForm');
173
+ const responseArea = document.getElementById('responseArea');
174
+ const emptyState = document.getElementById('emptyState');
175
+ const loadingState = document.getElementById('loadingState');
176
+ const responseContent = document.getElementById('responseContent');
177
+ const newsletterContent = document.getElementById('newsletterContent');
178
+ const counter = document.getElementById('counter');
179
+ let newsletterCount = 70;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
+ // Increment counter every 5 minutes to simulate usage
182
+ setInterval(() => {
183
+ newsletterCount++;
184
+ counter.textContent = newsletterCount;
185
+ }, 300000);
186
 
187
+ form.addEventListener('submit', async function(e) {
188
+ e.preventDefault();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
+ // Get form values
191
+ const topic = document.getElementById('topic').value;
192
+ const tone = document.getElementById('tone').value;
193
+ const audience = document.getElementById('audience').value;
194
+ const email = document.getElementById('email').value;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
 
196
+ // Show loading state
197
+ emptyState.classList.add('hidden');
198
+ loadingState.classList.remove('hidden');
199
+ responseContent.classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
200
 
201
+ try {
202
+ // Send data to webhook
203
+ const response = await fetch('https://workflow.porifyx.com/webhook/e09efa68-9f7a-42d9-bbe3-3ed71e252f53', {
204
+ method: 'POST',
205
+ headers: {
206
+ 'Content-Type': 'application/json',
207
+ },
208
+ body: JSON.stringify({
209
+ topic,
210
+ tone,
211
+ audience,
212
+ email
213
+ })
214
+ });
215
+
216
+ const text = await response.text();
217
+
218
+ // Hide loading state and show response
219
+ loadingState.classList.add('hidden');
220
+
221
+ if (text) {
222
+ // Format the response
223
+ newsletterContent.innerHTML = formatNewsletter(text);
224
+ responseContent.classList.remove('hidden');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
226
+ // Increment counter
227
+ newsletterCount++;
228
+ counter.textContent = newsletterCount;
229
+ } else {
230
+ showError("Received an unexpected response format from the server.");
231
  }
232
+ } catch (error) {
233
+ loadingState.classList.add('hidden');
234
+ showError("Failed to generate newsletter. Please try again later.");
235
+ console.error('Error:', error);
236
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  });
238
 
239
+ function formatNewsletter(content) {
240
+ // Convert newlines to HTML line breaks and preserve whitespace
241
+ let formatted = content.replace(/\n/g, '<br>');
242
+ return formatted;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
244
 
245
+ function showError(message) {
246
+ newsletterContent.innerHTML = `
247
+ <div class="bg-red-50 border-l-4 border-red-500 p-4 mb-4 rounded">
248
+ <div class="flex items-center">
249
+ <div class="flex-shrink-0">
250
+ <i class="fas fa-exclamation-circle text-red-500"></i>
251
+ </div>
252
+ <div class="ml-3">
253
+ <p class="text-sm text-red-700">
254
+ ${message}
255
+ </p>
256
+ </div>
257
+ </div>
258
+ </div>
259
+ `;
260
+ responseContent.classList.remove('hidden');
261
  }
262
  });
263
  </script>