From 19e65d78dc3d1559ecafb0bfb36cd78aad26a4c4 Mon Sep 17 00:00:00 2001 From: XuJiacheng Date: Mon, 12 Jan 2026 19:55:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0Redis=E9=9B=86?= =?UTF-8?q?=E6=88=90=E5=8D=8F=E8=AE=AE=E5=B9=B6=E9=87=8D=E6=9E=84=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=8E=A7=E5=88=B6=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refactor(backend): 重构后端服务以支持Redis协议 feat(backend): 添加Redis客户端和服务模块 feat(backend): 实现命令和日志路由处理Redis交互 refactor(frontend): 重构前端状态管理和组件结构 feat(frontend): 实现基于Redis的日志和命令功能 docs: 添加Redis集成协议文档 chore: 更新ESLint配置和依赖 --- .env.example | 16 ++ .eslintrc.js => .eslintrc.cjs | 23 +- docs/redis-integration-protocol.md | 110 +++++++ .../changes/update-redis-protocol/proposal.md | 13 + .../specs/command/spec.md | 15 + .../specs/logging/spec.md | 14 + .../specs/redis-connection/spec.md | 9 + .../changes/update-redis-protocol/tasks.md | 13 + openspec/project.md | 1 + package.json | 2 +- page.html | 14 + src/backend/routes/commands.js | 74 +++-- src/backend/routes/logs.js | 85 +++++- src/backend/server.js | 51 +++- src/backend/services/redisClient.js | 88 ++++++ src/backend/services/redisKeys.js | 11 + src/frontend/App.vue | 125 +++++++- src/frontend/components/Console.vue | 269 ++++++++++++------ src/frontend/components/DebugArea.vue | 260 ++++++++++------- src/frontend/components/ProjectSelector.vue | 49 ++-- src/frontend/main.js | 12 +- src/frontend/router/index.js | 20 +- src/frontend/store/projectStore.js | 10 + src/frontend/views/CommandView.vue | 60 ++-- src/frontend/views/LogView.vue | 19 +- src/frontend/views/MainView.vue | 10 - src/frontend/views/SidebarView.vue | 13 +- vite.config.js | 24 +- 样品图.png | Bin 0 -> 151553 bytes 29 files changed, 1061 insertions(+), 349 deletions(-) create mode 100644 .env.example rename .eslintrc.js => .eslintrc.cjs (69%) create mode 100644 docs/redis-integration-protocol.md create mode 100644 openspec/changes/update-redis-protocol/proposal.md create mode 100644 openspec/changes/update-redis-protocol/specs/command/spec.md create mode 100644 openspec/changes/update-redis-protocol/specs/logging/spec.md create mode 100644 openspec/changes/update-redis-protocol/specs/redis-connection/spec.md create mode 100644 openspec/changes/update-redis-protocol/tasks.md create mode 100644 page.html create mode 100644 src/backend/services/redisClient.js create mode 100644 src/backend/services/redisKeys.js create mode 100644 src/frontend/store/projectStore.js create mode 100644 样品图.png diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..472adb3 --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# Example environment variables for Web_BLS_ProjectConsole +# Copy this file to `.env` and fill values before running locally + +# Redis connection +REDIS_HOST=localhost # Redis host (default: localhost) +REDIS_PORT=6379 # Redis port (default: 6379) +REDIS_PASSWORD= # Redis password (leave empty if not set) +REDIS_DB=0 # Redis database number (default: 0) +REDIS_CONNECT_TIMEOUT_MS=2000 # Connection timeout in ms (default: 2000) + +# Application +# Optional: port for backend server. Current default in code is 3001 but you can override it here. +PORT=3001 + +# Node environment (development|production) +NODE_ENV=development diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 69% rename from .eslintrc.js rename to .eslintrc.cjs index 0a99f1e..e30f2be 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -1,29 +1,26 @@ -export default { +/** @type {import('eslint').Linter.Config} */ +module.exports = { + ignorePatterns: ['dist/**'], env: { browser: true, es2021: true, node: true }, - extends: [ - 'eslint:recommended', - 'plugin:vue/vue3-recommended' - ], + extends: ['eslint:recommended', 'plugin:vue/vue3-recommended'], parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - plugins: [ - 'vue' - ], + plugins: ['vue'], rules: { 'vue/multi-word-component-names': 'off', 'vue/no-unused-vars': 'warn', 'vue/no-unused-components': 'warn', 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn', - 'indent': ['error', 2], - 'quotes': ['error', 'single'], - 'semi': ['error', 'always'], + indent: ['error', 2], + quotes: ['error', 'single'], + semi: ['error', 'always'], 'no-trailing-spaces': 'error', 'comma-dangle': ['error', 'always-multiline'], 'object-curly-spacing': ['error', 'always'], @@ -33,8 +30,8 @@ export default { { files: ['*.vue'], rules: { - 'indent': 'off' + indent: 'off' } } ] -} \ No newline at end of file +}; diff --git a/docs/redis-integration-protocol.md b/docs/redis-integration-protocol.md new file mode 100644 index 0000000..68301f1 --- /dev/null +++ b/docs/redis-integration-protocol.md @@ -0,0 +1,110 @@ +# Redis 对接协议(供外部项目 AI 生成代码使用) + +本文档定义“外部项目 ↔ BLS Project Console”之间通过 Redis 交互的 **Key 命名、数据类型、写入方式、读取方式与数据格式**。 + +> 约束:每个需要关联本控制台的外部项目,必须在本项目使用的同一个 Redis 实例中: +> - 写入 2 个 Key(状态 + 控制台信息) +> - 读取 1 个 Key(控制指令队列) + +## 1. 命名约定 + +令: +- `projectName`:外部项目名称(建议只用字母数字下划线 `A-Za-z0-9_`;如使用中文也可,但需保证统一且 UTF-8)。 + +固定后缀: +- 状态:`${projectName}_项目状态` +- 控制台:`${projectName}_项目控制台` +- 控制:`${projectName}_控制` + +示例(projectName = `订单系统`): +- `订单系统_项目状态` +- `订单系统_项目控制台` +- `订单系统_控制` + +## 2. 外部项目需要写入的 2 个 Key + +### 2.1 `${projectName}_项目状态` + +- Redis 数据类型:**STRING** +- 写入方式:`SET ${projectName}_项目状态 ` +- value:状态枚举(必须为以下之一) + - `在线` + - `离线` + - `故障` + - `报错` + +建议(非强制): +- 定期刷新(如 5~30 秒) +- 可设置 TTL 防止僵尸在线(如 `SET key value EX 60`) + +### 2.2 `${projectName}_项目控制台` + +- Redis 数据类型:**LIST**(作为项目向控制台追加的“消息队列/日志队列”) +- 写入方式(推荐 FIFO):`RPUSH ${projectName}_项目控制台 ` + +value(推荐格式):一条 JSON 字符串,表示“错误/调试信息”或日志记录。 + +推荐 JSON Schema(字段尽量保持稳定,便于控制台解析): +```json +{ + "timestamp": "2026-01-12T12:34:56.789Z", + "level": "info", + "message": "连接成功", + "metadata": { + "module": "redis", + "host": "127.0.0.1" + } +} +``` + +字段说明: +- `timestamp`:ISO-8601 时间字符串 +- `level`:建议取值 `info|warn|error|debug`(小写) +- `message`:日志文本 +- `metadata`:可选对象(附加信息) + +## 3. 外部项目需要读取的 1 个 Key(控制指令) + +### 3.1 `${projectName}_控制` + +- Redis 数据类型:**LIST**(控制台向目标项目下发的“命令队列”) +- 队列模式(必须):**Redis List 队列**(生产 `RPUSH`,消费 `BLPOP`;必要时配合 `LTRIM`) +- 控制台写入方式(FIFO):`RPUSH ${targetProjectName}_控制 ` +- 外部项目读取方式(阻塞消费,推荐):`BLPOP ${projectName}_控制 0` + +> 说明:`BLPOP` 本身会原子性地“取出并移除”队列头元素,通常不需要额外 `LTRIM`。 +> 如果你的运行环境/客户端库无法可靠使用 `BLPOP`,或你希望采用“读取 + 修剪(LTRIM)”的显式确认方式,可使用兼容模式: +> 1) `LRANGE ${projectName}_控制 0 0` 读取 1 条 +> 2) 处理成功后执行 `LTRIM ${projectName}_控制 1 -1` 修剪已消费元素 + +value:**JSON 对象**(在 Redis 中以 JSON 字符串形式存储)。 + +推荐 JSON Schema: +```json +{ + "id": "cmd-1700000000000-abc123", + "timestamp": "2026-01-12T12:35:00.000Z", + "source": "BLS Project Console", + "command": "reload", + "args": { + "force": true + } +} +``` + +字段说明: +- `id`:命令唯一 ID(用于追踪) +- `timestamp`:命令下发时间 +- `source`:固定可写 `BLS Project Console` +- `command`:命令名称或命令文本 +- `args`:可选对象参数 + +## 4. 兼容与错误处理建议 + +- JSON 解析失败:外部项目应记录错误,并丢弃该条消息(避免死循环阻塞消费)。 +- 消息过长:建议控制单条消息大小(例如 < 64KB)。 +- 字符编码:统一 UTF-8。 + +## 5. 与本项目代码的对应关系(实现中) + +后端通过 `/api/commands` 将命令写入 `${targetProjectName}_控制`;通过 `/api/logs` 读取 `${projectName}_项目控制台`(以仓库当前实现为准)。 diff --git a/openspec/changes/update-redis-protocol/proposal.md b/openspec/changes/update-redis-protocol/proposal.md new file mode 100644 index 0000000..3b76b77 --- /dev/null +++ b/openspec/changes/update-redis-protocol/proposal.md @@ -0,0 +1,13 @@ +# Change: Update Redis Integration Protocol + +## Why +需要为“BLS Project Console ↔ 其他业务项目”的 Redis 交互约定一个稳定、可机器生成的协议,明确每个接入项目必须写入的状态与控制台信息,以及必须读取的控制指令队列。 + +## What Changes +- 统一 Redis Key 命名规则:每个项目写 2 个 key、读 1 个 key +- 明确每个 key 的 Redis 数据类型(STRING/LIST)与 value 格式(枚举值/JSON) +- 对齐 logging / command / redis-connection 三个 capability 的 requirements(以便实现端可依据 spec 开发) + +## Impact +- Affected specs: specs/redis-connection/spec.md, specs/logging/spec.md, specs/command/spec.md +- Affected code (planned): src/backend/routes/, src/backend/services/, src/frontend/components/ diff --git a/openspec/changes/update-redis-protocol/specs/command/spec.md b/openspec/changes/update-redis-protocol/specs/command/spec.md new file mode 100644 index 0000000..956ccc7 --- /dev/null +++ b/openspec/changes/update-redis-protocol/specs/command/spec.md @@ -0,0 +1,15 @@ +## MODIFIED Requirements + +### Requirement: Command Sending to Redis +The system SHALL send commands to a per-target Redis key. + +#### Scenario: Console enqueues a command for a target project +- **WHEN** the user sends a command from the console +- **THEN** the backend SHALL append a JSON message to Redis LIST key `${targetProjectName}_控制` +- **AND** the JSON message SHALL represent the command payload (an object) + +#### Scenario: Target project consumes a command +- **WHEN** a target project listens for commands +- **THEN** it SHALL consume messages from `${projectName}_控制` as JSON objects +- **AND** it SHOULD use Redis LIST queue semantics (producer `RPUSH`, consumer `BLPOP`) +- **AND** if `BLPOP` is not available, it MAY use a `LRANGE` + `LTRIM` compatibility pattern diff --git a/openspec/changes/update-redis-protocol/specs/logging/spec.md b/openspec/changes/update-redis-protocol/specs/logging/spec.md new file mode 100644 index 0000000..dc2272a --- /dev/null +++ b/openspec/changes/update-redis-protocol/specs/logging/spec.md @@ -0,0 +1,14 @@ +## MODIFIED Requirements + +### Requirement: Log Reading from Redis +The system SHALL read log records from per-project Redis keys. + +#### Scenario: External project writes console logs +- **WHEN** an external project emits debug/error information +- **THEN** it SHALL append entries to a Redis LIST key named `${projectName}_项目控制台` +- **AND** each entry SHALL be a JSON string representing a log record + +#### Scenario: Server reads project console logs +- **WHEN** the server is configured to show a given project +- **THEN** it SHALL read entries from `${projectName}_项目控制台` +- **AND** it SHALL present them in the console UI with timestamp, level and message diff --git a/openspec/changes/update-redis-protocol/specs/redis-connection/spec.md b/openspec/changes/update-redis-protocol/specs/redis-connection/spec.md new file mode 100644 index 0000000..515494b --- /dev/null +++ b/openspec/changes/update-redis-protocol/specs/redis-connection/spec.md @@ -0,0 +1,9 @@ +## ADDED Requirements + +### Requirement: Per-Project Status Key +The system SHALL standardize a per-project Redis status key for connected projects. + +#### Scenario: External project writes status +- **WHEN** an external project integrates with this console +- **THEN** it SHALL write a Redis STRING key named `${projectName}_项目状态` +- **AND** the value SHALL be one of: `在线`, `离线`, `故障`, `报错` diff --git a/openspec/changes/update-redis-protocol/tasks.md b/openspec/changes/update-redis-protocol/tasks.md new file mode 100644 index 0000000..b0bb0a1 --- /dev/null +++ b/openspec/changes/update-redis-protocol/tasks.md @@ -0,0 +1,13 @@ +## 1. Documentation +- [x] 1.1 Add Redis integration protocol doc for external projects +- [ ] 1.2 Link doc location from README (optional) + +## 2. Backend +- [x] 2.1 Add Redis client config + connection helper +- [x] 2.2 Implement command enqueue: write `${targetProjectName}_控制` LIST with JSON payload +- [x] 2.3 Implement log fetch/stream: read `${projectName}_项目控制台` LIST (and status `${projectName}_项目状态` STRING when needed) + +## 3. Frontend +- [x] 3.1 Wire selected project name into Console (targetProjectName) +- [x] 3.2 Replace simulated command send with API call to backend +- [x] 3.3 Replace simulated logs with backend-provided logs (polling or SSE) diff --git a/openspec/project.md b/openspec/project.md index 9f669fd..be287b9 100644 --- a/openspec/project.md +++ b/openspec/project.md @@ -62,6 +62,7 @@ BLS Project Console是一个前后端分离的Node.js项目,用于从Redis队 - **可靠性**: Redis连接需要具备重连机制,确保系统稳定运行 - **安全性**: API接口需要适当的访问控制 - **可扩展性**: 系统设计应支持未来功能扩展 +- **开发约束**: 一旦遇到 lint/build/tooling 失败(例如 ESLint 配置错误),必须优先修复并恢复可用的开发工作流,再继续功能开发 ## External Dependencies - **Redis**: 用于存储日志记录和控制台指令的消息队列服务 diff --git a/package.json b/package.json index f48c72d..456ae29 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "preview": "vite preview", "start": "node src/backend/server.js", "start:dev": "nodemon src/backend/server.js", - "lint": "eslint . --ext .js,.vue", + "lint": "eslint . --ext .js,.vue --config .eslintrc.cjs", "format": "prettier --write ." }, "dependencies": { diff --git a/page.html b/page.html new file mode 100644 index 0000000..b209a22 --- /dev/null +++ b/page.html @@ -0,0 +1,14 @@ + + + + + + + + BLS Project Console + + +
+ + + \ No newline at end of file diff --git a/src/backend/routes/commands.js b/src/backend/routes/commands.js index ebf9ecb..61ea823 100644 --- a/src/backend/routes/commands.js +++ b/src/backend/routes/commands.js @@ -1,22 +1,64 @@ -import express from 'express' -const router = express.Router() +import express from 'express'; + +const router = express.Router(); + +import { getRedisClient } from '../services/redisClient.js'; +import { projectControlKey } from '../services/redisKeys.js'; + +function isNonEmptyString(value) { + return typeof value === 'string' && value.trim().length > 0; +} // 发送指令 -router.post('/', (req, res) => { - const { command } = req.body - - if (!command) { +router.post('/', async (req, res) => { + const { targetProjectName, command, args } = req.body; + + if (!isNonEmptyString(targetProjectName)) { return res.status(400).json({ success: false, - message: '指令内容不能为空' - }) + message: 'targetProjectName 不能为空', + }); } - - // 这里将实现发送指令到Redis队列的逻辑 - res.status(200).json({ - success: true, - message: '指令已发送到Redis队列' - }) -}) -export default router \ No newline at end of file + if (!isNonEmptyString(command)) { + return res.status(400).json({ + success: false, + message: '指令内容不能为空', + }); + } + + const commandId = `cmd-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + const payload = { + id: commandId, + timestamp: new Date().toISOString(), + source: 'BLS Project Console', + command: command.trim(), + args: typeof args === 'object' && args !== null ? args : undefined, + }; + + try { + const redis = req.app?.locals?.redis || (await getRedisClient()); + if (!redis?.isReady) { + return res.status(503).json({ + success: false, + message: 'Redis 未就绪', + }); + } + const key = projectControlKey(targetProjectName.trim()); + await redis.rPush(key, JSON.stringify(payload)); + + return res.status(200).json({ + success: true, + message: '指令已写入目标项目控制队列', + commandId, + }); + } catch (err) { + console.error('Failed to enqueue command', err); + return res.status(500).json({ + success: false, + message: '写入Redis失败', + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/backend/routes/logs.js b/src/backend/routes/logs.js index 1b852dc..f6d3bb6 100644 --- a/src/backend/routes/logs.js +++ b/src/backend/routes/logs.js @@ -1,12 +1,79 @@ -import express from 'express' -const router = express.Router() +import express from 'express'; + +const router = express.Router(); + +import { getRedisClient } from '../services/redisClient.js'; +import { projectConsoleKey, projectStatusKey } from '../services/redisKeys.js'; + +function parsePositiveInt(value, defaultValue) { + const num = Number.parseInt(String(value), 10); + if (!Number.isFinite(num) || num <= 0) return defaultValue; + return num; +} // 获取日志列表 -router.get('/', (req, res) => { - // 这里将实现从Redis读取日志的逻辑 - res.status(200).json({ - logs: [] - }) -}) +router.get('/', async (req, res) => { + const projectName = typeof req.query.projectName === 'string' ? req.query.projectName.trim() : ''; + const limit = parsePositiveInt(req.query.limit, 200); -export default router \ No newline at end of file + if (!projectName) { + return res.status(200).json({ + logs: [], + projectStatus: null, + }); + } + + try { + const redis = req.app?.locals?.redis || (await getRedisClient()); + if (!redis?.isReady) { + return res.status(503).json({ + logs: [], + projectStatus: null, + message: 'Redis 未就绪', + }); + } + + const key = projectConsoleKey(projectName); + const list = await redis.lRange(key, -limit, -1); + + const logs = list + .map((raw, idx) => { + try { + const parsed = JSON.parse(raw); + const timestamp = parsed.timestamp || new Date().toISOString(); + const level = (parsed.level || 'info').toString().toLowerCase(); + const message = parsed.message != null ? String(parsed.message) : ''; + return { + id: parsed.id || `log-${timestamp}-${idx}`, + timestamp, + level, + message, + metadata: parsed.metadata && typeof parsed.metadata === 'object' ? parsed.metadata : undefined, + }; + } catch { + return { + id: `log-${Date.now()}-${idx}`, + timestamp: new Date().toISOString(), + level: 'info', + message: raw, + }; + } + }); + + const status = await redis.get(projectStatusKey(projectName)); + + return res.status(200).json({ + logs, + projectStatus: status || null, + }); + } catch (err) { + console.error('Failed to read logs', err); + return res.status(500).json({ + logs: [], + projectStatus: null, + message: '读取Redis失败', + }); + } +}); + +export default router; \ No newline at end of file diff --git a/src/backend/server.js b/src/backend/server.js index a3e087e..199aef9 100644 --- a/src/backend/server.js +++ b/src/backend/server.js @@ -1,25 +1,46 @@ -import express from 'express' -import cors from 'cors' -import logRoutes from './routes/logs.js' -import commandRoutes from './routes/commands.js' +import express from 'express'; +import cors from 'cors'; +import logRoutes from './routes/logs.js'; +import commandRoutes from './routes/commands.js'; +import { getRedisClient } from './services/redisClient.js'; -const app = express() -const PORT = 3001 +const app = express(); +const PORT = 3001; // 中间件 -app.use(cors()) -app.use(express.json()) +app.use(cors()); +app.use(express.json()); // 路由 -app.use('/api/logs', logRoutes) -app.use('/api/commands', commandRoutes) +app.use('/api/logs', logRoutes); +app.use('/api/commands', commandRoutes); // 健康检查 app.get('/api/health', (req, res) => { - res.status(200).json({ status: 'ok' }) -}) + res.status(200).json({ status: 'ok' }); +}); // 启动服务器 -app.listen(PORT, () => { - console.log(`Server running on port ${PORT}`) -}) \ No newline at end of file +const server = app.listen(PORT, async () => { + console.log(`Server running on port ${PORT}`); + + try { + const redis = await getRedisClient(); + app.locals.redis = redis; + console.log('[redis] client attached to app.locals'); + } catch (err) { + console.error('[redis] failed to connect on startup', err); + } +}); + +process.on('SIGINT', async () => { + try { + if (app.locals.redis) { + await app.locals.redis.quit(); + } + } catch { + // ignore + } finally { + server.close(() => process.exit(0)); + } +}); \ No newline at end of file diff --git a/src/backend/services/redisClient.js b/src/backend/services/redisClient.js new file mode 100644 index 0000000..70eb8a9 --- /dev/null +++ b/src/backend/services/redisClient.js @@ -0,0 +1,88 @@ +import { createClient } from 'redis'; + +let client = null; +let connectPromise = null; + +let lastErrorLogAt = 0; +const ERROR_LOG_THROTTLE_MS = 60_000; + +function parseIntOrDefault(value, defaultValue) { + const num = Number.parseInt(value, 10); + return Number.isFinite(num) ? num : defaultValue; +} + +export async function getRedisClient() { + if (client) return client; + + const host = process.env.REDIS_HOST || 'localhost'; + const port = parseIntOrDefault(process.env.REDIS_PORT, 6379); + const password = process.env.REDIS_PASSWORD || undefined; + const db = parseIntOrDefault(process.env.REDIS_DB, 0); + + const url = `redis://${host}:${port}`; + + client = createClient({ + url, + password, + database: db, + socket: { + connectTimeout: parseIntOrDefault(process.env.REDIS_CONNECT_TIMEOUT_MS, 2000), + reconnectStrategy: (retries) => { + // exponential-ish backoff with cap + return Math.min(1000 * Math.max(1, retries), 30_000); + }, + }, + }); + + client.on('error', (err) => { + const now = Date.now(); + if (now - lastErrorLogAt >= ERROR_LOG_THROTTLE_MS) { + lastErrorLogAt = now; + const code = err && typeof err === 'object' && 'code' in err ? String(err.code) : 'UNKNOWN'; + const message = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err); + const summary = `${code}: ${message.split('\n')[0]}`; + console.error('[redis] error', summary); + } + }); + client.on('connect', () => { + console.log('[redis] connect'); + }); + client.on('ready', () => { + console.log('[redis] ready'); + }); + client.on('end', () => { + console.log('[redis] end'); + }); + + connectPromise = client.connect().catch(() => { + // allow retries via next call + connectPromise = null; + return null; + }); + + return client; +} + +export async function ensureRedisReady(options = {}) { + const { timeoutMs = 2000 } = options; + const redis = await getRedisClient(); + if (redis.isReady) return true; + + if (!connectPromise) { + connectPromise = redis.connect().catch(() => { + connectPromise = null; + return null; + }); + } + + try { + await Promise.race([ + connectPromise, + new Promise((_, reject) => setTimeout(() => reject(new Error('Redis connect timeout')), timeoutMs)), + ]); + } catch { + // ignore + } + + return redis.isReady; +} diff --git a/src/backend/services/redisKeys.js b/src/backend/services/redisKeys.js new file mode 100644 index 0000000..f6b0a7f --- /dev/null +++ b/src/backend/services/redisKeys.js @@ -0,0 +1,11 @@ +export function projectStatusKey(projectName) { + return `${projectName}_项目状态`; +} + +export function projectConsoleKey(projectName) { + return `${projectName}_项目控制台`; +} + +export function projectControlKey(projectName) { + return `${projectName}_控制`; +} diff --git a/src/frontend/App.vue b/src/frontend/App.vue index 1cdadcf..b9460f9 100644 --- a/src/frontend/App.vue +++ b/src/frontend/App.vue @@ -1,20 +1,43 @@ @@ -389,9 +474,9 @@ onMounted(() => { vertical-align: top; padding: 0.1rem 0.2rem 0.1rem 0; white-space: nowrap; - width: 120px; - min-width: 120px; - max-width: 120px; + width: 100px; + min-width: 100px; + max-width: 100px; } .log-timestamp { diff --git a/src/frontend/components/DebugArea.vue b/src/frontend/components/DebugArea.vue index 8127d29..9763805 100644 --- a/src/frontend/components/DebugArea.vue +++ b/src/frontend/components/DebugArea.vue @@ -4,105 +4,147 @@

调试区域

- -
- +
-
- +
- + + + + + +
- +
- -
- +
- -
- +
-
-
+
{{ getTypeText(item.type) }}
-
{{ formatTimestamp(item.timestamp) }}
+
+ {{ formatTimestamp(item.timestamp) }} +
{{ getProjectName(item.projectId) }}
- +
-
{{ item.content }}
- +
+ {{ item.content }} +
+ - - + -
+

没有找到匹配的调试信息

- + -