添加容器日志
This commit is contained in:
parent
80304fcfa8
commit
7b90688164
|
|
@ -749,6 +749,31 @@ class DeploymentServer:
|
|||
self.app = Flask(__name__)
|
||||
self._setup_routes()
|
||||
|
||||
def get_all_containers(self):
|
||||
"""获取所有Docker容器列表"""
|
||||
try:
|
||||
cmd = "docker ps --format '{{.ID}}|{{.Names}}|{{.Status}}|{{.Image}}'"
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10)
|
||||
|
||||
if result.returncode != 0:
|
||||
return []
|
||||
|
||||
containers = []
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if line:
|
||||
parts = line.split('|')
|
||||
if len(parts) >= 4:
|
||||
containers.append({
|
||||
'id': parts[0],
|
||||
'name': parts[1],
|
||||
'status': parts[2],
|
||||
'image': parts[3]
|
||||
})
|
||||
return containers
|
||||
except Exception as e:
|
||||
Logger.error(f"获取容器列表失败: {e}")
|
||||
return []
|
||||
|
||||
def _setup_routes(self):
|
||||
"""设置路由"""
|
||||
|
||||
|
|
@ -834,7 +859,7 @@ class DeploymentServer:
|
|||
<h3>📖 使用说明</h3>
|
||||
<p><strong>触发部署:</strong>点击下方表格中的"部署链接",或直接访问 <code>http://IP:9999/项目名称</code></p>
|
||||
<p><strong>示例:</strong><code>http://IP:9999/tuoheng-device</code> 触发 tuoheng-device 项目部署</p>
|
||||
<p><strong>查看日志:</strong><a href="/logs" style="color: #1976d2; font-weight: bold;">点击这里查看部署日志</a></p>
|
||||
<p><strong>查看日志:</strong><a href="/logs" style="color: #1976d2; font-weight: bold;">点击这里查看部署日志</a> | <a href="/containers" style="color: #1976d2; font-weight: bold;">查看容器日志</a></p>
|
||||
</div>
|
||||
<h2>📦 可部署项目列表</h2>
|
||||
<table>
|
||||
|
|
@ -1063,11 +1088,329 @@ class DeploymentServer:
|
|||
except Exception as e:
|
||||
return jsonify({'logs': [], 'error': str(e)})
|
||||
|
||||
@self.app.route('/containers')
|
||||
def view_containers():
|
||||
"""容器列表页面"""
|
||||
containers = self.get_all_containers()
|
||||
|
||||
html = '''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Docker 容器管理</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1400px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
.info {
|
||||
background-color: #e3f2fd;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.back-link {
|
||||
color: #1976d2;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.back-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background-color: white;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-top: 20px;
|
||||
}
|
||||
th, td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
th {
|
||||
background-color: #1976d2;
|
||||
color: white;
|
||||
}
|
||||
tr:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container-link {
|
||||
color: #1976d2;
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.container-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.status-badge {
|
||||
display: inline-block;
|
||||
padding: 3px 8px;
|
||||
border-radius: 3px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
background-color: #4caf50;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🐳 Docker 容器管理</h1>
|
||||
<div class="info">
|
||||
<a href="/" class="back-link">← 返回首页</a>
|
||||
<p><strong>说明:</strong>点击容器名称查看该容器的实时日志</p>
|
||||
</div>
|
||||
<h2>📋 运行中的容器列表</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>容器ID</th>
|
||||
<th>容器名称</th>
|
||||
<th>状态</th>
|
||||
<th>镜像</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
'''
|
||||
|
||||
for container in containers:
|
||||
html += f'''
|
||||
<tr>
|
||||
<td>{container['id'][:12]}</td>
|
||||
<td>{container['name']}</td>
|
||||
<td><span class="status-badge">{container['status']}</span></td>
|
||||
<td>{container['image']}</td>
|
||||
<td><a href="/container-logs/{container['name']}" class="container-link">查看日志</a></td>
|
||||
</tr>
|
||||
'''
|
||||
|
||||
if not containers:
|
||||
html += '''
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center; padding: 20px; color: #999;">
|
||||
暂无运行中的容器
|
||||
</td>
|
||||
</tr>
|
||||
'''
|
||||
|
||||
html += '''
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
return html
|
||||
|
||||
@self.app.route('/container-logs/<container_name>')
|
||||
def view_container_logs(container_name):
|
||||
"""容器日志查看页面"""
|
||||
html = f'''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>容器日志 - {container_name}</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: 'Courier New', monospace;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}}
|
||||
.header {{
|
||||
background-color: #2d2d30;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 20px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}}
|
||||
h1 {{
|
||||
margin: 0;
|
||||
color: #4ec9b0;
|
||||
font-size: 24px;
|
||||
}}
|
||||
.controls {{
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}}
|
||||
button {{
|
||||
background-color: #0e639c;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}}
|
||||
button:hover {{
|
||||
background-color: #1177bb;
|
||||
}}
|
||||
.log-container {{
|
||||
background-color: #1e1e1e;
|
||||
border: 1px solid #3e3e42;
|
||||
border-radius: 5px;
|
||||
padding: 15px;
|
||||
height: calc(100vh - 180px);
|
||||
overflow-y: auto;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
}}
|
||||
.log-line {{
|
||||
margin: 2px 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}}
|
||||
.log-error {{
|
||||
color: #f48771;
|
||||
}}
|
||||
.log-warning {{
|
||||
color: #dcdcaa;
|
||||
}}
|
||||
.log-info {{
|
||||
color: #4ec9b0;
|
||||
}}
|
||||
.loading {{
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #858585;
|
||||
}}
|
||||
.back-link {{
|
||||
color: #4ec9b0;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}}
|
||||
.back-link:hover {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<div>
|
||||
<h1>📋 容器日志: {container_name}</h1>
|
||||
<a href="/containers" class="back-link">← 返回容器列表</a>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<button onclick="refreshLogs()">🔄 刷新</button>
|
||||
<button onclick="clearDisplay()">🗑️ 清空显示</button>
|
||||
<button onclick="toggleAutoRefresh()">⏱️ <span id="autoRefreshText">开启自动刷新</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="log-container" id="logContainer">
|
||||
<div class="loading">正在加载日志...</div>
|
||||
</div>
|
||||
<script>
|
||||
let autoRefreshInterval = null;
|
||||
let isAutoRefresh = false;
|
||||
const containerName = '{container_name}';
|
||||
|
||||
function formatLogLine(line) {{
|
||||
if (line.includes('ERROR') || line.includes('error') || line.includes('Exception') || line.includes('Failed')) {{
|
||||
return '<div class="log-line log-error">' + escapeHtml(line) + '</div>';
|
||||
}} else if (line.includes('WARN') || line.includes('warn') || line.includes('WARNING')) {{
|
||||
return '<div class="log-line log-warning">' + escapeHtml(line) + '</div>';
|
||||
}} else if (line.includes('INFO') || line.includes('info')) {{
|
||||
return '<div class="log-line log-info">' + escapeHtml(line) + '</div>';
|
||||
}} else {{
|
||||
return '<div class="log-line">' + escapeHtml(line) + '</div>';
|
||||
}}
|
||||
}}
|
||||
|
||||
function escapeHtml(text) {{
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}}
|
||||
|
||||
function refreshLogs() {{
|
||||
fetch('/api/container-logs/' + containerName)
|
||||
.then(response => response.json())
|
||||
.then(data => {{
|
||||
const container = document.getElementById('logContainer');
|
||||
if (data.logs && data.logs.length > 0) {{
|
||||
container.innerHTML = data.logs.map(formatLogLine).join('');
|
||||
container.scrollTop = container.scrollHeight;
|
||||
}} else if (data.error) {{
|
||||
container.innerHTML = '<div class="log-line log-error">获取日志失败: ' + data.error + '</div>';
|
||||
}} else {{
|
||||
container.innerHTML = '<div class="loading">暂无日志</div>';
|
||||
}}
|
||||
}})
|
||||
.catch(error => {{
|
||||
console.error('获取日志失败:', error);
|
||||
document.getElementById('logContainer').innerHTML =
|
||||
'<div class="log-line log-error">获取日志失败: ' + error.message + '</div>';
|
||||
}});
|
||||
}}
|
||||
|
||||
function clearDisplay() {{
|
||||
document.getElementById('logContainer').innerHTML = '<div class="loading">显示已清空,点击刷新重新加载</div>';
|
||||
}}
|
||||
|
||||
function toggleAutoRefresh() {{
|
||||
isAutoRefresh = !isAutoRefresh;
|
||||
const text = document.getElementById('autoRefreshText');
|
||||
|
||||
if (isAutoRefresh) {{
|
||||
text.textContent = '关闭自动刷新';
|
||||
autoRefreshInterval = setInterval(refreshLogs, 3000);
|
||||
refreshLogs();
|
||||
}} else {{
|
||||
text.textContent = '开启自动刷新';
|
||||
if (autoRefreshInterval) {{
|
||||
clearInterval(autoRefreshInterval);
|
||||
autoRefreshInterval = null;
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
|
||||
// 页面加载时获取日志
|
||||
refreshLogs();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
return html
|
||||
|
||||
@self.app.route('/api/container-logs/<container_name>')
|
||||
def get_container_logs(container_name):
|
||||
"""获取容器日志API"""
|
||||
try:
|
||||
cmd = f"docker logs --tail 500 {container_name}"
|
||||
result = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=10)
|
||||
|
||||
if result.returncode != 0:
|
||||
return jsonify({'logs': [], 'error': f'获取容器日志失败: {result.stderr}'})
|
||||
|
||||
# 合并 stdout 和 stderr
|
||||
logs = []
|
||||
if result.stdout:
|
||||
logs.extend(result.stdout.strip().split('\n'))
|
||||
if result.stderr:
|
||||
logs.extend(result.stderr.strip().split('\n'))
|
||||
|
||||
return jsonify({'logs': logs})
|
||||
except Exception as e:
|
||||
return jsonify({'logs': [], 'error': str(e)})
|
||||
|
||||
@self.app.route('/<project_name>')
|
||||
def deploy_project(project_name):
|
||||
"""触发项目部署"""
|
||||
# 避免与 /logs 路由冲突
|
||||
if project_name == 'logs' or project_name == 'api':
|
||||
# 避免与其他路由冲突
|
||||
if project_name in ['logs', 'api', 'containers', 'container-logs']:
|
||||
return jsonify({'code': 404, 'message': '路径不存在'})
|
||||
|
||||
Logger.info(f"收到HTTP部署请求: {project_name}")
|
||||
|
|
|
|||
Loading…
Reference in New Issue