show terminal expiry and fix SPA fallback

This commit is contained in:
lda
2026-07-21 00:34:29 +07:00 Verified
parent 0ff4a91cc2
commit 4eca9ea297
6 changed files with 61 additions and 10 deletions
Generated
+1 -1
View File
@@ -3728,7 +3728,7 @@ dependencies = [
[[package]] [[package]]
name = "wakey-vt100" name = "wakey-vt100"
version = "0.16.2-wakey.1" version = "0.16.2-wakey.2"
dependencies = [ dependencies = [
"itoa", "itoa",
"unicode-width", "unicode-width",
+57 -5
View File
@@ -178,6 +178,36 @@ function terminalCreatedTime(createdAtUnix: number): string {
}); });
} }
function terminalTimestamp(timestampUnix: number): string {
return new Date(timestampUnix * 1000).toLocaleString();
}
function terminalExpiryDisplay(
expiresAtUnix: number | null | undefined,
nowUnix: number,
): { label: string; warning: boolean } | null {
if (expiresAtUnix == null) return null;
const remainingSeconds = expiresAtUnix - nowUnix;
if (remainingSeconds <= 0) return { label: "expired", warning: true };
if (remainingSeconds < 60) return { label: "<1m left", warning: true };
if (remainingSeconds < 3600) {
return {
label: `${Math.floor(remainingSeconds / 60)}m left`,
warning: remainingSeconds < 10 * 60,
};
}
if (remainingSeconds < 24 * 3600) {
return {
label: `${Math.floor(remainingSeconds / 3600)}h left`,
warning: false,
};
}
return {
label: `${Math.floor(remainingSeconds / (24 * 3600))}d left`,
warning: false,
};
}
function applyTerminalModifiers( function applyTerminalModifiers(
data: string, data: string,
modifiers: TerminalModifiers, modifiers: TerminalModifiers,
@@ -230,6 +260,7 @@ export function TerminalPage({
const [terminalFontSize, setTerminalFontSize] = useState( const [terminalFontSize, setTerminalFontSize] = useState(
DEFAULT_TERMINAL_FONT_SIZE, DEFAULT_TERMINAL_FONT_SIZE,
); );
const [nowUnix, setNowUnix] = useState(() => Math.floor(Date.now() / 1000));
const [modifiers, setModifiers] = useState<TerminalModifiers>( const [modifiers, setModifiers] = useState<TerminalModifiers>(
NO_TERMINAL_MODIFIERS, NO_TERMINAL_MODIFIERS,
); );
@@ -238,6 +269,14 @@ export function TerminalPage({
); );
const [operatorId] = useState(terminalOperatorId); const [operatorId] = useState(terminalOperatorId);
useEffect(() => {
const clock = window.setInterval(
() => setNowUnix(Math.floor(Date.now() / 1000)),
30_000,
);
return () => window.clearInterval(clock);
}, []);
activeSessionRef.current = session; activeSessionRef.current = session;
selectedAgentIdRef.current = selectedAgentId; selectedAgentIdRef.current = selectedAgentId;
@@ -918,6 +957,14 @@ export function TerminalPage({
const tabLabel = sessionTitle const tabLabel = sessionTitle
? `${agentLabel} · ${sessionTitle}` ? `${agentLabel} · ${sessionTitle}`
: agentLabel; : agentLabel;
const expiry = terminalExpiryDisplay(
item.expires_at_unix,
nowUnix,
);
const timingTitle =
item.expires_at_unix != null
? `Expires ${terminalTimestamp(item.expires_at_unix)}`
: "No expiry";
return ( return (
<div <div
key={item.terminal_id} key={item.terminal_id}
@@ -932,9 +979,7 @@ export function TerminalPage({
aria-selected={active} aria-selected={active}
className="terminal-tab-select" className="terminal-tab-select"
disabled={locked || connection === "connecting"} disabled={locked || connection === "connecting"}
title={ title={`${locked ? "Attached in another browser\n" : ""}${tabLabel}\nCreated ${terminalTimestamp(item.created_at_unix)}\n${timingTitle}`}
locked ? "Attached in another browser" : tabLabel
}
onClick={() => void activateSession(item)} onClick={() => void activateSession(item)}
> >
<span <span
@@ -952,11 +997,18 @@ export function TerminalPage({
/> />
<span className="terminal-tab-label">{tabLabel}</span> <span className="terminal-tab-label">{tabLabel}</span>
<time <time
className={
expiry?.warning
? "text-amber-400"
: "text-[#7f8d9a]"
}
dateTime={new Date( dateTime={new Date(
item.created_at_unix * 1000, (item.expires_at_unix ?? item.created_at_unix) *
1000,
).toISOString()} ).toISOString()}
> >
{terminalCreatedTime(item.created_at_unix)} {expiry?.label ??
terminalCreatedTime(item.created_at_unix)}
</time> </time>
</button> </button>
<button <button
-1
View File
@@ -488,7 +488,6 @@
.terminal-tab time { .terminal-tab time {
flex: 0 0 auto; flex: 0 0 auto;
color: #7f8d9a;
font-size: 0.6875rem; font-size: 0.6875rem;
} }
+1 -1
+1 -1
View File
@@ -39,6 +39,6 @@ tracing-subscriber = { version = "0.3", features = [
] } ] }
time = { version = "0.3", features = ["formatting", "local-offset"] } time = { version = "0.3", features = ["formatting", "local-offset"] }
url = "2" url = "2"
vt100 = { package = "wakey-vt100", path = "../vendor/vt100", registry = "gitea", version = "=0.16.2-wakey.1" } vt100 = { package = "wakey-vt100", path = "../vendor/vt100", registry = "gitea", version = "=0.16.2-wakey.2" }
wakey = { path = "..", registry = "gitea", version = "0" } wakey = { path = "..", registry = "gitea", version = "0" }
wakey-core = { path = "../wakey-core", registry = "gitea", version = "0" } wakey-core = { path = "../wakey-core", registry = "gitea", version = "0" }
+1 -1
View File
@@ -71,7 +71,7 @@ fn public_api_routes(ui_dist_dir: std::path::PathBuf) -> Router<AppState> {
.route("/", get(|| async { Redirect::temporary("/ui/") })) // same as caddyfile .route("/", get(|| async { Redirect::temporary("/ui/") })) // same as caddyfile
.nest_service( .nest_service(
"/ui/", "/ui/",
get_service(ServeDir::new(ui_dist_dir).not_found_service(ServeFile::new(index_file))), get_service(ServeDir::new(ui_dist_dir).fallback(ServeFile::new(index_file))),
) )
.route("/healthz", get(api::healthz)) .route("/healthz", get(api::healthz))
.route("/api/v1/agents/enroll", post(api::enroll)) .route("/api/v1/agents/enroll", post(api::enroll))