mirror of
https://github.com/h9zdev/GeoSentinel
synced 2026-04-25 17:25:10 +02:00
2163 lines
72 KiB
HTML
2163 lines
72 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Surveillance Dashboard</title>
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<title>HayOS - H9 Dashboard</title>
|
||
<link rel="icon" href="https://haybnz.web.app/logo.png" type="image/x-icon">
|
||
<link href="https://fonts.googleapis.com/css2?family=Orbitron&family=Roboto+Mono&display=swap" rel="stylesheet">
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
||
<title>WiFi Search - HayOS</title>
|
||
<link rel="icon" href="https://haybnz.web.app/logo.png" type="image/x-icon">
|
||
<!-- Leaflet CSS from CDN -->
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
|
||
<!-- Leaflet MarkerCluster CSS -->
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
|
||
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
|
||
<!-- Font Awesome for icons -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" />
|
||
<script>
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
const startTime = Date.now();
|
||
const username = document.cookie.split('; ').find(row => row.startsWith('username='))?.split('=')[1] || 'anonymous';
|
||
const page = window.location.pathname;
|
||
|
||
function sendActivity(event, details = '') {
|
||
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||
fetch('/log-activity', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
username: username,
|
||
page: page,
|
||
event: event,
|
||
details: details,
|
||
duration: duration
|
||
})
|
||
});
|
||
}
|
||
|
||
// Log page view
|
||
sendActivity('page_view');
|
||
|
||
// Log clicks
|
||
document.addEventListener('click', function (e) {
|
||
const target = e.target.closest('button, a, input[type="submit"]');
|
||
if (target) {
|
||
const text = (target.innerText || target.value || target.href || '').substring(0, 100);
|
||
sendActivity('click', text);
|
||
}
|
||
});
|
||
|
||
// Log before unload (time spent)
|
||
window.addEventListener('beforeunload', function () {
|
||
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
||
navigator.sendBeacon('/log-activity', JSON.stringify({
|
||
username: username,
|
||
page: page,
|
||
event: 'page_leave',
|
||
duration: duration
|
||
}));
|
||
});
|
||
});
|
||
</script>
|
||
|
||
|
||
<style>
|
||
@import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono&display=swap');
|
||
|
||
:root {
|
||
--primary-color: #00FFD1;
|
||
/* Cyber Cyan */
|
||
--secondary-color: #00A3FF;
|
||
/* Electric Blue */
|
||
--accent-color: #00E5FF;
|
||
/* Soft Cyan */
|
||
--bg-dark: #020a0a;
|
||
/* Deep Void */
|
||
--glass-bg: rgba(5, 20, 20, 0.85);
|
||
--card-bg: rgba(0, 255, 209, 0.03);
|
||
--border-color: rgba(0, 255, 209, 0.2);
|
||
--text-main: #00FFD1;
|
||
--text-dim: rgba(0, 255, 209, 0.6);
|
||
--neon-glow: 0 0 15px rgba(0, 255, 209, 0.4);
|
||
--danger: #FF1744;
|
||
--success: #00E676;
|
||
--font-main: 'Share Tech Mono', 'Courier New', monospace;
|
||
}
|
||
|
||
body.sat-mode {
|
||
--primary-color: #FF00FF;
|
||
--secondary-color: #BC13FE;
|
||
--accent-color: #FF0055;
|
||
--text-main: #FFB3FF;
|
||
--text-dim: rgba(255, 179, 255, 0.6);
|
||
--border-color: rgba(255, 0, 255, 0.3);
|
||
--glass-bg: rgba(40, 0, 40, 0.85);
|
||
--neon-glow: 0 0 15px rgba(255, 0, 255, 0.4);
|
||
}
|
||
|
||
/* ---------- Base / Theme ---------- */
|
||
* {
|
||
box-sizing: border-box;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
html,
|
||
body {
|
||
height: 100%;
|
||
font-family: var(--font-main);
|
||
background: var(--bg-dark);
|
||
color: var(--text-main);
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.bg-grid {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 0;
|
||
background-image:
|
||
repeating-linear-gradient(0deg, transparent, transparent 49px, rgba(0, 255, 209, 0.03) 49px, rgba(0, 255, 209, 0.03) 50px),
|
||
repeating-linear-gradient(90deg, transparent, transparent 49px, rgba(0, 255, 209, 0.03) 49px, rgba(0, 255, 209, 0.03) 50px);
|
||
animation: gridMove 20s linear infinite;
|
||
}
|
||
|
||
@keyframes gridMove {
|
||
0% {
|
||
transform: translate(0, 0);
|
||
}
|
||
|
||
100% {
|
||
transform: translate(50px, 50px);
|
||
}
|
||
}
|
||
|
||
/* ---------- Top Navigation ---------- */
|
||
.topnav {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 60px;
|
||
background: rgba(2, 10, 10, 0.9);
|
||
backdrop-filter: blur(20px);
|
||
border-bottom: 1px solid var(--border-color);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 0 20px;
|
||
z-index: 1100;
|
||
}
|
||
|
||
.left-group {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
|
||
.logo-section {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.logo-text h2 {
|
||
font-size: 18px;
|
||
letter-spacing: 2px;
|
||
text-transform: uppercase;
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.logo-text p {
|
||
font-size: 10px;
|
||
color: var(--text-dim);
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
}
|
||
|
||
.nav-stats {
|
||
display: flex;
|
||
gap: 15px;
|
||
align-items: center;
|
||
}
|
||
|
||
.stat-item {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
padding: 6px 12px;
|
||
background: rgba(0, 255, 209, 0.05);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.status-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--success);
|
||
box-shadow: 0 0 10px var(--success);
|
||
}
|
||
|
||
/* Cyber Checkboxes */
|
||
.filter-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
margin: 15px 0;
|
||
justify-content: center;
|
||
background: rgba(0, 255, 209, 0.02);
|
||
padding: 10px;
|
||
border-bottom: 1px solid var(--border-color);
|
||
}
|
||
|
||
.cyber-check {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
font-size: 11px;
|
||
color: var(--text-dim);
|
||
transition: 0.3s;
|
||
user-select: none;
|
||
}
|
||
|
||
.cyber-check input {
|
||
display: none;
|
||
}
|
||
|
||
.cyber-check .box {
|
||
width: 14px;
|
||
height: 14px;
|
||
border: 1px solid var(--border-color);
|
||
position: relative;
|
||
background: rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.cyber-check input:checked+.box {
|
||
background: var(--primary-color);
|
||
box-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.cyber-check input:checked+.box::after {
|
||
content: "\f00c";
|
||
font-family: "Font Awesome 5 Free";
|
||
font-weight: 900;
|
||
position: absolute;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
color: #000;
|
||
font-size: 8px;
|
||
}
|
||
|
||
.cyber-check:hover {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.cyber-check.active {
|
||
color: var(--primary-color);
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.mode-label {
|
||
font-size: 12px;
|
||
letter-spacing: 2px;
|
||
color: var(--text-dim);
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.mode-label.active {
|
||
color: var(--primary-color);
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.switch {
|
||
position: relative;
|
||
display: inline-block;
|
||
width: 60px;
|
||
height: 24px;
|
||
}
|
||
|
||
.switch input {
|
||
opacity: 0;
|
||
width: 0;
|
||
height: 0;
|
||
}
|
||
|
||
.slider {
|
||
position: absolute;
|
||
cursor: pointer;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
transition: .4s;
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
.slider:before {
|
||
position: absolute;
|
||
content: "";
|
||
height: 16px;
|
||
width: 26px;
|
||
left: 4px;
|
||
bottom: 3px;
|
||
background-color: var(--text-dim);
|
||
transition: .4s;
|
||
}
|
||
|
||
input:checked+.slider {
|
||
background-color: rgba(0, 255, 209, 0.1);
|
||
}
|
||
|
||
input:checked+.slider:before {
|
||
transform: translateX(26px);
|
||
background-color: var(--primary-color);
|
||
box-shadow: var(--neon-glow);
|
||
}
|
||
|
||
/* ---------- Sidebar ---------- */
|
||
.sidebar {
|
||
position: fixed;
|
||
top: 60px;
|
||
left: 0;
|
||
width: 250px;
|
||
height: calc(100vh - 60px);
|
||
background: rgba(2, 10, 10, 0.95);
|
||
backdrop-filter: blur(15px);
|
||
border-right: 1px solid var(--border-color);
|
||
padding: 20px 0;
|
||
overflow-y: auto;
|
||
z-index: 1000;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.menu-title {
|
||
padding: 15px 20px 8px;
|
||
font-size: 10px;
|
||
color: var(--text-dim);
|
||
text-transform: uppercase;
|
||
letter-spacing: 2px;
|
||
border-bottom: 1px solid rgba(0, 255, 209, 0.05);
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.menu-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
padding: 12px 20px;
|
||
color: var(--text-dim);
|
||
transition: 0.3s;
|
||
cursor: pointer;
|
||
border-left: 2px solid transparent;
|
||
text-transform: uppercase;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.menu-item:hover,
|
||
.menu-item.active {
|
||
color: var(--primary-color);
|
||
background: rgba(0, 255, 209, 0.05);
|
||
border-left-color: var(--primary-color);
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.menu-icon {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
/* ---------- Main Content ---------- */
|
||
.container {
|
||
margin-top: 60px;
|
||
margin-left: 250px;
|
||
padding: 30px;
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.page-header h1 {
|
||
font-size: 24px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 4px;
|
||
margin-bottom: 10px;
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.page-header p {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
text-transform: uppercase;
|
||
letter-spacing: 2px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.search-bar {
|
||
display: flex;
|
||
gap: 15px;
|
||
background: var(--glass-bg);
|
||
padding: 20px;
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
margin-bottom: 25px;
|
||
backdrop-filter: blur(10px);
|
||
justify-content: center;
|
||
}
|
||
|
||
.search-bar select,
|
||
.search-bar input {
|
||
background: rgba(0, 0, 0, 0.5);
|
||
border: 1px solid var(--border-color);
|
||
color: #fff;
|
||
padding: 12px 15px;
|
||
font-family: var(--font-main);
|
||
font-size: 14px;
|
||
}
|
||
|
||
.search-bar select:focus,
|
||
.search-bar input:focus {
|
||
outline: none;
|
||
border-color: var(--primary-color);
|
||
box-shadow: 0 0 10px rgba(0, 255, 209, 0.2);
|
||
}
|
||
|
||
.search-bar button {
|
||
background: rgba(0, 255, 209, 0.1);
|
||
border: 1px solid var(--primary-color);
|
||
color: var(--primary-color);
|
||
padding: 12px 30px;
|
||
cursor: pointer;
|
||
text-transform: uppercase;
|
||
font-weight: bold;
|
||
letter-spacing: 2px;
|
||
transition: 0.3s;
|
||
clip-path: polygon(10% 0, 100% 0, 100% 70%, 90% 100%, 0 100%, 0 30%);
|
||
font-family: var(--font-main);
|
||
}
|
||
|
||
#search-type,
|
||
#search-input {
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border: 1px solid var(--border-color);
|
||
color: var(--primary-color);
|
||
padding: 12px 15px;
|
||
font-family: var(--font-main);
|
||
font-size: 14px;
|
||
outline: none;
|
||
transition: 0.3s;
|
||
box-shadow: inset 0 0 10px rgba(0, 255, 209, 0.05);
|
||
}
|
||
|
||
#search-type:focus,
|
||
#search-input:focus {
|
||
border-color: var(--primary-color);
|
||
box-shadow: 0 0 15px rgba(0, 255, 209, 0.3), inset 0 0 10px rgba(0, 255, 209, 0.1);
|
||
}
|
||
|
||
#search-type option {
|
||
background: #020a0a;
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.search-bar button:hover {
|
||
background: var(--primary-color);
|
||
color: #000;
|
||
box-shadow: var(--neon-glow);
|
||
transform: translateY(-2px);
|
||
}
|
||
|
||
/* Surveillance Grid Layout */
|
||
.surveillance-grid {
|
||
display: grid;
|
||
grid-template-columns: 350px 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 25px;
|
||
height: 65vh;
|
||
}
|
||
|
||
.results-sidebar {
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
overflow-y: auto;
|
||
padding: 15px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.4);
|
||
}
|
||
|
||
.map-container {
|
||
border: 2px solid var(--border-color);
|
||
position: relative;
|
||
box-shadow: 0 0 30px rgba(0, 0, 0, 0.5);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.map-container::after {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 255, 209, 0.05) 50%),
|
||
linear-gradient(90deg, rgba(255, 0, 0, 0.02), rgba(0, 255, 0, 0.01), rgba(0, 0, 255, 0.02));
|
||
background-size: 100% 4px, 3px 100%;
|
||
pointer-events: none;
|
||
z-index: 1000;
|
||
}
|
||
|
||
|
||
#map {
|
||
height: 100%;
|
||
width: 100%;
|
||
}
|
||
|
||
/* Node Item in Sidebar */
|
||
.node-item {
|
||
background: rgba(0, 255, 209, 0.03);
|
||
border: 1px solid rgba(0, 255, 209, 0.1);
|
||
padding: 16px;
|
||
cursor: pointer;
|
||
transition: 0.3s;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.node-item::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
width: 2px;
|
||
height: 100%;
|
||
background: var(--primary-color);
|
||
opacity: 0;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.node-item:hover {
|
||
background: rgba(0, 255, 209, 0.08);
|
||
border-color: var(--primary-color);
|
||
transform: translateX(5px);
|
||
}
|
||
|
||
.node-item:hover::before {
|
||
opacity: 1;
|
||
}
|
||
|
||
.node-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.node-name {
|
||
font-size: 11px;
|
||
font-weight: bold;
|
||
color: var(--text-main);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.node-type {
|
||
font-size: 9px;
|
||
background: rgba(0, 255, 209, 0.15);
|
||
padding: 2px 6px;
|
||
border-radius: 2px;
|
||
color: var(--primary-color);
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.node-meta {
|
||
font-size: 9px;
|
||
color: var(--text-dim);
|
||
}
|
||
|
||
.signal-meter {
|
||
height: 3px;
|
||
background: rgba(0, 255, 209, 0.1);
|
||
margin-top: 8px;
|
||
width: 100%;
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.signal-strength {
|
||
height: 100%;
|
||
background: var(--primary-color);
|
||
box-shadow: 0 0 5px var(--primary-color);
|
||
}
|
||
|
||
@media (max-width: 1100px) {
|
||
.surveillance-grid {
|
||
grid-template-columns: 1fr;
|
||
height: auto;
|
||
}
|
||
|
||
.results-sidebar {
|
||
height: 300px;
|
||
order: 2;
|
||
}
|
||
|
||
.map-container {
|
||
height: 50vh;
|
||
order: 1;
|
||
}
|
||
}
|
||
|
||
.toast {
|
||
position: fixed;
|
||
bottom: 30px;
|
||
right: 30px;
|
||
background: var(--danger);
|
||
color: #fff;
|
||
padding: 15px 30px;
|
||
border-radius: 4px;
|
||
display: none;
|
||
text-transform: uppercase;
|
||
font-weight: bold;
|
||
font-size: 12px;
|
||
z-index: 2000;
|
||
box-shadow: 0 0 20px rgba(255, 23, 68, 0.4);
|
||
}
|
||
|
||
/* ---------- Custom Leaflet Popup ---------- */
|
||
.leaflet-popup-content-wrapper {
|
||
background: var(--glass-bg);
|
||
color: var(--text-main);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 0;
|
||
}
|
||
|
||
.leaflet-popup-tip {
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
.leaflet-container a.leaflet-popup-close-button {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
/* ---------- Responsive ---------- */
|
||
@media (max-width: 968px) {
|
||
.container {
|
||
margin-left: 0;
|
||
padding: 20px;
|
||
}
|
||
|
||
.sidebar {
|
||
transform: translateX(-100%);
|
||
width: 260px;
|
||
}
|
||
|
||
.sidebar.open {
|
||
transform: translateX(0);
|
||
}
|
||
|
||
.hamburger {
|
||
display: flex !important;
|
||
}
|
||
|
||
.nav-stats {
|
||
display: none;
|
||
}
|
||
|
||
.search-bar {
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
|
||
.hamburger {
|
||
display: none;
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 1px solid var(--border-color);
|
||
background: rgba(0, 255, 209, 0.05);
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.hamburger span {
|
||
width: 24px;
|
||
height: 2px;
|
||
background: var(--primary-color);
|
||
position: relative;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.hamburger span::before,
|
||
.hamburger span::after {
|
||
content: '';
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 2px;
|
||
background: var(--primary-color);
|
||
left: 0;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.hamburger span::before {
|
||
top: -8px;
|
||
}
|
||
|
||
.hamburger span::after {
|
||
top: 8px;
|
||
}
|
||
|
||
.hamburger.open span {
|
||
background: transparent;
|
||
}
|
||
|
||
.hamburger.open span::before {
|
||
transform: rotate(45deg);
|
||
top: 0;
|
||
}
|
||
|
||
.hamburger.open span::after {
|
||
transform: rotate(-45deg);
|
||
top: 0;
|
||
}
|
||
|
||
.sidebar-backdrop {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
z-index: 950;
|
||
}
|
||
|
||
.sidebar-backdrop.show {
|
||
display: block;
|
||
}
|
||
|
||
/* Scrollbars */
|
||
::-webkit-scrollbar {
|
||
width: 5px;
|
||
height: 5px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--primary-color);
|
||
border-radius: 10px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: var(--bg-dark);
|
||
}
|
||
|
||
/* Additional styles for existing elements not explicitly covered by the new CSS, adapted to the theme */
|
||
.dashboard {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 16px;
|
||
padding: 20px 0;
|
||
}
|
||
|
||
.card {
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
padding: 16px;
|
||
color: var(--text-main);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.card h2 {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-bottom: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.card h2 i {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.apps-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||
gap: 12px;
|
||
}
|
||
|
||
.app-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 12px;
|
||
background: rgba(0, 255, 209, 0.05);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
text-align: center;
|
||
}
|
||
|
||
.app-item:hover {
|
||
background: rgba(0, 255, 209, 0.1);
|
||
transform: translateY(-2px);
|
||
box-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.app-item i {
|
||
font-size: 24px;
|
||
margin-bottom: 8px;
|
||
color: var(--primary-color);
|
||
/* Changed from yellow */
|
||
}
|
||
|
||
.app-item span {
|
||
font-size: 12px;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.bot-status p {
|
||
font-size: 13px;
|
||
margin-bottom: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.bot-status p i {
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
#coords {
|
||
font-size: 13px;
|
||
color: var(--primary-color);
|
||
/* Changed from yellow */
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
#history div {
|
||
font-size: 12px;
|
||
color: var(--text-dim);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
#results {
|
||
margin-top: 12px;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
padding: 10px;
|
||
background: rgba(4, 43, 43, 0.8);
|
||
border: 2px solid var(--border-color);
|
||
border-radius: 4px;
|
||
color: var(--primary-color);
|
||
/* Changed from yellow */
|
||
}
|
||
|
||
.device {
|
||
background: rgba(0, 255, 209, 0.1);
|
||
border: 1px solid var(--primary-color);
|
||
padding: 8px;
|
||
margin-bottom: 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.refresh-btn {
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
color: var(--primary-color);
|
||
/* Changed from yellow */
|
||
margin-top: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.refresh-btn i {
|
||
color: var(--primary-color);
|
||
/* Changed from yellow */
|
||
}
|
||
|
||
/* Spotify Mini Player */
|
||
#spotify {
|
||
background: transparent;
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.track-info {
|
||
background: transparent;
|
||
border-radius: 4px;
|
||
padding: 12px;
|
||
max-width: 280px;
|
||
}
|
||
|
||
.track-info>img {
|
||
width: 50px;
|
||
height: 50px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--primary-color);
|
||
object-fit: cover;
|
||
float: left;
|
||
margin-right: 10px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.track-info h3 {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text-main);
|
||
margin-bottom: 3px;
|
||
margin-top: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.track-info>p {
|
||
font-size: 10px;
|
||
color: var(--text-dim);
|
||
opacity: 0.7;
|
||
margin-bottom: 2px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.controls {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin: 10px 0 8px 0;
|
||
clear: both;
|
||
}
|
||
|
||
.controls button {
|
||
background: transparent;
|
||
border: 1.5px solid var(--primary-color);
|
||
color: var(--primary-color);
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 10px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.controls button:hover {
|
||
background: rgba(0, 255, 209, 0.1);
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
#play-button,
|
||
#pause-button {
|
||
width: 34px;
|
||
height: 34px;
|
||
background: var(--primary-color);
|
||
color: var(--bg-dark);
|
||
font-size: 12px;
|
||
border: none;
|
||
}
|
||
|
||
#play-button:hover,
|
||
#pause-button:hover {
|
||
background: var(--primary-color);
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.progress-container {
|
||
width: 100%;
|
||
margin: 8px 0;
|
||
}
|
||
|
||
.progress-bar {
|
||
width: 100%;
|
||
height: 3px;
|
||
background: rgba(0, 255, 209, 0.2);
|
||
border-radius: 2px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.progress-fill {
|
||
height: 100%;
|
||
background: var(--primary-color);
|
||
border-radius: 2px;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
.device-selector {
|
||
margin-top: 10px;
|
||
padding-top: 10px;
|
||
border-top: 1px solid rgba(0, 255, 209, 0.3);
|
||
}
|
||
|
||
.device-selector h4 {
|
||
font-size: 11px;
|
||
color: var(--text-main);
|
||
margin-bottom: 6px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.device-selector ul {
|
||
list-style: none;
|
||
padding: 0;
|
||
margin-bottom: 6px;
|
||
max-height: 60px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.device-selector li {
|
||
padding: 5px 8px;
|
||
color: var(--text-main);
|
||
font-size: 9px;
|
||
margin-bottom: 3px;
|
||
background: rgba(0, 255, 209, 0.05);
|
||
border-radius: 3px;
|
||
border: 1px solid var(--border-color);
|
||
}
|
||
|
||
#device-select {
|
||
width: 100%;
|
||
padding: 6px 8px;
|
||
background: transparent;
|
||
border: 1.5px solid var(--primary-color);
|
||
color: var(--text-main);
|
||
border-radius: 4px;
|
||
font-size: 10px;
|
||
margin-bottom: 6px;
|
||
cursor: pointer;
|
||
outline: none;
|
||
}
|
||
|
||
#device-select option {
|
||
background: var(--bg-dark);
|
||
color: var(--text-main);
|
||
}
|
||
|
||
#change-device {
|
||
width: 100%;
|
||
padding: 6px 10px;
|
||
background: transparent;
|
||
border: 1.5px solid var(--primary-color);
|
||
color: var(--text-main);
|
||
border-radius: 4px;
|
||
font-size: 10px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
#change-device:hover {
|
||
background: var(--primary-color);
|
||
color: var(--bg-dark);
|
||
}
|
||
|
||
#spotify>p {
|
||
color: var(--text-main);
|
||
text-align: center;
|
||
font-size: 12px;
|
||
opacity: 0.7;
|
||
padding: 20px;
|
||
}
|
||
|
||
/* Responsive Adjustments */
|
||
@media (max-width: 1200px) {
|
||
.dashboard {
|
||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||
}
|
||
|
||
.apps-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(90px, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 968px) {
|
||
.dashboard {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.apps-grid {
|
||
grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
|
||
}
|
||
}
|
||
|
||
@media (max-width: 420px) {
|
||
.topnav {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.logo-text h2 {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.logo-text p {
|
||
display: none;
|
||
}
|
||
|
||
.app-item span {
|
||
font-size: 11px;
|
||
}
|
||
}
|
||
|
||
/* Main Content specific styles */
|
||
.main-content {
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
min-height: calc(100vh - 60px);
|
||
}
|
||
|
||
/* Controls and Cards */
|
||
.controls {
|
||
background: var(--glass-bg);
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border-color);
|
||
margin-bottom: 20px;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.search-input {
|
||
flex: 1;
|
||
padding: 12px 14px;
|
||
border-radius: 4px;
|
||
border: 1px solid rgba(0, 255, 209, 0.14);
|
||
background: rgba(0, 255, 209, 0.03);
|
||
color: var(--text-main);
|
||
}
|
||
|
||
.search-btn {
|
||
padding: 12px 18px;
|
||
border-radius: 4px;
|
||
background: linear-gradient(135deg, var(--primary-color), var(--accent-color));
|
||
color: var(--bg-dark);
|
||
border: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.filters {
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.filter-chip {
|
||
padding: 8px 12px;
|
||
border-radius: 20px;
|
||
border: 1px solid var(--border-color);
|
||
background: rgba(0, 255, 209, 0.03);
|
||
color: var(--text-dim);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.cards-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||
gap: 16px;
|
||
}
|
||
|
||
.device-card {
|
||
background: var(--glass-bg);
|
||
padding: 16px;
|
||
border-radius: 4px;
|
||
border: 1px solid rgba(0, 255, 209, 0.08);
|
||
transition: all 0.25s;
|
||
overflow: hidden;
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.card-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.device-name {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.device-code {
|
||
font-size: 11px;
|
||
color: var(--text-dim);
|
||
font-family: monospace;
|
||
}
|
||
|
||
.card-actions {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 10px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.action-btn {
|
||
padding: 10px;
|
||
border-radius: 4px;
|
||
border: 1px solid var(--border-color);
|
||
background: rgba(0, 255, 209, 0.03);
|
||
color: var(--text-main);
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Map Popup */
|
||
.map-popup {
|
||
position: fixed;
|
||
inset: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.7);
|
||
z-index: 2000;
|
||
}
|
||
|
||
.map-popup-content {
|
||
background: var(--glass-bg);
|
||
padding: 14px;
|
||
border-radius: 4px;
|
||
width: 600px;
|
||
max-width: 95%;
|
||
}
|
||
|
||
.sidebar-backdrop {
|
||
display: none;
|
||
position: fixed;
|
||
inset: 60px 0 0 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
z-index: 950;
|
||
}
|
||
|
||
.sidebar-backdrop.show {
|
||
display: block;
|
||
}
|
||
|
||
.main-content {
|
||
padding: 12px;
|
||
}
|
||
|
||
.cards-grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.card-actions {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
@media (max-width: 420px) {
|
||
.topnav {
|
||
padding: 0 10px;
|
||
}
|
||
|
||
.logo-text h2 {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.logo-text p {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
/* Scrollbars */
|
||
::-webkit-scrollbar {
|
||
width: 6px;
|
||
height: 6px;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: rgba(0, 255, 255, 0.2);
|
||
border-radius: 6px;
|
||
}
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
<div class="bg-grid"></div>
|
||
|
||
<!-- Top Navigation -->
|
||
<nav class="topnav">
|
||
<div class="left-group">
|
||
<button class="hamburger" id="hamburger" aria-label="Open menu" aria-expanded="false">
|
||
<span></span>
|
||
</button>
|
||
|
||
<div class="logo-section" onclick="window.location.href='/'" style="cursor: pointer;">
|
||
<div class="logo-icon" aria-hidden="true">
|
||
<svg width="40" height="40" viewBox="0 0 80 80" xmlns="http://www.w3.org/2000/svg">
|
||
<g transform="translate(10, 15)">
|
||
<path
|
||
d="M30 30 C25 15, 35 10, 40 15 C45 10, 55 15, 50 30 C55 45, 45 50, 40 45 C35 50, 25 45, 30 30"
|
||
fill="none" stroke="#00FF88" stroke-width="4" />
|
||
<path d="M30 30 C25 20, 20 15, 15 10" stroke="#00FF88" stroke-width="3" fill="none" />
|
||
<path d="M50 30 C55 20, 60 15, 65 10" stroke="#00FF88" stroke-width="3" fill="none" />
|
||
<path d="M30 30 C25 40, 20 45, 15 50" stroke="#00FF88" stroke-width="3" fill="none" />
|
||
<path d="M50 30 C55 40, 60 45, 65 50" stroke="#00FF88" stroke-width="3" fill="none" />
|
||
<circle cx="30" cy="30" r="5" fill="none" stroke="#00FF88" stroke-width="2" opacity="0.0" />
|
||
<circle cx="50" cy="30" r="5" fill="none" stroke="#00FF88" stroke-width="2" opacity="0.0" />
|
||
<circle cx="15" cy="10" r="3" fill="none" stroke="#00FF88" stroke-width="1.5"
|
||
opacity="0.0" />
|
||
<circle cx="65" cy="10" r="3" fill="none" stroke="#00FF88" stroke-width="1.5"
|
||
opacity="0.0" />
|
||
<circle cx="15" cy="50" r="3" fill="none" stroke="#00FF88" stroke-width="1.5"
|
||
opacity="0.0" />
|
||
<circle cx="65" cy="50" r="3" fill="none" stroke="#00FF88" stroke-width="1.5"
|
||
opacity="0.0" />
|
||
</g>
|
||
</svg>
|
||
</div>
|
||
<div class="logo-text">
|
||
<h2>HayOS H9 EYE</h2>
|
||
<p id="user-info">Created by Hayden |</p>
|
||
<script>
|
||
(async function () {
|
||
try {
|
||
const res = await fetch('/api/username', { cache: 'no-store' });
|
||
const data = await res.json();
|
||
|
||
const userInfo = document.getElementById('user-info');
|
||
const deviceCount = document.getElementById('device-counts');
|
||
const statItem = document.querySelector('.stat-item');
|
||
|
||
if (data && data.username) {
|
||
// ✅ Insert username inside <p id="user-info">
|
||
userInfo.innerHTML = `Created by Hayden | ${data.username}`;
|
||
|
||
// ✅ Update device count
|
||
deviceCount.textContent = 'Online';
|
||
|
||
// ✅ Add logout icon inside stat-item
|
||
const logoutBtn = document.createElement('a');
|
||
logoutBtn.href = '/logout';
|
||
logoutBtn.innerHTML = '<i class="fas fa-sign-out-alt"></i>';
|
||
logoutBtn.title = 'Logout';
|
||
logoutBtn.style.marginLeft = '10px';
|
||
logoutBtn.style.color = '#00FFFF';
|
||
logoutBtn.style.cursor = 'pointer';
|
||
logoutBtn.style.textDecoration = 'none';
|
||
logoutBtn.style.fontSize = '16px';
|
||
logoutBtn.addEventListener('mouseenter', () => logoutBtn.style.color = '#FF4444');
|
||
logoutBtn.addEventListener('mouseleave', () => logoutBtn.style.color = '#00FFFF');
|
||
|
||
statItem.appendChild(logoutBtn);
|
||
} else {
|
||
userInfo.innerHTML = 'Created by Hayden | Guest';
|
||
deviceCount.textContent = 'Offline';
|
||
}
|
||
} catch (err) {
|
||
console.warn('Failed to fetch username:', err);
|
||
const userInfo = document.getElementById('user-info');
|
||
const deviceCount = document.getElementById('device-count');
|
||
if (userInfo) userInfo.innerHTML = 'Created by Hayden | Guest';
|
||
if (deviceCount) deviceCount.textContent = 'Offline';
|
||
}
|
||
})();
|
||
</script>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="nav-stats">
|
||
<div class="stat-item">
|
||
<div class="status-dot" aria-hidden="true"></div>
|
||
<span id="device-counts">Loading...</span>
|
||
</div>
|
||
<span id="error-message" style="display:none;margin-left:12px;color:#FF6B6B;font-size:13px;"></span>
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- Sidebar backdrop (mobile) -->
|
||
<div class="sidebar-backdrop" id="sidebar-backdrop" aria-hidden="true"></div>
|
||
|
||
<div class="container">
|
||
<!-- Vertical Sidebar -->
|
||
<aside class="sidebar" id="sidebar" aria-label="Primary navigation">
|
||
<div class="menu-group">
|
||
<div class="menu-title">Main</div>
|
||
<div class="menu-item active" data-href="/crmx"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
||
</svg><span>Dashboard</span></div>
|
||
<div class="menu-item" data-href="/surveillance"><svg class="menu-icon" fill="none"
|
||
stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />
|
||
<circle cx="12" cy="12" r="3" />
|
||
</svg><span>Surveillance</span></div>
|
||
<div class="menu-item" id="tor-chat" data-href="">
|
||
<svg class="menu-icon" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
||
<rect x="3" y="3" width="7" height="7" />
|
||
<rect x="14" y="3" width="7" height="7" />
|
||
<rect x="14" y="14" width="7" height="7" />
|
||
<rect x="3" y="14" width="7" height="7" />
|
||
</svg>
|
||
<span>Admin Pannel</span>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<div class="menu-item" data-href="/profiles"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||
<circle cx="9" cy="7" r="4" />
|
||
</svg><span>Profiles</span></div>
|
||
<div class="menu-item" data-href="/profiles"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||
</svg><span>Local Tor Chat</span></div>
|
||
<div class="menu-item" data-href="/geo"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z" />
|
||
<circle cx="12" cy="10" r="3" />
|
||
</svg><span>Geo Tracking</span></div>
|
||
</div>
|
||
|
||
<div class="menu-group">
|
||
<div class="menu-title">Data Sources</div>
|
||
<div class="menu-item" data-href="/cctv"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path
|
||
d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z" />
|
||
<circle cx="12" cy="13" r="3" />
|
||
</svg><span>CCTV Feeds</span></div>
|
||
<div class="menu-item" data-href="/mobile"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<circle cx="11" cy="11" r="8" />
|
||
<path d="m21 21-4.35-4.35" />
|
||
</svg><span>Mobile Tracking</span></div>
|
||
<div class="menu-item" data-href="/social"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
||
</svg><span>Social Media</span></div>
|
||
</div>
|
||
|
||
<div class="menu-group">
|
||
<div class="menu-title">System</div>
|
||
<div class="menu-item" data-href="/alerts"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" />
|
||
</svg><span>Alerts</span></div>
|
||
<div class="menu-item" data-href="fit"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" />
|
||
<path d="M8 12 L10 14 L16 8" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"
|
||
stroke-linejoin="round" fill="none" />
|
||
</svg><span>Fit</span></div>
|
||
<div class="menu-item" data-href="settings"><svg class="menu-icon" fill="none" stroke="currentColor"
|
||
stroke-width="2" viewBox="0 0 24 24">
|
||
<circle cx="12" cy="12" r="3" />
|
||
<path d="M12 1v6m0 6v6m-9-9h6m6 0h6" />
|
||
</svg><span>Settings</span></div>
|
||
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content" id="main">
|
||
<!-- Map Layer -->
|
||
<div class="map-wrapper">
|
||
<div id="map"></div>
|
||
</div>
|
||
|
||
<!-- Absolute Positioning Overlays -->
|
||
<div class="page-header">
|
||
<h1>HayOS - WiFi Surveillance Tracker</h1>
|
||
<p>Coded by Hayden Ø Powered by H9</p>
|
||
</div>
|
||
|
||
<div class="control-panel">
|
||
<div class="mode-toggle-container">
|
||
<span class="mode-label active" id="label-wifi">WIFI UPLINK</span>
|
||
<label class="switch">
|
||
<input type="checkbox" id="mode-switch" onchange="toggleMode()">
|
||
<span class="slider"></span>
|
||
</label>
|
||
<span class="mode-label" id="label-bluetooth">BT SCANNERS</span>
|
||
</div>
|
||
|
||
<div class="search-bar">
|
||
<div class="search-row">
|
||
<select id="search-type">
|
||
<option value="location">Location</option>
|
||
<option value="bssid">BSSID</option>
|
||
<option value="ssid">Wi-Fi SSID</option>
|
||
<option value="network">Network/IP</option>
|
||
</select>
|
||
<input type="text" id="search-input" placeholder="Enter search term">
|
||
</div>
|
||
<button class="search-btn" onclick="performSearch()">Intercept Targets</button>
|
||
</div>
|
||
|
||
<div class="filter-row">
|
||
<div
|
||
style="font-size: 10px; color: var(--text-dim); text-transform: uppercase; margin-bottom: 5px; letter-spacing: 1px;">
|
||
Hardware Filters:</div>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="router">
|
||
<div class="box"></div> WIFI
|
||
</label>
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="bluetooth">
|
||
<div class="box"></div> BLUETOOTH
|
||
</label>
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="camera">
|
||
<div class="box"></div> CCTV/CAM
|
||
</label>
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="dashcam">
|
||
<div class="box"></div> DASHCAM
|
||
</label>
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="tv">
|
||
<div class="box"></div> SMART_TV
|
||
</label>
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="car">
|
||
<div class="box"></div> VEHICLE
|
||
</label>
|
||
<label class="cyber-check"><input type="checkbox" checked class="filter-cb" value="headphone">
|
||
<div class="box"></div> AUDIO
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top: 15px; padding-top: 15px; border-top: 1px solid rgba(0, 255, 209, 0.1);">
|
||
<a href="#" class="search-btn" id="download-btn"
|
||
style="display: none; text-align: center; text-decoration: none;">Download Intel Report</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="results-sidebar" id="results-list">
|
||
<div style="text-align: center; color: var(--text-dim); padding: 50px 20px;">
|
||
<i class="fas fa-satellite-dish"
|
||
style="font-size: 24px; margin-bottom: 15px; display: block; color: var(--primary-color);"></i>
|
||
Awaiting Scanner Data...
|
||
</div>
|
||
</div>
|
||
|
||
<div class="toast" id="toast">No connections intercepted</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Leaflet JS from CDN -->
|
||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||
<!-- Leaflet MarkerCluster JS -->
|
||
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js"></script>
|
||
<script>
|
||
let map;
|
||
let markers = L.markerClusterGroup();
|
||
let results = [];
|
||
|
||
function initMap() {
|
||
map = L.map('map').setView([51.505, -0.09], 13);
|
||
|
||
const cyberLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||
attribution: '© OpenStreetMap contributors'
|
||
});
|
||
|
||
const satelliteLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
|
||
attribution: '© Esri, Maxar, Earthstar Geographics'
|
||
});
|
||
|
||
const hybridLayer = L.tileLayer('https://{s}.google.com/vt/lyrs=y&x={x}&y={y}&z={z}', {
|
||
maxZoom: 20,
|
||
subdomains: ['mt0', 'mt1', 'mt2', 'mt3'],
|
||
attribution: '© Google Maps'
|
||
});
|
||
|
||
const baseLayers = {
|
||
"CyberMap": cyberLayer,
|
||
"SatView": satelliteLayer,
|
||
"Hybrid": hybridLayer
|
||
};
|
||
|
||
L.control.layers(baseLayers).addTo(map);
|
||
cyberLayer.addTo(map);
|
||
document.getElementById('map').classList.add('cyber-map');
|
||
|
||
map.on('baselayerchange', (e) => {
|
||
if (e.name === "SatView") {
|
||
document.getElementById('map').classList.remove('cyber-map');
|
||
document.body.classList.add('sat-mode');
|
||
} else {
|
||
document.getElementById('map').classList.add('cyber-map');
|
||
document.body.classList.remove('sat-mode');
|
||
}
|
||
});
|
||
|
||
map.on('click', async (e) => {
|
||
const { lat, lng } = e.latlng;
|
||
await fetchNearbyDevices(lat, lng);
|
||
});
|
||
|
||
map.addLayer(markers);
|
||
}
|
||
|
||
// Toggle Mode logic
|
||
function toggleMode() {
|
||
const isBT = document.getElementById('mode-switch').checked;
|
||
document.getElementById('label-wifi').classList.toggle('active', !isBT);
|
||
document.getElementById('label-bluetooth').classList.toggle('active', isBT);
|
||
markers.clearLayers();
|
||
results = [];
|
||
showToast(`SENSORS_SWITCHED: ${isBT ? 'BLUETOOTH' : 'WIFI'}`);
|
||
}
|
||
|
||
// Fetch nearby devices from Flask backend
|
||
async function fetchNearbyDevices(lat, lon) {
|
||
const mode = document.getElementById('mode-switch').checked ? 'bluetooth' : 'wifi';
|
||
try {
|
||
const response = await fetch(`/nearby?lat=${lat}&lon=${lon}&mode=${mode}`);
|
||
const data = await response.json();
|
||
console.log('Nearby response:', data);
|
||
if (data.error) {
|
||
showToast(data.error);
|
||
return;
|
||
}
|
||
updateMap(data);
|
||
} catch (err) {
|
||
showToast('Error fetching devices');
|
||
console.error('Fetch error:', err);
|
||
}
|
||
}
|
||
|
||
// Perform search based on input
|
||
async function performSearch() {
|
||
const searchType = document.getElementById('search-type').value;
|
||
const searchInput = document.getElementById('search-input').value;
|
||
if (!searchInput) {
|
||
showToast('Please enter a search term');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const mode = document.getElementById('mode-switch').checked ? 'bluetooth' : 'wifi';
|
||
const response = await fetch(`/searchzz?type=${searchType}&query=${encodeURIComponent(searchInput)}&mode=${mode}`);
|
||
const data = await response.json();
|
||
console.log('Search response:', data);
|
||
if (data.error) {
|
||
showToast(data.error);
|
||
return;
|
||
}
|
||
updateMap(data);
|
||
} catch (err) {
|
||
showToast('Error performing search');
|
||
console.error('Search error:', err);
|
||
}
|
||
}
|
||
|
||
// Update map with device markers
|
||
let markerMap = {};
|
||
|
||
function updateMap(data) {
|
||
markers.clearLayers();
|
||
markerMap = {};
|
||
if (data && data.devices) results = data.devices;
|
||
console.log('Processed results for map:', results);
|
||
|
||
// Get active filters
|
||
const activeFilters = Array.from(document.querySelectorAll('.filter-cb:checked')).map(cb => cb.value);
|
||
|
||
// Apply filtering
|
||
const filteredResults = results.filter(device => {
|
||
const type = device.type || 'unknown';
|
||
if (type === 'router' || type === 'cell_tower' || type === 'iot_device') {
|
||
// For WiFi mode, show these if bluetooth isn't the primary focus
|
||
// or if they match generic categories.
|
||
// Actually, simple logic: if it's in the filters, show it.
|
||
return activeFilters.includes(type) || activeFilters.includes('router');
|
||
}
|
||
return activeFilters.includes(type);
|
||
});
|
||
|
||
if (filteredResults.length === 0) {
|
||
showToast('No devices found');
|
||
document.getElementById('results-list').innerHTML = `
|
||
<div style="text-align: center; color: var(--text-dim); padding: 50px 20px;">
|
||
No Matches Found
|
||
</div>`;
|
||
document.getElementById('download-btn').style.display = 'none';
|
||
return;
|
||
}
|
||
|
||
renderSidebar(filteredResults);
|
||
|
||
filteredResults.forEach((device, index) => {
|
||
const { lat, lon, ssid, bssid, cell_id, ip, vendor, signal, accuracy, timestamp, type } = device;
|
||
if (lat && lon) {
|
||
const icon = getDeviceIcon(type);
|
||
const marker = L.marker([lat, lon], { icon })
|
||
.bindPopup(`
|
||
<div style="font-family: 'Share Tech Mono', monospace; color: var(--primary-color);">
|
||
<strong style="font-size: 14px;">${ssid || cell_id || ip || 'Unknown Node'}</strong><br>
|
||
<hr style="border: 0.5px solid rgba(0,255,209,0.2); margin: 5px 0;">
|
||
<span style="font-size: 10px; color: #fff;">CATEGORY: ${type ? type.toUpperCase() : 'UNKNOWN'}</span><br>
|
||
${bssid ? `BSSID: ${bssid}<br>` : ''}
|
||
${vendor ? `VENDOR: ${vendor}<br>` : ''}
|
||
${signal ? `SIGNAL: ${signal} dBm<br>` : ''}
|
||
${accuracy ? `ACCURACY: ${accuracy} m<br>` : ''}
|
||
${timestamp ? `LAST_SEEN: ${new Date(timestamp).toLocaleString()}<br>` : ''}
|
||
</div>
|
||
`);
|
||
markers.addLayer(marker);
|
||
markerMap[index] = marker;
|
||
}
|
||
});
|
||
|
||
map.addLayer(markers);
|
||
|
||
const validDevices = filteredResults.filter(d => d.lat && d.lon);
|
||
if (validDevices.length > 0) {
|
||
const group = new L.featureGroup(validDevices.map(d => L.marker([d.lat, d.lon])));
|
||
map.fitBounds(group.getBounds().pad(0.2));
|
||
document.getElementById('download-btn').style.display = 'block';
|
||
setupDownload();
|
||
}
|
||
}
|
||
|
||
// Add listeners for real-time filtering
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('.filter-cb').forEach(cb => {
|
||
cb.addEventListener('change', () => {
|
||
updateMap(); // Re-render with existing 'results'
|
||
});
|
||
});
|
||
});
|
||
|
||
function renderSidebar(nodes) {
|
||
const list = document.getElementById('results-list');
|
||
list.innerHTML = '';
|
||
|
||
// Get active filters
|
||
const activeFilters = Array.from(document.querySelectorAll('.filter-cb:checked')).map(cb => cb.value);
|
||
|
||
const filteredNodes = nodes.filter(node => {
|
||
const type = node.type || 'unknown';
|
||
if (type === 'router' || type === 'cell_tower' || type === 'iot_device') {
|
||
return activeFilters.includes(type) || activeFilters.includes('router');
|
||
}
|
||
return activeFilters.includes(type);
|
||
});
|
||
|
||
filteredNodes.forEach((node) => {
|
||
const signal = node.signal ? Math.min(Math.abs(node.signal), 100) : 50;
|
||
const percent = 100 - (signal > 100 ? 100 : signal);
|
||
const lastSeen = node.timestamp ? formatRelativeTime(node.timestamp) : 'LIVE';
|
||
|
||
const item = document.createElement('div');
|
||
item.className = 'node-item';
|
||
item.onclick = () => {
|
||
// find marker by coordinates as we might have filtered
|
||
const marker = markers.getLayers().find(m => m.getLatLng().lat === node.lat && m.getLatLng().lng === node.lon);
|
||
if (marker) {
|
||
map.setView(marker.getLatLng(), 17);
|
||
marker.openPopup();
|
||
}
|
||
};
|
||
|
||
item.innerHTML = `
|
||
<div class="node-header">
|
||
<span class="node-name">${node.ssid || node.ip || 'Unknown'}</span>
|
||
<span class="node-type" style="background: ${getCategoryColor(node.type)}44; color: ${getCategoryColor(node.type)};">${node.type || 'node'}</span>
|
||
</div>
|
||
<div class="node-meta">
|
||
${node.vendor ? `VENDOR: ${node.vendor}` : `ID: ${node.bssid || node.ip || 'ANONYMOUS'}`}<br>
|
||
<span style="color: var(--accent-color); font-size: 9px;">LAST_SEEN: ${lastSeen}</span>
|
||
</div>
|
||
<div class="signal-meter">
|
||
<div class="signal-strength" style="width: ${percent}%; background: ${getCategoryColor(node.type)}; box-shadow: 0 0 5px ${getCategoryColor(node.type)}"></div>
|
||
</div>
|
||
`;
|
||
list.appendChild(item);
|
||
});
|
||
}
|
||
|
||
function formatRelativeTime(timestamp) {
|
||
const date = new Date(timestamp);
|
||
const now = new Date();
|
||
const diff = Math.floor((now - date) / 1000);
|
||
|
||
if (diff < 60) return `${diff}s AGO`;
|
||
if (diff < 3600) return `${Math.floor(diff / 60)}m AGO`;
|
||
if (diff < 86400) return `${Math.floor(diff / 3600)}h AGO`;
|
||
return date.toLocaleDateString();
|
||
}
|
||
|
||
function getCategoryColor(type) {
|
||
const colors = {
|
||
bluetooth: '#4A90E2',
|
||
car: '#FFD700',
|
||
camera: '#FF4444',
|
||
dashcam: '#FF4444',
|
||
tv: '#00FF88',
|
||
headphone: '#00FF88',
|
||
router: 'var(--primary-color)',
|
||
cell_tower: '#FF00FF'
|
||
};
|
||
return colors[type] || 'var(--primary-color)';
|
||
}
|
||
|
||
// Get icon based on device type
|
||
function getDeviceIcon(type) {
|
||
const icons = {
|
||
router: 'fas fa-wifi',
|
||
cell_tower: 'fas fa-broadcast-tower',
|
||
camera: 'fas fa-camera',
|
||
dashcam: 'fas fa-video',
|
||
car: 'fas fa-car',
|
||
tv: 'fas fa-tv',
|
||
headphone: 'fas fa-headphones',
|
||
iot_device: 'fas fa-microchip',
|
||
iot: 'fas fa-microchip',
|
||
bluetooth: 'fab fa-bluetooth',
|
||
default: 'fas fa-question-circle'
|
||
};
|
||
const iconClass = icons[type] || icons.default;
|
||
|
||
let color = 'var(--primary-color)';
|
||
if (type === 'bluetooth') color = '#4A90E2';
|
||
if (type === 'car') color = '#FFD700'; // Gold for transport
|
||
if (type === 'camera' || type === 'dashcam') color = '#FF4444'; // Red for surveillance
|
||
if (type === 'tv' || type === 'headphone') color = '#00FF88'; // Green for entertainment
|
||
|
||
return L.divIcon({
|
||
html: `<i class="${iconClass}" style="color: ${color}; font-size: 20px; text-shadow: 0 0 10px ${color}"></i>`,
|
||
className: 'custom-icon',
|
||
iconSize: [30, 30],
|
||
iconAnchor: [15, 30]
|
||
});
|
||
}
|
||
|
||
// Setup download button
|
||
function setupDownload() {
|
||
const btn = document.getElementById('download-btn');
|
||
const blob = new Blob([JSON.stringify(results, null, 2)], { type: 'application/json' });
|
||
btn.href = URL.createObjectURL(blob);
|
||
btn.download = 'wifi-search-results.json';
|
||
}
|
||
|
||
// Show toast notification
|
||
function showToast(message) {
|
||
const toast = document.getElementById('toast');
|
||
toast.textContent = message;
|
||
toast.style.display = 'block';
|
||
setTimeout(() => {
|
||
toast.style.display = 'none';
|
||
}, 3000);
|
||
}
|
||
|
||
// Dummy content loader for sidebar (placeholder)
|
||
function loadContent(page) {
|
||
console.log(`Loading ${page}`);
|
||
}
|
||
|
||
// Initialize map on load
|
||
window.onload = initMap;
|
||
</script>
|
||
|
||
<!-- ==== ADD THIS RIGHT BEFORE </body> (still inside <body>) ==== -->
|
||
<div class="fab-container">
|
||
<button id="chat-fab" class="fab-btn" aria-label="Open AI Chat">
|
||
<i class="fas fa-comment-dots"></i>
|
||
</button>
|
||
</div>
|
||
<!-- ==== END FAB ==== -->
|
||
<!-- ==== ADD THIS RIGHT AFTER the FAB (still before </body>) ==== -->
|
||
<div id="chat-modal" class="chat-modal" aria-hidden="true">
|
||
<div class="chat-header">
|
||
<span>Hayden's H9-AI Assistant</span>
|
||
<button id="close-chat" class="chat-close" aria-label="Close">×</button>
|
||
</div>
|
||
<div id="chat-messages" class="chat-messages"></div>
|
||
<!-- ADD THIS BEFORE <div class="chat-input-area"> -->
|
||
<div class="web-search-toggle">
|
||
<label>
|
||
<input type="checkbox" id="web-search-enabled">
|
||
<span class="toggle-slider"></span>
|
||
</label>
|
||
<span class="toggle-label">Web Search</span>
|
||
</div>
|
||
<!-- END ADD -->
|
||
<div class="chat-input-area">
|
||
<input type="text" id="chat-input" placeholder="Ask me anything…" autocomplete="off">
|
||
<button id="chat-send" aria-label="Send">
|
||
<i class="fas fa-paper-plane"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<!-- ==== END CHAT MODAL ==== -->
|
||
<style>
|
||
.web-search-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 12px;
|
||
font-size: 12px;
|
||
color: #00FFFF;
|
||
user-select: none;
|
||
}
|
||
|
||
.web-search-toggle input {
|
||
display: none;
|
||
}
|
||
|
||
.toggle-slider {
|
||
position: relative;
|
||
width: 34px;
|
||
height: 18px;
|
||
background: rgba(0, 255, 255, 0.2);
|
||
border: 1px solid #00FFFF;
|
||
border-radius: 18px;
|
||
cursor: pointer;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.toggle-slider::after {
|
||
content: '';
|
||
position: absolute;
|
||
width: 14px;
|
||
height: 14px;
|
||
background: #00FFFF;
|
||
border-radius: 50%;
|
||
top: 1px;
|
||
left: 1px;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
#web-search-enabled:checked+.toggle-slider {
|
||
background: #00FFFF;
|
||
}
|
||
|
||
#web-search-enabled:checked+.toggle-slider::after {
|
||
background: #0A1F1F;
|
||
transform: translateX(16px);
|
||
}
|
||
|
||
.toggle-label {
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ==== CHAT POPUP STYLES ==== */
|
||
.chat-modal {
|
||
position: fixed;
|
||
bottom: 90px;
|
||
right: 24px;
|
||
width: 380px;
|
||
height: 520px;
|
||
max-width: 90vw;
|
||
background: rgba(2, 10, 10, 0.95);
|
||
backdrop-filter: blur(15px);
|
||
border: 1px solid var(--border-color);
|
||
border-radius: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
|
||
transform: scale(0);
|
||
transform-origin: bottom right;
|
||
transition: 0.3s;
|
||
z-index: 1300;
|
||
}
|
||
|
||
.chat-modal.open {
|
||
transform: scale(1);
|
||
}
|
||
|
||
.chat-header {
|
||
background: rgba(0, 255, 209, 0.1);
|
||
padding: 15px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
border-bottom: 1px solid var(--border-color);
|
||
color: var(--primary-color);
|
||
text-transform: uppercase;
|
||
font-size: 12px;
|
||
letter-spacing: 2px;
|
||
}
|
||
|
||
.chat-messages {
|
||
flex: 1;
|
||
padding: 15px;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.message {
|
||
max-width: 85%;
|
||
padding: 10px 15px;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.message.user {
|
||
align-self: flex-end;
|
||
background: rgba(0, 255, 209, 0.15);
|
||
border: 1px solid var(--primary-color);
|
||
color: var(--primary-color);
|
||
}
|
||
|
||
.message.bot {
|
||
align-self: flex-start;
|
||
background: rgba(0, 163, 255, 0.1);
|
||
border: 1px solid var(--secondary-color);
|
||
color: var(--secondary-color);
|
||
}
|
||
|
||
.chat-input-area {
|
||
display: flex;
|
||
border-top: 1px solid var(--border-color);
|
||
background: rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
#chat-input {
|
||
flex: 1;
|
||
padding: 15px;
|
||
background: transparent;
|
||
border: none;
|
||
color: #fff;
|
||
font-family: var(--font-main);
|
||
}
|
||
|
||
#chat-input:focus {
|
||
outline: none;
|
||
}
|
||
|
||
#chat-send {
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--primary-color);
|
||
width: 50px;
|
||
cursor: pointer;
|
||
transition: 0.3s;
|
||
}
|
||
|
||
#chat-send:hover {
|
||
transform: scale(1.2);
|
||
text-shadow: var(--neon-glow);
|
||
}
|
||
|
||
.fab-btn {
|
||
width: 60px;
|
||
height: 60px;
|
||
border-radius: 50%;
|
||
background: var(--bg-dark);
|
||
border: 1px solid var(--primary-color);
|
||
color: var(--primary-color);
|
||
font-size: 24px;
|
||
cursor: pointer;
|
||
box-shadow: var(--neon-glow);
|
||
transition: 0.3s;
|
||
}
|
||
|
||
.fab-btn:hover {
|
||
transform: scale(1.1) rotate(15deg);
|
||
box-shadow: 0 0 25px var(--primary-color);
|
||
}
|
||
</style>
|
||
<script>
|
||
/* ==== CHAT POPUP LOGIC ==== */
|
||
const fabBtn = document.getElementById('chat-fab');
|
||
const modal = document.getElementById('chat-modal');
|
||
const closeBtn = document.getElementById('close-chat');
|
||
const input = document.getElementById('chat-input');
|
||
const sendBtn = document.getElementById('chat-send');
|
||
const messages = document.getElementById('chat-messages');
|
||
|
||
function openChat() { modal.classList.add('open'); input.focus(); }
|
||
function closeChat() { modal.classList.remove('open'); input.value = ''; }
|
||
fabBtn.addEventListener('click', openChat);
|
||
closeBtn.addEventListener('click', closeChat);
|
||
modal.addEventListener('click', e => { if (e.target === modal) closeChat(); });
|
||
|
||
function addMessage(text, type) {
|
||
const div = document.createElement('div');
|
||
div.className = `message ${type}`;
|
||
if (type === 'bot' && text === 'typing') {
|
||
div.innerHTML = `<span class="typing"></span><span class="typing"></span><span class="typing"></span>`;
|
||
} else {
|
||
div.textContent = text;
|
||
}
|
||
messages.appendChild(div);
|
||
messages.scrollTop = messages.scrollHeight;
|
||
}
|
||
|
||
/* ==== SEND MESSAGE → CALL YOUR FLASK ENDPOINT ==== */
|
||
async function sendMessage() {
|
||
const userText = input.value.trim();
|
||
if (!userText) return;
|
||
|
||
addMessage(userText, 'user');
|
||
input.value = '';
|
||
|
||
// Show typing
|
||
addMessage('typing', 'bot');
|
||
|
||
// Get web search state
|
||
const webSearchEnabled = document.getElementById('web-search-enabled').checked;
|
||
|
||
try {
|
||
const res = await fetch('/chatgpt', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
message: userText,
|
||
web_search: webSearchEnabled // NEW: send flag
|
||
})
|
||
});
|
||
const data = await res.json();
|
||
|
||
messages.lastElementChild.remove(); // remove typing
|
||
addMessage(data.reply, 'bot');
|
||
} catch (err) {
|
||
messages.lastElementChild.remove();
|
||
addMessage('Error – try again', 'bot');
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
sendBtn.addEventListener('click', sendMessage);
|
||
input.addEventListener('keydown', e => { if (e.key === 'Enter') sendMessage(); });
|
||
|
||
/* optional: close with ESC */
|
||
document.addEventListener('keydown', e => { if (e.key === 'Escape' && modal.classList.contains('open')) closeChat(); });
|
||
</script>
|
||
<!-- ==== SYNTAX HIGHLIGHTING ==== -->
|
||
<link rel="stylesheet"
|
||
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('pre code').forEach(hljs.highlightElement);
|
||
});
|
||
</script>
|
||
<!-- ==== END HIGHLIGHT ==== -->
|
||
<script>
|
||
function addMessage(text, type) {
|
||
const div = document.createElement('div');
|
||
div.className = `message ${type}`;
|
||
div.innerHTML = text; // Now supports HTML <pre><code>
|
||
|
||
messages.appendChild(div);
|
||
messages.scrollTop = messages.scrollHeight;
|
||
|
||
// Re-highlight new code blocks
|
||
div.querySelectorAll('pre code').forEach(hljs.highlightElement);
|
||
}
|
||
</script>
|
||
</body>
|
||
|
||
</html> |