filtro per step name, + filtri in logs
This commit is contained in:
+5
-32
@@ -101,41 +101,14 @@ def _page_pec(db_path: str) -> str:
|
||||
|
||||
|
||||
def _page_logs(db_path: str, run_id: int = None) -> str:
|
||||
run_ids = [r['rpa_process_id'] for r in q_logs.get_last_run_ids(db_path)]
|
||||
|
||||
if not run_ids:
|
||||
content = _tmpl('logs.html').substitute(
|
||||
run_info='nessun log', pagination='', tbl_logs='<p class="empty">Nessun log trovato.</p>'
|
||||
)
|
||||
return _base('Log DB', content, 'nav_logs', db_path, _page_css('logs'), _page_js('logs'))
|
||||
|
||||
if run_id is None or run_id not in run_ids:
|
||||
run_id = run_ids[0]
|
||||
|
||||
idx = run_ids.index(run_id)
|
||||
prev_id = run_ids[idx + 1] if idx + 1 < len(run_ids) else None
|
||||
next_id = run_ids[idx - 1] if idx > 0 else None
|
||||
|
||||
prev_btn = (f'<a class="pg-btn" href="/logs?run={prev_id}">← Precedente</a>'
|
||||
if prev_id else '<span class="pg-btn disabled">← Precedente</span>')
|
||||
next_btn = (f'<a class="pg-btn" href="/logs?run={next_id}">Successivo →</a>'
|
||||
if next_id else '<span class="pg-btn disabled">Successivo →</span>')
|
||||
options = ''.join(
|
||||
f'<option value="{rid}"{" selected" if rid == run_id else ""}>Run #{rid}</option>'
|
||||
for rid in run_ids
|
||||
)
|
||||
logs = q_logs.get_logs(db_path)
|
||||
pagination = (
|
||||
f'<div class="log-pagination">'
|
||||
f'{prev_btn}'
|
||||
f'<select class="pg-select" onchange="location.href=\'/logs?run=\'+this.value">{options}</select>'
|
||||
f'{next_btn}'
|
||||
f'<span class="pg-info">Run {idx + 1} / {len(run_ids)}</span>'
|
||||
f'</div>'
|
||||
'<div class="log-pagination">'
|
||||
'<div id="filter-process-names" class="pg-process-filter"></div>'
|
||||
'</div>'
|
||||
)
|
||||
|
||||
logs = q_logs.get_logs_by_run(db_path, run_id)
|
||||
content = _tmpl('logs.html').substitute(
|
||||
run_info=f'Run #{run_id}',
|
||||
run_info='',
|
||||
pagination=pagination,
|
||||
tbl_logs=q_logs.render_table(logs),
|
||||
)
|
||||
|
||||
+23
-10
@@ -4,19 +4,30 @@ from .db import query, count, e, dt, badge
|
||||
|
||||
|
||||
def get_logs(db_path: str) -> list:
|
||||
return query(db_path, "SELECT * FROM rpa_logs ORDER BY date_log DESC LIMIT 200")
|
||||
|
||||
|
||||
def get_last_run_ids(db_path: str, n: int = 10) -> list:
|
||||
"""Returns the last N distinct rpa_process_id values that have logs, DESC."""
|
||||
return query(db_path,
|
||||
"SELECT rpa_process_id FROM rpa_logs WHERE rpa_process_id IS NOT NULL "
|
||||
"GROUP BY rpa_process_id ORDER BY rpa_process_id DESC LIMIT ?", (n,))
|
||||
"SELECT l.*, COALESCE(p.process_name, 'unknown') AS process_name "
|
||||
"FROM rpa_logs l "
|
||||
"LEFT JOIN rpa_process p ON p.id = l.rpa_process_id "
|
||||
"ORDER BY l.date_log DESC LIMIT 500")
|
||||
|
||||
|
||||
def get_last_run_ids(db_path: str, n: int = 20) -> list:
|
||||
"""Returns the last N distinct rpa_process_id values with process_name and start_run date, DESC."""
|
||||
return query(db_path,
|
||||
"SELECT l.rpa_process_id, COALESCE(p.process_name, 'unknown') AS process_name, "
|
||||
"COALESCE(p.start_run, '') AS start_run "
|
||||
"FROM rpa_logs l "
|
||||
"LEFT JOIN rpa_process p ON p.id = l.rpa_process_id "
|
||||
"WHERE l.rpa_process_id IS NOT NULL "
|
||||
"GROUP BY l.rpa_process_id ORDER BY l.rpa_process_id DESC LIMIT ?", (n,))
|
||||
|
||||
|
||||
def get_logs_by_run(db_path: str, run_id: int) -> list:
|
||||
return query(db_path,
|
||||
"SELECT * FROM rpa_logs WHERE rpa_process_id=? ORDER BY date_log DESC", (run_id,))
|
||||
"SELECT l.*, COALESCE(p.process_name, 'unknown') AS process_name "
|
||||
"FROM rpa_logs l "
|
||||
"LEFT JOIN rpa_process p ON p.id = l.rpa_process_id "
|
||||
"WHERE l.rpa_process_id=? ORDER BY l.date_log DESC", (run_id,))
|
||||
|
||||
|
||||
def get_stats(db_path: str) -> dict:
|
||||
@@ -36,8 +47,10 @@ def render_table(rows: list) -> str:
|
||||
return badge(cls, lbl) if cls else e(tipo)
|
||||
|
||||
trs = ''.join(
|
||||
f'<tr data-type="{e(r.get("type", ""))}">'
|
||||
f'<td>{e(r.get("rpa_process_id"))}</td>'
|
||||
f'<tr data-type="{e(r.get("type", ""))}"'
|
||||
f' data-process-name="{e(r.get("process_name", "unknown"))}"'
|
||||
f' data-date="{str(r.get("date_log") or "")[:10]}">'
|
||||
f'<td><span class="proc-name-badge">{e(r.get("process_name", "—"))}</span> <span class="run-id">#{e(r.get("rpa_process_id"))}</span></td>'
|
||||
f'<td>{dt(r.get("date_log"))}</td>'
|
||||
f'<td>{_badge(r.get("type"))}</td>'
|
||||
f'<td class="nc">{e(r.get("log"))}</td>'
|
||||
|
||||
@@ -4,7 +4,7 @@ from .db import query, count, e, dt, dur, badge
|
||||
|
||||
|
||||
def get_processes(db_path: str) -> list:
|
||||
return query(db_path, "SELECT * FROM rpa_process ORDER BY start_run DESC LIMIT 50")
|
||||
return query(db_path, "SELECT id, process_name, note, completed, start_run, finish_run FROM rpa_process ORDER BY start_run DESC LIMIT 50")
|
||||
|
||||
|
||||
def get_stats(db_path: str) -> dict:
|
||||
@@ -25,7 +25,9 @@ def render_table(rows: list) -> str:
|
||||
return badge('ok', 'Completato') if completed in (1, True, '1') else badge('err', 'Incompleto')
|
||||
|
||||
trs = ''.join(
|
||||
f'<tr data-completed="{1 if r.get("completed") in (1, True, "1") else 0}">'
|
||||
f'<tr data-completed="{1 if r.get("completed") in (1, True, "1") else 0}"'
|
||||
f' data-process-name="{e(r.get("process_name", "unknown"))}">'
|
||||
f'<td><span class="proc-name-badge">{e(r.get("process_name", "—"))}</span></td>'
|
||||
f'<td>{dt(r.get("start_run"))}</td>'
|
||||
f'<td>{dt(r.get("finish_run"))}</td>'
|
||||
f'<td>{dur(r.get("start_run"), r.get("finish_run"))}</td>'
|
||||
@@ -36,6 +38,6 @@ def render_table(rows: list) -> str:
|
||||
)
|
||||
return (
|
||||
'<table><thead><tr>'
|
||||
'<th>Inizio</th><th>Fine</th><th>Durata</th><th>Stato</th><th>Note</th>'
|
||||
'<th>Processo</th><th>Inizio</th><th>Fine</th><th>Durata</th><th>Stato</th><th>Note</th>'
|
||||
f'</tr></thead><tbody>{trs}</tbody></table>'
|
||||
)
|
||||
|
||||
+105
-12
@@ -1,16 +1,109 @@
|
||||
(function(){
|
||||
var search = document.getElementById('log-search');
|
||||
var checks = document.querySelectorAll('.type-filter');
|
||||
function filter(){
|
||||
var q = search.value.toLowerCase();
|
||||
var active = {};
|
||||
checks.forEach(function(c){ if(c.checked) active[c.value] = 1; });
|
||||
document.querySelectorAll('tbody tr').forEach(function(tr){
|
||||
var type = tr.dataset.type || '';
|
||||
var text = tr.textContent.toLowerCase();
|
||||
tr.style.display = (active[type] && text.indexOf(q) >= 0) ? '' : 'none';
|
||||
var search = document.getElementById('log-search');
|
||||
var typeChecks = document.querySelectorAll('.type-filter');
|
||||
var pnContainer = document.getElementById('filter-process-names');
|
||||
var dateFrom = document.getElementById('filter-date-from');
|
||||
var dateTo = document.getElementById('filter-date-to');
|
||||
var filterBtn = document.getElementById('filter-date-btn');
|
||||
|
||||
var STORAGE_KEY = 'logs_pn_filter';
|
||||
var STORAGE_KEY_DATE = 'logs_date_filter';
|
||||
var STORAGE_KEY_ACTIVE = 'logs_date_active';
|
||||
|
||||
function toIsoDate(d) { return d.toISOString().slice(0, 10); }
|
||||
|
||||
// ── Restore / default date range ──────────────────────────────────────────
|
||||
function loadSavedDates() {
|
||||
try { return JSON.parse(sessionStorage.getItem(STORAGE_KEY_DATE) || 'null'); } catch(e) { return null; }
|
||||
}
|
||||
function saveDates() {
|
||||
try {
|
||||
sessionStorage.setItem(STORAGE_KEY_DATE, JSON.stringify({
|
||||
from: dateFrom ? dateFrom.value : '',
|
||||
to: dateTo ? dateTo.value : '',
|
||||
}));
|
||||
sessionStorage.setItem(STORAGE_KEY_ACTIVE, '1');
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
var dateActive = (function() {
|
||||
try { return sessionStorage.getItem(STORAGE_KEY_ACTIVE) === '1'; } catch(e) { return false; }
|
||||
})();
|
||||
|
||||
var savedDates = loadSavedDates();
|
||||
if (savedDates) {
|
||||
if (dateFrom) dateFrom.value = savedDates.from || '';
|
||||
if (dateTo) dateTo.value = savedDates.to || '';
|
||||
} else {
|
||||
var today = new Date();
|
||||
var twoWeeksAgo = new Date(today); twoWeeksAgo.setDate(today.getDate() - 14);
|
||||
if (dateFrom) dateFrom.value = toIsoDate(twoWeeksAgo);
|
||||
if (dateTo) dateTo.value = toIsoDate(today);
|
||||
}
|
||||
|
||||
// ── Process-name checkboxes (built from table rows) ───────────────────────
|
||||
var names = [];
|
||||
document.querySelectorAll('tbody tr[data-process-name]').forEach(function(tr) {
|
||||
var n = tr.getAttribute('data-process-name');
|
||||
if (n && names.indexOf(n) === -1) names.push(n);
|
||||
});
|
||||
names.sort();
|
||||
|
||||
function loadSaved() {
|
||||
try { return JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '{}'); } catch(e) { return {}; }
|
||||
}
|
||||
function saveState() {
|
||||
var state = {};
|
||||
Object.keys(pnChecks).forEach(function(n){ state[n] = pnChecks[n].checked; });
|
||||
try { sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch(e) {}
|
||||
}
|
||||
|
||||
var pnChecks = {};
|
||||
var saved = loadSaved();
|
||||
if (pnContainer) {
|
||||
names.forEach(function(name) {
|
||||
var lbl = document.createElement('label');
|
||||
var chk = document.createElement('input');
|
||||
chk.type = 'checkbox';
|
||||
chk.checked = (saved[name] !== undefined) ? saved[name] : true;
|
||||
chk.addEventListener('change', function(){ saveState(); applyFilter(); });
|
||||
pnChecks[name] = chk;
|
||||
lbl.appendChild(chk);
|
||||
lbl.appendChild(document.createTextNode(' ' + name));
|
||||
pnContainer.appendChild(lbl);
|
||||
});
|
||||
}
|
||||
search.addEventListener('input', filter);
|
||||
checks.forEach(function(c){ c.addEventListener('change', filter); });
|
||||
|
||||
// ── Table row filter (type + search + date + process_name) ────────────────
|
||||
function applyFilter() {
|
||||
var q = search ? search.value.toLowerCase() : '';
|
||||
var from = dateActive && dateFrom ? dateFrom.value : '';
|
||||
var to = dateActive && dateTo ? dateTo.value : '';
|
||||
var active = {};
|
||||
typeChecks.forEach(function(c){ if (c.checked) active[c.value] = 1; });
|
||||
document.querySelectorAll('tbody tr').forEach(function(tr) {
|
||||
var type = tr.dataset.type || '';
|
||||
var pn = tr.getAttribute('data-process-name') || '';
|
||||
var text = tr.textContent.toLowerCase();
|
||||
var date = tr.getAttribute('data-date') || '';
|
||||
var dateOk = (!from || date >= from) && (!to || date <= to);
|
||||
var pnOk = pnChecks[pn] ? pnChecks[pn].checked : true;
|
||||
tr.style.display = (active[type] && pnOk && text.indexOf(q) >= 0 && dateOk) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// ── Filter button — saves dates then applies filter ───────────────────────
|
||||
if (filterBtn) {
|
||||
filterBtn.addEventListener('click', function() {
|
||||
dateActive = true;
|
||||
saveDates();
|
||||
applyFilter();
|
||||
});
|
||||
}
|
||||
|
||||
if (search) search.addEventListener('input', applyFilter);
|
||||
typeChecks.forEach(function(c){ c.addEventListener('change', applyFilter); });
|
||||
|
||||
// Initial apply
|
||||
applyFilter();
|
||||
})();
|
||||
|
||||
+29
-2
@@ -1,14 +1,41 @@
|
||||
(function(){
|
||||
var chkOk = document.getElementById('chk-completed');
|
||||
var chkErr = document.getElementById('chk-incomplete');
|
||||
var pnContainer = document.getElementById('filter-process-names');
|
||||
|
||||
// Build process_name checkboxes dynamically from table data
|
||||
var rows = Array.from(document.querySelectorAll('tbody tr[data-process-name]'));
|
||||
var names = [];
|
||||
rows.forEach(function(tr) {
|
||||
var n = tr.getAttribute('data-process-name');
|
||||
if (n && names.indexOf(n) === -1) names.push(n);
|
||||
});
|
||||
names.sort();
|
||||
|
||||
var pnChecks = {}; // name → checkbox element
|
||||
names.forEach(function(name) {
|
||||
var lbl = document.createElement('label');
|
||||
var chk = document.createElement('input');
|
||||
chk.type = 'checkbox';
|
||||
chk.checked = true;
|
||||
chk.addEventListener('change', applyFilter);
|
||||
pnChecks[name] = chk;
|
||||
lbl.appendChild(chk);
|
||||
lbl.appendChild(document.createTextNode(' ' + name));
|
||||
pnContainer.appendChild(lbl);
|
||||
});
|
||||
|
||||
function applyFilter() {
|
||||
var showOk = chkOk.checked;
|
||||
var showErr = chkErr.checked;
|
||||
document.querySelectorAll('tbody tr[data-completed]').forEach(function(tr) {
|
||||
rows.forEach(function(tr) {
|
||||
var done = tr.getAttribute('data-completed') === '1';
|
||||
tr.style.display = (done ? showOk : showErr) ? '' : 'none';
|
||||
var pn = tr.getAttribute('data-process-name');
|
||||
var pnOk = !pnChecks[pn] || pnChecks[pn].checked;
|
||||
tr.style.display = (done ? showOk : showErr) && pnOk ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
chkOk.addEventListener('change', applyFilter);
|
||||
chkErr.addEventListener('change', applyFilter);
|
||||
})();
|
||||
|
||||
+17
-4
@@ -1,8 +1,20 @@
|
||||
/* logs.css — stili specifici per la pagina Log DB */
|
||||
.log-filters{display:flex;align-items:center;gap:14px;margin-bottom:12px;flex-wrap:wrap}
|
||||
.log-filters{display:flex;align-items:center;gap:14px;margin-bottom:8px;flex-wrap:wrap}
|
||||
.filter-hr{border:none;border-top:1px solid var(--brd);margin:6px 0}
|
||||
.log-filters input[type=search]{padding:6px 10px;border:1px solid var(--brd);border-radius:6px;font-size:.85rem;width:230px;background:var(--surf);color:var(--txt)}
|
||||
.log-filters label{display:flex;align-items:center;gap:5px;font-size:.85rem;cursor:pointer;color:var(--mut);user-select:none}
|
||||
.log-filters input[type=checkbox]{cursor:pointer;accent-color:var(--pri)}
|
||||
.log-filters .filter-sep{display:inline-block;width:1px;height:1.2rem;background:#cbd5e1;margin:0 .25rem;align-self:center}
|
||||
.date-label{display:flex;align-items:center;gap:.3rem;font-size:.85rem;color:var(--mut);white-space:nowrap}
|
||||
.date-label input[type=date]{padding:4px 7px;border:1px solid var(--brd);border-radius:6px;font-size:.82rem;background:var(--surf);color:var(--txt);cursor:pointer}
|
||||
.filter-date-btn{padding:4px 14px;border:1px solid var(--pri);border-radius:6px;font-size:.82rem;background:var(--pri);color:#fff;cursor:pointer;font-weight:600}
|
||||
.filter-date-btn:hover{opacity:.85}
|
||||
.pg-sep{display:inline-block;width:1px;height:1.4rem;background:#cbd5e1;margin:0 .25rem;align-self:center}
|
||||
.pg-run-info{font-size:.85rem;font-weight:600;color:var(--txt);white-space:nowrap;display:flex;align-items:center;gap:.4rem}
|
||||
.pg-proc-tag{font-size:.72rem;font-weight:600;padding:.1rem .45rem;border-radius:999px;background:#dbeafe;color:#1d4ed8;white-space:nowrap}
|
||||
.pg-process-filter{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;background:#f1f5f9;border-radius:6px;padding:.25rem .6rem}
|
||||
.pg-process-filter label{display:flex;align-items:center;gap:.3rem;font-size:.8rem;cursor:pointer;color:var(--txt);user-select:none;white-space:nowrap}
|
||||
.pg-process-filter input[type=checkbox]{cursor:pointer;accent-color:var(--pri);width:.85rem;height:.85rem}
|
||||
|
||||
/* push footer text above the fixed pagination bar */
|
||||
footer { padding-bottom: 5rem; }
|
||||
@@ -15,6 +27,7 @@ footer { padding-bottom: 5rem; }
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
z-index: 150;
|
||||
background: rgba(255,255,255,.96);
|
||||
@@ -24,8 +37,8 @@ footer { padding-bottom: 5rem; }
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,.18);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.pg-btn{padding:5px 13px;border:1px solid var(--brd);border-radius:6px;font-size:.85rem;text-decoration:none;color:var(--txt);background:var(--surf);cursor:pointer}
|
||||
.pg-btn:hover{background:var(--pri);color:#fff;border-color:var(--pri)}
|
||||
.pg-btn.disabled{opacity:.35;pointer-events:none;cursor:default}
|
||||
.pg-btn{padding:5px 13px;border:1px solid #06b6d4;border-radius:6px;font-size:.85rem;text-decoration:none;color:#0e7490;background:#ecfeff;cursor:pointer;font-weight:600}
|
||||
.pg-btn:hover{background:#06b6d4;color:#fff;border-color:#06b6d4}
|
||||
.pg-btn.disabled{opacity:.35;pointer-events:none;cursor:default;border-color:var(--brd);background:var(--surf);color:var(--txt)}
|
||||
.pg-select{padding:5px 8px;border:1px solid var(--brd);border-radius:6px;font-size:.85rem;background:var(--surf);color:var(--txt);cursor:pointer}
|
||||
.pg-info{font-size:.8rem;color:var(--mut);margin-left:4px}
|
||||
|
||||
@@ -28,3 +28,29 @@
|
||||
cursor: pointer;
|
||||
accent-color: #2563eb;
|
||||
}
|
||||
|
||||
.filter-sep {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 1.2rem;
|
||||
background: #cbd5e1;
|
||||
margin: 0 .25rem;
|
||||
align-self: center;
|
||||
}
|
||||
.filter-group-label {
|
||||
font-size: .85rem;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.proc-name-badge {
|
||||
display: inline-block;
|
||||
padding: .15rem .55rem;
|
||||
border-radius: 999px;
|
||||
font-size: .78rem;
|
||||
font-weight: 600;
|
||||
background: #dbeafe;
|
||||
color: #1d4ed8;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
+8
-2
@@ -1,7 +1,7 @@
|
||||
<section class="page-header">
|
||||
<div class="page-filter-wrap">
|
||||
<div class="page-filter-header">
|
||||
<h2>Log DB — ${run_info}</h2>
|
||||
<h2>Log DB</h2>
|
||||
<button id="btn-toggle-filters" class="btn-toggle-filters" title="Mostra/nascondi filtri">
|
||||
<svg id="filters-arrow" class="filters-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 6 15 12 9 18"/></svg>
|
||||
<span class="filters-btn-text">Filtri</span>
|
||||
@@ -10,7 +10,13 @@
|
||||
<div id="filters-collapsible" class="filters-collapsible">
|
||||
<div class="filters-collapsible-inner">
|
||||
<div class="log-filters">
|
||||
<input type="search" id="log-search" placeholder="Cerca nei log…" />
|
||||
<label class="date-label">Dal <input type="date" id="filter-date-from" /></label>
|
||||
<label class="date-label">Al <input type="date" id="filter-date-to" /></label>
|
||||
<button id="filter-date-btn" class="filter-date-btn">Filtra</button>
|
||||
</div>
|
||||
<hr class="filter-hr" />
|
||||
<div class="log-filters">
|
||||
<input type="search" id="log-search" placeholder="Cerca in questo log…" />
|
||||
<label><input type="checkbox" class="type-filter" value="log" checked> Log</label>
|
||||
<label><input type="checkbox" class="type-filter" value="warning" checked> Warning</label>
|
||||
<label><input type="checkbox" class="type-filter" value="error" checked> Error</label>
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
<div class="runs-filter" id="runs-filter">
|
||||
<label><input type="checkbox" id="chk-completed" checked> Completati</label>
|
||||
<label><input type="checkbox" id="chk-incomplete" checked> Incompleti</label>
|
||||
<span class="filter-sep"></span>
|
||||
<span class="filter-group-label">Processo:</span>
|
||||
<div id="filter-process-names" class="runs-filter"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user