poprawki i optymalizacje kodu
This commit is contained in:
@@ -208,4 +208,138 @@ body {
|
||||
|
||||
.border-dotted {
|
||||
border-style: dotted !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal własnego zakresu */
|
||||
#customRangeModal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--card-bg);
|
||||
padding: 25px;
|
||||
border-radius: 12px;
|
||||
max-width: 420px;
|
||||
width: 90%;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.modal-content h3 {
|
||||
margin-top: 0;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 20px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.modal-form-group:last-of-type {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.modal-label {
|
||||
display: block;
|
||||
color: #8b949e;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.modal-input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: var(--bg-dark);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-main);
|
||||
font-size: 14px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--blue-accent);
|
||||
}
|
||||
|
||||
.modal-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.modal-btn {
|
||||
padding: 8px 20px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-btn-cancel {
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.modal-btn-cancel:hover {
|
||||
background: rgba(48, 54, 61, 0.5);
|
||||
}
|
||||
|
||||
.modal-btn-apply {
|
||||
background: #1f6feb;
|
||||
border: 1px solid #1f6feb;
|
||||
color: #ffffff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.modal-btn-apply:hover {
|
||||
background: #1a5acc;
|
||||
}
|
||||
|
||||
.modal-input::-webkit-calendar-picker-indicator {
|
||||
filter: invert(1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Badge zakresu wykresu */
|
||||
.chart-range-badge {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: rgba(22, 27, 34, 0.9);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
font-size: 0.7rem;
|
||||
color: #8b949e;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
backdrop-filter: blur(4px);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.chart-range-badge {
|
||||
font-size: 0.65rem;
|
||||
padding: 3px 8px;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
20
static/js/gauge.js
Normal file
20
static/js/gauge.js
Normal file
@@ -0,0 +1,20 @@
|
||||
function setupGauges() {
|
||||
const config = {
|
||||
type: 'doughnut',
|
||||
data: { datasets: [{ data: [0, 100], backgroundColor: ['#198754', '#1a1d20'], borderWidth: 0, circumference: 180, rotation: 270 }] },
|
||||
options: { responsive: true, maintainAspectRatio: true, cutout: '75%', plugins: { legend: { display: false }, tooltip: { enabled: false } } }
|
||||
};
|
||||
Object.keys(phasesConfig).forEach(id => {
|
||||
const canvas = document.getElementById('gauge' + id);
|
||||
if (canvas) gauges[id] = new Chart(canvas, JSON.parse(JSON.stringify(config)));
|
||||
});
|
||||
}
|
||||
|
||||
function updateGaugeUI(id, val) {
|
||||
if (!gauges[id]) return;
|
||||
const pct = Math.max(0, Math.min(100, ((val - 190) / 80) * 100));
|
||||
let color = (val < THRESHOLDS.min || val > THRESHOLDS.max) ? '#dc3545' : ((val < 212 || val > 248) ? '#ffc107' : '#198754');
|
||||
gauges[id].data.datasets[0].data = [pct, 100 - pct];
|
||||
gauges[id].data.datasets[0].backgroundColor = [color, '#1a1d20'];
|
||||
gauges[id].update('none');
|
||||
}
|
||||
96
static/js/modal.js
Normal file
96
static/js/modal.js
Normal file
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Otwiera modal z wyborem własnego zakresu dat
|
||||
*/
|
||||
function openCustomRangePicker() {
|
||||
const modal = document.getElementById('customRangeModal');
|
||||
if (!modal) return;
|
||||
|
||||
// Ustaw domyślne wartości - od 24h temu do teraz
|
||||
const now = new Date();
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
|
||||
document.getElementById('customStartDate').value = formatDateTimeLocal(yesterday);
|
||||
document.getElementById('customEndDate').value = formatDateTimeLocal(now);
|
||||
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
/**
|
||||
* Zamyka modal z wyborem zakresu
|
||||
*/
|
||||
function closeCustomRangePicker() {
|
||||
const modal = document.getElementById('customRangeModal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Stosuje wybrany własny zakres
|
||||
*/
|
||||
async function applyCustomRange() {
|
||||
const startInput = document.getElementById('customStartDate');
|
||||
const endInput = document.getElementById('customEndDate');
|
||||
|
||||
if (!startInput.value || !endInput.value) {
|
||||
alert('Wybierz obie daty');
|
||||
return;
|
||||
}
|
||||
|
||||
const startDate = new Date(startInput.value);
|
||||
const endDate = new Date(endInput.value);
|
||||
const now = new Date();
|
||||
|
||||
// Walidacja
|
||||
if (startDate >= endDate) {
|
||||
alert('Data początkowa musi być wcześniejsza niż końcowa');
|
||||
return;
|
||||
}
|
||||
|
||||
if (endDate > now) {
|
||||
alert('Data końcowa nie może być w przyszłości');
|
||||
return;
|
||||
}
|
||||
|
||||
const diffDays = (endDate - startDate) / (1000 * 60 * 60 * 24);
|
||||
if (diffDays > 30) {
|
||||
alert('Maksymalny zakres to 30 dni');
|
||||
return;
|
||||
}
|
||||
|
||||
closeCustomRangePicker();
|
||||
document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
|
||||
document.getElementById('customRangeBtn').classList.add('active');
|
||||
|
||||
currentTimeRange = 'custom';
|
||||
|
||||
const startTime = startDate.getTime();
|
||||
const endTime = endDate.getTime();
|
||||
|
||||
await reloadDataForRange(startTime, endTime);
|
||||
|
||||
if (voltageChart) {
|
||||
voltageChart.options.scales.x.min = startTime;
|
||||
voltageChart.options.scales.x.max = endTime;
|
||||
voltageChart.update('none');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatuje datę do formatu datetime-local input
|
||||
*/
|
||||
function formatDateTimeLocal(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
// Zamknij modal po kliknięciu poza nim
|
||||
document.addEventListener('click', function(event) {
|
||||
const modal = document.getElementById('customRangeModal');
|
||||
if (modal && event.target === modal) {
|
||||
closeCustomRangePicker();
|
||||
}
|
||||
});
|
||||
@@ -34,6 +34,7 @@ function initMonitor(phases, defaultRange) {
|
||||
window.changeTimeRange(currentTimeRange);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Konfiguracja wykresu głównego (Zoom + Pan)
|
||||
*/
|
||||
@@ -54,27 +55,47 @@ function setupMainChart() {
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'time',
|
||||
time: {
|
||||
displayFormats: { hour: 'HH:mm', minute: 'HH:mm' },
|
||||
tooltipFormat: 'yyyy-MM-dd HH:mm:ss'
|
||||
},
|
||||
type: 'time',
|
||||
time: {
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss.SSS',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'HH:mm',
|
||||
day: 'dd LLL',
|
||||
week: 'dd LLL',
|
||||
month: 'LLL yyyy',
|
||||
quarter: 'LLL yyyy',
|
||||
year: 'yyyy'
|
||||
},
|
||||
tooltipFormat: 'dd.MM.yyyy HH:mm:ss'
|
||||
},
|
||||
|
||||
grid: { color: '#2d3139' },
|
||||
ticks: { color: '#8b949e' }
|
||||
},
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
suggestedMin: 200,
|
||||
suggestedMax: 250,
|
||||
beginAtZero: false,
|
||||
min: 190,
|
||||
max: 255,
|
||||
grid: { color: '#2d3139' },
|
||||
ticks: { stepSize: 5, color: '#c9d1d9' }
|
||||
ticks: {
|
||||
stepSize: 5,
|
||||
color: '#c9d1d9'
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
zoom: {
|
||||
limits: {
|
||||
x: {
|
||||
rangeMax: 30 * 24 * 60 * 60 * 1000
|
||||
min: 'original',
|
||||
max: () => Date.now(),
|
||||
minRange: 60 * 1000
|
||||
},
|
||||
y: {
|
||||
min: 190,
|
||||
max: 255
|
||||
}
|
||||
},
|
||||
zoom: {
|
||||
@@ -86,15 +107,30 @@ function setupMainChart() {
|
||||
},
|
||||
mode: 'x',
|
||||
onZoomComplete: async ({chart}) => {
|
||||
const now = Date.now();
|
||||
if (chart.scales.x.max > now) {
|
||||
chart.scales.x.max = now;
|
||||
chart.update('none');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
|
||||
currentTimeRange = 'precise';
|
||||
|
||||
await reloadDataForRange(chart.scales.x.min, chart.scales.x.max);
|
||||
}
|
||||
},
|
||||
pan: {
|
||||
enabled: true,
|
||||
mode: 'x',
|
||||
mode: 'x',
|
||||
onPanComplete: async ({chart}) => {
|
||||
const now = Date.now();
|
||||
if (chart.scales.x.max > now) {
|
||||
const rangeWidth = chart.scales.x.max - chart.scales.x.min;
|
||||
chart.scales.x.max = now;
|
||||
chart.scales.x.min = now - rangeWidth;
|
||||
chart.update('none');
|
||||
}
|
||||
|
||||
document.querySelectorAll('.time-btn').forEach(b => b.classList.remove('active'));
|
||||
currentTimeRange = 'precise';
|
||||
await reloadDataForRange(chart.scales.x.min, chart.scales.x.max);
|
||||
@@ -116,7 +152,7 @@ function setupMainChart() {
|
||||
const raw = ctx.raw;
|
||||
const val = (raw && raw.realV !== undefined) ? raw.realV : ctx.parsed.y;
|
||||
|
||||
if (datasetLabel.includes('Zanik')) return `ZANIK FAZY`;
|
||||
if (datasetLabel.includes('Zanik')) return 'ZANIK FAZY';
|
||||
if (datasetLabel.includes('Powrot')) return `POWROT: ${val.toFixed(1)}V`;
|
||||
if (datasetLabel.includes('Awaria')) return null;
|
||||
return `${datasetLabel}: ${val.toFixed(1)}V`;
|
||||
@@ -126,67 +162,88 @@ function setupMainChart() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobieranie danych i gotowych eventów z backendu
|
||||
*/
|
||||
async function reloadDataForRange(min, max, rangeName = null) {
|
||||
let urlParams = rangeName
|
||||
? `range=${rangeName}`
|
||||
: `start=${new Date(min).toISOString()}&end=${new Date(max).toISOString()}`;
|
||||
const now = Date.now();
|
||||
if (max && max > now) {
|
||||
max = now;
|
||||
}
|
||||
if (min && min > now) {
|
||||
min = now - 3600000;
|
||||
}
|
||||
|
||||
let urlParams = rangeName ? `range=${rangeName}` : `start=${new Date(min).toISOString()}&end=${new Date(max).toISOString()}`;
|
||||
const newDatasets = [];
|
||||
|
||||
|
||||
for (let id of Object.keys(phasesConfig)) {
|
||||
try {
|
||||
const raw = await fetch(`/api/timeseries/${id}?${urlParams}`).then(r => r.json());
|
||||
const proc = processPhaseData(id, raw);
|
||||
|
||||
newDatasets.push(proc.lineDataset);
|
||||
if (proc.outageLine) newDatasets.push(proc.outageLine);
|
||||
if (proc.outageLine) newDatasets.push(proc.outageLine);
|
||||
if (proc.outageDataset) newDatasets.push(proc.outageDataset);
|
||||
if (proc.recoveryDataset) newDatasets.push(proc.recoveryDataset);
|
||||
} catch (e) {
|
||||
console.error("Błąd pobierania fazy " + id, e);
|
||||
} catch (e) {
|
||||
console.error("Błąd pobierania fazy " + id, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const events = await fetch(`/api/events?${urlParams}`).then(r => r.json());
|
||||
renderEventLog(events, rangeName || 'precise');
|
||||
} catch (e) {
|
||||
console.error("Błąd pobierania zdarzeń", e);
|
||||
} catch (e) {
|
||||
console.error("Błąd pobierania zdarzeń", e);
|
||||
}
|
||||
|
||||
|
||||
voltageChart.data.datasets = newDatasets;
|
||||
if (rangeName) {
|
||||
voltageChart.update();
|
||||
voltageChart.update();
|
||||
} else {
|
||||
voltageChart.update('none');
|
||||
}
|
||||
|
||||
|
||||
const finalMin = rangeName ? voltageChart.scales.x.min : (min || voltageChart.scales.x.min);
|
||||
const finalMax = rangeName ? voltageChart.scales.x.max : (max || voltageChart.scales.x.max);
|
||||
updateRangeLabel(finalMin, finalMax);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dynamiczna aktualizacja napisu zakresu czasu nad logami
|
||||
*/
|
||||
|
||||
function updateRangeLabel(min, max) {
|
||||
const label = document.getElementById('eventRangeLabel');
|
||||
if (!label) return;
|
||||
const start = new Date(min);
|
||||
const end = new Date(max);
|
||||
const optTime = { hour: '2-digit', minute: '2-digit', hour12: false };
|
||||
const optDate = { day: '2-digit', month: '2-digit' };
|
||||
|
||||
|
||||
let rangeText = '';
|
||||
|
||||
if (start.toDateString() === end.toDateString()) {
|
||||
label.textContent = `Zakres: ${start.toLocaleDateString('pl-PL', optDate)}, ${start.toLocaleTimeString('pl-PL', optTime)} - ${end.toLocaleTimeString('pl-PL', optTime)}`;
|
||||
rangeText = `Zakres: ${start.toLocaleDateString('pl-PL', optDate)}, ${start.toLocaleTimeString('pl-PL', optTime)} - ${end.toLocaleTimeString('pl-PL', optTime)}`;
|
||||
} else {
|
||||
label.textContent = `Zakres: ${start.toLocaleDateString('pl-PL', optDate)} ${start.toLocaleTimeString('pl-PL', optTime)} - ${end.toLocaleDateString('pl-PL', optDate)} ${end.toLocaleTimeString('pl-PL', optTime)}`;
|
||||
rangeText = `Zakres: ${start.toLocaleDateString('pl-PL', optDate)} ${start.toLocaleTimeString('pl-PL', optTime)} - ${end.toLocaleDateString('pl-PL', optDate)} ${end.toLocaleTimeString('pl-PL', optTime)}`;
|
||||
}
|
||||
|
||||
// Aktualizuj label nad logami
|
||||
const label = document.getElementById('eventRangeLabel');
|
||||
if (label) {
|
||||
label.textContent = rangeText;
|
||||
}
|
||||
|
||||
const chartDisplay = document.getElementById('chartRangeDisplay');
|
||||
if (chartDisplay) {
|
||||
const optDateShort = { day: '2-digit', month: '2-digit' };
|
||||
if (start.toDateString() === end.toDateString()) {
|
||||
chartDisplay.textContent = `${start.toLocaleDateString('pl-PL', optDateShort)} ${start.toLocaleTimeString('pl-PL', optTime)} - ${end.toLocaleTimeString('pl-PL', optTime)}`;
|
||||
} else {
|
||||
chartDisplay.textContent = `${start.toLocaleDateString('pl-PL', optDateShort)} ${start.toLocaleTimeString('pl-PL', optTime)} - ${end.toLocaleDateString('pl-PL', optDateShort)} ${end.toLocaleTimeString('pl-PL', optTime)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,26 +389,4 @@ window.showEventOnChart = function(startTimeStr) {
|
||||
currentTimeRange = 'precise';
|
||||
reloadDataForRange(min, max);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function setupGauges() {
|
||||
const config = {
|
||||
type: 'doughnut',
|
||||
data: { datasets: [{ data: [0, 100], backgroundColor: ['#198754', '#1a1d20'], borderWidth: 0, circumference: 180, rotation: 270 }] },
|
||||
options: { responsive: true, maintainAspectRatio: true, cutout: '75%', plugins: { legend: { display: false }, tooltip: { enabled: false } } }
|
||||
};
|
||||
Object.keys(phasesConfig).forEach(id => {
|
||||
const canvas = document.getElementById('gauge' + id);
|
||||
if (canvas) gauges[id] = new Chart(canvas, JSON.parse(JSON.stringify(config)));
|
||||
});
|
||||
}
|
||||
|
||||
function updateGaugeUI(id, val) {
|
||||
if (!gauges[id]) return;
|
||||
const pct = Math.max(0, Math.min(100, ((val - 190) / 80) * 100));
|
||||
let color = (val < THRESHOLDS.min || val > THRESHOLDS.max) ? '#dc3545' : ((val < 212 || val > 248) ? '#ffc107' : '#198754');
|
||||
gauges[id].data.datasets[0].data = [pct, 100 - pct];
|
||||
gauges[id].data.datasets[0].backgroundColor = [color, '#1a1d20'];
|
||||
gauges[id].update('none');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user