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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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 vote down vote up
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>
    )
}