Skip to content

Web UI: Enhance group operations #1680

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/profiles_settings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions .idea/supervisor.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions INSTALL_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Supervisor 安装指南

## 安装方法

### 1. 使用 pip 安装(推荐)

```bash
pip install supervisor
```

### 2. 从源码安装

```bash
# 克隆仓库
git clone https://github.com/你的用户名/supervisor.git
cd supervisor

# 安装
pip install -e .
```

## 基本配置

1. 生成默认配置文件:

```bash
echo_supervisord_conf > supervisord.conf
```

2. 编辑配置文件,根据需要修改:

```bash
# 设置HTTP服务器端口
[inet_http_server]
port=0.0.0.0:9001
username=admin
password=123456

# 添加您的程序
[program:yourapp]
command=/path/to/your/program
```

## 启动 Supervisor

```bash
# 启动 Supervisor
supervisord -c supervisord.conf

# 使用 supervisorctl 控制进程
supervisorctl status
supervisorctl start all
supervisorctl stop all
supervisorctl restart all
```

## Web 界面

安装成功后,访问 http://localhost:9001 即可打开 Supervisor 的 Web 界面。

用户名:admin
密码:123456 (根据您的配置)

## 组操作

本版本支持对进程组进行操作:

- 重启组:点击组名旁边的"重启组"按钮
- 停止组:点击组名旁边的"停止组"按钮

## 日志查看

点击进程名后的"查看日志"按钮,可以查看该进程的日志输出。
83 changes: 83 additions & 0 deletions PACKAGING_GUIDE_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Supervisor 打包与发布指南

## 打包过程

### 1. 环境准备

确保安装了必要的打包工具:

```bash
pip install setuptools wheel twine
```

### 2. 修改版本号

1. 编辑 `supervisor/version.txt` 文件,设置正确的版本号
2. 修改配置文件中的端口设置(如需要)

### 3. 构建包

运行打包脚本生成分发包:

```bash
./build_package.sh
```

打包完成后,会在 `dist/` 目录下生成以下文件:
- `supervisor-4.3.0.tar.gz` - 源码分发包
- `supervisor-4.3.0-py2.py3-none-any.whl` - Python wheel 包

### 4. 测试安装包

在测试环境中测试生成的包:

```bash
# 从源码包安装
pip install dist/supervisor-4.3.0.tar.gz

# 或从 wheel 包安装
pip install dist/supervisor-4.3.0-py2.py3-none-any.whl
```

### 5. 上传到 PyPI(可选)

如果你有 PyPI 帐号并想公开发布,可以使用:

```bash
python -m twine upload dist/*
```

## 文档结构

发布版本应包含以下文档:

- `README_CN.md` - 中文项目说明
- `INSTALL_CN.md` - 中文安装指南
- `RELEASE_NOTES_CN.md` - 中文发布说明
- `PACKAGING_GUIDE_CN.md` - 中文打包指南(本文档)

## 本地部署

如果只需要本地部署,可以将打包好的分发包复制到目标机器并安装:

```bash
# 在目标机器上
pip install supervisor-4.3.0.tar.gz
```

## 打包脚本说明

`build_package.sh` 脚本执行以下操作:

1. 清理之前的构建文件
2. 构建源码分发包和 wheel 包
3. 显示生成的包文件列表
4. 提供上传到 PyPI 的命令提示

## 故障排除

如果在打包过程中遇到问题:

1. 检查 Python 版本是否兼容
2. 确认所有依赖已正确安装
3. 检查文件权限和路径是否正确
40 changes: 40 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Supervisor - 进程控制系统

Supervisor 是一个客户端/服务器系统,允许用户在类 UNIX 操作系统上控制多个进程。

## 特性

- **进程管理**:启动、停止、重启进程
- **自动重启**:进程崩溃时自动重启
- **状态监控**:监控进程状态
- **日志管理**:收集和管理进程输出
- **Web界面**:通过Web界面管理进程
- **组操作**:对进程组进行批量操作

## 增强功能

本版本在原始 Supervisor 基础上增加了以下功能:

