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''
+ ''
)
-
- 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 (
''
- '| Inizio | Fine | Durata | Stato | Note | '
+ 'Processo | Inizio | Fine | Durata | Stato | Note | '
f'
{trs}
'
)
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 @@