Repository navigation
Expand file tree
/
Copy pathdmr-archive.php
More file actions
164 lines (155 loc) · 7.86 KB
/
Copy pathdmr-archive.php
File metadata and controls
164 lines (155 loc) · 7.86 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
<?php
/**
* NewUI v4.0 — DMR call archive (Phase 86-archive)
*
* Full archive view for DMR recordings. The dashboard radio widget
* only shows the 100 most recent calls; this page lets the operator
* browse the entire `dmr_messages` table filtered by date range,
* direction, callsign, or transcript text.
*
* Reuses api/dmr-history.php (extended in fix-16 to accept
* date_from / date_to / direction / search filters) and the same
* api/dmr-audio.php playback proxy as the radio widget.
*/
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/inc/i18n.php';
require_once __DIR__ . '/inc/rbac.php';
// 2026-07-04 (GH #13) — pick the session profile matching the
// client's cookie (TCADMOBILE vs PHPSESSID). Without this, a
// browser holding a mobile cookie opens an empty desktop session
// here and bounces to login -> redirect loop.
require_once __DIR__ . '/inc/session-bootstrap.php';
sess_bootstrap_auto();
session_start();
if (empty($_SESSION['user_id'])) {
header('Location: login.php');
exit;
}
require_once __DIR__ . '/inc/force-pw-change.php';
force_pw_change_redirect();
// Same RBAC gate as api/dmr-audio.php and api/dmr-history.php.
// action.play_dmr_audio dropped 2026-08-15 (tools/rbac_permission_audit.php)
// -- no such permission was ever seeded; action.dmr_receive already covers
// DMR audio access.
$canView = is_admin() || rbac_can('action.dmr_receive');
if (!$canView) {
http_response_code(403);
echo '<h1>403 — Missing permission: action.dmr_receive</h1>';
exit;
}
// Suppress display_errors so any stray PHP notice doesn't leak into
// the rendered HTML and break layout (the way an undefined $level
// notice did on radio-ai.php before the same fix landed there).
ini_set('display_errors', '0');
$user = e($_SESSION['user']);
$level = current_role_name(); // navbar.php renders this
$theme = $_SESSION['day_night'] ?? 'Day';
$bs_theme = ($theme === 'Night') ? 'dark' : 'light';
$csrf = csrf_token();
$userPerms = rbac_user_permissions(); // navbar gates some links on these
$active_page = 'dmr-archive';
?><!DOCTYPE html>
<html lang="en" data-bs-theme="<?php echo $bs_theme; ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="csrf-token" content="<?php echo $csrf; ?>">
<title>DMR Archive — Tickets CAD</title>
<link rel="stylesheet" href="assets/vendor/bootstrap/bootstrap.min.css">
<link rel="stylesheet" href="assets/vendor/bootstrap/bootstrap-icons.min.css">
<!-- dashboard.css owns the .nav-main / .nav-menu-btn styles the
shared navbar template relies on; without it the menu lays out
as bare inline links instead of the proper toolbar. -->
<link rel="stylesheet" href="assets/css/dashboard.css?v=<?php echo file_exists(__DIR__ . '/assets/css/dashboard.css') ? filemtime(__DIR__ . '/assets/css/dashboard.css') : newui_version(); ?>">
<link rel="stylesheet" href="assets/css/radio-widget.css?v=<?php echo file_exists(__DIR__ . '/assets/css/radio-widget.css') ? filemtime(__DIR__ . '/assets/css/radio-widget.css') : newui_version(); ?>">
<style>
.dmr-archive-toolbar {
position: sticky; top: 0; z-index: 10;
background: var(--bs-body-bg);
border-bottom: 1px solid var(--bs-border-color);
padding: 0.75rem 1rem; margin-bottom: 1rem;
}
.dmr-archive-stats { color: var(--bs-secondary-color); font-size: 0.875rem; }
.dmr-archive-card {
background: var(--bs-tertiary-bg);
border: 1px solid var(--bs-border-color);
border-radius: 6px; padding: 0.75rem 1rem; margin-bottom: 0.5rem;
}
.dmr-archive-card.playing { border-color: var(--bs-primary); }
.dmr-archive-head { display: flex; gap: 0.75rem; align-items: center; flex-wrap: wrap; }
.dmr-archive-time { font-family: monospace; color: var(--bs-secondary-color); }
.dmr-archive-src { font-weight: 600; }
.dmr-archive-dur { color: var(--bs-secondary-color); font-size: 0.875rem; }
.dmr-archive-dir { font-size: 0.75rem; padding: 0.15rem 0.5rem; border-radius: 3px; text-transform: uppercase; }
.dmr-archive-dir.rx { background: var(--bs-info-bg-subtle); color: var(--bs-info-text-emphasis); }
.dmr-archive-dir.tx { background: var(--bs-warning-bg-subtle); color: var(--bs-warning-text-emphasis); }
.dmr-archive-transcript { margin-top: 0.4rem; color: var(--bs-body-color); font-size: 0.95rem; }
.dmr-archive-transcript.empty { color: var(--bs-secondary-color); font-style: italic; }
.dmr-archive-replay { margin-left: auto; }
.dmr-archive-loading { padding: 2rem; text-align: center; color: var(--bs-secondary-color); }
.dmr-archive-empty { padding: 3rem; text-align: center; color: var(--bs-secondary-color); }
</style>
</head>
<body>
<?php include_once __DIR__ . '/inc/navbar.php'; ?>
<main id="main-content" class="container-fluid p-3">
<div class="dmr-archive-toolbar">
<div class="row g-2 align-items-end">
<div class="col-auto">
<label for="archDateFrom" class="form-label small mb-1">From</label>
<input type="date" id="archDateFrom" class="form-control form-control-sm">
</div>
<div class="col-auto">
<label for="archDateTo" class="form-label small mb-1">To</label>
<input type="date" id="archDateTo" class="form-control form-control-sm">
</div>
<div class="col-auto">
<label for="archDirection" class="form-label small mb-1">Direction</label>
<select id="archDirection" class="form-select form-select-sm">
<option value="">All</option>
<option value="rx">RX only</option>
<option value="tx">TX only</option>
</select>
</div>
<div class="col-auto" style="min-width:14rem;flex:1;">
<label for="archSearch" class="form-label small mb-1">Search (callsign / DMR ID / transcript)</label>
<input type="search" id="archSearch" class="form-control form-control-sm" placeholder="N0NKI, 3127202, weather, ...">
</div>
<div class="col-auto">
<label for="archLimit" class="form-label small mb-1">Show up to</label>
<select id="archLimit" class="form-select form-select-sm">
<option value="100">100</option>
<option value="250" selected>250</option>
<option value="500">500</option>
<option value="1000">1000</option>
</select>
</div>
<div class="col-auto">
<button id="archApply" type="button" class="btn btn-primary btn-sm">
<i class="bi bi-funnel"></i> Apply
</button>
</div>
<div class="col-auto">
<button id="archToday" type="button" class="btn btn-outline-secondary btn-sm">Today</button>
<button id="arch7d" type="button" class="btn btn-outline-secondary btn-sm">7d</button>
<button id="arch30d" type="button" class="btn btn-outline-secondary btn-sm">30d</button>
</div>
</div>
<div class="dmr-archive-stats mt-2" id="archStats"> </div>
</div>
<div id="archResults">
<div class="dmr-archive-loading">
<div class="spinner-border spinner-border-sm"></div>
Loading recordings...
</div>
</div>
</main>
<script src="assets/vendor/bootstrap/bootstrap.bundle.min.js"></script>
<!-- Same toolbar wiring + day/night theme manager every other page
uses; without them the navbar's buttons (theme toggle, language
switcher, notification tray) are inert on this page. -->
<script src="assets/js/toolbar.js?v=<?php echo file_exists(__DIR__ . '/assets/js/toolbar.js') ? filemtime(__DIR__ . '/assets/js/toolbar.js') : newui_version(); ?>"></script>
<script src="assets/js/theme-manager.js?v=<?php echo file_exists(__DIR__ . '/assets/js/theme-manager.js') ? filemtime(__DIR__ . '/assets/js/theme-manager.js') : newui_version(); ?>"></script>
<script src="assets/js/dmr-archive.js?v=<?php echo filemtime(__DIR__ . '/assets/js/dmr-archive.js'); ?>"></script>
</body>
</html>