1. **组操作按钮**:在Web界面中直接操作进程组
- 重启组:一键重启整个组中的所有进程
- 停止组:一键停止整个组中的所有进程

2. **日志查看增强**:美化了日志查看页面
- 语法高亮
- 行号显示
- 自动滚动
- 搜索功能
- 复制功能

## 安装

详细安装说明请参考 [INSTALL_CN.md](INSTALL_CN.md)

## 许可证

Supervisor 是根据类 BSD 许可证发布的,详见 [LICENSE.txt](LICENSES.txt)

## 更多信息

- 官方文档:http://supervisord.org/
- 源代码:https://github.com/Supervisor/supervisor
48 changes: 48 additions & 0 deletions RELEASE_NOTES_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Supervisor 4.3.0 发布说明

## 版本亮点

Supervisor 4.3.0 版本是一个重要的增强版本,在标准 Supervisor 功能基础上增加了更多实用功能。

### 主要新特性

1. **进程组操作按钮**
- 在 Web 界面中直接添加了"重启组"和"停止组"按钮
- 可以一键操作整个进程组,提高管理效率
- 优化了按钮布局和交互体验

2. **增强的日志查看界面**
- 全新设计的日志查看页面
- 添加了语法高亮显示
- 支持行号显示
- 提供自动滚动功能
- 增加日志搜索功能
- 一键复制日志内容

3. **用户界面改进**
- 优化了组和进程的显示层次结构
- 改进了按钮布局和样式
- 增强了整体视觉体验

### 错误修复

- 修复了组操作功能中的异步回调处理问题
- 解决了多个界面显示和布局问题
- 修复了 RPC 接口调用相关的错误

## 安装指南

详细安装说明请参阅 [INSTALL_CN.md](INSTALL_CN.md)

## 升级说明

如果您正在从之前的版本升级,只需按照安装指南重新安装即可。配置文件格式保持兼容。

## 兼容性

- 支持 Python 2.7 及 Python 3.4+
- 兼容所有主流 UNIX/Linux 系统及 macOS

## 致谢

特别感谢所有为此版本贡献代码、测试和反馈的开发者。
163 changes: 163 additions & 0 deletions SUPERVISOR_USAGE_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Supervisor 使用说明

## 配置文件位置

当前项目使用的配置文件位于:
```
/Users/feiwentao/tianhei_projects/python_projests/supervisor/supervisord.conf
```

## 常用命令

为了更方便地操作 Supervisor,我们创建了一个命令辅助脚本 `supervisor_cmd.sh`。使用此脚本可以避免认证错误和路径问题。

### 基本命令

```bash
# 查看所有进程状态
./supervisor_cmd.sh status

# 启动特定进程
./supervisor_cmd.sh start <进程名>

# 停止特定进程
./supervisor_cmd.sh stop <进程名>

# 重启特定进程
./supervisor_cmd.sh restart <进程名>

# 关闭 Supervisor
./supervisor_cmd.sh shutdown
```

### 组操作命令

```bash
# 启动整个组
./supervisor_cmd.sh start <组名>:*

# 停止整个组
./supervisor_cmd.sh stop <组名>:*

# 重启整个组
./supervisor_cmd.sh restart <组名>:*
```

### 配置管理命令

```bash
# 重新读取配置文件
./supervisor_cmd.sh reread

# 更新配置(应用新的配置)
./supervisor_cmd.sh update

# 显示所有命令帮助
./supervisor_cmd.sh help
```

## 修改配置文件

使用文本编辑器打开配置文件:

```bash
vim supervisord.conf
# 或者
open -a TextEdit supervisord.conf
```

### 配置文件主要部分

1. **HTTP 服务器设置**:
```ini
[inet_http_server]
port=0.0.0.0:9001 # 端口设置
username=admin # 登录用户名
password=123456 # 登录密码
```

2. **进程配置**:
```ini
[program:程序名称]
command=要执行的命令 # 必填,程序启动命令
directory=工作目录 # 程序的工作目录
autostart=true # 是否自动启动
autorestart=true # 是否自动重启
redirect_stderr=true # 是否重定向错误输出
stdout_logfile=日志路径 # 日志文件位置
```

