From 172d50318a1036701d312bdfce019b216bfb7d4a Mon Sep 17 00:00:00 2001 From: coso Date: Sun, 14 Dec 2025 10:59:04 +0800 Subject: [PATCH] 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 --- eslint.config.js | 2 +- src/components/Dashboard.tsx | 103 ++++++++++++++++++++++++++++++++++- src/components/Providers.tsx | 67 ++++++++++++++++++++++- 3 files changed, 165 insertions(+), 7 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 36003bde2..a5d2dcb99 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -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", }, }, diff --git a/src/components/Dashboard.tsx b/src/components/Dashboard.tsx index d2d809527..460c47bbb 100644 --- a/src/components/Dashboard.tsx +++ b/src/components/Dashboard.tsx @@ -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(null); const [expandedTest, setExpandedTest] = useState(null); + // Token sync state + const [kiroStatus, setKiroStatus] = useState( + null, + ); + const [lastSyncTime, setLastSyncTime] = useState(null); + const [lastCheckTime, setLastCheckTime] = useState(null); + const kiroHashRef = useRef(""); + 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() { + {/* Token 同步状态 */} +
+
+

+ + Token 同步状态 +

+
+ + 自动监测中 (5s) +
+
+
+
+ Token 状态: + + {kiroStatus?.has_access_token ? "✓ 已加载" : "✗ 未加载"} + +
+
+ Refresh Token: + + {kiroStatus?.has_refresh_token ? "✓ 可用" : "✗ 不可用"} + +
+
+ 最后同步: + + {lastSyncTime ? lastSyncTime.toLocaleTimeString() : "从未同步"} + +
+
+ 最后检测: + + {lastCheckTime ? lastCheckTime.toLocaleTimeString() : "-"} + +
+
+
+ {error && (
{error} diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx index 6ae50448f..1a9914566 100644 --- a/src/components/Providers.tsx +++ b/src/components/Providers.tsx @@ -104,12 +104,14 @@ export function Providers() { ); const [kiroEnvVars, setKiroEnvVars] = useState([]); const kiroHashRef = useRef(""); + const [kiroLastSync, setKiroLastSync] = useState(null); // Gemini state const [geminiStatus, setGeminiStatus] = useState(null); const [geminiEnvVars, setGeminiEnvVars] = useState([]); const geminiHashRef = useRef(""); + const [geminiLastSync, setGeminiLastSync] = useState(null); // Qwen state const [qwenStatus, setQwenStatus] = useState( @@ -117,6 +119,10 @@ export function Providers() { ); const [qwenEnvVars, setQwenEnvVars] = useState([]); const qwenHashRef = useRef(""); + const [qwenLastSync, setQwenLastSync] = useState(null); + + // Last check time (used for display) + const [_lastCheckTime, setLastCheckTime] = useState(null); // OpenAI Custom state const [openaiStatus, setOpenaiStatus] = useState( @@ -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" && (
-

Kiro 凭证状态

+
+

Kiro 凭证状态

+
+ + 最后同步:{" "} + + {formatTime(kiroLastSync)} + + + + + 监测中 (5s) + +
+
凭证路径: @@ -592,7 +625,21 @@ export function Providers() { {/* Gemini Panel */} {activeProvider === "gemini" && (
-

Gemini CLI 凭证状态

+
+

Gemini CLI 凭证状态

+
+ + 最后同步:{" "} + + {formatTime(geminiLastSync)} + + + + + 监测中 (5s) + +
+
凭证路径: @@ -658,7 +705,21 @@ export function Providers() { {/* Qwen Panel */} {activeProvider === "qwen" && (
-

通义千问凭证状态

+
+

通义千问凭证状态

+
+ + 最后同步:{" "} + + {formatTime(qwenLastSync)} + + + + + 监测中 (5s) + +
+
凭证路径: