From 044e954bdac4e2ca69f885cf30e1c8d519b941d3 Mon Sep 17 00:00:00 2001 From: Yan Xell Date: Wed, 13 May 2026 11:06:48 +0200 Subject: [PATCH] filtro per step name, + filtri in logs --- Tool/pages.py | 37 ++---------- Tool/queries/logs.py | 33 +++++++---- Tool/queries/processes.py | 8 ++- static/js/logs.js | 117 ++++++++++++++++++++++++++++++++++---- static/js/runs.js | 31 +++++++++- static/pages/logs.css | 21 +++++-- static/pages/runs.css | 26 +++++++++ templates/logs.html | 10 +++- templates/runs.html | 3 + 9 files changed, 221 insertions(+), 65 deletions(-) diff --git a/Tool/pages.py b/Tool/pages.py index 445e65b..a5748ea 100644 --- a/Tool/pages.py +++ b/Tool/pages.py @@ -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='

Nessun log trovato.

' - ) - 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'← Precedente' - if prev_id else '← Precedente') - next_btn = (f'Successivo →' - if next_id else 'Successivo →') - options = ''.join( - f'' - for rid in run_ids - ) + logs = q_logs.get_logs(db_path) pagination = ( - f'
' - f'{prev_btn}' - f'' - f'{next_btn}' - f'Run {idx + 1} / {len(run_ids)}' - f'
' + '
' + '
' + '
' ) - - 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), ) diff --git a/Tool/queries/logs.py b/Tool/queries/logs.py index 04d8a79..b27f280 100644 --- a/Tool/queries/logs.py +++ b/Tool/queries/logs.py @@ -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'' - f'{e(r.get("rpa_process_id"))}' + f'' + f'{e(r.get("process_name", "—"))} #{e(r.get("rpa_process_id"))}' f'{dt(r.get("date_log"))}' f'{_badge(r.get("type"))}' f'{e(r.get("log"))}' diff --git a/Tool/queries/processes.py b/Tool/queries/processes.py index 6b17618..080c88a 100644 --- a/Tool/queries/processes.py +++ b/Tool/queries/processes.py @@ -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'' + f'' + f'{e(r.get("process_name", "—"))}' f'{dt(r.get("start_run"))}' f'{dt(r.get("finish_run"))}' f'{dur(r.get("start_run"), r.get("finish_run"))}' @@ -36,6 +38,6 @@ def render_table(rows: list) -> str: ) return ( '' - '' + '' f'{trs}
InizioFineDurataStatoNoteProcessoInizioFineDurataStatoNote
' ) diff --git a/static/js/logs.js b/static/js/logs.js index 75d47d7..e064817 100644 --- a/static/js/logs.js +++ b/static/js/logs.js @@ -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(); })(); diff --git a/static/js/runs.js b/static/js/runs.js index 9f4a665..37125c1 100644 --- a/static/js/runs.js +++ b/static/js/runs.js @@ -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); })(); diff --git a/static/pages/logs.css b/static/pages/logs.css index 571d56b..0e5cf1f 100644 --- a/static/pages/logs.css +++ b/static/pages/logs.css @@ -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} diff --git a/static/pages/runs.css b/static/pages/runs.css index b4a957d..cab64bd 100644 --- a/static/pages/runs.css +++ b/static/pages/runs.css @@ -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; +} diff --git a/templates/logs.html b/templates/logs.html index 6c7f4b4..1c2b861 100644 --- a/templates/logs.html +++ b/templates/logs.html @@ -1,7 +1,7 @@