3. **进程组配置**:
```ini
[group:组名]
programs=程序1,程序2 # 组内的程序列表
priority=999 # 启动优先级
```

### 添加新程序

1. 在配置文件末尾添加新的程序段:
```ini
[program:新程序名]
command=python /path/to/your/script.py
directory=/path/to/working/dir
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=./logs/新程序名.log
environment=变量1="值1",变量2="值2"
```

2. 如果要将程序添加到组中,先添加程序配置,然后添加或修改组配置:
```ini
[group:组名]
programs=现有程序1,现有程序2,新程序名
priority=999
```

### 修改现有程序配置

找到对应的 `[program:xxx]` 部分,修改相应的参数。常用参数包括:

- **command**: 启动命令
- **directory**: 工作目录
- **autostart**: 是否自动启动(true/false)
- **autorestart**: 自动重启(true/false/unexpected)
- **redirect_stderr**: 是否将错误输出重定向到标准输出(true/false)
- **stdout_logfile**: 标准输出日志文件路径
- **environment**: 环境变量设置

## Web 界面

Supervisor 提供了一个 Web 界面来管理您的进程:

- 地址:http://localhost:9001
- 用户名:admin
- 密码:123456 (根据配置文件设置)

通过 Web 界面,您可以:
- 查看所有进程的状态
- 启动/停止/重启单个进程
- 启动/停止/重启整个进程组
- 查看进程日志

## 常见问题解决

### 1. 认证错误
问题:`Server requires authentication: error: 401 Unauthorized`
解决:使用 `./supervisor_cmd.sh` 脚本执行命令,或指定配置文件路径:
```bash
supervisorctl -c $(pwd)/supervisord.conf -u admin -p 123456 status
```

### 2. 无法连接到 Supervisor
问题:`unix:///tmp/supervisor.sock no such file`
解决:确保 Supervisor 已启动,并且 socket 文件路径正确。

### 3. 进程启动后立即退出
问题:进程状态显示为 `FATAL``BACKOFF`
解决:
- 检查命令是否正确
- 查看进程日志了解详细错误信息
- 确保工作目录正确
- 检查环境变量设置
19 changes: 19 additions & 0 deletions build_package.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
# 打包和发布Supervisor
set -e

# 清理之前的构建
echo "清理之前的构建..."
rm -rf build/ dist/ *.egg-info/

# 构建源码包和wheel包
echo "构建源码包和wheel包..."
python setup.py sdist bdist_wheel

echo "构建完成!"
echo "生成的包在dist/目录下"
ls -la dist/

echo ""
echo "如需上传到PyPI,请运行:"
echo "python -m twine upload dist/*"
9 changes: 9 additions & 0 deletions scripts/counter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import time

print("计数器脚本已启动")

count = 0
while True:
count += 1
print(f"当前计数: {count}")
time.sleep(2)
10 changes: 10 additions & 0 deletions scripts/cpu_monitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import time
import psutil

print("CPU监控脚本已启动")

while True:
print(f"CPU使用率: {psutil.cpu_percent(interval=1)}%")
for i, percentage in enumerate(psutil.cpu_percent(interval=1, percpu=True)):
print(f"CPU {i}: {percentage}%")
time.sleep(8)
10 changes: 10 additions & 0 deletions scripts/memory_monitor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import time
import psutil

print("内存监控脚本已启动")

while True:
memory = psutil.virtual_memory()
print(f"内存使用率: {memory.percent}%")
print(f"可用内存: {memory.available / (1024 * 1024):.2f} MB")
time.sleep(10)
9 changes: 9 additions & 0 deletions scripts/time_printer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import time
import datetime

print("时间打印脚本已启动")

while True:
now = datetime.datetime.now()
print(f"当前时间: {now.strftime('%Y-%m-%d %H:%M:%S')}")
time.sleep(5)
223 changes: 206 additions & 17 deletions supervisor/http.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# -*- coding: utf-8 -*-
"""Medusa HTTP server implementation
This module contains the HTTP server implementation used by supervisor.
"""

import os
import stat
import time
@@ -6,6 +12,7 @@
import errno
import weakref
import traceback
import supervisor.options

