Initial commit — multi-tenant filtering, port constraints, chart bbox

This commit is contained in:
2026-05-04 22:41:09 -04:00
parent c3b07be67e
commit fcf1d2787a
1102 changed files with 7353 additions and 1166 deletions
+172 -3
View File
@@ -84,8 +84,9 @@
<div class="nav-item" id="nav-users">
<span data-i18n="nav.users">USERS</span>
<div class="nav-dropdown" id="dd-users">
<div class="dd-item" data-action="users-list" data-i18n="dd.manage">Manage Users</div>
<div class="dd-item" data-action="users-create" data-i18n="dd.create">Create User</div>
<div class="dd-item" data-action="users-list" data-i18n="dd.manage">Manage Users</div>
<div class="dd-item" data-action="users-create" data-i18n="dd.create">Create User</div>
<div class="dd-item dd-sep" data-action="org-management">Organizations &amp; Ports</div>
</div>
</div>
</nav>
@@ -371,11 +372,19 @@
<div class="form-field" style="margin-top:10px">
<label class="form-label">Role *</label>
<select class="form-input-select" id="uf-role">
<option value="USER">USER — Read only</option>
<option value="USER">USER — Read only (client)</option>
<option value="CLIENT_ADMIN">CLIENT ADMIN — Can record (client)</option>
<option value="ADMIN">ADMIN — Can edit aids</option>
<option value="SUPERADMIN">SUPERADMIN — Full access</option>
</select>
</div>
<div class="form-field" style="margin-top:10px" id="uf-company-row">
<label class="form-label">Company (client port) *</label>
<select class="form-input-select" id="uf-company">
<option value="">— Select company —</option>
</select>
<div style="font-size:0.7rem;color:var(--text-muted);margin-top:4px" id="uf-company-hint"></div>
</div>
<div id="uf-error" class="modal-error hidden" style="margin-top:10px"></div>
</div>
<div class="modal-footer">
@@ -712,6 +721,164 @@
</div>
</div>
<!-- DVR — AIS TRACK HISTORY ─────────────────────────────────────────── -->
<div id="modal-track-history" class="modal modal-wide hidden" style="max-width:820px">
<div class="modal-header">
<span class="modal-title">AIS TRACK HISTORY — DVR Replay</span>
<button class="modal-close" id="btn-close-track-history">&times;</button>
</div>
<div class="modal-body">
<div style="display:flex;gap:10px;margin-bottom:14px;align-items:flex-end;flex-wrap:wrap">
<div class="form-field" style="flex:1;min-width:160px">
<label class="form-label">MMSI</label>
<input class="form-input" id="dvr-mmsi" placeholder="e.g. 123456789">
</div>
<div class="form-field">
<label class="form-label">Date From</label>
<input class="form-input" id="dvr-from" type="date">
</div>
<div class="form-field">
<label class="form-label">Date To</label>
<input class="form-input" id="dvr-to" type="date">
</div>
<div class="form-field">
<label class="form-label">Source</label>
<select class="form-input-select" id="dvr-source">
<option value="vessel">Vessel (AIS)</option>
<option value="aton">AtoN (Type 21)</option>
</select>
</div>
<button class="btn-modal-primary" id="btn-dvr-load" style="padding:5px 14px;margin-bottom:0">LOAD TRACK</button>
</div>
<!-- Track stats bar -->
<div id="dvr-stats" style="font-size:0.72rem;color:var(--text-muted);margin-bottom:8px;display:none">
<span id="dvr-stats-text"></span>
<button class="chart-row-btn" id="btn-dvr-show-map" style="margin-left:10px">SHOW ON MAP</button>
<button class="chart-row-btn" id="btn-dvr-csv" style="margin-left:4px">EXPORT CSV</button>
</div>
<!-- DVR playback controls (visible once track loaded) -->
<div id="dvr-controls" style="display:none;background:#0d1b2a;border:1px solid #1e3a5f;border-radius:4px;padding:10px;margin-bottom:10px">
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap">
<button class="chart-row-btn" id="btn-dvr-play" title="Play">&#9654;</button>
<button class="chart-row-btn" id="btn-dvr-pause" title="Pause">&#9646;&#9646;</button>
<button class="chart-row-btn" id="btn-dvr-stop" title="Stop / Reset">&#9632;</button>
<label class="form-label" style="margin:0 4px 0 8px">Speed</label>
<select class="form-input-select" id="dvr-speed" style="width:80px">
<option value="1">1×</option>
<option value="5">5×</option>
<option value="20" selected>20×</option>
<option value="60">60×</option>
<option value="300">300×</option>
</select>
<div style="flex:1;min-width:160px">
<input type="range" id="dvr-slider" min="0" value="0" style="width:100%;accent-color:var(--accent)">
</div>
<span id="dvr-timestamp" class="mono" style="font-size:0.7rem;color:var(--accent);min-width:140px">--:--:-- UTC</span>
</div>
<div id="dvr-position" style="font-size:0.7rem;color:var(--text-muted);margin-top:6px">
<span id="dvr-lat">LAT --</span> &nbsp; <span id="dvr-lon">LON --</span>
&nbsp;·&nbsp; <span id="dvr-sog">SOG --</span> kn
&nbsp;·&nbsp; <span id="dvr-cog">COG --</span>°
</div>
</div>
<!-- Track table -->
<div id="dvr-body" style="max-height:280px;overflow-y:auto">
<div style="color:var(--text-muted);font-size:0.75rem;padding:20px 0;text-align:center">
Enter an MMSI and date range, then press LOAD TRACK.
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-modal-secondary" id="btn-close-track-history2">CLOSE</button>
</div>
</div>
<!-- ORGANIZATION MANAGEMENT (Ports, Companies, Buoy Ownership) ────────── -->
<div id="modal-org" class="modal modal-wide hidden" style="max-width:900px">
<div class="modal-header">
<span class="modal-title">ORGANIZATION — Ports, Companies &amp; Buoy Ownership</span>
<button class="modal-close" id="btn-close-org">&times;</button>
</div>
<div class="modal-body">
<div class="stab-bar" style="margin-bottom:14px">
<button class="stab active" data-otab="otab-ports">PORTS</button>
<button class="stab" data-otab="otab-companies">COMPANIES</button>
<button class="stab" data-otab="otab-ownership">BUOY OWNERSHIP</button>
</div>
<!-- TAB: PORTS -->
<div id="otab-ports" class="otab-panel">
<div style="font-size:0.72rem;color:var(--text-muted);margin-bottom:10px">
Ports are used to set the default map view for each company's users on login.
</div>
<table class="chart-table" id="org-ports-table">
<thead><tr><th>Name</th><th>Center</th><th>Zoom</th><th>Chart</th><th>Status</th><th></th></tr></thead>
<tbody id="org-ports-body"></tbody>
</table>
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
<input class="form-input" id="op-name" placeholder="Port name" style="width:150px">
<input class="form-input" id="op-lat" placeholder="Lat" type="number" step="0.0001" style="width:100px">
<input class="form-input" id="op-lon" placeholder="Lon" type="number" step="0.0001" style="width:100px">
<input class="form-input" id="op-zoom" placeholder="Zoom (12)" type="number" step="0.5" style="width:80px">
<input class="form-input" id="op-chart" placeholder="Chart folder (opt.)" style="width:160px">
<button class="btn-modal-primary" id="btn-port-add" style="padding:5px 12px">ADD PORT</button>
</div>
<div id="org-ports-status" class="save-status" style="margin-top:8px"></div>
</div>
<!-- TAB: COMPANIES -->
<div id="otab-companies" class="otab-panel hidden">
<table class="chart-table" id="org-companies-table">
<thead><tr><th>Company</th><th>Home Port</th><th>Email</th><th>Phone</th><th>Status</th><th></th></tr></thead>
<tbody id="org-companies-body"></tbody>
</table>
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
<input class="form-input" id="oc-name" placeholder="Company name" style="width:200px">
<select class="form-input-select" id="oc-port" style="width:160px">
<option value="">— Select port —</option>
</select>
<input class="form-input" id="oc-email" placeholder="Contact email" style="width:180px">
<input class="form-input" id="oc-phone" placeholder="Phone" style="width:130px">
<button class="btn-modal-primary" id="btn-company-add" style="padding:5px 12px">ADD COMPANY</button>
</div>
<div id="org-companies-status" class="save-status" style="margin-top:8px"></div>
</div>
<!-- TAB: BUOY OWNERSHIP -->
<div id="otab-ownership" class="otab-panel hidden">
<div style="display:flex;gap:10px;margin-bottom:10px;align-items:flex-end">
<div class="form-field" style="flex:1">
<label class="form-label">Filter by Company</label>
<select class="form-input-select" id="ow-filter-company">
<option value="">— All companies —</option>
</select>
</div>
<button class="chart-row-btn" id="btn-ow-load">LOAD</button>
</div>
<table class="chart-table" id="org-ownership-table">
<thead><tr><th>Company</th><th>Aid Name</th><th>MMSI</th><th>Notes</th><th></th></tr></thead>
<tbody id="org-ownership-body"></tbody>
</table>
<div style="margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;align-items:flex-end">
<select class="form-input-select" id="ow-company-sel" style="width:200px">
<option value="">— Company —</option>
</select>
<input class="form-input" id="ow-aid-id" placeholder="Aid ID (UUID)" style="width:230px">
<input class="form-input" id="ow-mmsi" placeholder="or MMSI" style="width:130px">
<input class="form-input" id="ow-notes" placeholder="Notes (opt.)" style="width:150px">
<button class="btn-modal-primary" id="btn-ow-assign" style="padding:5px 12px">ASSIGN</button>
</div>
<div id="org-ownership-status" class="save-status" style="margin-top:8px"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn-modal-secondary" id="btn-close-org2">CLOSE</button>
</div>
</div>
</div><!-- /modal-overlay -->
<script src="https://cdn.jsdelivr.net/npm/ol@v9.2.4/dist/ol.js"></script>
@@ -720,5 +887,7 @@
<script src="js/map.js"></script>
<script src="js/websocket.js"></script>
<script src="js/menu.js"></script>
<script src="js/dvr.js"></script>
<script src="js/org.js"></script>
</body>
</html>