eeeeelin commited on
Commit
6e684d8
·
1 Parent(s): 7b99dea

new ui/ux

Browse files
Files changed (2) hide show
  1. app/static/css/style.css +233 -0
  2. app/templates/dashboard.html +150 -211
app/static/css/style.css CHANGED
@@ -1252,3 +1252,236 @@ body {
1252
  opacity: 0;
1253
  }
1254
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1252
  opacity: 0;
1253
  }
1254
  }
1255
+
1256
+ .workbench-container {
1257
+ display: flex;
1258
+ height: calc(100vh - 70px); /* Subtract Navbar height approx */
1259
+ overflow: hidden;
1260
+ background-color: var(--background-color);
1261
+ }
1262
+
1263
+ /* --- SIDEBAR --- */
1264
+ .workbench-sidebar {
1265
+ width: 320px;
1266
+ background-color: var(--card-background);
1267
+ border-right: 1px solid var(--border-color);
1268
+ display: flex;
1269
+ flex-direction: column;
1270
+ padding: 1.5rem;
1271
+ overflow-y: auto;
1272
+ flex-shrink: 0;
1273
+ z-index: 20;
1274
+ }
1275
+
1276
+ .sidebar-header {
1277
+ margin-bottom: 1.5rem;
1278
+ color: var(--text-primary);
1279
+ }
1280
+
1281
+ .sidebar-header h2 {
1282
+ font-size: 1.25rem;
1283
+ display: flex;
1284
+ align-items: center;
1285
+ gap: 0.5rem;
1286
+ }
1287
+
1288
+ .sidebar-divider {
1289
+ border: 0;
1290
+ border-top: 1px solid var(--border-color);
1291
+ margin: 1.5rem 0;
1292
+ }
1293
+
1294
+ .config-group {
1295
+ margin-bottom: 1.25rem;
1296
+ }
1297
+
1298
+ /* Camera Tabs */
1299
+ .camera-tabs {
1300
+ display: flex;
1301
+ background-color: var(--background-color);
1302
+ padding: 0.25rem;
1303
+ border-radius: 0.5rem;
1304
+ margin-bottom: 1rem;
1305
+ }
1306
+
1307
+ .tab-btn {
1308
+ flex: 1;
1309
+ border: none;
1310
+ background: transparent;
1311
+ padding: 0.5rem;
1312
+ border-radius: 0.375rem;
1313
+ color: var(--text-secondary);
1314
+ font-weight: 500;
1315
+ cursor: pointer;
1316
+ transition: all 0.2s ease;
1317
+ display: flex;
1318
+ align-items: center;
1319
+ justify-content: center;
1320
+ gap: 0.5rem;
1321
+ }
1322
+
1323
+ .tab-btn:hover {
1324
+ color: var(--text-primary);
1325
+ }
1326
+
1327
+ .tab-btn.active {
1328
+ background-color: #ffffff;
1329
+ color: var(--primary-color);
1330
+ box-shadow: 0 1px 2px rgba(0,0,0,0.1);
1331
+ }
1332
+
1333
+ /* Config Card */
1334
+ .source-config-card {
1335
+ background-color: #f8fafc;
1336
+ border: 1px solid var(--border-color);
1337
+ border-radius: 0.5rem;
1338
+ padding: 1rem;
1339
+ }
1340
+
1341
+ .status-indicator {
1342
+ display: flex;
1343
+ align-items: center;
1344
+ gap: 0.5rem;
1345
+ font-size: 0.875rem;
1346
+ font-weight: 600;
1347
+ }
1348
+
1349
+ .status-dot {
1350
+ width: 8px;
1351
+ height: 8px;
1352
+ border-radius: 50%;
1353
+ background-color: var(--secondary-color);
1354
+ }
1355
+
1356
+ .status-dot.ready { background-color: var(--success-color); }
1357
+ .status-dot.error { background-color: var(--danger-color); }
1358
+
1359
+ .instruction-list {
1360
+ list-style: none;
1361
+ margin: 0;
1362
+ padding: 0;
1363
+ }
1364
+
1365
+ .instruction-list li {
1366
+ font-size: 0.85rem;
1367
+ color: var(--text-secondary);
1368
+ margin-bottom: 0.25rem;
1369
+ padding-left: 0.5rem;
1370
+ border-left: 2px solid var(--border-color);
1371
+ }
1372
+
1373
+ .file-name {
1374
+ font-size: 0.8rem;
1375
+ color: var(--text-secondary);
1376
+ }
1377
+
1378
+ .sidebar-footer {
1379
+ margin-top: auto;
1380
+ padding-top: 1.5rem;
1381
+ }
1382
+
1383
+ /* --- MAIN STAGE --- */
1384
+ .workbench-main {
1385
+ flex: 1;
1386
+ display: flex;
1387
+ flex-direction: column;
1388
+ overflow-y: auto;
1389
+ padding: 1.5rem;
1390
+ gap: 1.5rem;
1391
+ }
1392
+
1393
+ .video-stage-panel {
1394
+ background-color: var(--card-background);
1395
+ border-radius: 0.75rem;
1396
+ box-shadow: var(--shadow);
1397
+ padding: 1rem;
1398
+ display: flex;
1399
+ flex-direction: column;
1400
+ }
1401
+
1402
+ .stage-header {
1403
+ display: flex;
1404
+ justify-content: space-between;
1405
+ align-items: center;
1406
+ margin-bottom: 1rem;
1407
+ padding: 0 0.5rem;
1408
+ }
1409
+
1410
+ .stage-title {
1411
+ display: flex;
1412
+ align-items: center;
1413
+ gap: 1rem;
1414
+ }
1415
+
1416
+ .stage-title h3 {
1417
+ font-size: 1.125rem;
1418
+ font-weight: 600;
1419
+ margin: 0;
1420
+ }
1421
+
1422
+ .video-wrapper {
1423
+ position: relative;
1424
+ width: 100%;
1425
+ background-color: #0f172a;
1426
+ border-radius: 0.5rem;
1427
+ overflow: hidden;
1428
+ /* Maintain specific aspect ratio or adapt to content */
1429
+ min-height: 480px;
1430
+ display: flex;
1431
+ align-items: center;
1432
+ justify-content: center;
1433
+ }
1434
+
1435
+ /* Stage Elements */
1436
+ #setup-canvas,
1437
+ #live-feed {
1438
+ max-width: 100%;
1439
+ max-height: 70vh;
1440
+ display: block;
1441
+ }
1442
+
1443
+ #setup-canvas {
1444
+ cursor: crosshair;
1445
+ }
1446
+
1447
+ .stage-placeholder {
1448
+ display: flex;
1449
+ flex-direction: column;
1450
+ align-items: center;
1451
+ justify-content: center;
1452
+ color: #64748b;
1453
+ gap: 1rem;
1454
+ }
1455
+
1456
+ /* --- ANALYTICS PANEL --- */
1457
+ .analytics-panel {
1458
+ /* Uses existing grid styles, just ensuring spacing */
1459
+ padding-bottom: 2rem;
1460
+ }
1461
+
1462
+ .panel-title {
1463
+ font-size: 1.25rem;
1464
+ font-weight: 700;
1465
+ margin-bottom: 1rem;
1466
+ color: var(--text-primary);
1467
+ }
1468
+
1469
+ /* Responsive adjustments */
1470
+ @media (max-width: 1024px) {
1471
+ .workbench-container {
1472
+ flex-direction: column;
1473
+ height: auto;
1474
+ overflow: visible;
1475
+ }
1476
+
1477
+ .workbench-sidebar {
1478
+ width: 100%;
1479
+ border-right: none;
1480
+ border-bottom: 1px solid var(--border-color);
1481
+ max-height: 500px; /* Optional cap */
1482
+ }
1483
+
1484
+ .video-wrapper {
1485
+ min-height: 300px;
1486
+ }
1487
+ }
app/templates/dashboard.html CHANGED
@@ -1,235 +1,174 @@
1
  {% extends "base.html" %}
2
 
3
- {% block title %}Dashboard - R&R Vehicle Analytics{% endblock %}
4
-
5
  {% block content %}
6
- <!-- Processing Status Banner -->
7
- <div id="processing-banner" class="processing-banner {% if not processing_status or processing_status.status == 'completed' %}hidden{% endif %}">
8
- <div class="processing-content">
9
- <div class="spinner-small"></div>
10
- <span id="processing-text">
11
- {% if processing_status %}
12
- Processing video... {{ processing_status.progress }}%
13
- {% else %}
14
- Initializing...
15
- {% endif %}
16
- </span>
17
- <div class="progress-bar-inline">
18
- <div class="progress-bar-fill-inline" id="progress-bar" style="width: {% if processing_status %}{{ processing_status.progress }}{% else %}0{% endif %}%"></div>
19
  </div>
20
- </div>
21
- </div>
22
 
23
- <!-- Top Section: Camera Feeds + KPIs -->
24
- <div class="dashboard-top">
25
- <!-- Entry Camera -->
26
- <div class="camera-card">
27
- <div class="camera-header">
28
- <span class="camera-title">ENTRY CAM</span>
29
- <span class="live-indicator" id="entry-live-indicator">
30
- <span class="live-dot"></span>
31
- <span id="entry-status">{% if processing_status and processing_status.status == 'processing' %}PROCESSING{% else %}IDLE{% endif %}</span>
32
- </span>
 
 
 
 
 
 
 
 
 
 
 
33
  </div>
34
- <div class="camera-feed" id="entry-cam">
35
- <div class="camera-placeholder">
36
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
37
- <path d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
38
- </svg>
39
- <span>No Feed</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  </div>
41
  </div>
42
- </div>
43
-
44
- <!-- Center KPIs -->
45
- <div class="kpi-center">
46
- <div class="kpi-main">
47
- <div class="kpi-label-small">ESTIMATED PEOPLE ON-SITE</div>
48
- <div class="kpi-value-large" id="people-range">
49
- {% if session_data and session_data.statistics %}
50
- {{ session_data.statistics.people_on_site_min }} - {{ session_data.statistics.people_on_site_max }}
51
- {% else %}
52
- 0 - 0
53
- {% endif %}
54
  </div>
55
- <div class="kpi-sublabel">Min - Max Capacity</div>
56
  </div>
 
57
 
58
- <div class="kpi-row">
59
- <div class="kpi-item">
60
- <div class="kpi-number blue" id="vehicles-in">
61
- {% if session_data and session_data.statistics %}
62
- {{ session_data.statistics.vehicles_in }}
63
- {% else %}
64
- 0
65
- {% endif %}
 
 
66
  </div>
67
- <div class="kpi-label-small">VEHICLES IN</div>
68
  </div>
69
- <div class="kpi-divider"></div>
70
- <div class="kpi-item">
71
- <div class="kpi-number red" id="vehicles-out">
72
- {% if session_data and session_data.statistics %}
73
- {{ session_data.statistics.vehicles_out }}
74
- {% else %}
75
- 0
76
- {% endif %}
 
 
 
77
  </div>
78
- <div class="kpi-label-small">VEHICLES OUT</div>
79
  </div>
80
- </div>
81
 
82
- <div class="kpi-net">
83
- <strong>Net Vehicles: </strong>
84
- <span id="net-vehicles">
85
- {% if session_data and session_data.statistics %}
86
- {{ session_data.statistics.net_vehicles }}
87
- {% else %}
88
- 0
89
- {% endif %}
90
- </span>
91
- </div>
92
- </div>
93
-
94
- <!-- Exit Camera -->
95
- <div class="camera-card">
96
- <div class="camera-header">
97
- <span class="camera-title">EXIT CAM</span>
98
- <span class="live-indicator">
99
- <span class="live-dot"></span>
100
- IDLE
101
- </span>
102
- </div>
103
- <div class="camera-feed" id="exit-cam">
104
- <div class="camera-placeholder">
105
- <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
106
- <path d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
107
- </svg>
108
- <span>No Feed</span>
 
 
 
 
 
109
  </div>
110
- </div>
111
- </div>
112
- </div>
113
 
114
- <!-- Middle Section: Log + Distribution Chart -->
115
- <div class="dashboard-middle">
116
- <!-- Real-time Log -->
117
- <div class="card">
118
- <div class="card-header">
119
- <span class="card-title">
120
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
121
- <path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01"/>
122
- </svg>
123
- Real-time Log
124
- </span>
125
- <span class="event-count" id="event-count">
126
- {% if session_data and session_data.events %}
127
- {{ session_data.events|length }} events
128
- {% else %}
129
- 0 events
130
- {% endif %}
131
- </span>
132
- </div>
133
- <div class="event-log" id="event-log">
134
- <table class="data-table">
135
- <thead>
136
- <tr>
137
- <th>Timestamp</th>
138
- <th>Vehicle Type</th>
139
- <th>Direction</th>
140
- <th>Seats (Min - Max)</th>
141
- </tr>
142
- </thead>
143
- <tbody id="event-log-body">
144
- {% if session_data and session_data.events %}
145
- {% for event in session_data.events[-10:]|reverse %}
146
- <tr>
147
- <td>{{ event.timestamp[-8:] if event.timestamp else '--:--:--' }}</td>
148
- <td><span class="badge badge-{{ event.vehicle_type|lower }}">{{ event.vehicle_type }}</span></td>
149
- <td><span class="badge {% if event.direction == 'IN' %}badge-in{% else %}badge-out{% endif %}">{{ event.direction }}</span></td>
150
- <td>{{ event.seats_min }} - {{ event.seats_max }}</td>
151
- </tr>
152
- {% endfor %}
153
- {% else %}
154
- <tr class="empty-row">
155
- <td colspan="4" class="text-center text-secondary">No events recorded yet</td>
156
- </tr>
157
- {% endif %}
158
- </tbody>
159
- </table>
160
- </div>
161
- </div>
162
-
163
- <!-- Vehicle Distribution Chart -->
164
- <div class="card">
165
- <div class="card-header">
166
- <span class="card-title">
167
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
168
- <path d="M18 20V10M12 20V4M6 20v-6"/>
169
- </svg>
170
- Vehicle Distribution
171
- </span>
172
- </div>
173
- <div class="chart-container">
174
- <canvas id="distributionChart"></canvas>
175
- </div>
176
- </div>
177
  </div>
178
 
179
- <!-- Bottom Section: People Flow Trend -->
180
- <div class="card">
181
- <div class="card-header">
182
- <span class="card-title">
183
- <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
184
- <path d="M3 3v18h18M7 16l4-4 4 4 5-5"/>
185
- </svg>
186
- People Flow Trend
187
- </span>
188
- <div class="time-filter">
189
- <button class="time-btn active" data-period="hour">Hour</button>
190
- <button class="time-btn" data-period="day">Day</button>
191
- <button class="time-btn" data-period="week">Week</button>
192
- <button class="time-btn" data-period="month">Month</button>
193
- <button class="time-btn" data-period="year">Year</button>
194
- </div>
195
- </div>
196
- <div class="chart-container chart-large">
197
- <canvas id="flowChart"></canvas>
198
- </div>
199
- <div class="chart-legend">
200
- <span class="legend-item">
201
- <span class="legend-color blue"></span>
202
- Actual Flow
203
- </span>
204
- <span class="legend-item">
205
- <span class="legend-color red"></span>
206
- Predicted Flow
207
- </span>
208
- </div>
209
- </div>
210
 
211
- {% if not session_id %}
212
- <div class="no-session-overlay" id="no-session-overlay">
213
- <div class="no-session-content">
214
- <svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
215
- <path d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"/>
216
- </svg>
217
- <h2>No Active Session</h2>
218
- <p>Configure a video source to start tracking vehicles</p>
219
- <a href="{{ url_for('setup.configuration') }}" class="btn btn-primary">Go to Setup</a>
220
- </div>
221
- </div>
222
- {% endif %}
223
 
224
- <!-- Store session data for JavaScript -->
225
  <script>
226
- window.SESSION_ID = "{{ session_id or '' }}";
227
- window.LOCATION = "{{ location or 'Unknown' }}";
 
 
 
 
 
228
  </script>
229
- {% endblock %}
230
-
231
- {% block scripts %}
232
- <!-- Socket.IO Client -->
233
- <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.6.0/socket.io.min.js"></script>
234
- <script src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
235
- {% endblock %}
 
1
  {% extends "base.html" %}
2
 
 
 
3
  {% block content %}
4
+ <div class="workbench-container">
5
+
6
+ <aside class="workbench-sidebar">
7
+ <div class="sidebar-header">
8
+ <h2><i data-feather="settings"></i> Configuration</h2>
 
 
 
 
 
 
 
 
9
  </div>
 
 
10
 
11
+ <div class="config-group">
12
+ <label class="form-label">Location Name</label>
13
+ <input type="text" id="location-input" class="form-input" placeholder="e.g. R&R Skudai" value="{{ session.get('location', 'Unknown') }}">
14
+ </div>
15
+
16
+ <div class="config-group">
17
+ <label class="form-label">Session Date/Time</label>
18
+ <input type="datetime-local" id="session-time" class="form-input">
19
+ <small class="text-secondary">Establishes the timeline anchor.</small>
20
+ </div>
21
+
22
+ <hr class="sidebar-divider">
23
+
24
+ <label class="form-label">Select Camera to Configure</label>
25
+ <div class="camera-tabs">
26
+ <button class="tab-btn active" onclick="switchCameraContext('ENTRY')" id="tab-entry">
27
+ <i data-feather="log-in"></i> Entry Cam
28
+ </button>
29
+ <button class="tab-btn" onclick="switchCameraContext('EXIT')" id="tab-exit">
30
+ <i data-feather="log-out"></i> Exit Cam
31
+ </button>
32
  </div>
33
+
34
+ <div class="source-config-card">
35
+ <div class="status-indicator">
36
+ <span class="status-dot" id="config-status-dot"></span>
37
+ <span id="config-status-text">Not Configured</span>
38
+ </div>
39
+
40
+ <div class="config-group mt-2">
41
+ <label class="form-label">Video Source</label>
42
+ <input type="file" id="video-upload" accept="video/*" class="form-input" style="display: none;">
43
+ <button class="btn btn-outline btn-full" onclick="document.getElementById('video-upload').click()">
44
+ <i data-feather="upload"></i> Upload Video File
45
+ </button>
46
+ <div id="file-name-display" class="file-name text-truncate mt-1">No file selected</div>
47
+ </div>
48
+
49
+ <div class="config-group">
50
+ <label class="form-label">Instructions</label>
51
+ <ul class="instruction-list">
52
+ <li>1. Upload a video.</li>
53
+ <li>2. Draw the crossing line on the canvas.</li>
54
+ <li>3. Repeat for other camera.</li>
55
+ </ul>
56
+ <button class="btn btn-sm btn-secondary mt-2" id="clear-line-btn" disabled>
57
+ <i data-feather="refresh-cw"></i> Redraw Line
58
+ </button>
59
  </div>
60
  </div>
61
+
62
+ <div class="sidebar-footer">
63
+ <button id="btn-start-analysis" class="btn btn-primary btn-full btn-lg" onclick="startAnalysis()">
64
+ <i data-feather="play"></i> Start Analysis
65
+ </button>
66
+ <div id="processing-status" class="hidden mt-2">
67
+ <div class="progress-bar">
68
+ <div class="progress-bar-fill" style="width: 0%"></div>
69
+ </div>
70
+ <div class="text-center mt-1"><small>Processing...</small></div>
 
 
71
  </div>
 
72
  </div>
73
+ </aside>
74
 
