Download static/app.js from SyedaArisha/predictive-maintenance-rag-system: direct link, hf CLI and curl.
- Browser
- Download file 19.4 kB
-
https://huggingface.co/SyedaArisha/predictive-maintenance-rag-system/resolve/main/static/app.js
- Command line
-
hf download hf://SyedaArisha/predictive-maintenance-rag-system/static/app.js
-
curl -L -o app.js https://huggingface.co/SyedaArisha/predictive-maintenance-rag-system/resolve/main/static/app.js
19.4 kB
| /** | |
| * PredictiveAI Dashboard β Application Logic | |
| * All API wiring preserved. Visual rendering updated for editorial theme. | |
| */ | |
| const API = '/api'; | |
| const state = { currentView: 'overview', reports: [], fleet: [], schedule: [] }; | |
| const els = { | |
| loader: document.getElementById('app-loader'), | |
| navItems: document.querySelectorAll('.nav-link'), | |
| views: document.querySelectorAll('.view'), | |
| toasts: document.getElementById('toast-container'), | |
| pulse: document.getElementById('system-pulse'), | |
| }; | |
| // βββ Init βββββββββββββββββββββββββββββββββββββββββββββββ | |
| document.addEventListener('DOMContentLoaded', () => { | |
| els.navItems.forEach(n => n.addEventListener('click', e => { | |
| e.preventDefault(); | |
| switchView(n.dataset.view); | |
| })); | |
| try { initOverview(); initFleet(); initPredict(); initSchedule(); } | |
| catch (e) { console.error('Init error:', e); } | |
| setTimeout(() => { if (els.loader) els.loader.classList.add('hidden'); }, 500); | |
| }); | |
| function switchView(id) { | |
| if (state.currentView === id) return; | |
| els.navItems.forEach(n => n.classList.toggle('active', n.dataset.view === id)); | |
| els.views.forEach(v => v.classList.toggle('active', v.id === `view-${id}`)); | |
| state.currentView = id; | |
| if (id === 'overview') refreshOverview(); | |
| if (id === 'fleet') refreshFleet(); | |
| if (id === 'schedule') loadSchedule(); | |
| if (id === 'reports') loadReportsList(); | |
| } | |
| // βββ Utilities ββββββββββββββββββββββββββββββββββββββββββ | |
| function toast(msg, type = 'info') { | |
| if (!els.toasts) return; | |
| const t = document.createElement('div'); | |
| t.className = `toast toast--${type}`; | |
| t.textContent = msg; | |
| els.toasts.appendChild(t); | |
| setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 300); }, 3500); | |
| } | |
| function setPulse(online) { | |
| if (!els.pulse) return; | |
| const dot = els.pulse.querySelector('.pulse-dot'); | |
| const lbl = els.pulse.querySelector('.pulse-label'); | |
| if (dot) dot.style.background = online ? 'var(--ok)' : 'var(--danger)'; | |
| if (lbl) lbl.textContent = online ? 'System online' : 'System offline'; | |
| els.pulse.style.color = online ? 'var(--ok)' : 'var(--danger)'; | |
| } | |
| // Chart.js global overrides for light editorial theme | |
| if (typeof Chart !== 'undefined') { | |
| Chart.defaults.color = '#78716c'; | |
| Chart.defaults.borderColor = '#e7e5e4'; | |
| Chart.defaults.font.family = "'DM Sans', sans-serif"; | |
| } | |
| // βββ 1. Overview ββββββββββββββββββββββββββββββββββββββββ | |
| let chartHealth = null, chartRulDist = null; | |
| function initOverview() { | |
| const btn = document.getElementById('btn-refresh-overview'); | |
| if (btn) btn.addEventListener('click', refreshOverview); | |
| const hEl = document.getElementById('chart-health-dist'); | |
| if (hEl && typeof Chart !== 'undefined') { | |
| chartHealth = new Chart(hEl.getContext('2d'), { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Healthy', 'At risk', 'Critical'], | |
| datasets: [{ data: [0,0,0], backgroundColor: ['#059669','#d97706','#dc2626'], borderWidth: 0, hoverOffset: 2 }] | |
| }, | |
| options: { | |
| responsive: true, maintainAspectRatio: false, cutout: '72%', | |
| plugins: { legend: { position: 'right', labels: { color: '#44403c', font: { size: 11 }, padding: 14 } } } | |
| } | |
| }); | |
| } | |
| const rEl = document.getElementById('chart-rul-dist'); | |
| if (rEl && typeof Chart !== 'undefined') { | |
| chartRulDist = new Chart(rEl.getContext('2d'), { | |
| type: 'bar', | |
| data: { | |
| labels: ['0-24 h', '24-72 h', '72-150 h', '150+ h'], | |
| datasets: [{ label: 'Machines', data: [0,0,0,0], backgroundColor: 'rgba(5, 150, 105, 0.45)', borderRadius: 2 }] | |
| }, | |
| options: { | |
| responsive: true, maintainAspectRatio: false, | |
| scales: { | |
| y: { beginAtZero: true, grid: { color: '#e7e5e4' }, ticks: { color: '#78716c' } }, | |
| x: { grid: { display: false }, ticks: { color: '#78716c' } } | |
| }, | |
| plugins: { legend: { display: false } } | |
| } | |
| }); | |
| } | |
| refreshOverview(); | |
| } | |
| async function refreshOverview() { | |
| const btn = document.getElementById('btn-refresh-overview'); | |
| try { | |
| if (btn) btn.textContent = 'Loading...'; | |
| const [fleetRes, statusRes] = await Promise.all([ | |
| fetch(`${API}/fleet/rul`), | |
| fetch(`${API}/status`) | |
| ]); | |
| if (!fleetRes.ok || !statusRes.ok) throw new Error('API error'); | |
| const fleet = await fleetRes.json(); | |
| const status = await statusRes.json(); | |
| if (chartHealth) { | |
| chartHealth.data.datasets[0].data = [fleet.summary.healthy, fleet.summary.at_risk, fleet.summary.critical]; | |
| chartHealth.update(); | |
| } | |
| const b = [0,0,0,0]; | |
| fleet.fleet.forEach(m => { if (m.rul_hours<=24) b[0]++; else if (m.rul_hours<=72) b[1]++; else if (m.rul_hours<=150) b[2]++; else b[3]++; }); | |
| if (chartRulDist) { chartRulDist.data.datasets[0].data = b; chartRulDist.update(); } | |
| setText('kpi-total', fleet.summary.total_machines); | |
| setText('kpi-healthy', status.healthy_jobs); | |
| setText('kpi-atrisk', fleet.summary.at_risk); | |
| setText('kpi-critical', fleet.summary.critical); | |
| setPulse(true); | |
| if (btn) btn.textContent = 'Refresh'; | |
| } catch (e) { | |
| console.error(e); | |
| toast('Failed to connect to backend', 'error'); | |
| setPulse(false); | |
| if (btn) btn.textContent = 'Refresh'; | |
| } | |
| } | |
| function setText(id, v) { const el = document.getElementById(id); if (el) el.textContent = v; } | |
| // βββ 2. Fleet βββββββββββββββββββββββββββββββββββββββββββ | |
| function initFleet() { | |
| const btn = document.getElementById('btn-refresh-fleet'); | |
| const f1 = document.getElementById('fleet-filter'); | |
| const f2 = document.getElementById('fleet-model-filter'); | |
| if (btn) btn.addEventListener('click', refreshFleet); | |
| if (f1) f1.addEventListener('change', renderFleet); | |
| if (f2) f2.addEventListener('change', renderFleet); | |
| } | |
| async function refreshFleet() { | |
| try { | |
| const res = await fetch(`${API}/fleet/rul`); | |
| if (!res.ok) throw new Error(); | |
| const data = await res.json(); | |
| state.fleet = data.fleet; | |
| const t = data.summary.total_machines; | |
| setText('fleet-total', t); | |
| setWidth('bar-critical', (data.summary.critical / t) * 100); | |
| setWidth('bar-warning', (data.summary.at_risk / t) * 100); | |
| setWidth('bar-healthy', (data.summary.healthy / t) * 100); | |
| renderFleet(); | |
| toast('Fleet data updated', 'success'); | |
| } catch (e) { | |
| console.error(e); | |
| toast('Failed to load fleet data', 'error'); | |
| const g = document.getElementById('machine-grid'); | |
| if (g) g.innerHTML = '<div class="empty-msg">Unable to connect to backend.</div>'; | |
| } | |
| } | |
| function setWidth(id, pct) { const el = document.getElementById(id); if (el) el.style.width = `${pct}%`; } | |
| function renderFleet() { | |
| const grid = document.getElementById('machine-grid'); | |
| if (!grid) return; | |
| const sf = valOf('fleet-filter', 'all'); | |
| const mf = valOf('fleet-model-filter', 'all'); | |
| let list = state.fleet; | |
| if (sf !== 'all') list = list.filter(m => m.status === sf); | |
| if (mf !== 'all') list = list.filter(m => m.model === mf); | |
| if (!list.length) { grid.innerHTML = '<div class="empty-msg">No machines match filters.</div>'; return; } | |
| grid.innerHTML = list.map(m => { | |
| const s = m.status === 'Critical' ? 'crit' : m.status === 'At Risk' ? 'warn' : 'ok'; | |
| return `<div class="machine-card${s==='crit' ? ' machine-card--critical':''}"> | |
| <div class="mc-head"> | |
| <div><span class="mc-id">M-${m.machine_id}</span> <span class="mc-model">${m.model}</span></div> | |
| <span class="mc-status mc-status--${s}"><span class="mc-dot mc-dot--${s}"></span>${m.status}</span> | |
| </div> | |
| <div class="mc-prob">Failure risk <strong>${(m.failure_probability*100).toFixed(1)}%</strong></div> | |
| <div class="mc-rul"><span class="mc-rul-val">${m.rul_hours.toFixed(1)}</span><span class="mc-rul-unit">h</span></div> | |
| <div class="mc-sensors"><div>V ${m.sensors.volt}</div><div>R ${m.sensors.rotate}</div><div>P ${m.sensors.pressure}</div><div>Vib ${m.sensors.vibration}</div></div> | |
| </div>`; | |
| }).join(''); | |
| } | |
| function valOf(id, fallback) { const el = document.getElementById(id); return el ? el.value : fallback; } | |
| // βββ 3. Diagnostics βββββββββββββββββββββββββββββββββββββ | |
| let gaugeChart = null, rulTrendChart = null; | |
| function initPredict() { | |
| const form = document.getElementById('predict-form'); | |
| if (form) form.addEventListener('submit', runPredict); | |
| document.querySelectorAll('.btn-preset').forEach(b => b.addEventListener('click', e => { | |
| e.preventDefault(); | |
| const p = b.dataset.preset; | |
| const vals = { | |
| healthy: { volt: 170.5, rotate: 440.2, pressure: 100.1, vib: 40.2 }, | |
| atrisk: { volt: 185.0, rotate: 340.0, pressure: 100.0, vib: 45.0 }, | |
| critical: { volt: 210.5, rotate: 520.1, pressure: 115.5, vib: 55.2 }, | |
| }[p]; | |
| if (vals) { setVal('inp-volt', vals.volt); setVal('inp-rotate', vals.rotate); setVal('inp-pressure', vals.pressure); setVal('inp-vib', vals.vib); } | |
| })); | |
| const gEl = document.getElementById('chart-gauge'); | |
| if (gEl && typeof Chart !== 'undefined') { | |
| gaugeChart = new Chart(gEl.getContext('2d'), { | |
| type: 'doughnut', | |
| data: { datasets: [{ data: [0,100], backgroundColor: ['#059669','#f5f5f4'], borderWidth: 0, circumference: 180, rotation: 270, cutout: '82%' }] }, | |
| options: { responsive: true, maintainAspectRatio: false, plugins: { tooltip: { enabled: false } } } | |
| }); | |
| } | |
| const tEl = document.getElementById('chart-rul-trend'); | |
| if (tEl && typeof Chart !== 'undefined') { | |
| rulTrendChart = new Chart(tEl.getContext('2d'), { | |
| type: 'line', | |
| data: { labels: ['T-4','T-3','T-2','T-1','Now'], datasets: [{ data: [100,100,100,100,100], borderColor: '#059669', tension: .3, borderWidth: 1.5, pointRadius: 0 }] }, | |
| options: { responsive: true, maintainAspectRatio: false, scales: { x: { display: false }, y: { display: false, min: 0, max: 100 } }, plugins: { legend: { display: false } } } | |
| }); | |
| } | |
| } | |
| function setVal(id, v) { const el = document.getElementById(id); if (el) el.value = v; } | |
| async function runPredict(e) { | |
| e.preventDefault(); | |
| const body = { | |
| machineID: parseInt(valOf('inp-machine', '3')), | |
| age: parseFloat(valOf('inp-age', '5')), | |
| volt: parseFloat(valOf('inp-volt', '170')), | |
| rotate: parseFloat(valOf('inp-rotate', '440')), | |
| pressure: parseFloat(valOf('inp-pressure', '100')), | |
| vibration: parseFloat(valOf('inp-vib', '40')) | |
| }; | |
| hide('predict-empty'); hide('results-top'); hide('ai-insight-panel'); show('predict-loading'); | |
| try { | |
| const res = await fetch(`${API}/predict`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); | |
| if (!res.ok) throw new Error(); | |
| renderDiagnostic(await res.json()); | |
| } catch (err) { | |
| console.error(err); | |
| toast('Inference engine failed', 'error'); | |
| hide('predict-loading'); show('predict-empty'); | |
| } | |
| } | |
| function renderDiagnostic(d) { | |
| hide('predict-loading'); show('results-top', 'grid'); show('ai-insight-panel'); | |
| const pct = (d.failure_probability || 0) * 100; | |
| const rul = d.rul || 0; | |
| let clr = '#059669', cls = 'status-indicator--ok'; | |
| if (d.status === 'Critical') { clr = '#dc2626'; cls = 'status-indicator--crit'; } | |
| else if (d.status === 'At Risk') { clr = '#d97706'; cls = 'status-indicator--warn'; } | |
| setText('gauge-value', `${pct.toFixed(1)}%`); | |
| if (gaugeChart) { gaugeChart.data.datasets[0].data = [pct, 100-pct]; gaugeChart.data.datasets[0].backgroundColor[0] = clr; gaugeChart.update(); } | |
| const badge = document.getElementById('status-badge'); | |
| if (badge) { badge.textContent = d.status; badge.className = `status-indicator ${cls}`; } | |
| setText('rul-value', rul.toFixed(1)); | |
| const bar = document.getElementById('rul-bar'); | |
| if (bar) { bar.style.width = `${Math.max(0, Math.min(100, rul))}%`; bar.style.background = clr; } | |
| if (rulTrendChart) { | |
| const h = [rul+10, rul+5, rul+2, rul+1, rul].map(v => Math.min(100, Math.max(0, v))); | |
| rulTrendChart.data.datasets[0].data = h; | |
| rulTrendChart.data.datasets[0].borderColor = clr; | |
| rulTrendChart.update(); | |
| } | |
| const rep = document.getElementById('ai-report-content'); | |
| if (rep) { | |
| rep.innerHTML = (typeof marked !== 'undefined' && marked.parse) ? marked.parse(d.report || '*No report generated.*') : (d.report || 'No report.'); | |
| } | |
| toast('Diagnostic complete', 'success'); | |
| } | |
| function show(id, display) { const el = document.getElementById(id); if (el) el.style.display = display || 'block'; } | |
| function hide(id) { const el = document.getElementById(id); if (el) el.style.display = 'none'; } | |
| // βββ 4. Reports βββββββββββββββββββββββββββββββββββββββββ | |
| async function loadReportsList() { | |
| const list = document.getElementById('reports-list'); | |
| if (!list) return; | |
| list.innerHTML = '<div class="empty-msg"><div class="loader-bar" style="width:40px;margin:0 auto"></div></div>'; | |
| try { | |
| const res = await fetch(`${API}/reports`); | |
| if (!res.ok) throw new Error(); | |
| const data = await res.json(); | |
| state.reports = data.reports || []; | |
| if (!state.reports.length) { list.innerHTML = '<div class="empty-msg">No saved reports.</div>'; return; } | |
| list.innerHTML = state.reports.map(r => | |
| `<div class="report-item" onclick="viewReport('${r.filename}', this)">Machine ${r.machine_id}</div>` | |
| ).join(''); | |
| } catch (e) { list.innerHTML = '<div class="empty-msg">Error loading reports.</div>'; } | |
| } | |
| async function viewReport(file, el) { | |
| document.querySelectorAll('.report-item').forEach(i => i.classList.remove('active')); | |
| if (el) el.classList.add('active'); | |
| const v = document.getElementById('report-viewer'); | |
| if (!v) return; | |
| v.innerHTML = '<div class="empty-msg"><div class="loader-bar" style="width:40px;margin:0 auto"></div></div>'; | |
| try { | |
| const res = await fetch(`${API}/report/${file}`); | |
| if (!res.ok) throw new Error(); | |
| const data = await res.json(); | |
| v.innerHTML = (typeof marked !== 'undefined' && marked.parse) ? marked.parse(data.content) : data.content; | |
| } catch (e) { v.innerHTML = '<div class="empty-msg">Error loading report.</div>'; } | |
| } | |
| // βββ 5. Scheduling ββββββββββββββββββββββββββββββββββββββ | |
| function initSchedule() { | |
| const f = document.getElementById('schedule-filter'); | |
| if (f) f.addEventListener('change', renderSchedule); | |
| const modal = document.getElementById('reschedule-modal'); | |
| bindClick('btn-run-reschedule', () => { if (modal) modal.style.display = 'flex'; }); | |
| bindClick('modal-close', () => { if (modal) modal.style.display = 'none'; }); | |
| bindClick('modal-cancel', () => { if (modal) modal.style.display = 'none'; }); | |
| bindClick('modal-confirm', execReschedule); | |
| } | |
| function bindClick(id, fn) { const el = document.getElementById(id); if (el) el.addEventListener('click', fn); } | |
| async function loadSchedule() { | |
| try { | |
| const res = await fetch(`${API}/schedule`); | |
| if (!res.ok) throw new Error(); | |
| const data = await res.json(); | |
| if (data.error) { toast(data.error, 'info'); return; } | |
| state.schedule = data.data || []; | |
| updateSchedSummary(state.schedule); | |
| renderSchedule(); | |
| } catch (e) { toast('Error loading schedule', 'error'); } | |
| } | |
| function updateSchedSummary(s) { | |
| show('reschedule-summary', 'flex'); | |
| let moved = 0, alerts = 0; | |
| s.forEach(j => { if (j.status === 'Rescheduled') moved++; if (j.status === 'Risk Alert') alerts++; }); | |
| setText('rs-total', s.length); | |
| setText('rs-moved', moved); | |
| setText('rs-alerts', alerts); | |
| setText('rs-ok', s.length - moved - alerts); | |
| } | |
| function renderSchedule() { | |
| const tbody = document.getElementById('schedule-tbody'); | |
| if (!tbody) return; | |
| const f = valOf('schedule-filter', 'all'); | |
| let list = state.schedule; | |
| if (f !== 'all') list = list.filter(j => j.status === f); | |
| if (!list.length) { tbody.innerHTML = '<tr><td colspan="6" class="tbl-empty">No jobs for this filter.</td></tr>'; return; } | |
| tbody.innerHTML = list.map(j => { | |
| const sc = j.status === 'Rescheduled' ? 'rescheduled' : j.status === 'Risk Alert' ? 'risk' : 'scheduled'; | |
| const pc = j.priority === 'High' ? 'high' : j.priority === 'Medium' ? 'medium' : 'low'; | |
| const dur = typeof j.duration_hours === 'number' ? j.duration_hours.toFixed(1) : j.duration_hours; | |
| return `<tr> | |
| <td class="tbl-mono">${j.job_id}</td> | |
| <td class="tbl-mono tbl-bold">M-${j.machine_id}</td> | |
| <td>${dur} h</td> | |
| <td class="tbl-prio tbl-prio--${pc}">${j.priority}</td> | |
| <td class="tbl-status tbl-status--${sc}">${j.status}</td> | |
| <td style="max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="${j.notes||''}">${j.notes||''}</td> | |
| </tr>`; | |
| }).join(''); | |
| } | |
| async function execReschedule() { | |
| const btn = document.getElementById('modal-confirm'); | |
| if (!btn) return; | |
| const prev = btn.textContent; | |
| btn.textContent = 'Running...'; btn.disabled = true; | |
| try { | |
| const ids = (valOf('rs-machines', '')).split(',').map(s => s.trim()).filter(Boolean); | |
| const preds = {}; | |
| ids.forEach(id => { preds[id] = { failure_probability: 0.85, rul: 10.0 }; }); | |
| const res = await fetch(`${API}/reschedule`, { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ predictions: preds, failure_threshold: parseFloat(valOf('rs-threshold','0.7')), min_rul_hours: parseFloat(valOf('rs-min-rul','48')) }) | |
| }); | |
| if (!res.ok) throw new Error(); | |
| const data = await res.json(); | |
| state.schedule = data.schedule || []; | |
| updateSchedSummary(state.schedule); | |
| renderSchedule(); | |
| const modal = document.getElementById('reschedule-modal'); | |
| if (modal) modal.style.display = 'none'; | |
| toast(`Rescheduled ${data.rescheduled_count || 0} jobs`, 'success'); | |
| } catch (e) { toast('Rescheduling failed', 'error'); } | |
| finally { btn.textContent = prev; btn.disabled = false; } | |
| } | |