try:
import pwd
@@ -635,10 +642,11 @@ def checkused(self, socketname):
return True

class tail_f_producer:
def __init__(self, request, filename, head):
def __init__(self, request, filename, head, is_html=False):
self.request = weakref.ref(request)
self.filename = filename
self.delay = 0.1
self.is_html = is_html

self._open()
sz = self._fsize()
@@ -658,14 +666,45 @@ def more(self):
bytes_added = newsz - self.sz
if bytes_added < 0:
self.sz = 0
return "==> File truncated <==\n"
return "==> File truncated <==\n" if not self.is_html else "<span class='log-warn'>==> File truncated &lt;==</span>\n"
if bytes_added > 0:
self.file.seek(-bytes_added, 2)
bytes = self.file.read(bytes_added)
data = self.file.read(bytes_added)
self.sz = newsz
return bytes

if self.is_html and data:
# HTML escape to prevent breaking HTML structure
data = data.replace(b'&', b'&amp;').replace(b'<', b'&lt;').replace(b'>', b'&gt;')
# Highlight log levels
data = self._highlight_log_levels(data)

return data
return NOT_DONE_YET

def _highlight_log_levels(self, data):
"""Highlight different log levels"""
import re

# Convert byte data to string for regular expression
if isinstance(data, bytes):
data_str = data.decode('utf-8', errors='replace')
else:
data_str = data

# Define regex patterns for log levels and corresponding CSS classes
patterns = [
(r'\b(ERROR|CRITICAL|FATAL)\b', 'log-error'),
(r'\b(WARN|WARNING)\b', 'log-warn'),
(r'\b(INFO|NOTICE)\b', 'log-info'),
(r'\b(DEBUG|TRACE)\b', 'log-debug')
]

# Add span tags for each match
for pattern, css_class in patterns:
data_str = re.sub(pattern, r'<span class="%s">\1</span>' % css_class, data_str)

return data_str.encode('utf-8')

def _open(self):
self.file = open(self.filename, 'rb')
self.ino = os.fstat(self.file.fileno())[stat.ST_INO]
@@ -680,7 +719,7 @@ def _follow(self):
except (OSError, ValueError):
# file was unlinked
return

if self.ino != ino: # log rotation occurred
self._close()
self._open()
@@ -744,18 +783,168 @@ def handle_request(self, request):
request.error(404) # not found
return

mtime = os.stat(logfile)[stat.ST_MTIME]
request['Last-Modified'] = http_date.build_http_date(mtime)
request['Content-Type'] = 'text/plain;charset=utf-8'
# the lack of a Content-Length header makes the outputter
# send a 'Transfer-Encoding: chunked' response
request['X-Accel-Buffering'] = 'no'
# tell reverse proxy server (e.g., nginx) to disable proxy buffering
# (see also http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering)

request.push(tail_f_producer(request, logfile, 1024))

request.done()
# Get the format parameter from request to decide whether to return HTML or plain text
is_html = False
if query:
import cgi
parsed_query = cgi.parse_qs(query)
is_html = parsed_query.get('format', ['plain'])[0] == 'html'

if is_html:
# Return a beautified HTML page
mtime = os.stat(logfile)[stat.ST_MTIME]
request['Last-Modified'] = http_date.build_http_date(mtime)
request['Content-Type'] = 'text/html;charset=utf-8'
request['X-Accel-Buffering'] = 'no'

# Create the full process name
full_process_name = process_name
if group_name != process_name:
full_process_name = "%s:%s" % (group_name, process_name)

