feat:访问控制和dashboard重启功能

This commit is contained in:
2026-01-30 10:02:29 +08:00
parent 0e6a780c50
commit 0c4cc03efc
4 changed files with 119 additions and 83 deletions

View File

@@ -7,7 +7,8 @@ const config = {
rate: process.env.RATE_LIMIT_WINDOW || 10000,
maxRequests: process.env.MAX_REQUESTS_PER_WINDOW || 20,
debugMode: process.env.DEBUG_MODE === 'true',
logRetentionDays: process.env.LOG_RETENTION_DAYS || 30
logRetentionDays: process.env.LOG_RETENTION_DAYS || 30,
dashboardAllowedHosts: (process.env.DASHBOARD_ALLOWED_HOSTS || 'localhost,127.0.0.1').split(',').map(h => h.trim())
},
gitea: {
baseUrl: process.env.GITEA_BASE_URL,

View File

@@ -1,20 +1,21 @@
/**
* 映射关系编辑器路由模块
* 提供映射配置的 CRUD 操作 Jira API 代理
* 控制面板路由模块
* 提供映射配置的 CRUD 操作Jira API 代理和服务控制
*/
const { Hono } = require('hono');
const { exec } = require('child_process');
const axios = require('axios');
const fs = require('fs');
const path = require('path');
const logger = require('../utils/logger');
const editor = new Hono();
const control = new Hono();
const MAPPINGS_PATH = path.join(__dirname, '../../mappings.json');
const LOGS_DIR = path.join(__dirname, '../../logs');
const README_PATH = path.join(__dirname, '../../how-to-use.md');
editor.get('/status', (c) => {
control.get('/status', (c) => {
try {
let repoCount = 0;
if (fs.existsSync(MAPPINGS_PATH)) {
@@ -58,7 +59,7 @@ editor.get('/status', (c) => {
});
//获取历史统计数据
editor.get('/history', (c) => {
control.get('/history', (c) => {
try {
const history = [];
@@ -104,7 +105,7 @@ editor.get('/history', (c) => {
});
//获取当日日志
editor.get('/logs', (c) => {
control.get('/logs', (c) => {
try {
//获取今天的日志文件
const today = new Date().toISOString().split('T')[0]; //YYYY-MM-DD
@@ -135,7 +136,7 @@ editor.get('/logs', (c) => {
});
//清空当日日志
editor.post('/logs/clear', (c) => {
control.post('/logs/clear', (c) => {
try {
const today = new Date().toISOString().split('T')[0];
const logFile = path.join(LOGS_DIR, `sync-${today}.log`);
@@ -152,35 +153,38 @@ editor.post('/logs/clear', (c) => {
}
});
//控制机器人(重启
editor.post('/control', async (c) => {
//控制机器人(支持 PM2 软重启)
control.post('/restart', async (c) => {
try {
const { action } = await c.req.json();
logger.info(`[Control] Action received: ${action}`);
logger.info(`[Editor] Control action received: ${action}`);
//注意:实际的重启需要外部进程管理器(如 PM2
//这里只是记录日志
if (action === 'restart') {
logger.info('[Editor] Restart requested (requires PM2 or similar)');
return c.json({
success: true,
message: '重启请求已记录,请使用 PM2 或其他进程管理器执行重启'
});
logger.info('[Control] PM2 restart requested via dashboard');
//延迟执行,让响应先返回给客户端
setTimeout(() => {
exec('pm2 restart gitea-jira-sync', (err, stdout, stderr) => {
if (err) {
logger.error('[Control] PM2 restart failed:', err.message);
} else {
logger.info('[Control] PM2 restart executed successfully');
}
});
}, 500);
return c.json({ success: true, message: '正在重启服务,请稍候刷新页面...' });
}
return c.json({
success: false,
error: '不支持的操作'
});
return c.json({ success: false, error: '不支持的操作' });
} catch (e) {
logger.error('[Editor] Control error:', e.message);
logger.error('[Control] Restart error:', e.message);
return c.json({ success: false, error: e.message }, 500);
}
});
//读取 .env 文件
editor.get('/env', (c) => {
control.get('/env', (c) => {
try {
const envPath = path.join(__dirname, '../../.env');
@@ -200,7 +204,7 @@ editor.get('/env', (c) => {
});
//保存 .env 文件
editor.post('/env', async (c) => {
control.post('/env', async (c) => {
try {
const { content } = await c.req.json();
const envPath = path.join(__dirname, '../../.env');
@@ -226,7 +230,7 @@ editor.post('/env', async (c) => {
}
});
editor.get('/guide', (c) => {
control.get('/guide', (c) => {
try {
if (!fs.existsSync(README_PATH)) {
return c.json({
@@ -245,7 +249,7 @@ editor.get('/guide', (c) => {
});
//读取现有的 mappings.json
editor.get('/mappings', (c) => {
control.get('/mappings', (c) => {
try {
if (!fs.existsSync(MAPPINGS_PATH)) {
return c.json({ success: true, data: { repositories: {} } });
@@ -262,7 +266,7 @@ editor.get('/mappings', (c) => {
});
//保存/更新 mappings.json
editor.post('/mappings', async (c) => {
control.post('/mappings', async (c) => {
try {
const { repoName, config } = await c.req.json();
@@ -292,7 +296,7 @@ editor.post('/mappings', async (c) => {
});
//删除仓库配置
editor.delete('/mappings/:repoName', async (c) => {
control.delete('/mappings/:repoName', async (c) => {
try {
const repoName = decodeURIComponent(c.req.param('repoName'));
@@ -324,7 +328,7 @@ editor.delete('/mappings/:repoName', async (c) => {
});
//改名仓库配置
editor.post('/mappings/rename', async (c) => {
control.post('/mappings/rename', async (c) => {
try {
const { oldName, newName } = await c.req.json();
@@ -368,7 +372,7 @@ editor.post('/mappings/rename', async (c) => {
});
//保存配置接口(兼容旧版)
editor.post('/save', async (c) => {
control.post('/save', async (c) => {
try {
const newConfigObj = await c.req.json();
const repoName = Object.keys(newConfigObj)[0];
@@ -405,7 +409,7 @@ editor.post('/save', async (c) => {
});
//扫描 Jira 项目信息
editor.post('/scan', async (c) => {
control.post('/scan', async (c) => {
const { baseUrl, auth, projectKey: rawKey } = await c.req.json();
const inputKey = rawKey ? rawKey.trim() : '';
@@ -482,7 +486,7 @@ editor.post('/scan', async (c) => {
});
//扫描 Sprint 信息
editor.post('/scan-sprint', async (c) => {
control.post('/scan-sprint', async (c) => {
const { baseUrl, auth, issueKey } = await c.req.json();
let headers = { 'Accept': 'application/json' };
if (auth.token) headers['Authorization'] = `Bearer ${auth.token}`;
@@ -510,7 +514,7 @@ editor.post('/scan-sprint', async (c) => {
});
//代理 Jira API 请求
editor.post('/proxy-jira', async (c) => {
control.post('/proxy-jira', async (c) => {
const { url, auth } = await c.req.json();
try {
@@ -532,4 +536,4 @@ editor.post('/proxy-jira', async (c) => {
}
});
module.exports = editor;
module.exports = control;