big refactoring

This commit is contained in:
Luca Banfi
2026-05-14 15:57:18 +02:00
parent 4ef2041b71
commit c84bfc1a3b
41 changed files with 255 additions and 88 deletions
+2 -2
View File
@@ -11,8 +11,8 @@ REPORTS_DIR = Path(__file__).resolve().parent.parent
load_dotenv(REPORTS_DIR / '.env')
TMPL_DIR = REPORTS_DIR / "templates"
CSS_PATH = REPORTS_DIR / "static" / "style.css"
PAGES_CSS_DIR = REPORTS_DIR / "static" / "pages"
CSS_PATH = REPORTS_DIR / "static" / "css" / "style.css"
PAGES_CSS_DIR = REPORTS_DIR / "static" / "css"
JS_DIR = REPORTS_DIR / "static" / "js"
class DbType(StrEnum):
+1
View File
@@ -20,6 +20,7 @@ def get_iscrizioni(db_path: str) -> list:
i.updated_at
FROM rpa_intra_api_iscrizione i
LEFT JOIN rpa_mapping_aziende m ON m.id = i.azienda_sorgente_id
WHERE date(i.created_at) >= date('now', '-10 days')
ORDER BY i.id DESC
LIMIT 500
""")
+5
View File
@@ -20,6 +20,11 @@ def count(db_path: str, sql: str) -> int:
return rows[0]['n'] if rows else 0
def count_q(db_path: str, sql: str, params: tuple = ()) -> int:
rows = query(db_path, sql, params)
return rows[0]['n'] if rows else 0
# ------------------------------------------------------------------ HTML utils
def e(v) -> str:
+13 -6
View File
@@ -1,6 +1,6 @@
"""queries/email.py — rpa_intra_email_queue (Intraz)"""
from .db import query, count, e, dt, badge
from .db import query, count, count_q, e, dt, badge
def get_email(db_path: str) -> list:
@@ -8,16 +8,23 @@ def get_email(db_path: str) -> list:
"SELECT id, iscrizione_id, recipient, subject, attachment_path, "
"inviata, inviata_at, errore, created_at "
"FROM rpa_intra_email_queue "
"WHERE date(created_at) >= date('now', '-10 days') "
"ORDER BY created_at DESC LIMIT 200"
)
def get_stats(db_path: str) -> dict:
def get_stats(db_path: str, date_from: str = None, date_to: str = None) -> dict:
conds, params = [], []
if date_from: conds.append("date(created_at) >= ?"); params.append(date_from)
if date_to: conds.append("date(created_at) <= ?"); params.append(date_to)
where = (" WHERE " + " AND ".join(conds)) if conds else ""
and_pfx = (" AND" if conds else " WHERE")
t = tuple(params)
return {
'email_total': count(db_path, "SELECT COUNT(*) as n FROM rpa_intra_email_queue"),
'email_inviata': count(db_path, "SELECT COUNT(*) as n FROM rpa_intra_email_queue WHERE inviata=1"),
'email_errore': count(db_path, "SELECT COUNT(*) as n FROM rpa_intra_email_queue WHERE inviata=0 AND errore IS NOT NULL"),
'email_pending': count(db_path, "SELECT COUNT(*) as n FROM rpa_intra_email_queue WHERE inviata=0 AND errore IS NULL"),
'email_total': count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_intra_email_queue{where}", t),
'email_inviata': count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_intra_email_queue{where}{and_pfx} inviata=1", t),
'email_errore': count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_intra_email_queue{where}{and_pfx} inviata=0 AND errore IS NOT NULL", t),
'email_pending': count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_intra_email_queue{where}{and_pfx} inviata=0 AND errore IS NULL", t),
}
+13 -2
View File
@@ -42,6 +42,7 @@ def get_corsi(db_path: str) -> list:
LEFT JOIN rpa_mapping_aziende m ON m.company_code = f.company_code
LEFT JOIN rpa_intra_steps_status s ON s.rpa_intra_ftp_json_id = f.id
LEFT JOIN rpa_intra_bc_check bc ON bc.rpa_intra_ftp_json_id = f.id
WHERE date(f.created_at) >= date('now', '-10 days')
ORDER BY f.id DESC
""")
@@ -67,8 +68,18 @@ def _step_cell(step_val, err_val) -> str:
return badge_html
def get_stats(db_path: str) -> dict:
rows = get_corsi(db_path)
def get_stats(db_path: str, date_from: str = None, date_to: str = None) -> dict:
conds, params = [], []
if date_from: conds.append("date(f.created_at) >= ?"); params.append(date_from)
if date_to: conds.append("date(f.created_at) <= ?"); params.append(date_to)
where = (" WHERE " + " AND ".join(conds)) if conds else ""
rows = query(db_path, f"""
SELECT s.step_ftp, s.step_bc, s.step_iscrizione, s.step_odv,
s.step_sharepoint, s.step_report, s.step_email
FROM rpa_intra_ftp_json f
LEFT JOIN rpa_intra_steps_status s ON s.rpa_intra_ftp_json_id = f.id
{where}
""", tuple(params))
return {
'isc_total': len(rows),
'isc_ok': sum(1 for r in rows if all(r.get(s) in ('done', 'skipped') for s in _ALL_STEPS)),
+12 -4
View File
@@ -1,6 +1,6 @@
"""queries/logs.py — rpa_logs"""
from .db import query, count, e, dt, badge
from .db import query, count, count_q, e, dt, badge
def get_logs(db_path: str) -> list:
@@ -8,6 +8,7 @@ def get_logs(db_path: str) -> list:
"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 date(l.date_log) >= date('now', '-10 days') "
"ORDER BY l.date_log DESC LIMIT 500")
@@ -19,6 +20,7 @@ def get_last_run_ids(db_path: str, n: int = 20) -> list:
"FROM rpa_logs l "
"LEFT JOIN rpa_process p ON p.id = l.rpa_process_id "
"WHERE l.rpa_process_id IS NOT NULL "
"AND date(l.date_log) >= date('now', '-10 days') "
"GROUP BY l.rpa_process_id ORDER BY l.rpa_process_id DESC LIMIT ?", (n,))
@@ -30,10 +32,16 @@ def get_logs_by_run(db_path: str, run_id: int) -> list:
"WHERE l.rpa_process_id=? ORDER BY l.date_log DESC", (run_id,))
def get_stats(db_path: str) -> dict:
def get_stats(db_path: str, date_from: str = None, date_to: str = None) -> dict:
conds, params = [], []
if date_from: conds.append("date(date_log) >= ?"); params.append(date_from)
if date_to: conds.append("date(date_log) <= ?"); params.append(date_to)
where = (" WHERE " + " AND ".join(conds)) if conds else ""
and_pfx = (" AND" if conds else " WHERE")
t = tuple(params)
return {
'log_errors': count(db_path, "SELECT COUNT(*) as n FROM rpa_logs WHERE type='error'"),
'log_warnings': count(db_path, "SELECT COUNT(*) as n FROM rpa_logs WHERE type='warning'"),
'log_errors': count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_logs{where}{and_pfx} type='error'", t),
'log_warnings': count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_logs{where}{and_pfx} type='warning'", t),
}
+13 -7
View File
@@ -1,6 +1,6 @@
"""queries/pec.py — pec_comunicazioni"""
from .db import query, count, e, dt, badge
from .db import query, count, count_q, e, dt, badge
_PEC_JOIN = """
SELECT
@@ -16,15 +16,21 @@ _PEC_JOIN = """
def get_pec(db_path: str) -> list:
return query(db_path, _PEC_JOIN + "ORDER BY p.created_at DESC LIMIT 200")
return query(db_path, _PEC_JOIN + "WHERE date(p.created_at) >= date('now', '-10 days') ORDER BY p.created_at DESC LIMIT 200")
def get_stats(db_path: str) -> dict:
def get_stats(db_path: str, date_from: str = None, date_to: str = None) -> dict:
conds, params = [], []
if date_from: conds.append("date(created_at) >= ?"); params.append(date_from)
if date_to: conds.append("date(created_at) <= ?"); params.append(date_to)
where = (" WHERE " + " AND ".join(conds)) if conds else ""
and_pfx = (" AND" if conds else " WHERE")
t = tuple(params)
return {
'total_pec': count(db_path, "SELECT COUNT(*) as n FROM pec_comunicazioni"),
'pec_inviato': count(db_path, "SELECT COUNT(*) as n FROM pec_comunicazioni WHERE stato_invio='inviata'"),
'pec_errore': count(db_path, "SELECT COUNT(*) as n FROM pec_comunicazioni WHERE stato_invio='errore'"),
'pec_pending': count(db_path, "SELECT COUNT(*) as n FROM pec_comunicazioni WHERE stato_invio='pending'"),
'total_pec': count_q(db_path, f"SELECT COUNT(*) as n FROM pec_comunicazioni{where}", t),
'pec_inviato': count_q(db_path, f"SELECT COUNT(*) as n FROM pec_comunicazioni{where}{and_pfx} stato_invio='inviata'", t),
'pec_errore': count_q(db_path, f"SELECT COUNT(*) as n FROM pec_comunicazioni{where}{and_pfx} stato_invio='errore'", t),
'pec_pending': count_q(db_path, f"SELECT COUNT(*) as n FROM pec_comunicazioni{where}{and_pfx} stato_invio='pending'", t),
}
+16 -7
View File
@@ -1,18 +1,27 @@
"""queries/processes.py — rpa_process"""
from .db import query, count, e, dt, dur, badge
from .db import query, count, count_q, e, dt, dur, badge
def get_processes(db_path: str) -> list:
return query(db_path, "SELECT id, process_name, note, completed, start_run, finish_run FROM rpa_process ORDER BY id DESC LIMIT 50")
return query(db_path,
"SELECT id, process_name, note, completed, start_run, finish_run FROM rpa_process "
"WHERE date(start_run) >= date('now', '-10 days') "
"ORDER BY id DESC LIMIT 50")
def get_stats(db_path: str) -> dict:
total = count(db_path, "SELECT COUNT(*) as n FROM rpa_process")
completed = count(db_path, "SELECT COUNT(*) as n FROM rpa_process WHERE completed = 1")
def get_stats(db_path: str, date_from: str = None, date_to: str = None) -> dict:
conds, params = [], []
if date_from: conds.append("date(start_run) >= ?"); params.append(date_from)
if date_to: conds.append("date(start_run) <= ?"); params.append(date_to)
where = (" WHERE " + " AND ".join(conds)) if conds else ""
and_pfx = (" AND" if conds else " WHERE")
t = tuple(params)
total = count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_process{where}", t)
completed = count_q(db_path, f"SELECT COUNT(*) as n FROM rpa_process{where}{and_pfx} completed=1", t)
return {
'total_runs': total,
'completed_runs': completed,
'total_runs': total,
'completed_runs': completed,
'incomplete_runs': total - completed,
}
+3 -1
View File
@@ -17,7 +17,9 @@ def get_report(db_path: str, da_data: str = None, a_data: str = None) -> list:
where.append("sc.rilevato_at <= ?")
params.append(a_data + " 23:59:59")
where_sql = ("WHERE " + " AND ".join(where)) if where else ""
if not where:
where.append("date(sc.rilevato_at) >= date('now', '-10 days')")
where_sql = "WHERE " + " AND ".join(where)
sql = f"""
SELECT
+1
View File
@@ -14,6 +14,7 @@ def get_sharepoint(db_path: str) -> list:
json_extract(f.azienda, '$.ragione_sociale') AS azienda_nome
FROM rpa_intra_sharepoint sp
LEFT JOIN rpa_intra_ftp_json f ON f.id = sp.rpa_intra_ftp_json_id
WHERE date(sp.created_at) >= date('now', '-10 days')
ORDER BY sp.id DESC
LIMIT 500
""")
+21 -13
View File
@@ -10,29 +10,37 @@ def _css() -> str:
return CSS_PATH.read_text(encoding='utf-8')
def _page_subdirs(base: Path) -> list:
"""Return direct subdirectories of base, excluding 'shared'."""
return [d for d in sorted(base.iterdir()) if d.is_dir() and d.name != 'shared']
def _page_css(name: str) -> str:
parts = []
shared = PAGES_CSS_DIR / 'filters.css'
if shared.exists():
parts.append(shared.read_text(encoding='utf-8'))
main = PAGES_CSS_DIR / f'{name}.css'
if main.exists():
parts.append(main.read_text(encoding='utf-8'))
for f in sorted(PAGES_CSS_DIR.glob(f'{name}_*.css')):
parts.append(f.read_text(encoding='utf-8'))
for search_dir in [PAGES_CSS_DIR] + _page_subdirs(PAGES_CSS_DIR):
main = search_dir / f'{name}.css'
if main.exists():
parts.append(main.read_text(encoding='utf-8'))
for f in sorted(search_dir.glob(f'{name}_*.css')):
parts.append(f.read_text(encoding='utf-8'))
return '\n'.join(parts)
def _page_js(name: str) -> str:
parts = []
shared = JS_DIR / 'filters.js'
if shared.exists():
parts.append(shared.read_text(encoding='utf-8'))
main = JS_DIR / f'{name}.js'
if main.exists():
parts.append(main.read_text(encoding='utf-8'))
for f in sorted(JS_DIR.glob(f'{name}_*.js')):
parts.append(f.read_text(encoding='utf-8'))
shared_dir = JS_DIR / 'shared'
if shared_dir.exists():
for f in sorted(shared_dir.glob('*.js')):
parts.append(f.read_text(encoding='utf-8'))
for search_dir in [JS_DIR] + _page_subdirs(JS_DIR):
main = search_dir / f'page_{name}.js'
if main.exists():
parts.append(main.read_text(encoding='utf-8'))
for f in sorted(search_dir.glob(f'page_{name}_*.js')):
parts.append(f.read_text(encoding='utf-8'))
return '\n'.join(parts)
+10
View File
@@ -0,0 +1,10 @@
/* dashboard.css — stili specifici per la pagina Dashboard */
.sec-link{color:inherit;text-decoration:none}
.sec-link:hover{text-decoration:underline;text-underline-offset:3px}
/* Section collapse toggle */
.sec-toggle-btn{background:none;border:none;cursor:pointer;padding:0 0 0 .5rem;vertical-align:middle;color:inherit;opacity:.6;line-height:1}
.sec-toggle-btn:hover{opacity:1}
.sec-toggle-arrow{width:1.1em;height:1.1em;display:inline-block;transition:transform .22s ease;transform:rotate(-90deg)}
.sec-toggle-arrow.open{transform:rotate(0deg)}
.dash-grid-hidden{display:none}
@@ -13,7 +13,7 @@
}
var COLS = ['sorgente', 'azienda', 'sessione', 'corso', 'data',
'ftp', 'bc', 'iscrizione', 'odv', 'sharepoint', 'report', 'email'];
'ftp', 'bc', 'iscrizione', 'odv_number', 'odv', 'sharepoint', 'report', 'email'];
var STEP_COLS = ['ftp', 'bc', 'iscrizione', 'odv', 'sharepoint', 'report', 'email'];
var BADGE_CLS = { done: 'ok', processing: 'proc', pending: 'warn', error: 'err', skipped: 'info' };
+34
View File
@@ -0,0 +1,34 @@
(function () {
// ── Section collapse (visible by default) ────────────────────────────────
document.querySelectorAll('section:not(.page-header)').forEach(function (sec, idx) {
var grid = sec.querySelector('.grid');
if (!grid) return;
var h2 = sec.querySelector('h2');
if (!h2) return;
var key = 'dash_sec_' + idx;
var isOpen = (sessionStorage.getItem(key) !== 'closed');
grid.classList.toggle('dash-grid-hidden', !isOpen);
var btn = document.createElement('button');
btn.className = 'sec-toggle-btn';
btn.title = 'Espandi / Comprimi';
btn.innerHTML = (
'<svg class="sec-toggle-arrow' + (isOpen ? ' open' : '') + '"'
+ ' viewBox="0 0 24 24" fill="none" stroke="currentColor"'
+ ' stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">'
+ '<polyline points="6 9 12 15 18 9"/></svg>'
);
h2.appendChild(btn);
btn.addEventListener('click', function (e) {
e.preventDefault();
e.stopPropagation();
var hidden = grid.classList.toggle('dash-grid-hidden');
var arrow = btn.querySelector('.sec-toggle-arrow');
if (arrow) arrow.classList.toggle('open', !hidden);
try { sessionStorage.setItem(key, hidden ? 'closed' : 'open'); } catch(e2) {}
});
});
})();
+18 -42
View File
@@ -5,42 +5,29 @@
var pgSelect = document.getElementById('pg-run-select');
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);
}
// ── Date range filter (client-side DOM filtering) ─────────────────────────
window.initDateRangeFilter({
storageKey: 'logs_date_filter',
readUrlParams: false,
defaultFrom: function () {
var d = new Date(); d.setDate(d.getDate() - 14); return d.toISOString().slice(0, 10);
},
defaultTo: function () { return new Date().toISOString().slice(0, 10); },
onFilter: function () {
console.log('[logs] filtra clicked — from:', dateFrom && dateFrom.value, 'to:', dateTo && dateTo.value);
dateActive = true;
try { sessionStorage.setItem(STORAGE_KEY_ACTIVE, '1'); } catch(e) {}
applyFilter();
filterSelect();
},
});
// ── Process-name checkboxes (built from pagination select options) ─────────
var names = [];
@@ -53,12 +40,12 @@
}
function loadSaved() {
try { return JSON.parse(sessionStorage.getItem(STORAGE_KEY) || '{}'); } catch(e) { return {}; }
try { return JSON.parse(sessionStorage.getItem('logs_pn_filter') || '{}'); } 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) {}
try { sessionStorage.setItem('logs_pn_filter', JSON.stringify(state)); } catch(e) {}
}
var pnChecks = {};
@@ -124,17 +111,6 @@
});
}
// ── Filter button — saves dates then applies both filters ─────────────────
if (filterBtn) {
filterBtn.addEventListener('click', function() {
console.log('[logs] filtra clicked — from:', dateFrom && dateFrom.value, 'to:', dateTo && dateTo.value);
dateActive = true;
saveDates();
applyFilter();
filterSelect();
});
}
if (search) search.addEventListener('input', function() {
console.log('[logs] search input:', search.value);
applyFilter();
+92
View File
@@ -0,0 +1,92 @@
// filters/generic_filters.js — shared date-range filter initialiser
//
// Usage:
// var df = window.initDateRangeFilter({
// storageKey: 'my_page_date_filter', // sessionStorage key
// defaultFrom: function() { return '2025-01-01'; },
// defaultTo: function() { return '2025-01-02'; },
// readUrlParams: true, // sync from/to URL params on load (for server-side pages)
// onFilter: function(from, to) { ... },
// onReset: function() { ... }, // optional
// });
// // df.getDates() → { from, to }
window.initDateRangeFilter = function (opts) {
var dateFrom = document.getElementById('filter-date-from');
var dateTo = document.getElementById('filter-date-to');
var filterBtn = document.getElementById('filter-date-btn');
var resetBtn = document.getElementById('filter-date-reset');
function toIsoDate(d) { return d.toISOString().slice(0, 10); }
function getUrlParam(name) {
try { return new URLSearchParams(window.location.search).get(name); } catch(e) { return null; }
}
function loadSaved() {
try { return JSON.parse(sessionStorage.getItem(opts.storageKey) || 'null'); } catch(e) { return null; }
}
function saveDates(from, to) {
try { sessionStorage.setItem(opts.storageKey, JSON.stringify({ from: from, to: to })); } catch(e) {}
}
function clearSaved() {
try { sessionStorage.removeItem(opts.storageKey); } catch(e) {}
}
// ── Initialise input values ───────────────────────────────────────────────
var initFrom = '', initTo = '';
if (opts.readUrlParams) {
var urlFrom = getUrlParam('from');
var urlTo = getUrlParam('to');
if (urlFrom || urlTo) {
initFrom = urlFrom || '';
initTo = urlTo || '';
saveDates(initFrom, initTo);
}
}
if (!initFrom && !initTo) {
var saved = loadSaved();
if (saved) {
initFrom = saved.from || '';
initTo = saved.to || '';
} else {
initFrom = opts.defaultFrom ? opts.defaultFrom() : '';
initTo = opts.defaultTo ? opts.defaultTo() : '';
}
}
if (dateFrom) dateFrom.value = initFrom;
if (dateTo) dateTo.value = initTo;
// ── Wire buttons ─────────────────────────────────────────────────────────
if (filterBtn) {
filterBtn.addEventListener('click', function () {
var from = dateFrom ? dateFrom.value : '';
var to = dateTo ? dateTo.value : '';
saveDates(from, to);
if (opts.onFilter) opts.onFilter(from, to);
});
}
if (resetBtn && opts.onReset) {
resetBtn.addEventListener('click', function () {
clearSaved();
opts.onReset();
});
}
// ── Public API ────────────────────────────────────────────────────────────
return {
getDates: function () {
return {
from: dateFrom ? dateFrom.value : '',
to: dateTo ? dateTo.value : '',
};
},
toIsoDate: toIsoDate,
};
};
-3
View File
@@ -1,3 +0,0 @@
/* dashboard.css — stili specifici per la pagina Dashboard */
.sec-link{color:inherit;text-decoration:none}
.sec-link:hover{text-decoration:underline;text-underline-offset:3px}