# Build HTML header
html_head = '''<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Process %s Log</title>
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
color: #333;
}
.log-container {
max-width: 1200px;
margin: 20px auto;
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.log-header {
background: #2c3e50;
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.log-header h1 {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.log-controls {
display: flex;
gap: 10px;
}
.log-controls a button {
background: #3498db;
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}
.log-controls a button:hover {
background: #2980b9;
}
.log-content {
position: relative;
overflow: auto;
max-height: 70vh;
background: #282c34;
color: #abb2bf;
padding: 0;
margin: 0;
}
.log-content pre {
margin: 0;
padding: 15px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 13px;
line-height: 1.5;
tab-size: 4;
white-space: pre-wrap;
}
.log-footer {
display: flex;
justify-content: space-between;
padding: 10px 20px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}
.log-footer a {
color: #3498db;
text-decoration: none;
}
.log-footer a:hover {
text-decoration: underline;
}
/* Highlight specific log levels */
.log-error { color: #e06c75; }
.log-warn { color: #e5c07b; }
.log-info { color: #61afef; }
.log-debug { color: #98c379; }
</style>
</head>
<body>
<div class="log-container">
<div class="log-header">
<h1>Log for Process %s</h1>
<div class="log-controls">
<a href="%s"><button type="button">Refresh</button></a>
</div>
</div>
<div class="log-content">
<pre id="log-pre">''' % (full_process_name, full_process_name, request.args[0])

html_foot = '''</pre>
</div>
<div class="log-footer">
<a href="/index.html">Return to Home</a>
<div>Supervisor %s</div>
</div>
</div>
</body>
</html>''' % (supervisor.options.VERSION)

# Send response with beautified page
request.push(html_head)
request.push(tail_f_producer(request, logfile, 1024, is_html=True))
request.push(html_foot)
request.done()

else:
# Original plain text response
# Set content type and necessary headers
request['Content-Type'] = 'text/plain;charset=utf-8'
mtime = os.stat(logfile)[stat.ST_MTIME]
request['Last-Modified'] = http_date.build_http_date(mtime)
request['X-Accel-Buffering'] = 'no'

# Directly push log content
request.push(tail_f_producer(request, logfile, 1024, is_html=False))
request.done()

class mainlogtail_handler:
IDENT = 'Main Logtail HTTP Request Handler'
87 changes: 51 additions & 36 deletions supervisor/ui/status.html
Original file line number Diff line number Diff line change
@@ -3,66 +3,81 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:meld="https://github.com/Supervisor/supervisor">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Supervisor Status</title>
<link href="stylesheets/supervisor.css" rel="stylesheet" type="text/css" />
<link href="images/icon.png" rel="icon" type="image/png" />
</head>
<body>
<div id="wrapper">

<div id="header">
<img src="images/supervisor.gif" alt="Supervisor status" />
<img alt="Supervisor Status" src="images/supervisor.gif" />
</div>

<div meld:id="content">
<div class="hidden" meld:id="statusmessage">#</div>

<form meld:id="form" action="index.html" method="post">
<form action="index.html" method="post">
<ul id="buttons" class="clr">
<li class="action-button"><a href="index.html?action=refresh">Refresh</a></li>
<li class="action-button"><a href="index.html?action=restartall">Restart All</a></li>
<li class="action-button"><a href="index.html?action=stopall">Stop All</a></li>
<li><a href="index.html?action=refresh">Refresh</a></li>
<li><a href="index.html?action=restartall">Restart All</a></li>
<li><a href="index.html?action=stopall">Stop All</a></li>
</ul>

<table cellspacing="0" meld:id="statustable">
<thead>
<tr>
<th class="state">State</th>
<th class="desc">Description</th>
<th class="name">Name</th>
<th class="action">Action</th>
</tr>
</thead>
<div meld:id="process_groups">
<div class="process-group" meld:id="template_group">
<div class="group-header">
<div class="title-with-actions">
<h2 class="group-title" meld:id="group_title">Group Name</h2>
<div class="group-actions" meld:id="group_actions">
<ul>
<li><a href="#" meld:id="group_restart_anchor">Restart Group</a></li>
<li><a href="#" meld:id="group_stop_anchor">Stop Group</a></li>
</ul>
</div>
</div>
</div>
<table cellspacing="0" meld:id="statustable">
<tr>
<th class="state">State</th>
<th class="desc">Description</th>
<th class="name">Name</th>
<th class="action">Action</th>
</tr>

