feat: eDEX-UI enhancements - 3D rotating globe, top processes panel - Globe.gl 3D globe replaces UK SVG map, Manchester server location - Animated arcs from world cities, hex polygon land masses - Green atmosphere glow, auto-rotate, pulse rings - Top Processes panel with live polling every 5s - /api/processes endpoint returns top 7 by CPU - Color-coded CPU usage (green/amber/red)
This commit is contained in:
parent
ffd1b7e689
commit
4a271496c3
5 changed files with 510 additions and 44 deletions
26
api/app.py
26
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():
|
||||
|
|
|
|||
145
css/style.css
145
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
82
index.html
82
index.html
|
|
@ -8,6 +8,7 @@
|
|||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Orbitron:wght@400;500;600;700;800;900&family=Share+Tech+Mono&display=swap" rel="stylesheet">
|
||||
<script src="https://unpkg.com/globe.gl@2.45.1/dist/globe.gl.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Scanline overlay -->
|
||||
|
|
@ -74,58 +75,26 @@
|
|||
|
||||
<!-- Left Column: Map + Identity -->
|
||||
<div class="hud-col-left">
|
||||
<div class="panel map-panel">
|
||||
<div class="panel map-panel globe-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">OPERATOR LOCATION</span>
|
||||
<span class="panel-icon">↗</span>
|
||||
<span class="panel-title">GLOBAL NETWORK MAP</span>
|
||||
<span class="panel-status-dot status-green">● TRACKING</span>
|
||||
</div>
|
||||
<div class="map-content">
|
||||
<svg viewBox="0 0 250 400" class="uk-map-svg">
|
||||
<defs>
|
||||
<pattern id="mapGrid" width="20" height="20" patternUnits="userSpaceOnUse">
|
||||
<path d="M 20 0 L 0 0 0 20" fill="none" stroke="rgba(255,255,255,0.02)" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
<filter id="glow"><feGaussianBlur stdDeviation="3" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
|
||||
<radialGradient id="dotGlow" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0%" stop-color="#00cc33" stop-opacity="0.5"/>
|
||||
<stop offset="100%" stop-color="#00cc33" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
<rect width="250" height="400" fill="url(#mapGrid)"/>
|
||||
<!-- UK mainland outline -->
|
||||
<polygon points="80,12 95,8 120,5 140,8 155,18 152,35 148,55 142,72 138,88 135,100 142,108 150,118 155,135 158,155 162,175 168,195 172,215 170,235 168,255 172,272 168,280 155,288 142,292 130,296 115,302 100,308 85,315 72,318 65,310 75,295 82,280 88,268 85,255 72,248 62,238 55,222 52,205 55,192 60,182 68,175 78,168 82,155 80,140 75,125 70,112 62,100 55,82 52,65 58,45 68,30" fill="rgba(0,204,51,0.02)" stroke="#00cc33" stroke-width="1.2" stroke-opacity="0.4" stroke-linejoin="round"/>
|
||||
<!-- Northern Ireland (simplified) -->
|
||||
<polygon points="42,105 55,98 62,102 58,115 48,118 38,112" fill="rgba(0,204,51,0.015)" stroke="#00cc33" stroke-width="0.8" stroke-opacity="0.3" stroke-linejoin="round"/>
|
||||
<!-- Crosshair lines at Manchester -->
|
||||
<line x1="0" y1="172" x2="250" y2="172" stroke="#00cc33" stroke-width="0.3" stroke-opacity="0.25" stroke-dasharray="4,4"/>
|
||||
<line x1="90" y1="0" x2="90" y2="400" stroke="#00cc33" stroke-width="0.3" stroke-opacity="0.25" stroke-dasharray="4,4"/>
|
||||
<!-- Manchester glow -->
|
||||
<circle cx="90" cy="172" r="25" fill="url(#dotGlow)"/>
|
||||
<!-- Manchester dot -->
|
||||
<circle cx="90" cy="172" r="3" fill="#00cc33" filter="url(#glow)"/>
|
||||
<!-- Pulse rings -->
|
||||
<circle cx="90" cy="172" r="8" fill="none" stroke="#00cc33" stroke-width="0.8" class="pulse-ring pulse-ring-1"/>
|
||||
<circle cx="90" cy="172" r="16" fill="none" stroke="#00cc33" stroke-width="0.5" class="pulse-ring pulse-ring-2"/>
|
||||
<circle cx="90" cy="172" r="24" fill="none" stroke="#00cc33" stroke-width="0.3" class="pulse-ring pulse-ring-3"/>
|
||||
<!-- Manchester label -->
|
||||
<text x="108" y="168" fill="#00cc33" font-size="8" font-family="'JetBrains Mono',monospace" letter-spacing="1">MANCHESTER</text>
|
||||
<text x="108" y="180" fill="rgba(0,204,51,0.3)" font-size="6" font-family="'JetBrains Mono',monospace">53.48°N 2.24°W</text>
|
||||
<!-- Scan sweep -->
|
||||
<line x1="0" y1="0" x2="250" y2="0" stroke="#00cc33" stroke-width="1" stroke-opacity="0.4" class="map-scan-line"/>
|
||||
</svg>
|
||||
<div class="map-data-overlay">
|
||||
<div class="map-coord-block">
|
||||
<div class="globe-container" id="globeContainer">
|
||||
<div id="globeViz"></div>
|
||||
<div class="globe-data-overlay">
|
||||
<div class="globe-coord-block">
|
||||
<span class="coord-label">NODE</span>
|
||||
<span class="coord-value">MANCHESTER</span>
|
||||
</div>
|
||||
<div class="globe-coord-block">
|
||||
<span class="coord-label">LAT</span>
|
||||
<span class="coord-value">+53.48°</span>
|
||||
</div>
|
||||
<div class="map-coord-block">
|
||||
<div class="globe-coord-block">
|
||||
<span class="coord-label">LONG</span>
|
||||
<span class="coord-value">-2.24°</span>
|
||||
</div>
|
||||
<div class="map-coord-block">
|
||||
<span class="coord-label">ALT</span>
|
||||
<span class="coord-value">78m (ASL)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -148,6 +117,29 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top Processes -->
|
||||
<div class="panel hud-data-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">TOP PROCESSES</span>
|
||||
<span class="panel-status-dot status-green">● LIVE</span>
|
||||
</div>
|
||||
<div class="panel-content proc-display">
|
||||
<table class="proc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>PID</th>
|
||||
<th>NAME</th>
|
||||
<th>CPU</th>
|
||||
<th>MEM</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="procTableBody">
|
||||
<tr><td colspan="4" class="proc-loading">SCANNING...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Middle Column: AI Chat Terminal -->
|
||||
|
|
@ -574,5 +566,7 @@
|
|||
<script src="/js/main.js"></script>
|
||||
<script src="/js/nav.js"></script>
|
||||
<script src="/js/chat.js"></script>
|
||||
<script src="/js/globe.js"></script>
|
||||
<script src="/js/processes.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
253
js/globe.js
Normal file
253
js/globe.js
Normal file
|
|
@ -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);
|
||||
}
|
||||
|
||||
})();
|
||||
48
js/processes.js
Normal file
48
js/processes.js
Normal file
|
|
@ -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 += '<tr>'
|
||||
+ '<td>' + p.pid + '</td>'
|
||||
+ '<td>' + p.name + '</td>'
|
||||
+ '<td class="' + cls + '">' + p.cpu.toFixed(1) + '%</td>'
|
||||
+ '<td>' + p.mem.toFixed(1) + '%</td>'
|
||||
+ '</tr>';
|
||||
}
|
||||
tbody.innerHTML = html;
|
||||
})
|
||||
.catch(function () {
|
||||
// Silently fail, keep old data
|
||||
});
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
setTimeout(fetchProcesses, 1000);
|
||||
|
||||
// Poll every 5 seconds
|
||||
setInterval(fetchProcesses, 5000);
|
||||
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue