diff --git a/app.py b/app.py
index 0229cdf..77d153c 100644
--- a/app.py
+++ b/app.py
@@ -3,6 +3,7 @@
项目服务管理面板 v2.2.0
端口: 19013
新增: Web服务卡片添加自定义链接入口(+按钮)
+新增: 自定义新增外部web服务功能(新增服务按钮)
"""
import os
@@ -1057,11 +1058,14 @@ HTML_TEMPLATE = '''
-
+
+
@@ -1260,6 +1264,54 @@ HTML_TEMPLATE = '''
+
+
+
@@ -1337,7 +1389,12 @@ HTML_TEMPLATE = '''
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 = '
';
@@ -1347,13 +1404,15 @@ HTML_TEMPLATE = '''
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 = '''
}
function renderProjectCard(p, isActive = true) {
+ // 自定义服务特殊处理
+ if (p.isCustom) {
+ return `
+
+
+
${p.description || ''}
+
+
访问
+ ${p.admin_url ? `
后台` : ''}
+
+
+
+ `;
+ }
+
const statusInfo = getStatusInfo(p.status?.status);
// 处理后台链接:将 localhost 替换为 externalIp
@@ -1564,6 +1644,96 @@ HTML_TEMPLATE = '''
}
}
+ // ==================== 自定义服务管理 ====================
+
+ 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' },
diff --git a/logs/app.log b/logs/app.log
index e6d58f4..1ac4aa6 100644
--- a/logs/app.log
+++ b/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 -
diff --git a/logs/voice-chat-web.log b/logs/voice-chat-web.log
new file mode 100644
index 0000000..dd8ce2e
--- /dev/null
+++ b/logs/voice-chat-web.log
@@ -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
diff --git a/logs/xian-favor.log b/logs/xian-favor.log
index 3271f3c..832d2a8 100644
--- a/logs/xian-favor.log
+++ b/logs/xian-favor.log
@@ -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