Aryanshh commited on
Commit
ab38db6
·
1 Parent(s): 27b68d3

feat: Add professional Eco-HUD Dashboard

Browse files
dashboard/app.js ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const API_BASE = window.location.origin;
2
+
3
+ async function updateState() {
4
+ try {
5
+ const response = await fetch(`${API_BASE}/state`);
6
+ if (!response.ok) throw new Error('API Unreachable');
7
+
8
+ const data = await response.json();
9
+
10
+ // Update Status
11
+ const status = document.getElementById('connection-status');
12
+ status.textContent = 'ONLINE';
13
+ status.className = 'status-badge status-online';
14
+
15
+ // Update Multi-objective Metrics
16
+ document.getElementById('carbon-value').textContent = data.carbon.toFixed(0);
17
+ document.getElementById('cash-value').textContent = data.cash.toLocaleString();
18
+
19
+ // Update Shipments
20
+ const container = document.getElementById('shipments-container');
21
+ if (data.orders && data.orders.length > 0) {
22
+ container.innerHTML = data.orders.map(order => `
23
+ <div class="shipment-item">
24
+ <span>Order: ${order.product || 'Standard Set'}</span>
25
+ <strong>Quantity: ${order.quantity}</strong>
26
+ </div>
27
+ `).join('');
28
+ } else {
29
+ container.innerHTML = '<p class="placeholder">Supply chain optimized. No pending orders.</p>';
30
+ }
31
+
32
+ } catch (error) {
33
+ console.error('Update failed:', error);
34
+ const status = document.getElementById('connection-status');
35
+ status.textContent = 'RECONNECTING...';
36
+ status.className = 'status-badge';
37
+ }
38
+ }
39
+
40
+ // Poll every 2 seconds
41
+ setInterval(updateState, 2000);
42
+ updateState();
dashboard/index.html ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>NetZero-Nav Dashboard</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
9
+ </head>
10
+ <body>
11
+ <div class="container">
12
+ <header>
13
+ <div class="logo">
14
+ <span class="icon">🌍</span>
15
+ <h1>NETZERO-NAV</h1>
16
+ </div>
17
+ <div id="connection-status" class="status-badge">Connecting...</div>
18
+ </header>
19
+
20
+ <main>
21
+ <section class="metrics-grid">
22
+ <div class="card metric-card">
23
+ <h3>Carbon Footprint</h3>
24
+ <div class="gauge-container">
25
+ <div id="carbon-gauge" class="gauge">
26
+ <span id="carbon-value">0</span><small>kg</small>
27
+ </div>
28
+ </div>
29
+ <p class="limit">Limit: <span id="carbon-limit">1000</span>kg</p>
30
+ </div>
31
+
32
+ <div class="card metric-card">
33
+ <h3>Capital Balance</h3>
34
+ <div class="value-large">$<span id="cash-value">0</span></div>
35
+ <div class="trend" id="cash-trend">Fulfillment: <span id="orders-done">0</span></div>
36
+ </div>
37
+ </section>
38
+
39
+ <section class="details-section">
40
+ <div class="card full-width">
41
+ <h3>Active Logistics Stream</h3>
42
+ <div id="shipments-container" class="shipment-list">
43
+ <p class="placeholder">Awaiting shipment data...</p>
44
+ </div>
45
+ </div>
46
+ </section>
47
+
48
+ <section class="task-info">
49
+ <div class="card">
50
+ <h3>Environment Alerts</h3>
51
+ <div id="news-ticker" class="ticker-box">
52
+ No active disruptions detected.
53
+ </div>
54
+ </div>
55
+ </section>
56
+ </main>
57
+
58
+ <footer>
59
+ <p>&copy; 2026 NetZero-Nav | Eco-Resilient Supply Chain Intelligence</p>
60
+ </footer>
61
+ </div>
62
+ <script src="app.js"></script>
63
+ </body>
64
+ </html>
dashboard/style.css ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ --bg-cream: #FDFBF7;
3
+ --card-white: #FFFFFF;
4
+ --primary-green: #2C5F2D;
5
+ --sage-green: #97BC62;
6
+ --deep-forest: #004225;
7
+ --border-tan: #E9E1D4;
8
+ --text-main: #333333;
9
+ --text-muted: #666666;
10
+ --accent-gold: #D4AF37;
11
+ }
12
+
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ body {
20
+ background-color: var(--bg-cream);
21
+ color: var(--text-main);
22
+ font-family: 'Outfit', sans-serif;
23
+ line-height: 1.6;
24
+ }
25
+
26
+ .container {
27
+ max-width: 1200px;
28
+ margin: 0 auto;
29
+ padding: 2rem;
30
+ }
31
+
32
+ header {
33
+ display: flex;
34
+ justify-content: space-between;
35
+ align-items: center;
36
+ margin-bottom: 3rem;
37
+ border-bottom: 2px solid var(--border-tan);
38
+ padding-bottom: 1rem;
39
+ }
40
+
41
+ .logo {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 0.75rem;
45
+ }
46
+
47
+ .logo h1 {
48
+ font-family: 'Playfair Display', serif;
49
+ font-size: 1.8rem;
50
+ letter-spacing: 2px;
51
+ color: var(--deep-forest);
52
+ }
53
+
54
+ .status-badge {
55
+ background: var(--border-tan);
56
+ padding: 0.5rem 1rem;
57
+ border-radius: 20px;
58
+ font-size: 0.8rem;
59
+ font-weight: 600;
60
+ }
61
+
62
+ .status-online { background: #E1F5E1; color: #2C5F2D; }
63
+
64
+ .metrics-grid {
65
+ display: grid;
66
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
67
+ gap: 2rem;
68
+ margin-bottom: 2rem;
69
+ }
70
+
71
+ .card {
72
+ background: var(--card-white);
73
+ border: 1px solid var(--border-tan);
74
+ border-radius: 12px;
75
+ padding: 2rem;
76
+ box-shadow: 0 4px 15px rgba(0,0,0,0.02);
77
+ transition: transform 0.3s ease;
78
+ }
79
+
80
+ .card h3 {
81
+ font-size: 0.9rem;
82
+ text-transform: uppercase;
83
+ letter-spacing: 1px;
84
+ color: var(--text-muted);
85
+ margin-bottom: 1.5rem;
86
+ }
87
+
88
+ .gauge-container {
89
+ height: 100px;
90
+ display: flex;
91
+ justify-content: center;
92
+ align-items: center;
93
+ }
94
+
95
+ .gauge {
96
+ font-size: 2.5rem;
97
+ font-weight: 700;
98
+ color: var(--primary-green);
99
+ }
100
+
101
+ .gauge small {
102
+ font-size: 1rem;
103
+ margin-left: 0.2rem;
104
+ }
105
+
106
+ .value-large {
107
+ font-size: 3rem;
108
+ font-weight: 600;
109
+ color: var(--deep-forest);
110
+ }
111
+
112
+ .shipment-list {
113
+ display: flex;
114
+ flex-direction: column;
115
+ gap: 0.75rem;
116
+ }
117
+
118
+ .shipment-item {
119
+ background: var(--bg-cream);
120
+ padding: 1rem;
121
+ border-radius: 8px;
122
+ border-left: 4px solid var(--sage-green);
123
+ display: flex;
124
+ justify-content: space-between;
125
+ }
126
+
127
+ .ticker-box {
128
+ background: var(--bg-cream);
129
+ padding: 1rem;
130
+ border-radius: 8px;
131
+ font-style: italic;
132
+ color: var(--deep-forest);
133
+ }
134
+
135
+ footer {
136
+ text-align: center;
137
+ margin-top: 4rem;
138
+ padding-top: 1rem;
139
+ border-top: 1px solid var(--border-tan);
140
+ color: var(--text-muted);
141
+ font-size: 0.8rem;
142
+ }
143
+
144
+ @media (max-width: 768px) {
145
+ .container { padding: 1rem; }
146
+ }
netzero_nav/server.py CHANGED
@@ -1,16 +1,23 @@
1
  from fastapi import FastAPI
2
  from fastapi.responses import RedirectResponse
 
3
  from pydantic import BaseModel
4
  from typing import Optional
 
 
5
  from netzero_nav.env import AtlasEcoEnv
6
  from netzero_nav.models import Action, Observation, StepResponse
7
 
8
  app = FastAPI(title="NetZero Nav: Eco-Resilient Logistics")
9
  _env = AtlasEcoEnv()
10
 
 
 
 
 
11
  @app.get("/")
12
  def root():
13
- return RedirectResponse(url="/docs")
14
 
15
  class ResetRequest(BaseModel):
16
  task: Optional[str] = "easy"
 
1
  from fastapi import FastAPI
2
  from fastapi.responses import RedirectResponse
3
+ from fastapi.staticfiles import StaticFiles
4
  from pydantic import BaseModel
5
  from typing import Optional
6
+ import os
7
+
8
  from netzero_nav.env import AtlasEcoEnv
9
  from netzero_nav.models import Action, Observation, StepResponse
10
 
11
  app = FastAPI(title="NetZero Nav: Eco-Resilient Logistics")
12
  _env = AtlasEcoEnv()
13
 
14
+ # Mount Static Dashboard
15
+ static_path = os.path.join(os.getcwd(), "dashboard")
16
+ app.mount("/dashboard", StaticFiles(directory=static_path), name="dashboard")
17
+
18
  @app.get("/")
19
  def root():
20
+ return RedirectResponse(url="/dashboard/index.html")
21
 
22
  class ResetRequest(BaseModel):
23
  task: Optional[str] = "easy"