@hoox-sh/hoox-tui 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +247 -0
- package/bin/hoox-tui.js +35 -0
- package/dist/main.js +17594 -0
- package/package.json +72 -0
- package/src/app.tsx +696 -0
- package/src/cli-bridge-test-double.ts +212 -0
- package/src/components/layout/layout.test.tsx +205 -0
- package/src/components/layout/sidebar.tsx +69 -0
- package/src/components/layout/statusbar.test.tsx +423 -0
- package/src/components/layout/statusbar.tsx +348 -0
- package/src/components/shared/animated-border.tsx +58 -0
- package/src/components/shared/chrome.test.tsx +29 -0
- package/src/components/shared/command-palette.tsx +361 -0
- package/src/components/shared/cool-brackets.tsx +75 -0
- package/src/components/shared/crash-screen.tsx +167 -0
- package/src/components/shared/error-boundary.tsx +99 -0
- package/src/components/shared/keybinding-hint.tsx +37 -0
- package/src/components/shared/panel.tsx +61 -0
- package/src/components/shared/quit-modal.tsx +65 -0
- package/src/components/shared/shared.test.tsx +538 -0
- package/src/components/shared/spinner.tsx +85 -0
- package/src/components/shared/status-dot.tsx +54 -0
- package/src/components/shared/view-header.tsx +53 -0
- package/src/components/ui/connection-toasts.test.ts +50 -0
- package/src/components/ui/connection-toasts.ts +215 -0
- package/src/components/ui/dialog.tsx +281 -0
- package/src/components/ui/select.tsx +197 -0
- package/src/components/ui/toast.tsx +185 -0
- package/src/components/views/ai-chat.test.tsx +81 -0
- package/src/components/views/ai-chat.tsx +576 -0
- package/src/components/views/config-editor/action-bar.tsx +102 -0
- package/src/components/views/config-editor/code-editor.tsx +597 -0
- package/src/components/views/config-editor/file-tree.tsx +113 -0
- package/src/components/views/config-editor/types.ts +37 -0
- package/src/components/views/config-editor.test.tsx +465 -0
- package/src/components/views/config-editor.tsx +709 -0
- package/src/components/views/dashboard/alerts-panel.tsx +154 -0
- package/src/components/views/dashboard/auto-repair-panel.tsx +229 -0
- package/src/components/views/dashboard/model-health-section.tsx +276 -0
- package/src/components/views/dashboard/pyne-health-section.tsx +112 -0
- package/src/components/views/dashboard.test.tsx +347 -0
- package/src/components/views/dashboard.tsx +647 -0
- package/src/components/views/db-query.test.tsx +115 -0
- package/src/components/views/db-query.tsx +849 -0
- package/src/components/views/edge-topology.test.tsx +198 -0
- package/src/components/views/edge-topology.tsx +572 -0
- package/src/components/views/kv-viewer.test.tsx +241 -0
- package/src/components/views/kv-viewer.tsx +717 -0
- package/src/components/views/logs-viewer.test.tsx +273 -0
- package/src/components/views/logs-viewer.tsx +616 -0
- package/src/components/views/queue-depth.test.tsx +221 -0
- package/src/components/views/queue-depth.tsx +376 -0
- package/src/components/views/secrets-viewer.test.tsx +65 -0
- package/src/components/views/secrets-viewer.tsx +548 -0
- package/src/components/views/service-manager.test.tsx +358 -0
- package/src/components/views/service-manager.tsx +946 -0
- package/src/components/views/settings.test.tsx +648 -0
- package/src/components/views/settings.tsx +1497 -0
- package/src/components/views/setup-wizard.test.tsx +297 -0
- package/src/components/views/setup-wizard.tsx +1321 -0
- package/src/components/views/trade-monitor.test.tsx +592 -0
- package/src/components/views/trade-monitor.tsx +745 -0
- package/src/components/views/worker-detail.test.tsx +289 -0
- package/src/components/views/worker-detail.tsx +676 -0
- package/src/components/views/worker-settings.test.tsx +43 -0
- package/src/components/views/worker-settings.tsx +563 -0
- package/src/components/views/workers-overview.test.tsx +260 -0
- package/src/components/views/workers-overview.tsx +631 -0
- package/src/hooks/hooks.test.ts +72 -0
- package/src/hooks/index.ts +11 -0
- package/src/hooks/renderer-ref.ts +22 -0
- package/src/hooks/use-keyboard.ts +130 -0
- package/src/hooks/use-polling.ts +73 -0
- package/src/hooks/use-service-data.ts +18 -0
- package/src/main.tsx +117 -0
- package/src/network-test-double.ts +133 -0
- package/src/opentui-augment.d.ts +45 -0
- package/src/opentui-jsx.d.ts +70 -0
- package/src/services/cli-bridge/index.ts +1662 -0
- package/src/services/cli-bridge/standalone.ts +315 -0
- package/src/services/cli-bridge/types.ts +307 -0
- package/src/services/cli-bridge.test.ts +406 -0
- package/src/services/clipboard.test.ts +57 -0
- package/src/services/clipboard.ts +180 -0
- package/src/services/dashboard-settings-loader.ts +31 -0
- package/src/services/dev-log.test.ts +168 -0
- package/src/services/dev-log.ts +194 -0
- package/src/services/hoox-path-service.test.ts +183 -0
- package/src/services/hoox-path-service.ts +185 -0
- package/src/services/tui-connection.test.ts +212 -0
- package/src/services/tui-connection.ts +236 -0
- package/src/services/tui-storage.test.ts +92 -0
- package/src/services/tui-storage.ts +86 -0
- package/src/stores/config-store.test.ts +360 -0
- package/src/stores/service-store.test.ts +1026 -0
- package/src/stores/session-restore.test.ts +134 -0
- package/src/stores/ui-store.test.ts +276 -0
- package/src/test-setup.ts +265 -0
- package/src/test-utils.ts +179 -0
- package/src/types.ts +153 -0
- package/src/utils/colors.test.ts +326 -0
- package/src/utils/formatters.test.ts +236 -0
- package/src/view-registry.test.ts +132 -0
- package/src/view-registry.tsx +368 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @jsxImportSource @opentui/react */
|
|
7
|
+
import { useState, useEffect } from "react";
|
|
8
|
+
import { Colors } from "@hoox-sh/hoox-shared";
|
|
9
|
+
|
|
10
|
+
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
11
|
+
|
|
12
|
+
export interface SpinnerProps {
|
|
13
|
+
label?: string;
|
|
14
|
+
color?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function Spinner({
|
|
18
|
+
label = "Loading...",
|
|
19
|
+
color = Colors.highlight,
|
|
20
|
+
}: SpinnerProps) {
|
|
21
|
+
const [frame, setFrame] = useState(0);
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
// Skip the animation loop in test environments. The OpenTUI test
|
|
25
|
+
// runner wraps renders in `act(...)`; an interval that fires every
|
|
26
|
+
// 80ms triggers state updates that aren't wrapped in `act`, which
|
|
27
|
+
// surfaces as noisy warnings and (in some configurations) causes
|
|
28
|
+
// related test assertions to fail. Tests are not validating
|
|
29
|
+
// animation timing, so a static spinner is correct here.
|
|
30
|
+
if (
|
|
31
|
+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
|
|
32
|
+
.IS_REACT_ACT_ENVIRONMENT === true
|
|
33
|
+
) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const timer = setInterval(() => {
|
|
38
|
+
setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
|
|
39
|
+
}, 80);
|
|
40
|
+
return () => clearInterval(timer);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<box flexDirection="row" gap={1} alignItems="center">
|
|
45
|
+
<text fg={color}>{SPINNER_FRAMES[frame]}</text>
|
|
46
|
+
<text fg={Colors.muted} dim>
|
|
47
|
+
{label}
|
|
48
|
+
</text>
|
|
49
|
+
</box>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface EmptyStateProps {
|
|
54
|
+
message: string;
|
|
55
|
+
suggestion?: string;
|
|
56
|
+
icon?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function EmptyState({
|
|
60
|
+
message,
|
|
61
|
+
suggestion,
|
|
62
|
+
icon = "∅",
|
|
63
|
+
}: EmptyStateProps) {
|
|
64
|
+
return (
|
|
65
|
+
<box
|
|
66
|
+
flexDirection="column"
|
|
67
|
+
alignItems="center"
|
|
68
|
+
justifyContent="center"
|
|
69
|
+
paddingY={2}
|
|
70
|
+
gap={1}
|
|
71
|
+
>
|
|
72
|
+
<text fg={Colors.muted} dim>
|
|
73
|
+
{icon}
|
|
74
|
+
</text>
|
|
75
|
+
<text fg={Colors.foreground} dim>
|
|
76
|
+
{message}
|
|
77
|
+
</text>
|
|
78
|
+
{suggestion && (
|
|
79
|
+
<text fg={Colors.muted} dim>
|
|
80
|
+
{suggestion}
|
|
81
|
+
</text>
|
|
82
|
+
)}
|
|
83
|
+
</box>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @jsxImportSource @opentui/react */
|
|
7
|
+
/**
|
|
8
|
+
* StatusDot — a compact color-coded indicator for operational state.
|
|
9
|
+
*
|
|
10
|
+
* Renders a distinct terminal character per status:
|
|
11
|
+
* - operational → "█" green (WorkerStatusColor.operational)
|
|
12
|
+
* - degraded → "▌" amber (WorkerStatusColor.degraded)
|
|
13
|
+
* - down → "░" red (WorkerStatusColor.down), dimmed
|
|
14
|
+
*
|
|
15
|
+
* Optional `pulse` blinks the character for operational status
|
|
16
|
+
* (uses the Text `blink` attribute to pulse).
|
|
17
|
+
*/
|
|
18
|
+
import { WorkerStatusColor } from "@hoox-sh/hoox-shared";
|
|
19
|
+
|
|
20
|
+
// ── Types ──────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export type StatusDotStatus = "operational" | "degraded" | "down";
|
|
23
|
+
|
|
24
|
+
export interface StatusDotProps {
|
|
25
|
+
/** The operational status to display */
|
|
26
|
+
status: StatusDotStatus;
|
|
27
|
+
/** Whether to pulse/blink the indicator (only visible on 'operational') */
|
|
28
|
+
pulse?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Character & color lookup ───────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
const DOT_CHAR: Record<StatusDotStatus, string> = {
|
|
34
|
+
operational: "█",
|
|
35
|
+
degraded: "▌",
|
|
36
|
+
down: "░",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const DOT_COLOR = WorkerStatusColor;
|
|
40
|
+
|
|
41
|
+
// ── Component ──────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
export function StatusDot({ status, pulse = false }: StatusDotProps) {
|
|
44
|
+
const char = DOT_CHAR[status];
|
|
45
|
+
const color = DOT_COLOR[status];
|
|
46
|
+
const isDown = status === "down";
|
|
47
|
+
const shouldBlink = pulse && status === "operational";
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<text fg={color} bold={!isDown} dim={isDown} blink={shouldBlink}>
|
|
51
|
+
{char}
|
|
52
|
+
</text>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** @jsxImportSource @opentui/react */
|
|
7
|
+
/**
|
|
8
|
+
* ViewHeader — shared title row for high-traffic views.
|
|
9
|
+
*
|
|
10
|
+
* Cool-bracketed title on the left, optional right-side meta, dim rule under.
|
|
11
|
+
* OpenTUI: never nest <text> inside <text>.
|
|
12
|
+
*/
|
|
13
|
+
import { Colors } from "@hoox-sh/hoox-shared";
|
|
14
|
+
import type { ReactNode } from "react";
|
|
15
|
+
import { CoolBrackets } from "./cool-brackets";
|
|
16
|
+
|
|
17
|
+
export interface ViewHeaderProps {
|
|
18
|
+
title: string;
|
|
19
|
+
/** Right-aligned meta (counts, status) — string or sibling text nodes */
|
|
20
|
+
meta?: ReactNode;
|
|
21
|
+
/** Dim horizontal rule under the title row (default true) */
|
|
22
|
+
showDivider?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ViewHeader({
|
|
26
|
+
title,
|
|
27
|
+
meta,
|
|
28
|
+
showDivider = true,
|
|
29
|
+
}: ViewHeaderProps) {
|
|
30
|
+
return (
|
|
31
|
+
<box
|
|
32
|
+
flexDirection="row"
|
|
33
|
+
gap={2}
|
|
34
|
+
paddingBottom={showDivider ? 1 : 0}
|
|
35
|
+
borderBottom={showDivider}
|
|
36
|
+
borderStyle={showDivider ? "single" : undefined}
|
|
37
|
+
borderColor={showDivider ? Colors.border : undefined}
|
|
38
|
+
justifyContent="space-between"
|
|
39
|
+
width="100%"
|
|
40
|
+
>
|
|
41
|
+
<CoolBrackets open="┌" close="┐" gap={1}>
|
|
42
|
+
<text fg={Colors.accent} bold>
|
|
43
|
+
{title}
|
|
44
|
+
</text>
|
|
45
|
+
</CoolBrackets>
|
|
46
|
+
{meta ? (
|
|
47
|
+
<box flexDirection="row" gap={1}>
|
|
48
|
+
{meta}
|
|
49
|
+
</box>
|
|
50
|
+
) : null}
|
|
51
|
+
</box>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from "bun:test";
|
|
7
|
+
import {
|
|
8
|
+
messageAuthMissing,
|
|
9
|
+
messageAuthRequired,
|
|
10
|
+
messageConnected,
|
|
11
|
+
messageConnectionLost,
|
|
12
|
+
messageOfflineStartup,
|
|
13
|
+
messageReconnected,
|
|
14
|
+
} from "./connection-toasts";
|
|
15
|
+
|
|
16
|
+
describe("connection-toasts messages", () => {
|
|
17
|
+
it("connected includes mode and host", () => {
|
|
18
|
+
expect(messageConnected("local", "localhost:8787")).toBe(
|
|
19
|
+
"Connected · LOCAL · localhost:8787"
|
|
20
|
+
);
|
|
21
|
+
expect(messageConnected("remote", "hoox.example.workers.dev")).toBe(
|
|
22
|
+
"Connected · REMOTE · hoox.example.workers.dev"
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("lost includes mode and host", () => {
|
|
27
|
+
expect(messageConnectionLost("remote", "gw.test")).toContain("REMOTE");
|
|
28
|
+
expect(messageConnectionLost("local", "localhost:8787")).toContain("LOCAL");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("reconnected includes downtime", () => {
|
|
32
|
+
const msg = messageReconnected("remote", "gw.test", 1_000, 61_000);
|
|
33
|
+
expect(msg).toContain("Reconnected");
|
|
34
|
+
expect(msg).toContain("REMOTE");
|
|
35
|
+
expect(msg).toContain("gw.test");
|
|
36
|
+
expect(msg).toContain("downtime");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("auth messages are explicit", () => {
|
|
40
|
+
expect(messageAuthRequired("remote", "gw")).toContain("Auth failed");
|
|
41
|
+
expect(messageAuthMissing("gw")).toContain("No API credentials");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("offline startup distinguishes auth", () => {
|
|
45
|
+
expect(messageOfflineStartup("remote", "gw", "auth")).toContain("(auth)");
|
|
46
|
+
expect(
|
|
47
|
+
messageOfflineStartup("local", "localhost:8787", "network")
|
|
48
|
+
).not.toContain("(auth)");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2026 HOOX · HOOX · jango-blockchained (hoox-sh)
|
|
3
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Connection toast notifications — mode-aware messages for LOCAL/REMOTE
|
|
8
|
+
* state changes (connected, lost, reconnected, auth, rate limit).
|
|
9
|
+
*
|
|
10
|
+
* Uses the Hoox-styled toast helpers from ./toast.tsx.
|
|
11
|
+
* Message builders are pure and exported for unit tests.
|
|
12
|
+
*/
|
|
13
|
+
import { toastSuccess, toastWarning, toastInfo, toastError } from "./toast";
|
|
14
|
+
import { formatDuration } from "@hoox-sh/hoox-shared";
|
|
15
|
+
import type { TuiMode } from "../../services/tui-connection";
|
|
16
|
+
import { remoteAuthMissingHint } from "../../services/tui-connection";
|
|
17
|
+
|
|
18
|
+
// ─── Pure message builders (testable without toast side effects) ─────────────
|
|
19
|
+
|
|
20
|
+
export function messageConnected(mode: TuiMode, host: string): string {
|
|
21
|
+
const label = mode === "remote" ? "REMOTE" : "LOCAL";
|
|
22
|
+
return `Connected · ${label} · ${host}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function messageConnectionLost(mode: TuiMode, host: string): string {
|
|
26
|
+
const label = mode === "remote" ? "REMOTE" : "LOCAL";
|
|
27
|
+
return `Connection lost · ${label} · ${host}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function messageReconnected(
|
|
31
|
+
mode: TuiMode,
|
|
32
|
+
host: string,
|
|
33
|
+
disconnectedAt: number,
|
|
34
|
+
now: number = Date.now()
|
|
35
|
+
): string {
|
|
36
|
+
const label = mode === "remote" ? "REMOTE" : "LOCAL";
|
|
37
|
+
const duration = formatDuration(Math.max(0, now - disconnectedAt));
|
|
38
|
+
return `Reconnected · ${label} · ${host} · ${duration} downtime`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function messageAuthRequired(mode: TuiMode, host: string): string {
|
|
42
|
+
const label = mode === "remote" ? "REMOTE" : "LOCAL";
|
|
43
|
+
return `Auth failed · ${label} · ${host}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function messageAuthMissing(host: string): string {
|
|
47
|
+
return `No API credentials · REMOTE · ${host}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function messageOfflineStartup(
|
|
51
|
+
mode: TuiMode,
|
|
52
|
+
host: string,
|
|
53
|
+
kind: "auth" | "network" | "rate-limit" | "unknown"
|
|
54
|
+
): string {
|
|
55
|
+
const label = mode === "remote" ? "REMOTE" : "LOCAL";
|
|
56
|
+
if (kind === "auth") {
|
|
57
|
+
return `Could not connect · ${label} · ${host} (auth)`;
|
|
58
|
+
}
|
|
59
|
+
if (kind === "rate-limit") {
|
|
60
|
+
return `Could not connect · ${label} · ${host} (rate limited)`;
|
|
61
|
+
}
|
|
62
|
+
return `Could not connect · ${label} · ${host}`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─── Toast factory functions ─────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Show a "Reconnected" toast with downtime duration.
|
|
69
|
+
* Called when the connection state transitions back to 'connected' after a
|
|
70
|
+
* disconnection period.
|
|
71
|
+
*
|
|
72
|
+
* @param disconnectedAt Timestamp (ms) when the disconnection began
|
|
73
|
+
* @deprecated Prefer {@link toastReconnectedMode} for mode-aware copy
|
|
74
|
+
*/
|
|
75
|
+
export function toastReconnected(disconnectedAt: number): void {
|
|
76
|
+
const now = Date.now();
|
|
77
|
+
const downtime = now - disconnectedAt;
|
|
78
|
+
const duration = formatDuration(downtime);
|
|
79
|
+
toastSuccess(`Reconnected \u2022 ${duration} downtime`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Mode-aware reconnect toast. */
|
|
83
|
+
export function toastReconnectedMode(
|
|
84
|
+
mode: TuiMode,
|
|
85
|
+
host: string,
|
|
86
|
+
disconnectedAt: number
|
|
87
|
+
): void {
|
|
88
|
+
toastSuccess(messageReconnected(mode, host, disconnectedAt), {
|
|
89
|
+
duration: 4000,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Show a rate limit warning toast.
|
|
95
|
+
* Called when the API returns HTTP 429.
|
|
96
|
+
*/
|
|
97
|
+
export function toastRateLimited(): void {
|
|
98
|
+
toastWarning("API rate limited \u2014 backing off", {
|
|
99
|
+
duration: 6000,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Show a config saved confirmation toast.
|
|
105
|
+
* Called after config-store persists to disk.
|
|
106
|
+
*/
|
|
107
|
+
export function toastConfigSaved(): void {
|
|
108
|
+
toastSuccess("Configuration saved", {
|
|
109
|
+
duration: 3000,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Show a deploy started toast (loading/persistent).
|
|
115
|
+
* Returns the toast ID so it can be updated to success/error when deploy completes.
|
|
116
|
+
*
|
|
117
|
+
* @param workerName Name of the worker being deployed
|
|
118
|
+
* @returns Toast ID for updating later
|
|
119
|
+
*/
|
|
120
|
+
export function toastDeployStarted(workerName: string): string | number {
|
|
121
|
+
return toastInfo(`Deploy started: ${workerName}`, {
|
|
122
|
+
duration: 0, // persistent until updated
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Show a deploy completed toast.
|
|
128
|
+
* Updates or creates a success toast for the completed deployment.
|
|
129
|
+
*
|
|
130
|
+
* @param workerName Name of the deployed worker
|
|
131
|
+
*/
|
|
132
|
+
export function toastDeployCompleted(workerName: string): void {
|
|
133
|
+
toastSuccess(`Deploy completed: ${workerName}`, {
|
|
134
|
+
duration: 4000,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Show a deploy failed toast.
|
|
140
|
+
*
|
|
141
|
+
* @param workerName Name of the worker that failed to deploy
|
|
142
|
+
* @param error Error message
|
|
143
|
+
*/
|
|
144
|
+
export function toastDeployFailed(workerName: string, error: string): void {
|
|
145
|
+
toastWarning(`Deploy failed: ${workerName} \u2014 ${error}`, {
|
|
146
|
+
duration: 8000,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Show a connection lost warning toast.
|
|
152
|
+
* Called when the connection transitions to 'offline'.
|
|
153
|
+
* @deprecated Prefer {@link toastConnectionLostMode}
|
|
154
|
+
*/
|
|
155
|
+
export function toastConnectionLost(): void {
|
|
156
|
+
toastWarning("Connection lost \u2014 retrying with backoff", {
|
|
157
|
+
duration: 0, // persistent until reconnected
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Mode-aware connection lost toast. */
|
|
162
|
+
export function toastConnectionLostMode(mode: TuiMode, host: string): void {
|
|
163
|
+
toastWarning(messageConnectionLost(mode, host), {
|
|
164
|
+
description:
|
|
165
|
+
mode === "remote"
|
|
166
|
+
? "Check gateway URL, network, and HOOX_API_TOKEN"
|
|
167
|
+
: "Is `hoox dev` / wrangler running on this host?",
|
|
168
|
+
duration: 0,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** First successful connect (startup or after offline). */
|
|
173
|
+
export function toastConnectedMode(mode: TuiMode, host: string): void {
|
|
174
|
+
toastSuccess(messageConnected(mode, host), { duration: 3500 });
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** HTTP 401/403 while talking to the API. */
|
|
178
|
+
export function toastAuthRequiredMode(mode: TuiMode, host: string): void {
|
|
179
|
+
toastError(messageAuthRequired(mode, host), {
|
|
180
|
+
description:
|
|
181
|
+
mode === "remote"
|
|
182
|
+
? remoteAuthMissingHint()
|
|
183
|
+
: "Check HOOX_API_TOKEN if the local gateway enforces auth",
|
|
184
|
+
duration: 8000,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Remote launch without fail-closed auth (no Bearer and no Access pair).
|
|
190
|
+
* Prefer gating with `resolveTuiConnectionEnv().hasAuth` / `isRemoteAuthReady()`.
|
|
191
|
+
*/
|
|
192
|
+
export function toastAuthMissingRemote(host: string): void {
|
|
193
|
+
toastWarning(messageAuthMissing(host), {
|
|
194
|
+
description: remoteAuthMissingHint(),
|
|
195
|
+
duration: 7000,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Startup could not reach the API. */
|
|
200
|
+
export function toastOfflineStartup(
|
|
201
|
+
mode: TuiMode,
|
|
202
|
+
host: string,
|
|
203
|
+
kind: "auth" | "network" | "rate-limit" | "unknown"
|
|
204
|
+
): void {
|
|
205
|
+
const description =
|
|
206
|
+
kind === "auth"
|
|
207
|
+
? remoteAuthMissingHint()
|
|
208
|
+
: mode === "remote"
|
|
209
|
+
? "Remote gateway unreachable — CLI fallback is disabled in REMOTE mode"
|
|
210
|
+
: "Trying CLI fallback if available";
|
|
211
|
+
toastWarning(messageOfflineStartup(mode, host, kind), {
|
|
212
|
+
description,
|
|
213
|
+
duration: 6000,
|
|
214
|
+
});
|
|
215
|
+
}
|