Files
openclaw-monitor/code/frontend/src/pages/Dashboard/index.js
flying-hero c39a912456 feat: 添加龙虾详情页面功能
- 新建 LobsterDetail 组件,显示龙虾详细信息
- 添加 React Router 路由配置 (/lobster/:id)
- Dashboard 添加详情按钮,支持跳转到详情页
- 详情页功能:
  * 基本信息展示(名称、专长、端口、容器、工作区)
  * 运行状态指示器(带脉冲动画)
  * 快速操作(查看记忆、访问服务、复制地址)
  * 运行统计卡片
- 修复 index.html 缺少 root 挂载点问题
- 添加一键重启脚本 restart.sh
- 更新任务跟踪文档

🦸 北极星指引方向,飞行侠展翅飞翔
2026-04-02 13:15:41 +08:00

196 lines
4.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import axios from 'axios';
const API_BASE = 'http://localhost:8000/api';
function Dashboard() {
const [lobsters, setLobsters] = useState([]);
const [loading, setLoading] = useState(true);
const navigate = useNavigate();
useEffect(() => {
fetchLobsters();
const interval = setInterval(fetchLobsters, 5000);
return () => clearInterval(interval);
}, []);
const fetchLobsters = async () => {
try {
const response = await axios.get(`${API_BASE}/lobsters/`);
setLobsters(response.data);
setLoading(false);
} catch (error) {
console.error('获取龙虾状态失败:', error);
}
};
if (loading) {
return <div className="loading">加载中...</div>;
}
return (
<div className="dashboard">
<h1>🦞 龙虾舰队监控中心</h1>
<div className="lobster-grid">
{lobsters.map(lobster => (
<div key={lobster.id} className="lobster-card">
<div className="lobster-header">
<span className="lobster-name">{lobster.emoji} {lobster.name}</span>
<span className={`status status-${lobster.status}`}>{lobster.status}</span>
</div>
<div className="lobster-info">
<p>专长{lobster.specialty}</p>
<p>端口{lobster.port}</p>
<p>容器{lobster.container}</p>
</div>
<div className="lobster-actions">
<button className="detail-btn" onClick={() => navigate(`/lobster/${lobster.id}`)}>
📊 详情
</button>
<button className="memory-btn" onClick={() => navigate(`/lobster/${lobster.id}/memory`)}>
🧠 记忆
</button>
</div>
</div>
))}
</div>
</div>
);
}
export default Dashboard;
// CSS 样式
const styles = `
.dashboard {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
.dashboard h1 {
color: #1a365d;
margin-bottom: 30px;
text-align: center;
}
.lobster-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
}
.lobster-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
transition: transform 0.2s;
}
.lobster-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.15);
}
.lobster-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 2px solid #e2e8f0;
}
.lobster-name {
font-size: 1.3em;
font-weight: bold;
color: #2d3748;
}
.status {
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85em;
font-weight: 600;
}
.status-healthy {
background: #c6f6d5;
color: #22543d;
}
.status-warning {
background: #feebc8;
color: #744210;
}
.status-error {
background: #fed7d7;
color: #742a2a;
}
.lobster-info {
margin-bottom: 20px;
}
.lobster-info p {
margin: 8px 0;
color: #4a5568;
display: flex;
justify-content: space-between;
}
.lobster-actions {
display: flex;
gap: 10px;
}
.lobster-actions button {
flex: 1;
padding: 10px 16px;
border: none;
border-radius: 8px;
font-size: 1em;
cursor: pointer;
transition: all 0.2s;
font-weight: 600;
}
.detail-btn {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.detail-btn:hover {
opacity: 0.9;
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(102, 126, 234, 0.4);
}
.memory-btn {
background: #edf2f7;
color: #4a5568;
}
.memory-btn:hover {
background: #e2e8f0;
transform: translateY(-2px);
}
.loading {
text-align: center;
padding: 50px;
color: #718096;
font-size: 1.2em;
}
`;
// 注入样式
if (typeof document !== 'undefined') {
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
}