mirror of
https://github.com/sipeed/picoclaw.git
synced 2026-06-12 18:08:54 +00:00
feat(web): add service log level controls (#2227)
- centralize gateway log level resolution and normalization - propagate debug flags to spawned launcher and gateway processes - add a log level selector to the logs page - cover the new behavior with backend and config tests
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query"
|
||||
import { useEffect, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { type AppConfig, getAppConfig, patchAppConfig } from "@/api/channels"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { refreshGatewayState } from "@/store/gateway"
|
||||
|
||||
const LOG_LEVEL_OPTIONS = ["debug", "info", "warn", "error", "fatal"] as const
|
||||
type GatewayLogLevel = (typeof LOG_LEVEL_OPTIONS)[number]
|
||||
|
||||
const LOG_LEVEL_LABELS: Record<GatewayLogLevel, string> = {
|
||||
debug: "Debug",
|
||||
info: "Info",
|
||||
warn: "Warn",
|
||||
error: "Error",
|
||||
fatal: "Fatal",
|
||||
}
|
||||
|
||||
function getGatewayLogLevel(config: AppConfig | undefined): GatewayLogLevel {
|
||||
const gateway = config?.gateway
|
||||
if (typeof gateway === "object" && gateway !== null) {
|
||||
const logLevel = (gateway as Record<string, unknown>).log_level
|
||||
if (
|
||||
typeof logLevel === "string" &&
|
||||
LOG_LEVEL_OPTIONS.includes(logLevel as GatewayLogLevel)
|
||||
) {
|
||||
return logLevel as GatewayLogLevel
|
||||
}
|
||||
}
|
||||
return "warn"
|
||||
}
|
||||
|
||||
export function LogLevelSelect() {
|
||||
const { t } = useTranslation()
|
||||
const queryClient = useQueryClient()
|
||||
const [logLevel, setLogLevel] = useState<GatewayLogLevel>("warn")
|
||||
const [savingLogLevel, setSavingLogLevel] = useState(false)
|
||||
|
||||
const { data: configData } = useQuery({
|
||||
queryKey: ["config"],
|
||||
queryFn: getAppConfig,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setLogLevel(getGatewayLogLevel(configData))
|
||||
}, [configData])
|
||||
|
||||
const handleLogLevelChange = async (nextValue: string) => {
|
||||
const nextLevel = nextValue as GatewayLogLevel
|
||||
const previousLevel = logLevel
|
||||
setLogLevel(nextLevel)
|
||||
setSavingLogLevel(true)
|
||||
|
||||
try {
|
||||
await patchAppConfig({
|
||||
gateway: {
|
||||
log_level: nextLevel,
|
||||
},
|
||||
})
|
||||
await queryClient.invalidateQueries({ queryKey: ["config"] })
|
||||
await refreshGatewayState({ force: true })
|
||||
} catch (error) {
|
||||
setLogLevel(previousLevel)
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("pages.logs.log_level_error"),
|
||||
)
|
||||
} finally {
|
||||
setSavingLogLevel(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
value={logLevel}
|
||||
onValueChange={handleLogLevelChange}
|
||||
disabled={savingLogLevel}
|
||||
>
|
||||
<SelectTrigger size="sm" className="w-28">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent align="end">
|
||||
{LOG_LEVEL_OPTIONS.map((level) => (
|
||||
<SelectItem key={level} value={level}>
|
||||
{LOG_LEVEL_LABELS[level]}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { IconTrash } from "@tabler/icons-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
import { LogLevelSelect } from "@/components/logs/log-level-select"
|
||||
import { LogsPanel } from "@/components/logs/logs-panel"
|
||||
import { PageHeader } from "@/components/page-header"
|
||||
import { Button } from "@/components/ui/button"
|
||||
@@ -17,15 +18,19 @@ export function LogsPage() {
|
||||
<PageHeader
|
||||
title={t("navigation.logs")}
|
||||
children={
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearLogs}
|
||||
disabled={logs.length === 0 || clearing}
|
||||
>
|
||||
<IconTrash className="size-4" />
|
||||
{t("pages.logs.clear")}
|
||||
</Button>
|
||||
<>
|
||||
<LogLevelSelect />
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={clearLogs}
|
||||
disabled={logs.length === 0 || clearing}
|
||||
>
|
||||
<IconTrash className="size-4" />
|
||||
{t("pages.logs.clear")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -547,6 +547,7 @@
|
||||
"unsaved_changes": "You have unsaved changes."
|
||||
},
|
||||
"logs": {
|
||||
"log_level_error": "Failed to update log level.",
|
||||
"clear": "Clear logs",
|
||||
"empty": "Waiting for logs..."
|
||||
}
|
||||
|
||||
@@ -547,6 +547,7 @@
|
||||
"unsaved_changes": "您有未保存的更改。"
|
||||
},
|
||||
"logs": {
|
||||
"log_level_error": "更新日志等级失败。",
|
||||
"clear": "清空日志",
|
||||
"empty": "等待日志中..."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user