Add service information feature with version checks
All checks were successful
PostgreSQL Compatibility Matrix / PG14 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG15 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG16 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG17 smoke (push) Successful in 8s
PostgreSQL Compatibility Matrix / PG18 smoke (push) Successful in 8s

This commit introduces a new "Service Information" section displaying runtime details, installed version, and update status for the NexaPG application. It includes backend API endpoints, database schema changes, and a corresponding frontend page that allows users to check for updates against the official repository. The `.env` example now includes an `APP_VERSION` variable, and related documentation has been updated.
This commit is contained in:
2026-02-13 08:54:13 +01:00
parent fd24a3a548
commit 0445a72764
12 changed files with 462 additions and 1 deletions

View File

@@ -0,0 +1,133 @@
import React, { useEffect, useState } from "react";
import { apiFetch } from "../api";
import { useAuth } from "../state";
function formatUptime(seconds) {
const total = Math.max(0, Number(seconds || 0));
const d = Math.floor(total / 86400);
const h = Math.floor((total % 86400) / 3600);
const m = Math.floor((total % 3600) / 60);
const s = total % 60;
if (d > 0) return `${d}d ${h}h ${m}m`;
if (h > 0) return `${h}h ${m}m ${s}s`;
return `${m}m ${s}s`;
}
export function ServiceInfoPage() {
const { tokens, refresh } = useAuth();
const [info, setInfo] = useState(null);
const [message, setMessage] = useState("");
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const load = async () => {
setError("");
const data = await apiFetch("/service/info", {}, tokens, refresh);
setInfo(data);
};
useEffect(() => {
load().catch((e) => setError(String(e.message || e)));
}, []);
const checkNow = async () => {
try {
setBusy(true);
setError("");
setMessage("");
const result = await apiFetch("/service/info/check", { method: "POST" }, tokens, refresh);
await load();
if (result.last_check_error) {
setMessage(`Version check finished with warning: ${result.last_check_error}`);
} else if (result.update_available) {
setMessage(`Update available: ${result.latest_version}`);
} else {
setMessage("Version check completed. No update detected.");
}
} catch (e) {
setError(String(e.message || e));
} finally {
setBusy(false);
}
};
if (!info) {
return <div className="card">Loading service information...</div>;
}
return (
<div>
<h2>Service Information</h2>
<p className="muted">Runtime details, installed version, and update check status for this NexaPG instance.</p>
{error && <div className="card error">{error}</div>}
{message && <div className="test-connection-result ok">{message}</div>}
<div className="grid three">
<div className="card">
<h3>Application</h3>
<div className="overview-kv">
<span>App Name</span>
<strong>{info.app_name}</strong>
<span>Environment</span>
<strong>{info.environment}</strong>
<span>API Prefix</span>
<strong>{info.api_prefix}</strong>
</div>
</div>
<div className="card">
<h3>Runtime</h3>
<div className="overview-kv">
<span>Host</span>
<strong>{info.hostname}</strong>
<span>Python</span>
<strong>{info.python_version}</strong>
<span>Uptime</span>
<strong>{formatUptime(info.uptime_seconds)}</strong>
</div>
</div>
<div className="card">
<h3>Version Status</h3>
<div className="overview-kv">
<span>Current NexaPG Version</span>
<strong>{info.app_version}</strong>
<span>Latest Known Version</span>
<strong>{info.latest_version || "-"}</strong>
<span>Update Status</span>
<strong className={info.update_available ? "lag-bad" : "pill primary"}>
{info.update_available ? "Update available" : "Up to date"}
</strong>
<span>Last Check</span>
<strong>{info.last_checked_at ? new Date(info.last_checked_at).toLocaleString() : "never"}</strong>
</div>
<div className="form-actions" style={{ marginTop: 12 }}>
<button type="button" className="secondary-btn" disabled={busy} onClick={checkNow}>
Check for Updates
</button>
</div>
</div>
</div>
<div className="card">
<h3>Release Source</h3>
<p className="muted">
Update checks run against the official NexaPG repository. This source is fixed in code and cannot be changed
via UI.
</p>
<div className="overview-kv">
<span>Source Repository</span>
<strong>{info.update_source}</strong>
<span>Latest Reference Type</span>
<strong>{info.latest_ref || "-"}</strong>
</div>
</div>
<div className="card">
<h3>Version Control Policy</h3>
<p className="muted">
Version and update-source settings are not editable in the app. Only code maintainers of the official NexaPG
repository can change that behavior.
</p>
</div>
</div>
);
}