mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-22 05:04:33 +08:00
feat: add token sync time display and file monitoring status
- Add last sync time display for each OAuth provider (Kiro, Gemini, Qwen) - Add file hash monitoring indicator (5s interval) in provider panels - Add Token sync status card in Dashboard with: - Token status - Refresh token availability - Last sync time - Last check time - Auto-reload credentials when file changes detected - Update eslint config to ignore _prefixed variables
This commit is contained in:
+1
-1
@@ -27,7 +27,7 @@ export default [
|
||||
...tseslint.configs.recommended.rules,
|
||||
...reactHooks.configs.recommended.rules,
|
||||
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_", varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" }],
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import {
|
||||
Activity,
|
||||
Server,
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
RefreshCw,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
startServer,
|
||||
@@ -20,6 +21,10 @@ import {
|
||||
ServerStatus,
|
||||
Config,
|
||||
TestResult,
|
||||
getKiroCredentials,
|
||||
checkAndReloadCredentials,
|
||||
getTokenFileHash,
|
||||
KiroCredentialStatus,
|
||||
} from "@/hooks/useTauri";
|
||||
|
||||
interface TestState {
|
||||
@@ -39,6 +44,14 @@ export function Dashboard() {
|
||||
const [copiedCmd, setCopiedCmd] = useState<string | null>(null);
|
||||
const [expandedTest, setExpandedTest] = useState<string | null>(null);
|
||||
|
||||
// Token sync state
|
||||
const [kiroStatus, setKiroStatus] = useState<KiroCredentialStatus | null>(
|
||||
null,
|
||||
);
|
||||
const [lastSyncTime, setLastSyncTime] = useState<Date | null>(null);
|
||||
const [lastCheckTime, setLastCheckTime] = useState<Date | null>(null);
|
||||
const kiroHashRef = useRef<string>("");
|
||||
|
||||
const fetchStatus = async () => {
|
||||
try {
|
||||
const s = await getServerStatus();
|
||||
@@ -60,10 +73,50 @@ export function Dashboard() {
|
||||
useEffect(() => {
|
||||
fetchStatus();
|
||||
fetchConfig();
|
||||
const interval = setInterval(fetchStatus, 2000);
|
||||
return () => clearInterval(interval);
|
||||
loadKiroStatus();
|
||||
initTokenHash();
|
||||
|
||||
const statusInterval = setInterval(fetchStatus, 2000);
|
||||
const tokenInterval = setInterval(checkTokenFileChanges, 5000);
|
||||
|
||||
return () => {
|
||||
clearInterval(statusInterval);
|
||||
clearInterval(tokenInterval);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const initTokenHash = async () => {
|
||||
try {
|
||||
kiroHashRef.current = await getTokenFileHash();
|
||||
} catch (e) {
|
||||
console.error("Failed to get initial hash:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const loadKiroStatus = async () => {
|
||||
try {
|
||||
const status = await getKiroCredentials();
|
||||
setKiroStatus(status);
|
||||
} catch (e) {
|
||||
console.error("Failed to load Kiro status:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const checkTokenFileChanges = async () => {
|
||||
setLastCheckTime(new Date());
|
||||
try {
|
||||
const result = await checkAndReloadCredentials(kiroHashRef.current);
|
||||
kiroHashRef.current = result.new_hash;
|
||||
if (result.changed && result.reloaded) {
|
||||
await loadKiroStatus();
|
||||
setLastSyncTime(new Date());
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Token check error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStart = async () => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
@@ -281,6 +334,50 @@ export function Dashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Token 同步状态 */}
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<h3 className="font-semibold flex items-center gap-2">
|
||||
<RefreshCw className="h-4 w-4" />
|
||||
Token 同步状态
|
||||
</h3>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
自动监测中 (5s)
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-4 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">Token 状态:</span>
|
||||
<span
|
||||
className={`ml-2 ${kiroStatus?.has_access_token ? "text-green-600" : "text-red-500"}`}
|
||||
>
|
||||
{kiroStatus?.has_access_token ? "✓ 已加载" : "✗ 未加载"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">Refresh Token:</span>
|
||||
<span
|
||||
className={`ml-2 ${kiroStatus?.has_refresh_token ? "text-green-600" : "text-red-500"}`}
|
||||
>
|
||||
{kiroStatus?.has_refresh_token ? "✓ 可用" : "✗ 不可用"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">最后同步:</span>
|
||||
<span className="ml-2">
|
||||
{lastSyncTime ? lastSyncTime.toLocaleTimeString() : "从未同步"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-muted-foreground">最后检测:</span>
|
||||
<span className="ml-2">
|
||||
{lastCheckTime ? lastCheckTime.toLocaleTimeString() : "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="rounded-lg border border-red-500 bg-red-50 p-4 text-red-700">
|
||||
{error}
|
||||
|
||||
@@ -104,12 +104,14 @@ export function Providers() {
|
||||
);
|
||||
const [kiroEnvVars, setKiroEnvVars] = useState<EnvVariable[]>([]);
|
||||
const kiroHashRef = useRef<string>("");
|
||||
const [kiroLastSync, setKiroLastSync] = useState<Date | null>(null);
|
||||
|
||||
// Gemini state
|
||||
const [geminiStatus, setGeminiStatus] =
|
||||
useState<GeminiCredentialStatus | null>(null);
|
||||
const [geminiEnvVars, setGeminiEnvVars] = useState<EnvVariable[]>([]);
|
||||
const geminiHashRef = useRef<string>("");
|
||||
const [geminiLastSync, setGeminiLastSync] = useState<Date | null>(null);
|
||||
|
||||
// Qwen state
|
||||
const [qwenStatus, setQwenStatus] = useState<QwenCredentialStatus | null>(
|
||||
@@ -117,6 +119,10 @@ export function Providers() {
|
||||
);
|
||||
const [qwenEnvVars, setQwenEnvVars] = useState<EnvVariable[]>([]);
|
||||
const qwenHashRef = useRef<string>("");
|
||||
const [qwenLastSync, setQwenLastSync] = useState<Date | null>(null);
|
||||
|
||||
// Last check time (used for display)
|
||||
const [_lastCheckTime, setLastCheckTime] = useState<Date | null>(null);
|
||||
|
||||
// OpenAI Custom state
|
||||
const [openaiStatus, setOpenaiStatus] = useState<OpenAICustomStatus | null>(
|
||||
@@ -176,12 +182,15 @@ export function Providers() {
|
||||
}, []);
|
||||
|
||||
const checkFileChanges = async () => {
|
||||
setLastCheckTime(new Date());
|
||||
|
||||
// Check Kiro
|
||||
try {
|
||||
const kiroResult = await checkAndReloadCredentials(kiroHashRef.current);
|
||||
kiroHashRef.current = kiroResult.new_hash;
|
||||
if (kiroResult.changed && kiroResult.reloaded) {
|
||||
await loadKiroStatus();
|
||||
setKiroLastSync(new Date());
|
||||
setMessage({
|
||||
type: "success",
|
||||
text: "[Kiro] 检测到凭证文件变化,已自动重新加载",
|
||||
@@ -200,6 +209,7 @@ export function Providers() {
|
||||
geminiHashRef.current = geminiResult.new_hash;
|
||||
if (geminiResult.changed && geminiResult.reloaded) {
|
||||
await loadGeminiStatus();
|
||||
setGeminiLastSync(new Date());
|
||||
setMessage({
|
||||
type: "success",
|
||||
text: "[Gemini] 检测到凭证文件变化,已自动重新加载",
|
||||
@@ -218,6 +228,7 @@ export function Providers() {
|
||||
qwenHashRef.current = qwenResult.new_hash;
|
||||
if (qwenResult.changed && qwenResult.reloaded) {
|
||||
await loadQwenStatus();
|
||||
setQwenLastSync(new Date());
|
||||
setMessage({
|
||||
type: "success",
|
||||
text: "[Qwen] 检测到凭证文件变化,已自动重新加载",
|
||||
@@ -336,14 +347,17 @@ export function Providers() {
|
||||
await reloadCredentials();
|
||||
await loadKiroStatus();
|
||||
kiroHashRef.current = await getTokenFileHash();
|
||||
setKiroLastSync(new Date());
|
||||
} else if (provider === "gemini") {
|
||||
await reloadGeminiCredentials();
|
||||
await loadGeminiStatus();
|
||||
geminiHashRef.current = await getGeminiTokenFileHash();
|
||||
setGeminiLastSync(new Date());
|
||||
} else if (provider === "qwen") {
|
||||
await reloadQwenCredentials();
|
||||
await loadQwenStatus();
|
||||
qwenHashRef.current = await getQwenTokenFileHash();
|
||||
setQwenLastSync(new Date());
|
||||
}
|
||||
setMessage({ type: "success", text: `[${provider}] 凭证加载成功!` });
|
||||
} catch (e: any) {
|
||||
@@ -470,6 +484,11 @@ export function Providers() {
|
||||
}
|
||||
};
|
||||
|
||||
const formatTime = (date: Date | null) => {
|
||||
if (!date) return "从未同步";
|
||||
return date.toLocaleTimeString();
|
||||
};
|
||||
|
||||
const currentEnvVars =
|
||||
activeProvider === "kiro"
|
||||
? kiroEnvVars
|
||||
@@ -529,7 +548,21 @@ export function Providers() {
|
||||
{/* Kiro Panel */}
|
||||
{activeProvider === "kiro" && (
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h3 className="mb-3 font-semibold">Kiro 凭证状态</h3>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="font-semibold">Kiro 凭证状态</h3>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<span>
|
||||
最后同步:{" "}
|
||||
<span className="text-foreground">
|
||||
{formatTime(kiroLastSync)}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
监测中 (5s)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">凭证路径:</span>
|
||||
@@ -592,7 +625,21 @@ export function Providers() {
|
||||
{/* Gemini Panel */}
|
||||
{activeProvider === "gemini" && (
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h3 className="mb-3 font-semibold">Gemini CLI 凭证状态</h3>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="font-semibold">Gemini CLI 凭证状态</h3>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<span>
|
||||
最后同步:{" "}
|
||||
<span className="text-foreground">
|
||||
{formatTime(geminiLastSync)}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
监测中 (5s)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">凭证路径:</span>
|
||||
@@ -658,7 +705,21 @@ export function Providers() {
|
||||
{/* Qwen Panel */}
|
||||
{activeProvider === "qwen" && (
|
||||
<div className="rounded-lg border bg-card p-4">
|
||||
<h3 className="mb-3 font-semibold">通义千问凭证状态</h3>
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="font-semibold">通义千问凭证状态</h3>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
<span>
|
||||
最后同步:{" "}
|
||||
<span className="text-foreground">
|
||||
{formatTime(qwenLastSync)}
|
||||
</span>
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
监测中 (5s)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 grid grid-cols-2 gap-4 text-sm">
|
||||
<div>
|
||||
<span className="text-muted-foreground">凭证路径:</span>
|
||||
|
||||
Reference in New Issue
Block a user