mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-11 18:28:45 +02:00
Add files via upload
This commit is contained in:
6 files changed
+1546
-141
No files matched your search
@@ -0,0 +1,428 @@
|
||||
/**
|
||||
* Audit log datetime picker — cross-browser, locale-aware (SLS-style calendar + time columns).
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var registry = {};
|
||||
var popover = null;
|
||||
var activeFieldId = null;
|
||||
var draft = null;
|
||||
var viewYear = 0;
|
||||
var viewMonth = 0;
|
||||
|
||||
function pad2(n) {
|
||||
return String(n).padStart(2, '0');
|
||||
}
|
||||
|
||||
function pickerLocale() {
|
||||
if (typeof auditLocale === 'function') return auditLocale();
|
||||
if (typeof window.__locale === 'string' && window.__locale.startsWith('zh')) return 'zh-CN';
|
||||
return 'en-US';
|
||||
}
|
||||
|
||||
function pickerT(key, fallback) {
|
||||
if (typeof auditT === 'function') return auditT(key, null, fallback);
|
||||
if (typeof t === 'function') {
|
||||
var v = t(key);
|
||||
if (v && v !== key) return v;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function partsToStorage(p) {
|
||||
if (!p) return '';
|
||||
return p.y + '-' + pad2(p.m) + '-' + pad2(p.d) + 'T' + pad2(p.h) + ':' + pad2(p.mi);
|
||||
}
|
||||
|
||||
function parseStorage(value) {
|
||||
if (!value) return null;
|
||||
var m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(String(value).trim());
|
||||
if (!m) return null;
|
||||
return { y: +m[1], m: +m[2], d: +m[3], h: +m[4], mi: +m[5] };
|
||||
}
|
||||
|
||||
function formatDisplay(parts) {
|
||||
if (!parts) return '';
|
||||
var loc = pickerLocale();
|
||||
try {
|
||||
var d = new Date(parts.y, parts.m - 1, parts.d, parts.h, parts.mi, 0, 0);
|
||||
return d.toLocaleString(loc, {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
} catch (_) {
|
||||
return partsToStorage(parts).replace('T', ' ');
|
||||
}
|
||||
}
|
||||
|
||||
function nowParts() {
|
||||
var n = new Date();
|
||||
return { y: n.getFullYear(), m: n.getMonth() + 1, d: n.getDate(), h: n.getHours(), mi: n.getMinutes() };
|
||||
}
|
||||
|
||||
function startOfTodayParts() {
|
||||
var n = new Date();
|
||||
return { y: n.getFullYear(), m: n.getMonth() + 1, d: n.getDate(), h: 0, mi: 0 };
|
||||
}
|
||||
|
||||
function monthTitle(year, month) {
|
||||
var loc = pickerLocale();
|
||||
if (loc.startsWith('zh')) {
|
||||
return year + '\u5e74' + pad2(month) + '\u6708';
|
||||
}
|
||||
try {
|
||||
return new Date(year, month - 1, 1).toLocaleString(loc, { month: 'long', year: 'numeric' });
|
||||
} catch (_) {
|
||||
return year + '-' + pad2(month);
|
||||
}
|
||||
}
|
||||
|
||||
function weekdayHeaders() {
|
||||
var loc = pickerLocale();
|
||||
if (loc.startsWith('zh')) {
|
||||
return ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'];
|
||||
}
|
||||
return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
||||
}
|
||||
|
||||
function buildMonthGrid(year, month) {
|
||||
var first = new Date(year, month - 1, 1);
|
||||
var start = new Date(first);
|
||||
start.setDate(first.getDate() - first.getDay());
|
||||
var cells = [];
|
||||
var cursor = new Date(start);
|
||||
for (var i = 0; i < 42; i++) {
|
||||
cells.push({
|
||||
y: cursor.getFullYear(),
|
||||
m: cursor.getMonth() + 1,
|
||||
d: cursor.getDate(),
|
||||
inMonth: cursor.getMonth() === month - 1
|
||||
});
|
||||
cursor.setDate(cursor.getDate() + 1);
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
function ensurePopover() {
|
||||
if (popover) return popover;
|
||||
popover = document.createElement('div');
|
||||
popover.className = 'audit-dt-popover';
|
||||
popover.hidden = true;
|
||||
popover.setAttribute('role', 'dialog');
|
||||
popover.innerHTML =
|
||||
'<div class="audit-dt-popover-inner">' +
|
||||
'<div class="audit-dt-head">' +
|
||||
'<button type="button" class="audit-dt-nav" data-nav="prev" aria-label="prev">‹</button>' +
|
||||
'<span class="audit-dt-month-label"></span>' +
|
||||
'<button type="button" class="audit-dt-nav" data-nav="next" aria-label="next">›</button>' +
|
||||
'</div>' +
|
||||
'<div class="audit-dt-body">' +
|
||||
'<div class="audit-dt-calendar"></div>' +
|
||||
'<div class="audit-dt-time">' +
|
||||
'<div class="audit-dt-time-col" data-part="hour">' +
|
||||
'<span class="audit-dt-time-label audit-dt-hour-label"></span>' +
|
||||
'<div class="audit-dt-time-list"></div>' +
|
||||
'</div>' +
|
||||
'<div class="audit-dt-time-col" data-part="minute">' +
|
||||
'<span class="audit-dt-time-label audit-dt-minute-label"></span>' +
|
||||
'<div class="audit-dt-time-list"></div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="audit-dt-footer">' +
|
||||
'<button type="button" class="audit-dt-footer-btn" data-action="clear"></button>' +
|
||||
'<button type="button" class="audit-dt-footer-btn" data-action="today"></button>' +
|
||||
'<button type="button" class="audit-dt-footer-btn audit-dt-footer-btn--primary" data-action="confirm"></button>' +
|
||||
'</div>' +
|
||||
'</div>';
|
||||
document.body.appendChild(popover);
|
||||
|
||||
popover.addEventListener('click', function (ev) {
|
||||
ev.stopPropagation();
|
||||
var btn = ev.target.closest('[data-nav]');
|
||||
if (btn) {
|
||||
if (btn.getAttribute('data-nav') === 'prev') {
|
||||
viewMonth -= 1;
|
||||
if (viewMonth < 1) { viewMonth = 12; viewYear -= 1; }
|
||||
} else {
|
||||
viewMonth += 1;
|
||||
if (viewMonth > 12) { viewMonth = 1; viewYear += 1; }
|
||||
}
|
||||
renderPopover();
|
||||
return;
|
||||
}
|
||||
var dayBtn = ev.target.closest('[data-day]');
|
||||
if (dayBtn && draft) {
|
||||
draft.y = +dayBtn.getAttribute('data-y');
|
||||
draft.m = +dayBtn.getAttribute('data-m');
|
||||
draft.d = +dayBtn.getAttribute('data-d');
|
||||
if (draft.y !== viewYear || draft.m !== viewMonth) {
|
||||
viewYear = draft.y;
|
||||
viewMonth = draft.m;
|
||||
renderCalendar();
|
||||
} else {
|
||||
updateDaySelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
var timeBtn = ev.target.closest('[data-time]');
|
||||
if (timeBtn && draft) {
|
||||
var part = timeBtn.getAttribute('data-part');
|
||||
var val = +timeBtn.getAttribute('data-time');
|
||||
if (part === 'hour') draft.h = val;
|
||||
if (part === 'minute') draft.mi = val;
|
||||
updateTimeSelection();
|
||||
return;
|
||||
}
|
||||
var actionBtn = ev.target.closest('[data-action]');
|
||||
if (!actionBtn) return;
|
||||
var action = actionBtn.getAttribute('data-action');
|
||||
if (action === 'clear') {
|
||||
applyValue(activeFieldId, '');
|
||||
closePopover();
|
||||
} else if (action === 'today') {
|
||||
if (draft) {
|
||||
var t = nowParts();
|
||||
draft.y = t.y; draft.m = t.m; draft.d = t.d;
|
||||
viewYear = t.y; viewMonth = t.m;
|
||||
}
|
||||
renderPopover();
|
||||
} else if (action === 'confirm') {
|
||||
applyValue(activeFieldId, partsToStorage(draft));
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', onDocumentClick);
|
||||
document.addEventListener('keydown', onDocumentKeydown);
|
||||
document.addEventListener('languagechange', function () {
|
||||
if (!popover.hidden) renderPopover();
|
||||
refreshAllDisplays();
|
||||
});
|
||||
|
||||
return popover;
|
||||
}
|
||||
|
||||
function onDocumentClick(ev) {
|
||||
if (!popover || popover.hidden) return;
|
||||
if (popover.contains(ev.target)) return;
|
||||
if (activeFieldId && registry[activeFieldId] && registry[activeFieldId].wrap.contains(ev.target)) return;
|
||||
closePopover();
|
||||
}
|
||||
|
||||
function onDocumentKeydown(ev) {
|
||||
if (ev.key === 'Escape' && popover && !popover.hidden) {
|
||||
closePopover();
|
||||
}
|
||||
}
|
||||
|
||||
function positionPopover(fieldWrap) {
|
||||
var rect = fieldWrap.getBoundingClientRect();
|
||||
var width = 320;
|
||||
popover.style.width = width + 'px';
|
||||
var left = rect.left;
|
||||
if (left + width > window.innerWidth - 12) {
|
||||
left = Math.max(12, window.innerWidth - width - 12);
|
||||
}
|
||||
popover.style.left = left + 'px';
|
||||
var top = rect.bottom + 6;
|
||||
if (top + 340 > window.innerHeight - 12) {
|
||||
top = Math.max(12, rect.top - 340 - 6);
|
||||
}
|
||||
popover.style.top = top + 'px';
|
||||
}
|
||||
|
||||
function renderCalendar() {
|
||||
if (!popover || !draft) return;
|
||||
popover.querySelector('.audit-dt-month-label').textContent = monthTitle(viewYear, viewMonth);
|
||||
var cal = popover.querySelector('.audit-dt-calendar');
|
||||
var headers = weekdayHeaders();
|
||||
var html = '<div class="audit-dt-weekdays">';
|
||||
headers.forEach(function (h) { html += '<span>' + h + '</span>'; });
|
||||
html += '</div><div class="audit-dt-days">';
|
||||
buildMonthGrid(viewYear, viewMonth).forEach(function (cell) {
|
||||
var cls = 'audit-dt-day';
|
||||
if (!cell.inMonth) cls += ' is-other-month';
|
||||
if (draft && cell.y === draft.y && cell.m === draft.m && cell.d === draft.d) cls += ' is-selected';
|
||||
html += '<button type="button" class="' + cls + '" data-day="1" data-y="' + cell.y +
|
||||
'" data-m="' + cell.m + '" data-d="' + cell.d + '">' + cell.d + '</button>';
|
||||
});
|
||||
html += '</div>';
|
||||
cal.innerHTML = html;
|
||||
}
|
||||
|
||||
function renderTimeLists() {
|
||||
if (!popover || !draft) return;
|
||||
var hourList = popover.querySelector('[data-part="hour"] .audit-dt-time-list');
|
||||
var minuteList = popover.querySelector('[data-part="minute"] .audit-dt-time-list');
|
||||
var hourHtml = '';
|
||||
var minuteHtml = '';
|
||||
var h;
|
||||
for (h = 0; h < 24; h++) {
|
||||
hourHtml += '<button type="button" class="audit-dt-time-item' + (draft && draft.h === h ? ' is-selected' : '') +
|
||||
'" data-part="hour" data-time="' + h + '">' + pad2(h) + '</button>';
|
||||
}
|
||||
for (h = 0; h < 60; h++) {
|
||||
minuteHtml += '<button type="button" class="audit-dt-time-item' + (draft && draft.mi === h ? ' is-selected' : '') +
|
||||
'" data-part="minute" data-time="' + h + '">' + pad2(h) + '</button>';
|
||||
}
|
||||
hourList.innerHTML = hourHtml;
|
||||
minuteList.innerHTML = minuteHtml;
|
||||
scrollTimeSelection(hourList, draft.h);
|
||||
scrollTimeSelection(minuteList, draft.mi);
|
||||
}
|
||||
|
||||
function updateDaySelection() {
|
||||
if (!popover || !draft) return;
|
||||
popover.querySelectorAll('.audit-dt-day').forEach(function (btn) {
|
||||
var selected = +btn.getAttribute('data-y') === draft.y &&
|
||||
+btn.getAttribute('data-m') === draft.m &&
|
||||
+btn.getAttribute('data-d') === draft.d;
|
||||
btn.classList.toggle('is-selected', selected);
|
||||
});
|
||||
}
|
||||
|
||||
function updateTimeSelection() {
|
||||
if (!popover || !draft) return;
|
||||
var hourList = popover.querySelector('[data-part="hour"] .audit-dt-time-list');
|
||||
var minuteList = popover.querySelector('[data-part="minute"] .audit-dt-time-list');
|
||||
if (!hourList || !minuteList || !hourList.children.length) {
|
||||
renderTimeLists();
|
||||
return;
|
||||
}
|
||||
hourList.querySelectorAll('.audit-dt-time-item').forEach(function (btn) {
|
||||
btn.classList.toggle('is-selected', +btn.getAttribute('data-time') === draft.h);
|
||||
});
|
||||
minuteList.querySelectorAll('.audit-dt-time-item').forEach(function (btn) {
|
||||
btn.classList.toggle('is-selected', +btn.getAttribute('data-time') === draft.mi);
|
||||
});
|
||||
scrollTimeSelection(hourList, draft.h);
|
||||
scrollTimeSelection(minuteList, draft.mi);
|
||||
}
|
||||
|
||||
function renderPopover() {
|
||||
if (!popover || !draft) return;
|
||||
popover.querySelector('.audit-dt-hour-label').textContent = pickerT('settingsAudit.pickerHour', 'Hour');
|
||||
popover.querySelector('.audit-dt-minute-label').textContent = pickerT('settingsAudit.pickerMinute', 'Min');
|
||||
popover.querySelector('[data-action="clear"]').textContent = pickerT('settingsAudit.pickerClear', 'Clear');
|
||||
popover.querySelector('[data-action="today"]').textContent = pickerT('settingsAudit.pickerToday', 'Today');
|
||||
popover.querySelector('[data-action="confirm"]').textContent = pickerT('settingsAudit.pickerConfirm', 'OK');
|
||||
renderCalendar();
|
||||
renderTimeLists();
|
||||
}
|
||||
|
||||
function scrollTimeSelection(listEl, value) {
|
||||
var sel = listEl.querySelector('.is-selected');
|
||||
if (sel && sel.scrollIntoView) {
|
||||
sel.scrollIntoView({ block: 'center' });
|
||||
}
|
||||
}
|
||||
|
||||
function openPopover(fieldId) {
|
||||
ensurePopover();
|
||||
var entry = registry[fieldId];
|
||||
if (!entry) return;
|
||||
activeFieldId = fieldId;
|
||||
var stored = entry.wrap.dataset.value || '';
|
||||
draft = parseStorage(stored) || nowParts();
|
||||
viewYear = draft.y;
|
||||
viewMonth = draft.m;
|
||||
renderPopover();
|
||||
positionPopover(entry.wrap);
|
||||
popover.hidden = false;
|
||||
}
|
||||
|
||||
function closePopover() {
|
||||
if (!popover) return;
|
||||
popover.hidden = true;
|
||||
activeFieldId = null;
|
||||
draft = null;
|
||||
}
|
||||
|
||||
function refreshDisplay(fieldId) {
|
||||
var entry = registry[fieldId];
|
||||
if (!entry) return;
|
||||
var parts = parseStorage(entry.wrap.dataset.value || '');
|
||||
entry.input.value = parts ? formatDisplay(parts) : '';
|
||||
entry.input.placeholder = pickerT('settingsAudit.datetimePlaceholder', 'Select date & time');
|
||||
entry.clearBtn.hidden = !parts;
|
||||
}
|
||||
|
||||
function refreshAllDisplays() {
|
||||
Object.keys(registry).forEach(refreshDisplay);
|
||||
}
|
||||
|
||||
function applyValue(fieldId, storageValue) {
|
||||
var entry = registry[fieldId];
|
||||
if (!entry) return;
|
||||
entry.wrap.dataset.value = storageValue || '';
|
||||
refreshDisplay(fieldId);
|
||||
}
|
||||
|
||||
function bindField(fieldId) {
|
||||
var wrap = document.getElementById(fieldId);
|
||||
if (!wrap || wrap.dataset.auditDtBound === '1') return;
|
||||
var input = wrap.querySelector('.audit-datetime-input');
|
||||
var openBtn = wrap.querySelector('.audit-datetime-open-btn');
|
||||
var clearBtn = wrap.querySelector('.audit-datetime-clear-btn');
|
||||
if (!input || !openBtn || !clearBtn) return;
|
||||
|
||||
wrap.dataset.auditDtBound = '1';
|
||||
registry[fieldId] = { wrap: wrap, input: input, clearBtn: clearBtn };
|
||||
|
||||
openBtn.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
if (!popover || popover.hidden || activeFieldId !== fieldId) {
|
||||
openPopover(fieldId);
|
||||
} else {
|
||||
closePopover();
|
||||
}
|
||||
});
|
||||
input.addEventListener('click', function (ev) {
|
||||
ev.stopPropagation();
|
||||
openPopover(fieldId);
|
||||
});
|
||||
clearBtn.addEventListener('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
applyValue(fieldId, '');
|
||||
});
|
||||
refreshDisplay(fieldId);
|
||||
}
|
||||
|
||||
window.AuditDatetimePicker = {
|
||||
init: function () {
|
||||
bindField('audit-filter-since-field');
|
||||
bindField('audit-filter-until-field');
|
||||
refreshAllDisplays();
|
||||
},
|
||||
getValue: function (inputId) {
|
||||
var fieldId = inputId === 'audit-filter-since' ? 'audit-filter-since-field' : 'audit-filter-until-field';
|
||||
var entry = registry[fieldId];
|
||||
return entry ? (entry.wrap.dataset.value || '') : '';
|
||||
},
|
||||
setValue: function (inputId, dateObj) {
|
||||
if (!dateObj || Number.isNaN(dateObj.getTime())) return;
|
||||
var fieldId = inputId === 'audit-filter-since' ? 'audit-filter-since-field' : 'audit-filter-until-field';
|
||||
var p = {
|
||||
y: dateObj.getFullYear(),
|
||||
m: dateObj.getMonth() + 1,
|
||||
d: dateObj.getDate(),
|
||||
h: dateObj.getHours(),
|
||||
mi: dateObj.getMinutes()
|
||||
};
|
||||
applyValue(fieldId, partsToStorage(p));
|
||||
},
|
||||
clearAll: function () {
|
||||
applyValue('audit-filter-since-field', '');
|
||||
applyValue('audit-filter-until-field', '');
|
||||
closePopover();
|
||||
}
|
||||
};
|
||||
})();
|
||||
Reference in new issue
Block a user