Files
SafeLine/homepage/src/components/detection/SampleSteps.tsx
2023-05-22 22:40:20 +08:00

255 lines
6.7 KiB
TypeScript

import { useState } from "react";
import {
Box,
Button,
RadioGroup,
StepLabel,
TextField,
FormControlLabel,
Radio,
Typography,
} from "@mui/material";
import {
Dialog,
DialogTitle,
DialogContent,
DialogContentText,
DialogActions,
} from "@mui/material";
import Stepper from "@mui/material/Stepper";
import Step from "@mui/material/Step";
import SampleCount from "@/components/detection/SampleCount";
import {
Table,
TableHead,
TableRow,
TableCell,
TableBody,
} from "@mui/material";
import { sampleLength, sampleSummary } from "@/utils";
export default SampleSteps;
interface SampleStepsProps {
// onDetect: (samples: Array<{ sample: string; isAttack: boolean }>) => void;
onDetect: (value: { sample: string; isAttack: boolean }) => void;
}
function SampleSteps({ onDetect }: SampleStepsProps) {
const [activeStep, setActiveStep] = useState(0);
const [completed, setCompleted] = useState([false, false, false]);
const [sampleText, setSampleText] = useState("");
const [sampleIsAttack, setSampleIsAttack] = useState(false);
const [sampleTextError, setSampleTextError] = useState("");
const [count, setCount] = useState({
total: 0,
normal: 0,
attack: 0,
});
const nextStep = () => {
setActiveStep(activeStep + 1);
completed[activeStep] = true;
setCompleted([...completed]);
};
const handleCommit = () => {
if (sampleText == "") {
setSampleTextError("样本内容不能为空");
return;
}
nextStep();
};
const handleMark = (v: Array<{ isAttack: boolean }>) => {
const isAttack = v[0].isAttack;
setSampleIsAttack(isAttack);
setCount({ total: 1, normal: isAttack ? 0 : 1, attack: isAttack ? 1 : 0 });
nextStep();
};
const handleDetect = () => {
onDetect({
sample: sampleText,
isAttack: sampleIsAttack,
});
};
const handleStep = (n: number) => {
if (completed[n]) setActiveStep(n);
};
return (
<Box>
<Stepper nonLinear activeStep={activeStep} sx={{ marginBottom: 2 }}>
<Step completed={completed[0]} onClick={() => handleStep(0)}>
<StepLabel></StepLabel>
</Step>
<Step completed={completed[1]} onClick={() => handleStep(1)}>
<StepLabel></StepLabel>
</Step>
<Step completed={completed[2]} onClick={() => handleStep(2)}>
<StepLabel></StepLabel>
</Step>
</Stepper>
{activeStep == 0 && (
<Box sx={{ p: 1 }}>
<TextField
multiline
fullWidth
minRows={4}
error={sampleTextError != ""}
helperText={sampleTextError}
value={sampleText}
onChange={(e) => setSampleText(e.target.value)}
placeholder={`GET /path/api HTTP/1.1
Host: example.com`}
sx={{ mb: 2 }}
/>
<Button fullWidth variant="contained" onClick={handleCommit}>
</Button>
</Box>
)}
{activeStep == 1 && (
<SampleMarkable onChange={handleMark} samples={[sampleText]} />
)}
{activeStep == 2 && (
<Box>
<SampleCount
total={count.total}
normal={count.normal}
attack={count.attack}
/>
<Button
sx={{ mt: 2 }}
fullWidth
variant="contained"
onClick={handleDetect}
>
</Button>
</Box>
)}
</Box>
);
}
function SampleMarkable({
samples,
onChange,
}: {
samples: Array<string>;
onChange: (value: Array<{ sample: string; isAttack: boolean }>) => void;
}) {
const [sampleDetail, setSampleDetail] = useState("");
const [rows, setRows] = useState(() => {
const rows: Array<{
isAttack: boolean;
summary: string;
size: string;
raw: string;
}> = [];
samples.forEach((i) => {
rows.push({
isAttack: false,
summary: sampleSummary(i),
raw: i,
size: sampleLength(i),
});
});
return rows;
});
const handle = () => {
onChange(rows.map((i) => ({ sample: i.raw, isAttack: i.isAttack })));
};
const handleType = (n: number) => (v: string) => {
rows[n].isAttack = v == "attack";
setRows([...rows]);
};
const handleSampleDetail = (text: string) => () => {
setSampleDetail(text);
};
const handleClose = () => {
setSampleDetail("");
};
return (
<Box>
<Table sx={{ mb: 2 }}>
<TableHead>
<TableRow>
<TableCell width={350}></TableCell>
<TableCell width={150}></TableCell>
<TableCell></TableCell>
<TableCell width={100}></TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow
key={index}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>
<RadioGroup
value={row.isAttack ? "attack" : "normal"}
onChange={(e) => handleType(index)(e.target.value)}
sx={{ flexDirection: "row" }}
>
<FormControlLabel
value="attack"
control={<Radio sx={{ color: "divider" }} />}
label="攻击样本"
/>
<FormControlLabel
value="normal"
control={<Radio sx={{ color: "divider" }} />}
label="普通样本"
/>
</RadioGroup>
</TableCell>
<TableCell>{row.size}</TableCell>
<TableCell>
<Typography
sx={{
fontFamily:
"ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace",
}}
>
{row.summary}
</Typography>
</TableCell>
<TableCell>
<Button onClick={handleSampleDetail(row.raw)}></Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Button onClick={handle} fullWidth variant="contained">
</Button>
<Dialog open={sampleDetail != ""} onClick={handleClose}>
<DialogTitle></DialogTitle>
<DialogContent>
<DialogContentText>{sampleDetail}</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleClose}></Button>
</DialogActions>
</Dialog>
</Box>
);
}