Repository navigation
Expand file tree
/
Copy pathmactrack_view_dashboard.php
More file actions
568 lines (471 loc) · 21.8 KB
/
Copy pathmactrack_view_dashboard.php
File metadata and controls
568 lines (471 loc) · 21.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDtool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
$guest_account = true;
chdir('../../');
require('./include/auth.php');
require_once('./plugins/mactrack/lib/mactrack_functions.php');
$title = __('MacTrack - Dashboard', 'mactrack');
// AJAX card/layout endpoints used by the shared card chrome (js/cards.js).
if (isset_request_var('action') && get_nfilter_request_var('action') == 'dash_card') {
header('Content-Type: application/json');
print mactrack_dashboard_card_ajax();
exit;
} elseif (isset_request_var('action') && get_nfilter_request_var('action') == 'dash_layout') {
header('Content-Type: application/json');
print mactrack_dashboard_layout_save();
exit;
}
general_header();
mactrack_dashboard();
bottom_footer();
/**
* The dashboard's card catalogue: each key maps to a title, a grid column
* span (out of 8) and whether the card can expand to show extra detail.
*
* @return array Ordered map of card key => definition.
*/
function mactrack_dashboard_card_meta(): array {
return [
'inventory' => ['title' => __('Inventory', 'mactrack'), 'span' => 2, 'expandable' => false],
'device_status' => ['title' => __('Device status', 'mactrack'), 'span' => 2, 'expandable' => false],
'address_totals' => ['title' => __('Address and port totals', 'mactrack'), 'span' => 2, 'expandable' => false],
'settings' => ['title' => __('Collection settings', 'mactrack'), 'span' => 2, 'expandable' => false],
'scan_runtimes' => ['title' => __('Scan runtimes', 'mactrack'), 'span' => 4, 'expandable' => true],
'table_sizes' => ['title' => __('Table sizes', 'mactrack'), 'span' => 4, 'expandable' => true],
'macwatch' => ['title' => __('MacWatch activity', 'mactrack'), 'span' => 2, 'expandable' => false],
'vendors' => ['title' => __('Vendors', 'mactrack'), 'span' => 2, 'expandable' => true],
];
}
/**
* The card keys available in this environment, in default order.
*
* @return string[]
*/
function mactrack_dashboard_available_cards(): array {
return array_keys(mactrack_dashboard_card_meta());
}
/**
* Formats an integer for display with thousands separators.
*
* @param mixed $value The raw value.
*
* @return string The formatted count.
*/
function mactrack_dashboard_count($value): string {
return number_format((float) $value, 0);
}
/**
* Formats a byte count into a human-readable size (KB/MB/GB).
*
* @param mixed $bytes The raw byte count.
*
* @return string The formatted size.
*/
function mactrack_dashboard_bytes($bytes): string {
$bytes = (float) $bytes;
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, $i < 2 ? 0 : 1) . ' ' . $units[$i];
}
/**
* Formats a duration in seconds as a compact human string.
*
* @param mixed $seconds The raw seconds.
*
* @return string The formatted duration.
*/
function mactrack_dashboard_seconds($seconds): string {
$seconds = (int) $seconds;
if ($seconds <= 0) {
return '0s';
}
if ($seconds < 60) {
return $seconds . 's';
}
$minutes = intdiv($seconds, 60);
$rest = $seconds % 60;
if ($minutes < 60) {
return $minutes . 'm ' . $rest . 's';
}
$hours = intdiv($minutes, 60);
$minutes = $minutes % 60;
return $hours . 'h ' . $minutes . 'm';
}
/**
* Formats a timestamp for display, or a dash when empty.
*
* @param mixed $value The raw timestamp.
*
* @return string The formatted time, or '—' when empty.
*/
function mactrack_dashboard_time($value): string {
$value = (string) $value;
if ($value === '' || $value === '0000-00-00 00:00:00' || $value === null) {
return '—';
}
return $value;
}
/**
* Builds a two-column key/value table row for a card body.
*
* @param string $label The row label.
* @param string $value The already-formatted value.
*
* @return string The <tr> markup.
*/
function mactrack_dashboard_kv_row($label, $value): string {
return '<tr><th class="mtdashKvLabel">' . html_escape($label) . '</th><td>' . html_escape($value) . '</td></tr>';
}
/**
* Builds a labelled proportion bar for a card body.
*
* @param string $label The bar label.
* @param int $value The value represented.
* @param int $max The maximum (full-width) value.
* @param string $state Severity class suffix (up/warn/down/none).
*
* @return string The bar markup.
*/
function mactrack_dashboard_bar($label, $value, $max, $state = 'none'): string {
$pct = ($max > 0) ? max(0, min(100, round(($value / $max) * 100))) : 0;
return '<div class="mtdashBarRow">'
. '<div class="mtdashBarHead"><span>' . html_escape($label) . '</span><span>' . html_escape(mactrack_dashboard_count($value)) . '</span></div>'
. '<div class="mtdashBar"><span class="mtdashBarFill mtdash-' . html_escape($state) . '" style="width:' . $pct . '%"></span></div>'
. '</div>';
}
/**
* Request-scoped device/site/address aggregate counts.
*
* @return array The aggregate counts.
*/
function mactrack_dashboard_counts(): array {
static $counts = null;
if ($counts !== null) {
return $counts;
}
$row = db_fetch_row('SELECT
COUNT(*) AS devices,
SUM(disabled = "") AS enabled,
SUM(disabled = "on") AS disabled,
SUM(disabled = "" AND snmp_status = 3) AS up,
SUM(disabled = "" AND snmp_status = 1) AS down,
SUM(disabled = "" AND snmp_status = 0) AS unknown,
SUM(disabled = "" AND snmp_status = 4) AS error,
SUM(ports_total) AS ports_total,
SUM(ports_active) AS ports_active,
SUM(ports_trunk) AS ports_trunk,
SUM(macs_active) AS macs_active,
SUM(ips_total) AS ips_total,
(SELECT COUNT(*) FROM mac_track_sites) AS sites,
(SELECT COUNT(*) FROM mac_track_device_types) AS device_types,
(SELECT COUNT(*) FROM mac_track_interfaces) AS interfaces,
(SELECT COUNT(*) FROM mac_track_oui_database) AS oui
FROM mac_track_devices');
$counts = array_map('intval', is_array($row) ? $row : []);
return $counts;
}
/**
* Renders the inner HTML body for a single dashboard card.
*
* @param string $key The card key.
*
* @return string The card body HTML.
*/
function mactrack_dashboard_card_body(string $key): string {
ob_start();
switch ($key) {
case 'inventory':
$c = mactrack_dashboard_counts();
print '<table class="mtdashTable mtdashKv"><tbody>';
print mactrack_dashboard_kv_row(__('Sites', 'mactrack'), mactrack_dashboard_count($c['sites']));
print mactrack_dashboard_kv_row(__('Devices', 'mactrack'), mactrack_dashboard_count($c['devices'] ?? 0));
print mactrack_dashboard_kv_row(__('Enabled devices', 'mactrack'), mactrack_dashboard_count($c['enabled'] ?? 0));
print mactrack_dashboard_kv_row(__('Disabled devices', 'mactrack'), mactrack_dashboard_count($c['disabled'] ?? 0));
print mactrack_dashboard_kv_row(__('Device types', 'mactrack'), mactrack_dashboard_count($c['device_types']));
print mactrack_dashboard_kv_row(__('Interfaces tracked', 'mactrack'), mactrack_dashboard_count($c['interfaces']));
print '</tbody></table>';
break;
case 'device_status':
$c = mactrack_dashboard_counts();
$max = max(1, (int) ($c['enabled'] ?? 0));
print '<div class="mtdashBars">';
print mactrack_dashboard_bar(__('Up', 'mactrack'), (int) ($c['up'] ?? 0), $max, 'up');
print mactrack_dashboard_bar(__('Down', 'mactrack'), (int) ($c['down'] ?? 0), $max, 'down');
print mactrack_dashboard_bar(__('Unknown', 'mactrack'), (int) ($c['unknown'] ?? 0), $max, 'warn');
print mactrack_dashboard_bar(__('Error', 'mactrack'), (int) ($c['error'] ?? 0), $max, 'warn');
print mactrack_dashboard_bar(__('Disabled', 'mactrack'), (int) ($c['disabled'] ?? 0), max(1, (int) ($c['devices'] ?? 1)), 'none');
print '</div>';
break;
case 'address_totals':
$c = mactrack_dashboard_counts();
print '<table class="mtdashTable mtdashKv"><tbody>';
print mactrack_dashboard_kv_row(__('Active MACs', 'mactrack'), mactrack_dashboard_count($c['macs_active'] ?? 0));
print mactrack_dashboard_kv_row(__('IP addresses', 'mactrack'), mactrack_dashboard_count($c['ips_total'] ?? 0));
print mactrack_dashboard_kv_row(__('Total ports', 'mactrack'), mactrack_dashboard_count($c['ports_total'] ?? 0));
print mactrack_dashboard_kv_row(__('Active ports', 'mactrack'), mactrack_dashboard_count($c['ports_active'] ?? 0));
print mactrack_dashboard_kv_row(__('Trunk ports', 'mactrack'), mactrack_dashboard_count($c['ports_trunk'] ?? 0));
print mactrack_dashboard_kv_row(__('Unique OUI vendors', 'mactrack'), mactrack_dashboard_count($c['oui']));
print '</tbody></table>';
break;
case 'settings':
$frequency = read_config_option('mt_collection_timing');
print '<table class="mtdashTable mtdashKv"><tbody>';
print mactrack_dashboard_kv_row(__('Scanning frequency', 'mactrack'), $frequency === 'disabled' || $frequency === '' ? __('Disabled', 'mactrack') : $frequency . ' ' . __('minutes', 'mactrack'));
print mactrack_dashboard_kv_row(__('Concurrent processes', 'mactrack'), (string) (read_config_option('mt_processes') ?: '—'));
print mactrack_dashboard_kv_row(__('Scanner max runtime', 'mactrack'), (read_config_option('mt_script_runtime') ?: '—') . ' ' . __('minutes', 'mactrack'));
print mactrack_dashboard_kv_row(__('MAC retention', 'mactrack'), (read_config_option('mt_data_retention') ?: '—') . ' ' . __('days', 'mactrack'));
print mactrack_dashboard_kv_row(__('IP retention', 'mactrack'), (read_config_option('mt_data_retention_ip') ?: '—') . ' ' . __('days', 'mactrack'));
print mactrack_dashboard_kv_row(__('Reverse DNS', 'mactrack'), read_config_option('mt_reverse_dns') != '' ? __('Enabled', 'mactrack') : __('Disabled', 'mactrack'));
print '</tbody></table>';
break;
case 'scan_runtimes':
$run = db_fetch_row('SELECT
MAX(last_rundate) AS last_scan,
AVG(last_runduration) AS avg_dur,
MAX(last_runduration) AS max_dur,
SUM(last_rundate >= DATE_SUB(NOW(), INTERVAL 1 HOUR)) AS last_hour
FROM mac_track_devices
WHERE disabled = ""');
print '<table class="mtdashTable mtdashKv"><tbody>';
print mactrack_dashboard_kv_row(__('Most recent scan', 'mactrack'), mactrack_dashboard_time($run['last_scan'] ?? ''));
print mactrack_dashboard_kv_row(__('Average duration', 'mactrack'), mactrack_dashboard_seconds($run['avg_dur'] ?? 0));
print mactrack_dashboard_kv_row(__('Longest duration', 'mactrack'), mactrack_dashboard_seconds($run['max_dur'] ?? 0));
print mactrack_dashboard_kv_row(__('Scanned in last hour', 'mactrack'), mactrack_dashboard_count($run['last_hour'] ?? 0));
print '</tbody></table>';
$slow = db_fetch_assoc('SELECT device_name, last_runduration, last_rundate
FROM mac_track_devices
WHERE disabled = "" AND last_runduration > 0
ORDER BY last_runduration DESC
LIMIT 8');
print '<table class="mtdashTable"><thead><tr><th>' . __esc('Slowest devices', 'mactrack') . '</th><th>' . __esc('Duration', 'mactrack') . '</th><th>' . __esc('Last scan', 'mactrack') . '</th></tr></thead><tbody>';
if (cacti_sizeof($slow)) {
foreach ($slow as $d) {
print '<tr><th scope="row">' . html_escape($d['device_name']) . '</th><td>' . html_escape(mactrack_dashboard_seconds($d['last_runduration'])) . '</td><td>' . html_escape(mactrack_dashboard_time($d['last_rundate'])) . '</td></tr>';
}
} else {
print '<tr><td colspan="3" class="mtdashEmpty">' . __esc('No scan runtimes recorded yet.', 'mactrack') . '</td></tr>';
}
print '</tbody></table>';
break;
case 'table_sizes':
$tables = db_fetch_assoc('SELECT TABLE_NAME AS name, TABLE_ROWS AS rows,
(DATA_LENGTH + INDEX_LENGTH) AS bytes
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME LIKE "mac\\_track%"
ORDER BY (DATA_LENGTH + INDEX_LENGTH) DESC
LIMIT 12');
print '<table class="mtdashTable"><thead><tr><th>' . __esc('Table', 'mactrack') . '</th><th>' . __esc('Rows', 'mactrack') . '</th><th>' . __esc('Size', 'mactrack') . '</th></tr></thead><tbody>';
if (cacti_sizeof($tables)) {
$max = 1;
foreach ($tables as $t) {
$max = max($max, (int) $t['bytes']);
}
foreach ($tables as $t) {
$pct = round(((int) $t['bytes'] / $max) * 100);
print '<tr><th scope="row">' . html_escape($t['name']) . '</th>'
. '<td>' . html_escape(mactrack_dashboard_count($t['rows'])) . '</td>'
. '<td><div class="mtdashInlineBar"><span class="mtdashBarFill mtdash-none" style="width:' . $pct . '%"></span></div>' . html_escape(mactrack_dashboard_bytes($t['bytes'])) . '</td></tr>';
}
} else {
print '<tr><td colspan="3" class="mtdashEmpty">' . __esc('No table statistics available.', 'mactrack') . '</td></tr>';
}
print '</tbody></table>';
break;
case 'macwatch':
$mw = db_fetch_row('SELECT COUNT(*) AS entries,
SUM(discovered) AS discovered,
MAX(date_last_seen) AS last_seen
FROM mac_track_macwatch');
print '<table class="mtdashTable mtdashKv"><tbody>';
print mactrack_dashboard_kv_row(__('Watched MACs', 'mactrack'), mactrack_dashboard_count($mw['entries'] ?? 0));
print mactrack_dashboard_kv_row(__('Discovered', 'mactrack'), mactrack_dashboard_count($mw['discovered'] ?? 0));
print mactrack_dashboard_kv_row(__('Last match', 'mactrack'), mactrack_dashboard_time($mw['last_seen'] ?? ''));
print '</tbody></table>';
break;
case 'vendors':
$c = mactrack_dashboard_counts();
print '<table class="mtdashTable mtdashKv"><tbody>';
print mactrack_dashboard_kv_row(__('OUI database entries', 'mactrack'), mactrack_dashboard_count($c['oui']));
print '</tbody></table>';
$top = db_fetch_assoc('SELECT dt.vendor AS vendor, COUNT(*) AS devices
FROM mac_track_devices AS d
INNER JOIN mac_track_device_types AS dt
ON d.device_type_id = dt.device_type_id
WHERE dt.vendor != ""
GROUP BY dt.vendor
ORDER BY devices DESC
LIMIT 8');
print '<table class="mtdashTable"><thead><tr><th>' . __esc('Vendor', 'mactrack') . '</th><th>' . __esc('Devices', 'mactrack') . '</th></tr></thead><tbody>';
if (cacti_sizeof($top)) {
foreach ($top as $v) {
print '<tr><th scope="row">' . html_escape($v['vendor']) . '</th><td>' . html_escape(mactrack_dashboard_count($v['devices'])) . '</td></tr>';
}
} else {
print '<tr><td colspan="2" class="mtdashEmpty">' . __esc('No vendor data available.', 'mactrack') . '</td></tr>';
}
print '</tbody></table>';
break;
}
return (string) ob_get_clean();
}
/**
* Renders a complete dashboard card (header + tools + body) for the given key.
*
* @param string $key The card key.
* @param bool $expanded Whether the card starts expanded.
*
* @return string The card HTML, or '' when the key is unknown.
*/
function mactrack_dashboard_render_card(string $key, bool $expanded = false): string {
$meta = mactrack_dashboard_card_meta();
if (!isset($meta[$key])) {
return '';
}
$def = $meta[$key];
$tools = '';
if ($def['expandable']) {
$tools .= '<button type="button" class="mtdashCardTool" data-tool="expand" aria-label="' . __esc('Show more', 'mactrack') . '" title="' . __esc('Show more', 'mactrack') . '"><i class="fa fa-chevron-down" aria-hidden="true"></i></button>';
$tools .= '<button type="button" class="mtdashCardTool" data-tool="collapse" aria-label="' . __esc('Show less', 'mactrack') . '" title="' . __esc('Show less', 'mactrack') . '"><i class="fa fa-chevron-up" aria-hidden="true"></i></button>';
}
$tools .= '<button type="button" class="mtdashCardTool" data-tool="maximize" aria-label="' . __esc('Open in a dialog', 'mactrack') . '" title="' . __esc('Open in a dialog', 'mactrack') . '"><i class="fa fa-window-maximize" aria-hidden="true"></i></button>';
$tools .= '<button type="button" class="mtdashCardTool" data-tool="refresh" aria-label="' . __esc('Refresh', 'mactrack') . '" title="' . __esc('Refresh', 'mactrack') . '"><i class="fa fa-sync-alt" aria-hidden="true"></i></button>';
$tools .= '<button type="button" class="mtdashCardTool" data-tool="remove" aria-label="' . __esc('Remove from page', 'mactrack') . '" title="' . __esc('Remove from page', 'mactrack') . '"><i class="fa fa-times" aria-hidden="true"></i></button>';
$classes = 'mtdashCard mtdashSpan' . (int) $def['span'];
if ($def['expandable']) {
$classes .= ' mtdashCardExpandable';
}
if ($expanded) {
$classes .= ' mtdashCardExpanded';
}
return '<section class="' . $classes . '" data-card="' . html_escape($key) . '">'
. '<header class="mtdashCardHeader">'
. '<button type="button" class="mtdashCardDrag" aria-label="' . __esc('Drag to reorder card', 'mactrack') . '"><i class="fa fa-bars" aria-hidden="true"></i></button>'
. '<h2 class="mtdashCardTitle">' . html_escape($def['title']) . '</h2>'
. '<span class="mtdashCardTools">' . $tools . '</span>'
. '</header>'
. '<div class="mtdashCardBody">' . mactrack_dashboard_card_body($key) . '</div>'
. '</section>';
}
/**
* The current user's saved dashboard layout (present cards in order plus
* expanded state). With no saved layout every card is present in default order.
*
* @return array{order: string[], expanded: array<string, bool>}
*/
function mactrack_dashboard_layout(): array {
$available = mactrack_dashboard_available_cards();
$saved = json_decode((string) read_user_setting('mactrack_dashboard_layout', '', true), true);
if (!is_array($saved) || !isset($saved['order']) || !is_array($saved['order'])) {
return ['order' => $available, 'expanded' => []];
}
$order = [];
foreach ($saved['order'] as $key) {
if (is_string($key) && in_array($key, $available, true) && !in_array($key, $order, true)) {
$order[] = $key;
}
}
$expanded = [];
if (isset($saved['expanded']) && is_array($saved['expanded'])) {
foreach ($saved['expanded'] as $key => $on) {
if ($on && in_array($key, $available, true)) {
$expanded[(string) $key] = true;
}
}
}
return ['order' => $order, 'expanded' => $expanded];
}
/**
* Persists the posted dashboard layout (card order and expanded state) for the
* current user in a single settings_user row, validated to available cards.
*
* @return string A JSON status document.
*/
function mactrack_dashboard_layout_save(): string {
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !function_exists('csrf_check') || !csrf_check(false)) {
return (string) json_encode(['error' => __('Invalid request. Please try again.', 'mactrack')]);
}
$available = mactrack_dashboard_available_cards();
$posted = json_decode(isset_request_var('layout') ? (string) get_nfilter_request_var('layout') : '', true);
$order = [];
$expanded = [];
if (is_array($posted)) {
if (isset($posted['order']) && is_array($posted['order'])) {
foreach ($posted['order'] as $key) {
if (is_string($key) && in_array($key, $available, true) && !in_array($key, $order, true)) {
$order[] = $key;
}
}
}
if (isset($posted['expanded']) && is_array($posted['expanded'])) {
foreach ($posted['expanded'] as $key => $on) {
if ($on && is_string($key) && in_array($key, $available, true)) {
$expanded[$key] = true;
}
}
}
}
set_user_setting('mactrack_dashboard_layout', json_encode(['order' => $order, 'expanded' => $expanded]));
return (string) json_encode(['ok' => true]);
}
/**
* AJAX: render a single dashboard card, used when adding one from the
* catalogue or refreshing one in place.
*
* @return string A JSON document with the card key and rendered HTML.
*/
function mactrack_dashboard_card_ajax(): string {
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !function_exists('csrf_check') || !csrf_check(false)) {
return (string) json_encode(['error' => __('Invalid request. Please try again.', 'mactrack')]);
}
$key = isset_request_var('card') ? (string) get_nfilter_request_var('card') : '';
if (!in_array($key, mactrack_dashboard_available_cards(), true)) {
return (string) json_encode(['error' => __('Unknown card.', 'mactrack')]);
}
$expanded = isset_request_var('expanded') && get_nfilter_request_var('expanded') === '1';
return (string) json_encode(['card' => $key, 'html' => mactrack_dashboard_render_card($key, $expanded)]);
}
/**
* Renders the MacTrack Dashboard tab: the add-card toolbar and the grid of
* admin status cards.
*
* @return void
*/
function mactrack_dashboard(): void {
mactrack_tabs();
$layout = mactrack_dashboard_layout();
$meta = mactrack_dashboard_card_meta();
$absent = array_values(array_diff(mactrack_dashboard_available_cards(), $layout['order']));
print '<div id="mactrack_dashboard_panel" class="mtdashPanel">';
print '<div class="mtdashToolbar">';
print '<label class="mtdashToolbarLabel" for="mactrack_dashboard_add">' . __esc('Add card', 'mactrack') . '</label>';
print '<select id="mactrack_dashboard_add" class="mtdashAdd"><option value="">' . __esc('Add a dashboard card…', 'mactrack') . '</option>';
foreach ($absent as $key) {
print '<option value="' . html_escape($key) . '">' . html_escape($meta[$key]['title']) . '</option>';
}
print '</select>';
print '</div>';
print '<div id="mactrack_dashboard" class="mtdashGrid">';
foreach ($layout['order'] as $key) {
print mactrack_dashboard_render_card($key, !empty($layout['expanded'][$key]));
}
print '</div>';
// Modal container reused by the maximize tool.
print '<div id="mactrack_dashboard_dialog" class="mtdashDialog" style="display:none"></div>';
print '</div>';
print "<script type='text/javascript' " . plugin_mactrack_csp_nonce() . ">initMactrackDashboard();</script>";
}