diff --git a/show-version.bat b/show-version.bat index 511f64a..933c64e 100644 --- a/show-version.bat +++ b/show-version.bat @@ -3,6 +3,7 @@ setlocal EnableExtensions EnableDelayedExpansion chcp 65001 > nul rem Същите ключове като GET /healthz: ok, version, git. rem Префиксът е от VERSION; SHA е от git (не може да е в commit-натия файл). +rem Двоен клик / без аргументи — pause. От терминал: show-version.bat -q set "ROOT=%~dp0" if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%" @@ -14,11 +15,18 @@ if exist "%ROOT%\VERSION" ( ) set "GIT_SHA=dev" -for /f "usebackq delims=" %%I in (`git rev-parse --short=7 HEAD 2^>nul`) do set "GIT_SHA=%%I" +set "SHA_FILE=%TEMP%\rip-help-git-sha.txt" +git rev-parse --short=7 HEAD > "%SHA_FILE%" 2>nul +if exist "%SHA_FILE%" ( + set /p GIT_SHA=<"%SHA_FILE%" + del "%SHA_FILE%" >nul 2>&1 +) +if not defined GIT_SHA set "GIT_SHA=dev" +if "!GIT_SHA!"=="" set "GIT_SHA=dev" echo {"ok": true, "version": "%APP_VERSION%+!GIT_SHA!", "git": "!GIT_SHA!"} echo. echo Сравни "git" с https://rip.inex-project.net/healthz echo Production вижда SHA едва след commit + push + Coolify redeploy. echo. -pause +if /i not "%~1"=="-q" pause diff --git a/webapp/main.py b/webapp/main.py index fdf3075..0643abc 100644 --- a/webapp/main.py +++ b/webapp/main.py @@ -216,9 +216,17 @@ def _rich_html_with_images(html: str) -> str: return _IMG_PLACEHOLDER_RE.sub(sub, html) +def _fmt_ts(value, n: int = 19) -> str: + return str(value)[:n] if value else "" + + def _row_to_dict(cols, r, *, full_text: bool = False) -> dict: d = dict(zip(cols, r)) - d["updated_at"] = str(d["updated_at"])[:16] if d["updated_at"] else "" + d["updated_at"] = _fmt_ts(d.get("updated_at"), 16) + if "created_at" in d: + d["created_at"] = _fmt_ts(d.get("created_at")) + if "processed_at" in d: + d["processed_at"] = _fmt_ts(d.get("processed_at")) try: d["images"] = json.loads(d["images"]) if d.get("images") else [] except Exception: @@ -251,9 +259,9 @@ def fetch_sections( conn = db_conn() cur = conn.cursor() sql = """ - SELECT s.prefix, s.code, s.title, s.keywords, s.char_count, - s.source_file, s.output_path, s.updated_at, - s.images, s.html_text, f.section_count + SELECT s.id, s.prefix, s.code, s.title, s.keywords, s.char_count, + s.source_file, s.output_path, s.updated_at, s.created_at, + f.processed_at, s.images, s.html_text, f.section_count FROM rip_help_sections s LEFT JOIN rip_help_files f ON f.file_path = s.source_file AND f.prefix = s.prefix @@ -353,9 +361,9 @@ def api_section(code: str): cur = conn.cursor() cur.execute( """ - SELECT s.prefix, s.code, s.title, s.keywords, s.char_count, - s.source_file, s.output_path, s.updated_at, - s.images, s.html_text, f.section_count + SELECT s.id, s.prefix, s.code, s.title, s.keywords, s.char_count, + s.source_file, s.output_path, s.updated_at, s.created_at, + f.processed_at, s.images, s.html_text, f.section_count FROM rip_help_sections s LEFT JOIN rip_help_files f ON f.file_path = s.source_file AND f.prefix = s.prefix diff --git a/webapp/templates/viewer.html b/webapp/templates/viewer.html index 7e240df..9380410 100644 --- a/webapp/templates/viewer.html +++ b/webapp/templates/viewer.html @@ -940,9 +940,42 @@ async function pollRescan(jobId) { } // ── EDITOR ──────────────────────────── +function tsKey(v) { + return v ? String(v) : ''; +} +function rowId(r) { + const n = Number(r && r.id); + return Number.isFinite(n) ? n : 0; +} +function sortEditorRows(rows) { + const fileTs = new Map(); + const fileId = new Map(); + (rows || []).forEach(r => { + const f = (fileBase(r.source_file) || r.source_file || '').toLowerCase(); + const t = tsKey(r.processed_at) || tsKey(r.created_at) || tsKey(r.updated_at); + if (t > (fileTs.get(f) || '')) fileTs.set(f, t); + const id = rowId(r); + if (id > (fileId.get(f) || 0)) fileId.set(f, id); + }); + return [...(rows || [])].sort((a, b) => { + const fa = (fileBase(a.source_file) || a.source_file || '').toLowerCase(); + const fb = (fileBase(b.source_file) || b.source_file || '').toLowerCase(); + const byFileTs = (fileTs.get(fb) || '').localeCompare(fileTs.get(fa) || ''); + if (byFileTs) return byFileTs; + const byFileId = (fileId.get(fb) || 0) - (fileId.get(fa) || 0); + if (byFileId) return byFileId; + const sa = tsKey(a.created_at) || tsKey(a.updated_at); + const sb = tsKey(b.created_at) || tsKey(b.updated_at); + const bySec = sb.localeCompare(sa); + if (bySec) return bySec; + return rowId(b) - rowId(a); + }); +} + function renderEditor(rows) { const tbody = document.getElementById('editor-body'); - tbody.innerHTML = rows.map(r => ` + const sorted = sortEditorRows(rows); + tbody.innerHTML = sorted.map(r => ` ${r.code} ${esc(r.title)} @@ -967,7 +1000,7 @@ function renderEditor(rows) { ${r.updated_at} `).join(''); - document.getElementById('editor-stats').textContent = rows.length + ' реда'; + document.getElementById('editor-stats').textContent = sorted.length + ' реда'; } function filterEditor() {