75
+ <main class="workbench-main">
76
+
77
+ <section class="video-stage-panel">
78
+ <div class="stage-header">
79
+ <div class="stage-title">
80
+ <span class="badge badge-processing hidden" id="live-badge">LIVE ANALYSIS</span>
81
+ <h3 id="stage-label">Entry Camera Preview</h3>
82
+ </div>
83
+ <div class="stage-controls">
84
+ <span class="text-secondary" style="font-size: 0.9rem;">Mode: <span id="mode-label">Setup</span></span>
85
  </div>
 
86
  </div>
87
+
88
+ <div class="video-wrapper" id="video-container">
89
+ <canvas id="setup-canvas"></canvas>
90
+
91
+ <img id="live-feed" src="" class="hidden" alt="Analysis Feed">
92
+
93
+ <video id="preview-player" style="display:none;" muted playsinline></video>
94
+
95
+ <div id="stage-placeholder" class="stage-placeholder">
96
+ <i data-feather="video" style="width: 48px; height: 48px; opacity: 0.5;"></i>
97
+ <p>Select a camera and upload video to begin configuration</p>
98
  </div>
 
99
  </div>
100
+ </section>
101
 
102
+ <section class="analytics-panel">
103
+ <h3 class="panel-title">Session Analytics</h3>
104
+
105
+ <div class="kpi-grid">
106
+ <div class="kpi-card">
107
+ <div class="kpi-icon blue"><i data-feather="truck"></i></div>
108
+ <div class="kpi-content">
109
+ <div class="kpi-label">Total Vehicles</div>
110
+ <div class="kpi-value" id="total-vehicles">0</div>
111
+ </div>
112
+ </div>
113
+ <div class="kpi-card">
114
+ <div class="kpi-icon green"><i data-feather="arrow-right-circle"></i></div>
115
+ <div class="kpi-content">
116
+ <div class="kpi-label">Entry Flow</div>
117
+ <div class="kpi-value positive" id="in-count">0</div>
118
+ </div>
119
+ </div>
120
+ <div class="kpi-card">
121
+ <div class="kpi-icon orange"><i data-feather="arrow-left-circle"></i></div>
122
+ <div class="kpi-content">
123
+ <div class="kpi-label">Exit Flow</div>
124
+ <div class="kpi-value negative" id="out-count">0</div>
125
+ </div>
126
+ </div>
127
+ <div class="kpi-card">
128
+ <div class="kpi-icon red"><i data-feather="users"></i></div>
129
+ <div class="kpi-content">
130
+ <div class="kpi-label">Est. People</div>
131
+ <div class="kpi-value" id="total-people">0</div>
132
+ </div>
133
+ </div>
134
  </div>
 
 
 
135
 
136
+ <div class="grid-2">
137
+ <div class="card">
138
+ <div class="card-header">
139
+ <div class="card-title"><i data-feather="bar-chart-2"></i> Vehicle Classification</div>
140
+ </div>
141
+ <div class="chart-container">
142
+ <canvas id="vehicle-chart"></canvas>
143
+ </div>
144
+ </div>
145
+ <div class="card">
146
+ <div class="card-header">
147
+ <div class="card-title"><i data-feather="list"></i> Live Event Log</div>
148
+ <span class="badge badge-processing" id="log-count">0 events</span>
149
+ </div>
150
+ <div class="event-log" id="event-log">
151
+ </div>
152
+ </div>
153
+ </div>
154
+ </section>
155
+ </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  </div>
157
 
158
+ <script src="https://cdn.socket.io/4.6.0/socket.io.min.js"></script>
159
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
160
+ <script src="https://unpkg.com/feather-icons"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
161
 
162
+ <script src="{{ url_for('static', filename='js/dashboard.js') }}"></script>
163
+ <script src="{{ url_for('static', filename='js/setup_canvas.js') }}"></script>
 
 
 
 
 
 
 
 
 
 
164
 
 
165
  <script>
166
+ // Quick initialize for icons
167
+ feather.replace();
168
+
169
+ // Set default datetime to now
170
+ const now = new Date();
171
+ now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
172
+ document.getElementById('session-time').value = now.toISOString().slice(0,16);
173
  </script>
174
+ {% endblock %}