antd#PageHeader TypeScript Examples
The following examples show how to use
antd#PageHeader.
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: DocumentEditor.tsx From yakit with GNU Affero General Public License v3.0 | 6 votes |
DocumentEditor: React.FC<DocumentEditorProp> = (props) => {
const [markdown, setMarkdown] = useState(props.markdown);
return <div>
<PageHeader
title={"编辑/添加模块文档"} subTitle={props.yakScript.ScriptName + `[${props.yakScript.Id}]`}
extra={[
<Button
type={"primary"}
onClick={e => {
ipcRenderer.invoke("SaveMarkdownDocument", {
YakScriptId: props.yakScript.Id,
YakScriptName: props.yakScript.ScriptName,
Markdown: markdown,
}).then(() => {
success("保存文档成功")
}).catch((e: any) => {
console.info(e)
}).finally()
}}
>保存 / 创建文档</Button>
]}
/>
<MDEditor
value={markdown}
onChange={e => setMarkdown(e || "")}
maxHeight={1000} height={700}
>
</MDEditor>
</div>
}
Example #2
Source File: PageContent.tsx From iot-center-v2 with MIT License | 6 votes |
PageContent: FunctionComponent<PageContentProps> = (props) => (
<Layout.Content
style={{
paddingLeft: 60,
paddingRight: 60,
paddingTop: 55,
margin: 0,
minHeight: 280,
minWidth: 350,
height: '100vh',
overflowY: props.forceShowScroll ? 'scroll' : 'auto',
}}
>
<PageHeader
title={props.title}
style={{paddingLeft: 0, paddingRight: 0, paddingTop: 0}}
extra={props?.titleExtra}
/>
{props.message ? (
<Alert
message={props.message.title}
description={props.message.description}
type={props.message.type}
showIcon
closable
/>
) : undefined}
<div className="site-layout-background" style={{minHeight: 360}}>
<Spin spinning={props.spin ?? false}>{props.children}</Spin>
</div>
</Layout.Content>
)
Example #3
Source File: AnalyzerPage.tsx From yakit with GNU Affero General Public License v3.0 | 6 votes |
AnalyzerPage: React.FC<AnalyzerPageProp> = (props) => {
const [response, setResponse] = useState<any>();
const [error, setError] = useState("");
useEffect(() => {
ipcRenderer.invoke("http-analyze", {
IsHTTPS: props.isHttps,
Request: props.request,
Response: props.response,
})
}, [props])
useEffect(() => {
ipcRenderer.on("client-http-analyze-data", (e: any, data: any) => {
})
ipcRenderer.on("client-http-analyze-error", (e: any, details: any) => {
setError(details)
})
return () => {
}
}, [])
return <div>
<PageHeader title={"HTTP 模糊测试分析器"}/>
<Row gutter={8}>
<Col span={12}>
<div style={{height: 500}}>
<YakEditor value={props.request} readOnly={true}/>
</div>
</Col>
<Col span={12}>
<div style={{height: 500}}>
<YakEditor value={props.response} readOnly={true}/>
</div>
</Col>
</Row>
</div>
}
Example #4
Source File: index.tsx From nebula-studio with Apache License 2.0 | 6 votes |
NebulaBreadcrumb: React.FC<IProps> = (props: IProps) => {
const { routes, extraNode } = props;
return (
<PageHeader
title={null}
className={styles.studioBreadcrumb}
breadcrumbRender={() => {
return <div className={cls(styles.breadcrumbContainer, 'studioCenterLayout')}>
<Breadcrumb
className={styles.breadcrumb}
routes={routes}
itemRender={itemRender}
/>
{extraNode}
</div>;
}}
/>
);
}
Example #5
Source File: Page.tsx From wildduck-ui with MIT License | 6 votes |
/** Page Renderer */
render(): JSX.Element {
const { children, error, loading, title, subTitle, extra } = this.props;
return (
<PageHeader title={title} subTitle={subTitle} extra={extra} className={this.props.className}>
{React.Children.map(children, (childElement) => {
return React.cloneElement(childElement as React.ReactElement<any>, {
error,
loading,
});
})}
</PageHeader>
);
}
Example #6
Source File: index.tsx From surveyo with Apache License 2.0 | 6 votes |
function Graphiql() {
const GRAPHQL_ENDPOINT = process.env.REACT_APP_GRAPHQL_ENDPOINT;
const {id} = useParams();
const {getIdTokenClaims} = useAuth0();
const graphQLFetcher = async (graphQLParams: any) => {
const token = await getIdTokenClaims();
return await fetch(GRAPHQL_ENDPOINT!, {
method: 'post',
headers: {
'Content-Type': 'application/json',
'X-Auth-Token': token.__raw,
},
body: JSON.stringify(graphQLParams),
}).then(response => response.json());
};
return (
<PageHeader ghost title="Query">
<Layout style={{height: '80vh'}}>
<Row style={{height: '100%'}}>
<GraphiQL fetcher={graphQLFetcher} query={makeDefaultQuery(id)} />
</Row>
</Layout>
</PageHeader>
);
}
Example #7
Source File: index.tsx From surveyo with Apache License 2.0 | 6 votes |
function PageLoading() {
return (
<PageHeader title="...">
<Row gutter={[16, 16]}>
<Col span={24}>
<Card loading />
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Card loading />
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Card loading />
</Col>
</Row>
</PageHeader>
);
}
Example #8
Source File: index.tsx From surveyo with Apache License 2.0 | 6 votes |
export default function Dashboard() {
return (
<PageHeader
ghost={true}
title="Dashboard"
extra={[
<Link to="/create">
<Button icon={<PlusOutlined />} type="primary">
New survey
</Button>
</Link>,
]}
>
<DashboardHelper />
</PageHeader>
);
}
Example #9
Source File: CommonApp.tsx From disco-cube-admin with MIT License | 6 votes |
CommonApp: React.FC<Props> = ({
onBack,
isRunning,
isCommand,
onStart,
onStop,
appName,
}) => {
return (
<Segment spacing={10} width="100%" maxWidth={500} height={"100%"}>
<PageHeader
ghost={false}
onBack={onBack}
title="Apps"
subTitle={appName.toUpperCase()}
></PageHeader>
<VerticalSpacer space={10} />
{!isRunning && (
<Button type="primary" disabled={isCommand} onClick={() => onStart({})}>
Start
</Button>
)}
{isRunning && (
<Button type="primary" onClick={onStop}>
Stop
</Button>
)}
</Segment>
);
}
Example #10
Source File: CubemapApp.tsx From disco-cube-admin with MIT License | 6 votes |
CubemapApp: React.FC<Props> = ({
onBack,
onStartCubemap,
isRunning,
onStopCubemap,
isCommand,
}) => {
const [selected, setSelected] = React.useState(randomOne(cubemaps));
return (
<Segment spacing={10} width="100%" maxWidth={500} height={"100%"}>
<PageHeader ghost={false} onBack={onBack} title="Apps" subTitle="Cubemap"></PageHeader>
<VerticalSpacer space={10} />
<Vertical spacing={20}>
<Select defaultValue={selected} value={selected} onChange={value => setSelected(value)}>
{Object.values(cubemaps).map(id => (
<Select.Option key={id} value={id}>
{id}
</Select.Option>
))}
</Select>
{!isRunning && (
<Button
type="primary"
disabled={isCommand}
onClick={() => onStartCubemap({ cubemapId: selected })}
>
Start
</Button>
)}
{isRunning && (
<Button type="primary" onClick={onStopCubemap}>
Stop
</Button>
)}
</Vertical>
</Segment>
);
}
Example #11
Source File: PluginExecutor.tsx From yakit with GNU Affero General Public License v3.0 | 5 votes |
PluginExecutor: React.FC<PluginExecutorProp> = (props) => {
const {script, settingShow, settingNode} = props;
const [token, setToken] = useState(randomString(40));
const [loading, setLoading] = useState(false);
const [infoState, {reset, setXtermRef}, xtermRef] = useHoldingIPCRStream(
script.ScriptName,
"exec-yak-script",
token,
() => {
setTimeout(() => setLoading(false), 300)
}
)
const executeByParams = useMemoizedFn((p: YakExecutorParam[]) => {
setLoading(true)
setTimeout(() => {
ipcRenderer.invoke("exec-yak-script", {
Params: [...p, ...(props.extraYakExecutorParams || [])],
YakScriptId: props.script.Id,
}, token)
}, 300);
})
return <div style={{height: "100%", display: "flex", flexFlow: "column"}}>
<PageHeader
title={script.ScriptName} style={{marginBottom: 0, paddingBottom: 0}}
subTitle={props.subTitle}
extra={props.extraNode}
>
{!!settingShow && settingNode}
<YakScriptParamsSetter
{...script}
loading={loading}
onParamsConfirm={executeByParams}
onClearData={() => {
xtermClear(xtermRef)
reset()
}}
onCanceled={() => {
ipcRenderer.invoke("cancel-exec-yak-script", token)
}}
styleSize={props.size}
submitVerbose={"开始执行"}
primaryParamsOnly={true}
/>
</PageHeader>
<Divider/>
<PluginResultUI
script={script} loading={loading} progress={infoState.processState} results={infoState.messageState}
featureType={infoState.featureTypeState}
feature={infoState.featureMessageState}
statusCards={infoState.statusState} onXtermRef={setXtermRef}
/>
</div>
}
Example #12
Source File: index.tsx From nebula-dashboard with Apache License 2.0 | 5 votes |
render() {
const { breadcrumb: routes, title, showBackBtn, extra } = this.props.config;
const itemRender = (route, _params, routes, _paths) => {
const last = routes.indexOf(route) === routes.length - 1;
return last ? (
<span>{route.breadcrumbName}</span>
) : (
<Link to={route.path}>{route.breadcrumbName}</Link>
);
};
const { pathname } = this.props.location;
const currentPage = extra
? extra.filter(item => item.value === pathname)
: null;
return (
<PageHeader
className="page-header"
title={
<>
{showBackBtn && (
<Icon
className="btn-return blue"
icon="#iconreturn"
onClick={this.handleBack}
/>
)}
<span>{title}</span>
</>
}
breadcrumb={{ itemRender, routes }}
extra={
extra && currentPage.length !== 0 ? (
<Radio.Group
options={extra}
onChange={this.handlePageView}
value={currentPage[0].value}
optionType="button"
buttonStyle="solid"
/>
) : null
}
/>
);
}
Example #13
Source File: index.tsx From surveyo with Apache License 2.0 | 5 votes |
function GqlViz() {
const {id} = useParams();
const {loading, error, data} = useQuery<GetChartData, GetChartDataVariables>(
GET_CHART_DATA,
{
variables: {id},
}
);
if (loading) {
return <Card title loading />;
}
if (error) {
return (
<Card title>
<Alert message={error.message} type="warning" />
</Card>
);
}
const makeChart = (field: GetChartData_getForm_fields) => {
switch (field.type) {
case 'NetPromoterScore':
return <ChartNetPromoterScore {...field} />;
case 'Rating':
return <ChartRating {...field} />;
case 'SingleChoice':
return <ChartSingleChoice {...field} />;
case 'Text':
return <ChartText {...field} />;
default:
return null;
}
};
return (
<PageHeader ghost={true} title={data!.getForm!.title}>
<Row gutter={[16, 16]}>
{data!.getForm!.fields.map(field => {
const chart = makeChart(field);
if (chart) {
return (
<Col span={12}>
<Card style={{height: '100%'}}>
<h3>{field.title}</h3>
<div style={{height: 'fit-content'}}>{chart}</div>
</Card>
</Col>
);
} else {
return null;
}
})}
</Row>
</PageHeader>
);
}
Example #14
Source File: VideoApp.tsx From disco-cube-admin with MIT License | 5 votes |
VideoApp: React.FC<Props> = ({
onBack,
onStartVideo,
isRunning,
onStopVideo,
isCommand,
}) => {
const [selectedVideo, setSelectedVideo] = React.useState(randomOne(videos));
return (
<Segment spacing={10} width="100%" maxWidth={500} height={"100%"}>
<PageHeader ghost={false} onBack={onBack} title="Apps" subTitle="Video"></PageHeader>
<VerticalSpacer space={10} />
<Vertical spacing={20}>
<Select
defaultValue={selectedVideo}
value={selectedVideo}
onChange={value => setSelectedVideo(value)}
>
{Object.values(videos).map(id => (
<Select.Option key={id} value={id}>
{id}
</Select.Option>
))}
</Select>
{!isRunning && (
<Button
type="primary"
disabled={isCommand}
onClick={() => onStartVideo({ videoId: selectedVideo })}
>
Start
</Button>
)}
{isRunning && (
<Button type="primary" onClick={onStopVideo}>
Stop
</Button>
)}
</Vertical>
</Segment>
);
}
Example #15
Source File: RPIDemos.tsx From disco-cube-admin with MIT License | 5 votes |
RPIDemos: React.FC<Props> = ({
onBack,
onStartDemo,
isRunning,
onStopDemo,
isCommand,
}) => {
const [selectedDemoId, setSelectedDemoId] = React.useState(`D0`);
return (
<Segment spacing={10} width="100%" maxWidth={500} height={"100%"}>
<PageHeader ghost={false} onBack={onBack} title="Apps" subTitle="RPI Demos"></PageHeader>
<VerticalSpacer space={10} />
<Vertical spacing={20}>
<Select
defaultValue={selectedDemoId}
value={selectedDemoId}
onChange={value => setSelectedDemoId(value)}
>
{Object.keys(demos).map(id => (
<Select.Option key={id} value={id} disabled={(demos as any)[id].disabled}>
{id} - {(demos as any)[id].description}
</Select.Option>
))}
</Select>
{!isRunning && (
<Button
type="primary"
disabled={isCommand}
onClick={() => onStartDemo({ demoId: selectedDemoId })}
>
Start
</Button>
)}
{isRunning && (
<Button type="primary" onClick={onStopDemo}>
Stop
</Button>
)}
</Vertical>
</Segment>
);
}
Example #16
Source File: PaintApp.tsx From disco-cube-admin with MIT License | 5 votes |
PaintApp: React.FC<Props> = ({
onBack,
isRunning,
isCommand,
onStart,
onAppStateUpdated,
onStop,
}) => {
const [settings, setSettings] = React.useState<PaintingSettings>({
bushSize: 1,
brushColor: { r: 255, g: 0, b: 0 },
});
return (
<Segment spacing={10} width="100%" maxWidth={500} height={"100%"}>
<PageHeader
ghost={false}
onBack={onBack}
title="Apps"
subTitle={"PAINT"}
extra={[
isRunning ? (
<Button key="stop" type="primary" onClick={onStop}>
Stop
</Button>
) : (
<Button key="start" type="primary" disabled={isCommand} onClick={() => onStart({})}>
start
</Button>
),
]}
></PageHeader>
<VerticalSpacer space={10} />
{isRunning && (
<Vertical spacing={10}>
<PaintControls settings={settings} onSettingsChange={setSettings} />
<Tabs
tabBarStyle={{ textAlign: "center", marginTop: 0 }}
size="large"
tabPosition="bottom"
type="card"
>
{narray(6).map(i => (
<Tabs.TabPane tab={i + ""} key={i + ""}>
<PaintCanvas
width={64}
height={64}
settings={settings}
onDataChanged={data => onAppStateUpdated({ face: i, data })}
/>
</Tabs.TabPane>
))}
</Tabs>
</Vertical>
)}
</Segment>
);
}
Example #17
Source File: index.tsx From S2 with MIT License | 5 votes |
Header: React.FC<HeaderProps> = React.memo(
({
className,
title,
width,
description,
exportCfg,
advancedSortCfg,
switcherCfg,
sheet,
extra,
dataCfg,
options,
...restProps
}) => {
const PRE_CLASS = 's2-header';
const getExtraComponents = () => {
return (
<>
{extra}
{switcherCfg.open && (
<SwitcherHeader
sheet={sheet}
dataCfg={dataCfg}
options={options}
{...switcherCfg}
/>
)}
{advancedSortCfg.open && (
<AdvancedSort sheet={sheet} {...advancedSortCfg} />
)}
{exportCfg.open && (
<Export key={'export'} sheet={sheet} {...exportCfg} />
)}
</>
);
};
return (
<PageHeader
className={cx(PRE_CLASS, className)}
style={{ width }}
ghost={false}
title={title}
extra={getExtraComponents()}
{...restProps}
>
{description}
</PageHeader>
);
},
)
Example #18
Source File: index.tsx From yugong with MIT License | 5 votes |
Index: React.FC<CustomPersetProps> = ({ runningData, onChange }) => {
const btnreset = get(runningData, '[0].arguments[1].data');
const btnsubmit = get(runningData, '[0].arguments[2].data');
const handleChangeReset = useCallback(
(e) => {
const runD = cloneDeep(runningData);
set(runD, '[0].arguments[1].data', e.target.value)
onChange(runD)
},
[onChange, runningData],
)
const handleChangeSubmit = useCallback(
(e) => {
const runD = cloneDeep(runningData);
set(runD, '[0].arguments[2].data', e.target.value)
onChange(runD)
},
[onChange, runningData],
)
return (
<FormModuleContext.Provider
value={{
runningData,
onChangeRunningData: onChange,
dataPath: '[0].arguments[0].data'
}}
>
<PageHeader title="表单项" />
<SortableFormData />
<PageHeader title="表单按钮" />
<LineItem label="重置">
<Input value={btnreset} placeholder="重置" onChange={handleChangeReset}/>
</LineItem>
<LineItem label="提交">
<Input value={btnsubmit} placeholder="提交" onChange={handleChangeSubmit} />
</LineItem>
</FormModuleContext.Provider>
);
}
Example #19
Source File: AppsPageContent.tsx From disco-cube-admin with MIT License | 5 votes |
AppsPageContent: React.FC<Props> = ({
onOpenPage,
runningAppName,
onCancelCommand,
command,
onStopApp,
}) => {
return (
<Segment spacing={10} width="100%" maxWidth={500} height={"100%"}>
<PageHeader
ghost={false}
//onBack={() => window.history.back()}
title="Apps"
//subTitle="This is a subtitle"
></PageHeader>
<Vertical spacing={5}>
<Label>RUNNING APP</Label>
<div>{runningAppName ? runningAppName : "NONE"}</div>
{runningAppName && (
<Button onClick={onStopApp} type="primary" style={{ width: 200 }}>
Stop
</Button>
)}
</Vertical>
<VerticalSpacer space={20} />
<Vertical spacing={5}>
<Label>command</Label>
<div>{command ? command : "NONE"}</div>
{command && (
<Button onClick={onCancelCommand} type="primary" style={{ width: 200 }}>
Cancel
</Button>
)}
</Vertical>
<VerticalSpacer space={20} />
<Label>APPs</Label>
<Grid spacing={20}>
{Object.entries(apps).map(([key, { path, icon: Icon, label }]) => (
<Button
key={key}
type="primary"
style={{ width: 100, height: 100 }}
onClick={() => onOpenPage(path)}
>
<Vertical horizontalAlign="center" verticalAlign="center" spacing={8}>
<Icon style={iconStyles} />
<div>{label}</div>
</Vertical>
</Button>
))}
</Grid>
</Segment>
);
}
Example #20
Source File: HTTPFlowDetail.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
HTTPFlowDetail: React.FC<HTTPFlowDetailProp> = (props) => {
const [flow, setFlow] = useState<HTTPFlow>();
const [loading, setLoading] = useState(false);
const actionFuzzer = [
{
id: 'send-fuzzer-info',
label: '发送到Fuzzer',
contextMenuGroupId: 'send-fuzzer-info',
run: () => {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {
isHttps: flow?.IsHTTPS,
request: Buffer.from(flow?.Request || []).toString("utf8")
}
})
if (props.onClose) props.onClose()
}
},
{
id: 'send-to-plugin',
label: '发送到数据包扫描',
contextMenuGroupId: 'send-fuzzer-info',
run: () => ipcRenderer.invoke("send-to-packet-hack", {
request: flow?.Request,
ishttps: flow?.IsHTTPS,
response: flow?.Response
})
}
]
useEffect(() => {
if (!props.hash) {
return
}
//
// ipcRenderer.on(props.hash, (e: any, data: HTTPFlow) => {
// setFlow(data)
// setTimeout(() => setLoading(false), 300)
// })
// ipcRenderer.on(`ERROR:${props.hash}`, (e: any, details: any) => {
// failed(`查询该请求失败[${props.hash}]: ` + details)
// })
setLoading(true)
ipcRenderer.invoke("GetHTTPFlowByHash", {Hash: props.hash}).then((data: HTTPFlow) => {
setFlow(data)
}).catch(e => {
failed(`GetHTTPFlow ByHash[${props.hash}] failed`)
}).finally(() => setTimeout(() => setLoading(false), 300))
// ipcRenderer.invoke("get-http-flow", props.hash)
return () => {
// ipcRenderer.removeAllListeners(props.hash)
// ipcRenderer.removeAllListeners(`ERROR:${props.hash}`)
}
}, [props.hash])
return <Spin spinning={loading} style={{width: "100%", marginBottom: 24}}>
{flow ? <>
{props.noHeader ? undefined : <PageHeader
title={`请求详情`} subTitle={props.hash}
extra={
props.fetchRequest ?
<Space>
<Tooltip title={"上一个请求"}>
<Button type="link" disabled={!!props.isFront}
icon={<LeftOutlined/>} onClick={() => {
props?.fetchRequest!(1)
}}></Button>
</Tooltip>
<Tooltip title={"下一个请求"}>
<Button type="link" disabled={!!props.isBehind}
icon={<RightOutlined/>} onClick={() => {
props?.fetchRequest!(2)
}}></Button>
</Tooltip>
</Space>
:
<></>
}/>
}
<Space direction={"vertical"} style={{width: "100%"}}>
<Descriptions column={4} bordered={true} size={"small"}>
<Descriptions.Item key={"method"} span={1} label={"HTTP 方法"}><Tag color={"geekblue"}><Text
style={{maxWidth: 500}}>{flow.Method}</Text></Tag></Descriptions.Item>
<Descriptions.Item key={"url"} span={3} label={"请求 URL"}>
<Text style={{maxWidth: 500}} copyable={true}>{flow.Url}</Text>
</Descriptions.Item>
<Descriptions.Item key={"https"} span={1} label={"HTTPS"}><Tag color={"geekblue"}>
<div
style={{maxWidth: 500}}>{flow.IsHTTPS ? "True" : "False"}</div>
</Tag></Descriptions.Item>
<Descriptions.Item key={"status"} span={1} label={"StatusCode"}><Tag
color={"geekblue"}>{flow.StatusCode}</Tag></Descriptions.Item>
<Descriptions.Item key={"size"} span={1} label={"Body大小"}><Tag color={"geekblue"}>
<div style={{maxWidth: 500}}>{flow.BodySizeVerbose}</div>
</Tag></Descriptions.Item>
<Descriptions.Item key={"type"} span={1} label={"Content-Type"}><Tag color={"geekblue"}>
<div style={{maxWidth: 500}}>{flow.ContentType}</div>
</Tag></Descriptions.Item>
</Descriptions>
<div style={{width: "100%", overflow: "auto"}}>
{flow.GetParams.length > 0 || flow.PostParams.length > 0 || flow.CookieParams.length > 0 ? <Tabs>
{flow.GetParams.length > 0 && <Tabs.TabPane key={"get"} tab={"GET 参数"}>
<FuzzableParamList data={flow.GetParams} sendToWebFuzzer={() => {
if (props.onClose) props.onClose()
}}/>
</Tabs.TabPane>}
{flow.PostParams.length > 0 && <Tabs.TabPane key={"post"} tab={"POST 参数"}>
<FuzzableParamList data={flow.PostParams} sendToWebFuzzer={() => {
if (props.onClose) props.onClose()
}}/>
</Tabs.TabPane>}
{flow.CookieParams.length > 0 && <Tabs.TabPane key={"cookie"} tab={"Cookie 参数"}>
<FuzzableParamList data={flow.CookieParams} sendToWebFuzzer={() => {
if (props.onClose) props.onClose()
}}/>
</Tabs.TabPane>}
</Tabs> : ""}
</div>
<Row gutter={8}>
<Col span={12}>
<Card title={"原始 HTTP 请求"} size={"small"} bodyStyle={{padding: 0}}>
<div style={{height: 350}}>
<YakEditor readOnly={true} type={"http"}//theme={"fuzz-http-theme"}
value={new Buffer(flow.Request).toString("utf-8")}
actions={[...actionFuzzer]}/>
</div>
</Card>
</Col>
<Col span={12}>
<Card title={"原始 HTTP 响应"} size={"small"} bodyStyle={{padding: 0}}>
<div style={{height: 350}}>
<YakEditor readOnly={true} type={"http"}// theme={"fuzz-http-theme"}
value={new Buffer(flow.Response).toString("utf-8")}
/>
</div>
</Card>
</Col>
</Row>
{/*<Collapse>*/}
{/* <Collapse.Panel key={"request-raw"} header={"原始 HTTP 请求数据包内容"}>*/}
{/* </Collapse.Panel>*/}
{/* <Collapse.Panel key={"response-raw"} header={"原始 HTTP 响应数据包内容"}>*/}
{/* </Collapse.Panel>*/}
{/*</Collapse>*/}
<Row gutter={8}>
<Col span={12}>
<Collapse defaultActiveKey={"request"}>
<Collapse.Panel key={"request"} header={"Request Headers"}>
<Descriptions bordered={true} column={1} size={"small"}>
{(flow?.RequestHeader || []).sort((i, e) => {
return i.Header.localeCompare(e.Header)
}).map(i => {
return <Descriptions.Item key={i.Header} label={<Text style={{width: 240}}>
<Tag>{i.Header}</Tag>
</Text>}>
<Text
copyable={true}
style={{maxWidth: 500}}
ellipsis={{tooltip: true}}>{i.Value}</Text>
</Descriptions.Item>
})}
</Descriptions>
</Collapse.Panel>
</Collapse>
</Col>
<Col span={12}>
<Collapse defaultActiveKey={"response"}>
<Collapse.Panel key={"response"} header={"Response Headers"}>
<Descriptions bordered={true} column={1} size={"small"}>
{(flow?.ResponseHeader || []).sort((i, e) => {
return i.Header.localeCompare(e.Header)
}).map(i => {
return <Descriptions.Item key={i.Header} label={<Text style={{width: 240}}>
<Tag>{i.Header}</Tag>
</Text>}>
<Text
copyable={true}
style={{maxWidth: 500}}
ellipsis={{tooltip: true}}>{i.Value}</Text>
</Descriptions.Item>
})}
</Descriptions>
</Collapse.Panel>
</Collapse>
</Col>
</Row>
</Space>
</> : ""}
</Spin>
}
Example #21
Source File: index.tsx From dnde with GNU General Public License v3.0 | 4 votes |
EditPage = () => {
const ref = useRef<any>(null);
const { templateId }: { templateId: string | undefined } = useParams();
const [trigger, { data, isError, isLoading, isSuccess }] = useLazyGetTemplateQuery();
useEffect(() => {
if (templateId === 'new' || typeof templateId === 'undefined') {
ref.current && ref.current.loadJson(null);
} else {
if (templateId) {
message.loading({ content: 'Fetching Template...', key: LOADING_KEY, duration: 0 });
trigger({ id: templateId });
}
}
}, []);
useEffect(() => {
if (isSuccess && data) {
try {
ref.current && ref.current.loadJson(data.response.data);
} catch (e) {
message.error('Unable to load template', 3);
}
} else if (isSuccess && !data) {
message.error('Template is empty', 2);
}
if (isSuccess) {
message.destroy(LOADING_KEY);
}
if (isError) {
message.info('Network error, template not fetched.', 2);
}
}, [isError, isLoading, isSuccess, data]);
const copyJsonInClipBoard = (e: any) => {
if (ref.current) {
e.preventDefault();
const json = ref.current.getJson();
logger.log('json', json);
navigator.clipboard.writeText(json);
success('Copied to Clipboard & logged in devtools ');
}
};
const copyHTMLAsClipBoard = (e: any) => {
if (ref.current) {
const html = ref.current.getHtml();
navigator.clipboard.writeText(html);
logger.log('html', html);
success('Copied to clipboard & logged in devtools ');
e.preventDefault();
}
};
const copyPreviewImage = async (e: any) => {
if (ref.current) {
e.preventDefault();
const html = ref.current.html;
navigator.clipboard.writeText(await generatePreview(html));
success('Preview Image Copied to clipboard');
}
};
return (
<div style={{ flex: '1', display: 'flex', width: '100%', height: '100%' }}>
<Row style={{ height: '100%', width: '100%' }} justify="center">
<Prompt
when={UNDOREDO.undo.length > 1 || UNDOREDO.redo.length > 1}
message={() => 'Are you sure you want to leave, your changes will be lost'}
/>
<Col lg={24} xl={0}>
<div style={{ textAlign: 'center', padding: '40px', paddingTop: '10%' }}>
<h3>Sorry, You need a device with a larger screen to perform editing, atleast '{'>'}=1200px'</h3>
</div>
</Col>
<Col xs={0} xl={24}>
<Layout style={{ height: '100%' }}>
<PageHeader
ghost={false}
onBack={() => window.history.back()}
title="dnde"
subTitle=""
style={{ borderBottom: '1px solid #e8e8e8' }}
extra={[
<>
<SendTestMail editorRef={ref} key="4" />
{/* <Button key="5" onClick={copyPreviewImage}>
Copy Preview Image
</Button> */}
<Button key="2" onClick={copyHTMLAsClipBoard}>
Copy as html
</Button>
<Button key="1" onClick={copyJsonInClipBoard}>
Copy as json
</Button>
</>,
]}
></PageHeader>
<Content>
<Editor ref={ref} />
</Content>
</Layout>
</Col>
</Row>
</div>
);
}
Example #22
Source File: ShellReceiverPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
ShellReceiverPage: React.FC<ShellReceiverPageProp> = (props) => {
const [addrs, setAddrs] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [updatingAddrs, setUpdatingAddrs] = useState(true);
const waitingSyncAddr = () => {
setUpdatingAddrs(true)
};
const removeListenPort = (addr: string) => {
waitingSyncAddr()
ipcRenderer.invoke("listening-port-cancel", addr)
}
const startListenPort = (addr: string) => {
if (!addr.includes(":")) {
failed(`无法启动端口监听程序,端口格式不合理: [${addr}]`)
return
}
const result = addr.split(":", 2);
const host = result[0];
const port = result[1];
if (!host || !port) {
failed(`无法解析主机/端口`)
return;
}
if (addrs.includes(addr)) {
Modal.error({title: "该地址已经被占用: " + addr})
failed("该地址已经被占用: " + addr)
return;
}
setLoading(true)
setTimeout(() => {
ipcRenderer.invoke("listening-port", host, port).then(() => {
success("监听端口成功")
}).catch((e: any) => {
failed(`ERROR: ${JSON.stringify(e)}`)
}).finally(() => {
waitingSyncAddr()
setTimeout(() => setLoading(false), 300)
})
}, 500)
};
useEffect(() => {
const id = setInterval(() => {
ipcRenderer.invoke("listening-port-query-addrs").then(r => {
setAddrs(r)
}).finally(() => {
if (updatingAddrs) {
setUpdatingAddrs(false)
}
})
}, 1000)
return () => {
clearInterval(id)
}
}, [])
const createForm = () => {
const m = showModal({
title: "开始监听一个 Yak 所属服务器的端口",
width: "50%",
content: <>
<CreateShellReceiverForm onCheck={addr => {
return true
}} onCreated={(addr) => {
startListenPort(addr);
m.destroy()
}}/>
</>
})
}
useEffect(() => {
const errorKey = "client-listening-port-end";
ipcRenderer.on(errorKey, (e: any, data: any) => {
Modal.info({title: `端口[${data}]被关闭`})
})
return () => {
ipcRenderer.removeAllListeners(errorKey)
}
}, [])
return <div style={{width: "100%", height: "100%", display: "flex", flexFlow: "column"}}>
<PageHeader
title={"Reverse Shell Receiver"}
subTitle={
<Space>
{/*<Button type={"primary"}>开启端口并监听</Button>*/}
<div>反弹 Shell 接收工具,可以在服务器上开启一个端口,进行监听,并进行交互。</div>
</Space>
}
></PageHeader>
<div style={{flex: 1,overflowY: "hidden"}}>
<AutoSpin spinning={loading || updatingAddrs}>
<Tabs
className="tabs-container"
tabBarStyle={{marginBottom: 8}}
type={"editable-card"}
onEdit={(key, action) => {
if (action === "add") {
createForm()
} else if (action === "remove") {
removeListenPort(`${key}`)
}
}}
>
{(addrs || []).length > 0 ? (
addrs.map((e) => {
return (
<Tabs.TabPane key={e} tab={`${e}`} closable={false}>
<ShellItem addr={e} removeListenPort={removeListenPort} />
</Tabs.TabPane>
)
})
) : (
<Tabs.TabPane closable={false} key={"empty"} tab={"开始监听端口"}>
<CreateShellReceiverForm
title={"开始监听:在服务器上开启一个端口"}
onCheck={(addr) => {
return true
}}
onCreated={(addr) => {
startListenPort(addr)
}}
/>
</Tabs.TabPane>
)}
</Tabs>
</AutoSpin>
</div>
</div>
}
Example #23
Source File: ReverseServerPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
ReverseServerPage: React.FC<ReverseServerPageProp> = (props) => {
const [bridge, setBridge] = useState(false);
const [bridgeLoading, setBridgeLoading] = useState(false);
const [bridgeIP, setBridgeIP] = useState<string>("");
const [bridgeAddr, setBridgeAddr] = useState("");
const [bridgeSecret, setBridgeSecret] = useState("");
const [params, setParams] = useState<StartFacadeServerParams>({
ConnectParam: {
Addr: "", Secret: "",
},
DNSLogLocalPort: 53,
DNSLogRemotePort: 0,
EnableDNSLogServer: false,
ExternalDomain: "",
FacadeRemotePort: 0,
LocalFacadeHost: "0.0.0.0",
LocalFacadePort: 4434,
Verify: false
});
const [token, _] = useState(randomString(40));
const [loading, setLoading] = useState(false);
const [logs, setLogs, getLogs] = useGetState<ReverseNotification[]>([]);
const [reverseToken, setReverseToken] = useState(randomString(20));
useEffect(() => {
const messages: ReverseNotification[] = [];
ipcRenderer.on(`${token}-data`, (_, data: ExecResult) => {
if (!data.IsMessage) {
return
}
try {
const message = ExtractExecResultMessage(data) as ExecResultLog;
if (message.level !== "facades-msg") {
info(JSON.stringify(message))
return
}
const obj = JSON.parse(message.data) as ReverseNotification;
obj.timestamp = message.timestamp;
messages.unshift(obj)
if (messages.length > 100) {
messages.pop()
}
} catch (e) {
}
})
ipcRenderer.on(`${token}-error`, (e: any, data: any) => {
if (data) {
failed(`error: ${JSON.stringify(data)}`)
}
})
ipcRenderer.on(`${token}-end`, () => {
setLoading(false)
})
const id = setInterval(() => {
if (getLogs().length !== messages.length || getLogs().length === 0) {
setLogs([...messages])
return
}
if (messages.length <= 0) {
return
}
if (messages.length > 0) {
if (messages[0].uuid !== getLogs()[0].uuid) {
setLogs([...messages])
}
}
}, 500)
return () => {
clearInterval(id)
ipcRenderer.invoke("cancel-StartFacades", token)
ipcRenderer.removeAllListeners(`${token}-end`);
ipcRenderer.removeAllListeners(`${token}-error`);
ipcRenderer.removeAllListeners(`${token}-data`);
}
}, [token])
const connectBridge = useMemoizedFn(() => {
setBridgeLoading(true)
ipcRenderer.invoke("GetTunnelServerExternalIP", {
Addr: bridgeAddr, Secret: bridgeSecret,
}).then((data: { IP: string }) => {
saveValue(BRIDGE_ADDR, bridgeAddr)
saveValue(BRIDGE_SECRET, bridgeSecret)
setBridgeIP(data.IP)
}).finally(() => {
setBridgeLoading(false)
})
});
// 设置 Bridge
useEffect(() => {
if (!bridgeAddr) {
getValue(BRIDGE_ADDR).then((data: string) => {
if (!!data) {
setBridgeAddr(`${data}`)
}
})
}
if (!bridgeSecret) {
getValue(BRIDGE_SECRET).then((data: string) => {
if (!!data) {
setBridgeSecret(`${data}`)
}
})
}
}, [])
useEffect(() => {
setBridgeLoading(true)
setTimeout(() => {
connectBridge()
}, 500)
}, [])
useEffect(() => {
if (!!bridgeIP) {
setBridge(false)
setParams({...params, ConnectParam: {Addr: bridgeAddr, Secret: bridgeSecret}})
}
}, [bridgeIP])
return <div>
<PageHeader
title={"反连服务器"}
subTitle={<Space>
{bridgeIP ? <Tag
onClose={() => {
setBridge(true)
setBridgeIP("")
}}
closable={true}
color={"green"}>公网 <Text strong={true} style={{color: "#229900"}} copyable={true}
>{bridgeIP}</Text></Tag> : <Form onSubmitCapture={e => e.preventDefault()}>
<SwitchItem size={"small"} label={"公网穿透服务"} value={bridge} setValue={setBridge}
formItemStyle={{marginBottom: 0}}/>
</Form>}
使用协议端口复用技术,同时在一个端口同时实现 HTTP / RMI / HTTPS 等协议的反连
</Space>}
extra={<>
<Space>
{loading && <Button
danger={true} type={"primary"}
onClick={() => {
ipcRenderer.invoke("cancel-StartFacades", token)
}}
>关闭反连</Button>}
</Space>
</>}
>
{bridge && <Card title={"公网配置"} size={"small"}>
<AutoSpin spinning={bridgeLoading}>
<Space direction={"vertical"}>
<Alert type={"success"} message={<Space>
<div>
在自己的服务器安装 yak 核心引擎,执行 <Text code={true} copyable={true}>yak bridge --secret
[your-pass]</Text> 启动
Yak Bridge 公网服务 <Divider type={"vertical"}/> <Text style={{color: "#999"}}>yak
version {`>=`} v1.0.11-sp9</Text>
</div>
</Space>}/>
<Form onSubmitCapture={e => {
e.preventDefault()
connectBridge()
}} layout={"inline"}>
<InputItem label={"公网 Bridge 地址"} value={bridgeAddr} setValue={setBridgeAddr}/>
<InputItem label={"密码"} type={"password"} value={bridgeSecret} setValue={setBridgeSecret}/>
<Form.Item colon={false} label={" "}>
<Button type="primary" htmlType="submit"> 连接公网服务器 </Button>
</Form.Item>
</Form>
</Space>
</AutoSpin>
</Card>}
{loading && <Alert
type={"info"}
message={<Space direction={"vertical"}>
<Space>
本地 RMI 反连 <CopyableField
text={`rmi://${bridgeIP && params.ConnectParam?.Addr ? bridgeIP : "127.0.0.1"}:${params.LocalFacadePort}/${reverseToken}`}/>
</Space>
<Space>
本地 HTTP 反连 <CopyableField
text={`http://${bridgeIP && params.ConnectParam?.Addr ? bridgeIP : "127.0.0.1"}:${params.LocalFacadePort}/${reverseToken}`}/>
</Space>
<Space>
本地 HTTPS 反连 <CopyableField
text={`https://${bridgeIP && params.ConnectParam?.Addr ? bridgeIP : "127.0.0.1"}:${params.LocalFacadePort}/${reverseToken}`}/>
</Space>
</Space>}>
</Alert>}
</PageHeader>
<Row>
<div style={{width: "100%"}}>
{loading ? <>
<ReverseNotificationTable loading={loading} logs={logs}/>
</> : <StartFacadeServerForm
params={params} setParams={setParams}
remoteMode={!!bridgeIP}
onSubmit={() => {
ipcRenderer.invoke("StartFacades", {
...params,
ConnectParam: (!!bridgeIP) ? params.ConnectParam : undefined
} as StartFacadeServerParams, token).then(() => {
info("开始启动反连服务器")
setLoading(true)
})
}}/>}
</div>
</Row>
</div>
}
Example #24
Source File: PayloadManager.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
PayloadManagerPage: React.FC<PayloadManagerPageProp> = (props) => {
const [groups, setGroups] = useState<string[]>([])
const [selected, setSelected] = useState("")
const [response, setResponse] = useState<QueryGeneralResponse<Payload>>()
const [params, setParams] = useState<QueryPayloadParams>({
Keyword: "",
Group: "",
Pagination: {Page: 1, Limit: 20, Order: "desc", OrderBy: "updated_at"}
})
const [selectedRows, setSelectedRows] = useState<Payload[]>([])
const [loading, setLoading] = useState(false)
const rowSelection = {
selectedRowKeys: selectedRows.map((item) => item.Id),
onChange: (selectedRowKeys, selectedRows) => setSelectedRows(selectedRows),
fixed: true
}
const pagination: PaginationSchema | undefined = response?.Pagination
const updateGroup = () => {
ipcRenderer
.invoke("GetAllPayloadGroup")
.then((data: { Groups: string[] }) => {
setGroups(data.Groups || [])
})
.catch((e: any) => {
failed(e?.details || "call GetAllPayloadGroup failed")
})
.finally()
}
const updateDict = (page?: number, limit?: number) => {
ipcRenderer
.invoke("QueryPayload", {
...params,
Group: selected,
Pagination: {
...params.Pagination,
Page: page || params.Pagination.Page,
Limit: limit || params.Pagination.Limit
}
} as QueryPayloadParams)
.then((data) => {
setResponse(data)
})
.catch((e: any) => {
failed(e?.details || "query payload failed")
})
}
const delDictContent = (id?: number) => {
let params: any = {}
if (id !== undefined) params.Id = +id
else params.Ids = selectedRows.map((item) => +item.Id)
ipcRenderer
.invoke("DeletePayloadByContent", params)
.then(() => {
setSelectedRows([])
updateDict()
})
.catch((e: any) => {
failed("batch delete failed")
})
}
useEffect(() => {
updateGroup()
}, [])
useEffect(() => {
if (!selected) {
return
}
updateDict()
}, [selected])
return (
<div className='payload-manager-page'>
<PageHeader
title={"Payload / 字典管理"}
subTitle={`增加 / 删除 / 管理字典,可以通过 fuzz 模块 {{x(字典名)}} 来渲染`}
/>
<Row gutter={18} style={{flexGrow: 1}}>
<Col span={8}>
<AutoCard
title={"选择 / 查看已有字典"}
size={"small"} loading={loading}
bordered={false}
bodyStyle={{overflow: "auto"}}
extra={
!props.readOnly && <Form size={"small"} onSubmitCapture={(e) => e.preventDefault()}>
<Form.Item style={{marginBottom: 0}} label={" "} colon={false}>
<Button.Group>
<Button
size={"small"}
onClick={() => {
let m = showModal({
title: "创建新的 Payload 组/字典",
content: (
<>
<CreatePayloadGroup
onLoading={() => {
setLoading(true)
}}
onLoadingFinished={() => {
setTimeout(() => setLoading(false), 300)
}}
Group={""}
onFinished={(e) => {
info("创建/修改 Payload 字典/组成功")
updateGroup()
m.destroy()
}}
/>
</>
),
width: "60%"
})
}}
>
新增 / 扩充字典
</Button>
<Button
size={"small"}
onClick={() => {
let m = showModal({
title: "上传新的 Payload 组/字典",
content: (
<>
<UploadPayloadGroup
Group={""}
onFinished={(e) => {
info("上传 Payload 字典/组成功")
updateGroup()
m.destroy()
}}
/>
</>
),
width: "60%",
maskClosable: false
})
}}
>
上传字典
</Button>
</Button.Group>
</Form.Item>
</Form>
}
>
<List<string>
style={{height: 200}}
dataSource={groups}
renderItem={(element, index) => {
return (
<List.Item id={index.toString()}>
<Button.Group style={{width: "100%", textAlign: "left"}}>
<Button
style={{width: "100%", textAlign: "left"}}
type={selected === element ? "primary" : undefined}
onClick={(e) => setSelected(element)}
>
字典分组名:{element}
</Button>
{props.selectorHandle && <Popconfirm title={"确定要使用该字典?"}
onConfirm={() => {
props.selectorHandle && props.selectorHandle(fuzzTag(element))
}}
>
<Button type={"primary"} icon={<ThunderboltFilled/>}/>
</Popconfirm>}
{!props.readOnly && <Popconfirm
title={"确定删除该字典吗?"}
onConfirm={(e) => {
ipcRenderer
.invoke("DeletePayloadByGroup", {
Group: element
})
.then(() => {
updateGroup()
if (selected === element) {
setSelected("")
setResponse(undefined)
}
})
.catch((e: any) => {
failed("Delete Payload By Group failed")
})
}}
>
<Button
danger={true}
icon={<DeleteOutlined/>}
type={selected === element ? "primary" : undefined}
/>
</Popconfirm>}
</Button.Group>
</List.Item>
)
}}
/>
</AutoCard>
</Col>
<Col span={16}>
<AutoCard
title={
<>
<span>字典内容</span>
{selectedRows.length > 0 && !props.readOnly && (
<Button size='small' type='link' danger onClick={() => delDictContent()}>
批量删除
</Button>
)}
</>
}
size={"small"}
bordered={false}
bodyStyle={{overflow: "auto", padding: 0}}
extra={
props.readOnly ?
(
!!props.selectorHandle ? <Button size={"small"} type={"primary"} onClick={() => {
props.selectorHandle && props.selectorHandle(`{{x(${selected})}}`)
}}>
选择该Fuzz标签
</Button> : <CopyToClipboard
text={`{{x(${selected})}}`}
onCopy={(text, ok) => {
if (ok) success("已复制到粘贴板")
}}
>
<Button size={"small"}>复制Fuzz标签</Button>
</CopyToClipboard>
) : <Form
size={"small"}
onSubmitCapture={(e) => {
e.preventDefault()
updateDict(1, 20)
}}
layout={"inline"}
style={{marginBottom: 0}}
>
<Form.Item style={{marginBottom: 0}}>
{selected && <Tag color={"geekblue"}>{selected}</Tag>}
</Form.Item>
<InputItem
label={"搜索"}
style={{marginBottom: 0}}
setValue={(Keyword) => setParams({...params, Keyword})}
value={params.Keyword}
/>
<Form.Item colon={false} label={" "} style={{marginBottom: 0}}>
<Button.Group>
<Button type='primary' htmlType='submit'>
{" "}
Search{" "}
</Button>
{!!props.selectorHandle ? <Button type={"primary"} onClick={() => {
props.selectorHandle && props.selectorHandle(`{{x(${selected})}}`)
}}>
选择该Fuzz标签
</Button> : <CopyToClipboard
text={`{{x(${selected})}}`}
onCopy={(text, ok) => {
if (ok) success("已复制到粘贴板")
}}
>
<Button>复制Fuzz标签</Button>
</CopyToClipboard>}
</Button.Group>
</Form.Item>
</Form>
}
>
<Table<Payload>
style={{height: 200}}
bordered={true}
size={"small"}
rowKey={(row) => row.Id}
rowSelection={rowSelection}
columns={[
{title: "所属字典", render: (e: Payload) => <Tag>{e.Group}</Tag>},
{
title: "字典内容",
render: (e: Payload) => (
<Text style={{width: 500}} ellipsis={{tooltip: true}}>
{e.Content}
</Text>
)
},
{
title: "操作",
fixed: "right",
render: (e: Payload) => (
<Button danger onClick={() => delDictContent(e.Id)}>
删除
</Button>
)
}
]}
onChange={(p) => {
updateDict(p.current, p.pageSize)
}}
pagination={{
size: "small",
pageSize: pagination?.Limit || 10,
total: response?.Total || 0,
showTotal: (i) => <Tag>共{i}条历史记录</Tag>
}}
dataSource={response?.Data}
/>
</AutoCard>
</Col>
</Row>
</div>
)
}
Example #25
Source File: MITMPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
MITMPage: React.FC<MITMPageProp> = (props) => {
const [status, setStatus] = useState<"idle" | "hijacked" | "hijacking">("idle");
const [error, setError] = useState("");
const [host, setHost] = useState("127.0.0.1");
const [port, setPort] = useState(8083);
const [downstreamProxy, setDownstreamProxy] = useState<string>();
const [loading, setLoading] = useState(false);
const [caCerts, setCaCerts] = useState<CaCertData>({
CaCerts: new Buffer(""), LocalFile: "",
});
const [enableInitialPlugin, setEnableInitialPlugin] = useState(false);
// 存储修改前和修改后的包!
const [currentPacketInfo, setCurrentPacketInfo] = useState<{
currentPacket: Uint8Array,
currentPacketId: number,
isHttp: boolean
}>({currentPacketId: 0, currentPacket: new Buffer([]), isHttp: true});
const {currentPacket, currentPacketId, isHttp} = currentPacketInfo;
const clearCurrentPacket = () => {
setCurrentPacketInfo({currentPacketId: 0, currentPacket: new Buffer([]), isHttp: true})
}
const [modifiedPacket, setModifiedPacket] = useState<Uint8Array>(new Buffer([]));
// 自动转发 与 劫持响应的自动设置
const [autoForward, setAutoForward, getAutoForward] = useGetState<"manual" | "log" | "passive">("log");
const isManual = autoForward === "manual";
const [hijackAllResponse, setHijackAllResponse] = useState(false); // 劫持所有请求
const [allowHijackCurrentResponse, setAllowHijackCurrentResponse] = useState(false); // 仅劫持一个请求
const [initialed, setInitialed] = useState(false);
const [forResponse, setForResponse] = useState(false);
const [haveSideCar, setHaveSideCar] = useState(true);
const [urlInfo, setUrlInfo] = useState("监听中...")
const [ipInfo, setIpInfo] = useState("")
// 设置初始化启动的插件
const [defaultPlugins, setDefaultPlugins] = useState<string[]>([]);
// yakit log message
const [logs, setLogs] = useState<ExecResultLog[]>([]);
const latestLogs = useLatest<ExecResultLog[]>(logs);
const [_, setLatestStatusHash, getLatestStatusHash] = useGetState("");
const [statusCards, setStatusCards] = useState<StatusCardProps[]>([])
// filter 过滤器
const [mitmFilter, setMITMFilter] = useState<MITMFilterSchema>();
// 内容替代模块
const [replacers, setReplacers] = useState<MITMContentReplacerRule[]>([]);
// mouse
const mouseState = useMouse();
// 操作系统类型
const [system, setSystem] = useState<string>()
useEffect(() => {
ipcRenderer.invoke('fetch-system-name').then((res) => setSystem(res))
}, [])
useEffect(() => {
// 设置 MITM 初始启动插件选项
getValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN).then(a => {
setEnableInitialPlugin(!!a)
})
}, [])
// 用于接受后端传回的信息
useEffect(() => {
setInitialed(false)
// 用于前端恢复状态
ipcRenderer.invoke("mitm-have-current-stream").then(data => {
const {haveStream, host, port} = data;
if (haveStream) {
setStatus("hijacking")
setHost(host);
setPort(port);
}
}).finally(() => {
recover()
setTimeout(() => setInitialed(true), 500)
})
// 用于启动 MITM 开始之后,接受开始成功之后的第一个消息,如果收到,则认为说 MITM 启动成功了
ipcRenderer.on("client-mitm-start-success", () => {
setStatus("hijacking")
setTimeout(() => {
setLoading(false)
}, 300)
})
// 用于 MITM 的 Message (YakitLog)
const messages: ExecResultLog[] = [];
const statusMap = new Map<string, StatusCardProps>();
let lastStatusHash = '';
ipcRenderer.on("client-mitm-message", (e, data: ExecResult) => {
let msg = ExtractExecResultMessage(data);
if (msg !== undefined) {
// logHandler.logs.push(msg as ExecResultLog)
// if (logHandler.logs.length > 25) {
// logHandler.logs.shift()
// }
const currentLog = msg as ExecResultLog;
if (currentLog.level === "feature-status-card-data") {
lastStatusHash = `${currentLog.timestamp}-${currentLog.data}`
try {
// 解析 Object
const obj = JSON.parse(currentLog.data)
const {id, data} = obj;
if (!data) {
statusMap.delete(`${id}`)
} else {
statusMap.set(`${id}`, {Data: data, Id: id, Timestamp: currentLog.timestamp})
}
} catch (e) {
}
return
}
messages.push(currentLog)
if (messages.length > 25) {
messages.shift()
}
}
})
// let currentFlow: HTTPFlow[] = []
ipcRenderer.on("client-mitm-history-update", (e: any, data: any) => {
// currentFlow.push(data.historyHTTPFlow as HTTPFlow)
//
// if (currentFlow.length > 30) {
// currentFlow = [...currentFlow.slice(0, 30)]
// }
// setFlows([...currentFlow])
})
ipcRenderer.on("client-mitm-error", (e, msg) => {
if (!msg) {
info("MITM 劫持服务器已关闭")
} else {
failed("MITM 劫持服务器异常或被关闭")
Modal.error({
mask: true, title: "启动 MITM 服务器 ERROR!",
content: <>{msg}</>
})
}
ipcRenderer.invoke("mitm-stop-call")
setError(`${msg}`)
setStatus("idle")
setTimeout(() => {
setLoading(false)
}, 300)
});
ipcRenderer.on("client-mitm-filter", (e, msg) => {
ipcRenderer
.invoke("get-value", DefaultMitmFilter)
.then((res: any) => {
if (res) {
const filter = {
includeSuffix: res.includeSuffix,
excludeMethod: res.excludeMethod,
excludeSuffix: res.excludeSuffix,
includeHostname: res.includeHostname,
excludeHostname: res.excludeHostname,
excludeContentTypes: res.excludeContentTypes,
}
setMITMFilter(filter)
ipcRenderer.invoke("mitm-filter", {
updateFilter: true, ...filter
})
} else {
setMITMFilter({
includeSuffix: msg.includeSuffix,
excludeMethod: msg.excludeMethod,
excludeSuffix: msg.excludeSuffix,
includeHostname: msg.includeHostname,
excludeHostname: msg.excludeHostname,
excludeContentTypes: msg.excludeContentTypes,
})
}
})
})
const updateLogs = () => {
if (latestLogs.current.length !== messages.length) {
setLogs([...messages])
return
}
if (latestLogs.current.length > 0 && messages.length > 0) {
if (latestLogs.current[0].data !== messages[0].data) {
setLogs([...messages])
return
}
}
if (getLatestStatusHash() !== lastStatusHash) {
setLatestStatusHash(lastStatusHash)
const tmpCurrent: StatusCardProps[] = [];
statusMap.forEach((value, key) => {
tmpCurrent.push(value)
})
setStatusCards(tmpCurrent.sort((a, b) => a.Id.localeCompare(b.Id)))
}
}
updateLogs()
let id = setInterval(() => {
updateLogs()
}, 1000)
return () => {
clearInterval(id);
ipcRenderer.removeAllListeners("client-mitm-error")
// ipcRenderer.invoke("mitm-close-stream")
}
}, [])
useEffect(() => {
if (hijackAllResponse && currentPacketId > 0) {
allowHijackedResponseByRequest(currentPacketId)
}
}, [hijackAllResponse, currentPacketId])
useEffect(() => {
ipcRenderer.on("client-mitm-hijacked", forwardHandler);
return () => {
ipcRenderer.removeAllListeners("client-mitm-hijacked")
}
}, [autoForward])
useEffect(() => {
ipcRenderer.invoke("mitm-auto-forward", !isManual).finally(() => {
console.info(`设置服务端自动转发:${!isManual}`)
})
}, [autoForward])
useEffect(() => {
ipcRenderer.on("client-mitm-content-replacer-update", (e, data: MITMResponse) => {
setReplacers(data?.replacers || [])
return
});
return () => {
ipcRenderer.removeAllListeners("client-mitm-content-replacer-update")
}
}, [])
useEffect(() => {
if (currentPacketId <= 0 && status === "hijacked") {
recover()
const id = setInterval(() => {
recover()
}, 500)
return () => {
clearInterval(id)
}
}
}, [currentPacketId])
useEffect(() => {
ipcRenderer.invoke("DownloadMITMCert", {}).then((data: CaCertData) => {
setCaCerts(data)
})
}, [])
const addr = `http://${host}:${port}`;
// 自动转发劫持,进行的操作
const forwardHandler = useMemoizedFn((e: any, msg: MITMResponse) => {
setMITMFilter({
includeSuffix: msg.includeSuffix,
excludeMethod: msg.excludeMethod,
excludeSuffix: msg.excludeSuffix,
includeHostname: msg.includeHostname,
excludeHostname: msg.excludeHostname,
excludeContentTypes: msg.excludeContentTypes,
})
// passive 模式是 mitm 插件模式
// 在这个模式下,应该直接转发,不应该操作数据包
// if (passiveMode) {
// if (msg.forResponse) {
// forwardResponse(msg.responseId || 0)
// } else {
// forwardRequest(msg.id || 0)
// }
// return
// }
if (msg.forResponse) {
if (!msg.response || !msg.responseId) {
failed("BUG: MITM 错误,未能获取到正确的 Response 或 Response ID")
return
}
if (!isManual) {
forwardResponse(msg.responseId || 0)
if (!!currentPacket) {
clearCurrentPacket()
}
} else {
setForResponse(true)
setStatus("hijacked")
setCurrentPacketInfo({
currentPacket: msg.response,
currentPacketId: msg.responseId,
isHttp: msg.isHttps
})
// setCurrentPacket(new Buffer(msg.response).toString("utf8"))
// setCurrentPacketId(msg.responseId || 0);
}
} else {
if (msg.request) {
if (!isManual) {
forwardRequest(msg.id)
if (!!currentPacket) {
clearCurrentPacket()
}
// setCurrentPacket(String.fromCharCode.apply(null, msg.request))
} else {
setStatus("hijacked")
setForResponse(false)
// setCurrentPacket(msg.request)
// setCurrentPacketId(msg.id)
setCurrentPacketInfo({currentPacket: msg.request, currentPacketId: msg.id, isHttp: msg.isHttps})
setUrlInfo(msg.url)
ipcRenderer.invoke("fetch-url-ip", msg.url.split('://')[1].split('/')[0]).then((res) => {
setIpInfo(res)
})
}
}
}
})
// 这个 Forward 主要用来转发修改后的内容,同时可以转发请求和响应
const forward = useMemoizedFn(() => {
// ID 不存在
if (!currentPacketId) {
return
}
setLoading(true);
setStatus("hijacking");
setAllowHijackCurrentResponse(false)
setForResponse(false)
if (forResponse) {
ipcRenderer.invoke("mitm-forward-modified-response", modifiedPacket, currentPacketId).finally(() => {
clearCurrentPacket()
setTimeout(() => setLoading(false))
})
} else {
ipcRenderer.invoke("mitm-forward-modified-request", modifiedPacket, currentPacketId).finally(() => {
clearCurrentPacket()
setTimeout(() => setLoading(false))
})
}
})
const recover = useMemoizedFn(() => {
ipcRenderer.invoke("mitm-recover").then(() => {
// success("恢复 MITM 会话成功")
})
})
const start = useMemoizedFn(() => {
setLoading(true)
setError("")
ipcRenderer.invoke("mitm-start-call", host, port, downstreamProxy).catch((e: any) => {
notification["error"]({message: `启动中间人劫持失败:${e}`})
})
})
const stop = useMemoizedFn(() => {
setLoading(true)
ipcRenderer.invoke("mitm-stop-call").then(() => {
setStatus("idle")
}).catch((e: any) => {
notification["error"]({message: `停止中间人劫持失败:${e}`})
}).finally(() => setTimeout(() => {
setLoading(false)
}, 300))
})
const hijacking = useMemoizedFn(() => {
// setCurrentPacket(new Buffer([]));
clearCurrentPacket()
setLoading(true);
setStatus("hijacking");
})
function getCurrentId() {
return currentPacketId
}
const downloadCert = useMemoizedFn(() => {
return <Tooltip title={'请先下载 SSL/TLS 证书'}>
<Button
type={"link"}
style={{padding: '4px 6px'}}
onClick={() => {
const text = `wget -e use_proxy=yes -e http_proxy=${addr} http://download-mitm-cert.yaklang.io -O yakit-mitm-cert.pem`
showModal({
title: "下载 SSL/TLS 证书以劫持 HTTPS",
width: "50%",
content: <Space direction={"vertical"} style={{width: "100%"}}>
<AutoCard
title={"证书配置"}
extra={<Button
type={"link"}
onClick={() => {
saveABSFileToOpen("yakit证书.crt.pem", caCerts.CaCerts)
// openABSFileLocated(caCerts.LocalFile)
}}
>
下载到本地并打开
</Button>} size={"small"} bodyStyle={{padding: 0}}>
<div style={{height: 360}}>
<YakEditor bytes={true}
valueBytes={caCerts.CaCerts}
/>
</div>
</AutoCard>
<Alert message={<Space>
在设置代理后访问:<CopyableField text={"http://download-mitm-cert.yaklang.io"}/> 可自动下载证书
</Space>}/>
</Space>
})
}}
>HTTPS 证书配置</Button>
</Tooltip>
})
const contentReplacer = useMemoizedFn(() => {
return <Button
type={"link"} style={{padding: `4px 6px`}}
onClick={() => {
let m = showDrawer({
placement: "top", height: "50%",
content: (
<MITMContentReplacer
rules={replacers}
onSaved={rules => {
setReplacers(rules)
m.destroy()
}}/>
),
maskClosable: false,
})
}}
>
匹配/标记/替换
</Button>
})
const setFilter = useMemoizedFn(() => {
return <Button type={"link"} style={{padding: '4px 6px'}}
onClick={() => {
let m = showDrawer({
placement: "top", height: "50%",
content: <>
<MITMFilters
filter={mitmFilter}
onFinished={(filter) => {
setMITMFilter({...filter})
m.destroy()
}}/>
</>
});
}}
>过滤器</Button>
})
const handleAutoForward = useMemoizedFn((e: "manual" | "log" | "passive") => {
if (!isManual) {
info("切换为劫持自动放行模式(仅记录)")
setHijackAllResponse(false)
} else {
info("切换为手动放行模式(可修改劫持)")
}
setAutoForward(e)
if (currentPacket && currentPacketId) {
forward()
}
})
const execFuzzer = useMemoizedFn((value: string) => {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {isHttps: currentPacketInfo.isHttp, request: value}
})
})
const execPlugin = useMemoizedFn((value: string) => {
ipcRenderer.invoke("send-to-packet-hack", {
request: currentPacketInfo.currentPacket,
ishttps: currentPacketInfo.isHttp
})
})
const shiftAutoForwardHotkey = useHotkeys('ctrl+t', () => {
handleAutoForward(isManual ? "manual" : "log")
}, [autoForward])
if (!initialed) {
return <div style={{textAlign: "center", paddingTop: 120}}>
<Spin spinning={true} tip={"正在初始化 MITM"}/>
</div>
}
return <div style={{height: "100%", width: "100%"}}>
{(() => {
switch (status) {
case "idle":
return <Spin spinning={loading}>
<Form
style={{marginTop: 40}}
onSubmitCapture={e => {
e.preventDefault()
start()
if (enableInitialPlugin) {
enableMITMPluginMode(defaultPlugins).then(() => {
info("被动扫描插件模式已启动")
})
}
}}
layout={"horizontal"} labelCol={{span: 7}}
wrapperCol={{span: 13}}
>
<Item label={"劫持代理监听主机"}>
<Input value={host} onChange={e => setHost(e.target.value)}/>
</Item>
<Item label={"劫持代理监听端口"}>
<InputNumber value={port} onChange={e => setPort(e)}/>
</Item>
{/*<SwitchItem label={"启动 MITM 插件"} size={"small"} setValue={e => {*/}
{/* setEnableInitialPlugin(e)*/}
{/* if (e) {*/}
{/* saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "true")*/}
{/* } else {*/}
{/* saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "")*/}
{/* }*/}
{/*}} value={enableInitialPlugin}/>*/}
<Item label={"选择插件"} colon={true}>
<div style={{height: 200, maxWidth: 420}}>
<SimplePluginList
disabled={!enableInitialPlugin}
bordered={true}
initialSelected={defaultPlugins}
onSelected={(list: string[]) => {
setDefaultPlugins(list)
}} pluginTypes={"mitm,port-scan"}
verbose={<div>MITM 与 端口扫描插件</div>}/>
</div>
</Item>
<Item label={"下游代理"} help={"为经过该 MITM 代理的请求再设置一个代理,通常用于访问中国大陆无法访问的网站或访问特殊网络/内网,也可用于接入被动扫描"}>
<Input value={downstreamProxy} onChange={e => setDownstreamProxy(e.target.value)}/>
</Item>
<Item label={"内容规则"} help={"使用规则进行匹配、替换、标记、染色,同时配置生效位置"}>
<Space>
<Button
onClick={() => {
let m = showDrawer({
placement: "top", height: "50%",
content: (
<MITMContentReplacerViewer/>
),
maskClosable: false,
})
}}
>已有规则</Button>
<Button type={"link"} onClick={() => {
const m = showModal({
title: "从 JSON 中导入",
width: "60%",
content: (
<>
<MITMContentReplacerImport onClosed={() => {
m.destroy()
}}/>
</>
)
})
}}>从 JSON 导入</Button>
<Button type={"link"} onClick={() => {
showModal({
title: "导出配置 JSON",
width: "50%",
content: (
<>
<MITMContentReplacerExport/>
</>
)
})
}}>导出为 JSON</Button>
</Space>
</Item>
<Item label={" "} colon={false}>
<Space>
<Button type={"primary"} htmlType={"submit"}>
劫持启动
</Button>
<Divider type={"vertical"}/>
<Checkbox
checked={enableInitialPlugin}
onChange={node => {
const e = node.target.checked;
setEnableInitialPlugin(e)
if (e) {
saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "true")
} else {
saveValue(CONST_DEFAULT_ENABLE_INITIAL_PLUGIN, "")
}
}}
>
插件自动加载
</Checkbox>
</Space>
</Item>
</Form>
</Spin>
case "hijacking":
case "hijacked":
return <div id={"mitm-hijacking-container"} ref={shiftAutoForwardHotkey as Ref<any>} tabIndex={-1}
style={{marginLeft: 12, marginRight: 12, height: "100%"}}>
<Row gutter={14} style={{height: "100%"}}>
<Col span={haveSideCar ? 24 : 24}
style={{display: "flex", flexDirection: "column", height: "100%"}}>
<PageHeader
className="mitm-header-title"
title={'劫持 HTTP Request'} subTitle={`http://${host}:${port}`}
style={{marginRight: 0, paddingRight: 0, paddingTop: 0, paddingBottom: 8}}
extra={
<Space>
<ChromeLauncherButton host={host} port={port}/>
{contentReplacer()}
{setFilter()}
{downloadCert()}
<Button danger={true} type={"link"}
onClick={() => {
stop()
setUrlInfo("监听中...")
setIpInfo("")
}} icon={<PoweroffOutlined/>}
/>
</Space>}>
<Row>
<Col span={12}>
<div style={{width: "100%", textAlign: "left"}}>
<Space>
<Button
type={"primary"}
disabled={status === "hijacking"}
onClick={() => {
forward()
}}>提交数据</Button>
<Button
disabled={status === "hijacking"}
danger={true}
onClick={() => {
hijacking()
if (forResponse) {
dropResponse(currentPacketId).finally(() => {
setTimeout(() => {
setLoading(false)
}, 300)
})
} else {
dropRequest(currentPacketId).finally(() => {
setTimeout(() => setLoading(false), 300)
})
}
setUrlInfo("监听中...")
setIpInfo("")
}}>丢弃请求</Button>
{
(!forResponse && !!currentPacket) && // 劫持到的请求有内容
status === "hijacked" && // 劫持到的状态是 hijacked
!hijackAllResponse && // 如果已经设置了劫持所有请求,就不展示了
<Button
disabled={allowHijackCurrentResponse}
type={allowHijackCurrentResponse ? "primary" : "default"}
onClick={() => {
if (!allowHijackCurrentResponse) {
allowHijackedResponseByRequest(currentPacketId)
setAllowHijackCurrentResponse(true)
} else {
setAllowHijackCurrentResponse(false)
}
}}>
劫持响应 {
allowHijackCurrentResponse &&
<CheckOutlined/>
}
</Button>}
</Space>
</div>
</Col>
<Col span={12}>
<div style={{width: "100%", textAlign: "right"}}>
<Space>
{isManual && <div>
<span style={{marginRight: 4}}>劫持响应:</span>
<Checkbox checked={hijackAllResponse} onClick={e => {
if (!hijackAllResponse) {
info("劫持所有响应内容")
} else {
info("仅劫持请求")
}
setHijackAllResponse(!hijackAllResponse)
}}/>
</div>}
<SelectOne
data={[
{text: "手动劫持", value: "manual"},
{text: "自动放行", value: "log"},
{text: "被动日志", value: "passive"},
]}
value={autoForward}
formItemStyle={{marginBottom: 0}}
setValue={(e) => {
ipcRenderer.invoke("mitm-filter", {updateFilter: true, ...mitmFilter})
handleAutoForward(e)
}}
/>
</Space>
</div>
</Col>
</Row>
<Row>
<Col span={12}>
<div style={{
width: "100%", textAlign: "left", height: '100%',
display: 'flex'
}}>
{!isManual &&
<Text style={{alignSelf: 'center'}}>
{`目标:自动放行中...`}</Text>}
{autoForward === "manual" &&
<>
<Text title={urlInfo} ellipsis={true} style={{
alignSelf: 'center',
maxWidth: 300
}}>{status === 'hijacking' ? '目标:监听中...' : `目标:${urlInfo}`}</Text>
{ipInfo && status !== 'hijacking' &&
<Tag
color='green'
title={ipInfo}
style={{
marginLeft: 5,
alignSelf: "center",
maxWidth: 140,
cursor: "pointer"
}}
>
{`${ipInfo}`}
<CopyToClipboard
text={`${ipInfo}`}
onCopy={(text, ok) => {
if (ok) success("已复制到粘贴板")
}}
>
<CopyOutlined style={{marginLeft: 5}}/>
</CopyToClipboard>
</Tag>
}
</>
}
</div>
</Col>
<Col span={12}>
<div style={{width: "100%", textAlign: "right"}}>
<Button
type={"link"} onClick={() => recover()}
icon={<ReloadOutlined/>}
>恢复请求</Button>
</div>
</Col>
</Row>
</PageHeader>
<div style={{flex: 1, overflowY: 'hidden'}}>
{/*<Spin wrapperClassName={"mitm-loading-spin"} spinning={status === "hijacking"}>*/}
<div style={{height: "100%"}}>
<ResizeBox
isVer={false}
firstNode={(
<MITMPluginList
proxy={`http://${host}:${port}`}
downloadCertNode={downloadCert}
setFilterNode={setFilter}
onExit={() => {
stop()
}}
onSubmitScriptContent={e => {
ipcRenderer.invoke("mitm-exec-script-content", e)
}}
onSubmitYakScriptId={(id: number, params: YakExecutorParam[]) => {
info(`加载 MITM 插件[${id}]`)
ipcRenderer.invoke("mitm-exec-script-by-id", id, params)
}}
/>
// <MITMPluginOperator />
)}
firstMinSize={"330px"}
secondMinSize={"340px"}
firstRatio={"330px"}
secondNode={(
<AutoCard
style={{margin: 0, padding: 0}}
bodyStyle={{margin: 0, padding: 0, overflowY: "hidden"}}
>
{autoForward === "log" && (
<MITMPluginCard
onSubmitScriptContent={(e) => {
ipcRenderer.invoke("mitm-exec-script-content", e)
}}
onSubmitYakScriptId={(
id: number,
params: YakExecutorParam[]
) => {
info(`加载 MITM 插件[${id}]`)
ipcRenderer.invoke("mitm-exec-script-by-id", id, params)
}}
/>
)}
{autoForward === "manual" && (
<HTTPPacketEditor
originValue={currentPacket}
noHeader={true}
bordered={false}
onChange={setModifiedPacket}
noPacketModifier={true}
readOnly={status === "hijacking"}
refreshTrigger={
(forResponse ? `rsp` : `req`) + `${currentPacketId}`
}
actions={[
// {
// id: "send-to-scan-packet", label: "发送到数据包扫描器",
// run: e => {
// // console.info(mouseState)
// scanPacket(mouseState, false, "GET / HTTP/1.1\r\nHost: www.baidu.com", "")
// }, contextMenuGroupId: "Scanners",
// },
...(forResponse
? [
{
id: "trigger-auto-hijacked",
label: "切换为自动劫持模式",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_T
],
run: () => {
handleAutoForward(getAutoForward() === "manual" ? "log" : "manual")
},
contextMenuGroupId: "Actions"
},
{
id: "forward-response",
label: "放行该 HTTP Response",
run: function () {
forward()
// hijacking()
// forwardResponse(getCurrentId()).finally(() => {
// setTimeout(() => setLoading(false), 300)
// })
},
contextMenuGroupId: "Actions"
},
{
id: "drop-response",
label: "丢弃该 HTTP Response",
run: function () {
hijacking()
dropResponse(getCurrentId()).finally(
() => {
setTimeout(
() => setLoading(false),
300
)
}
)
},
contextMenuGroupId: "Actions"
}
]
: [
{
id: "trigger-auto-hijacked",
label: "切换为自动劫持模式",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_T
],
run: () => {
handleAutoForward(getAutoForward() === "manual" ? "log" : "manual")
},
contextMenuGroupId: "Actions"
},
{
id: "send-to-fuzzer",
label: "发送到 Web Fuzzer",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_R
],
run: function (StandaloneEditor: any) {
execFuzzer(StandaloneEditor.getModel().getValue())
},
contextMenuGroupId: "Actions"
},
{
id: "send-to-plugin",
label: "发送到 数据包扫描",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_E
],
run: function (StandaloneEditor: any) {
if (!StandaloneEditor.getModel().getValue()) return
execPlugin(StandaloneEditor.getModel().getValue())
},
contextMenuGroupId: "Actions"
},
{
id: "forward-response",
label: "放行该 HTTP Request",
keybindings: [
monaco.KeyMod.Shift |
(system === "Darwin" ? monaco.KeyMod.WinCtrl : monaco.KeyMod.CtrlCmd) |
monaco.KeyCode.KEY_F
],
run: function () {
forward()
// hijacking()
// forwardRequest(getCurrentId()).finally(() => {
// setTimeout(() => setLoading(false), 300)
// })
},
contextMenuGroupId: "Actions"
},
{
id: "drop-response",
label: "丢弃该 HTTP Request",
run: function () {
hijacking()
dropRequest(getCurrentId()).finally(
() => {
setTimeout(
() => setLoading(false),
300
)
}
)
},
contextMenuGroupId: "Actions"
},
{
id: "hijack-current-response",
label: "劫持该 Request 对应的响应",
run: function () {
allowHijackedResponseByRequest(
getCurrentId()
)
},
contextMenuGroupId: "Actions"
}
])
]}
/>
)}
{autoForward === "passive" && (
<MITMPluginLogViewer
messages={logs} status={statusCards}
/>
)}
</AutoCard>
)}
/>
</div>
{/*</Spin>*/}
</div>
</Col>
</Row>
</div>
default:
return <div/>
}
})()}
</div>
}
Example #26
Source File: YakBatchExecutorLegacy.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
YakBatchExecutorLegacy: React.FC<YakBatchExecutorProp> = (props) => {
const [params, setParams] = useState<ExecBatchYakScriptParams>({
Concurrent: 5,
Keyword: props.keyword,
DisableNucleiWorkflow: true,
Limit: 100,
Target: "",
TotalTimeoutSeconds: 180,
Type: "nuclei"
});
const [totalLoading, setTotalLoading] = useState(true);
const [tasks, setTasks] = useState<ExecBatchYakScriptTask[]>([]);
const [error, setError] = useState("");
const [token, setToken] = useState("");
const [executing, setExecuting] = useState(false);
useEffect(() => {
setTotalLoading(true)
setTimeout(() => setTotalLoading(false), 500)
const token = randomString(40);
setToken(token);
setTasks([]);
setParams({...params, Keyword: props.keyword, Target: ""})
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, 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])
if (totalLoading) {
return <div style={{textAlign: "center", width: "100%", marginTop: 100}}>
<Spin>正在加载专用漏洞库</Spin>
</div>
}
return <div>
<PageHeader
title={`漏洞与风险监测专题:${props.verbose ? props.verbose : props.keyword}`}
style={{width: "100%"}}
>
<div style={{textAlign: "center", width: "100%"}}>
<Form style={{
textAlign: "center",
}} onSubmitCapture={e => {
e.preventDefault()
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)
}}>
<Space direction={"vertical"}>
<Space>
<span>输入想要检测的目标:</span>
<Form.Item
style={{marginBottom: 0}}
>
<Input
style={{width: 600}}
value={params.Target}
onChange={e => {
setParams({...params, Target: e.target.value})
}}
suffix={<Space>
{!executing ? <Button style={{width: 120}} type="primary"
htmlType="submit"
> 开始检测 </Button> : <Popconfirm
title={"确定要停止该漏洞检测?"}
onConfirm={e => {
ipcRenderer.invoke("cancel-exec-batch-yak-script", token)
}}
>
<Button style={{width: 120}} danger={true}> 强制停止 </Button>
</Popconfirm>}
</Space>}
/>
</Form.Item>
<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={"限制模块数量"}
setValue={Limit => setParams({...params, Limit})}
defaultValue={params.Limit}
/>
<InputInteger
label={"总超时时间/s"}
setValue={TotalTimeoutSeconds => setParams({
...params,
TotalTimeoutSeconds
})}
defaultValue={params.TotalTimeoutSeconds}
/>
</Form>
</>
})
}}>额外参数</Button>
</Space>
<div style={{width: "100%", textAlign: "right"}}>
<Space direction={"vertical"}>
<Space>
<Tag>并发/线程: {params.Concurrent}</Tag>
<Tag>总超时: {params.TotalTimeoutSeconds} sec</Tag>
<Tag>限制模块最大数: {params.Limit}</Tag>
<p style={{color: "#999", marginBottom: 0}}>
可接受输入为:URL / IP / 域名 / 主机:端口
</p>
<div style={{width: 80}}/>
</Space>
</Space>
</div>
</Space>
</Form>
</div>
</PageHeader>
<Divider/>
<Row gutter={12} style={{height: "100%"}}>
<div
style={{
width: "100%", textAlign: "center",
marginLeft: 20, marginRight: 20,
marginBottom: 100,
}}
>
<Table<ExecBatchYakScriptTask>
pagination={false}
dataSource={tasks}
bordered={true} size={"small"}
rowKey={(row) => {
return row.Id
}}
columns={[
{
title: "模块名称",
width: 400,
render: (i: ExecBatchYakScriptTask) => <div style={{overflow: "auto", width: 400}}>
<Text
ellipsis={{tooltip: true}} copyable={true} style={{width: 300}}
>{i.Id}</Text>
</div>
},
{
title: "模块状态", width: 150,
render: (i: ExecBatchYakScriptTask) => StatusToVerboseTag(i.Status)
},
{
title: "执行过程预览", render: (i: ExecBatchYakScriptTask) => {
return <ExecResultsViewer results={i.Results} oneLine={true}/>
}
},
{
title: "操作",
render: (i: ExecBatchYakScriptTask) => <div>
<Space>
<Button
type={"primary"} size={"small"}
onClick={e => {
if (!i.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `单体模块测试: ${i.PoC.ScriptName}`,
width: "75%",
content: <>
<YakScriptOperator script={i.PoC}/>
</>
})
}}
>单独检测</Button>
<Button
size={"small"}
onClick={e => {
if (!i.PoC) {
Modal.error({title: "没有模块信息"})
return
}
showModal({
title: `源码: ${i.PoC.ScriptName}`,
width: "75%",
content: <>
<div style={{height: 400}}>
<YakEditor
readOnly={true} type={"yaml"} value={i.PoC.Content}
/>
</div>
</>
})
}}
>源码</Button>
<Button type={"primary"} size={"small"} disabled={true}>待开发...</Button>
</Space>
</div>
},
]}
>
</Table>
</div>
</Row>
</div>
}
Example #27
Source File: YakScriptManager.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
YakScriptManagerPage: React.FC<YakScriptManagerPageProp> = (props) => {
const [response, setResponse] = useState<QueryYakScriptsResponse>({
Data: [], Pagination: {
Limit: props.limit || 15, Page: 1,
Order: "desc", OrderBy: "updated_at"
},
Total: 0
});
const [selectedScript, setSelectedScript] = useState<YakScript>();
const {Data, Pagination, Total} = response;
const [params, setParams] = useState<QueryYakScriptRequest>({
Pagination: {
Limit: props.limit || 15, Page: 1,
Order: "desc", OrderBy: "updated_at"
}, Type: props.type || undefined,
Keyword: props.keyword || "", IsHistory: false
});
const [loading, setLoading] = useState(false);
const isMainPage = !props.onLoadYakScript
const update = (page?: number, limit?: number) => {
const newParams = {
...params
}
if (page) newParams.Pagination.Page = page;
if (limit) newParams.Pagination.Limit = limit;
setLoading(true)
ipcRenderer.invoke("QueryYakScript", newParams).then((data: QueryYakScriptsResponse) => {
setResponse(data)
}).finally(() => setTimeout(() => setLoading(false), 300))
};
useEffect(() => {
update(1)
}, [params.Type])
const renderTable = () => {
return <Space direction={"vertical"} style={{width: "100%"}}>
{!props.onlyViewer && <Form onSubmitCapture={e => {
e.preventDefault()
update(1)
}} layout={"inline"}>
<InputItem
label={"搜索关键字"}
setValue={Keyword => setParams({...params, Keyword})}
value={params.Keyword}
/>
<Form.Item colon={false}>
<Button.Group>
<Button type="primary" htmlType="submit">搜索</Button>
<Button onClick={e => {
if (!params.Keyword) {
Modal.error({title: "关键字为空无法生成批量扫描能力"});
return
}
showDrawer({
title: "", width: "93%", mask: false, keyboard: false,
content: <>
<YakBatchExecutorLegacy
keyword={params.Keyword || ""}
verbose={`自定义搜索关键字: ${params.Keyword}`}
/>
</>,
})
}}>批量</Button>
</Button.Group>
</Form.Item>
</Form>}
<Table<YakScript>
size={"small"}
dataSource={Data}
rowKey={"Id"}
loading={loading} bordered={true}
scroll={{y: 750}}
expandable={{
expandedRowRender: (i: YakScript) => {
return <div style={{height: 400}}>
<YakEditor
type={"yak"} readOnly={true} value={i.Content}
/>
</div>
},
}}
onRow={isMainPage ? r => {
return {
onClick: () => {
setSelectedScript(r)
}
}
} : undefined}
pagination={{
size: "small",
pageSize: Pagination?.Limit || 10,
total: Total,
showTotal: (i) => <Tag>共{i}条历史记录</Tag>,
// onChange(page: number, limit?: number): any {
// update(page, limit)
// },
}}
onChange={(p) => {
update(p.current, p.pageSize)
}}
columns={isMainPage ? [
{
title: "模块名称", width: 300,
render: (i: YakScript) => <Tag><Text
style={{maxWidth: 260}} copyable={true}
ellipsis={{tooltip: true}}>
{i.ScriptName}
</Text></Tag>
},
// {
// title: "描述", render: (i: YakScript) => <Text
// style={{maxWidth: 300}}
// ellipsis={{tooltip: true}}
// >{i.Help}</Text>, width: 330,
// },
{
title: "操作", fixed: "right", width: 135, render: (i: YakScript) => <Space>
<Button size={"small"} onClick={e => {
let m = showDrawer({
title: "修改当前 Yak 模块", width: "90%", keyboard: false,
content: <>
<YakScriptCreatorForm
modified={i} onChanged={i => update()}
onCreated={(created) => {
m.destroy()
}}
/>
</>
})
}}>修改</Button>
<Popconfirm
title={"确认想要删除该模块?"}
onConfirm={e => {
ipcRenderer.invoke("delete-yak-script", i.Id)
setLoading(true)
setTimeout(() => update(1), 1000)
}}
>
<Button size={"small"} danger={true}>删除</Button>
</Popconfirm>
</Space>
},
] : [
{
title: "模块名称", fixed: "left",
render: (i: YakScript) => <Tag><Text style={{maxWidth: 200}} copyable={true}
ellipsis={{tooltip: true}}>
{i.ScriptName}
</Text></Tag>
},
{
title: "描述", render: (i: YakScript) => <Text
style={{maxWidth: 200}}
ellipsis={{tooltip: true}}
>{i.Help}</Text>
},
{
title: "操作", fixed: "right", render: (i: YakScript) => <Space>
{props.onLoadYakScript && <Button size={"small"} onClick={e => {
props.onLoadYakScript && props.onLoadYakScript(i)
}} type={"primary"}>加载</Button>}
</Space>
},
]}
/>
</Space>
}
return <div>
{!props.onlyViewer && <PageHeader
title={"Yak 模块管理器"}
subTitle={<Space>
<Button
icon={<ReloadOutlined/>}
type={"link"}
onClick={() => {
update()
}}
/>
{props.type ? undefined : <Form layout={"inline"}>
<ManySelectOne
formItemStyle={{marginBottom: 0, width: 200}}
label={"模块类型"}
data={[
{value: "yak", text: "Yak 原生模块"},
{value: "nuclei", text: "nuclei Yaml模块"},
{value: undefined, text: "全部"},
]}
setValue={Type => setParams({...params, Type})} value={params.Type}
/>
</Form>}
<div>
你可以在这里管理 / 添加你的 Yak 模块
</div>
</Space>}
extra={[
isMainPage ? <Popconfirm
title={<>
确定要加载本地 yaml(nuclei) poc 吗?<br/>
可通过 <Text mark={true} copyable={true}>yak update-nuclei-poc</Text> 一键更新已知 PoC
</>}
onConfirm={() => {
ipcRenderer.invoke("update-nuclei-poc")
}}
>
<Button>加载 PoC(nuclei)</Button>
</Popconfirm> : undefined,
<Button type={"primary"} onClick={e => {
let m = showDrawer({
title: "创建新的 Yakit 模块",
keyboard: false,
width: "95%",
content: <>
<YakScriptCreatorForm onCreated={() => {
m.destroy()
}} onChanged={e => update(1)}/>
</>
})
}}>创建新脚本</Button>
]}
/>}
{(isMainPage && !props.onlyViewer) ? <Row gutter={12}>
<Col span={8}>
{renderTable()}
</Col>
<Col span={16}>
{selectedScript ? <YakScriptOperator script={selectedScript}/> : <Empty/>}
</Col>
</Row> : <Row>
<Col span={24}>
{renderTable()}
</Col>
</Row>}
</div>
}
Example #28
Source File: StringFuzzer.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
StringFuzzer: React.FC<StringFuzzerProp> = (props) => {
const [template, setTemplate] = useState("");
const [loading, setLoading] = useState(false);
const [random, _] = useState(randomString(20));
const token = `client-string-fuzzer-${random}`;
// 高级配置选项
const [advanced, setAdvanced] = useState(props.advanced ? "advanced" : "ordinary");
const [buildTemp, setBuildTemp] = useState<string>();
const [encodeTemp, setEncodeTemp] = useState<string>();
const getCurrentEncodeTemplate = () => {
const res = encodeOperators.filter(i => i.name === encodeTemp);
if (res.length > 0 && res[0]) {
return res[0];
}
return false
};
const getCurrentBuildTemplate = () => {
const res = fuzzOperators.filter(i => i.name === buildTemp);
if (res.length > 0 && res[0]) {
return res[0];
}
return false
};
useEffect(() => {
const temp = getCurrentEncodeTemplate();
if (temp && template && !template.includes(`{{${temp.tag}(`)) {
setEncodeTemp(undefined)
}
if (!template) {
setBuildTemp(undefined)
}
}, [template])
useEffect(() => {
if (!random) {
return
}
ipcRenderer.on(token, (e, data: { error: any, data: { Results: string[] } }) => {
if (data.error) {
failed(((data.error?.details) || data.error?.detail) || "未知错误")
return
}
const {Results} = data.data;
let m = showDrawer({
content: <div style={{height: "100%", overflow: "auto"}}>
<PageHeader title={"Payload 测试结果"}>
</PageHeader>
<div style={{height: "80%"}}>
<List
size={"small"} dataSource={Results}
pagination={{
pageSize: 15, showTotal: r => {
return <Tag>总量:{r}</Tag>
}, size: "small",
}} bordered={true}
renderItem={e => {
return <List.Item>
<Text
copyable={true}
// ellipsis={{tooltip: true}}
// style={{width: 300}}
>{e}</Text>
</List.Item>
}}>
</List>
</div>
</div>,
width: "35%", mask: true,
})
setLoading(false)
})
return () => {
ipcRenderer.removeAllListeners(token)
}
}, [random])
const removeEncodeTemplateTag = () => {
const res = encodeOperators.filter(i => i.name === encodeTemp);
if (res.length > 0 && res[0] && template) {
const item = res[0];
let newTemp = template.replace(`{{${item.tag}(`, "");
const index = newTemp.lastIndexOf(")}}");
if (index >= 0 && newTemp.length >= (index + 3)) {
newTemp = newTemp.substr(0, index) + newTemp.substr(index + 3)
setTemplate(newTemp)
} else {
warn("移除编码标签失败(标签结构已被破坏,请重构)")
}
} else {
warn("移除编码标签失败: 找不到标签内容(标签类型)")
}
setEncodeTemp(undefined)
};
const removeBuildTemplateTag = () => {
const t = getCurrentBuildTemplate()
if (t && template) {
if (!t.tag) {
warn("不支持移除带参数的基础标签");
setBuildTemp(undefined)
return
}
let newTemp = template.replace(`{{${t.tag}`, "");
const index = newTemp.lastIndexOf("}}");
if (index >= 0 && newTemp.length >= (index + 2)) {
newTemp = newTemp.substr(0, index) + newTemp.substr(index + 2)
setTemplate(newTemp)
} else {
warn("移除基础标签失败(标签结构已被破坏,请重构)")
}
} else {
warn("移除基础标签失败,空 Payload 或找不到标签数据")
}
setBuildTemp(undefined)
}
const submit = () => {
if (!template) {
notification["warning"]({message: "Fuzz模版为空"})
return
}
setLoading(true)
ipcRenderer.invoke("string-fuzzer", {template, token})
}
return <Spin spinning={loading}>
<PageHeader title={"Fuzzer Tag 调试工具"}>
</PageHeader>
<Tabs defaultActiveKey={advanced}>
<Tabs.TabPane tab={"简易模式"} key={"ordinary"} disabled={props.disableBasicMode}>
<PageHeader title={<div style={{fontSize: 14}}>
简易模式适合复制 Fuzz 过后的 Payload 来查看结果
</div>}/>
<Form
onSubmitCapture={e => {
e.preventDefault()
submit()
}}
labelCol={{span: 7}} wrapperCol={{span: 15}}
>
<Form.Item label={"Fuzz模版"}>
<Input placeholder={"{{randstr}}"} value={template}
onChange={e => setTemplate(e.target.value)}/>
</Form.Item>
<Form.Item label={" "} colon={false}>
<Space>
<Button type={"primary"} htmlType={"submit"}>
查看 Fuzz 结果
</Button>
</Space>
</Form.Item>
</Form>
</Tabs.TabPane>
<Tabs.TabPane key={"advanced"} tab={"调试模式"}>
<PageHeader title={"调试模式"} subTitle={"调试模式适合生成或者修改 Payload,在调试完成后,可以在 Web Fuzzer 中使用"}/>
<Space direction={"vertical"} style={{width: "100%"}} size={24}>
<div style={{height: 120}}>
<YakEditor type={"http"}
value={template} readOnly={false} setValue={setTemplate}/>
</div>
<Form layout={"horizontal"} onSubmitCapture={e => {
e.preventDefault()
submit()
}} labelCol={{span: 7}} wrapperCol={{span: 14}}>
<ManySelectOne
label={`选择基础 Fuzz 标签`}
value={buildTemp}
data={fuzzOperators.map(i => {
return {value: i.name, text: i.name}
})}
setValue={r => {
setBuildTemp(r)
setEncodeTemp(undefined)
}}
>
</ManySelectOne>
{buildTemp && <Form.Item label={" "} colon={false}>
<Card bordered={true} title={"基础标签"} size={"small"} extra={[
<Button
danger={true}
onClick={() => {
removeBuildTemplateTag()
}}
>移除编码标签</Button>
]}>
{(() => {
if (!buildTemp) {
return
}
const res = fuzzOperators.filter(i => i.name === buildTemp);
if (res.length > 0 && res[0].optionsRender) {
return res[0].optionsRender(template, setTemplate)
}
})()}
</Card>
</Form.Item>}
<ManySelectOne
disabled={!!encodeTemp}
label={"Payload 编码 / 编码标签"}
value={encodeTemp}
setValue={e => {
setEncodeTemp(e)
}}
data={encodeOperators.map(i => {
return {value: i.name, text: i.name}
})}
>
</ManySelectOne>
{encodeTemp && <Form.Item
label={" "} colon={false}
help={"本标签一般负责对 Payload 进行编码等处理,可以嵌套在一个普通(基础)标签外部"}
>
<Card bordered={true} title={"编码标签"} size={"small"} extra={[
<Button
danger={true}
onClick={() => {
removeEncodeTemplateTag()
}}
>移除编码标签</Button>
]}>
{(() => {
if (!encodeTemp) {
return
}
const res = encodeOperators.filter(i => i.name === encodeTemp);
if (res.length > 0 && res[0].optionsRender) {
return res[0].optionsRender(template, setTemplate)
}
})()}
</Card>
</Form.Item>}
<Form.Item label={" "} colon={false}>
<Space>
<Button htmlType={"submit"}>查看生成后的 Payload</Button>
{props.insertCallback && <Button
type={"primary"}
onClick={() => {
if (props.insertCallback) {
props.insertCallback(template)
}
}}
>插入标签所在位置</Button>}
<Popconfirm
title={"确认要重置你的 Payload 吗?"}
onConfirm={() => {
setBuildTemp("")
setEncodeTemp("")
setTemplate("")
}}
>
<Button>重置</Button>
</Popconfirm>
</Space>
</Form.Item>
</Form>
</Space>
</Tabs.TabPane>
</Tabs>
</Spin>
}
Example #29
Source File: CodecPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
CodecPage: React.FC<CodecPageProp> = (props) => {
const [text, setText] = useState("")
const [result, setResult] = useState("")
const [loading, setLoading] = useState(true)
const [leftWidth, setLeftWidth] = useState<boolean>(false)
const [rightWidth, setRightWidth] = useState<boolean>(false)
const [leftLine, setLeftLine] = useState<boolean>(true)
const [rightLine, setRightLine] = useState<boolean>(false)
const [codecType, setCodecType] = useState<CodecType>();
const [params, setParams] = useState<YakExecutorParam[]>([]);
const [codecPlugin, setCodecPlugin] = useState<CodecType[]>([]);
const [pluginLoading, setPluginLoading] = useState<boolean>(false)
const [pluginVisible, setPluginVisible] = useState<boolean>(false)
let timer: any = null
const codec = (t: string, params?: YakExecutorParam[], isYakScript?: boolean) => {
if (!t) {
failed("BUG: 空的解码类型")
return
}
if (!text && !isYakScript) {
failed("左侧编辑器内容为空,请输入内容后重试!")
return
}
ipcRenderer
.invoke("Codec", {Type: t, Text: text, Params: params || [], ScriptName: isYakScript ? t : ""})
.then((res) => {
onHandledResult(res?.Result || "")
})
.catch((err) => {
onHandleError(`${err}`)
})
}
const onHandledResult = (data: string) => {
setResult(data)
}
const onHandleError = (err: string) => {
if (err) failed(`CODEC 解码失败:${err}`)
}
useEffect(() => {
setLoading(true)
setTimeout(() => setLoading(false), 300)
}, [])
const renderCodecTypes = useMemoizedFn((items: CodecType[], notAutoExec?: boolean, isYakScript?: boolean) => {
return (
items.map((item) => {
if ((item.subTypes || []).length > 0) {
return (
<Dropdown
key={item.verbose}
overlay={
<Menu activeKey={codecType?.key}>
{item.subTypes?.map((subItem) => {
return (
<Menu.Item
key={`${subItem.key}`}
onClick={() => {
setCodecType(subItem)
if (!notAutoExec) {
codec(subItem.key || "", [], isYakScript)
}
}}>
<span>
{subItem.verbose}
</span>
</Menu.Item>
)
})}
</Menu>
}
placement='bottomLeft'
>
<Button
type={((item?.subTypes || []).filter(i => {
return i.key === codecType?.key
})).length > 0 ? "primary" : undefined}
>
{item.verbose}
<DownOutlined/>
</Button>
</Dropdown>
)
} else {
return (
<Button
key={item.key}
type={codecType?.key === item.key ? "primary" : undefined}
onClick={() => {
setCodecType(item);
if (!notAutoExec) {
codec(item.key || "", [], isYakScript)
}
}}
style={{marginRight: 8}}
>
{item.verbose}
</Button>
)
}
})
)
})
const search = useMemoizedFn((keyword?: string) => {
setPluginLoading(true)
queryYakScriptList(
"codec",
(i: YakScript[], total) => {
setCodecPlugin([{
subTypes: i.map(script => {
return {
key: script.ScriptName,
help: script.Help,
verbose: script.ScriptName,
isYakScript: true
}
}), key: "from-yakit-codec-plugin", verbose: "CODEC 社区插件"
}])
},
() => setTimeout(() => setPluginLoading(false), 300),
10,
undefined,
keyword
)
})
useEffect(() => {
search()
}, [])
return (
<AutoSpin spinning={loading}>
<PageHeader
title={"Codec"} className={"codec-pageheader-title"}
subTitle={<>
{codecType && <Tag color={"geekblue"}>当前类型:{codecType?.verbose}</Tag>}
{codecType && (codecType?.params || []).length <= 0 &&
<Button type={"primary"} size={"small"} onClick={e => {
codec(codecType?.key || "", [], codecType?.isYakScript)
}}>立即执行</Button>}
</>}
/>
<div className={"codec-function-bar"}>
<Space direction={"vertical"} style={{width: "100%"}}>
<Space>
{renderCodecTypes(CodecMenu)}
</Space>
<Space>
{renderCodecTypes(EncAmpDecMenu, true)}
{/* {renderCodecTypes(codecPlugin, false, true)} */}
<Popover
overlayClassName="codec-plugin-lib"
trigger="hover"
placement="bottomLeft"
visible={pluginVisible}
onVisibleChange={setPluginVisible}
content={
<div style={{width: 250}}>
<Input placeholder="模糊搜索插件名" allowClear onChange={event => {
if (timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
search(event.target.value)
}, 500);
}}></Input>
<List
loading={pluginLoading}
size="small"
dataSource={codecPlugin[0]?.subTypes || []}
rowKey={row => row.key || ""}
renderItem={item => <List.Item>
<div style={{width: "100%", padding: "5px 7px"}} onClick={() => {
setCodecType(item)
codec(item.key || "", [], true)
setPluginVisible(false)
}}>
{item.key || ""}
</div>
</List.Item>}
/>
</div>
}>
<Button
type={(codecPlugin[0]?.subTypes || []).filter(item => codecType?.key === item.key).length !== 0 ? 'primary' : 'default'}>CODEC
社区插件 <DownOutlined style={{fontSize: 10}}/></Button>
</Popover>
</Space>
{codecType && codecType?.params && codecType.params.length > 0 && <Row
style={{width: "100%"}}
gutter={20}
>
<Col span={codecType?.help ? 18 : 24}>
<Divider>设置参数</Divider>
<YakScriptParamsSetter
primaryParamsOnly={true} styleSize={"small"}
Params={(codecType?.params || [])}
onParamsConfirm={finalParams => {
setParams([...finalParams])
codec(codecType?.key || "", finalParams, codecType?.isYakScript)
}}
hideClearButton={true}
submitVerbose={"执行"}
/>
</Col>
{codecType?.help && <Col span={6} style={{paddingTop: 30}}>
<Alert type={"info"} message={codecType?.help}/>
</Col>}
</Row>}
</Space>
</div>
<div className={"codec-content"}>
<Row wrap={false} justify='space-between' style={{flexGrow: 1}}>
<Col flex={leftWidth ? "0 1 80%" : rightWidth ? "0 1 18%" : "0 1 49%"}>
<AutoCard
className='codec-card-body'
headStyle={{height: 28, minHeight: 28, padding: 0}}
bodyStyle={{padding: 0}}
extra={
<>
<Button
size={"small"}
type={leftLine ? "primary" : "link"}
icon={<LineConversionIcon/>}
onClick={() => setLeftLine(!leftLine)}
/>
<Button
size={"small"}
type={leftWidth ? "primary" : "link"}
icon={<ArrowsAltOutlined/>}
onClick={() => {
setLeftWidth(!leftWidth)
setRightWidth(false)
}}
/>
</>
}
>
<div className='editor-body'>
<YakEditor value={text} noWordWrap={!leftLine} setValue={setText}/>
</div>
</AutoCard>
</Col>
<Col flex='0 1 2%'>
<div className={"exchange-btn"}>
<SwapOutlined
className={"exchange-icon"}
onClick={() => {
const left = text
const right = result
setText(right)
setResult(left)
}}
/>
</div>
</Col>
<Col flex={rightWidth ? "0 1 80%" : leftWidth ? "0 1 18%" : "0 1 49%"}>
<AutoCard
className='codec-card-body'
headStyle={{height: 28, minHeight: 28, padding: 0}}
bodyStyle={{padding: 0}}
extra={
<>
<Button
size={"small"}
type={rightLine ? "primary" : "link"}
icon={<LineConversionIcon/>}
onClick={() => setRightLine(!rightLine)}
/>
<Button
size={"small"}
type={rightWidth ? "primary" : "link"}
icon={<ArrowsAltOutlined/>}
onClick={() => {
setRightWidth(!rightWidth)
setLeftWidth(false)
}}
/>
</>
}
>
<div className='editor-body'>
<YakEditor value={result} noWordWrap={!rightLine} setValue={setResult} readOnly={true}
type={"http"}/>
</div>
</AutoCard>
</Col>
</Row>
</div>
</AutoSpin>
)
}