Skip to content

Commit 0aa1c3a

Browse files
committed
add flatpickr to handle datetime selection for better control
1 parent 5aae2ca commit 0aa1c3a

5 files changed

Lines changed: 93 additions & 36 deletions

File tree

gateway/sds_gateway/static/js/actions/DownloadActionManager.js

Lines changed: 66 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,35 +49,32 @@ function formatUtcRange(startEpochSec, startMs, endMs) {
4949
return fmt(startDate) + " - " + fmt(endDate) + " (UTC)";
5050
}
5151

52-
/** Format ms from capture start as datetime-local value (local time). */
53-
function msToDatetimeLocal(captureStartEpochSec, ms) {
52+
/** Format ms from capture start as UTC string for display (Y-m-d H:i:s). */
53+
function msToUtcString(captureStartEpochSec, ms) {
5454
if (!Number.isFinite(captureStartEpochSec) || !Number.isFinite(ms)) return "";
5555
const d = new Date(captureStartEpochSec * 1000 + ms);
5656
const pad2 = (x) => String(x).padStart(2, "0");
57-
const pad3 = (x) => String(x).padStart(3, "0");
5857
return (
59-
d.getFullYear() +
58+
d.getUTCFullYear() +
6059
"-" +
61-
pad2(d.getMonth() + 1) +
60+
pad2(d.getUTCMonth() + 1) +
6261
"-" +
63-
pad2(d.getDate()) +
64-
"T" +
65-
pad2(d.getHours()) +
62+
pad2(d.getUTCDate()) +
63+
" " +
64+
pad2(d.getUTCHours()) +
6665
":" +
67-
pad2(d.getMinutes()) +
66+
pad2(d.getUTCMinutes()) +
6867
":" +
69-
pad2(d.getSeconds()) +
70-
"." +
71-
pad3(d.getMilliseconds())
68+
pad2(d.getUTCSeconds())
7269
);
7370
}
7471

75-
/** Parse datetime-local value to ms from capture start (UTC epoch sec). */
76-
function datetimeLocalToMs(captureStartEpochSec, valueStr) {
77-
if (!Number.isFinite(captureStartEpochSec) || !valueStr || !valueStr.trim()) return NaN;
78-
const d = new Date(valueStr.trim());
79-
if (Number.isNaN(d.getTime())) return NaN;
80-
return d.getTime() - captureStartEpochSec * 1000;
72+
/** Parse UTC date string (Y-m-d H:i:s or Y-m-d H:i) to epoch ms. */
73+
function parseUtcStringToEpochMs(str) {
74+
if (!str || !str.trim()) return NaN;
75+
const s = str.trim();
76+
const d = new Date(s.endsWith("Z") ? s : s.replace(" ", "T") + "Z");
77+
return Number.isFinite(d.getTime()) ? d.getTime() : NaN;
8178
}
8279

8380
class DownloadActionManager {
@@ -411,6 +408,33 @@ class DownloadActionManager {
411408
endDateTimeEntry.disabled = !hasEpoch;
412409
}
413410
if (durationMs <= 0) return;
411+
var fpStart = null, fpEnd = null;
412+
var epochStart = captureStartEpochSec * 1000;
413+
var epochEnd = epochStart + durationMs;
414+
if (hasEpoch && typeof flatpickr !== 'undefined' && startDateTimeEntry && endDateTimeEntry) {
415+
var fpOpts = {
416+
enableTime: true,
417+
enableSeconds: true,
418+
utc: true,
419+
dateFormat: 'Y-m-d H:i:S',
420+
time_24hr: true,
421+
minDate: epochStart,
422+
maxDate: epochEnd,
423+
allowInput: true,
424+
static: true,
425+
appendTo: webDownloadModal || undefined,
426+
};
427+
flatpickr(startDateTimeEntry, Object.assign({}, fpOpts, {
428+
onChange: function() { syncFromDateTimeEntries(); }
429+
}));
430+
flatpickr(endDateTimeEntry, Object.assign({}, fpOpts, {
431+
onChange: function() { syncFromDateTimeEntries(); }
432+
}));
433+
fpStart = startDateTimeEntry._flatpickr;
434+
fpEnd = endDateTimeEntry._flatpickr;
435+
startDateTimeEntry.disabled = false;
436+
endDateTimeEntry.disabled = false;
437+
}
414438
noUiSlider.create(sliderEl, {
415439
start: [0, durationMs],
416440
connect: true,
@@ -444,8 +468,10 @@ class DownloadActionManager {
444468
if (startTimeEntry) startTimeEntry.value = String(Math.round(startMs));
445469
if (endTimeEntry) endTimeEntry.value = String(Math.round(endMs));
446470
if (hasEpoch) {
447-
if (startDateTimeEntry) startDateTimeEntry.value = msToDatetimeLocal(captureStartEpochSec, startMs);
448-
if (endDateTimeEntry) endDateTimeEntry.value = msToDatetimeLocal(captureStartEpochSec, endMs);
471+
if (fpStart && typeof fpStart.setDate === 'function') fpStart.setDate(epochStart + startMs);
472+
else if (startDateTimeEntry) startDateTimeEntry.value = msToUtcString(captureStartEpochSec, startMs);
473+
if (fpEnd && typeof fpEnd.setDate === 'function') fpEnd.setDate(epochStart + endMs);
474+
else if (endDateTimeEntry) endDateTimeEntry.value = msToUtcString(captureStartEpochSec, endMs);
449475
}
450476
});
451477
if (rangeLabel) {
@@ -471,10 +497,11 @@ class DownloadActionManager {
471497
if (startTimeEntry) startTimeEntry.value = startVal;
472498
if (endTimeEntry) endTimeEntry.value = endVal;
473499
if (hasEpoch && startDateTimeEntry && endDateTimeEntry) {
474-
startDateTimeEntry.value = msToDatetimeLocal(captureStartEpochSec, 0);
475-
endDateTimeEntry.value = msToDatetimeLocal(captureStartEpochSec, durationMs);
476-
startDateTimeEntry.disabled = false;
477-
endDateTimeEntry.disabled = false;
500+
if (fpStart && typeof fpStart.setDate === 'function') fpStart.setDate(epochStart);
501+
else startDateTimeEntry.value = msToUtcString(captureStartEpochSec, 0);
502+
if (fpEnd && typeof fpEnd.setDate === 'function') fpEnd.setDate(epochEnd);
503+
else endDateTimeEntry.value = msToUtcString(captureStartEpochSec, durationMs);
504+
if (!fpStart) { startDateTimeEntry.disabled = false; endDateTimeEntry.disabled = false; }
478505
}
479506

480507
function syncSliderFromEntries() {
@@ -492,18 +519,28 @@ class DownloadActionManager {
492519
}
493520
function syncFromDateTimeEntries() {
494521
if (!hasEpoch || !sliderEl.noUiSlider || !startDateTimeEntry || !endDateTimeEntry) return;
495-
var startMs = datetimeLocalToMs(captureStartEpochSec, startDateTimeEntry.value);
496-
var endMs = datetimeLocalToMs(captureStartEpochSec, endDateTimeEntry.value);
522+
var startMs, endMs;
523+
if (startDateTimeEntry._flatpickr && endDateTimeEntry._flatpickr) {
524+
var dStart = startDateTimeEntry._flatpickr.selectedDates[0];
525+
var dEnd = endDateTimeEntry._flatpickr.selectedDates[0];
526+
startMs = dStart ? dStart.getTime() - epochStart : 0;
527+
endMs = dEnd ? dEnd.getTime() - epochStart : durationMs;
528+
} else {
529+
startMs = parseUtcStringToEpochMs(startDateTimeEntry.value) - epochStart;
530+
endMs = parseUtcStringToEpochMs(endDateTimeEntry.value) - epochStart;
531+
}
497532
if (Number.isNaN(startMs) || Number.isNaN(endMs)) return;
498533
startMs = Math.max(0, Math.min(startMs, durationMs));
499534
endMs = Math.max(0, Math.min(endMs, durationMs));
500535
if (startMs >= endMs) endMs = Math.min(startMs + fileCadenceMs, durationMs);
536+
var cur = sliderEl.noUiSlider.get();
537+
if (Math.round(Number(cur[0])) === Math.round(startMs) && Math.round(Number(cur[1])) === Math.round(endMs)) return;
501538
sliderEl.noUiSlider.set([startMs, endMs]);
502539
}
503540
if (startTimeEntry) startTimeEntry.addEventListener('change', syncSliderFromEntries);
504541
if (endTimeEntry) endTimeEntry.addEventListener('change', syncSliderFromEntries);
505-
if (startDateTimeEntry) startDateTimeEntry.addEventListener('change', syncFromDateTimeEntries);
506-
if (endDateTimeEntry) endDateTimeEntry.addEventListener('change', syncFromDateTimeEntries);
542+
if (startDateTimeEntry && !startDateTimeEntry._flatpickr) startDateTimeEntry.addEventListener('change', syncFromDateTimeEntries);
543+
if (endDateTimeEntry && !endDateTimeEntry._flatpickr) endDateTimeEntry.addEventListener('change', syncFromDateTimeEntries);
507544
}
508545

509546
/**
@@ -653,7 +690,6 @@ class DownloadActionManager {
653690
const dataFilesTotalSizeRaw = button.getAttribute("data-data-files-total-size");
654691
const dataFilesTotalSize = dataFilesTotalSizeRaw !== null && dataFilesTotalSizeRaw !== '' ? parseInt(dataFilesTotalSizeRaw, 10) : NaN;
655692
const captureStartEpochSec = parseInt(button.getAttribute("data-capture-start-epoch-sec"), 10);
656-
const captureUuid = button.getAttribute("data-capture-uuid") || undefined;
657693
this.initializeCaptureDownloadSlider(
658694
Number.isNaN(durationMs) ? 0 : durationMs,
659695
Number.isNaN(fileCadenceMs) ? 1000 : fileCadenceMs,
@@ -663,7 +699,7 @@ class DownloadActionManager {
663699
dataFilesCount: Number.isNaN(dataFilesCount) ? 0 : dataFilesCount,
664700
totalFilesCount: Number.isNaN(totalFilesCount) ? 0 : totalFilesCount,
665701
dataFilesTotalSize: Number.isNaN(dataFilesTotalSize) ? undefined : dataFilesTotalSize,
666-
captureUuid: captureUuid,
702+
captureUuid: captureUuid || undefined,
667703
captureStartEpochSec: Number.isNaN(captureStartEpochSec) ? undefined : captureStartEpochSec,
668704
},
669705
);

gateway/sds_gateway/static/js/file-list.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,24 @@ class FileListCapturesTableManager extends CapturesTableManager {
575575
this.searchButtonLoading = document.getElementById("search-btn-loading");
576576
}
577577

578+
/**
579+
* Use web download modal (with temporal slider) when DownloadActionManager is available.
580+
*/
581+
handleDownloadCapture(button) {
582+
if (window.currentDownloadManager && document.getElementById("webDownloadModal")) {
583+
const captureUuid = button.getAttribute("data-capture-uuid");
584+
const captureName = button.getAttribute("data-capture-name") || captureUuid;
585+
if (captureUuid) {
586+
window.currentDownloadManager.handleCaptureDownload(
587+
captureUuid,
588+
captureName,
589+
button,
590+
);
591+
}
592+
return;
593+
}
594+
}
595+
578596
/**
579597
* Override showLoading to toggle button contents instead of showing separate indicator
580598
*/

gateway/sds_gateway/templates/base.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
2222
<link rel="stylesheet"
2323
href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/15.7.1/nouislider.min.css" />
24+
<link rel="stylesheet"
25+
href="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.css" />
2426
{% block css %}
2527
<!-- Your stuff: Third-party CSS libraries go here -->
2628
<!-- This file stores project-specific CSS -->
@@ -44,6 +46,7 @@
4446
<script src="{% static 'js/core/PermissionsManager.js' %}"></script>
4547
<script src="{% static 'js/core/DOMUtils.js' %}"></script>
4648
<script src="{% static 'js/core/PageLifecycleManager.js' %}"></script>
49+
<script src="https://cdn.jsdelivr.net/npm/flatpickr@4.6.13/dist/flatpickr.min.js"></script>
4750
{% endblock javascript %}
4851
</head>
4952
{# djlint:off H021 #}

gateway/sds_gateway/templates/users/file_list.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,11 @@ <h5 class="modal-title" id="uploadResultModalLabel">Upload Result</h5>
527527
}
528528
};
529529

530-
// Initialize managers for captures
531-
const permissionsManager = new PermissionsManager(pageConfig.permissions);
530+
// Initialize managers for captures (use window.* — classes are attached by module/scripts)
531+
const permissionsManager = new window.PermissionsManager(pageConfig.permissions);
532532

533533
// Initialize download manager
534-
const downloadManager = new DownloadActionManager({
534+
const downloadManager = new window.DownloadActionManager({
535535
permissions: permissionsManager
536536
});
537537

gateway/sds_gateway/templates/users/partials/web_download_modal.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
tabindex="-1"
55
aria-labelledby="webDownloadModalLabel-{{ item.uuid }}"
66
aria-hidden="true">
7-
<div class="modal-dialog">
7+
<div class="modal-dialog modal-lg">
88
<div class="modal-content">
99
<div class="modal-header">
1010
<h5 class="modal-title" id="webDownloadModalLabel-{{ item.uuid }}">
@@ -55,11 +55,11 @@ <h5 class="modal-title" id="webDownloadModalLabel-{{ item.uuid }}">
5555
<div class="row">
5656
<div class="col-6">
5757
<label for="startDateTimeEntry">Start (date/time UTC)</label>
58-
<input type="datetime-local" id="startDateTimeEntry" class="form-control" step="0.001" />
58+
<input type="text" id="startDateTimeEntry" class="form-control" placeholder="YYYY-MM-DD HH:mm:ss" />
5959
</div>
6060
<div class="col-6">
6161
<label for="endDateTimeEntry">End (date/time UTC)</label>
62-
<input type="datetime-local" id="endDateTimeEntry" class="form-control" step="0.001" />
62+
<input type="text" id="endDateTimeEntry" class="form-control" placeholder="YYYY-MM-DD HH:mm:ss" />
6363
</div>
6464
</div>
6565
<p class="small text-muted mt-1 mb-0">Valid range: <span id="temporalRangeHint">0 – — ms</span> (same values as the slider above)</p>

0 commit comments

Comments
 (0)