This commit is contained in:
2026-09-11 14:21:54 +03:00
parent 7eb0472bb6
commit 695fca0d84
3 changed files with 60 additions and 11 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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 => `
<tr>
<td><span class="code-badge">${r.code}</span></td>
<td class="title-cell">${esc(r.title)}</td>
@@ -967,7 +1000,7 @@ function renderEditor(rows) {
<td style="font-size:11px;color:var(--muted);font-family:var(--mono);white-space:nowrap">${r.updated_at}</td>
</tr>
`).join('');
document.getElementById('editor-stats').textContent = rows.length + ' реда';
document.getElementById('editor-stats').textContent = sorted.length + ' реда';
}
function filterEditor() {