Add React frontend and Django backend code
- React components: Dashboard, MemoryCalendar, SearchBox, ToolList - Django backend structure - Package configuration files
This commit is contained in:
16
code/backend/manage.py
Executable file
16
code/backend/manage.py
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def main():
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django."
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
30
code/frontend/package.json
Normal file
30
code/frontend/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "lobster-monitor-frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "龙虾舰队监控中心 - React 前端",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"react-scripts": "5.0.1",
|
||||||
|
"axios": "^1.6.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
83
code/frontend/src/components/MemoryCalendar/index.js
Normal file
83
code/frontend/src/components/MemoryCalendar/index.js
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_BASE = 'http://localhost:8000/api';
|
||||||
|
|
||||||
|
function MemoryCalendar({ lobsterId }) {
|
||||||
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
||||||
|
const [memoryDates, setMemoryDates] = useState([]);
|
||||||
|
const [selectedDate, setSelectedDate] = useState(null);
|
||||||
|
const [memoryContent, setMemoryContent] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchMemoryDates();
|
||||||
|
}, [currentMonth, lobsterId]);
|
||||||
|
|
||||||
|
const fetchMemoryDates = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_BASE}/lobsters/${lobsterId}/memory/dates/`);
|
||||||
|
setMemoryDates(response.data.dates);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取记忆日期失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMemory = async (date) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_BASE}/lobsters/${lobsterId}/memory/${date}/`);
|
||||||
|
setMemoryContent(response.data.content);
|
||||||
|
setSelectedDate(date);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取记忆失败:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderCalendar = () => {
|
||||||
|
const year = currentMonth.getFullYear();
|
||||||
|
const month = currentMonth.getMonth();
|
||||||
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||||
|
const days = [];
|
||||||
|
|
||||||
|
for (let day = 1; day <= daysInMonth; day++) {
|
||||||
|
const dateStr = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
|
||||||
|
const hasMemory = memoryDates.includes(dateStr);
|
||||||
|
|
||||||
|
days.push(
|
||||||
|
<div
|
||||||
|
key={day}
|
||||||
|
className={`calendar-day ${hasMemory ? 'has-memory' : ''} ${selectedDate === dateStr ? 'selected' : ''}`}
|
||||||
|
onClick={() => hasMemory && fetchMemory(dateStr)}
|
||||||
|
>
|
||||||
|
{day}
|
||||||
|
{hasMemory && <span className="memory-indicator"></span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return days;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="memory-calendar">
|
||||||
|
<div className="calendar-header">
|
||||||
|
<button onClick={() => setCurrentMonth(new Date(currentMonth.setMonth(currentMonth.getMonth() - 1)))}>◀</button>
|
||||||
|
<span>{currentMonth.getFullYear()}年 {currentMonth.getMonth() + 1}月</span>
|
||||||
|
<button onClick={() => setCurrentMonth(new Date(currentMonth.setMonth(currentMonth.getMonth() + 1)))}>▶</button>
|
||||||
|
</div>
|
||||||
|
<div className="calendar-grid">
|
||||||
|
{['日', '一', '二', '三', '四', '五', '六'].map(day => (
|
||||||
|
<div key={day} className="calendar-weekday">{day}</div>
|
||||||
|
))}
|
||||||
|
{renderCalendar()}
|
||||||
|
</div>
|
||||||
|
{memoryContent && (
|
||||||
|
<div className="memory-content">
|
||||||
|
<h3>📅 {selectedDate} 的记忆</h3>
|
||||||
|
<pre>{memoryContent}</pre>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MemoryCalendar;
|
||||||
59
code/frontend/src/components/SearchBox/index.js
Normal file
59
code/frontend/src/components/SearchBox/index.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_BASE = 'http://localhost:8000/api';
|
||||||
|
|
||||||
|
function SearchBox({ lobsterId }) {
|
||||||
|
const [query, setQuery] = useState('');
|
||||||
|
const [results, setResults] = useState([]);
|
||||||
|
const [searching, setSearching] = useState(false);
|
||||||
|
|
||||||
|
const handleSearch = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!query.trim()) return;
|
||||||
|
|
||||||
|
setSearching(true);
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${API_BASE}/lobsters/${lobsterId}/search/`, {
|
||||||
|
params: { q: query }
|
||||||
|
});
|
||||||
|
setResults(response.data.results);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('搜索失败:', error);
|
||||||
|
} finally {
|
||||||
|
setSearching(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="search-box">
|
||||||
|
<form onSubmit={handleSearch}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={query}
|
||||||
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
|
placeholder="搜索文档..."
|
||||||
|
/>
|
||||||
|
<button type="submit" disabled={searching}>
|
||||||
|
{searching ? '搜索中...' : '🔍 搜索'}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{results.length > 0 && (
|
||||||
|
<div className="search-results">
|
||||||
|
<h3>搜索结果 ({results.length})</h3>
|
||||||
|
{results.map((result, index) => (
|
||||||
|
<div key={index} className="search-result">
|
||||||
|
<h4>{result.title}</h4>
|
||||||
|
<p className="snippet">{result.snippet}</p>
|
||||||
|
<a href={result.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
查看原文
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SearchBox;
|
||||||
59
code/frontend/src/components/ToolList/index.js
Normal file
59
code/frontend/src/components/ToolList/index.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
function ToolList() {
|
||||||
|
const tools = [
|
||||||
|
{
|
||||||
|
name: 'Git 版本控制',
|
||||||
|
status: '🟢 运行中',
|
||||||
|
description: '代码版本管理服务',
|
||||||
|
access: [
|
||||||
|
{ type: 'Web', url: 'http://localhost:18003' },
|
||||||
|
{ type: 'Git', url: 'git://127.0.0.1:9418/monitor-dashboard.git' },
|
||||||
|
{ type: 'HTTP', url: 'http://localhost:18003/monitor-dashboard.git' }
|
||||||
|
],
|
||||||
|
usage: [
|
||||||
|
'克隆仓库:git clone http://localhost:18003/monitor-dashboard.git',
|
||||||
|
'提交更改:git add . && git commit -m "说明"',
|
||||||
|
'推送代码:git push origin master'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="tool-list">
|
||||||
|
<h1>🛠️ 工具列表</h1>
|
||||||
|
{tools.map((tool, index) => (
|
||||||
|
<div key={index} className="tool-card">
|
||||||
|
<div className="tool-header">
|
||||||
|
<h2>{tool.name}</h2>
|
||||||
|
<span className="tool-status">{tool.status}</span>
|
||||||
|
</div>
|
||||||
|
<p className="tool-description">{tool.description}</p>
|
||||||
|
|
||||||
|
<div className="tool-access">
|
||||||
|
<h3>访问方式:</h3>
|
||||||
|
{tool.access.map((item, i) => (
|
||||||
|
<div key={i} className="access-item">
|
||||||
|
<span className="access-type">{item.type}:</span>
|
||||||
|
<a href={item.url} target="_blank" rel="noopener noreferrer">
|
||||||
|
{item.url}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="tool-usage">
|
||||||
|
<h3>使用方法:</h3>
|
||||||
|
<ul>
|
||||||
|
{tool.usage.map((item, i) => (
|
||||||
|
<li key={i}><code>{item}</code></li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ToolList;
|
||||||
60
code/frontend/src/pages/Dashboard/index.js
Normal file
60
code/frontend/src/pages/Dashboard/index.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const API_BASE = 'http://localhost:8000/api';
|
||||||
|
|
||||||
|
function Dashboard() {
|
||||||
|
const [lobsters, setLobsters] = useState([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
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 onClick={() => window.location.href = `/lobster/${lobster.id}`}>
|
||||||
|
📊 详情
|
||||||
|
</button>
|
||||||
|
<button onClick={() => window.location.href = `/lobster/${lobster.id}/memory`}>
|
||||||
|
🧠 记忆
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Dashboard;
|
||||||
Reference in New Issue
Block a user