ahooks#useVirtualList TypeScript Examples
The following examples show how to use
ahooks#useVirtualList.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: PluginList.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
PluginList: React.FC<PluginListProp> = React.memo((props) => {
const {
loading,
lists,
getLists,
total,
selected,
allSelectScript,
selectScript,
unSelectScript,
disabled,
search,
extra,
...restCard
} = props
const [limit, setLimit] = useState(200)
const [keyword, setKeyword] = useState("")
const [indeterminate, setIndeterminate] = useState(false)
const [checked, setChecked] = useState(false)
const containerRef = useRef()
const wrapperRef = useRef()
const [list] = useVirtualList(getLists(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 40,
overscan: 20
})
const [vlistWidth, setVListWidth] = useState(260)
const [vlistHeigth, setVListHeight] = useState(600)
useEffect(() => {
const totalYakScript = lists.length
const filterArr = lists.filter((item) => selected.indexOf(item.ScriptName) > -1)
const IndeterminateFlag =
(filterArr.length > 0 && filterArr.length < totalYakScript && selected.length !== 0) ||
(filterArr.length === 0 && selected.length !== 0)
const checkedFlag = filterArr.length === totalYakScript && selected.length !== 0
setIndeterminate(IndeterminateFlag)
setChecked(checkedFlag)
}, [selected, lists])
return (
<div className='plugin-list-body'>
<AutoCard
size='small'
bordered={false}
{...restCard}
extra={
!props.readOnly && <Space>
<Popover
title={"额外设置"}
trigger={["click"]}
content={
<div>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
search({limit: limit, keyword: keyword})
}}
>
<InputInteger
label={"插件展示数量"}
value={limit}
setValue={setLimit}
formItemStyle={{marginBottom: 4}}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type='primary' htmlType='submit'>
刷新
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button size={"small"} icon={<SettingOutlined/>} type={"link"}/>
</Popover>
<Popover
title={"搜索插件关键字"}
trigger={["click"]}
content={
<div>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
search({limit: limit, keyword: keyword})
}}
>
<InputItem
label={""}
extraFormItemProps={{
style: {marginBottom: 4},
colon: false
}}
value={keyword}
setValue={setKeyword}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type='primary' htmlType='submit'>
搜索
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button size={"small"} type={!!keyword ? "primary" : "link"} icon={<SearchOutlined/>}/>
</Popover>
<Checkbox
disabled={props.disabled}
indeterminate={indeterminate}
onChange={(r) => allSelectScript(r.target.checked)}
checked={checked}
>
全选
</Checkbox>
{extra || <></>}
</Space>
}
>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setVListWidth(width - 90)
setVListHeight(height)
}}
handleWidth={true}
handleHeight={true}
refreshMode={"debounce"}
refreshRate={50}
/>
<div ref={containerRef as any} style={{height: vlistHeigth, overflow: "auto"}}>
<div ref={wrapperRef as any}>{list.map((i) => <YakScriptCheckbox
key={i.data.ScriptName} readOnly={props.readOnly}
info={i.data} selectScript={selectScript} unSelectScript={unSelectScript}
vlistWidth={vlistWidth} selected={selected} disabled={disabled}
/>)}</div>
</div>
</AutoCard>
</div>
)
})
Example #2
Source File: HackerPlugin.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
HackerPlugin: React.FC<HackerPluginProps> = React.memo((props) => {
const [token, setToken] = useState<string>(randomString(40))
const [loading, setLoading] = useState<boolean>(false)
const [lists, setLists, getLists] = useGetState<YakScript[]>([])
const [keyword, setKeyword] = useState<string>("")
const [limit, setLimit] = useState<number>(100)
const [total, setTotal] = useState<number>(0)
const [selected, setSelected] = useState<string[]>([])
const [indeterminate, setIndeterminate] = useState<boolean>(false)
const [checked, setChecked] = useState<boolean>(false)
const containerRef = useRef()
const wrapperRef = useRef()
const [list] = useVirtualList(getLists(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 40,
overscan: 20
})
const [vlistHeigth, setVListHeight] = useState(600)
const [execting, setExecting] = useState<boolean>(false)
const [infoState, {reset, setXtermRef}, xtermRef] = useHoldingIPCRStream(
`execute-packet-yak-script`,
"ExecutePacketYakScript",
token,
() => setExecting(false)
)
const search = useMemoizedFn(() => {
setLoading(true)
queryYakScriptList(
"packet-hack",
(data, total) => {
setTotal(total || 0)
setLists(data)
},
() => setTimeout(() => setLoading(false), 300),
limit,
undefined,
keyword
)
})
const selectYakScript = useMemoizedFn((info: YakScript) => {
setSelected([info.ScriptName])
// if (!selected.includes(info.ScriptName)) {
// setSelected([...selected, info.ScriptName])
// }
})
const unselectYakScript = useMemoizedFn((info: YakScript) => {
setSelected([])
// setSelected(selected.filter((i) => i !== info.ScriptName))
})
// useEffect(() => {
// const totalYakScript = lists.length
// const filterArr = lists.filter((item) => selected.indexOf(item.ScriptName) > -1)
// const IndeterminateFlag =
// (filterArr.length > 0 && filterArr.length < totalYakScript && selected.length !== 0) ||
// (filterArr.length === 0 && selected.length !== 0)
// const checkedFlag = filterArr.length === totalYakScript && selected.length !== 0
// setIndeterminate(IndeterminateFlag)
// setChecked(checkedFlag)
// }, [selected, lists])
const startScript = useMemoizedFn(() => {
if (selected.length === 0) {
failed("请选一个插件后在点击执行")
return
}
setExecting(true)
const params: ExecutePacketYakScriptProp = {
ScriptName: selected[0],
IsHttps: props.isHTTPS,
Request: props.request
}
if (!!props.response) params.Response = props.response
ipcRenderer
.invoke("ExecutePacketYakScript", params, token)
.then(() => {})
.catch((e) => {
failed(`Start Packet Checker Error: ${e}`)
setExecting(false)
})
})
const cancelScript = useMemoizedFn(() => {
ipcRenderer.invoke("cancel-ExecutePacketYakScript", token)
})
useEffect(() => {
search()
}, [])
const renderListItem = useMemoizedFn((info: YakScript) => {
return (
<div key={info.ScriptName} className='list-opt'>
<Checkbox
checked={selected.includes(info.ScriptName)}
onChange={(r) => {
if (r.target.checked) selectYakScript(info)
else unselectYakScript(info)
}}
>
<Space>
<Text style={{maxWidth: 270}} ellipsis={{tooltip: true}}>
{info.ScriptName}
</Text>
{info.Help && (
<Button
size={"small"}
type={"link"}
onClick={() => {
showModal({
width: "40%",
title: "Help",
content: <>{info.Help}</>
})
}}
icon={<QuestionCircleOutlined />}
/>
)}
</Space>
</Checkbox>
<div style={{flex: 1, textAlign: "right"}}>
{info.Author && (
<Tooltip title={info.Author}>
<Button size={"small"} type={"link"} icon={<UserOutlined />} />
</Tooltip>
)}
</div>
</div>
)
})
return (
<div className='mitm-exec-plugin'>
<div className='left-body'>
<AutoCard
size='small'
bordered={false}
title={"数据包扫描插件(暂只支持单选)"}
bodyStyle={{padding: "0 4px", overflowY: "hidden"}}
extra={
<Space>
{/* <Checkbox
indeterminate={indeterminate}
onChange={(r) => {
if (r.target.checked) {
const newSelected = [...lists.map((i) => i.ScriptName), ...selected]
setSelected(newSelected.filter((e, index) => newSelected.indexOf(e) === index))
} else {
setSelected([])
}
}}
checked={checked}
>
全选
</Checkbox> */}
<Popover
title={"额外设置"}
trigger={["click"]}
content={
<div>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
search()
}}
>
<InputInteger
label={"插件展示数量"}
value={limit}
setValue={setLimit}
formItemStyle={{marginBottom: 4}}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type='primary' htmlType='submit'>
刷新
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button size={"small"} icon={<SettingOutlined />} type={"link"} />
</Popover>
<Popover
title={"搜索插件关键字"}
trigger={["click"]}
content={
<div>
<Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
search()
}}
>
<InputItem
label={""}
extraFormItemProps={{style: {marginBottom: 4}, colon: false}}
value={keyword}
setValue={setKeyword}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type='primary' htmlType='submit'>
搜索
</Button>
</Form.Item>
</Form>
</div>
}
>
<Button
size={"small"}
type={!!keyword ? "primary" : "link"}
icon={<SearchOutlined />}
/>
</Popover>
{execting ? (
<Button
type='link'
danger
style={{padding: "4px 0"}}
icon={<PoweroffOutlined />}
onClick={cancelScript}
/>
) : (
<Button
type='link'
style={{padding: "4px 0"}}
icon={<CaretRightOutlined />}
onClick={() => {
xtermClear(xtermRef)
reset()
startScript()
}}
/>
)}
</Space>
}
>
<div style={{height: "100%"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setVListHeight(height)
}}
handleWidth={true}
handleHeight={true}
refreshMode={"debounce"}
refreshRate={50}
/>
<div ref={containerRef as any} style={{height: vlistHeigth, overflow: "auto"}}>
<div ref={wrapperRef as any}>{list.map((i) => renderListItem(i.data))}</div>
</div>
</div>
</AutoCard>
</div>
<div className='right-body'>
<AutoCard
size='small'
bordered={false}
title={
<Space>
{"已选插件 / 当页插件 / 插件总量"}
<Tag>{`${selected.length} / ${lists.length} / ${total}`}</Tag>
</Space>
}
bodyStyle={{padding: 0, paddingLeft: 5}}
>
<PluginResultUI
results={infoState.messageState}
progress={infoState.processState}
featureType={infoState.featureTypeState}
feature={infoState.featureMessageState}
statusCards={infoState.statusState}
loading={loading}
onXtermRef={setXtermRef}
/>
</AutoCard>
</div>
</div>
)
})
Example #3
Source File: BatchExecutorPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BatchExecutorPage: React.FC<BatchExecutorPageProp> = (props) => {
const [loading, setLoading] = useState(false);
const [pluginType, setPluginType] = useState<"yak" | "nuclei" | string>("yak");
const [limit, setLimit] = useState(200);
const [keyword, setKeyword] = useState("");
const [scripts, setScripts, getScripts] = useGetState<YakScript[]>([]);
const [total, setTotal] = useState(0);
const [selected, setSelected] = useState<string[]>([]);
const [indeterminate, setIndeterminate] = useState(false);
const [checked, setChecked] = useState(false)
const [executing, setExecuting] = useState(false);
const [token, setToken] = useState<string>(randomString(40));
const [percent, setPercent] = useState(0.0);
// 处理性能问题
const containerRef = useRef();
const wrapperRef = useRef();
const [list] = useVirtualList(getScripts(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 50, overscan: 20,
})
const [vlistHeigth, setVListHeight] = useState(600);
// 执行任务历史列表
const [taskHistory, setTaskHistory] = useState<TaskHistoryProps[]>([])
useEffect(() => {
setLoading(true)
ipcRenderer
.invoke("get-value", ExecuteTaskHistory)
.then((res: any) => {
setTaskHistory(res ? JSON.parse(res) : [])
})
.catch(() => {
})
.finally(() => {
setTimeout(() => setLoading(false), 300)
})
}, [])
useEffect(() => {
const totalYakScript = scripts.length;
const filterArr = scripts.filter((item) => selected.indexOf(item.ScriptName) > -1)
const IndeterminateFlag =
(filterArr.length > 0 && filterArr.length < totalYakScript && selected.length !== 0) ||
(filterArr.length === 0 && selected.length !== 0)
const checkedFlag = filterArr.length === totalYakScript && selected.length !== 0
setIndeterminate(IndeterminateFlag)
setChecked(checkedFlag)
}, [selected, scripts])
const search = useMemoizedFn(() => {
setLoading(true)
queryYakScriptList(
pluginType,
(data, total) => {
setTotal(total || 0)
setScripts(data)
}, () => setTimeout(() => setLoading(false), 300),
limit, undefined, keyword,
(pluginType === "yak" ? {
IsBatch: true
} : {
ExcludeNucleiWorkflow: true,
}) as any,
)
})
useEffect(() => {
setSelected([]);
if (!pluginType) return;
search()
}, [pluginType])
const selectYakScript = useMemoizedFn((y: YakScript) => {
if (!selected.includes(y.ScriptName)) {
setSelected([...selected, y.ScriptName])
}
});
const unselectYakScript = useMemoizedFn((y: YakScript) => {
setSelected(selected.filter(i => i !== y.ScriptName))
})
const renderListItem = useMemoizedFn((y: YakScript) => {
return <YakScriptWithCheckboxLine
key={y.ScriptName}
selected={selected.includes(y.ScriptName)} plugin={y} onSelected={selectYakScript}
onUnselected={unselectYakScript}
/>
});
const run = useMemoizedFn((t: TargetRequest) => {
setPercent(0)
//@ts-ignore
const time = Date.parse(new Date()) / 1000
const obj: TaskHistoryProps = {
target: t,
selected: selected,
pluginType: pluginType,
limit: limit,
keyword: keyword || "",
time: formatTimestamp(time)
}
const arr = [...taskHistory]
if (taskHistory.length === 10) arr.pop()
arr.unshift(obj)
setTaskHistory(arr)
ipcRenderer.invoke("set-value", ExecuteTaskHistory, JSON.stringify(arr))
const tokens = randomString(40)
setToken(tokens)
StartExecBatchYakScript(t, selected, tokens).then(() => {
setExecuting(true)
}).catch(e => {
failed(`启动批量执行插件失败:${e}`)
})
});
const cancel = useMemoizedFn(() => {
CancelBatchYakScript(token).then()
});
useEffect(() => {
ipcRenderer.on(`${token}-data`, async (e, data: any) => {
try {
if (data.ProgressMessage) {
setPercent(data.ProgressPercent)
return
}
} catch (e) {
console.info(e)
}
})
ipcRenderer.on(`${token}-error`, async (e, data) => {
failed(`批量执行插件遇到问题: ${data}`)
})
ipcRenderer.on(`${token}-end`, async (e) => {
setTimeout(() => setExecuting(false), 300)
})
return () => {
ipcRenderer.removeAllListeners(`${token}-data`)
ipcRenderer.removeAllListeners(`${token}-error`)
ipcRenderer.removeAllListeners(`${token}-end`)
}
}, [token])
const executeHistory = useMemoizedFn((item: TaskHistoryProps) => {
setLoading(true)
setLimit(item.limit)
setKeyword(item.keyword)
if (item.pluginType === pluginType) setTimeout(() => search(), 300);
else setPluginType(item.pluginType)
setTimeout(() => {
setSelected(item.selected)
setLoading(false)
}, 300);
})
return <div style={{width: "100%", height: "100%", display: "flex", overflowY: "hidden"}}>
<div style={{width: 470, height: "100%"}}>
{/*<AutoSpin*/}
{/* spinning={loading}*/}
{/*>*/}
<AutoCard
size={"small"}
bordered={false}
title={<Space>
<SelectOne label={"插件"} formItemStyle={{marginBottom: 0}} size={"small"} data={[
{text: "YAK 插件", value: "yak"},
{text: "YAML POC", value: "nuclei"},
]} value={pluginType} setValue={setPluginType}/>
</Space>}
bodyStyle={{
paddingLeft: 4,
paddingRight: 4,
overflow: "hidden", display: "flex", flexDirection: "column",
}}
extra={<Space>
<Popover title={"额外设置"} trigger={["click"]} content={<div>
<Form size={"small"} onSubmitCapture={e => {
e.preventDefault()
search()
}}>
<InputInteger
label={"插件展示数量"} value={limit} setValue={setLimit}
formItemStyle={{marginBottom: 4}}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type="primary" htmlType="submit">刷新</Button>
</Form.Item>
</Form>
</div>}>
<Button size={"small"} icon={<SettingOutlined/>} type={"link"}/>
</Popover>
<Popover title={"搜索插件关键字"} trigger={["click"]} content={<div>
<Form size={"small"} onSubmitCapture={e => {
e.preventDefault()
search()
}}>
<InputItem
label={""}
extraFormItemProps={{style: {marginBottom: 4}, colon: false}}
value={keyword}
setValue={setKeyword}
/>
<Form.Item colon={false} label={""} style={{marginBottom: 10}}>
<Button type="primary" htmlType="submit">搜索</Button>
</Form.Item>
</Form>
</div>}>
<Button size={"small"} type={!!keyword ? "primary" : "link"} icon={<SearchOutlined/>}/>
</Popover>
<Checkbox indeterminate={indeterminate} onChange={(r) => {
if (r.target.checked) {
const newSelected = [...scripts.map(i => i.ScriptName), ...selected];
setSelected(newSelected.filter((e, index) => newSelected.indexOf(e) === index));
} else {
setSelected([]);
}
}} checked={checked}>
全选
</Checkbox>
</Space>}
>
<div style={{flex: "1", overflow: "hidden"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setVListHeight(height)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}
/>
<div ref={containerRef as any} style={{height: vlistHeigth, overflow: "auto"}}>
<div ref={wrapperRef as any}>
{list.map(i => renderListItem(i.data))}
</div>
</div>
</div>
</AutoCard>
{/*</AutoSpin>*/}
</div>
<div style={{marginLeft: 12, flex: 1, backgroundColor: "#fff", overflow: "hidden"}}>
<AutoCard
title={<Space>
{"已选插件 / 当页插件 / 插件总量"}
<Tag>{`${selected.length} / ${scripts.length} / ${total}`}</Tag>
</Space>}
size={"small"} bordered={false}
extra={<Space>
{(percent > 0 || executing) && <div style={{width: 200}}>
<Progress status={executing ? "active" : undefined} percent={
parseInt((percent * 100).toFixed(0))
}/>
</div>}
</Space>}
bodyStyle={{display: "flex", flexDirection: "column", padding: '0 5px', overflow: "hidden"}}
>
{/* <ExecSelectedPlugins
disableStartButton={selected.length === 0}
onSubmit={run}
onCancel={cancel}
executing={executing}
loading={loading}
history={taskHistory}
executeHistory={executeHistory}
/> */}
<Divider style={{margin: 4}}/>
<div style={{flex: '1', overflow: "hidden"}}>
<AutoCard style={{padding: 4}} bodyStyle={{padding: 4, overflow: "hidden"}} bordered={false}>
<BatchExecutorResultUI token={token} executing={executing}/>
</AutoCard>
</div>
</AutoCard>
</div>
</div>
}
Example #4
Source File: BatchExecutorPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BatchTaskViewer: React.FC<BatchTaskViewerProp> = React.memo((props) => {
const [tasks, setTasks, getTasks] = useGetState<BatchTask[]>([]);
const containerRef = useRef();
const listRef = useRef();
const [height, setHeight] = useState(300);
const [list, scrollTo] = useVirtualList<BatchTask>(getTasks(), {
containerTarget: containerRef,
wrapperTarget: listRef,
itemHeight: 40,
overscan: 5,
})
const [xtermRef, setXtermRef, getXtermRef] = useGetState<any>(null)
// 转换task内的result数据
const convertTask = (task: BatchTask) => {
// @ts-ignore
const results: ExecResult[] = task.Results.filter((item) => !!item.Result).map((item) => item.Result)
const messages: ExecResultMessage[] = []
for (let item of results) {
if (!item.IsMessage) continue
try {
const raw = item.Message
const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
messages.push(obj)
} catch (e) {
console.error(e)
}
}
return messages
}
const statusTag = (task: BatchTask) => {
const messages: ExecResultMessage[] = convertTask(task)
const logs: ExecResultLog[] = messages
.filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[]
const haveResult = logs.filter((i) => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0
return haveResult ? <Tag color={"red"}>HIT</Tag> : "-"
}
const details = (task: BatchTask) => {
const infos: ExecResultMessage[] = convertTask(task)
const messages: ExecResultMessage[] = []
const featureMessages: ExecResultMessage[] = []
const featureTypes: ExecResultMessage[] = []
const processKVPair: Map<string, number> = new Map<string, number>()
const statusKVPair: Map<string, CacheStatusCardProps> = new Map<string, CacheStatusCardProps>()
for (let item of infos) {
try {
// 处理 Process KVPair
if (item.type === "progress") {
const processData = item.content as ExecResultProgress
if (processData && processData.id) {
processKVPair.set(
processData.id,
Math.max(processKVPair.get(processData.id) || 0, processData.progress)
)
}
return
}
// 处理 log feature-status-card-data
const logData = item.content as ExecResultLog
if (item.type === "log" && logData.level === "feature-status-card-data") {
try {
const obj = JSON.parse(logData.data)
const {id, data, tags} = obj
const {timestamp} = logData
const originData = statusKVPair.get(id)
if (originData && originData.Timestamp > timestamp) {
return
}
statusKVPair.set(id, {
Id: id,
Data: data,
Timestamp: timestamp,
Tags: Array.isArray(tags) ? tags : []
})
} catch (e) {
}
return
}
if (item.type === "log" && logData.level === "json-feature") {
try {
featureTypes.unshift(item)
} catch (e) {
}
return
}
if (item.type === "log" && logData.level === "feature-table-data") {
try {
featureMessages.unshift(item)
} catch (e) {
}
return
}
messages.unshift(item)
// 只缓存 100 条结果(日志类型 + 数据类型)
if (messages.length > 100) {
messages.pop()
}
} catch (e) {
}
}
let results = messages.filter((i) => i.type === "log").map((i) => i.content as ExecResultLog)
let featureResults = featureMessages
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog)
.filter((i) => i.data !== "null")
let featureType = featureTypes
.filter((i) => i.type === "log")
.map((i) => i.content as ExecResultLog)
.filter((i) => i.data !== "null")
const processes: ExecResultProgress[] = []
processKVPair.forEach((value, id) => {
processes.push({id: id, progress: value})
})
const cacheStatusKVPair: { [x: string]: StatusCardInfoProps } = {}
const statusCards: StatusCardProps[] = []
statusKVPair.forEach((value) => {
const item = JSON.parse(JSON.stringify(value))
item.Tag = item.Tags[0] || ""
delete item.Tags
statusCards.push(item)
})
statusCards.sort((a, b) => a.Id.localeCompare(b.Id))
for (let item of statusCards) {
if (item.Tag) {
if (cacheStatusKVPair[item.Tag]) {
cacheStatusKVPair[item.Tag].info.push(item)
} else {
cacheStatusKVPair[item.Tag] = {tag: item.Tag, info: [item]}
}
} else {
cacheStatusKVPair[item.Id] = {tag: item.Id, info: [item]}
}
}
return (
<PluginResultUI
loading={false}
progress={processes.sort((a, b) => a.id.localeCompare(b.id))}
results={results}
featureType={featureType}
feature={featureResults}
statusCards={Object.values(cacheStatusKVPair)}
onXtermRef={setXtermRef}
></PluginResultUI>
)
}
useEffect(() => {
if (!props.checked) setTasks(props.tasks)
}, [props.tasks])
useEffect(() => {
if (props.checked) {
const filterTasks: BatchTask[] = getTasks()
.filter(item => item.Results.length !== 0)
.filter(item =>
(convertTask(item).filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[])
.filter((i) => ["json", "success"]
.includes((i?.level || "").toLowerCase())).length > 0
)
setTasks(filterTasks)
} else {
setTasks(props.tasks)
}
}, [props.checked])
return <AutoCard bodyStyle={{padding: 0, overflow: "hidden"}} style={{height: "100%"}}>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setHeight(height)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}
/>
<div ref={containerRef as Ref<any>} style={{height: height, overflow: "auto"}}>
<div ref={listRef as Ref<any>}>
{list.map(i => {
return <div className="history-list-task-opt" key={i.data.TaskId}>
<Text ellipsis={{tooltip: true}} copyable={true}
style={{width: 300}}>{`${i.data.Target} / ${i.data.PoC.ScriptName}`}</Text>
<Divider type='vertical'/>
{statusTag(i.data)}
<Divider type='vertical'/>
<Tag color="green">{formatTimestamp(i.data.CreatedAt)}</Tag>
<Divider type='vertical'/>
<Button type="link" onClick={(e) => {
let m = showDrawer({
title: "poc详情",
keyboard: false,
width: "60%",
onClose: () => {
m.destroy()
},
content: (details(i.data))
})
setTimeout(() => {
// @ts-ignore
const execResults: ExecResult[] = i.data.Results.filter(
(item) => !!item.Result
).map((item) => item.Result)
for (let item of execResults) writeExecResultXTerm(getXtermRef(), item)
}, 500)
}}>详情</Button>
</div>
})}
</div>
</div>
</AutoCard>
})
Example #5
Source File: YakBatchExecutors.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
BugTestExecutor: React.FC<YakBatchExecutorsProp> = (props) => {
const update = useUpdate()
const [listHeight, setListHeight] = useState<number>(500)
const [params, setParams] = useState<ExecBatchYakScriptParams>({
Concurrent: 5,
Keyword: props.keyword.split("-")[0],
Limit: 10000,
Target: props.sendTarget ? JSON.parse(props.sendTarget).join(",") : "",
DisableNucleiWorkflow: true,
ExcludedYakScript: [
"[fingerprinthub-web-fingerprints]: FingerprintHub Technology Fingerprint",
"[tech-detect]: Wappalyzer Technology Detection"
],
TotalTimeoutSeconds: 180,
Type: "nuclei"
})
const [totalLoading, setTotalLoading] = useState(true)
const [tasks, setTasks, getTasks] = useGetState<ExecBatchYakScriptTask[]>([])
const [filterTasks, setFilterTasks, getFilterTasks] = useGetState<ExecBatchYakScriptTask[]>([])
const [error, setError] = useState("")
const [token, setToken] = useState("")
const [executing, setExecuting] = useState(false)
const [checked, setChecked] = useState<boolean>(false)
const [uploadLoading, setUploadLoading] = useState(false)
const containerRef = useRef(null)
const wrapperRef = useRef(null)
const listRef = useRef(null)
const filterContainerRef = useRef(null)
const filterWrapperRef = useRef(null)
const [list] = useVirtualList(getTasks(), {
containerTarget: containerRef,
wrapperTarget: wrapperRef,
itemHeight: 50,
overscan: 5
})
const [filterList] = useVirtualList(getFilterTasks(), {
containerTarget: filterContainerRef,
wrapperTarget: filterWrapperRef,
itemHeight: 50,
overscan: 5
})
window.onresize = () => {
let timer: any = null
window.onresize = () => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
if (!listRef || !listRef.current) return
const list = listRef.current as unknown as HTMLDivElement
setListHeight(list.clientHeight - 10)
}, 50)
}
}
useEffect(() => {
setTimeout(() => {
if (!listRef || !listRef.current) return
const list = listRef.current as unknown as HTMLDivElement
setListHeight(list.clientHeight - 10)
}, 600)
return () => {
window.onresize = null
}
}, [])
useEffect(() => {
let timer = setTimeout(() => {
update()
}, 100)
return () => {
clearTimeout(timer)
}
}, [listHeight])
useEffect(() => {
setTotalLoading(true)
setTimeout(() => setTotalLoading(false), 500)
const token = randomString(40)
setToken(token)
setTasks([])
setParams({...params, Keyword: props.keyword.split("-")[0]})
const tempTasks = new Map<string, ExecBatchYakScriptTask>()
const updateTasks = () => {
let items: ExecBatchYakScriptTask[] = []
tempTasks.forEach((v, k) => {
items.push(v)
})
setTasks(items.sort((a, b) => b.Id.localeCompare(a.Id)))
}
const dataChannel = `${token}-exec-batch-yak-script-data`
const errorChannel = `${token}-exec-batch-yak-script-error`
const endChannel = `${token}-exec-batch-yak-script-end`
let updateTableTick = setInterval(updateTasks, 1000)
ipcRenderer.on(dataChannel, async (e: any, data: ExecBatchYakScriptResult) => {
if (data.ProgressMessage) {
return
}
let element = tempTasks.get(data.Id)
if (element === undefined) {
tempTasks.set(data.Id, {
Id: data.Id,
PoC: data.PoC,
Results: [],
Status: data.Status,
progress: 0
})
// updateTasks()
return
} else {
element.Status = data.Status
if (!element.Ok) {
element.Ok = data.Ok || false
}
element.Reason = data.Reason
if (data.Result) {
element.Results.push({...data.Result})
}
// updateTasks()
return
}
})
ipcRenderer.on(errorChannel, (e: any, error: any) => {
setError(error)
})
ipcRenderer.on(endChannel, (e: any, data: any) => {
info("模块加载完成 / 执行完毕")
setExecuting(false)
updateTasks()
})
ipcRenderer.invoke(
"exec-batch-yak-script",
{...params, Keyword: props.keyword.split("-")[0], Target: ""},
token
)
setExecuting(true)
return () => {
clearInterval(updateTableTick)
ipcRenderer.invoke("cancel-exec-batch-yak-script", token)
ipcRenderer.removeAllListeners(dataChannel)
ipcRenderer.removeAllListeners(errorChannel)
ipcRenderer.removeAllListeners(endChannel)
}
}, [props.keyword])
// 转换task内的result数据
const convertTask = (task: ExecBatchYakScriptTask) => {
// @ts-ignore
const results: ExecResult[] = task.Results
const messages: ExecResultMessage[] = []
for (let item of results) {
if (!item.IsMessage) continue
try {
const raw = item.Message
const obj: ExecResultMessage = JSON.parse(Buffer.from(raw).toString("utf8"))
messages.push(obj)
} catch (e) {
console.error(e)
}
}
return messages
}
useEffect(() => {
if (checked) {
const filters: ExecBatchYakScriptTask[] = getTasks()
.filter((item) => item.Results.length !== 0)
.filter(
(item) =>
(
convertTask(item)
.filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[]
).filter((i) => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0
)
setFilterTasks(filters)
} else {
setFilterTasks([])
}
}, [checked])
useEffect(() => {
if (tasks) {
const filters: ExecBatchYakScriptTask[] = getTasks()
.filter((item) => item.Results.length !== 0)
.filter(
(item) =>
(
convertTask(item)
.filter((e) => e.type === "log")
.map((i) => i.content)
.sort((a: any, b: any) => a.timestamp - b.timestamp) as ExecResultLog[]
).filter((i) => ["json", "success"].includes((i?.level || "").toLowerCase())).length > 0
)
if (JSON.stringify(filterTasks) !== JSON.stringify(filters)) setFilterTasks(filters)
}
}, [tasks])
if (totalLoading) {
return (
<div style={{textAlign: "center", width: "100%", marginTop: 100}}>
<Spin>正在加载专用漏洞库</Spin>
</div>
)
}
return (
<div className='bug-test-container'>
<Row>
<Col span={3}></Col>
<Col span={18}>
<Form
style={{textAlign: "center"}}
onSubmitCapture={(e) => {
e.preventDefault()
if (tasks.length === 0) {
Modal.error({title: "模块还未加载,请点击右上角配置进行YAML POC更新"})
return
}
if (!params.Target) {
Modal.error({title: "检测目标不能为空"})
return
}
if (!params.Keyword) {
Modal.error({title: "无 PoC 关键字选择"})
return
}
if (!token) {
Modal.error({title: "BUG:无 Token 生成,请重新打开该页"})
}
ipcRenderer.invoke("exec-batch-yak-script", params, token)
setExecuting(true)
setChecked(false)
}}
>
<Space style={{width: "80%"}} direction={"vertical"}>
<Spin spinning={uploadLoading}>
<ContentUploadInput
type="textarea"
beforeUpload={(f) => {
if (f.type !== "text/plain") {
failed(`${f.name}非txt文件,请上传txt格式文件!`)
return false
}
setUploadLoading(true)
ipcRenderer.invoke("fetch-file-content", (f as any).path).then((res) => {
setParams({...params, Target: res})
setTimeout(() => setUploadLoading(false), 100)
})
return false
}}
item={{
style: {textAlign: "left"},
label: "检测的目标",
}}
textarea={{
isBubbing: true,
setValue: (Target) => setParams({...params, Target}),
value: params.Target,
rows: 1,
placeholder: "可接受输入为:URL / IP / 域名 / 主机:端口,逗号分隔"
}}
suffixNode={
executing ? (
<Popconfirm
title={"确定要停止该漏洞检测?"}
onConfirm={(e) => ipcRenderer.invoke("cancel-exec-batch-yak-script", token)}
>
<Button type='primary' danger>
强制停止
</Button>
</Popconfirm>
) : (
<Button type='primary' htmlType='submit'>
开始检测
</Button>
)
}
></ContentUploadInput>
</Spin>
<div style={{width: "100%", textAlign: "left", paddingLeft: 84}}>
<Space>
<Tag>并发/线程: {params.Concurrent}</Tag>
<Tag>总超时: {params.TotalTimeoutSeconds} sec</Tag>
<Button
type={"link"}
style={{margin: 0, paddingLeft: 0}}
onClick={(e) => {
showModal({
title: "设置批量检测额外参数",
content: (
<>
<Form
onSubmitCapture={(e) => e.preventDefault()}
labelCol={{span: 7}}
wrapperCol={{span: 14}}
>
<InputInteger
label={"并发量(线程)"}
setValue={(Concurrent) =>
setParams({...params, Concurrent})
}
defaultValue={params.Concurrent}
/>
<InputInteger
label={"总超时时间/s"}
setValue={(TotalTimeoutSeconds) =>
setParams({
...params,
TotalTimeoutSeconds
})
}
defaultValue={params.TotalTimeoutSeconds}
/>
</Form>
</>
)
})
}}
>
额外参数
</Button>
</Space>
</div>
</Space>
</Form>
</Col>
<Col span={3} style={{position: "relative"}}>
<div style={{width: 140, position: "absolute", right: 2, bottom: 2}}>
<span style={{display: "inline-block", height: 22, marginRight: 5}}>只展示命中项</span>
<Switch checked={checked} onChange={(checked) => setChecked(checked)}></Switch>
</div>
</Col>
</Row>
<Divider style={{margin: "10px 0"}} />
<div ref={listRef} className='bug-test-list'>
{tasks.length === 0 ? (
<div>
<Empty
style={{marginTop: 75}}
description={"模块还未加载,请点击右上角配置进行插件仓库更新"}
></Empty>
</div>
) : checked ? (
<div ref={filterContainerRef} style={{height: listHeight, overflow: "auto"}}>
<div ref={filterWrapperRef}>
{filterList.map((ele) => (
<div className='list-item' key={ele.data.Id}>
<Text ellipsis={{tooltip: true}} copyable={true} style={{width: 260}}>
{ele.data.Id}
</Text>
<Divider type='vertical' />
<div style={{width: 120, textAlign: "center"}}>
{StatusToVerboseTag(ele.data.Status)}
</div>
<Divider type='vertical' />
<div>
<ExecResultsViewer results={ele.data.Results} oneLine={true} />
</div>
<Divider type='vertical' />
<div style={{flexGrow: 1, textAlign: "right"}}>
<Space>
<Button
type={"primary"}
size={"small"}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `单体模块测试: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<YakScriptOperator script={ele.data.PoC} target={params.Target} />
</>
)
})
}}
>
复测
</Button>
<Button
size={"small"}
style={{marginRight: 8}}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `源码: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<div style={{height: 400}}>
<YakEditor
readOnly={true}
type={"yaml"}
value={ele.data.PoC.Content}
/>
</div>
</>
)
})
}}
>
源码
</Button>
</Space>
</div>
</div>
))}
</div>
</div>
) : (
<div ref={containerRef} style={{height: listHeight, overflow: "auto"}}>
<div ref={wrapperRef}>
{list.map((ele) => (
<div className='list-item' key={ele.data.Id}>
<Text ellipsis={{tooltip: true}} copyable={true} style={{width: 260}}>
{ele.data.Id}
</Text>
<Divider type='vertical' />
<div style={{width: 120, textAlign: "center"}}>
{StatusToVerboseTag(ele.data.Status)}
</div>
<Divider type='vertical' />
<div>
<ExecResultsViewer results={ele.data.Results} oneLine={true} />
</div>
<Divider type='vertical' />
<div style={{flexGrow: 1, textAlign: "right"}}>
<Space>
<Button
type={"primary"}
size={"small"}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `单体模块测试: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<YakScriptOperator script={ele.data.PoC} target={params.Target} />
</>
)
})
}}
>
复测
</Button>
<Button
size={"small"}
style={{marginRight: 8}}
onClick={(e) => {
if (!ele.data.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `源码: ${ele.data.PoC.ScriptName}`,
width: "75%",
content: (
<>
<div style={{height: 400}}>
<YakEditor
readOnly={true}
type={"yaml"}
value={ele.data.PoC.Content}
/>
</div>
</>
)
})
}}
>
源码
</Button>
</Space>
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
)
}