diff --git a/docs/Frpilot开发提示词.md b/docs/Frpilot开发提示词.md index 4d26deb..ea129db 100644 --- a/docs/Frpilot开发提示词.md +++ b/docs/Frpilot开发提示词.md @@ -104,4 +104,11 @@ Layout布局content区域的磨砂玻璃卡片圆角样式我要改动一下: - 将frpc的运行日志实时显示在仪表盘页面的日志区域内,并且可以清楚等 - 仪表盘页面的停止按钮也可以停止frpc服务进程 -- frpc启动避免出现frpc运行的黑色窗口 \ No newline at end of file +- frpc启动避免出现frpc运行的黑色窗口 + +##### 3.5.1 仪表盘功能优化调整 + +- frpc服务启动后,还可以在下拉列表修改frpc版本,启动后就不能再修改版本,而且下拉列表里面要显示正在使用的frpc版本 +- 切换菜单之后再切回仪表盘页面,实时日志内容没了,需要保持实时日志内容不会随页面切换而清空 + +#### 3.6 自动更新功能开发 \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 0bbfbee..135f8cf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -7,8 +7,10 @@ diff --git a/src/stores/frpc.ts b/src/stores/frpc.ts new file mode 100644 index 0000000..0b893f7 --- /dev/null +++ b/src/stores/frpc.ts @@ -0,0 +1,152 @@ +import { defineStore } from "pinia" +import dayjs from "dayjs" +import { listen } from "@tauri-apps/api/event" +import { getFrpcStatus, startFrpc, stopFrpc } from "@/api/frp" +import { translate } from "@/i18n" +import { useAppStore } from "@/stores/app" +import type { FrpcExitEvent, FrpcLogEvent, FrpcStatus } from "@/types/frp" +import { isTauri } from "@/utils/window" + +/** 日志级别 */ +export type LogLevel = "INFO" | "WARN" | "ERROR" | "DEBUG" + +/** 一行实时日志 */ +export interface LogLine { + time: string + level: LogLevel + message: string +} + +/** 日志最大保留行数 */ +const MAX_LOGS = 500 + +/** + * Frpc 运行状态与实时日志。 + * + * - 日志、选中版本放在 store 中,切换路由(组件卸载)后依然保留; + * - 事件监听在应用根组件初始化一次,离开仪表盘后仍持续收集日志; + * - 运行中时下拉框只展示当前使用的版本(由组件禁用下拉)。 + */ +export const useFrpcStore = defineStore("frpc", () => { + const appStore = useAppStore() + + const logs = ref([]) + /** 下拉框选中的版本记录 ID;运行时即正在使用的版本 */ + const selectedId = ref(undefined) + /** 正在运行的 Frpc 版本号(用于日志区标签) */ + const runningVersion = ref("") + const starting = ref(false) + const stopping = ref(false) + const initialized = ref(false) + + /** 是否正在运行 */ + const isRunning = computed(() => appStore.runStatus === "running") + + /** 追加一行日志(最多保留 MAX_LOGS 行) */ + function pushLog(level: LogLevel, message: string, time?: string) { + logs.value.push({ time: time || dayjs().format("HH:mm:ss"), level, message }) + if (logs.value.length > MAX_LOGS) { + logs.value.splice(0, logs.value.length - MAX_LOGS) + } + } + + function clearLogs() { + logs.value = [] + } + + /** 将后端日志级别归一化为 UI 级别 */ + function normalizeLevel(level: string): LogLevel { + switch (level.toUpperCase()) { + case "WARN": + case "WARNING": + return "WARN" + case "ERROR": + case "FATAL": + return "ERROR" + case "DEBUG": + case "TRACE": + return "DEBUG" + default: + return "INFO" + } + } + + /** 后端实时日志事件(frpc-log) */ + function onBackendLog(event: FrpcLogEvent) { + pushLog(normalizeLevel(event.level), event.message, event.time ?? undefined) + } + + /** 后端进程退出事件(frpc-exit) */ + function onBackendExit(event: FrpcExitEvent) { + runningVersion.value = "" + if (event.reason === "stopped") { + appStore.runStatus = "stopped" + return + } + appStore.runStatus = event.code != null && event.code !== 0 ? "error" : "stopped" + } + + /** 应用启动时调用一次:订阅后端事件并同步运行状态 */ + async function init() { + if (initialized.value) return + initialized.value = true + + pushLog("INFO", translate("dashboard.logReady")) + + if (!isTauri()) return + + // 监听在应用根组件初始化一次,长期有效(应用运行期间无需取消订阅) + await listen("frpc-log", (event) => onBackendLog(event.payload)) + await listen("frpc-exit", (event) => onBackendExit(event.payload)) + + // 同步当前运行状态 + try { + const status = await getFrpcStatus() + appStore.runStatus = status.running ? "running" : "stopped" + runningVersion.value = status.running ? status.version ?? "" : "" + } catch { + // 读取失败时保持默认状态 + } + } + + /** 一键启动:由后端生成 frpc.toml 并拉起独立进程 */ + async function start(id: number): Promise { + starting.value = true + try { + const status = await startFrpc(id) + appStore.runStatus = "running" + selectedId.value = String(id) + runningVersion.value = status.version ?? "" + return status + } finally { + starting.value = false + } + } + + /** 停止 Frpc 服务进程 */ + async function stop(): Promise { + stopping.value = true + try { + const status = await stopFrpc() + appStore.runStatus = "stopped" + runningVersion.value = "" + return status + } finally { + stopping.value = false + } + } + + return { + logs, + selectedId, + runningVersion, + starting, + stopping, + isRunning, + pushLog, + clearLogs, + init, + start, + stop, + } +}) diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 7ea689f..e4663bd 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -88,9 +88,10 @@
{{ t("dashboard.frpcVersion") }} {{ t("dashboard.start") }} @@ -111,8 +112,8 @@ {{ t("dashboard.stop") }} @@ -127,12 +128,14 @@ >

{{ t("dashboard.logs") }}

- {{ runningVersion }} + + {{ frpcStore.runningVersion }} + {{ statusText }}
- +
@@ -140,8 +143,8 @@ ref="logBox" class="min-h-0 flex-1 overflow-y-auto px-5 py-3 font-mono text-xs leading-relaxed" > -

{{ t("dashboard.logEmpty") }}

-
+

{{ t("dashboard.logEmpty") }}

+
{{ line.time }} [{{ line.level }}] {{ line.message }} @@ -153,7 +156,6 @@