Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 17e946ae56 |
178
app.py
178
app.py
@@ -3,6 +3,7 @@
|
||||
项目服务管理面板 v2.2.0
|
||||
端口: 19013
|
||||
新增: Web服务卡片添加自定义链接入口(+按钮)
|
||||
新增: 自定义新增外部web服务功能(新增服务按钮)
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -1057,11 +1058,14 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
</div>
|
||||
|
||||
<!-- 筛选器 -->
|
||||
<div class="flex gap-2 mb-4">
|
||||
<div class="flex gap-2 mb-4 flex-wrap">
|
||||
<button onclick="filterType('all')" class="filter-btn px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600 text-sm" data-type="all">全部</button>
|
||||
<button onclick="filterType('web')" class="filter-btn px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600 text-sm" data-type="web">Web服务</button>
|
||||
<button onclick="filterType('cli')" class="filter-btn px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600 text-sm" data-type="cli">CLI工具</button>
|
||||
<button onclick="filterType('extension')" class="filter-btn px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600 text-sm" data-type="extension">插件</button>
|
||||
<button onclick="showAddServiceModal()" class="btn bg-green-600 hover:bg-green-700 px-3 py-1 rounded-lg text-sm flex items-center gap-1 ml-2">
|
||||
<i class="ri-add-circle-line"></i> 新增服务
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 项目列表 -->
|
||||
@@ -1260,6 +1264,54 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增自定义服务模态框 -->
|
||||
<div id="addServiceModal" class="modal-overlay hidden">
|
||||
<div class="card rounded-xl modal-content p-6">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="font-bold text-lg">新增自定义服务</h3>
|
||||
<button onclick="closeAddServiceModal()" class="text-gray-400 hover:text-white"><i class="ri-close-line text-xl"></i></button>
|
||||
</div>
|
||||
|
||||
<form id="addServiceForm" onsubmit="saveCustomService(event)">
|
||||
<div class="mb-3">
|
||||
<label class="block text-gray-400 text-sm mb-1">服务名称 *</label>
|
||||
<input type="text" id="serviceName" required class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如:外部API、监控面板">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-gray-400 text-sm mb-1">端口(使用统一IP)</label>
|
||||
<input type="text" id="servicePort" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如:8080" onchange="updateServicePreview()">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-gray-400 text-sm mb-1">路径(可选)</label>
|
||||
<input type="text" id="servicePath" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如:/admin" onchange="updateServicePreview()">
|
||||
<p id="servicePreview" class="text-sm text-cyan-400 mt-1"></p>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-gray-400 text-sm mb-1">完整URL(不填则用端口生成)</label>
|
||||
<input type="text" id="serviceFullUrl" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如:http://external.com:8080">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-gray-400 text-sm mb-1">后台URL(可选)</label>
|
||||
<input type="text" id="serviceAdminUrl" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="例如:http://external.com:8080/admin">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-gray-400 text-sm mb-1">描述</label>
|
||||
<input type="text" id="serviceDesc" class="w-full bg-gray-700 text-gray-200 px-3 py-2 rounded-lg" placeholder="简要描述">
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<button type="button" onclick="closeAddServiceModal()" class="btn bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded-lg">取消</button>
|
||||
<button type="submit" class="btn bg-green-600 hover:bg-green-700 px-4 py-2 rounded-lg">添加</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 导航按钮 -->
|
||||
<div class="nav-buttons">
|
||||
<button onclick="scrollToTop()" class="nav-btn" title="回到顶部"><i class="ri-arrow-up-line"></i></button>
|
||||
@@ -1337,7 +1389,12 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
|
||||
function renderProjects() {
|
||||
const list = document.getElementById('projectsList');
|
||||
const filtered = currentFilter === 'all' ? projects : projects.filter(p => p.type === currentFilter);
|
||||
|
||||
// 合合自定义服务到项目列表
|
||||
const customServices = getCustomServices();
|
||||
const allProjects = [...projects, ...customServices];
|
||||
|
||||
const filtered = currentFilter === 'all' ? allProjects : allProjects.filter(p => p.type === currentFilter || (currentFilter === 'web' && p.isCustom));
|
||||
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML = '<div class="text-center py-12 text-gray-400"><i class="ri-folder-open-line text-4xl"></i><p class="mt-2">暂无项目</p></div>';
|
||||
@@ -1347,13 +1404,15 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
const activeStatus = getActiveStatus();
|
||||
const grouped = {};
|
||||
filtered.forEach(p => {
|
||||
if (!grouped[p.type]) grouped[p.type] = [];
|
||||
grouped[p.type].push(p);
|
||||
const groupType = p.isCustom ? 'custom' : p.type;
|
||||
if (!grouped[groupType]) grouped[groupType] = [];
|
||||
grouped[groupType].push(p);
|
||||
});
|
||||
|
||||
let html = '';
|
||||
const typeNames = {
|
||||
'web': { name: 'Web服务', icon: 'ri-server-line', color: 'blue' },
|
||||
'custom': { name: '自定义服务', icon: 'ri-global-line', color: 'cyan' },
|
||||
'cron': { name: 'Cron任务', icon: 'ri-time-line', color: 'yellow' },
|
||||
'cli': { name: 'CLI工具', icon: 'ri-terminal-box-line', color: 'purple' },
|
||||
'extension': { name: '浏览器插件', icon: 'ri-chrome-line', color: 'pink' }
|
||||
@@ -1399,6 +1458,27 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
}
|
||||
|
||||
function renderProjectCard(p, isActive = true) {
|
||||
// 自定义服务特殊处理
|
||||
if (p.isCustom) {
|
||||
return `
|
||||
<div class="card rounded-lg p-3 hover:border-cyan-500 transition-colors border-cyan-500/30">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="status-dot bg-cyan-400" style="box-shadow: 0 0 8px #22d3ee;" title="外部服务"></div>
|
||||
<h3 class="font-semibold text-sm truncate">${p.name}</h3>
|
||||
</div>
|
||||
<span class="text-xs text-cyan-400">外部</span>
|
||||
</div>
|
||||
<p class="text-gray-400 text-xs mb-2 truncate">${p.description || ''}</p>
|
||||
<div class="flex items-center gap-1 text-xs flex-wrap">
|
||||
<a href="${p.url}" target="_blank" class="px-2 py-0.5 rounded bg-cyan-500/20 text-cyan-400 hover:bg-cyan-500/30">访问</a>
|
||||
${p.admin_url ? `<a href="${p.admin_url}" target="_blank" class="text-yellow-400 hover:text-yellow-300 ml-1">后台</a>` : ''}
|
||||
<button onclick="deleteCustomService('${p.id}')" class="text-red-400 hover:text-red-300 ml-2" title="删除服务"><i class="ri-delete-bin-line"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
const statusInfo = getStatusInfo(p.status?.status);
|
||||
|
||||
// 处理后台链接:将 localhost 替换为 externalIp
|
||||
@@ -1564,6 +1644,96 @@ HTML_TEMPLATE = '''<!DOCTYPE html>
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 自定义服务管理 ====================
|
||||
|
||||
function getCustomServices() {
|
||||
const stored = localStorage.getItem('customServices');
|
||||
return stored ? JSON.parse(stored) : [];
|
||||
}
|
||||
|
||||
function saveCustomServices(services) {
|
||||
localStorage.setItem('customServices', JSON.stringify(services));
|
||||
}
|
||||
|
||||
function showAddServiceModal() {
|
||||
document.getElementById('serviceName').value = '';
|
||||
document.getElementById('servicePort').value = '';
|
||||
document.getElementById('servicePath').value = '';
|
||||
document.getElementById('serviceFullUrl').value = '';
|
||||
document.getElementById('serviceAdminUrl').value = '';
|
||||
document.getElementById('serviceDesc').value = '';
|
||||
document.getElementById('servicePreview').textContent = '';
|
||||
document.getElementById('addServiceModal').classList.remove('hidden');
|
||||
}
|
||||
|
||||
function closeAddServiceModal() {
|
||||
document.getElementById('addServiceModal').classList.add('hidden');
|
||||
}
|
||||
|
||||
function updateServicePreview() {
|
||||
const port = document.getElementById('servicePort').value.trim();
|
||||
const path = document.getElementById('servicePath').value.trim();
|
||||
const preview = document.getElementById('servicePreview');
|
||||
|
||||
if (port) {
|
||||
preview.textContent = `预览: http://${externalIp}:${port}${path || ''}`;
|
||||
} else {
|
||||
preview.textContent = '';
|
||||
}
|
||||
}
|
||||
|
||||
function saveCustomService(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const name = document.getElementById('serviceName').value.trim();
|
||||
const port = document.getElementById('servicePort').value.trim();
|
||||
const path = document.getElementById('servicePath').value.trim();
|
||||
const fullUrl = document.getElementById('serviceFullUrl').value.trim();
|
||||
const adminUrl = document.getElementById('serviceAdminUrl').value.trim();
|
||||
const desc = document.getElementById('serviceDesc').value.trim();
|
||||
|
||||
if (!name) {
|
||||
alert('请输入服务名称');
|
||||
return;
|
||||
}
|
||||
|
||||
let mainUrl;
|
||||
if (fullUrl) {
|
||||
mainUrl = fullUrl;
|
||||
} else if (port) {
|
||||
mainUrl = `http://${externalIp}:${port}${path || ''}`;
|
||||
} else {
|
||||
alert('请输入端口或完整URL');
|
||||
return;
|
||||
}
|
||||
|
||||
const services = getCustomServices();
|
||||
const newService = {
|
||||
id: 'custom_' + Date.now(),
|
||||
name: name,
|
||||
url: mainUrl,
|
||||
admin_url: adminUrl || null,
|
||||
description: desc || '自定义外部服务',
|
||||
type: 'custom',
|
||||
isCustom: true
|
||||
};
|
||||
services.push(newService);
|
||||
saveCustomServices(services);
|
||||
|
||||
closeAddServiceModal();
|
||||
loadProjects();
|
||||
}
|
||||
|
||||
function deleteCustomService(serviceId) {
|
||||
if (!confirm('确定删除此自定义服务?')) return;
|
||||
|
||||
const services = getCustomServices();
|
||||
const filtered = services.filter(s => s.id !== serviceId);
|
||||
saveCustomServices(filtered);
|
||||
|
||||
loadProjects();
|
||||
}
|
||||
|
||||
function getStatusInfo(status) {
|
||||
const map = {
|
||||
'running': { text: '运行中', class: 'status-running', textColor: 'text-green-400' },
|
||||
|
||||
91
logs/app.log
91
logs/app.log
@@ -1,8 +1,8 @@
|
||||
[2026-04-20 12:33:54] ==================================================
|
||||
[2026-04-20 12:33:54] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-20 12:33:54] 访问地址: http://localhost:19013
|
||||
[2026-04-20 12:33:54] 进程PID: 3711750
|
||||
[2026-04-20 12:33:54] ==================================================
|
||||
[2026-04-23 12:55:21] ==================================================
|
||||
[2026-04-23 12:55:21] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-23 12:55:21] 访问地址: http://localhost:19013
|
||||
[2026-04-23 12:55:21] 进程PID: 1025030
|
||||
[2026-04-23 12:55:21] ==================================================
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: off
|
||||
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
|
||||
@@ -10,66 +10,21 @@ WARNING: This is a development server. Do not use it in a production deployment.
|
||||
* Running on http://127.0.0.1:19013
|
||||
* Running on http://192.168.2.17:19013
|
||||
Press CTRL+C to quit
|
||||
127.0.0.1 - - [20/Apr/2026 12:33:58] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:33:59] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:08] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:09] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:18] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:19] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:24] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:26] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:28] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:29] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:30] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.10 - - [20/Apr/2026 12:34:32] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:35] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.10 - - [20/Apr/2026 12:34:37] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:38] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:39] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:48] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:49] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:54] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:56] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:34:58] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:34:59] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:35:08] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:35:09] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:35:18] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:35:19] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:35:24] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:35:26] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:35:28] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [20/Apr/2026 12:35:29] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [20/Apr/2026 12:35:30] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.10 - - [20/Apr/2026 12:35:32] "GET /api/projects HTTP/1.1" 200 -
|
||||
[2026-04-20 12:35:32] ⚠️ 进程收到 SIGTERM 信号,即将退出!
|
||||
[2026-04-20 16:25:05] ==================================================
|
||||
[2026-04-20 16:25:05] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-20 16:25:05] 访问地址: http://localhost:19013
|
||||
[2026-04-20 16:25:05] 进程PID: 3729863
|
||||
[2026-04-20 16:25:05] ==================================================
|
||||
[2026-04-20 16:29:02] 从系统 crontab 同步了 12 个任务
|
||||
[2026-04-20 16:30:55] 更新 Cron 任务 ID=1: 版本 1
|
||||
[2026-04-20 16:32:42] 更新 Cron 任务 ID=11: 版本 1
|
||||
[2026-04-20 21:28:14] ⚠️ 进程收到 SIGTERM 信号,即将退出!
|
||||
[2026-04-20 22:08:29] ==================================================
|
||||
[2026-04-20 22:08:29] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-20 22:08:29] 访问地址: http://localhost:19013
|
||||
[2026-04-20 22:08:29] 进程PID: 3871388
|
||||
[2026-04-20 22:08:29] ==================================================
|
||||
[2026-04-20 23:20:05] ⚠️ 进程收到 SIGTERM 信号,即将退出!
|
||||
[2026-04-20 23:20:06] ==================================================
|
||||
[2026-04-20 23:20:06] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-20 23:20:06] 访问地址: http://localhost:19013
|
||||
[2026-04-20 23:20:06] 进程PID: 3894049
|
||||
[2026-04-20 23:20:06] ==================================================
|
||||
[2026-04-21 15:44:37] ==================================================
|
||||
[2026-04-21 15:44:37] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-21 15:44:37] 访问地址: http://localhost:19013
|
||||
[2026-04-21 15:44:37] 进程PID: 66363
|
||||
[2026-04-21 15:44:37] ==================================================
|
||||
[2026-04-21 19:00:30] ==================================================
|
||||
[2026-04-21 19:00:30] 项目服务管理面板 v2.0.0 启动
|
||||
[2026-04-21 19:00:30] 访问地址: http://localhost:19013
|
||||
[2026-04-21 19:00:30] 进程PID: 120669
|
||||
[2026-04-21 19:00:30] ==================================================
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:23] "GET / HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:24] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [23/Apr/2026 12:55:25] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:28] "GET / HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:29] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.14 - - [23/Apr/2026 12:55:30] "GET /api/projects HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [23/Apr/2026 12:55:31] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:32] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.14 - - [23/Apr/2026 12:55:33] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:38] "GET / HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:39] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.14 - - [23/Apr/2026 12:55:40] "GET /api/projects HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [23/Apr/2026 12:55:41] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:48] "GET / HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:49] "GET / HTTP/1.1" 200 -
|
||||
192.168.2.14 - - [23/Apr/2026 12:55:50] "GET /api/projects HTTP/1.1" 200 -
|
||||
192.168.2.8 - - [23/Apr/2026 12:55:51] "GET /api/projects HTTP/1.1" 200 -
|
||||
127.0.0.1 - - [23/Apr/2026 12:55:54] "GET / HTTP/1.1" 200 -
|
||||
|
||||
6
logs/voice-chat-web.log
Normal file
6
logs/voice-chat-web.log
Normal file
@@ -0,0 +1,6 @@
|
||||
|
||||
==================================================
|
||||
[2026-04-22T12:29:26.685865] start
|
||||
Command: mkdir -p logs && MODEL_SERVICE_URL=http://192.168.2.5:12001 nohup python3 main.py > logs/server.log 2>&1 & disown
|
||||
Directory: /home/xian/.openclaw/workspace-coder/works/voice-chat-web
|
||||
/bin/sh: 1: disown: not found
|
||||
@@ -79,3 +79,9 @@ Directory: /home/xian/.openclaw/workspace-coder/works/xian-favor
|
||||
Command: mkdir -p logs && nohup python3 -c "from xian_favor.api import start_server; start_server(port=19014)" > logs/app.log 2>&1 & disown
|
||||
Directory: /home/xian/.openclaw/workspace-coder/works/xian-favor
|
||||
/bin/sh: 1: disown: not found
|
||||
|
||||
==================================================
|
||||
[2026-04-23T00:57:27.686904] start
|
||||
Command: mkdir -p logs && nohup python3 -c "from xian_favor.api import start_server; start_server(port=19014)" > logs/app.log 2>&1 & disown
|
||||
Directory: /home/xian/.openclaw/workspace-coder/works/xian-favor
|
||||
/bin/sh: 1: disown: not found
|
||||
|
||||
Reference in New Issue
Block a user