first commit
This commit is contained in:
289
static/script.js
Normal file
289
static/script.js
Normal file
@@ -0,0 +1,289 @@
|
||||
let currentSeriesData = [];
|
||||
let darkMode = true;
|
||||
|
||||
function toggleTheme() {
|
||||
darkMode = !darkMode;
|
||||
document.body.classList.toggle('bg-dark', darkMode);
|
||||
document.body.classList.toggle('text-light', darkMode);
|
||||
document.querySelector('.theme-switch').className = `bi ${darkMode ? 'bi-moon-stars-fill' : 'bi-sun-fill'} theme-switch`;
|
||||
document.querySelectorAll('.form-select, .form-control').forEach(el => {
|
||||
el.classList.toggle('bg-dark', darkMode);
|
||||
el.classList.toggle('text-light', darkMode);
|
||||
el.classList.toggle('border-secondary', darkMode);
|
||||
});
|
||||
}
|
||||
|
||||
async function fetchDatabases() {
|
||||
const res = await fetch('/databases');
|
||||
const dbs = await res.json();
|
||||
const select = document.getElementById('db-select');
|
||||
select.innerHTML = '<option value="">Wybierz bazę danych...</option>';
|
||||
dbs.forEach(db => {
|
||||
const option = document.createElement('option');
|
||||
option.value = db;
|
||||
option.textContent = db;
|
||||
select.appendChild(option);
|
||||
});
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const dbParam = params.get('db');
|
||||
if (dbParam && dbs.includes(dbParam)) {
|
||||
select.value = dbParam;
|
||||
fetchSeries();
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSeries() {
|
||||
const db = document.getElementById('db-select').value;
|
||||
if (!db) return;
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
params.set('db', db);
|
||||
window.history.replaceState({}, '', `${location.pathname}?${params}`);
|
||||
showMessage('Ładowanie serii...', 'info');
|
||||
const res = await fetch(`/series?db=${db}`);
|
||||
const series = await res.json();
|
||||
currentSeriesData = series;
|
||||
renderSeriesTable(series);
|
||||
showMessage('', 'info');
|
||||
}
|
||||
|
||||
function renderSeriesTable(series) {
|
||||
const container = document.getElementById('series-list');
|
||||
container.innerHTML = '';
|
||||
if (!Array.isArray(series) || series.length === 0) {
|
||||
container.innerHTML = `<tr><td colspan="3" class="text-center">Brak serii w wybranej bazie danych</td></tr>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const baseNames = new Map();
|
||||
series.forEach(s => {
|
||||
const entity = s.tags?.entity_id;
|
||||
if (!entity) return;
|
||||
const base = entity.replace(/_\d+$/, '');
|
||||
if (baseNames.has(base)) baseNames.get(base).push(s.series);
|
||||
else baseNames.set(base, [s.series]);
|
||||
});
|
||||
|
||||
const duplicates = new Set();
|
||||
for (const [_, entries] of baseNames.entries()) {
|
||||
if (entries.length > 1) entries.forEach(ser => duplicates.add(ser));
|
||||
}
|
||||
|
||||
series.forEach((s, i) => {
|
||||
const id = `series-${i}`;
|
||||
const isDuplicate = duplicates.has(s.series);
|
||||
const row = document.createElement('tr');
|
||||
row.className = isDuplicate ? 'table-warning' : '';
|
||||
row.innerHTML = `
|
||||
<td><input class="form-check-input" type="checkbox" value="${s.series}" id="${id}" onchange="updateCount()"></td>
|
||||
<td class="series-item">
|
||||
<label class="form-check-label" for="${id}">${s.series}</label>
|
||||
${isDuplicate ? '<div class="text-warning small">Duplikat entity_id</div>' : ''}
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-danger me-1" onclick="deleteSingleSeries('${s.series}')"><i class="bi bi-trash"></i></button>
|
||||
<button class="btn btn-sm btn-outline-warning" onclick="openTimeDeleteModal('${s.series}')"><i class="bi bi-clock-history"></i></button>
|
||||
</td>
|
||||
`;
|
||||
container.appendChild(row);
|
||||
});
|
||||
updateCount();
|
||||
}
|
||||
|
||||
function filterSeries() {
|
||||
const term = document.getElementById('series-filter').value.toLowerCase();
|
||||
const filtered = currentSeriesData.filter(s => s.series.toLowerCase().includes(term));
|
||||
renderSeriesTable(filtered);
|
||||
}
|
||||
|
||||
function updateCount() {
|
||||
const count = document.querySelectorAll('input[type="checkbox"]:checked').length;
|
||||
document.getElementById('selected-count').innerText = `Zaznaczono: ${count}`;
|
||||
}
|
||||
|
||||
function toggleSelectAll() {
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]:not(#select-all)');
|
||||
const selectAll = document.getElementById('select-all').checked;
|
||||
checkboxes.forEach(checkbox => checkbox.checked = selectAll);
|
||||
updateCount();
|
||||
}
|
||||
|
||||
function showMessage(message, type) {
|
||||
const el = document.getElementById('info-message');
|
||||
el.textContent = message;
|
||||
el.className = `alert alert-${type} ${message ? 'd-block' : 'd-none'}`;
|
||||
}
|
||||
|
||||
async function deleteSelected() {
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked:not(#select-all)');
|
||||
if (checkboxes.length === 0) return showMessage('Nie zaznaczono żadnych serii', 'warning');
|
||||
const series = Array.from(checkboxes).map(cb => cb.value);
|
||||
const db = document.getElementById('db-select').value;
|
||||
const preview = series.map(s => `DROP SERIES FROM "${s.split(',')[0]}" WHERE ...`).join('\n');
|
||||
document.getElementById('confirmMessage').textContent = `Czy na pewno chcesz usunąć ${series.length} serii?`;
|
||||
document.getElementById('queryPreview').textContent = preview;
|
||||
const modal = new bootstrap.Modal(document.getElementById('confirmModal'));
|
||||
modal.show();
|
||||
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
||||
modal.hide();
|
||||
await deleteSeries(series);
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteSingleSeries(seriesId) {
|
||||
document.getElementById('confirmMessage').textContent = `Czy na pewno chcesz usunąć serię:`;
|
||||
document.getElementById('queryPreview').textContent = `DROP SERIES FROM "${seriesId.split(',')[0]}" WHERE ...`;
|
||||
const modal = new bootstrap.Modal(document.getElementById('confirmModal'));
|
||||
modal.show();
|
||||
document.getElementById('confirmDeleteBtn').onclick = async () => {
|
||||
modal.hide();
|
||||
await deleteSeries([seriesId]);
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteSeries(series) {
|
||||
const db = document.getElementById('db-select').value;
|
||||
showMessage('Usuwanie serii...', 'info');
|
||||
const res = await fetch('/delete', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ db, series })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
showMessage(`Usunięto ${data.deleted} serii`, 'success');
|
||||
setTimeout(fetchSeries, 1000);
|
||||
} else {
|
||||
showMessage('Wystąpił błąd podczas usuwania serii', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
fetchDatabases();
|
||||
|
||||
|
||||
|
||||
let currentSeriesForTimeDelete = null;
|
||||
|
||||
function openTimeDeleteModal(seriesId) {
|
||||
currentSeriesForTimeDelete = seriesId;
|
||||
document.getElementById('time-from').value = '';
|
||||
document.getElementById('time-to').value = '';
|
||||
document.getElementById('time-query-preview').textContent = '';
|
||||
const modal = new bootstrap.Modal(document.getElementById('timeDeleteModal'));
|
||||
modal.show();
|
||||
}
|
||||
|
||||
async function confirmTimeDelete() {
|
||||
const from = document.getElementById('time-from').value;
|
||||
const to = document.getElementById('time-to').value;
|
||||
const db = document.getElementById('db-select').value;
|
||||
const series = currentSeriesForTimeDelete;
|
||||
|
||||
if (!from || !to || !series || !db) {
|
||||
showMessage('Uzupełnij wszystkie pola i wybierz bazę danych', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
showMessage('Usuwanie danych z serii...', 'info');
|
||||
const res = await fetch('/delete_range', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ db, series, from, to })
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.status === 'success') {
|
||||
showMessage('Dane zostały usunięte', 'success');
|
||||
setTimeout(fetchSeries, 1000);
|
||||
} else {
|
||||
showMessage(`Błąd: ${data.error}`, 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function setPredefinedRange(value) {
|
||||
if (!value) return;
|
||||
|
||||
const now = new Date();
|
||||
const to = now.toISOString().slice(0, 16);
|
||||
let fromDate = new Date(now);
|
||||
|
||||
if (value.endsWith('h')) {
|
||||
fromDate.setHours(fromDate.getHours() - parseInt(value));
|
||||
} else if (value.endsWith('d')) {
|
||||
fromDate.setDate(fromDate.getDate() - parseInt(value));
|
||||
}
|
||||
|
||||
const from = fromDate.toISOString().slice(0, 16);
|
||||
document.getElementById('time-from').value = from;
|
||||
document.getElementById('time-to').value = to;
|
||||
}
|
||||
|
||||
|
||||
function setPredefinedRange(value) {
|
||||
if (!value) return;
|
||||
|
||||
const now = new Date();
|
||||
const to = now.toISOString().slice(0, 16);
|
||||
let fromDate = new Date(now);
|
||||
|
||||
if (value.endsWith('h')) {
|
||||
fromDate.setHours(fromDate.getHours() - parseInt(value));
|
||||
} else if (value.endsWith('d')) {
|
||||
fromDate.setDate(fromDate.getDate() - parseInt(value));
|
||||
} else if (value === 'before-current-year') {
|
||||
const start = new Date(now.getFullYear(), 0, 1);
|
||||
document.getElementById('time-from').value = '2000-01-01T00:00';
|
||||
document.getElementById('time-to').value = start.toISOString().slice(0, 16);
|
||||
return;
|
||||
} else if (value === 'keep-2y') {
|
||||
fromDate.setFullYear(fromDate.getFullYear() - 2);
|
||||
} else if (value === 'keep-3y') {
|
||||
fromDate.setFullYear(fromDate.getFullYear() - 3);
|
||||
}
|
||||
|
||||
const from = fromDate.toISOString().slice(0, 16);
|
||||
document.getElementById('time-from').value = from;
|
||||
document.getElementById('time-to').value = to;
|
||||
}
|
||||
|
||||
async function deleteRangeForSelected() {
|
||||
const checkboxes = document.querySelectorAll('input[type="checkbox"]:checked:not(#select-all)');
|
||||
if (checkboxes.length === 0) {
|
||||
showMessage('Zaznacz przynajmniej jedną serię', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const fromInput = document.getElementById('time-from').value;
|
||||
const toInput = document.getElementById('time-to').value;
|
||||
const db = document.getElementById('db-select').value;
|
||||
|
||||
if (!fromInput || !toInput || !db) {
|
||||
showMessage('Wybierz zakres czasu i bazę danych', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const from = new Date(fromInput).toISOString();
|
||||
const to = new Date(toInput).toISOString();
|
||||
const series = Array.from(checkboxes).map(cb => cb.value);
|
||||
|
||||
showMessage('Usuwanie danych z wielu serii...', 'info');
|
||||
for (const s of series) {
|
||||
await fetch('/delete_range', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ db, series: s, from, to })
|
||||
});
|
||||
}
|
||||
|
||||
showMessage('Dane zostały usunięte', 'success');
|
||||
setTimeout(fetchSeries, 1000);
|
||||
}
|
||||
|
||||
function openTimeDeleteModalForMany() {
|
||||
currentSeriesForTimeDelete = null; // żeby nie używać pojedynczej serii
|
||||
document.getElementById('time-from').value = '';
|
||||
document.getElementById('time-to').value = '';
|
||||
document.getElementById('time-query-preview').textContent = '';
|
||||
const modal = new bootstrap.Modal(document.getElementById('timeDeleteModal'));
|
||||
modal.show();
|
||||
}
|
19
static/styles.css
Normal file
19
static/styles.css
Normal file
@@ -0,0 +1,19 @@
|
||||
body {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.series-item {
|
||||
font-family: monospace;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.theme-switch {
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
}
|
Reference in New Issue
Block a user