<tbody meld:id="tbody">
<tr meld:id="tr" class="">
<td meld:id="status_td" class="status"><span meld:id="status_text" class="statusnominal">nominal</span></td>
<td meld:id="info_td"><span meld:id="info_text">Info</span></td>
<td meld:id="name_td"><a href="#" meld:id="name_anchor" target="_blank">Name</a></td>
<td meld:id="action_td" class="action">
<ul>
<li meld:id="actionitem_td">
<a href="#" name="#" meld:id="actionitem_anchor">Action</a>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<tr meld:id="tr" class="">
<td class="status">
<span meld:id="status_text" class="statusnominal">Normal</span>
</td>
<td>
<span meld:id="info_text">Info</span>
</td>
<td>
<a href="#" meld:id="name_anchor">Name</a>
</td>
<td class="action">
<ul meld:id="actionitem_td">
<li meld:id="actionitem">
<a href="#" meld:id="actionitem_anchor">Action</a>
</li>
</ul>
</td>
</tr>
</table>
</div>
</div>
</form>

</div>

<div class="push">
</div>
</div>

<div id="footer" class="clr">
<div class="left">
<a href="http://supervisord.org">Supervisor</a> <span meld:id="supervisor_version">#</span>
<a href="http://supervisord.org">Supervisor</a>
<span meld:id="supervisor_version">#</span>
</div>
<div class="right">
&amp;copy; 2006-<span meld:id="copyright_date">#</span> <strong><a href="http://agendaless.com/">Agendaless Consulting and Contributors</a></strong>
© 2006-<span meld:id="copyright_date">#</span>
<strong><a href="http://agendaless.com/">Agendaless Consulting and Contributors</a></strong>
</div>
</div>
</body>
523 changes: 368 additions & 155 deletions supervisor/ui/stylesheets/supervisor.css

Large diffs are not rendered by default.

255 changes: 241 additions & 14 deletions supervisor/ui/tail.html
Original file line number Diff line number Diff line change
@@ -3,25 +3,252 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:meld="https://github.com/Supervisor/supervisor">
<head>
<title meld:id="title">Supervisor Status</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title meld:id="title">Process Log</title>
<link href="stylesheets/supervisor.css" rel="stylesheet" type="text/css" />
<link href="images/icon.png" rel="icon" type="image/png" />
<style>
body {
font-family: 'Helvetica Neue', Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f5f5f5;
color: #333;
}

.log-container {
max-width: 1200px;
margin: 20px auto;
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}

.log-header {
background: #2c3e50;
color: white;
padding: 15px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}

.log-header h1 {
margin: 0;
font-size: 18px;
font-weight: 500;
}

.log-controls {
display: flex;
gap: 10px;
}

.log-controls button {
background: #3498db;
color: white;
border: none;
border-radius: 4px;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
transition: background 0.2s;
}

.log-controls button:hover {
background: #2980b9;
}

.log-search {
display: flex;
padding: 10px;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}

.log-search input {
flex: 1;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 4px;
font-size: 14px;
}

.log-content {
position: relative;
overflow: auto;
max-height: 70vh;
background: #282c34;
color: #abb2bf;
padding: 0;
margin: 0;
}

.log-content pre {
margin: 0;
padding: 15px;
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 13px;
line-height: 1.5;
tab-size: 4;
counter-reset: line;
}

.log-content code {
display: block;
position: relative;
padding-left: 50px;
}

.log-content code:before {
counter-increment: line;
content: counter(line);
position: absolute;
left: 0;
width: 40px;
padding-right: 10px;
color: #636d83;
text-align: right;
user-select: none;
}

.log-footer {
display: flex;
justify-content: space-between;
padding: 10px 20px;
background: #f8f9fa;
border-top: 1px solid #e9ecef;
}

