mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-02 10:52:22 +00:00
ui: compact the current ProxMenux version status
This commit is contained in:
@@ -47,6 +47,7 @@ NODE_FILES = (
|
||||
"tests/test_reboot_health_refresh.cjs",
|
||||
"tests/test_host_update_controls.cjs",
|
||||
"tests/test_reboot_recovery.cjs",
|
||||
"tests/test_proxmenux_version_row.cjs",
|
||||
"tests/test_lxc_update_jobs.cjs",
|
||||
"tests/test_api_import_boundaries.cjs",
|
||||
"tests/test_api_errors.cjs",
|
||||
|
||||
@@ -5,6 +5,7 @@ import * as Confirm from "@radix-ui/react-alert-dialog"
|
||||
import { Button } from "./ui/button"
|
||||
import { getAuthToken } from "@/lib/api-config"
|
||||
import { useT } from "@/lib/i18n/provider"
|
||||
import { CheckCircle2, AlertTriangle, Info, ChevronRight } from "lucide-react"
|
||||
|
||||
function headers() {
|
||||
const token = getAuthToken()
|
||||
@@ -145,14 +146,32 @@ export function ProxMenuxVersionCard({ getApiUrl }: { getApiUrl: Url }) {
|
||||
return () => { disposed = true; active?.abort(); clearInterval(interval); window.removeEventListener("focus", read); document.removeEventListener("visibilitychange", read) }
|
||||
}, [getApiUrl])
|
||||
const state = value?.state ?? "unknown"
|
||||
return <section aria-label={t("healthStatus.versions.title")} className="mt-3 mx-2 sm:mx-3 rounded-lg border bg-muted/10 p-3 text-[10px] sm:text-xs">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 mb-3"><h3 className="font-semibold">{t("healthStatus.versions.title")}</h3>
|
||||
<span className={state === "unknown" ? "text-amber-500" : state === "available" ? "text-blue-500" : "text-green-500"}>{t(`healthStatus.versions.${state}`)}</span></div>
|
||||
<dl className="grid grid-cols-2 gap-4">
|
||||
const versions = <dl className="grid grid-cols-2 gap-4">
|
||||
<div><dt className="text-muted-foreground mb-1">{t("healthStatus.versions.installed")}</dt><dd className="font-mono font-semibold">{value?.installed ?? t("healthStatus.versions.unverified")}</dd></div>
|
||||
<div><dt className="text-muted-foreground mb-1">{t(state === "unknown" && value?.available ? "healthStatus.versions.lastKnown" : "healthStatus.versions.latest")}</dt><dd className="font-mono font-semibold">{value?.available ?? t("healthStatus.versions.unverified")}</dd></div>
|
||||
</dl>
|
||||
const metadata = <>
|
||||
{value && <p className="mt-3 text-muted-foreground">{t(`healthStatus.versions.${value.channel}`)}</p>}
|
||||
{value?.checked_at && <p className="mt-1 text-muted-foreground">{t("healthStatus.versions.checked", { time: new Date(value.checked_at).toLocaleString() })}</p>}
|
||||
</>
|
||||
if (state !== "available") return <details aria-label={t("healthStatus.versions.title")} className="group text-[10px] sm:text-xs">
|
||||
<summary className="flex items-start gap-2 py-1.5 px-2 sm:px-3 rounded-md hover:bg-muted/40 cursor-pointer list-none [&::-webkit-details-marker]:hidden">
|
||||
{state === "current" ? <CheckCircle2 aria-hidden="true" className="h-3 w-3 sm:h-3.5 sm:w-3.5 shrink-0 text-green-500" /> : <AlertTriangle aria-hidden="true" className="h-3 w-3 sm:h-3.5 sm:w-3.5 shrink-0 text-amber-500" />}
|
||||
<span className="font-medium shrink-0">ProxMenux</span>
|
||||
<span className={`flex-1 min-w-0 break-words ${state === "current" ? "text-muted-foreground" : "text-amber-500"}`}>
|
||||
{state === "current" ? t("healthStatus.versions.currentVersion", { version: value!.installed! }) : t("healthStatus.versions.unknown")}
|
||||
</span>
|
||||
<ChevronRight aria-hidden="true" className="h-3 w-3 shrink-0 text-muted-foreground transition-transform group-open:rotate-90" />
|
||||
</summary>
|
||||
<div className="ml-7 mr-2 sm:mr-3 pb-2">
|
||||
{state === "unknown" && versions}
|
||||
{metadata}
|
||||
</div>
|
||||
</details>
|
||||
return <section aria-label={t("healthStatus.versions.title")} className="mt-3 mx-2 sm:mx-3 rounded-lg border bg-muted/10 p-3 text-[10px] sm:text-xs">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 mb-3"><h3 className="font-semibold">{t("healthStatus.versions.title")}</h3>
|
||||
<span className="flex items-center gap-1.5 text-blue-500"><Info aria-hidden="true" className="h-3.5 w-3.5" />{t("healthStatus.versions.available")}</span></div>
|
||||
{versions}
|
||||
{metadata}
|
||||
</section>
|
||||
}
|
||||
|
||||
@@ -5326,6 +5326,7 @@
|
||||
"lastKnown": "Last known available version",
|
||||
"available": "Update available",
|
||||
"current": "ProxMenux is up to date",
|
||||
"currentVersion": "Version {version} is up to date",
|
||||
"unknown": "Could not verify the available version",
|
||||
"unverified": "Unverified",
|
||||
"stable": "Stable channel",
|
||||
|
||||
@@ -5198,6 +5198,7 @@
|
||||
"lastKnown": "Posledná známa dostupná verzia",
|
||||
"available": "Dostupná aktualizácia",
|
||||
"current": "ProxMenux je aktuálny",
|
||||
"currentVersion": "Verzia {version} je aktuálna",
|
||||
"unknown": "Dostupnú verziu sa nepodarilo overiť",
|
||||
"unverified": "Neoverené",
|
||||
"stable": "Stabilný kanál",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Render the shipped component with an inert snapshot; no network or power.
|
||||
const assert=require('node:assert/strict'),fs=require('node:fs'),path=require('node:path'),vm=require('node:vm')
|
||||
const app=path.resolve(__dirname,'../AppImage'),ts=require(path.join(app,'node_modules/typescript'))
|
||||
const react=require(path.join(app,'node_modules/react'))
|
||||
const {renderToStaticMarkup}=require(path.join(app,'node_modules/react-dom/server'))
|
||||
const code=ts.transpileModule(fs.readFileSync(path.join(app,'components/host-update-status.tsx'),'utf8'),{
|
||||
compilerOptions:{target:ts.ScriptTarget.ES2020,module:ts.ModuleKind.CommonJS,jsx:ts.JsxEmit.ReactJSX,esModuleInterop:true}}).outputText
|
||||
for(const locale of ['en','sk']){
|
||||
const catalog=JSON.parse(fs.readFileSync(path.join(app,'messages',locale,'common.json'),'utf8'))
|
||||
const t=(key,params={})=>{const text=key.split('.').reduce((x,k)=>x?.[k],catalog);assert.equal(typeof text,'string',key);return text.replace(/\{(\w+)\}/g,(_,k)=>params[k]??'{'+k+'}')}
|
||||
const versions=catalog.healthStatus.versions
|
||||
let value, effects=0
|
||||
const exports={}
|
||||
vm.runInNewContext(code,{exports,require:name=>{
|
||||
if(name==='react')return {...react,useState:()=>[value,()=>{}],useEffect:()=>{},useRef:()=>({current:null})}
|
||||
if(name==='react/jsx-runtime')return require(path.join(app,'node_modules/react/jsx-runtime'))
|
||||
if(name==='@radix-ui/react-alert-dialog'||name==='./ui/button')return {}
|
||||
if(name==='@/lib/i18n/provider')return {useT:()=>t}
|
||||
if(name==='@/lib/api-config')return {getAuthToken:()=>null}
|
||||
if(name==='lucide-react')return require(path.join(app,'node_modules/lucide-react'))
|
||||
throw Error('Unexpected boundary '+name)
|
||||
},fetch:()=>{effects++;throw Error('Forbidden request')},console})
|
||||
const snapshot={installed:'1.2.7',available:'1.2.7',state:'current',channel:'stable',checked_at:'2026-10-28T08:00:00Z'}
|
||||
const render=data=>{value=data;return renderToStaticMarkup(react.createElement(exports.ProxMenuxVersionCard,{getApiUrl:p=>p}))}
|
||||
const current=render(snapshot)
|
||||
assert.ok(current.includes('<details')&¤t.includes('<summary'),'current version is a compact native disclosure')
|
||||
assert.ok(!current.includes('<section')&&!current.includes('<dl>'),'up-to-date version has no expanded update card')
|
||||
assert.ok(current.includes('1.2.7')&¤t.includes('text-green-500'))
|
||||
assert.ok(current.includes(versions.stable)&¤t.includes(versions.checked.split('{')[0]))
|
||||
const available=render({...snapshot,state:'available',available:'1.2.8'})
|
||||
assert.ok(available.includes('<section')&&available.includes('<dl'))
|
||||
assert.ok(available.includes('1.2.7')&&available.includes('1.2.8')&&available.includes(versions.available))
|
||||
assert.ok(available.includes('text-blue-500'))
|
||||
for(const data of [null,{...snapshot,state:'unknown'},{...snapshot,state:'unknown',available:null,checked_at:null,channel:'beta'}]){
|
||||
const unknown=render(data)
|
||||
assert.ok(unknown.includes('<details')&&unknown.includes('text-amber-500'))
|
||||
assert.ok(unknown.includes(versions.unknown)&&!unknown.includes('text-green-500'))
|
||||
if(data?.available)assert.ok(unknown.includes(versions.lastKnown),'old version remains explicitly last known')
|
||||
else assert.ok(unknown.includes(versions.unverified),'missing data is never a current version')
|
||||
}
|
||||
assert.equal(effects,0,'rendering/disclosure never triggers update, installation or power')
|
||||
}
|
||||
console.log('PASS actual ProxMenux version rendering: compact current, expanded update, unknown/last-known; EN/SK and zero effects')
|
||||
Reference in New Issue
Block a user