security: CORS hardening, path traversal fix, WebSocket auth + cleanup
- Restrict CORS to localhost origins (was allow_origins=[*])
- Require valid JWT on WebSocket /ws (anonymous no longer gets admin view)
- Fix path traversal in delete_cell(): resolve() + parent check
- Validate cell_id format in /charts/download-noaa/{cell_id}
- Exclude charts/ and Cartas/ from git (keep US1GC09M world overview)
- Add NOAA ENC Portal external link in charts catalog tab
- Untrack __pycache__/, .db, .claude/ session files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+81
-2
@@ -171,14 +171,31 @@ const Modal = {
|
||||
btn.textContent = 'SAVING...';
|
||||
status.textContent = '';
|
||||
|
||||
// MMSI: empty string → unassign (sends "" so backend nulls the column).
|
||||
// Non-empty → must be digits-only; backend enforces uniqueness across aids.
|
||||
const mmsiVal = v('ef-mmsi');
|
||||
if (mmsiVal && !/^\d{6,9}$/.test(mmsiVal)) {
|
||||
status.textContent = 'MMSI must be 6–9 digits, or blank to unassign.';
|
||||
status.className = 'save-status err';
|
||||
btn.disabled = false; btn.textContent = 'SAVE CHANGES';
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = {
|
||||
puerto_responsable: v('ef-puerto') || null,
|
||||
empresa_responsable: v('ef-empresa') || null,
|
||||
caracteristica_luz: v('ef-luz') || null,
|
||||
alcance_nm: flt('ef-alcance'),
|
||||
radio_borneo_m: flt('ef-borneo'),
|
||||
observaciones: v('ef-obs') || null,
|
||||
lamp_id: v('ef-lamp') || null,
|
||||
displacement_warn_m: flt('ef-drift-warn'),
|
||||
displacement_alarm_m: flt('ef-drift-alarm'),
|
||||
signal_loss_min: flt('ef-signal-loss') ? parseInt(v('ef-signal-loss')) : null,
|
||||
din3_function: v('ef-din3') || null,
|
||||
din4_function: v('ef-din4') || null,
|
||||
observaciones: v('ef-obs') || null,
|
||||
lamp_id: v('ef-lamp') || null,
|
||||
mmsi: mmsiVal, // "" unassigns; backend handles
|
||||
tipo_ais: mmsiVal ? 'ATON_21' : 'SIN_AIS',
|
||||
modificado_por: Auth.session.nombre,
|
||||
motivo_cambio: motivo,
|
||||
};
|
||||
@@ -273,6 +290,24 @@ function buildEditForm(p) {
|
||||
</div>
|
||||
</div>` : '';
|
||||
|
||||
// AIS link — when set, AIS Type 21 with this MMSI updates the buoy's
|
||||
// lat_actual (ghost marker) and triggers drift/battery alerts using this
|
||||
// aid's per-buoy thresholds.
|
||||
const aisBlock = `
|
||||
<div class="modal-section-label">AIS LINK</div>
|
||||
<div style="font-size:0.68rem;color:var(--text-muted);margin-bottom:6px">
|
||||
MMSI of the AtoN transponder on this buoy.
|
||||
Once set, the system listens for AIS Type 21 with this MMSI and shows
|
||||
the AIS-reported position as a transparent ghost over the nominal marker.
|
||||
Leave blank if the buoy has no AIS.
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">MMSI</label>
|
||||
<input class="form-input" id="ef-mmsi" type="text" inputmode="numeric"
|
||||
pattern="\\d{6,9}" maxlength="9"
|
||||
value="${p.mmsi || ''}" placeholder="e.g. 993001002">
|
||||
</div>`;
|
||||
|
||||
return `
|
||||
<div class="modal-section-label">GENERAL</div>
|
||||
|
||||
@@ -327,12 +362,56 @@ function buildEditForm(p) {
|
||||
value="${p.radio_borneo_m ?? 10}">
|
||||
</div>
|
||||
|
||||
<div class="modal-section-label">ALERT THRESHOLDS</div>
|
||||
<div style="font-size:0.68rem;color:var(--text-muted);margin-bottom:6px">
|
||||
Leave blank to use global settings. Override per buoy based on anchor chain length and operating area.
|
||||
</div>
|
||||
<div class="field-row-modal">
|
||||
<div class="form-field">
|
||||
<label class="form-label">Drift warn (m)</label>
|
||||
<input class="form-input" id="ef-drift-warn" type="number" step="1" min="1"
|
||||
value="${p.displacement_warn_m ?? ''}" placeholder="global">
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">Drift alarm (m)</label>
|
||||
<input class="form-input" id="ef-drift-alarm" type="number" step="1" min="1"
|
||||
value="${p.displacement_alarm_m ?? ''}" placeholder="global">
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">Signal loss (min)</label>
|
||||
<input class="form-input" id="ef-signal-loss" type="number" step="1" min="1"
|
||||
value="${p.signal_loss_min ?? ''}" placeholder="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-row-modal">
|
||||
<div class="form-field">
|
||||
<label class="form-label">Digital IN3 function</label>
|
||||
<select class="form-input-select" id="ef-din3">
|
||||
<option value="">— Not connected —</option>
|
||||
<option value="WATER_INGRESS_WARN" ${p.din3_function==='WATER_INGRESS_WARN' ?'selected':''}>Water ingress (warning)</option>
|
||||
<option value="WATER_INGRESS_CRITICAL" ${p.din3_function==='WATER_INGRESS_CRITICAL' ?'selected':''}>Water ingress (critical)</option>
|
||||
<option value="LISTING" ${p.din3_function==='LISTING' ?'selected':''}>Listing / tilt sensor</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label class="form-label">Digital IN4 function</label>
|
||||
<select class="form-input-select" id="ef-din4">
|
||||
<option value="">— Not connected —</option>
|
||||
<option value="WATER_INGRESS_WARN" ${p.din4_function==='WATER_INGRESS_WARN' ?'selected':''}>Water ingress (warning)</option>
|
||||
<option value="WATER_INGRESS_CRITICAL" ${p.din4_function==='WATER_INGRESS_CRITICAL' ?'selected':''}>Water ingress (critical)</option>
|
||||
<option value="LISTING" ${p.din4_function==='LISTING' ?'selected':''}>Listing / tilt sensor</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-label">Observations</label>
|
||||
<textarea class="form-textarea" id="ef-obs"
|
||||
style="height:64px">${p.observaciones || ''}</textarea>
|
||||
</div>
|
||||
|
||||
${aisBlock}
|
||||
|
||||
${nominalBlock}
|
||||
|
||||
<div class="modal-section-label">AUDIT</div>
|
||||
|
||||
Reference in New Issue
Block a user