mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 22:02:08 +03:00
OmniRoute is an intelligent API gateway that unifies 20+ AI providers behind a single OpenAI-compatible endpoint. Features include intelligent routing with 6 strategies, multi-format translation (OpenAI/Claude/Gemini/Responses API), circuit breakers, semantic caching, combo fallback chains, real-time health monitoring, and a full dashboard with provider management, analytics, and CLI tool integration. Key highlights: - 20+ providers (Claude Code, Codex, Gemini CLI, GitHub Copilot, iFlow, Qwen, Kiro, etc.) - 6 routing strategies (Fill First, Round Robin, P2C, Random, Least Used, Cost Optimized) - Export/Import database backup with full archive support - Translator Playground with 4 modes (Playground, Chat Tester, Test Bench, Live Monitor) - 100% TypeScript across src/ and open-sse/ - Docker support with multi-stage builds - Comprehensive documentation and 9 dashboard screenshots
101 lines
3.3 KiB
TypeScript
101 lines
3.3 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* CloudSyncStatus — Compact sync status indicator for the sidebar
|
|
*
|
|
* Shows cloud sync connection state with a small icon + label.
|
|
* Fetches status from /api/sync/cloud periodically.
|
|
* Listens for 'cloud-status-changed' events to re-poll immediately.
|
|
*
|
|
* @module shared/components/CloudSyncStatus
|
|
*/
|
|
|
|
import { useState, useEffect, useRef, useCallback } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
const STATUS_CONFIG = {
|
|
connected: { icon: "cloud_done", color: "text-green-500", label: "Cloud" },
|
|
syncing: { icon: "cloud_sync", color: "text-blue-400 animate-pulse", label: "Syncing..." },
|
|
disconnected: { icon: "cloud_off", color: "text-amber-500", label: "Cloud Off" },
|
|
error: { icon: "cloud_off", color: "text-red-400", label: "Cloud Error" },
|
|
disabled: { icon: "cloud_off", color: "text-text-muted/50", label: "Disabled" },
|
|
};
|
|
|
|
export default function CloudSyncStatus({ collapsed = false }) {
|
|
const [status, setStatus] = useState("disabled");
|
|
const [lastSync, setLastSync] = useState(null);
|
|
const mountedRef = useRef(true);
|
|
const router = useRouter();
|
|
|
|
const poll = useCallback(async () => {
|
|
try {
|
|
const res = await fetch("/api/sync/cloud");
|
|
if (!mountedRef.current) return;
|
|
if (!res.ok) {
|
|
setStatus("disconnected");
|
|
return;
|
|
}
|
|
const data = await res.json();
|
|
if (!mountedRef.current) return;
|
|
|
|
if (!data.enabled) setStatus("disabled");
|
|
else if (data.syncing) setStatus("syncing");
|
|
else if (data.connected) {
|
|
setStatus("connected");
|
|
if (data.lastSync) setLastSync(new Date(data.lastSync));
|
|
} else setStatus("disconnected");
|
|
} catch {
|
|
if (mountedRef.current) setStatus("disconnected");
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
mountedRef.current = true;
|
|
|
|
// Schedule initial poll outside of effect body to avoid setState-in-effect lint
|
|
queueMicrotask(poll);
|
|
const interval = setInterval(poll, 30000);
|
|
|
|
// Listen for immediate re-poll events from EndpointPageClient
|
|
const handleCloudChange = () => {
|
|
setTimeout(poll, 500); // Small delay to let backend settle
|
|
};
|
|
globalThis.addEventListener("cloud-status-changed", handleCloudChange);
|
|
|
|
return () => {
|
|
mountedRef.current = false;
|
|
clearInterval(interval);
|
|
globalThis.removeEventListener("cloud-status-changed", handleCloudChange);
|
|
};
|
|
}, [poll]);
|
|
|
|
// Don't render if cloud sync is disabled
|
|
if (status === "disabled") return null;
|
|
|
|
const config = STATUS_CONFIG[status];
|
|
|
|
return (
|
|
<button
|
|
onClick={() => router.push("/dashboard/endpoint")}
|
|
className="flex items-center gap-2 px-3 py-1.5 text-xs rounded-lg hover:bg-white/5 transition-colors cursor-pointer w-full"
|
|
title={
|
|
lastSync
|
|
? `Cloud ${status === "connected" ? "connected" : "disconnected"} — Last sync: ${lastSync.toLocaleTimeString()}`
|
|
: config.label
|
|
}
|
|
aria-label={`Cloud sync status: ${config.label}`}
|
|
>
|
|
<span className={`material-symbols-outlined text-[16px] ${config.color}`} aria-hidden="true">
|
|
{config.icon}
|
|
</span>
|
|
{!collapsed && (
|
|
<span
|
|
className={`truncate ${status === "connected" ? "text-green-500" : "text-text-muted"}`}
|
|
>
|
|
{config.label}
|
|
</span>
|
|
)}
|
|
</button>
|
|
);
|
|
}
|