mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 21:22:28 +03:00
fix(video-bridge): clarify remote runtime status
This commit is contained in:
@@ -27,6 +27,11 @@ interface RuntimeStatus {
|
||||
ffmpegVersion: string | null;
|
||||
ffprobeVersion: string | null;
|
||||
reason?: string;
|
||||
restricted?: boolean;
|
||||
}
|
||||
|
||||
interface ModalityBridgeVideoTabProps {
|
||||
runtimeHostname?: string;
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
@@ -46,6 +51,14 @@ function fromApi(value: unknown): VideoState {
|
||||
|
||||
function parseRuntimeStatus(value: unknown): RuntimeStatus | null {
|
||||
const record = asRecord(value);
|
||||
if (record.restricted === true) {
|
||||
return {
|
||||
available: false,
|
||||
ffmpegVersion: null,
|
||||
ffprobeVersion: null,
|
||||
restricted: true,
|
||||
};
|
||||
}
|
||||
if (typeof record.available !== "boolean") return null;
|
||||
return {
|
||||
available: record.available,
|
||||
@@ -55,12 +68,24 @@ function parseRuntimeStatus(value: unknown): RuntimeStatus | null {
|
||||
};
|
||||
}
|
||||
|
||||
function isLoopbackDashboardHost(hostname: string): boolean {
|
||||
const normalized = hostname.trim().toLowerCase();
|
||||
return (
|
||||
normalized === "localhost" ||
|
||||
normalized === "127.0.0.1" ||
|
||||
normalized === "::1" ||
|
||||
normalized === "[::1]"
|
||||
);
|
||||
}
|
||||
|
||||
function clampNumber(raw: string, min: number, max: number, fallback: number): number {
|
||||
const parsed = Number.parseInt(raw, 10);
|
||||
return Math.min(max, Math.max(min, Number.isFinite(parsed) ? parsed : fallback));
|
||||
}
|
||||
|
||||
export default function ModalityBridgeVideoTab() {
|
||||
export default function ModalityBridgeVideoTab({
|
||||
runtimeHostname,
|
||||
}: ModalityBridgeVideoTabProps = {}) {
|
||||
const t = useTranslations("settings");
|
||||
const tRoot = useTranslations();
|
||||
const [settings, setSettings] = useState<VideoState | null>(null);
|
||||
@@ -71,14 +96,18 @@ export default function ModalityBridgeVideoTab() {
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const hostname = runtimeHostname ?? window.location.hostname;
|
||||
const runtimeStatusRequest = isLoopbackDashboardHost(hostname)
|
||||
? fetch("/api/modality-bridge/video/runtime")
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.catch(() => null)
|
||||
: Promise.resolve({ restricted: true });
|
||||
void Promise.all([
|
||||
fetch("/api/settings").then((response) => {
|
||||
if (!response.ok) throw new Error("settings load failed");
|
||||
return response.json();
|
||||
}),
|
||||
fetch("/api/modality-bridge/video/runtime")
|
||||
.then((response) => (response.ok ? response.json() : null))
|
||||
.catch(() => null),
|
||||
runtimeStatusRequest,
|
||||
])
|
||||
.then(([settingsValue, runtimeValue]: [unknown, unknown]) => {
|
||||
if (cancelled) return;
|
||||
@@ -94,7 +123,7 @@ export default function ModalityBridgeVideoTab() {
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
}, [runtimeHostname]);
|
||||
|
||||
const update = async (patch: Partial<VideoState>) => {
|
||||
setErrorState(null);
|
||||
@@ -169,6 +198,11 @@ export default function ModalityBridgeVideoTab() {
|
||||
FFmpeg {runtime.ffmpegVersion} · ffprobe {runtime.ffprobeVersion}
|
||||
</div>
|
||||
</>
|
||||
) : runtime?.restricted ? (
|
||||
<>
|
||||
<strong>{t("authz.badge.strict")}</strong>
|
||||
<div className="mt-1 text-xs">{tRoot("endpoint.badgeLoopbackTooltip")}</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<strong>{t("modalityBridgeVideoRuntimeUnavailable")}</strong>
|
||||
|
||||
@@ -87,11 +87,11 @@ describe("ModalityBridgeVideoTab", () => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
async function render(): Promise<HTMLDivElement> {
|
||||
async function render(props: { runtimeHostname?: string } = {}): Promise<HTMLDivElement> {
|
||||
const element = document.createElement("div");
|
||||
document.body.appendChild(element);
|
||||
const root = createRoot(element);
|
||||
await act(async () => root.render(<ModalityBridgeVideoTab />));
|
||||
await act(async () => root.render(<ModalityBridgeVideoTab {...props} />));
|
||||
roots.push({ root, element });
|
||||
await waitFor(
|
||||
() => element.querySelector('[data-testid="modality-bridge-video-frame-count"]') !== null,
|
||||
@@ -127,6 +127,20 @@ describe("ModalityBridgeVideoTab", () => {
|
||||
expect(element.textContent).not.toContain("modalityBridgeVideoComingSoon");
|
||||
});
|
||||
|
||||
it("labels runtime status as strict loopback without probing it from a LAN dashboard", async () => {
|
||||
const element = await render({ runtimeHostname: "192.168.0.15" });
|
||||
|
||||
expect(element.textContent).toContain("authz.badge.strict");
|
||||
expect(element.textContent).toContain("endpoint.badgeLoopbackTooltip");
|
||||
expect(element.textContent).not.toContain("modalityBridgeVideoRuntimeUnavailable");
|
||||
expect(element.textContent).not.toContain("modalityBridgeVideoRuntimeInstall");
|
||||
expect(
|
||||
fetchMock.mock.calls.some(([input]) =>
|
||||
String(input).includes("/api/modality-bridge/video/runtime")
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("persists the enable toggle and clamps frame count to 16", async () => {
|
||||
const element = await render();
|
||||
const toggle = element.querySelector('[role="switch"]') as HTMLButtonElement;
|
||||
|
||||
Reference in New Issue
Block a user