show terminal expiry and fix SPA fallback
This commit is contained in:
Generated
+1
-1
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
Submodule vendor/vt100 updated: 7065eac180...56e9e0be17
@@ -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" }
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user