/* 高亮特定日志级别 */
.log-error { color: #e06c75; }
.log-warn { color: #e5c07b; }
.log-info { color: #61afef; }
.log-debug { color: #98c379; }
</style>
</head>
<body>

<div meld:id="content">

<pre meld:id="tailbody"></pre>

<form meld:id="form" action="index.html" method="POST">

<div style="margin-top: 10px;">
<a href="tail.html?processname=thisprocess" meld:id="refresh_anchor">
<input type="button" value=" Refresh " name="refresh" />
</a>
<div class="log-container">
<div class="log-header">
<h1 meld:id="header_title">Process Log</h1>
<div class="log-controls">
<button id="auto-scroll-btn" type="button">Auto Scroll</button>
<button id="copy-logs-btn" type="button">Copy Logs</button>
<a href="tail.html?processname=thisprocess" meld:id="refresh_anchor">
<button type="button">Refresh</button>
</a>
</div>
</div>

<div class="log-search">
<input type="text" id="log-search-input" placeholder="Search logs..." />
</div>

<div class="log-content">
<pre meld:id="tailbody"></pre>
</div>

<div class="log-footer">
<span id="log-stats">Lines: 0</span>
<a href="index.html">Return to Home</a>
</div>
</div>

</form>

</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 初始化日志内容
const logContent = document.querySelector('.log-content');
const pre = document.querySelector('pre');
const logText = pre.textContent;

// 清空 pre 并添加带行号的格式化日志
pre.innerHTML = '';
const lines = logText.split('\n');
lines.forEach(function(line) {
if(line.trim()) {
const code = document.createElement('code');

// 添加日志级别高亮
if(line.includes('ERROR') || line.includes('CRITICAL')) {
code.classList.add('log-error');
} else if(line.includes('WARN') || line.includes('WARNING')) {
code.classList.add('log-warn');
} else if(line.includes('INFO')) {
code.classList.add('log-info');
} else if(line.includes('DEBUG')) {
code.classList.add('log-debug');
}

code.textContent = line;
pre.appendChild(code);
}
});

// 更新行数统计
const logStats = document.getElementById('log-stats');
const lineCount = pre.querySelectorAll('code').length;
logStats.textContent = `Lines: ${lineCount}`;

// 自动滚动
let autoScroll = true;

function scrollToBottom() {
if(autoScroll) {
logContent.scrollTop = logContent.scrollHeight;
}
}

scrollToBottom();

const autoScrollBtn = document.getElementById('auto-scroll-btn');
autoScrollBtn.addEventListener('click', function() {
autoScroll = !autoScroll;
this.textContent = autoScroll ? 'Turn Off Auto Scroll' : 'Auto Scroll';
if(autoScroll) {
scrollToBottom();
}
});

// 复制日志
const copyLogsBtn = document.getElementById('copy-logs-btn');
copyLogsBtn.addEventListener('click', function() {
const logText = Array.from(pre.querySelectorAll('code'))
.map(code => code.textContent)
.join('\n');

navigator.clipboard.writeText(logText).then(function() {
const originalText = copyLogsBtn.textContent;
copyLogsBtn.textContent = 'Copied!';
setTimeout(() => {
copyLogsBtn.textContent = originalText;
}, 2000);
});
});

// 搜索功能
const searchInput = document.getElementById('log-search-input');
searchInput.addEventListener('input', function() {
const searchTerm = this.value.toLowerCase();
const codes = pre.querySelectorAll('code');

if(searchTerm) {
codes.forEach(function(code) {
if(code.textContent.toLowerCase().includes(searchTerm)) {
code.style.backgroundColor = 'rgba(255, 215, 0, 0.2)';
} else {
code.style.backgroundColor = '';
}
});
} else {
codes.forEach(function(code) {
code.style.backgroundColor = '';
});
}
});
});
</script>

</body>
</html>
2 changes: 1 addition & 1 deletion supervisor/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4.3.0.dev0
4.3.0
422 changes: 312 additions & 110 deletions supervisor/web.py

Large diffs are not rendered by default.

22 changes: 22 additions & 0 deletions supervisor_cmd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/bin/bash
# Supervisor 命令辅助脚本

# 如果没有提供参数,则显示帮助
if [ $# -eq 0 ]; then
echo "Supervisor 命令辅助脚本"
echo "用法: $0 <命令>"
echo ""
echo "常用命令:"
echo " status - 查看所有进程状态"
echo " start <name> - 启动进程"
echo " stop <name> - 停止进程"
echo " restart <name> - 重启进程"
echo " shutdown - 关闭 Supervisor"
echo " reread - 重新读取配置"
echo " update - 更新配置"
echo " help - 显示更多命令"
exit 1
fi

# 使用配置文件路径运行 supervisorctl
supervisorctl -c $(pwd)/supervisord.conf "$@"