Compare commits
6 Commits
v1.1.0
...
a451726e52
| Author | SHA1 | Date | |
|---|---|---|---|
| a451726e52 | |||
| 9ecb392a41 | |||
| 208d397236 | |||
| 65204c3e6c | |||
| 4d2d388f18 | |||
| 0c4cc03efc |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -23,6 +23,3 @@ logs/
|
|||||||
.vscode/
|
.vscode/
|
||||||
.cache/
|
.cache/
|
||||||
sync_database.sqlite
|
sync_database.sqlite
|
||||||
|
|
||||||
//暂时忽略
|
|
||||||
app.js
|
|
||||||
12
README.md
12
README.md
@@ -126,9 +126,10 @@ curl -u username:password https://jira.example.com/rest/api/2/issue/TEST-1/trans
|
|||||||
|
|
||||||
### 第四步:启动服务
|
### 第四步:启动服务
|
||||||
|
|
||||||
#### 4.1 直接运行
|
#### 4.1 使用PM2运行
|
||||||
```bash
|
```bash
|
||||||
node index.js
|
npm install -g pm2
|
||||||
|
pm2 start index.js --name gitea-jira-sync
|
||||||
```
|
```
|
||||||
|
|
||||||
输出示例:
|
输出示例:
|
||||||
@@ -372,16 +373,13 @@ gitea-jira-sync/
|
|||||||
│ ├── syncManager.js - Gitea->Jira同步管理
|
│ ├── syncManager.js - Gitea->Jira同步管理
|
||||||
│ └── jiraSyncManager.js - Jira->Gitea同步管理
|
│ └── jiraSyncManager.js - Jira->Gitea同步管理
|
||||||
├── routes/ - 路由模块
|
├── routes/ - 路由模块
|
||||||
│ └── editor.js - 配置编辑器路由
|
│ └── control.js - 配置编辑器路由
|
||||||
├── services/ - 第三方API服务
|
├── services/ - 第三方API服务
|
||||||
│ ├── gitea.js - Gitea API客户端
|
│ ├── gitea.js - Gitea API客户端
|
||||||
│ └── jira.js - Jira API客户端
|
│ └── jira.js - Jira API客户端
|
||||||
└── utils/ - 工具函数
|
└── utils/ - 工具函数
|
||||||
├── logger.js - 日志模块
|
├── logger.js - 日志模块
|
||||||
├── circuitBreaker.js - 熔断器实现
|
└── circuitBreaker.js - 熔断器实现
|
||||||
└── tests_created_by_claude/
|
|
||||||
├── cleanup-test-issues.js
|
|
||||||
└── comprehensive-test.js
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|||||||
26
index.js
26
index.js
@@ -6,7 +6,7 @@ const config = require('./src/config/env');
|
|||||||
const { getConfiguredRepos } = require('./src/config/mappings');
|
const { getConfiguredRepos } = require('./src/config/mappings');
|
||||||
const { handleIssueEvent } = require('./src/logic/syncManager');
|
const { handleIssueEvent } = require('./src/logic/syncManager');
|
||||||
const { handleJiraHook } = require('./src/logic/jiraSyncManager');
|
const { handleJiraHook } = require('./src/logic/jiraSyncManager');
|
||||||
const editorRoutes = require('./src/routes/editor');
|
const editorRoutes = require('./src/routes/control');
|
||||||
const logger = require('./src/utils/logger');
|
const logger = require('./src/utils/logger');
|
||||||
|
|
||||||
const app = new Hono();
|
const app = new Hono();
|
||||||
@@ -47,6 +47,30 @@ setInterval(() => {
|
|||||||
}
|
}
|
||||||
}, 5 * 60 * 1000);
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
|
//内网访问控制中间件:保护管理界面,只允许dotenv配置的域名访问
|
||||||
|
const internalOnlyMiddleware = async (c, next) => {
|
||||||
|
const pathname = new URL(c.req.url).pathname;
|
||||||
|
|
||||||
|
if (pathname.startsWith('/hooks/')) {
|
||||||
|
return await next();
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = (c.req.header('host') || '').split(':')[0];
|
||||||
|
const allowedHosts = config.app.dashboardAllowedHosts;
|
||||||
|
|
||||||
|
if (!allowedHosts.some(allowed => host === allowed || host.endsWith('.' + allowed))) {
|
||||||
|
logger.security(`Blocked access from unauthorized host: ${host}`, {
|
||||||
|
path: pathname,
|
||||||
|
ip: c.req.header('x-forwarded-for') || c.req.header('x-real-ip') || 'unknown'
|
||||||
|
});
|
||||||
|
return c.text('Forbidden - Access denied from this domain', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
|
||||||
|
app.use('*', internalOnlyMiddleware);
|
||||||
|
|
||||||
//Gitea webhook处理入口
|
//Gitea webhook处理入口
|
||||||
app.post('/hooks/gitea', rateLimiter, async (c) => {
|
app.post('/hooks/gitea', rateLimiter, async (c) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ function updateServiceStatus(status) {
|
|||||||
//控制机器人
|
//控制机器人
|
||||||
async function controlBot(action) {
|
async function controlBot(action) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/control', {
|
const res = await fetch('/api/restart', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ action })
|
body: JSON.stringify({ action })
|
||||||
@@ -188,7 +188,14 @@ async function controlBot(action) {
|
|||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
alert(`操作成功: ${data.message || action}`);
|
alert(`操作成功: ${data.message || action}`);
|
||||||
|
//服务重启后延迟刷新页面
|
||||||
|
if (action === 'restart') {
|
||||||
|
setTimeout(() => {
|
||||||
|
location.reload();
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
loadDashboardData();
|
loadDashboardData();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
alert(`操作失败: ${data.error}`);
|
alert(`操作失败: ${data.error}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,34 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-CN">
|
<html lang="zh-CN">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Gitea-Jira 映射配置生成器</title>
|
<title>Gitea-Jira 映射配置生成器</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: 'Inter', system-ui, sans-serif; }
|
body {
|
||||||
.step-card { transition: all 0.3s ease; }
|
font-family: 'Inter', system-ui, sans-serif;
|
||||||
.step-active { border-left: 4px solid #3b82f6; }
|
}
|
||||||
.jira-icon { width: 16px; height: 16px; display: inline-block; vertical-align: middle; margin-right: 6px; }
|
|
||||||
|
.step-card {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-active {
|
||||||
|
border-left: 4px solid #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jira-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="bg-gray-50 text-gray-800 min-h-screen p-6">
|
<body class="bg-gray-50 text-gray-800 min-h-screen p-6">
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto">
|
<div class="max-w-4xl mx-auto">
|
||||||
@@ -21,7 +38,8 @@
|
|||||||
<p class="text-sm text-gray-500 mt-1">连接 Jira,自动提取 ID,生成 mappings.json 配置</p>
|
<p class="text-sm text-gray-500 mt-1">连接 Jira,自动提取 ID,生成 mappings.json 配置</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button onclick="openSettings()" class="text-sm bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded transition">
|
<button onclick="openSettings()"
|
||||||
|
class="text-sm bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded transition">
|
||||||
全局设置
|
全局设置
|
||||||
</button>
|
</button>
|
||||||
<div class="text-xs bg-blue-100 text-blue-800 px-3 py-1 rounded-full">v2.0</div>
|
<div class="text-xs bg-blue-100 text-blue-800 px-3 py-1 rounded-full">v2.0</div>
|
||||||
@@ -29,32 +47,38 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<!-- 全局设置模态窗口 -->
|
<!-- 全局设置模态窗口 -->
|
||||||
<div id="settingsModal" class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
<div id="settingsModal"
|
||||||
|
class="hidden fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
<div class="bg-white rounded-lg shadow-xl p-6 max-w-lg w-full mx-4">
|
<div class="bg-white rounded-lg shadow-xl p-6 max-w-lg w-full mx-4">
|
||||||
<h2 class="text-xl font-bold mb-4">全局设置</h2>
|
<h2 class="text-xl font-bold mb-4">全局设置</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Jira 地址 (Base URL)</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Jira 地址 (Base URL)</label>
|
||||||
<input type="text" id="settingsJiraUrl" placeholder="https://your-domain.atlassian.net" class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input type="text" id="settingsJiraUrl" placeholder="https://your-domain.atlassian.net"
|
||||||
|
class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">Jira 用户名/邮箱</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">Jira 用户名/邮箱</label>
|
||||||
<input type="text" id="settingsJiraUser" placeholder="email@example.com" class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input type="text" id="settingsJiraUser" placeholder="email@example.com"
|
||||||
|
class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
<p class="text-xs text-gray-400 mt-1">使用 PAT 时可留空</p>
|
<p class="text-xs text-gray-400 mt-1">使用 PAT 时可留空</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">API Token / 密码</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">API Token / 密码</label>
|
||||||
<input type="password" id="settingsJiraToken" placeholder="ATATT3xFfGF0..." class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input type="password" id="settingsJiraToken" placeholder="ATATT3xFfGF0..."
|
||||||
|
class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
<p class="text-xs text-gray-400 mt-1">推荐使用 PAT (Personal Access Token)</p>
|
<p class="text-xs text-gray-400 mt-1">推荐使用 PAT (Personal Access Token)</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-6 flex justify-end gap-2">
|
<div class="mt-6 flex justify-end gap-2">
|
||||||
<button onclick="closeSettings()" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm font-medium transition">
|
<button onclick="closeSettings()"
|
||||||
|
class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm font-medium transition">
|
||||||
取消
|
取消
|
||||||
</button>
|
</button>
|
||||||
<button onclick="saveSettings()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
<button onclick="saveSettings()"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
||||||
保存设置
|
保存设置
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,20 +92,25 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">当前名称</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">当前名称</label>
|
||||||
<input type="text" id="renameOldName" readonly class="w-full border rounded px-3 py-2 text-sm bg-gray-100 text-gray-600">
|
<input type="text" id="renameOldName" readonly
|
||||||
|
class="w-full border rounded px-3 py-2 text-sm bg-gray-100 text-gray-600">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">新名称</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">新名称</label>
|
||||||
<input type="text" id="renameNewName" placeholder="例如: owner/repo" class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input type="text" id="renameNewName" placeholder="例如: owner/repo"
|
||||||
|
class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
<p class="text-xs text-gray-400 mt-1">格式: owner/repo</p>
|
<p class="text-xs text-gray-400 mt-1">格式: owner/repo</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="renameError" class="hidden mt-3 bg-red-50 text-red-700 p-3 rounded text-sm border border-red-200"></div>
|
<div id="renameError"
|
||||||
|
class="hidden mt-3 bg-red-50 text-red-700 p-3 rounded text-sm border border-red-200"></div>
|
||||||
<div class="mt-6 flex justify-end gap-2">
|
<div class="mt-6 flex justify-end gap-2">
|
||||||
<button onclick="closeRenameModal()" class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm font-medium transition">
|
<button onclick="closeRenameModal()"
|
||||||
|
class="bg-gray-200 hover:bg-gray-300 text-gray-700 px-4 py-2 rounded text-sm font-medium transition">
|
||||||
取消
|
取消
|
||||||
</button>
|
</button>
|
||||||
<button onclick="confirmRename()" id="renameConfirmBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
<button onclick="confirmRename()" id="renameConfirmBtn"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
||||||
确认改名
|
确认改名
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -91,15 +120,18 @@
|
|||||||
<!-- Step 0: 选择/新建仓库 -->
|
<!-- Step 0: 选择/新建仓库 -->
|
||||||
<div id="step0" class="bg-white rounded-lg shadow p-6 mb-6 step-card step-active">
|
<div id="step0" class="bg-white rounded-lg shadow p-6 mb-6 step-card step-active">
|
||||||
<h2 class="text-lg font-semibold mb-4 flex items-center">
|
<h2 class="text-lg font-semibold mb-4 flex items-center">
|
||||||
<span class="bg-blue-100 text-blue-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">0</span>
|
<span
|
||||||
|
class="bg-blue-100 text-blue-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">0</span>
|
||||||
选择仓库配置
|
选择仓库配置
|
||||||
</h2>
|
</h2>
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<button onclick="loadExistingMappings()" id="loadBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
<button onclick="loadExistingMappings()" id="loadBtn"
|
||||||
|
class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
||||||
加载现有配置
|
加载现有配置
|
||||||
</button>
|
</button>
|
||||||
<button onclick="createNewMapping()" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition ml-2">
|
<button onclick="createNewMapping()"
|
||||||
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition ml-2">
|
||||||
新建仓库配置
|
新建仓库配置
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,33 +143,39 @@
|
|||||||
|
|
||||||
<div id="newRepoContainer" class="hidden">
|
<div id="newRepoContainer" class="hidden">
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">新仓库名称</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">新仓库名称</label>
|
||||||
<input type="text" id="newRepoName" placeholder="例如: loren/issueBotTest" class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input type="text" id="newRepoName" placeholder="例如: loren/issueBotTest"
|
||||||
<button onclick="confirmNewRepo()" class="mt-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
|
<button onclick="confirmNewRepo()"
|
||||||
|
class="mt-2 bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
||||||
确认并继续
|
确认并继续
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="step0Error" class="hidden mt-3 bg-red-50 text-red-700 p-3 rounded text-sm border border-red-200"></div>
|
<div id="step0Error" class="hidden mt-3 bg-red-50 text-red-700 p-3 rounded text-sm border border-red-200">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 1: 项目配置 -->
|
<!-- Step 1: 项目配置 -->
|
||||||
<div id="step1" class="bg-white rounded-lg shadow p-6 mb-6 step-card hidden">
|
<div id="step1" class="bg-white rounded-lg shadow p-6 mb-6 step-card hidden">
|
||||||
<h2 class="text-lg font-semibold mb-4 flex items-center">
|
<h2 class="text-lg font-semibold mb-4 flex items-center">
|
||||||
<span class="bg-blue-100 text-blue-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">1</span>
|
<span
|
||||||
|
class="bg-blue-100 text-blue-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">1</span>
|
||||||
项目配置
|
项目配置
|
||||||
</h2>
|
</h2>
|
||||||
<div class="mb-3 bg-blue-50 border border-blue-200 rounded p-3 flex items-center justify-between">
|
<div class="mb-3 bg-blue-50 border border-blue-200 rounded p-3 flex items-center justify-between">
|
||||||
<p class="text-sm text-blue-800">
|
<p class="text-sm text-blue-800">
|
||||||
<span class="font-semibold">当前配置仓库:</span> <span id="currentRepoName" class="font-mono">-</span>
|
<span class="font-semibold">当前配置仓库:</span> <span id="currentRepoName" class="font-mono">-</span>
|
||||||
</p>
|
</p>
|
||||||
<button onclick="backToStart()" class="text-xs bg-white hover:bg-gray-50 text-gray-700 px-3 py-1 rounded border">
|
<button onclick="backToStart()"
|
||||||
|
class="text-xs bg-white hover:bg-gray-50 text-gray-700 px-3 py-1 rounded border">
|
||||||
返回
|
返回
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-4">
|
<div class="grid grid-cols-1 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 mb-1">目标 Jira 项目 Key</label>
|
<label class="block text-sm font-medium text-gray-700 mb-1">目标 Jira 项目 Key</label>
|
||||||
<input type="text" id="projectKey" placeholder="例如: TEST" class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none uppercase font-bold text-gray-700">
|
<input type="text" id="projectKey" placeholder="例如: TEST"
|
||||||
|
class="w-full border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none uppercase font-bold text-gray-700">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sprint ID 提取区域 -->
|
<!-- Sprint ID 提取区域 -->
|
||||||
@@ -145,20 +183,25 @@
|
|||||||
<h3 class="text-sm font-semibold text-gray-700 mb-2">配置 Sprint 映射(可选)</h3>
|
<h3 class="text-sm font-semibold text-gray-700 mb-2">配置 Sprint 映射(可选)</h3>
|
||||||
<p class="text-xs text-gray-500 mb-3">输入一个已在目标 Sprint 中的工单 Key,系统将自动提取 Sprint ID 并建立映射</p>
|
<p class="text-xs text-gray-500 mb-3">输入一个已在目标 Sprint 中的工单 Key,系统将自动提取 Sprint ID 并建立映射</p>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<input type="text" id="issueKey" placeholder="例如: TEST-196" class="w-1/5 border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none uppercase">
|
<input type="text" id="issueKey" placeholder="例如: TEST-196"
|
||||||
<button onclick="fetchSprintId()" id="fetchSprintBtn" class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded text-sm font-medium transition whitespace-nowrap">
|
class="w-1/5 border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none uppercase">
|
||||||
|
<button onclick="fetchSprintId()" id="fetchSprintBtn"
|
||||||
|
class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded text-sm font-medium transition whitespace-nowrap">
|
||||||
提取 Sprint
|
提取 Sprint
|
||||||
</button>
|
</button>
|
||||||
<div id="sprintResult" class="flex-1 flex items-center px-3 text-sm"></div>
|
<div id="sprintResult" class="flex-1 flex items-center px-3 text-sm"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="sprintMilestone" class="hidden mt-3 flex gap-2 items-center">
|
<div id="sprintMilestone" class="hidden mt-3 flex gap-2 items-center">
|
||||||
<label class="text-xs font-medium text-gray-600 whitespace-nowrap">里程碑名称:</label>
|
<label class="text-xs font-medium text-gray-600 whitespace-nowrap">里程碑名称:</label>
|
||||||
<input type="text" id="milestoneName" placeholder="例如: v1.0.1" class="flex-1 border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
<input type="text" id="milestoneName" placeholder="例如: v1.0.1"
|
||||||
<button onclick="addSprintMapping()" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition whitespace-nowrap">
|
class="flex-1 border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 outline-none">
|
||||||
|
<button onclick="addSprintMapping()"
|
||||||
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition whitespace-nowrap">
|
||||||
添加映射
|
添加映射
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="sprintError" class="hidden mt-2 bg-red-50 text-red-700 p-2 rounded text-xs border border-red-200"></div>
|
<div id="sprintError"
|
||||||
|
class="hidden mt-2 bg-red-50 text-red-700 p-2 rounded text-xs border border-red-200"></div>
|
||||||
|
|
||||||
<!-- Sprint 映射列表 -->
|
<!-- Sprint 映射列表 -->
|
||||||
<div id="sprintListContainer" class="hidden mt-4">
|
<div id="sprintListContainer" class="hidden mt-4">
|
||||||
@@ -168,24 +211,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex justify-end">
|
<div class="mt-4 flex justify-end">
|
||||||
<button onclick="scanJira()" id="scanBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded shadow text-sm font-medium transition flex items-center">
|
<button onclick="scanJira()" id="scanBtn"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded shadow text-sm font-medium transition flex items-center">
|
||||||
<span id="scanBtnText">开始扫描</span>
|
<span id="scanBtnText">开始扫描</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="scanError" class="hidden mt-3 bg-red-50 text-red-700 p-3 rounded text-sm border border-red-200"></div>
|
<div id="scanError" class="hidden mt-3 bg-red-50 text-red-700 p-3 rounded text-sm border border-red-200">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Step 2: 交互映射 -->
|
<!-- Step 2: 交互映射 -->
|
||||||
<div id="step2" class="bg-white rounded-lg shadow p-6 mb-6 hidden">
|
<div id="step2" class="bg-white rounded-lg shadow p-6 mb-6 hidden">
|
||||||
<h2 class="text-lg font-semibold mb-4 flex items-center">
|
<h2 class="text-lg font-semibold mb-4 flex items-center">
|
||||||
<span class="bg-blue-100 text-blue-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">2</span>
|
<span
|
||||||
|
class="bg-blue-100 text-blue-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">2</span>
|
||||||
配置映射关系
|
配置映射关系
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- 优先级 -->
|
<!-- 优先级 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide border-b pb-2 mb-3">优先级映射 (Priority)</h3>
|
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide border-b pb-2 mb-3">优先级映射
|
||||||
|
(Priority)</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="priorityContainer">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="priorityContainer">
|
||||||
<!-- 动态生成 -->
|
<!-- 动态生成 -->
|
||||||
</div>
|
</div>
|
||||||
@@ -193,7 +240,8 @@
|
|||||||
|
|
||||||
<!-- 工单类型 -->
|
<!-- 工单类型 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide border-b pb-2 mb-3">类型映射 (不填写的类型不会同步)(Issue Types)</h3>
|
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide border-b pb-2 mb-3">类型映射
|
||||||
|
(不填写的类型不会同步)(Issue Types)</h3>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="typeContainer">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4" id="typeContainer">
|
||||||
<!-- 动态生成 -->
|
<!-- 动态生成 -->
|
||||||
</div>
|
</div>
|
||||||
@@ -205,8 +253,27 @@
|
|||||||
|
|
||||||
<!-- 状态流转 -->
|
<!-- 状态流转 -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide border-b pb-2 mb-3">状态动作 (Transitions)</h3>
|
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide border-b pb-2 mb-3">状态动作
|
||||||
<div class="bg-yellow-50 border border-yellow-200 rounded p-3 mb-3 text-xs text-yellow-800 hidden" id="transWarning"></div>
|
(Transitions)</h3>
|
||||||
|
<div class="bg-yellow-50 border border-yellow-200 rounded p-3 mb-3 text-xs text-yellow-800 hidden"
|
||||||
|
id="transWarning"></div>
|
||||||
|
|
||||||
|
<!-- 手动扫描 Transition -->
|
||||||
|
<div class="mb-4 p-3 bg-purple-50 border border-purple-200 rounded">
|
||||||
|
<p class="text-xs text-purple-700 mb-2">输入工单 Key 扫描该工单当前可用的状态流转,扫描结果会追加到下拉菜单中</p>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<input type="text" id="transIssueKey" placeholder="例如: TEST-123"
|
||||||
|
class="flex-1 border rounded px-3 py-2 text-sm focus:ring-2 focus:ring-purple-500 outline-none uppercase">
|
||||||
|
<button onclick="scanTransitions()" id="scanTransBtn"
|
||||||
|
class="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded text-sm font-medium transition whitespace-nowrap">
|
||||||
|
扫描流转
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="transResult" class="mt-2 text-sm"></div>
|
||||||
|
<div id="transError"
|
||||||
|
class="hidden mt-2 bg-red-50 text-red-700 p-2 rounded text-xs border border-red-200"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-3">
|
<div class="grid grid-cols-1 gap-3">
|
||||||
<div class="flex items-center justify-between bg-gray-50 p-3 rounded border">
|
<div class="flex items-center justify-between bg-gray-50 p-3 rounded border">
|
||||||
<span class="font-mono text-sm font-medium text-gray-700">关闭 (Close)</span>
|
<span class="font-mono text-sm font-medium text-gray-700">关闭 (Close)</span>
|
||||||
@@ -228,18 +295,23 @@
|
|||||||
<div class="mt-6 border-t pt-6">
|
<div class="mt-6 border-t pt-6">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide">配置预览(可编辑)</h3>
|
<h3 class="text-sm font-bold text-gray-800 uppercase tracking-wide">配置预览(可编辑)</h3>
|
||||||
<button onclick="updatePreview()" class="text-xs bg-blue-50 hover:bg-blue-100 text-blue-600 px-3 py-1 rounded transition">
|
<button onclick="updatePreview()"
|
||||||
|
class="text-xs bg-blue-50 hover:bg-blue-100 text-blue-600 px-3 py-1 rounded transition">
|
||||||
刷新预览
|
刷新预览
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<textarea id="jsonPreview" class="w-full h-96 bg-gray-900 text-gray-100 font-mono text-xs p-4 rounded outline-none border-2 border-gray-700 focus:border-blue-500 transition" spellcheck="false" placeholder="配置将在此显示,可直接编辑..."></textarea>
|
<textarea id="jsonPreview"
|
||||||
<div id="jsonError" class="hidden mt-2 bg-red-50 text-red-700 p-2 rounded text-xs border border-red-200"></div>
|
class="w-full h-96 bg-gray-900 text-gray-100 font-mono text-xs p-4 rounded outline-none border-2 border-gray-700 focus:border-blue-500 transition"
|
||||||
|
spellcheck="false" placeholder="配置将在此显示,可直接编辑..."></textarea>
|
||||||
|
<div id="jsonError"
|
||||||
|
class="hidden mt-2 bg-red-50 text-red-700 p-2 rounded text-xs border border-red-200"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-6 flex justify-end border-t pt-4">
|
<div class="mt-6 flex justify-end border-t pt-4">
|
||||||
<button onclick="saveToFile()" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded shadow text-sm font-medium transition">
|
<button onclick="saveToFile()"
|
||||||
|
class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded shadow text-sm font-medium transition">
|
||||||
保存到 mappings.json
|
保存到 mappings.json
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -249,20 +321,23 @@
|
|||||||
<div id="step3" class="bg-white rounded-lg shadow p-6 mb-12 hidden">
|
<div id="step3" class="bg-white rounded-lg shadow p-6 mb-12 hidden">
|
||||||
<h2 class="text-lg font-semibold mb-4 flex items-center justify-between">
|
<h2 class="text-lg font-semibold mb-4 flex items-center justify-between">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span class="bg-green-100 text-green-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">3</span>
|
<span
|
||||||
|
class="bg-green-100 text-green-600 rounded-full w-8 h-8 flex items-center justify-center text-sm mr-3">3</span>
|
||||||
保存成功
|
保存成功
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<div id="saveResult" class="text-sm"></div>
|
<div id="saveResult" class="text-sm"></div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<button onclick="location.reload()" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
<button onclick="location.reload()"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition">
|
||||||
继续配置其他仓库
|
继续配置其他仓库
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="app.js"></script>
|
<script src="mappingsEditor.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
1124
public/mappingsEditor.js
Normal file
1124
public/mappingsEditor.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,8 @@ const config = {
|
|||||||
rate: process.env.RATE_LIMIT_WINDOW || 10000,
|
rate: process.env.RATE_LIMIT_WINDOW || 10000,
|
||||||
maxRequests: process.env.MAX_REQUESTS_PER_WINDOW || 20,
|
maxRequests: process.env.MAX_REQUESTS_PER_WINDOW || 20,
|
||||||
debugMode: process.env.DEBUG_MODE === 'true',
|
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: {
|
gitea: {
|
||||||
baseUrl: process.env.GITEA_BASE_URL,
|
baseUrl: process.env.GITEA_BASE_URL,
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
/**
|
/**
|
||||||
* 映射关系编辑器路由模块
|
* 控制面板路由模块
|
||||||
* 提供映射配置的 CRUD 操作和 Jira API 代理
|
* 提供映射配置的 CRUD 操作、Jira API 代理和服务控制
|
||||||
*/
|
*/
|
||||||
const { Hono } = require('hono');
|
const { Hono } = require('hono');
|
||||||
|
const { exec } = require('child_process');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const logger = require('../utils/logger');
|
const logger = require('../utils/logger');
|
||||||
|
|
||||||
const editor = new Hono();
|
const control = new Hono();
|
||||||
|
|
||||||
const MAPPINGS_PATH = path.join(__dirname, '../../mappings.json');
|
const MAPPINGS_PATH = path.join(__dirname, '../../mappings.json');
|
||||||
const LOGS_DIR = path.join(__dirname, '../../logs');
|
const LOGS_DIR = path.join(__dirname, '../../logs');
|
||||||
const README_PATH = path.join(__dirname, '../../how-to-use.md');
|
const README_PATH = path.join(__dirname, '../../how-to-use.md');
|
||||||
|
|
||||||
editor.get('/status', (c) => {
|
control.get('/status', (c) => {
|
||||||
try {
|
try {
|
||||||
let repoCount = 0;
|
let repoCount = 0;
|
||||||
if (fs.existsSync(MAPPINGS_PATH)) {
|
if (fs.existsSync(MAPPINGS_PATH)) {
|
||||||
@@ -58,7 +59,7 @@ editor.get('/status', (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//获取历史统计数据
|
//获取历史统计数据
|
||||||
editor.get('/history', (c) => {
|
control.get('/history', (c) => {
|
||||||
try {
|
try {
|
||||||
const history = [];
|
const history = [];
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ editor.get('/history', (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//获取当日日志
|
//获取当日日志
|
||||||
editor.get('/logs', (c) => {
|
control.get('/logs', (c) => {
|
||||||
try {
|
try {
|
||||||
//获取今天的日志文件
|
//获取今天的日志文件
|
||||||
const today = new Date().toISOString().split('T')[0]; //YYYY-MM-DD
|
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 {
|
try {
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const today = new Date().toISOString().split('T')[0];
|
||||||
const logFile = path.join(LOGS_DIR, `sync-${today}.log`);
|
const logFile = path.join(LOGS_DIR, `sync-${today}.log`);
|
||||||
@@ -152,35 +153,38 @@ editor.post('/logs/clear', (c) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//控制机器人(重启等)
|
//控制机器人(支持 PM2 软重启)
|
||||||
editor.post('/control', async (c) => {
|
control.post('/restart', async (c) => {
|
||||||
try {
|
try {
|
||||||
const { action } = await c.req.json();
|
const { action } = await c.req.json();
|
||||||
|
logger.info(`[Control] Action received: ${action}`);
|
||||||
|
|
||||||
logger.info(`[Editor] Control action received: ${action}`);
|
|
||||||
|
|
||||||
//注意:实际的重启需要外部进程管理器(如 PM2)
|
|
||||||
//这里只是记录日志
|
|
||||||
if (action === 'restart') {
|
if (action === 'restart') {
|
||||||
logger.info('[Editor] Restart requested (requires PM2 or similar)');
|
logger.info('[Control] PM2 restart requested via dashboard');
|
||||||
return c.json({
|
|
||||||
success: true,
|
//延迟执行,让响应先返回给客户端
|
||||||
message: '重启请求已记录,请使用 PM2 或其他进程管理器执行重启'
|
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({
|
return c.json({ success: false, error: '不支持的操作' });
|
||||||
success: false,
|
|
||||||
error: '不支持的操作'
|
|
||||||
});
|
|
||||||
} catch (e) {
|
} 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);
|
return c.json({ success: false, error: e.message }, 500);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
//读取 .env 文件
|
//读取 .env 文件
|
||||||
editor.get('/env', (c) => {
|
control.get('/env', (c) => {
|
||||||
try {
|
try {
|
||||||
const envPath = path.join(__dirname, '../../.env');
|
const envPath = path.join(__dirname, '../../.env');
|
||||||
|
|
||||||
@@ -200,7 +204,7 @@ editor.get('/env', (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//保存 .env 文件
|
//保存 .env 文件
|
||||||
editor.post('/env', async (c) => {
|
control.post('/env', async (c) => {
|
||||||
try {
|
try {
|
||||||
const { content } = await c.req.json();
|
const { content } = await c.req.json();
|
||||||
const envPath = path.join(__dirname, '../../.env');
|
const envPath = path.join(__dirname, '../../.env');
|
||||||
@@ -226,7 +230,7 @@ editor.post('/env', async (c) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
editor.get('/guide', (c) => {
|
control.get('/guide', (c) => {
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(README_PATH)) {
|
if (!fs.existsSync(README_PATH)) {
|
||||||
return c.json({
|
return c.json({
|
||||||
@@ -245,7 +249,7 @@ editor.get('/guide', (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//读取现有的 mappings.json
|
//读取现有的 mappings.json
|
||||||
editor.get('/mappings', (c) => {
|
control.get('/mappings', (c) => {
|
||||||
try {
|
try {
|
||||||
if (!fs.existsSync(MAPPINGS_PATH)) {
|
if (!fs.existsSync(MAPPINGS_PATH)) {
|
||||||
return c.json({ success: true, data: { repositories: {} } });
|
return c.json({ success: true, data: { repositories: {} } });
|
||||||
@@ -262,7 +266,7 @@ editor.get('/mappings', (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//保存/更新 mappings.json
|
//保存/更新 mappings.json
|
||||||
editor.post('/mappings', async (c) => {
|
control.post('/mappings', async (c) => {
|
||||||
try {
|
try {
|
||||||
const { repoName, config } = await c.req.json();
|
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 {
|
try {
|
||||||
const repoName = decodeURIComponent(c.req.param('repoName'));
|
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 {
|
try {
|
||||||
const { oldName, newName } = await c.req.json();
|
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 {
|
try {
|
||||||
const newConfigObj = await c.req.json();
|
const newConfigObj = await c.req.json();
|
||||||
const repoName = Object.keys(newConfigObj)[0];
|
const repoName = Object.keys(newConfigObj)[0];
|
||||||
@@ -405,7 +409,7 @@ editor.post('/save', async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//扫描 Jira 项目信息
|
//扫描 Jira 项目信息
|
||||||
editor.post('/scan', async (c) => {
|
control.post('/scan', async (c) => {
|
||||||
const { baseUrl, auth, projectKey: rawKey } = await c.req.json();
|
const { baseUrl, auth, projectKey: rawKey } = await c.req.json();
|
||||||
const inputKey = rawKey ? rawKey.trim() : '';
|
const inputKey = rawKey ? rawKey.trim() : '';
|
||||||
|
|
||||||
@@ -482,7 +486,7 @@ editor.post('/scan', async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//扫描 Sprint 信息
|
//扫描 Sprint 信息
|
||||||
editor.post('/scan-sprint', async (c) => {
|
control.post('/scan-sprint', async (c) => {
|
||||||
const { baseUrl, auth, issueKey } = await c.req.json();
|
const { baseUrl, auth, issueKey } = await c.req.json();
|
||||||
let headers = { 'Accept': 'application/json' };
|
let headers = { 'Accept': 'application/json' };
|
||||||
if (auth.token) headers['Authorization'] = `Bearer ${auth.token}`;
|
if (auth.token) headers['Authorization'] = `Bearer ${auth.token}`;
|
||||||
@@ -510,7 +514,7 @@ editor.post('/scan-sprint', async (c) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
//代理 Jira API 请求
|
//代理 Jira API 请求
|
||||||
editor.post('/proxy-jira', async (c) => {
|
control.post('/proxy-jira', async (c) => {
|
||||||
const { url, auth } = await c.req.json();
|
const { url, auth } = await c.req.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -532,4 +536,4 @@ editor.post('/proxy-jira', async (c) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = editor;
|
module.exports = control;
|
||||||
Reference in New Issue
Block a user