feat(database): add CRUD operations for parsed sessions and update session name functionality
This commit is contained in:
1205
frontend/package-lock.json
generated
1205
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1 @@
|
||||
98e3c997ed559906a235af48ee6be17d
|
||||
340ef73da49fc2e1f0f32752fcad55cd
|
||||
@@ -1,24 +1,19 @@
|
||||
import React, {useEffect, useRef, useState} from 'react';
|
||||
import {Button, Card, Layout, message, Select, Spin, Table} from 'antd';
|
||||
import {Button, Card, Layout, message, Spin, Table} from 'antd';
|
||||
import {DownloadOutlined, PlayCircleOutlined, StopOutlined, UploadOutlined} from '@ant-design/icons';
|
||||
import '../App.css';
|
||||
import {
|
||||
GetNetworkInterfaces,
|
||||
ReadRawJsonFile,
|
||||
SaveParsedDataToDatabase,
|
||||
StartCapture,
|
||||
StartCaptureWithFilter,
|
||||
StopCapture,
|
||||
StopAndParseCapture
|
||||
} from '../../wailsjs/go/service/App';
|
||||
import { GetCaptureStatus } from '../../wailsjs/go/service/App';
|
||||
import { EventsOn } from '../../wailsjs/runtime';
|
||||
import {useCaptureStore} from '../store/useCaptureStore';
|
||||
|
||||
const { Header, Content, Sider } = Layout;
|
||||
|
||||
interface NetworkInterface {
|
||||
name: string
|
||||
description: string
|
||||
addresses: string[]
|
||||
is_loopback: boolean
|
||||
}
|
||||
const { Content, Sider } = Layout;
|
||||
|
||||
interface Equipment {
|
||||
id: string
|
||||
@@ -40,21 +35,15 @@ interface CaptureResult {
|
||||
}
|
||||
|
||||
function CapturePage() {
|
||||
// 只用全局 parsedData
|
||||
const parsedData = useCaptureStore(state => state.parsedData);
|
||||
const setParsedData = useCaptureStore(state => state.setParsedData);
|
||||
|
||||
// 其余状态全部本地 useState
|
||||
const [interfaces, setInterfaces] = useState<NetworkInterface[]>([]);
|
||||
const [selectedInterface, setSelectedInterface] = useState('');
|
||||
const [isCapturing, setIsCapturing] = useState(false);
|
||||
const [capturedData, setCapturedData] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [interfaceLoading, setInterfaceLoading] = useState(false);
|
||||
const [uploadedFileName, setUploadedFileName] = useState('');
|
||||
const [statusText, setStatusText] = useState('准备就绪');
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
// 顶部 message 只显示一次
|
||||
const showMessage = (type: 'success' | 'error' | 'warning', content: string) => {
|
||||
message.destroy();
|
||||
message[type](content);
|
||||
@@ -73,65 +62,91 @@ function CapturePage() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchInterfaces = async () => {
|
||||
const resetUiState = () => {
|
||||
setIsCapturing(false);
|
||||
setCapturedData([]);
|
||||
setLoading(false);
|
||||
setInterfaceLoading(true);
|
||||
try {
|
||||
const response = await safeApiCall(
|
||||
() => GetNetworkInterfaces(),
|
||||
'获取网络接口失败'
|
||||
);
|
||||
if (!response || response.length === 0) {
|
||||
showMessage('error', '未获取到任何网络接口');
|
||||
setInterfaces([]);
|
||||
setSelectedInterface('');
|
||||
return;
|
||||
}
|
||||
setInterfaces(response);
|
||||
let defaultSelected = '';
|
||||
console.log("获取的网卡:"+JSON.stringify(response))
|
||||
for (const iface of response) {
|
||||
if (iface.addresses && iface.addresses.some(ip => ip.includes('192.168'))) {
|
||||
defaultSelected = iface.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!defaultSelected && response.length > 0) {
|
||||
defaultSelected = response[0].name;
|
||||
}
|
||||
setSelectedInterface(defaultSelected);
|
||||
} catch (error) {
|
||||
console.error('获取网络接口时发生未知错误:', error);
|
||||
showMessage('error', '获取网络接口时发生未知错误');
|
||||
setInterfaces([]);
|
||||
setSelectedInterface('');
|
||||
} finally {
|
||||
setInterfaceLoading(false);
|
||||
}
|
||||
setUploadedFileName('');
|
||||
setParsedData({ items: [], heroes: [] } as CaptureResult);
|
||||
setStatusText('准备就绪');
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const syncCaptureStatus = async () => {
|
||||
resetUiState();
|
||||
try {
|
||||
const status = await GetCaptureStatus();
|
||||
if (!cancelled && status?.status === 'starting') {
|
||||
setIsCapturing(true);
|
||||
setStatusText('启动中...');
|
||||
} else if (!cancelled && status?.is_capturing) {
|
||||
setIsCapturing(true);
|
||||
setStatusText('抓包中');
|
||||
}
|
||||
} catch (err) {
|
||||
// ignore status sync errors
|
||||
}
|
||||
};
|
||||
syncCaptureStatus();
|
||||
return () => {
|
||||
// Leave capture page: ensure backend is not left running
|
||||
StopCapture().catch(() => {});
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const off = EventsOn('capture:ready_to_parse', () => {
|
||||
setStatusText('抓包成功,待解析数据');
|
||||
});
|
||||
const offStarted = EventsOn('capture:started', () => {
|
||||
setIsCapturing(true);
|
||||
setStatusText('抓包中');
|
||||
});
|
||||
const offStartFailed = EventsOn('capture:start_failed', () => {
|
||||
setIsCapturing(false);
|
||||
setStatusText('准备就绪');
|
||||
showMessage('error', '开始抓包失败');
|
||||
});
|
||||
return () => {
|
||||
off();
|
||||
offStarted();
|
||||
offStartFailed();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const startCapture = async () => {
|
||||
if (!selectedInterface) {
|
||||
showMessage('warning', '请选择网络接口');
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
try {
|
||||
const result = await safeApiCall(
|
||||
() => StartCapture(selectedInterface),
|
||||
'开始抓包失败'
|
||||
);
|
||||
if (result === undefined) {
|
||||
// Best-effort reset to avoid "capture already running"
|
||||
await StopCapture().catch(() => {});
|
||||
setParsedData({ items: [], heroes: [] } as CaptureResult);
|
||||
setIsCapturing(true);
|
||||
setStatusText('启动中...');
|
||||
|
||||
try {
|
||||
console.log('StartCaptureWithFilter calling', new Date().toISOString());
|
||||
await StartCaptureWithFilter('', '');
|
||||
console.log('StartCaptureWithFilter returned', new Date().toISOString());
|
||||
showMessage('success', '开始抓包');
|
||||
} catch (err) {
|
||||
const msg = String(err);
|
||||
if (msg.includes('capture already running')) {
|
||||
// 后端仍在抓包,允许前端停止
|
||||
setIsCapturing(true);
|
||||
setStatusText('抓包中');
|
||||
showMessage('warning', '抓包已在进行');
|
||||
return;
|
||||
}
|
||||
setIsCapturing(false);
|
||||
setStatusText('准备就绪');
|
||||
showMessage('error', '开始抓包失败');
|
||||
return;
|
||||
}
|
||||
setIsCapturing(true);
|
||||
showMessage('success', '开始抓包');
|
||||
} catch (error) {
|
||||
console.error('开始抓包时发生未知错误:', error);
|
||||
console.error('开始抓包出错:', error);
|
||||
setIsCapturing(false);
|
||||
setStatusText('准备就绪');
|
||||
showMessage('error', '开始抓包失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -139,31 +154,45 @@ function CapturePage() {
|
||||
};
|
||||
|
||||
const stopCapture = async () => {
|
||||
console.log('stopCapture clicked', new Date().toISOString());
|
||||
setLoading(false);
|
||||
setIsCapturing(false);
|
||||
setCapturedData([]);
|
||||
setStatusText('抓取中...');
|
||||
try {
|
||||
setLoading(true);
|
||||
// 新接口:直接停止抓包并解析
|
||||
const parsedData = await safeApiCall(
|
||||
() => StopAndParseCapture(),
|
||||
'停止抓包并解析数据失败'
|
||||
);
|
||||
console.log("解析数据:"+JSON.stringify(parsedData))
|
||||
console.log('StopAndParseCapture calling', new Date().toISOString());
|
||||
const parsedData = await StopAndParseCapture();
|
||||
console.log('StopAndParseCapture returned', new Date().toISOString());
|
||||
if (!parsedData || !Array.isArray((parsedData as CaptureResult).items)) {
|
||||
setParsedData({ items: [], heroes: [] } as CaptureResult);
|
||||
showMessage('error', '解析数据失败');
|
||||
setIsCapturing(false);
|
||||
setStatusText('解析失败');
|
||||
showMessage('error', '解析失败');
|
||||
return;
|
||||
}
|
||||
setParsedData(parsedData as CaptureResult);
|
||||
setIsCapturing(false);
|
||||
setStatusText('导入成功');
|
||||
showMessage('success', '数据处理完成');
|
||||
} catch (error) {
|
||||
console.error('停止抓包时发生未知错误:', error);
|
||||
setIsCapturing(false);
|
||||
setCapturedData([]);
|
||||
const errMsg = String(error);
|
||||
console.error('停止抓包出错:', error);
|
||||
setParsedData({ items: [], heroes: [] } as CaptureResult);
|
||||
setLoading(false);
|
||||
showMessage('error', '抓包失败,已重置状态');
|
||||
if (errMsg.includes('capture starting')) {
|
||||
setIsCapturing(true);
|
||||
setStatusText('启动中,停止已请求');
|
||||
showMessage('warning', '启动中,停止已请求');
|
||||
return;
|
||||
}
|
||||
if (errMsg.includes('no captured data') || errMsg.includes('没有数据')) {
|
||||
setIsCapturing(false);
|
||||
setStatusText('未获取到任何数据');
|
||||
showMessage('error', '未获取到任何数据');
|
||||
return;
|
||||
}
|
||||
setIsCapturing(false);
|
||||
setStatusText('抓包失败');
|
||||
showMessage('error', '抓包失败');
|
||||
return;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@@ -171,37 +200,26 @@ function CapturePage() {
|
||||
};
|
||||
|
||||
const exportData = () => {
|
||||
// 检查是否有解析数据
|
||||
if (!parsedData || !Array.isArray(parsedData.items) || parsedData.items.length === 0) {
|
||||
showMessage('warning', '没有数据可导出');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建要导出的数据内容
|
||||
const exportContent = JSON.stringify(parsedData, null, 2);
|
||||
|
||||
// 创建 Blob 对象
|
||||
const blob = new Blob([exportContent], { type: 'text/plain;charset=utf-8' });
|
||||
|
||||
// 创建下载链接
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = 'gear.txt';
|
||||
|
||||
// 触发下载
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
// 清理
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
showMessage('success', '数据导出成功');
|
||||
} catch (error) {
|
||||
console.error('导出数据时发生错误:', error);
|
||||
showMessage('error', '导出数据失败');
|
||||
console.error('导出数据出错:', error);
|
||||
showMessage('error', '数据导出失败');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -216,7 +234,6 @@ function CapturePage() {
|
||||
const text = e.target?.result as string;
|
||||
const json = JSON.parse(text);
|
||||
|
||||
// 校验数据格式
|
||||
if (!json || typeof json !== 'object') {
|
||||
showMessage('error', '文件格式错误:不是有效的JSON对象');
|
||||
return;
|
||||
@@ -242,23 +259,20 @@ function CapturePage() {
|
||||
return;
|
||||
}
|
||||
|
||||
// 数据校验通过,保存到数据库
|
||||
const sessionName = `import_${Date.now()}`;
|
||||
const equipmentJSON = JSON.stringify(json.items);
|
||||
const heroesJSON = JSON.stringify(json.heroes);
|
||||
|
||||
await SaveParsedDataToDatabase(sessionName, equipmentJSON, heroesJSON);
|
||||
|
||||
// 更新界面显示
|
||||
const safeData = {
|
||||
items: json.items,
|
||||
heroes: json.heroes
|
||||
};
|
||||
setParsedData(safeData);
|
||||
setUploadedFileName(''); // 清空文件名显示
|
||||
|
||||
showMessage('success', `数据导入成功:${json.items.length}件装备,${json.heroes.length}个英雄,已保存到数据库`);
|
||||
setUploadedFileName('');
|
||||
|
||||
showMessage('success', `导入成功:${json.items.length}件装备,${json.heroes.length}个英雄`);
|
||||
} catch (err) {
|
||||
console.error('文件处理错误:', err);
|
||||
if (err instanceof SyntaxError) {
|
||||
@@ -276,7 +290,6 @@ function CapturePage() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const json = await ReadRawJsonFile();
|
||||
console.log('已加载本地解析数据:', json);
|
||||
const safeData = {
|
||||
items: Array.isArray(json.items) ? json.items : [],
|
||||
heroes: Array.isArray(json.heroes) ? json.heroes : []
|
||||
@@ -306,52 +319,15 @@ function CapturePage() {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchInterfaces();
|
||||
}, []);
|
||||
|
||||
const equipmentColumns = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
},
|
||||
{
|
||||
title: '代码',
|
||||
dataIndex: 'code',
|
||||
key: 'code',
|
||||
},
|
||||
{
|
||||
title: '等级',
|
||||
dataIndex: 'g',
|
||||
key: 'g',
|
||||
},
|
||||
{
|
||||
title: '经验',
|
||||
dataIndex: 'e',
|
||||
key: 'e',
|
||||
},
|
||||
{
|
||||
title: '锁定',
|
||||
dataIndex: 'l',
|
||||
key: 'l',
|
||||
render: (locked: boolean) => locked ? '是' : '否',
|
||||
},
|
||||
{
|
||||
title: '魔法值',
|
||||
dataIndex: 'mg',
|
||||
key: 'mg',
|
||||
},
|
||||
{
|
||||
title: '力量',
|
||||
dataIndex: 'p',
|
||||
key: 'p',
|
||||
},
|
||||
{
|
||||
title: '技能',
|
||||
dataIndex: 'sk',
|
||||
key: 'sk',
|
||||
},
|
||||
{ title: 'ID', dataIndex: 'id', key: 'id' },
|
||||
{ title: '代码', dataIndex: 'code', key: 'code' },
|
||||
{ title: '等级', dataIndex: 'g', key: 'g' },
|
||||
{ title: '经验', dataIndex: 'e', key: 'e' },
|
||||
{ title: '锁定', dataIndex: 'l', key: 'l', render: (locked: boolean) => locked ? '是' : '否' },
|
||||
{ title: '魔法值', dataIndex: 'mg', key: 'mg' },
|
||||
{ title: '力量', dataIndex: 'p', key: 'p' },
|
||||
{ title: '技能', dataIndex: 'sk', key: 'sk' },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -359,30 +335,13 @@ function CapturePage() {
|
||||
<Sider width={220} style={{ background: '#f5f5f5' }}>
|
||||
<div style={{ padding: '16px 0px 12px 12px' }}>
|
||||
<Card title="抓包控制" size="small" style={{ marginBottom: 12, marginTop: 0 }}>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<label>网络接口:</label>
|
||||
<Select
|
||||
style={{ width: '100%', marginTop: 6 }}
|
||||
value={selectedInterface}
|
||||
onChange={setSelectedInterface}
|
||||
placeholder="选择网络接口"
|
||||
loading={interfaceLoading}
|
||||
>
|
||||
{interfaces.map((iface) => (
|
||||
<Select.Option key={iface.name} value={iface.name}>
|
||||
{iface.addresses}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlayCircleOutlined />}
|
||||
onClick={startCapture}
|
||||
disabled={isCapturing || !selectedInterface}
|
||||
disabled={isCapturing}
|
||||
loading={loading}
|
||||
style={{ flex: 1, height: 32, minWidth: 0, fontSize: 12 }}
|
||||
>
|
||||
@@ -393,7 +352,6 @@ function CapturePage() {
|
||||
icon={<StopOutlined />}
|
||||
onClick={stopCapture}
|
||||
disabled={!isCapturing}
|
||||
loading={loading}
|
||||
style={{ flex: 1, height: 32, minWidth: 0, fontSize: 12 }}
|
||||
>
|
||||
停止抓包
|
||||
@@ -429,8 +387,19 @@ function CapturePage() {
|
||||
|
||||
<Card title="抓包状态" size="small" style={{ marginTop: 12 }}>
|
||||
<div>
|
||||
<p style={{ marginBottom: 4 }}>状态: {isCapturing ? '正在抓包...' : '准备就绪'}</p>
|
||||
{/*<p style={{ marginBottom: 4 }}>捕获数据: {capturedData.length} 条</p>*/}
|
||||
<p style={{ marginBottom: 4 }}>
|
||||
状态: <span style={{
|
||||
color: (
|
||||
statusText === '未获取到任何数据' ||
|
||||
statusText === '解析失败' ||
|
||||
statusText === '抓包失败'
|
||||
)
|
||||
? '#d32029'
|
||||
: (statusText === '抓包成功,待解析数据' || statusText === '导入成功')
|
||||
? '#389e0d'
|
||||
: undefined
|
||||
}}>{statusText}</span>
|
||||
</p>
|
||||
<p style={{ marginBottom: 4 }}>英雄数目: {Array.isArray(parsedData?.heroes) ? parsedData.heroes.length : 0} 个</p>
|
||||
{parsedData && (
|
||||
<p style={{ marginBottom: 0 }}>解析装备: {Array.isArray(parsedData?.items) ? parsedData.items.length : 0} 件</p>
|
||||
@@ -457,7 +426,7 @@ function CapturePage() {
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<p>暂无数据</p>
|
||||
<p style={{ color: '#999', fontSize: '12px' }}>
|
||||
{parsedData ? '数据为空,请检查数据源或上传文件' : '请开始抓包或上传JSON文件'}
|
||||
{parsedData ? '数据为空,请检查数据源或导入文件。' : '请开始抓包或导入JSON文件。'}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
@@ -468,4 +437,4 @@ function CapturePage() {
|
||||
);
|
||||
}
|
||||
|
||||
export default CapturePage;
|
||||
export default CapturePage;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Card, Col, Layout, Row, Space, Statistic, Table, Tag} from 'antd';
|
||||
import {Button, Card, Col, Input, Layout, Modal, Row, Select, Space, Statistic, Table, Tag} from 'antd';
|
||||
import {BarChartOutlined, DatabaseOutlined, ReloadOutlined, SettingOutlined,} from '@ant-design/icons';
|
||||
import * as App from '../../wailsjs/go/service/App';
|
||||
import {model} from '../../wailsjs/go/models';
|
||||
@@ -25,16 +25,24 @@ interface Equipment {
|
||||
const DatabasePage: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [latestData, setLatestData] = useState<model.ParsedResult | null>(null);
|
||||
const [sessions, setSessions] = useState<model.ParsedSession[]>([]);
|
||||
const [selectedSessionId, setSelectedSessionId] = useState<number | null>(null);
|
||||
const [renameOpen, setRenameOpen] = useState(false);
|
||||
const [renameValue, setRenameValue] = useState('');
|
||||
const { success, error, info } = useMessage();
|
||||
|
||||
// 加载最新解析数据
|
||||
const loadLatestData = async () => {
|
||||
// 防止重复调用
|
||||
if (loading) return;
|
||||
|
||||
setLoading(true);
|
||||
const formatSessionLabel = (session: model.ParsedSession) => {
|
||||
const date = session.created_at ? new Date(session.created_at * 1000) : null;
|
||||
const timeText = date ? date.toLocaleString() : '';
|
||||
return timeText ? `${session.session_name}(${timeText})` : session.session_name;
|
||||
};
|
||||
|
||||
const loadParsedDataById = async (id: number, skipLoading?: boolean) => {
|
||||
if (!skipLoading) {
|
||||
setLoading(true);
|
||||
}
|
||||
try {
|
||||
const parsedResult = await App.GetLatestParsedDataFromDatabase();
|
||||
const parsedResult = await App.GetParsedDataByID(id);
|
||||
if (parsedResult && (parsedResult.items?.length > 0 || parsedResult.heroes?.length > 0)) {
|
||||
setLatestData(parsedResult);
|
||||
success('数据加载成功');
|
||||
@@ -46,13 +54,101 @@ const DatabasePage: React.FC = () => {
|
||||
error('加载数据失败');
|
||||
console.error('Load data error:', err);
|
||||
setLatestData(null);
|
||||
} finally {
|
||||
if (!skipLoading) {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const loadSessions = async (preferLatest?: boolean) => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const list = await App.GetParsedSessions();
|
||||
setSessions(list || []);
|
||||
if (list && list.length > 0) {
|
||||
const nextId = preferLatest ? list[0].id : (selectedSessionId ?? list[0].id);
|
||||
setSelectedSessionId(nextId);
|
||||
await loadParsedDataById(nextId, true);
|
||||
} else {
|
||||
setLatestData(null);
|
||||
info('暂无解析数据');
|
||||
}
|
||||
} catch (err) {
|
||||
error('加载数据失败');
|
||||
console.error('Load data error:', err);
|
||||
setLatestData(null);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openRename = () => {
|
||||
if (!selectedSessionId) {
|
||||
info('请先选择一条解析数据');
|
||||
return;
|
||||
}
|
||||
const current = sessions.find(s => s.id === selectedSessionId);
|
||||
setRenameValue(current?.session_name || '');
|
||||
setRenameOpen(true);
|
||||
};
|
||||
|
||||
const handleRenameOk = async () => {
|
||||
if (!selectedSessionId) {
|
||||
setRenameOpen(false);
|
||||
return;
|
||||
}
|
||||
const nextName = renameValue.trim();
|
||||
if (!nextName) {
|
||||
info('名称不能为空');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
await App.UpdateParsedSessionName(selectedSessionId, nextName);
|
||||
success('名称已更新');
|
||||
setRenameOpen(false);
|
||||
await loadSessions();
|
||||
} catch (err) {
|
||||
error('更新名称失败');
|
||||
console.error('Rename error:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!selectedSessionId) {
|
||||
info('请先选择一条解析数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '确认删除该解析数据?',
|
||||
content: '删除后无法恢复',
|
||||
okText: '删除',
|
||||
cancelText: '取消',
|
||||
okButtonProps: { danger: true },
|
||||
onOk: async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
await App.DeleteParsedSession(selectedSessionId);
|
||||
success('已删除');
|
||||
setRenameOpen(false);
|
||||
setSelectedSessionId(null);
|
||||
await loadSessions(true);
|
||||
} catch (err) {
|
||||
error('删除失败');
|
||||
console.error('Delete error:', err);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadLatestData();
|
||||
loadSessions();
|
||||
}, []);
|
||||
|
||||
// 装备表格列定义
|
||||
@@ -163,19 +259,31 @@ const DatabasePage: React.FC = () => {
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={loadLatestData}
|
||||
onClick={loadSessions}
|
||||
loading={loading}
|
||||
>
|
||||
刷新数据
|
||||
</Button>
|
||||
<span style={{ color: '#666' }}>
|
||||
显示最新一次抓包解析的数据
|
||||
</span>
|
||||
<Select
|
||||
style={{ minWidth: 320 }}
|
||||
placeholder="请选择解析数据"
|
||||
value={selectedSessionId ?? undefined}
|
||||
onChange={(value: number) => {
|
||||
setSelectedSessionId(value);
|
||||
loadParsedDataById(value);
|
||||
}}
|
||||
options={sessions.map(s => ({
|
||||
value: s.id,
|
||||
label: formatSessionLabel(s),
|
||||
}))}
|
||||
/>
|
||||
<Button onClick={openRename}>重命名</Button>
|
||||
<Button danger onClick={handleDelete}>删除</Button>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 装备表格 */}
|
||||
<Card title="最新解析的装备数据">
|
||||
<Card title="解析的装备数据">
|
||||
{latestData?.items && latestData.items.length > 0 ? (
|
||||
<Table
|
||||
columns={equipmentColumns}
|
||||
@@ -199,9 +307,24 @@ const DatabasePage: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
<Modal
|
||||
title="重命名解析数据"
|
||||
open={renameOpen}
|
||||
onOk={handleRenameOk}
|
||||
onCancel={() => setRenameOpen(false)}
|
||||
okText="保存"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Input
|
||||
value={renameValue}
|
||||
onChange={e => setRenameValue(e.target.value)}
|
||||
placeholder="请输入名称"
|
||||
maxLength={50}
|
||||
/>
|
||||
</Modal>
|
||||
</Content>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default DatabasePage;
|
||||
export default DatabasePage;
|
||||
|
||||
@@ -48,6 +48,22 @@ export namespace model {
|
||||
this.heroes = source["heroes"];
|
||||
}
|
||||
}
|
||||
export class ParsedSession {
|
||||
id: number;
|
||||
session_name: string;
|
||||
created_at: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ParsedSession(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.id = source["id"];
|
||||
this.session_name = source["session_name"];
|
||||
this.created_at = source["created_at"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
10
frontend/wailsjs/go/service/App.d.ts
vendored
10
frontend/wailsjs/go/service/App.d.ts
vendored
@@ -2,6 +2,8 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {model} from '../models';
|
||||
|
||||
export function DeleteParsedSession(arg1:number):Promise<void>;
|
||||
|
||||
export function ExportCurrentData(arg1:string):Promise<void>;
|
||||
|
||||
export function ExportData(arg1:Array<string>,arg2:string):Promise<void>;
|
||||
@@ -20,6 +22,10 @@ export function GetLatestParsedDataFromDatabase():Promise<model.ParsedResult>;
|
||||
|
||||
export function GetNetworkInterfaces():Promise<Array<model.NetworkInterface>>;
|
||||
|
||||
export function GetParsedDataByID(arg1:number):Promise<model.ParsedResult>;
|
||||
|
||||
export function GetParsedSessions():Promise<Array<model.ParsedSession>>;
|
||||
|
||||
export function ParseData(arg1:Array<string>):Promise<string>;
|
||||
|
||||
export function ReadRawJsonFile():Promise<model.ParsedResult>;
|
||||
@@ -30,6 +36,10 @@ export function SaveParsedDataToDatabase(arg1:string,arg2:string,arg3:string):Pr
|
||||
|
||||
export function StartCapture(arg1:string):Promise<void>;
|
||||
|
||||
export function StartCaptureWithFilter(arg1:string,arg2:string):Promise<void>;
|
||||
|
||||
export function StopAndParseCapture():Promise<model.ParsedResult>;
|
||||
|
||||
export function StopCapture():Promise<void>;
|
||||
|
||||
export function UpdateParsedSessionName(arg1:number,arg2:string):Promise<void>;
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function DeleteParsedSession(arg1) {
|
||||
return window['go']['service']['App']['DeleteParsedSession'](arg1);
|
||||
}
|
||||
|
||||
export function ExportCurrentData(arg1) {
|
||||
return window['go']['service']['App']['ExportCurrentData'](arg1);
|
||||
}
|
||||
@@ -38,6 +42,14 @@ export function GetNetworkInterfaces() {
|
||||
return window['go']['service']['App']['GetNetworkInterfaces']();
|
||||
}
|
||||
|
||||
export function GetParsedDataByID(arg1) {
|
||||
return window['go']['service']['App']['GetParsedDataByID'](arg1);
|
||||
}
|
||||
|
||||
export function GetParsedSessions() {
|
||||
return window['go']['service']['App']['GetParsedSessions']();
|
||||
}
|
||||
|
||||
export function ParseData(arg1) {
|
||||
return window['go']['service']['App']['ParseData'](arg1);
|
||||
}
|
||||
@@ -58,6 +70,10 @@ export function StartCapture(arg1) {
|
||||
return window['go']['service']['App']['StartCapture'](arg1);
|
||||
}
|
||||
|
||||
export function StartCaptureWithFilter(arg1, arg2) {
|
||||
return window['go']['service']['App']['StartCaptureWithFilter'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function StopAndParseCapture() {
|
||||
return window['go']['service']['App']['StopAndParseCapture']();
|
||||
}
|
||||
@@ -65,3 +81,7 @@ export function StopAndParseCapture() {
|
||||
export function StopCapture() {
|
||||
return window['go']['service']['App']['StopCapture']();
|
||||
}
|
||||
|
||||
export function UpdateParsedSessionName(arg1, arg2) {
|
||||
return window['go']['service']['App']['UpdateParsedSessionName'](arg1, arg2);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,10 @@ export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user