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