diff --git a/api/app.py b/api/app.py index f1b45bd..92b8655 100644 --- a/api/app.py +++ b/api/app.py @@ -196,6 +196,32 @@ def server_stats(): 'timestamp': time.time() }) +# ─── Top Processes ─────────────────────────────────── +@app.route('/api/processes') +def top_processes(): + try: + raw = shell("ps aux --sort=-%cpu | awk 'NR>1 && NR<=8{print $2,$3,$4,$11}' | head -7") + procs = [] + for line in raw.strip().split('\n'): + parts = line.split(None, 3) + if len(parts) >= 4: + procs.append({ + 'pid': parts[0], + 'cpu': float(parts[1]), + 'mem': float(parts[2]), + 'name': parts[3].split('/')[-1][:20] + }) + elif len(parts) == 3: + procs.append({ + 'pid': parts[0], + 'cpu': float(parts[1]), + 'mem': float(parts[2]), + 'name': 'unknown' + }) + return jsonify(procs) + except Exception as e: + return jsonify([]), 200 + # ─── Services Status ───────────────────────────────── @app.route('/api/services') def services(): diff --git a/css/style.css b/css/style.css index a56bf01..429d7c8 100644 --- a/css/style.css +++ b/css/style.css @@ -2078,3 +2078,148 @@ a:hover { color: #fff; text-shadow: none; } font-size: 0.68rem; } } + +/* ============================ + 3D GLOBE PANEL + ============================ */ +.globe-panel { + min-height: 300px; +} + +.globe-container { + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 0.5rem; + overflow: hidden; + min-height: 280px; +} + +#globeViz { + display: flex; + align-items: center; + justify-content: center; +} + +#globeViz canvas { + cursor: grab; +} + +#globeViz canvas:active { + cursor: grabbing; +} + +.globe-data-overlay { + position: absolute; + bottom: 0.5rem; + left: 0.75rem; + right: 0.75rem; + display: flex; + justify-content: space-around; + gap: 0.5rem; + pointer-events: none; +} + +.globe-coord-block { + text-align: center; +} + +.globe-coord-block .coord-label { + display: block; + font-family: var(--font-mono); + font-size: 0.45rem; + letter-spacing: 2px; + color: var(--text-secondary); +} + +.globe-coord-block .coord-value { + display: block; + font-family: var(--font-mono); + font-size: 0.65rem; + color: var(--status-green); + text-shadow: 0 0 6px var(--status-green-glow); +} + +/* ============================ + TOP PROCESSES TABLE + ============================ */ +.proc-display { + padding: 0.5rem !important; +} + +.proc-table { + width: 100%; + border-collapse: collapse; + font-family: var(--font-mono); + font-size: 0.6rem; +} + +.proc-table th { + text-align: left; + padding: 0.3rem 0.4rem; + font-size: 0.5rem; + letter-spacing: 2px; + color: var(--text-secondary); + border-bottom: 1px solid var(--border); + font-weight: 400; +} + +.proc-table td { + padding: 0.25rem 0.4rem; + color: #999; + border-bottom: 1px solid rgba(255, 255, 255, 0.02); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100px; +} + +.proc-table tr:hover td { + color: var(--status-green); + background: rgba(0, 204, 51, 0.02); +} + +.proc-table .proc-cpu-high { + color: var(--mil-red); + text-shadow: 0 0 4px rgba(139, 0, 0, 0.4); +} + +.proc-table .proc-cpu-med { + color: var(--amber); +} + +.proc-table .proc-cpu-low { + color: var(--status-green); +} + +.proc-loading { + text-align: center; + color: #555; + letter-spacing: 2px; + font-size: 0.5rem; + padding: 1rem !important; + animation: hintPulse 2s ease-in-out infinite; +} + +/* Globe responsive */ +@media (max-width: 1024px) { + .globe-panel { + min-height: 240px; + } + .globe-container { + min-height: 220px; + } +} + +@media (max-width: 768px) { + .globe-panel { + min-height: 200px; + } + .globe-container { + min-height: 180px; + } + .proc-table { + font-size: 0.55rem; + } +} diff --git a/index.html b/index.html index e4aa5d6..a58ff4b 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,7 @@ + @@ -74,58 +75,26 @@
-
+
- OPERATOR LOCATION - + GLOBAL NETWORK MAP + ● TRACKING
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - MANCHESTER - 53.48°N 2.24°W - - - -
-
+
+
+
+
+ NODE + MANCHESTER +
+
LAT +53.48°
-
+
LONG -2.24°
-
- ALT - 78m (ASL) -
@@ -148,6 +117,29 @@
+ + +
+
+ TOP PROCESSES + ● LIVE +
+
+ + + + + + + + + + + + +
PIDNAMECPUMEM
SCANNING...
+
+
@@ -574,5 +566,7 @@ + + diff --git a/js/globe.js b/js/globe.js new file mode 100644 index 0000000..4cd812f --- /dev/null +++ b/js/globe.js @@ -0,0 +1,253 @@ +/* =================================================== + JAESWIFT.XYZ — 3D Globe (Globe.gl) + eDEX-UI inspired rotating globe with server location + =================================================== */ + +(function () { + 'use strict'; + + const container = document.getElementById('globeViz'); + if (!container || typeof Globe === 'undefined') return; + + // Manchester coordinates + const SERVER_LAT = 53.48; + const SERVER_LNG = -2.24; + + // Wait for container to be visible and sized + function initGlobe() { + const rect = container.parentElement.getBoundingClientRect(); + const size = Math.min(rect.width, 340); + + if (size < 50) { + setTimeout(initGlobe, 200); + return; + } + + const globe = Globe() + .globeImageUrl('') + .backgroundColor('rgba(0,0,0,0)') + .width(size) + .height(size) + .showAtmosphere(true) + .atmosphereColor('#00cc33') + .atmosphereAltitude(0.15) + // Hex polygons for land masses + .hexPolygonsData([]) + // Points - server location + .pointsData([{ + lat: SERVER_LAT, + lng: SERVER_LNG, + size: 0.6, + color: '#00cc33', + label: 'MANCHESTER' + }]) + .pointAltitude('size') + .pointColor('color') + .pointRadius(0.4) + .pointsMerge(false) + // Rings - pulse effect + .ringsData([{ + lat: SERVER_LAT, + lng: SERVER_LNG, + maxR: 3, + propagationSpeed: 1.5, + repeatPeriod: 1200 + }]) + .ringColor(function () { return '#00cc3380'; }) + .ringMaxRadius('maxR') + .ringPropagationSpeed('propagationSpeed') + .ringRepeatPeriod('repeatPeriod') + // Labels + .labelsData([{ + lat: SERVER_LAT, + lng: SERVER_LNG, + text: 'MCR // 53.48°N', + color: '#00cc33', + size: 0.7 + }]) + .labelColor('color') + .labelSize('size') + .labelDotRadius(0.3) + .labelAltitude(0.01) + .labelText('text') + .labelResolution(2) + // Custom globe material + .onGlobeReady(function () { + // Style the globe mesh + var globeMesh = globe.scene().children.find(function (c) { + return c.type === 'Mesh' && c.geometry && c.geometry.type === 'SphereGeometry'; + }); + if (globeMesh && globeMesh.material) { + globeMesh.material.color.setHex(0x050505); + globeMesh.material.emissive.setHex(0x001a00); + globeMesh.material.emissiveIntensity = 0.15; + } + }) + (container); + + // Auto rotate + globe.controls().autoRotate = true; + globe.controls().autoRotateSpeed = 0.4; + globe.controls().enableZoom = false; + globe.controls().enablePan = false; + globe.controls().minPolarAngle = Math.PI * 0.35; + globe.controls().maxPolarAngle = Math.PI * 0.65; + + // Point camera at Manchester + globe.pointOfView({ lat: SERVER_LAT, lng: SERVER_LNG, altitude: 2.2 }, 1500); + + // Fetch country polygons for land outlines + fetch('https://unpkg.com/world-atlas@2/countries-110m.json') + .then(function (r) { return r.json(); }) + .then(function (worldData) { + var countries = topojsonFeature(worldData, worldData.objects.countries).features; + globe + .hexPolygonsData(countries) + .hexPolygonResolution(3) + .hexPolygonMargin(0.4) + .hexPolygonColor(function () { + return 'rgba(0, 204, 51, 0.12)'; + }) + .hexPolygonAltitude(0.005); + }) + .catch(function () { + // Fallback: no country data, globe still works + }); + + // Simulated visitor arcs (random locations connecting to Manchester) + function generateRandomArcs() { + var cities = [ + { lat: 40.71, lng: -74.01 }, // New York + { lat: 35.68, lng: 139.69 }, // Tokyo + { lat: 48.86, lng: 2.35 }, // Paris + { lat: -33.87, lng: 151.21 }, // Sydney + { lat: 55.76, lng: 37.62 }, // Moscow + { lat: 1.35, lng: 103.82 }, // Singapore + { lat: 37.57, lng: 126.98 }, // Seoul + { lat: 19.43, lng: -99.13 }, // Mexico City + { lat: 52.52, lng: 13.41 }, // Berlin + { lat: -23.55, lng: -46.63 }, // São Paulo + { lat: 28.61, lng: 77.21 }, // Delhi + { lat: 34.05, lng: -118.24 }, // LA + ]; + + var arcs = []; + var count = 2 + Math.floor(Math.random() * 3); + for (var i = 0; i < count; i++) { + var city = cities[Math.floor(Math.random() * cities.length)]; + arcs.push({ + startLat: city.lat, + startLng: city.lng, + endLat: SERVER_LAT, + endLng: SERVER_LNG, + color: ['rgba(0, 204, 51, 0.6)', 'rgba(0, 204, 51, 0.05)'] + }); + } + return arcs; + } + + globe + .arcsData(generateRandomArcs()) + .arcColor('color') + .arcDashLength(0.5) + .arcDashGap(0.2) + .arcDashAnimateTime(2000) + .arcStroke(0.4) + .arcAltitudeAutoScale(0.3); + + // Refresh arcs periodically + setInterval(function () { + globe.arcsData(generateRandomArcs()); + }, 5000); + + // Handle resize + var resizeTimer; + window.addEventListener('resize', function () { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(function () { + var newRect = container.parentElement.getBoundingClientRect(); + var newSize = Math.min(newRect.width, 340); + globe.width(newSize).height(newSize); + }, 250); + }); + } + + // Topojson helper + function topojsonFeature(topology, obj) { + if (!obj || !obj.geometries) return { type: 'FeatureCollection', features: [] }; + return { + type: 'FeatureCollection', + features: obj.geometries.map(function (geom) { + return { + type: 'Feature', + geometry: topojsonGeometry(topology, geom), + properties: geom.properties || {}, + id: geom.id + }; + }) + }; + } + + function topojsonGeometry(topology, obj) { + var arcs = topology.arcs; + var transform = topology.transform; + + function decodeArc(arcIdx) { + var arc = arcs[arcIdx < 0 ? ~arcIdx : arcIdx]; + var coords = []; + var x = 0, y = 0; + for (var i = 0; i < arc.length; i++) { + x += arc[i][0]; + y += arc[i][1]; + var lon = x, lat = y; + if (transform) { + lon = lon * transform.scale[0] + transform.translate[0]; + lat = lat * transform.scale[1] + transform.translate[1]; + } + coords.push([lon, lat]); + } + if (arcIdx < 0) coords.reverse(); + return coords; + } + + function decodeRing(arcIndices) { + var coords = []; + for (var i = 0; i < arcIndices.length; i++) { + var arcCoords = decodeArc(arcIndices[i]); + if (i > 0) arcCoords.shift(); + coords = coords.concat(arcCoords); + } + return coords; + } + + var type = obj.type; + if (type === 'Polygon') { + return { type: 'Polygon', coordinates: obj.arcs.map(decodeRing) }; + } else if (type === 'MultiPolygon') { + return { + type: 'MultiPolygon', + coordinates: obj.arcs.map(function (polygon) { + return polygon.map(decodeRing); + }) + }; + } else if (type === 'GeometryCollection') { + return { + type: 'GeometryCollection', + geometries: (obj.geometries || []).map(function (g) { + return topojsonGeometry(topology, g); + }) + }; + } + return { type: type, coordinates: [] }; + } + + // Init when DOM ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function () { + setTimeout(initGlobe, 300); + }); + } else { + setTimeout(initGlobe, 300); + } + +})(); diff --git a/js/processes.js b/js/processes.js new file mode 100644 index 0000000..eb278b8 --- /dev/null +++ b/js/processes.js @@ -0,0 +1,48 @@ +/* =================================================== + JAESWIFT.XYZ — Top Processes Panel + Live-updating process table from /api/processes + =================================================== */ + +(function () { + 'use strict'; + + var tbody = document.getElementById('procTableBody'); + if (!tbody) return; + + function cpuClass(val) { + if (val >= 50) return 'proc-cpu-high'; + if (val >= 15) return 'proc-cpu-med'; + return 'proc-cpu-low'; + } + + function fetchProcesses() { + fetch('/api/processes') + .then(function (r) { return r.json(); }) + .then(function (procs) { + if (!procs || !procs.length) return; + + var html = ''; + for (var i = 0; i < procs.length; i++) { + var p = procs[i]; + var cls = cpuClass(p.cpu); + html += '' + + '' + p.pid + '' + + '' + p.name + '' + + '' + p.cpu.toFixed(1) + '%' + + '' + p.mem.toFixed(1) + '%' + + ''; + } + tbody.innerHTML = html; + }) + .catch(function () { + // Silently fail, keep old data + }); + } + + // Initial fetch + setTimeout(fetchProcesses, 1000); + + // Poll every 5 seconds + setInterval(fetchProcesses, 5000); + +})();