@ant-design/icons#ReloadOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#ReloadOutlined.
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: index.tsx From fe-v5 with Apache License 2.0 | 6 votes |
export default function RefreshIcon(props: Props) {
const { t } = useTranslation();
const { onClick, className } = props;
const [refreshing, setRefreshing] = useState<boolean>(false);
const handleRefresh = (e) => {
if (refreshing) return;
setRefreshing(true);
onClick();
setTimeout(() => {
setRefreshing(false);
}, 1000);
};
return (
<Button
className={className ? className + ' reload-icon' : 'reload-icon'}
loading={refreshing}
onClick={handleRefresh}
icon={<ReloadOutlined className='refresh' spin={refreshing} />}
></Button>
);
}
Example #2
Source File: index.tsx From memex with MIT License | 6 votes |
renderInboxOptions() {
return (
<div className="inbox-drawer-header">
<div>
<Checkbox />
</div>
<div>
<Tooltip placement="left" title="Commit">
<Button
style={{ border: 'none' }}
onClick={() => {
this.confirmCommitDocs();
}}
shape="circle"
icon={<FileAddOutlined style={{ fontSize: 14 }} />}
/>
</Tooltip>
<Tooltip placement="left" title="Refresh">
<Button
style={{ border: 'none' }}
onClick={() => {
this.getInbox();
}}
shape="circle"
icon={<ReloadOutlined style={{ fontSize: 14 }} />}
/>
</Tooltip>
</div>
</div>
);
}
Example #3
Source File: out.tsx From web-pdm with Apache License 2.0 | 6 votes |
IconRenders = {
undo: <RollbackOutlined />,
redo: <RollbackOutlined style={{ transform: 'scaleX(-1)' }} />,
min: <ZoomOutOutlined />,
max: <ZoomInOutlined />,
full: <BorderOutlined />,
miniMap: <PictureFilled />,
miniMapNo: <PictureOutlined />,
dagreLayout: <PartitionOutlined />,
relationLayout: <UngroupOutlined />,
reload: <ReloadOutlined />,
image: <DownloadOutlined />,
darkness: <SnippetsFilled />,
light: <SnippetsOutlined />,
colorClose: <BgColorsOutlined />,
colorOpen: <BgColorsOutlined />
}
Example #4
Source File: ErrorNetwork.tsx From posthog-foss with MIT License | 6 votes |
export function ErrorNetwork(): JSX.Element {
return (
<div>
<h1 className="page-title">Network Error</h1>
<p>There was an issue loading the requested resource.</p>
<p>
<Button onClick={() => window.location.reload()}>
<ReloadOutlined /> Reload the page!
</Button>
</p>
</div>
)
}
Example #5
Source File: index.tsx From nanolooker with MIT License | 5 votes |
Peers: React.FC = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const { peers, getPeers, count, isLoading: isPeersLoading } = usePeers();
const refreshPeers = async () => {
setIsLoading(true);
await refreshActionDelay(getPeers);
setIsLoading(false);
};
const opacity = isLoading ? 0.5 : 1;
React.useEffect(() => {
if (isEmpty(peers)) return;
protocolVersions = {};
Object.values(peers).forEach(({ protocol_version: protocolVersion }) => {
if (!protocolVersions[protocolVersion]) {
protocolVersions[protocolVersion] = 1;
} else {
protocolVersions[protocolVersion] += 1;
}
});
protocolVersion = Object.keys(protocolVersions).reduce(function (a, b) {
return protocolVersions[a] > protocolVersions[b] ? a : b;
});
percentProtocolVersion = new BigNumber(protocolVersions[protocolVersion])
.times(100)
.dividedBy(count)
.toFormat(2);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [peers]);
return (
<Card
size="small"
title={t("pages.status.peers")}
extra={
<Tooltip title={t("pages.status.reload")}>
<Button
type="primary"
icon={<ReloadOutlined />}
size="small"
onClick={refreshPeers}
loading={isLoading}
/>
</Tooltip>
}
>
<LoadingStatistic
title={t("pages.status.connectedPeers")}
value={count}
isLoading={isPeersLoading}
style={{ opacity }}
/>
<LoadingStatistic
title={t("pages.status.protocolVersion", {
protocolVersion,
})}
value={percentProtocolVersion}
suffix="%"
isLoading={isPeersLoading}
style={{ opacity }}
/>
</Card>
);
}
Example #6
Source File: HTTPFuzzerHistory.tsx From yakit with GNU Affero General Public License v3.0 | 5 votes |
HTTPFuzzerHistorySelector: React.FC<HTTPFuzzerHistorySelectorProp> = React.memo((props) => {
const [tasks, setTasks] = useState<HTTPFuzzerTask[]>([]);
const [loading, setLoading] = useState(false);
const deleteAll = useMemoizedFn(()=>{
setLoading(true)
ipcRenderer.invoke("DeleteHistoryHTTPFuzzerTask", {}).then(()=>{
info("Delete History")
reload()
}).finally(()=>setTimeout(()=>setLoading(false), 300))
})
const reload = useMemoizedFn(() => {
setLoading(true)
ipcRenderer.invoke("QueryHistoryHTTPFuzzerTask", {}).then((data: { Tasks: HTTPFuzzerTask[] }) => {
setTasks(data.Tasks)
}).finally(() => setTimeout(() => setLoading(false), 300))
})
useEffect(() => {
reload()
}, [])
return <Card size={"small"} bordered={false} title={<Space>
Web Fuzzer History
<Button type={"link"} size={"small"} icon={<ReloadOutlined/>} onClick={e => {
reload()
}}/>
<Popconfirm title={"确定删除吗?"} onConfirm={()=>{
deleteAll()
}}>
<Button type={"link"} size={"small"} danger={true}
icon={<DeleteOutlined />}
/>
</Popconfirm>
</Space>}>
<List<HTTPFuzzerTask>
loading={loading}
dataSource={tasks} pagination={{total: tasks.length, size: "small", pageSize: 10}}
renderItem={(i: HTTPFuzzerTask) => {
return <Card size={"small"} style={{marginBottom: 8}} hoverable={true} onClick={e => {
e.preventDefault()
props.onSelect(i.Id)
}}>
<Space>
<div>
{`ID:${i.Id} ${formatTimestamp(i.CreatedAt)}`}
</div>
<Tag>共{i.HTTPFlowTotal}个</Tag>
{i.HTTPFlowSuccessCount != i.HTTPFlowTotal && <Tag>成功:{i.HTTPFlowSuccessCount}个</Tag>}
</Space>
</Card>
}}
/>
</Card>
})
Example #7
Source File: index.tsx From nanolooker with MIT License | 5 votes |
BlockCount: React.FC = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = React.useState(false);
const [isInitialLoading, setIsInitialLoading] = React.useState(true);
const {
count,
unchecked,
cemented,
getBlockCount,
isLoading: isBlockCountLoading,
} = React.useContext(BlockCountContext);
const refreshBlockCount = async () => {
setIsLoading(true);
await refreshActionDelay(getBlockCount);
setIsLoading(false);
};
React.useEffect(() => {
let interval: number = window.setInterval(() => {
try {
getBlockCount();
} catch (_e) {
clearInterval(interval);
}
}, POLL_INTERVAL);
return () => clearInterval(interval);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
React.useEffect(() => {
if (!isBlockCountLoading) {
setIsInitialLoading(false);
}
}, [isBlockCountLoading]);
const opacity = isLoading ? 0.5 : 1;
return (
<Card
size="small"
title={t("common.blocks")}
extra={
<Tooltip title={t("pages.status.reload")}>
<Button
type="primary"
icon={<ReloadOutlined />}
size="small"
onClick={refreshBlockCount}
loading={isLoading}
/>
</Tooltip>
}
>
<Skeleton active loading={!count}>
<LoadingStatistic
title={t("pages.status.count")}
value={count}
isLoading={isInitialLoading}
style={{ opacity }}
/>
<LoadingStatistic
isLoading={isInitialLoading}
title={t("pages.status.unchecked")}
tooltip={t("tooltips.unchecked")}
value={unchecked}
style={{ opacity }}
/>
<LoadingStatistic
title={t("pages.status.cemented")}
tooltip={t("tooltips.cemented")}
value={cemented}
isLoading={isInitialLoading}
style={{ opacity }}
/>
</Skeleton>
</Card>
);
}
Example #8
Source File: Background.tsx From datart with Apache License 2.0 | 5 votes |
export function Background() {
const [formVisible, setFormVisible] = useState(false);
const userSettings = useSelector(selectUserSettings);
const organizations = useSelector(selectOrganizations);
const userSettingLoading = useSelector(selectUserSettingLoading);
const error = useSelector(selectInitializationError);
const t = useI18NPrefix('main.background');
const showForm = useCallback(() => {
setFormVisible(true);
}, []);
const hideForm = useCallback(() => {
setFormVisible(false);
}, []);
let visible = true;
let content;
if (userSettingLoading) {
content = (
<Hint>
<SettingOutlined className="img loading" />
<p>{t('loading')}</p>
</Hint>
);
} else if (error) {
content = (
<Hint>
<ReloadOutlined className="img" />
<p>{t('initError')}</p>
</Hint>
);
} else if (
!userSettingLoading &&
!(userSettings && userSettings.length) &&
!organizations.length
) {
content = (
<>
<Hint className="add" onClick={showForm}>
<AppstoreAddOutlined className="img" />
<p>{t('createOrg')}</p>
</Hint>
<OrganizationForm visible={formVisible} onCancel={hideForm} />
</>
);
} else {
visible = false;
}
return <Container visible={visible}>{content}</Container>;
}
Example #9
Source File: ErrorCard.tsx From jitsu with MIT License | 5 votes |
ErrorCard: FC<ErrorCardProps> = ({
title,
icon,
error,
description,
descriptionWithContacts,
stackTrace,
className,
onReload,
}) => {
if (description === undefined && error !== undefined) {
description = error.message
}
if (stackTrace === undefined && error !== undefined) {
stackTrace = error.stack
}
return (
<Card bordered={false} className={cn(className, "max-h-full")}>
<Card.Meta
avatar={icon || <ExclamationCircleOutlined className={styles.icon} />}
title={title || "An Error Occured"}
description={
<>
<Fragment key="description">
{description !== undefined ? (
description
) : (
<span>
{descriptionWithContacts !== undefined ? (
<>
{descriptionWithContacts}
{descriptionWithContacts && <br />}
</>
) : (
<>
{"The application component crashed because of an internal error."}
<br />
</>
)}
{"Please, try to reload the page first and if the problem is still present contact us at"}{" "}
<Typography.Paragraph copyable={{ tooltips: false }} className="inline">
{"[email protected]"}
</Typography.Paragraph>{" "}
{"and our engineers will fix the problem asap."}
</span>
)}
</Fragment>
{stackTrace && (
<Collapse key="stack-trace" bordered={false} className={`mt-2 ${styles.stackTraceCard}`}>
<Collapse.Panel key={1} header="Error Stack Trace">
<div className="overflow-y-auto">
<Typography.Paragraph
copyable={{
text: stackTrace,
icon: [<CopyOutlined />, <CheckOutlined />],
}}
className={`flex flex-row ${styles.errorStackContainer}`}
>
<pre className="text-xs">{stackTrace}</pre>
</Typography.Paragraph>
</div>
</Collapse.Panel>
</Collapse>
)}
{onReload && (
<div key="reload-button" className="flex justify-center items-center mt-2">
<Button type="default" onClick={onReload} icon={<ReloadOutlined />}>{`Reload`}</Button>
</div>
)}
</>
}
/>
</Card>
)
}
Example #10
Source File: index.tsx From memex with MIT License | 5 votes |
render() {
const { handleClose, layout, selectedDoc } = this.props;
const { data, loading } = this.state;
const { visible, width, title, closeMask = false } = layout.drawer;
return (
<div>
<Drawer
width={width}
title={title}
style={{ left: 48 }}
placement="left"
closable
mask={!closeMask}
onClose={() => {
handleClose();
this.resetSelectedDoc();
}}
visible={visible}
>
{loading ? (
<div className="loading-screen">
<Spin />
</div>
) : null}
<div className="inbox-drawer-header">
<div>
<Checkbox />
</div>
<div>
<Tooltip placement="left" title="Commit">
<Button
style={{ border: 'none' }}
onClick={() => {
this.confirmCommitDocs();
}}
shape="circle"
icon={<FileAddOutlined style={{ fontSize: 14 }} />}
/>
</Tooltip>
<Tooltip placement="left" title="Refresh">
<Button
style={{ border: 'none' }}
onClick={() => {
this.getInbox();
}}
shape="circle"
icon={<ReloadOutlined style={{ fontSize: 14 }} />}
/>
</Tooltip>
</div>
</div>
<div style={{ padding: '0px 16px' }}>
<Tabs defaultActiveKey="1">
<TabPane tab="Uncommitted" key="uncommitted">
{data.map((item: any) => {
if (!item.doc.committed) {
return this.renderInboxItem(item);
}
})}
</TabPane>
<TabPane tab="Committed" key="committed">
{data.map((item: any) => {
if (item.doc.committed) {
return this.renderInboxItem(item);
}
})}
</TabPane>
</Tabs>
</div>
</Drawer>
</div>
);
}
Example #11
Source File: InternalMetricsTab.tsx From posthog-foss with MIT License | 5 votes |
export function InternalMetricsTab(): JSX.Element {
const { openSections, systemStatus, queries, queriesLoading, showAnalyzeQueryButton } = useValues(systemStatusLogic)
const { setOpenSections, loadQueries } = useActions(systemStatusLogic)
const [showIdle, setShowIdle] = useState(false)
const postgresQueries = useMemo(
() => queries?.postgres_running?.filter(({ state }) => showIdle || state !== 'idle'),
[showIdle, queries]
)
const dashboard = systemStatus?.internal_metrics.clickhouse
const reloadQueries = (e: React.MouseEvent): void => {
e.stopPropagation()
loadQueries()
}
return (
<Card>
<Collapse activeKey={openSections} onChange={(keys) => setOpenSections(keys as string[])}>
{dashboard ? (
<Collapse.Panel header="Dashboards" key="0">
<Dashboard id={dashboard.id.toString()} shareToken={dashboard.share_token} internal />
</Collapse.Panel>
) : null}
<Collapse.Panel header="PostgreSQL - currently running queries" key="1">
<div className="mb float-right">
<Checkbox
checked={showIdle}
onChange={(e) => {
setShowIdle(e.target.checked)
}}
>
Show idle queries
</Checkbox>
<Button style={{ marginLeft: 8 }} onClick={reloadQueries}>
<ReloadOutlined /> Reload Queries
</Button>
</div>
<QueryTable queries={postgresQueries} loading={queriesLoading} />
</Collapse.Panel>
{queries?.clickhouse_running != undefined ? (
<Collapse.Panel header="Clickhouse - currently running queries" key="2">
<div className="mb float-right">
<Button style={{ marginLeft: 8 }} onClick={reloadQueries}>
<ReloadOutlined /> Reload Queries
</Button>
</div>
<QueryTable
queries={queries?.clickhouse_running}
loading={queriesLoading}
showAnalyze={showAnalyzeQueryButton}
/>
</Collapse.Panel>
) : null}
{queries?.clickhouse_slow_log != undefined ? (
<Collapse.Panel header="Clickhouse - slow query log (past 6 hours)" key="3">
<div className="mb float-right">
<Button style={{ marginLeft: 8 }} onClick={reloadQueries}>
<ReloadOutlined /> Reload Queries
</Button>
</div>
<QueryTable
queries={queries?.clickhouse_slow_log}
loading={queriesLoading}
showAnalyze={showAnalyzeQueryButton}
/>
</Collapse.Panel>
) : null}
</Collapse>
<AnalyzeQueryModal />
</Card>
)
}
Example #12
Source File: DomainAssetPage.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
DomainAssetPage: React.FC<DomainAssetPageProps> = (props: DomainAssetPageProps) => {
const [params, setParams] = useState<QueryDomainsRequest>({
Pagination: genDefaultPagination(20),
});
const [response, setResponse] = useState<QueryGeneralResponse<Domain>>({
Data: [],
Pagination: genDefaultPagination(20),
Total: 0
});
const [loading, setLoading] = useState(false);
const {Data, Total, Pagination} = response;
const [outputDomainKeyword, setOutputDomainKeyword] = useState("*");
const [checkedURL, setCheckedURL] = useState<string[]>([])
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([])
const update = useMemoizedFn((page?: number, limit?: number,) => {
const newParams = {
...params,
}
if (page) newParams.Pagination.Page = page;
if (limit) newParams.Pagination.Limit = limit;
setLoading(true)
ipcRenderer.invoke("QueryDomains", newParams).then(data => {
setResponse(data)
}).catch((e: any) => {
failed("QueryExecHistory failed: " + `${e}`)
}).finally(() => {
setTimeout(() => setLoading(false), 200)
})
})
const delDomain = useMemoizedFn((host?: string) => {
const params = !!host ? {DomainKeyword: host} : {DeleteAll: true}
setLoading(true)
ipcRenderer
.invoke("DeleteDomains", params)
.then(() => {
update(1)
})
.catch((e) => {
failed(`DelDomain failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 300))
})
useEffect(() => {
update(1, 20)
}, [])
return <Table<Domain>
loading={loading}
pagination={{
size: "small", current: +Pagination.Page,
pageSize: Pagination?.Limit || 10, showSizeChanger: true,
total: Total, showTotal: (i) => <Tag>共{i}条历史记录</Tag>,
onChange: (page: number, limit?: number) => {
update(page, limit)
}
}}
title={e => {
return <div style={{display: "flex", justifyContent: "space-between"}}>
<Space>
<div>域名资产</div>
<Button
type={"link"} onClick={() => {
update(1)
setSelectedRowKeys([])
setCheckedURL([])
}}
size={"small"} icon={<ReloadOutlined/>}
/>
</Space>
<Space>
<Popover title={"输入想要导出的域名关键字"}
trigger={["click"]}
content={<div>
<Form layout={"inline"} size={"small"} onSubmitCapture={e => {
e.preventDefault()
startExecYakCode("Output Domains", {
Script: OutputAsset.outputDomainByKeyword, Params: [
{Key: "keyword", Value: outputDomainKeyword}
]
})
}}>
<InputItem
label={"域名关键字"} value={outputDomainKeyword}
setValue={setOutputDomainKeyword}
/>
<Form.Item colon={false} label={" "}>
<Button size={"small"} type="primary" htmlType="submit"> 导出 </Button>
</Form.Item>
</Form>
</div>}
>
<Button
type={"primary"}
size={"small"}
>导出域名</Button>
</Popover>
<Popconfirm title="确定删除所有域名资产吗? 不可恢复" onConfirm={e => {
delDomain()
setSelectedRowKeys([])
setCheckedURL([])
}}>
<Button
type="link"
danger
size="small"
>删除全部</Button>
</Popconfirm>
<DropdownMenu
menu={{data: [
{key:'bug-test',title:"发送到漏洞检测"},
{key:'scan-port',title:"发送到端口扫描"},
{key:'brute',title:"发送到爆破"}
]}}
dropdown={{placement: "bottomRight"}}
onClick={(key) => {
if(checkedURL.length === 0){
failed("请最少选择一个选项再进行操作")
return
}
ipcRenderer.invoke("send-to-tab", {
type: key,
data:{URL: JSON.stringify(checkedURL)}
})
}}
>
<Button type="link" icon={<LineMenunIcon />}></Button>
</DropdownMenu>
</Space>
</div>
}}
size={"small"} bordered={true}
dataSource={Data}
rowKey={e => `${e.ID}`}
rowSelection={{
onChange: (selectedRowKeys, selectedRows) => {
setSelectedRowKeys(selectedRowKeys as string[])
setCheckedURL(selectedRows.map(item => item.DomainName))
},
selectedRowKeys
}}
columns={[
{
title: "域名",
render: (i: Domain) => <Text style={{maxWidth: 470}} ellipsis={{tooltip: true}}>{i.DomainName}</Text>,
filterIcon: (filtered) => {
return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
},
filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
return (
params &&
setParams && (
<TableFilterDropdownString
label={"搜索关键字"}
params={params}
setParams={setParams}
filterName={"DomainKeyword"}
confirm={confirm}
setSelectedKeys={setSelectedKeys}
update={update}
/>
)
)
}
},
{
title: "IP",
dataIndex: "IPAddr",
width: 160,
filterIcon: (filtered) => {
return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
},
filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
return (
params &&
setParams && (
<TableFilterDropdownString
label={"搜索IP"}
params={params}
setParams={setParams}
filterName={"Network"}
confirm={confirm}
setSelectedKeys={setSelectedKeys}
update={update}
/>
)
)
}
},
{
title: "HTMLTitle",
dataIndex: "HTTPTitle",
filterIcon: (filtered) => {
return params && <SearchOutlined style={{color: filtered ? "#1890ff" : undefined}}/>
},
filterDropdown: ({setSelectedKeys, selectedKeys, confirm}) => {
return (
params &&
setParams && (
<TableFilterDropdownString
label={"搜索关键字"}
params={params}
setParams={setParams}
filterName={"Title"}
confirm={confirm}
setSelectedKeys={setSelectedKeys}
update={update}
/>
)
)
}
},
{
title: "操作",
render: (i: Domain) => (
<Space>
<Button
size="small"
type={"link"}
danger
onClick={() => {
delDomain(i.DomainName)
setSelectedRowKeys([])
setCheckedURL([])
}}
>
删除
</Button>
</Space>
)
}
]}
>
</Table>
}
Example #13
Source File: Recycle.tsx From datart with Apache License 2.0 | 4 votes |
Recycle = memo(
({ type, orgId, selectedId, list, listLoading, onInit }: RecycleProps) => {
const dispatch = useDispatch();
const history = useHistory();
const { showSaveForm } = useContext(SaveFormContext);
const vizs = useSelector(selectVizs);
const isOwner = useSelector(selectIsOrgOwner);
const permissionMap = useSelector(selectPermissionMap);
const tg = useI18NPrefix('global');
useEffect(() => {
onInit();
}, [onInit]);
const redirect = useCallback(
vizId => {
if (vizId) {
history.push(`/organizations/${orgId}/vizs/${vizId}`);
} else {
history.push(`/organizations/${orgId}/vizs`);
}
},
[history, orgId],
);
const del = useCallback(
(id, type) => e => {
e.stopPropagation();
dispatch(
deleteViz({
params: { id, archive: false },
type,
resolve: () => {
message.success(tg('operation.deleteSuccess'));
dispatch(removeTab({ id, resolve: redirect }));
},
}),
);
},
[dispatch, redirect, tg],
);
const moreMenuClick = useCallback(
(id, name, vizType) =>
({ key, domEvent }) => {
domEvent.stopPropagation();
switch (key) {
case 'reset':
showSaveForm({
vizType,
type: CommonFormTypes.Edit,
visible: true,
initialValues: { id, name, parentId: void 0 },
onSave: (values, onClose) => {
let index = getInsertedNodeIndex(values, vizs);
dispatch(
unarchiveViz({
params: {
id,
vizType,
...values,
parentId: values.parentId || null,
index,
},
resolve: () => {
message.success(tg('operation.restoreSuccess'));
dispatch(removeTab({ id, resolve: redirect }));
onClose();
},
}),
);
},
});
break;
default:
break;
}
},
[dispatch, showSaveForm, redirect, vizs, tg],
);
const toDetail = useCallback(
id => () => {
history.push(`/organizations/${orgId}/vizs/${id}`);
},
[history, orgId],
);
return (
<Wrapper>
<List
dataSource={list}
loading={listLoading && { indicator: <LoadingOutlined /> }}
renderItem={({ id, name, vizType, loading }) => {
let allowManage = false;
if (type === 'viz') {
const viz = vizs.find(v => v.id === id);
const path = viz
? getPath(
vizs as Array<{ id: string; parentId: string }>,
{ id, parentId: viz.parentId },
VizResourceSubTypes.Folder,
)
: [id];
allowManage = getCascadeAccess(
isOwner,
permissionMap,
ResourceTypes.View,
path,
PermissionLevels.Manage,
);
} else {
allowManage = !!calcAc(
isOwner,
permissionMap,
ResourceTypes.Viz,
PermissionLevels.Manage,
id,
);
}
return (
<ListItem
selected={selectedId === id}
className={classnames({
recycle: true,
disabled: loading,
})}
onClick={toDetail(id)}
actions={[
allowManage && (
<Popup
trigger={['click']}
placement="bottomRight"
content={
<Menu
prefixCls="ant-dropdown-menu"
selectable={false}
onClick={moreMenuClick(id, name, vizType)}
>
<MenuListItem
key="reset"
prefix={<ReloadOutlined className="icon" />}
>
{tg('button.restore')}
</MenuListItem>
<MenuListItem
key="delelte"
prefix={<DeleteOutlined className="icon" />}
>
<Popconfirm
title={tg('operation.deleteConfirm')}
onConfirm={del(id, vizType)}
>
{tg('button.delete')}
</Popconfirm>
</MenuListItem>
</Menu>
}
>
<Button
type="link"
icon={<MoreOutlined />}
className="btn-hover"
onClick={stopPPG}
/>
</Popup>
),
]}
>
<List.Item.Meta title={name} />
</ListItem>
);
}}
/>
</Wrapper>
);
},
)
Example #14
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 #15
Source File: App.tsx From pcap2socks-gui with MIT License | 4 votes |
render() {
return (
<Layout className="layout">
<Content className="content-wrapper">
<div className="content">
{(() => {
switch (this.state.stage) {
case STAGE_WELCOME:
return this.renderWelcome();
case STAGE_INTERFACE:
return this.renderInterface();
case STAGE_DEVICE:
return this.renderDevice();
case STAGE_PROXY:
return this.renderProxy();
case STAGE_RUNNING:
return this.renderRunning();
default:
return;
}
})()}
</div>
<div className="footer">
{(() => {
if (this.state.stage > STAGE_WELCOME && this.state.stage <= STAGE_PROXY) {
return (
<Button
className="button"
disabled={this.state.loading > 0}
icon={<LeftOutlined />}
onClick={() => this.setState({ stage: this.state.stage - 1 })}
>
上一步
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_INTERFACE) {
return (
<Button
className="button"
disabled={this.state.loading > 0 && this.state.loading !== 1}
icon={<ReloadOutlined />}
onClick={this.updateInterfaces}
>
刷新网卡列表
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_PROXY) {
return (
<Button
className="button"
disabled={this.state.loading > 0}
icon={<FolderOpenOutlined />}
onClick={() => {
const node = document.getElementById("open");
if (node) {
node.click();
}
}}
>
导入代理配置
<input id="open" type="file" onChange={this.import} style={{ display: "none" }} />
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_PROXY) {
return (
<Button className="button" icon={<ExportOutlined />} onClick={this.export}>
导出代理配置
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_PROXY) {
return (
<Button
className="button"
disabled={this.state.loading > 0 && this.state.loading !== 2}
loading={this.state.loading === 2}
icon={<ExperimentOutlined />}
onClick={this.test}
>
测试代理服务器
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_WELCOME && this.state.ready) {
return (
<Tooltip title={this.state.destination}>
<Button
className="button"
type="primary"
disabled={this.state.loading > 0 && this.state.loading !== 3}
loading={this.state.loading === 3}
icon={<PlayCircleOutlined />}
onClick={this.run}
>
以上次的配置运行
</Button>
</Tooltip>
);
}
})()}
{(() => {
if (this.state.stage >= STAGE_WELCOME && this.state.stage < STAGE_PROXY) {
return (
<Button
className="button"
disabled={this.state.loading > 0}
icon={<RightOutlined />}
type="primary"
onClick={() => this.setState({ stage: this.state.stage + 1 })}
>
下一步
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_PROXY) {
return (
<Button
className="button"
type="primary"
disabled={this.state.loading > 0 && this.state.loading !== 3}
loading={this.state.loading === 3}
icon={<PoweroffOutlined />}
onClick={this.run}
>
运行
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_RUNNING) {
return (
<Button className="button" icon={<GlobalOutlined />} onClick={this.notifyNetwork}>
显示网络设置
</Button>
);
}
})()}
{(() => {
if (this.state.stage === STAGE_RUNNING) {
return (
<Button
className="button"
type="primary"
danger
disabled={this.state.loading > 0 && this.state.loading !== 4}
loading={this.state.loading === 4}
icon={<PoweroffOutlined />}
onClick={this.stopConfirm}
>
停止
</Button>
);
}
})()}
</div>
</Content>
</Layout>
);
}
Example #16
Source File: BasicTable.tsx From fe-v5 with Apache License 2.0 | 4 votes |
// 泛型函数组件 https://juejin.im/post/5cd7f2c4e51d453a7d63b715#heading-7
function BasicTable<T>(props: IBasicTableProps<T>) {
const prefixCls = `${props.prefixCls || 'dantd'}-table`;
const filterType = props.filterType || 'half';
const tmpDataSource = props.dataSource || ([] as T[]);
const t = intlZhMap;
const [dataSource, setDataSource] = useState(tmpDataSource);
const [queryFormValues, setQueryFormValues] = useState<any>({});
const [isQueryInit, setQueryInit] = useState(false);
const {
showFilter = true,
showSearch = true,
showReloadBtn = true,
showQueryOptionBtns = true,
showQueryCollapseButton = true,
queryFormProps = {},
isQuerySearchOnChange = true,
loading = false,
showBodyBg = false,
queryFormColumns,
queryMode = 'default',
searchPos = 'full',
reloadBtnPos = 'right',
reloadBtnType = 'icon',
antProps = {
rowKey: 'id',
},
} = props;
// 整理搜索区的展示状态
// 是否展示长条搜索区
const showFullSearch = showSearch && searchPos === 'full';
// 搜索按钮展示的位置
const showReloadBtn2SearchRight =
searchPos === 'full' && showReloadBtn && reloadBtnPos === 'right';
const showReloadBtn2FilterRight =
(!showSearch || searchPos !== 'full') &&
showReloadBtn &&
reloadBtnPos === 'right';
const showReloadBtn2FilterLeft = showReloadBtn && reloadBtnPos === 'left';
const searchPlaceholder =
props.searchPlaceholder || t('table.search.placeholder');
const tableClassName = classNames(prefixCls, props.className);
const tableContentClassName = classNames({
[`${prefixCls}-table-content`]: true,
[`${prefixCls}-table-content-noborder`]: props.hideContentBorder,
});
const tableBodyCls = classNames({
[`${prefixCls}-body`]: !!queryFormColumns || showBodyBg,
[`${prefixCls}-body-compact`]: queryMode === 'compact',
});
const tableQueryCls = classNames({
[`${prefixCls}-query`]: !!queryFormColumns,
[`${prefixCls}-query-compact`]: queryMode === 'compact',
});
const filterSearchInputRef = useRef({}) as any;
const clearFiltersRef = useRef({}) as any;
const [searchQuery, setSearchQuery] = useState('');
const [showRightHeader, setShowRightHeader] = useState<boolean>(false);
const [sorterState, sorterDispatch] = useReducer(sorterReducer, {});
const rowSelection = props.rowSelection;
const selectRowNum = rowSelection
? rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length
: -1;
const sorterNames = {
ascend: t('table.sort.ascend'),
descend: t('table.sort.descend'),
};
const showTotal = (total: number) => {
return `${t('table.total.prefix')} ${total} ${t('table.total.suffix')}`;
};
// 生成列的 dataIndex 数组
const columnsDataIndexArr =
props.columns.map((columnItem) => {
let dataIndex = columnItem.dataIndex;
if (typeof dataIndex === 'object' && dataIndex !== null) {
// metadata name
dataIndex = dataIndex.join(' ');
}
return dataIndex;
}) || [];
const renderCurTarget = (
data: Object,
parentKey: Array<String>,
curTarget = '',
) => {
let returnItem = '';
Object.entries(data).forEach(([curKey, curItem]) => {
// 如果属性的值是对象,继续递归
if (typeof curItem === 'object' && curItem !== null) {
returnItem += ' ' + renderCurTarget(curItem, parentKey.concat(curKey));
}
columnsDataIndexArr.some((dataIndex) => {
// 如果data对象的属性匹配到了columnsDataIndexArr中的某一项
if (dataIndex === parentKey.concat(curKey).join(' ')) {
// 当值为String|Number|Array类型时,将值加到curTarget中
if (
typeof curItem === 'string' ||
typeof curItem === 'number' ||
Array.isArray(curItem)
) {
returnItem += ' ' + curItem;
}
}
});
});
return `${curTarget} ${returnItem}`;
};
const dataSourceMap = useMemo(() => {
if (!props.dataSource || !Array.isArray(props.dataSource)) {
return [];
}
return props.dataSource.reduce((acc, curVal, curIdx) => {
// let curTarget = ''
// Object.entries(curVal).forEach(([curKey, curItem]) => {
// if (columnsDataIndexArr.indexOf(curKey) >= 0 && (typeof curItem === 'string' || typeof curItem === 'number')) {
// curTarget = `${curTarget} ${curItem}`
// }
// })
return {
...acc,
[curIdx]: renderCurTarget(curVal, []).toLowerCase(),
};
}, {});
}, [columnsDataIndexArr, props.dataSource]);
// console.log('basicTable dataSourceMap', dataSourceMap)
const columnsMap = useMemo(() => {
const result = {} as any;
if (props.columns && props.columns.length > 0) {
props.columns.forEach((columnItem) => {
if (columnItem.dataIndex) {
result[columnItem.dataIndex as string] = {
title: columnItem.title,
dataIndex: columnItem.dataIndex,
value: null,
commonSearch: columnItem.commonSearch,
commonFilter: columnItem.commonFilter,
commonSorter: columnItem.commonSorter,
};
}
});
}
return result;
}, [props.columns]);
// console.log('basicTable columnsMap', columnsMap)
function columnInit(initColumnState) {
return initColumnState;
}
function columnsReducer(
state: IColumnsReducerState,
action: TColumnsReducerAction,
) {
switch (action.type) {
case 'update':
return {
...state,
[action.dataIndex]: {
...state[action.dataIndex],
dataIndex: action.dataIndex,
type: action.updateType,
value: action.value,
},
};
case 'clear':
return columnInit(action.initialState);
default:
return state;
}
}
const [columnsState, columnsDispatch] = useReducer(
columnsReducer,
columnsMap,
columnInit,
);
const debouncedSearchQuery = useDebounce(searchQuery, 300);
useDeepCompareEffect(() => {
// 模糊匹配
let newDataSource = [...tmpDataSource];
if (showSearch && debouncedSearchQuery && debouncedSearchQuery.trim()) {
if (props.onSearch) {
// 使用用户自定义的search回调
props.onSearch(debouncedSearchQuery.trim());
return;
} else {
// 使用组件默认的search回调
const debouncedSearchArr = debouncedSearchQuery
.trim()
.toLowerCase()
.split(' ');
newDataSource = newDataSource.filter((fiterItem, filterIdx) => {
const filterStr = dataSourceMap[filterIdx];
return debouncedSearchArr.every(
(someItem) => filterStr.indexOf(someItem) >= 0,
);
});
}
}
if (queryFormColumns && Object.keys(queryFormValues).length > 0) {
newDataSource = _.filter(newDataSource, (sourceItem) => {
return Object.entries(queryFormValues).every(
([queryKey, queryValue]) => {
if (
(!queryValue && queryValue !== 0) ||
(Array.isArray(queryValue) && queryValue.length === 0)
) {
return true;
}
if (typeof queryValue === 'string') {
return (
sourceItem[queryKey] &&
sourceItem[queryKey].indexOf(queryValue) >= 0
);
}
if (typeof queryValue === 'number') {
return sourceItem[queryKey] === queryValue;
}
if (Array.isArray(queryValue) && queryValue.length > 0) {
return queryValue.indexOf(sourceItem[queryKey]) >= 0;
}
},
);
});
}
setDataSource(newDataSource);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSearchQuery, queryFormValues, props.dataSource]);
const handleChange = (
pagination: any,
filters: any,
sorter: any,
extra: any,
) => {
sorterDispatch({
type: 'update',
value: sorter,
});
Object.entries(filters).forEach(([filterKey, filterValue]) => {
if (columnsMap[filterKey].commonFilter) {
columnsDispatch({
type: 'update',
dataIndex: filterKey,
updateType: 'filter',
value: filterValue,
});
}
});
checkRightHeader(filters, sorter);
if (props.onChange) {
props.onChange(pagination, filters, sorter, extra);
}
};
const checkRightHeader = (filters?: any, sorter?: any, search?: any) => {
const checkSorter = sorter || sorterState;
const checkFilters = filters || columnsState;
const checkSearch = search || columnsState;
const isSearch = Object.values(checkSearch).some((columnItem: any) => {
return !!(columnItem.type === 'search' && columnItem.value);
});
let isFilter = false;
if (filters) {
isFilter = Object.values(checkFilters).some((columnItem: any) => {
return columnItem && columnItem.length > 0;
});
} else {
isFilter = Object.values(checkFilters).some((columnItem: any) => {
return !!(
columnItem.type === 'filter' &&
columnItem.value &&
columnItem.value.length > 0
);
});
}
const isSorter = !!checkSorter.column;
const res = isSearch || isFilter || isSorter;
setShowRightHeader(res);
};
const handleFilterSearch = useCallback(
(
selectedKeys: React.ReactText[] | undefined,
confirm: (() => void) | undefined,
dataIndex: string | number,
) => {
if (confirm) {
confirm();
}
if (selectedKeys && dataIndex) {
columnsDispatch({
type: 'update',
dataIndex,
updateType: 'search',
value: selectedKeys[0],
});
}
},
[],
);
const handleFilterSearchReset = useCallback(
(
clearFilters: ((selectedKeys: string[]) => void) | undefined,
dataIndex: string | number | undefined,
) => {
if (clearFilters) {
clearFilters([]);
}
if (dataIndex) {
columnsDispatch({
type: 'update',
dataIndex,
});
}
},
[],
);
const handleClearAll = () => {
sorterDispatch({ type: 'clear' });
columnsDispatch({ type: 'clear', initialState: columnsMap });
setShowRightHeader(false);
setTimeout(() => {
Object.values(clearFiltersRef.current).forEach((filterItem: any) => {
if (filterItem && filterItem.clearFilters) {
filterItem.clearFilters([]);
}
});
});
};
const handleSortClear = () => {
sorterDispatch({ type: 'clear' });
checkRightHeader(null, {});
};
const handleFilterClear = (columnValue: IColumnsReducerValue) => {
columnsDispatch({
type: 'update',
dataIndex: columnValue.dataIndex,
value: [],
});
const tmpFilters = Object.values(columnsState).map((filterItem: any) => {
if (filterItem.dataIndex === columnValue.dataIndex) {
return {
[filterItem.dataIndex]: [],
};
}
return {
[filterItem.dataIndex]: filterItem.value || [],
};
});
checkRightHeader(tmpFilters, sorterState, columnsState);
};
const handleFilterSearchClear = (columnValue: IColumnsReducerValue) => {
columnsDispatch({
type: 'update',
dataIndex: columnValue.dataIndex,
});
if (
clearFiltersRef.current[columnValue.dataIndex] &&
clearFiltersRef.current[columnValue.dataIndex].clearFilters
) {
clearFiltersRef.current[columnValue.dataIndex].clearFilters([]);
}
checkRightHeader(null, sorterState, {
...columnsState,
[columnValue.dataIndex]: {
title: columnsState[columnValue.dataIndex].title,
dataIndex: columnsState[columnValue.dataIndex].dataIndex,
value: null,
},
});
};
const renderColumns = () => {
// if (!dataSource || (dataSource && dataSource.length === 0)) {
// return props.columns;
// }
const handledColumns = props.columns.map((columnItem) => {
const currentItem = _.cloneDeep(columnItem);
// filter
if (currentItem.commonFilter && !currentItem.filters) {
const filters = _.uniq(_.map(dataSource, columnItem.dataIndex));
currentItem.filters = filters.map((value: string) => {
return {
text: value,
value,
};
});
currentItem.filterIcon = () => <FilterOutlined />;
currentItem.filteredValue =
columnsState[columnItem.dataIndex as string].value;
currentItem.onFilter = (value, record: any) => {
if (currentItem.dataIndex && record[currentItem.dataIndex]) {
return record[currentItem.dataIndex] === value;
}
return false;
};
}
// sort
if (currentItem.commonSorter) {
currentItem.sorter = (aItem: any, bItem: any) => {
const a = aItem[currentItem.dataIndex as string];
const b = bItem[currentItem.dataIndex as string];
// number
const numA = Number(a);
const numB = Number(b);
if (!isNaN(numA) && !isNaN(numB)) {
return numA - numB;
}
// date
const dateA = +new Date(a);
const dateB = +new Date(b);
if (!isNaN(dateA) && !isNaN(dateB)) {
return dateA - dateB;
}
// string
if (typeof a === 'string' && typeof b === 'string') {
return a > b ? 1 : -1;
}
return 0;
};
currentItem.sortOrder =
sorterState.columnKey === currentItem.dataIndex && sorterState.order;
}
// Search
if (currentItem.commonSearch) {
currentItem.filterIcon = () => <SearchOutlined />;
currentItem.onFilterDropdownVisibleChange = (visible: boolean) => {
if (
visible &&
filterSearchInputRef.current &&
filterSearchInputRef.current.select
) {
setTimeout(() => {
if (
filterSearchInputRef.current &&
filterSearchInputRef.current.select
) {
filterSearchInputRef.current.select();
}
return null;
});
}
};
currentItem.filterDropdown = ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => {
clearFiltersRef.current[currentItem.dataIndex as string] = {
clearFilters,
};
return (
<div style={{ padding: 8 }}>
<Input
data-testid='filter-search-input'
autoFocus={true}
ref={(node) => {
filterSearchInputRef.current = node;
}}
placeholder={t('table.filter.search.placeholder')}
value={selectedKeys && selectedKeys[0]}
onChange={(e) => {
if (setSelectedKeys) {
return setSelectedKeys(
e.target.value ? [e.target.value] : [],
);
}
return [];
}}
onPressEnter={() =>
handleFilterSearch(
selectedKeys,
confirm,
currentItem.dataIndex as string,
)
}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Button
type='primary'
onClick={() =>
handleFilterSearch(
selectedKeys,
confirm,
currentItem.dataIndex as string,
)
}
icon='search'
size='small'
data-testid='search-btn-ok'
style={{ width: 90, marginRight: 8 }}
>
{t('table.filter.search.btn.ok')}
</Button>
<Button
onClick={() =>
handleFilterSearchReset(clearFilters, currentItem.dataIndex)
}
size='small'
style={{ width: 90 }}
>
{t('table.filter.search.btn.cancel')}
</Button>
</div>
);
};
let searchWords: any[] = [];
const tmpStateValue =
columnsState[currentItem.dataIndex as string].value;
if (typeof tmpStateValue === 'string') {
searchWords = [tmpStateValue];
}
if (
Array.isArray(tmpStateValue) &&
typeof tmpStateValue[0] === 'string'
) {
searchWords = [tmpStateValue[0]];
}
currentItem.onFilter = (value, record: any) => {
return record[currentItem.dataIndex as string]
.toString()
.toLowerCase()
.includes(value.toLowerCase());
};
if (!currentItem.render) {
currentItem.render = (value, row, index) => {
if (currentItem.searchRender) {
return currentItem.searchRender(
value,
row,
index,
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={searchWords}
autoEscape
textToHighlight={String(value)}
/>,
);
} else {
return (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={searchWords}
autoEscape
textToHighlight={String(value)}
/>
);
}
};
}
}
return currentItem;
});
// console.log('basicTable handledColumns', handledColumns)
return handledColumns;
};
const isSearch = Object.values(columnsState).some((columnItem: any) => {
return !!(columnItem.type === 'search' && columnItem.value);
});
const isFilter = Object.values(columnsState).some((columnItem: any) => {
return !!(
columnItem.type === 'filter' &&
columnItem.value &&
columnItem.value.length > 0
);
});
const handleReload = () => {
if (props.onReload) {
props.onReload();
}
};
const handleSearchChange = (e) => {
const query = e.target.value;
setSearchQuery(query);
};
const handleQueryFormInit = (data) => {
if (!isQueryInit) {
setQueryFormValues(data);
setQueryInit(true);
}
};
const handleQueryFormChange = (data) => {
setQueryFormValues(data);
};
const handleQueryFormReset = (data) => {
setQueryFormValues(data);
};
const renderRightHeader = (params) => {
if (!showFilter) {
return null;
}
return (
<>
<div>
<b
style={{
display: 'inline-block',
marginTop: 3,
}}
>
{t('table.filter.header.title')}
</b>
</div>
{(isSearch || isFilter) &&
Object.values(columnsState as IColumnsReducerState).map(
(columnValue) => {
if (columnValue.type === 'search' && columnValue.value) {
return (
<div
key={`search-header-${columnValue.dataIndex}`}
className={`${params.headerClsPrefix}-item`}
>
<Tooltip
title={
<span>
{columnValue.title}
{':'}
{columnValue.value}
</span>
}
>
<Button
size='small'
className='table-header-item-btn'
onClick={() => handleFilterSearchClear(columnValue)}
>
<span className='table-header-item-btn-content'>
{columnValue.title}
{':'}
{columnValue.value}
</span>
<CloseOutlined />
</Button>
</Tooltip>
</div>
);
}
if (
columnValue.type === 'filter' &&
columnValue.value &&
columnValue.value.length > 0
) {
return (
<div
key={`search-header-${columnValue.dataIndex}`}
className={`${params.headerClsPrefix}-item`}
>
<Tooltip
title={
<span>
{columnValue.title}
{':'}
{columnValue.value.join(',')}
</span>
}
>
<Button
size='small'
className='table-header-item-btn'
onClick={() => handleFilterClear(columnValue)}
>
<span className='table-header-item-btn-content'>
{columnValue.title}
{':'}
{columnValue.value.join(',')}
</span>
<CloseOutlined />
</Button>
</Tooltip>
</div>
);
}
return null;
},
)}
{sorterState.columnKey && sorterState.column && (
<div className={`${params.headerClsPrefix}-item`}>
<Tooltip
title={
<span>
{sorterState.column.title}
{`: ${sorterNames[sorterState.order as TSorterNames]}`}
</span>
}
>
<Button
size='small'
className='table-header-item-btn'
onClick={handleSortClear}
>
<span className='table-header-item-btn-content'>
{sorterState.column.title}
{`: ${sorterNames[sorterState.order as TSorterNames]}`}
</span>
<CloseOutlined />
</Button>
</Tooltip>
</div>
)}
<div className={`${params.headerClsPrefix}-item`}>
<Button
size='small'
type='link'
data-testid='btn-clearall'
onClick={handleClearAll}
>
{t('table.filter.header.btn.clear')}
</Button>
</div>
</>
);
};
const renderSearch = () => {
return (
<Tooltip placement='topLeft' title={searchPlaceholder}>
<Input
data-testid='search-input'
prefix={
reloadBtnPos === 'right' && (
<SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
)
}
suffix={
reloadBtnPos === 'left' && (
<SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
)
}
allowClear={true}
value={searchQuery}
onChange={handleSearchChange}
placeholder={searchPlaceholder}
/>
</Tooltip>
);
};
const renderReloadBtn = () => {
if (reloadBtnType === 'icon') {
const reloadBtnCls = classNames({
[`${prefixCls}-header-loadbtn`]: true,
[`${prefixCls}-header-loadbtn-icon`]: true,
[`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
[`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
});
return (
<ReloadOutlined
onClick={handleReload}
spin={loading}
className={reloadBtnCls}
/>
);
}
if (reloadBtnType === 'btn') {
const reloadBtnCls = classNames({
[`${prefixCls}-header-loadbtn`]: true,
[`${prefixCls}-header-loadbtn-btn`]: true,
[`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
[`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
});
return (
<Button
className={reloadBtnCls}
loading={loading}
icon={<ReloadOutlined />}
data-testid='reload-btn'
onClick={handleReload}
/>
);
}
};
return (
<ConfigProvider {...props.antConfig}>
<div className={tableClassName} style={props.style}>
{queryFormColumns && (
<div className={tableQueryCls}>
<QueryForm
onChange={
isQuerySearchOnChange
? handleQueryFormChange
: handleQueryFormInit
}
onReset={handleQueryFormReset}
onSearch={handleQueryFormChange}
columns={queryFormColumns}
showOptionBtns={showQueryOptionBtns}
showCollapseButton={showQueryCollapseButton}
{...queryFormProps}
/>
</div>
)}
<div className={tableBodyCls}>
{!!props.tableTitle && <h3> {props.tableTitle} </h3>}
{showFullSearch && (
<Row className={`${prefixCls}-header-search`}>
{showSearch ? renderSearch() : <div></div>}
{showReloadBtn2SearchRight && renderReloadBtn()}
</Row>
)}
{/* 自定义格式 */}
{filterType === 'flex' && (
<div
style={{
display: 'flex',
justifyContent: 'flex-start',
marginBottom: '10px',
}}
>
{showReloadBtn2FilterLeft && renderReloadBtn()}
{props.leftHeader}
</div>
)}
{filterType === 'none' && searchPos !== 'right' && (
<Row className={`${prefixCls}-header-filter`}>
{showReloadBtn2FilterLeft && renderReloadBtn()}
<Col
data-testid='left-header'
className={classNames(
`${prefixCls}-header-filter-left`,
props.leftHeader !== undefined &&
`${prefixCls}-header-filter-left-minh`,
)}
>
{props.leftHeader}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
{filterType === 'none' && showSearch && searchPos === 'right' && (
<Row className={`${prefixCls}-header-filter`} align='middle'>
<Col
data-testid='left-header'
className={classNames(
`${prefixCls}-header-filter-left`,
props.leftHeader !== undefined &&
`${prefixCls}-header-filter-left-minh`,
)}
span={13}
>
{showReloadBtn2FilterLeft && renderReloadBtn()}
{props.leftHeader}
</Col>
<Col
span={6}
data-testid='right-header'
className={`${prefixCls}-header-filter-right`}
>
{props.customHeader && (
<div data-testid='custom-header'>{props.customHeader}</div>
)}
</Col>
<Col
data-testid='right-header'
className={`${prefixCls}-header-filter-right`}
span={5}
>
{renderSearch()}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
{filterType === 'line' && (
<Row className={`${prefixCls}-header-filter`}>
{showReloadBtn2FilterLeft && renderReloadBtn()}
<Col
data-testid='right-header'
className={`${prefixCls}-header-filter-line`}
span={24}
>
{showRightHeader &&
renderRightHeader({
headerClsPrefix: `${prefixCls}-header-filter-line`,
})}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
{filterType === 'half' && (
<Row className={`${prefixCls}-header-filter`}>
{showReloadBtn2FilterLeft && renderReloadBtn()}
<Col
data-testid='left-header'
className={classNames(
`${prefixCls}-header-filter-left`,
props.leftHeader !== undefined &&
`${prefixCls}-header-filter-left-minh`,
)}
span={12}
>
{props.leftHeader}
</Col>
<Col
data-testid='right-header'
className={`${prefixCls}-header-filter-right`}
span={12}
>
{showRightHeader &&
renderRightHeader({
headerClsPrefix: `${prefixCls}-header-filter-right`,
})}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
<div className={`${prefixCls}-table-wrapper`}>
<div className={tableContentClassName}>
<Table
loading={loading}
columns={renderColumns()}
dataSource={dataSource}
bordered={false}
rowSelection={rowSelection}
onChange={handleChange}
pagination={{
showTotal: showTotal,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: pageSizeOptions,
...props.pagination,
}}
{...antProps}
/>
{rowSelection && selectRowNum !== undefined && selectRowNum > 0 && (
<div className={`${prefixCls}-table-content-select-num`}>
{t('table.select.num')}
{selectRowNum}
</div>
)}
</div>
</div>
</div>
</div>
</ConfigProvider>
);
}
Example #17
Source File: DashboardReloadAction.tsx From posthog-foss with MIT License | 4 votes |
export function DashboardReloadAction(): JSX.Element {
const { itemsLoading, autoRefresh, refreshMetrics } = useValues(dashboardLogic)
const { refreshAllDashboardItemsManual, setAutoRefresh } = useActions(dashboardLogic)
const [open, setOpen] = useState(false)
return (
<>
<Dropdown.Button
overlay={
<Menu data-attr="auto-refresh-picker" id="auto-refresh-picker">
<div
id="auto-refresh-check"
key="auto-refresh-check"
onClick={(e) => {
e.stopPropagation()
setOpen(true)
setAutoRefresh(!autoRefresh.enabled, autoRefresh.interval)
}}
>
<Tooltip title={`Refresh dashboard automatically`} placement="bottomLeft">
<Checkbox
onChange={(e) => {
e.stopPropagation()
e.preventDefault()
}}
checked={autoRefresh.enabled}
/>
<label
style={{
marginLeft: 10,
cursor: 'pointer',
}}
>
Auto refresh
</label>
</Tooltip>
</div>
<Menu.Divider />
<Menu.ItemGroup title="Refresh interval">
<Radio.Group
onChange={(e) => {
setAutoRefresh(true, parseInt(e.target.value))
}}
value={autoRefresh.interval}
style={{ width: '100%' }}
>
<Space direction="vertical" style={{ width: '100%' }}>
{intervalOptions.map(({ label, value }) => (
<Radio key={value} value={value} style={{ width: '100%' }}>
{label}
</Radio>
))}
</Space>
</Radio.Group>
</Menu.ItemGroup>
</Menu>
}
trigger={['click']}
onClick={() => refreshAllDashboardItemsManual()}
icon={<DownOutlined />}
disabled={itemsLoading}
buttonsRender={([leftButton, rightButton]) => [
React.cloneElement(leftButton as React.ReactElement, { style: { paddingLeft: 10 } }),
rightButton,
]}
visible={open}
onVisibleChange={(toOpen) => setOpen(toOpen)}
>
<span className="dashboard-items-action-icon">
{itemsLoading ? <LoadingOutlined /> : <ReloadOutlined />}
</span>
<span className={clsx('dashboard-items-action-refresh-text', { hidden: itemsLoading })}>
<LastRefreshText />
</span>
<span className={clsx('dashboard-items-action-refresh-text', 'completed', { hidden: !itemsLoading })}>
Refreshed {refreshMetrics.completed} out of {refreshMetrics.total}
</span>
</Dropdown.Button>
</>
)
}
Example #18
Source File: Icon.tsx From html2sketch with MIT License | 4 votes |
IconSymbol: FC = () => {
return (
<Row>
{/*<CaretUpOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
{/*/>*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
{/*/>*/}
{/*<StepBackwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
{/*<StepForwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
<StepForwardOutlined />
<ShrinkOutlined />
<ArrowsAltOutlined />
<DownOutlined />
<UpOutlined />
<LeftOutlined />
<RightOutlined />
<CaretUpOutlined />
<CaretDownOutlined />
<CaretLeftOutlined />
<CaretRightOutlined />
<VerticalAlignTopOutlined />
<RollbackOutlined />
<FastBackwardOutlined />
<FastForwardOutlined />
<DoubleRightOutlined />
<DoubleLeftOutlined />
<VerticalLeftOutlined />
<VerticalRightOutlined />
<VerticalAlignMiddleOutlined />
<VerticalAlignBottomOutlined />
<ForwardOutlined />
<BackwardOutlined />
<EnterOutlined />
<RetweetOutlined />
<SwapOutlined />
<SwapLeftOutlined />
<SwapRightOutlined />
<ArrowUpOutlined />
<ArrowDownOutlined />
<ArrowLeftOutlined />
<ArrowRightOutlined />
<LoginOutlined />
<LogoutOutlined />
<MenuFoldOutlined />
<MenuUnfoldOutlined />
<BorderBottomOutlined />
<BorderHorizontalOutlined />
<BorderInnerOutlined />
<BorderOuterOutlined />
<BorderLeftOutlined />
<BorderRightOutlined />
<BorderTopOutlined />
<BorderVerticleOutlined />
<PicCenterOutlined />
<PicLeftOutlined />
<PicRightOutlined />
<RadiusBottomleftOutlined />
<RadiusBottomrightOutlined />
<RadiusUpleftOutlined />
<RadiusUprightOutlined />
<FullscreenOutlined />
<FullscreenExitOutlined />
<QuestionOutlined />
<PauseOutlined />
<MinusOutlined />
<PauseCircleOutlined />
<InfoOutlined />
<CloseOutlined />
<ExclamationOutlined />
<CheckOutlined />
<WarningOutlined />
<IssuesCloseOutlined />
<StopOutlined />
<EditOutlined />
<CopyOutlined />
<ScissorOutlined />
<DeleteOutlined />
<SnippetsOutlined />
<DiffOutlined />
<HighlightOutlined />
<AlignCenterOutlined />
<AlignLeftOutlined />
<AlignRightOutlined />
<BgColorsOutlined />
<BoldOutlined />
<ItalicOutlined />
<UnderlineOutlined />
<StrikethroughOutlined />
<RedoOutlined />
<UndoOutlined />
<ZoomInOutlined />
<ZoomOutOutlined />
<FontColorsOutlined />
<FontSizeOutlined />
<LineHeightOutlined />
<SortAscendingOutlined />
<SortDescendingOutlined />
<DragOutlined />
<OrderedListOutlined />
<UnorderedListOutlined />
<RadiusSettingOutlined />
<ColumnWidthOutlined />
<ColumnHeightOutlined />
<AreaChartOutlined />
<PieChartOutlined />
<BarChartOutlined />
<DotChartOutlined />
<LineChartOutlined />
<RadarChartOutlined />
<HeatMapOutlined />
<FallOutlined />
<RiseOutlined />
<StockOutlined />
<BoxPlotOutlined />
<FundOutlined />
<SlidersOutlined />
<AndroidOutlined />
<AppleOutlined />
<WindowsOutlined />
<IeOutlined />
<ChromeOutlined />
<GithubOutlined />
<AliwangwangOutlined />
<DingdingOutlined />
<WeiboSquareOutlined />
<WeiboCircleOutlined />
<TaobaoCircleOutlined />
<Html5Outlined />
<WeiboOutlined />
<TwitterOutlined />
<WechatOutlined />
<AlipayCircleOutlined />
<TaobaoOutlined />
<SkypeOutlined />
<FacebookOutlined />
<CodepenOutlined />
<CodeSandboxOutlined />
<AmazonOutlined />
<GoogleOutlined />
<AlipayOutlined />
<AntDesignOutlined />
<AntCloudOutlined />
<ZhihuOutlined />
<SlackOutlined />
<SlackSquareOutlined />
<BehanceSquareOutlined />
<DribbbleOutlined />
<DribbbleSquareOutlined />
<InstagramOutlined />
<YuqueOutlined />
<AlibabaOutlined />
<YahooOutlined />
<RedditOutlined />
<SketchOutlined />
<AccountBookOutlined />
<AlertOutlined />
<ApartmentOutlined />
<ApiOutlined />
<QqOutlined />
<MediumWorkmarkOutlined />
<GitlabOutlined />
<MediumOutlined />
<GooglePlusOutlined />
<AppstoreAddOutlined />
<AppstoreOutlined />
<AudioOutlined />
<AudioMutedOutlined />
<AuditOutlined />
<BankOutlined />
<BarcodeOutlined />
<BarsOutlined />
<BellOutlined />
<BlockOutlined />
<BookOutlined />
<BorderOutlined />
<BranchesOutlined />
<BuildOutlined />
<BulbOutlined />
<CalculatorOutlined />
<CalendarOutlined />
<CameraOutlined />
<CarOutlined />
<CarryOutOutlined />
<CiCircleOutlined />
<CiOutlined />
<CloudOutlined />
<ClearOutlined />
<ClusterOutlined />
<CodeOutlined />
<CoffeeOutlined />
<CompassOutlined />
<CompressOutlined />
<ContactsOutlined />
<ContainerOutlined />
<ControlOutlined />
<CopyrightCircleOutlined />
<CopyrightOutlined />
<CreditCardOutlined />
<CrownOutlined />
<CustomerServiceOutlined />
<DashboardOutlined />
<DatabaseOutlined />
<DeleteColumnOutlined />
<DeleteRowOutlined />
<DisconnectOutlined />
<DislikeOutlined />
<DollarCircleOutlined />
<DollarOutlined />
<DownloadOutlined />
<EllipsisOutlined />
<EnvironmentOutlined />
<EuroCircleOutlined />
<EuroOutlined />
<ExceptionOutlined />
<ExpandAltOutlined />
<ExpandOutlined />
<ExperimentOutlined />
<ExportOutlined />
<EyeOutlined />
<FieldBinaryOutlined />
<FieldNumberOutlined />
<FieldStringOutlined />
<DesktopOutlined />
<DingtalkOutlined />
<FileAddOutlined />
<FileDoneOutlined />
<FileExcelOutlined />
<FileExclamationOutlined />
<FileOutlined />
<FileImageOutlined />
<FileJpgOutlined />
<FileMarkdownOutlined />
<FilePdfOutlined />
<FilePptOutlined />
<FileProtectOutlined />
<FileSearchOutlined />
<FileSyncOutlined />
<FileTextOutlined />
<FileUnknownOutlined />
<FileWordOutlined />
<FilterOutlined />
<FireOutlined />
<FlagOutlined />
<FolderAddOutlined />
<FolderOutlined />
<FolderOpenOutlined />
<ForkOutlined />
<FormatPainterOutlined />
<FrownOutlined />
<FunctionOutlined />
<FunnelPlotOutlined />
<GatewayOutlined />
<GifOutlined />
<GiftOutlined />
<GlobalOutlined />
<GoldOutlined />
<GroupOutlined />
<HddOutlined />
<HeartOutlined />
<HistoryOutlined />
<HomeOutlined />
<HourglassOutlined />
<IdcardOutlined />
<ImportOutlined />
<InboxOutlined />
<InsertRowAboveOutlined />
<InsertRowBelowOutlined />
<InsertRowLeftOutlined />
<InsertRowRightOutlined />
<InsuranceOutlined />
<InteractionOutlined />
<KeyOutlined />
<LaptopOutlined />
<LayoutOutlined />
<LikeOutlined />
<LineOutlined />
<LinkOutlined />
<Loading3QuartersOutlined />
<LoadingOutlined />
<LockOutlined />
<MailOutlined />
<ManOutlined />
<MedicineBoxOutlined />
<MehOutlined />
<MenuOutlined />
<MergeCellsOutlined />
<MessageOutlined />
<MobileOutlined />
<MoneyCollectOutlined />
<MonitorOutlined />
<MoreOutlined />
<NodeCollapseOutlined />
<NodeExpandOutlined />
<NodeIndexOutlined />
<NotificationOutlined />
<NumberOutlined />
<PaperClipOutlined />
<PartitionOutlined />
<PayCircleOutlined />
<PercentageOutlined />
<PhoneOutlined />
<PictureOutlined />
<PoundCircleOutlined />
<PoundOutlined />
<PoweroffOutlined />
<PrinterOutlined />
<ProfileOutlined />
<ProjectOutlined />
<PropertySafetyOutlined />
<PullRequestOutlined />
<PushpinOutlined />
<QrcodeOutlined />
<ReadOutlined />
<ReconciliationOutlined />
<RedEnvelopeOutlined />
<ReloadOutlined />
<RestOutlined />
<RobotOutlined />
<RocketOutlined />
<SafetyCertificateOutlined />
<SafetyOutlined />
<ScanOutlined />
<ScheduleOutlined />
<SearchOutlined />
<SecurityScanOutlined />
<SelectOutlined />
<SendOutlined />
<SettingOutlined />
<ShakeOutlined />
<ShareAltOutlined />
<ShopOutlined />
<ShoppingCartOutlined />
<ShoppingOutlined />
<SisternodeOutlined />
<SkinOutlined />
<SmileOutlined />
<SolutionOutlined />
<SoundOutlined />
<SplitCellsOutlined />
<StarOutlined />
<SubnodeOutlined />
<SyncOutlined />
<TableOutlined />
<TabletOutlined />
<TagOutlined />
<TagsOutlined />
<TeamOutlined />
<ThunderboltOutlined />
<ToTopOutlined />
<ToolOutlined />
<TrademarkCircleOutlined />
<TrademarkOutlined />
<TransactionOutlined />
<TrophyOutlined />
<UngroupOutlined />
<UnlockOutlined />
<UploadOutlined />
<UsbOutlined />
<UserAddOutlined />
<UserDeleteOutlined />
<UserOutlined />
<UserSwitchOutlined />
<UsergroupAddOutlined />
<UsergroupDeleteOutlined />
<VideoCameraOutlined />
<WalletOutlined />
<WifiOutlined />
<BorderlessTableOutlined />
<WomanOutlined />
<BehanceOutlined />
<DropboxOutlined />
<DeploymentUnitOutlined />
<UpCircleOutlined />
<DownCircleOutlined />
<LeftCircleOutlined />
<RightCircleOutlined />
<UpSquareOutlined />
<DownSquareOutlined />
<LeftSquareOutlined />
<RightSquareOutlined />
<PlayCircleOutlined />
<QuestionCircleOutlined />
<PlusCircleOutlined />
<PlusSquareOutlined />
<MinusSquareOutlined />
<MinusCircleOutlined />
<InfoCircleOutlined />
<ExclamationCircleOutlined />
<CloseCircleOutlined />
<CloseSquareOutlined />
<CheckCircleOutlined />
<CheckSquareOutlined />
<ClockCircleOutlined />
<FormOutlined />
<DashOutlined />
<SmallDashOutlined />
<YoutubeOutlined />
<CodepenCircleOutlined />
<AliyunOutlined />
<PlusOutlined />
<LinkedinOutlined />
<AimOutlined />
<BugOutlined />
<CloudDownloadOutlined />
<CloudServerOutlined />
<CloudSyncOutlined />
<CloudUploadOutlined />
<CommentOutlined />
<ConsoleSqlOutlined />
<EyeInvisibleOutlined />
<FileGifOutlined />
<DeliveredProcedureOutlined />
<FieldTimeOutlined />
<FileZipOutlined />
<FolderViewOutlined />
<FundProjectionScreenOutlined />
<FundViewOutlined />
<MacCommandOutlined />
<PlaySquareOutlined />
<OneToOneOutlined />
<RotateLeftOutlined />
<RotateRightOutlined />
<SaveOutlined />
<SwitcherOutlined />
<TranslationOutlined />
<VerifiedOutlined />
<VideoCameraAddOutlined />
<WhatsAppOutlined />
{/*</Col>*/}
</Row>
);
}
Example #19
Source File: index.tsx From S2 with MIT License | 4 votes |
SwitcherContent: React.FC<SwitcherContentProps> = React.memo(
(props) => {
const {
innerContentClassName,
contentTitleText = i18n('行列切换'),
resetText = i18n('恢复默认'),
onToggleVisible,
onSubmit,
sheetType,
...defaultFields
} = props;
const defaultState = getSwitcherState(defaultFields);
const [state, setState] = React.useState<SwitcherState>(defaultState);
const [draggingItemId, setDraggingItemId] = React.useState<string>(null);
const SWITCHER_CONFIG = React.useMemo(getSwitcherConfig, []);
const onBeforeDragStart = (initial: BeforeCapture) => {
setDraggingItemId(initial.draggableId);
};
const onDragEnd = ({ destination, source }: DropResult) => {
// reset dragging item id
setDraggingItemId(null);
// cancelled or drop to where can't drop
if (!destination) {
return;
}
// don't change position
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const updatedState = moveItem(
state[source.droppableId],
state[destination.droppableId],
source,
destination,
);
setState({ ...state, ...updatedState });
};
const onReset = () => {
setState(defaultState);
};
const onConfirm = () => {
onToggleVisible();
onSubmit?.(generateSwitchResult(state));
};
const onVisibleItemChange = (
fieldType: FieldType,
checked: boolean,
id: string,
parentId?: string,
) => {
const updatedState = checkItem(state[fieldType], checked, id, parentId);
setState({
...state,
[fieldType]: updatedState,
});
};
const isNothingChanged = isEqual(defaultState, state);
const displayFieldItems = SWITCHER_FIELDS.filter(
(filed) => sheetType !== 'table' || filed === FieldType.Cols,
);
return (
<DragDropContext
onBeforeCapture={onBeforeDragStart}
onDragEnd={onDragEnd}
>
<div
className={cx(
innerContentClassName,
getSwitcherClassName(CLASS_NAME_PREFIX),
)}
>
<header className={getSwitcherClassName(CLASS_NAME_PREFIX, 'header')}>
{contentTitleText}
</header>
<main
className={cx(
getSwitcherClassName(CLASS_NAME_PREFIX, 'main'),
getMainLayoutClassName(sheetType),
)}
>
{displayFieldItems.map((type) => (
<Dimension
{...defaultFields[type]}
key={type}
fieldType={type}
items={state[type]}
crossRows={shouldCrossRows(sheetType, type)}
droppableType={SWITCHER_CONFIG[type].droppableType}
draggingItemId={draggingItemId}
onVisibleItemChange={onVisibleItemChange}
/>
))}
</main>
<footer className={getSwitcherClassName(CLASS_NAME_PREFIX, 'footer')}>
<Button
type={'text'}
icon={<ReloadOutlined />}
className={getSwitcherClassName(
CLASS_NAME_PREFIX,
'footer',
'reset-button',
)}
disabled={isNothingChanged}
onClick={onReset}
>
{resetText}
</Button>
<div
className={getSwitcherClassName(
CLASS_NAME_PREFIX,
'footer',
'actions',
)}
>
<Button className="action-button" onClick={onToggleVisible}>
{i18n('取消')}
</Button>
<Button
className="action-button"
type="primary"
disabled={isNothingChanged}
onClick={onConfirm}
>
{i18n('确定')}
</Button>
</div>
</footer>
</div>
</DragDropContext>
);
},
)
Example #20
Source File: DynamicDashboardTitle.tsx From iot-center-v2 with MIT License | 4 votes |
DynamicDashboardTitle: React.FC<TDynamicDashboardTitleProps> = (
props
) => {
const {
dashboardKey,
isEditing,
setIsEditing,
onDeleteDashboard,
onEditAccept,
onEditCancel,
onOpenSettings,
onReloadDashboard,
// newName,
// setNewName,
} = props
const editable = (
<div style={{width: '100%'}}>
{dashboardKey}{' '}
{/*
<Input
value={newName}
onChange={(e) => setNewName(e.target.value)}
style={{width: 'auto'}}
/>
*/}
<Tooltip title={'Cancel editing'}>
<Button
size="small"
type="text"
icon={<CloseOutlined />}
onClick={onEditCancel}
></Button>
</Tooltip>
<Tooltip title={'Save changes'} color="green">
<Button
size="small"
type="text"
style={{color: 'green'}}
icon={<CheckOutlined />}
onClick={onEditAccept}
></Button>
</Tooltip>
<Tooltip title={'Delete dashboard'} color="red">
<Button
size="small"
type="text"
icon={<DeleteOutlined />}
onClick={onDeleteDashboard}
danger
></Button>
</Tooltip>
<Tooltip title="Dashboard settings" color="#4040ad">
<Button
size="small"
type="text"
style={{color: '#4040ad'}}
icon={<SettingOutlined />}
onClick={onOpenSettings}
></Button>
</Tooltip>
</div>
)
const fixed = (
<>
{dashboardKey}{' '}
<Tooltip title={'Edit dashboard'}>
<Button
size="small"
type="text"
icon={<EditOutlined />}
onClick={() => setIsEditing(true)}
></Button>
</Tooltip>
<Tooltip title={'Reload dashboard'}>
<Button
size="small"
type="text"
icon={<ReloadOutlined />}
onClick={onReloadDashboard}
></Button>
</Tooltip>
</>
)
return <>{isEditing ? editable : fixed}</>
}
Example #21
Source File: EventsList.tsx From jitsu with MIT License | 4 votes |
EventsList: React.FC<{
type: EventType
filterOptions: FilterOption[]
}> = ({ type, filterOptions }) => {
const statusOptions = [
{ label: "All", value: null },
{ label: "Error", value: "error" },
]
const listInnerRef = useRef()
const location = useLocation()
const params = new URLSearchParams(location.search)
const [autoReload, setAutoReload] = useState(true)
const [selectedEvent, setSelectedEvent] = useState(null)
const [events, setEvents] = useState<Event[]>([])
const [filteredEvents, setFilteredEvents] = useState<Event[]>([])
const [term, setTerm] = useState(params.get("q"))
const [idFilter, setIdFilter] = useState(
filterOptions.find(f => f.value === params.get("id"))?.value ?? filterOptions[0]?.value
)
const [statusFilter, setStatusFilter] = useState(
statusOptions.find(f => f.value === params.get("status"))?.value ?? statusOptions[0]?.value
)
const [reloadCount, setReloadCount] = useState(0)
const services = useServices()
const history = useHistory()
const destinationsMap: Record<string, DestinationData> = destinationsStore.listIncludeHidden.reduce((index, dst) => {
index[dst._uid] = dst
return index
}, {})
useEffect(() => {
if (!idFilter) {
history.push({ search: null })
return
}
let queryParams = omitBy({ type, id: idFilter, status: statusFilter }, isNull)
if (term) {
queryParams["q"] = term
}
history.push({ search: new URLSearchParams(queryParams).toString() })
}, [idFilter, statusFilter, term])
const { data, error } = useLoaderAsObject(() => {
if (!idFilter) {
return null
}
const ids = type === EventType.Destination ? `${services.activeProject.id}.${idFilter}` : idFilter
setSelectedEvent(null)
return services.backendApiClient
.get(
`/events/cache?project_id=${services.activeProject.id}&limit=500&namespace=${type}&ids=${ids}&status=${
statusFilter ?? ""
}`,
{ proxy: true }
)
.then(events => {
return { events, id: idFilter }
})
}, [idFilter, statusFilter, reloadCount])
useEffect(() => {
const interval = setInterval(() => {
if (!autoReload || selectedEvent) {
return
}
setReloadCount(reloadCount + 1)
}, 15000)
return () => clearInterval(interval)
}, [autoReload, selectedEvent, reloadCount])
const filterByTerm = (events, term) => {
return term ? events.filter(i => JSON.stringify(i.rawJson).indexOf(term) !== -1) : events
}
const search = term => {
setTerm(term)
setFilteredEvents(filterByTerm(events, term))
}
useEffect(() => {
const initialEvents = error || !data ? [] : processEvents(type, data)
setEvents(initialEvents)
setFilteredEvents(filterByTerm(initialEvents, term))
}, [error, data])
if (!filterOptions.length) {
return <NoDataFlowing showHint={true} />
}
const filters = (
<>
<div className={`mb-6 flex ${styles.filters}`}>
<SelectFilter
className="mr-5"
label={type === EventType.Token ? "API Key" : "Destination"}
initialValue={idFilter}
options={filterOptions}
onChange={option => {
setIdFilter(option.value)
}}
/>
<SelectFilter
className="mr-5"
label="Status"
initialValue={statusFilter}
options={statusOptions}
onChange={option => {
setStatusFilter(option.value)
}}
/>
<Button
size="large"
type="primary"
className={styles.reloadBtn}
onClick={() => {
setReloadCount(count => count + 1)
}}
>
<ReloadOutlined /> Reload
</Button>
</div>
<Input className="w-full" placeholder="Filter" value={term} onChange={e => search(e.target.value)} />
</>
)
const eventStatusMessage = event => {
const error = event.status === EventStatus.Error
const skip = event.status === EventStatus.Skip
if (type === EventType.Token) {
if (skip) {
return `Skip`
}
return error ? event.rawJson.error ?? "Error" : "Success"
}
return error
? "Failed - at least one destination load is failed"
: skip
? "Skipped - event was not sent to destination"
: "Success - successfully sent to destination"
}
if (error) {
return (
<div className="w-full">
{filters}
<CenteredError error={error} />
</div>
)
} else if (!data) {
return (
<div className="w-full">
{filters}
<CenteredSpin />
</div>
)
}
const { last_minute_limited, cache_capacity_per_interval, interval_seconds } = data?.events
const alert =
last_minute_limited > 0 ? (
<div className="mt-4">
<Alert
message={`This isn't a full list of all events. Jitsu doesn't cache all events, but the only ${cache_capacity_per_interval} event per ${interval_seconds} seconds. Other ${last_minute_limited} events from the last minute have been being processed and stored to the destinations but haven't been saved into the cache.`}
type="warning"
/>
</div>
) : null
const onScroll = () => {
if (!listInnerRef.current) {
return
}
const { scrollTop } = listInnerRef.current
const startAutoReload = scrollTop === 0
if (startAutoReload === autoReload) {
return
}
setAutoReload(startAutoReload)
}
return (
<>
{filters}
{alert}
<div
className={`mt-3 transition-all duration-300 ${styles.autoReloadInfo} ${
autoReload && !selectedEvent ? "" : "opacity-0"
}`}
>
<ReloadOutlined spin={true} /> Auto reload is enabled. <a onClick={() => setAutoReload(false)}>Disable</a>
</div>
<div className={styles.eventsList} ref={listInnerRef} onScroll={onScroll}>
{!filteredEvents.length ? <NoDataFlowing showHint={false} /> : null}
{filteredEvents.map(event => {
const active = event.eventId === selectedEvent
return (
<div key={event.eventId}>
<div
className={`overflow-hidden w-full flex flex-row border-b border-secondaryText border-opacity-50 items-center cursor-pointer h-12 ${
selectedEvent === event.eventId ? "bg-bgSecondary" : "hover:bg-bgComponent"
}`}
key="header"
onClick={() => setSelectedEvent(active ? null : event.eventId)}
>
<div className="w-6 flex items-center justify-center px-3 text-lg" key="icon">
<Tooltip title={eventStatusMessage(event)}>
{event.status === EventStatus.Error ? (
<ExclamationCircleOutlined className="text-error" />
) : event.status === EventStatus.Pending || event.status === EventStatus.Skip ? (
<MinusCircleOutlined className="text-warning" />
) : (
<CheckCircleOutlined className="text-success" />
)}
</Tooltip>
</div>
<div
className={`text-xxs whitespace-nowrap text-secondaryText px-1 ${styles.timestampColumn}`}
key="time"
>
<div>{event.timestamp.format("YYYY-MM-DD HH:mm:ss")} UTC</div>
<div className="text-xxs">{event.timestamp.fromNow()}</div>
</div>
<div
className="pl-4 text-3xs text-secondaryText font-monospace overflow-hidden overflow-ellipsis h-12 leading-4 flex-shrink"
key="json"
>
{event.rawJson.malformed ? event.rawJson.malformed : JSON.stringify(event.rawJson, null, 2)}
</div>
<div
className={cn(
"w-12 text-testPale flex items-center justify-center px-2 text-xl transition-transform duration-500",
styles.expandBtn,
active && "transform rotate-90"
)}
key="expand"
>
<RightCircleOutlined />
</div>
</div>
<div key="details">
{active && <EventsView event={event} allDestinations={destinationsMap} className="pb-6" />}
</div>
</div>
)
})}
</div>
</>
)
}
Example #22
Source File: ShapeComponent.tsx From ant-simple-draw with MIT License | 4 votes |
Shape: FC<ShapeType> = memo(function Shape({ children, style, element, defaultStyle }) {
const currentElement = useRef<HTMLDivElement | null>(null);
const [cursors, setCursors] = useState<Record<pointType, string>>();
const [curComponent, active] = useSelector(
createSelector([(state: storeType) => state.component], (component) => {
const active = element?.componentId === component.curComponent?.componentId ? true : false;
return [component.curComponent, active] as const;
}),
);
const dispatch = useDispatch();
/**
@description 拖拽图形
*/
const handleMouseDownOnShape: React.MouseEventHandler<HTMLDivElement> = async (e) => {
e.stopPropagation();
dispatch(isClickComponentAction(true));
dispatch(curComponentAction(element));
setCursors(getCursor(element));
const pos = { ...defaultStyle };
const startY = e.clientY;
const startX = e.clientX;
// 如果直接修改属性,值的类型会变为字符串,所以要转为数值型
const startTop = Number(pos.top);
const startLeft = Number(pos.left);
// 如果元素没有移动,则不保存快照
let hasMove = false;
const move = async (moveEvent: MouseEvent) => {
hasMove = true;
const curX = moveEvent.clientX;
const curY = moveEvent.clientY;
pos.top = curY - startY + startTop;
pos.left = curX - startX + startLeft;
await dispatch(setShapeStyleAction(pos));
// 触发元素移动事件,用于显示标线、吸附功能
// curY - startY > 0 true 表示向下移动 false 表示向上移动
// curX - startX > 0 true 表示向右移动 false 表示向左移动
await dispatch(
showMarkLineAction({
isDownward: curY - startY > 0,
isRightward: curX - startX > 0,
}),
);
};
const up = () => {
hasMove && dispatch(recordSnapshot());
// 触发元素停止移动事件,用于隐藏标线
dispatch(hideMarkLineAction());
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
};
/**
* @description 动态的布局连接桩
*/
const getPointStyle = (point: string) => {
const { width, height } = defaultStyle as { width: number; height: number };
const hasT = /t/.test(point);
const hasB = /b/.test(point);
const hasL = /l/.test(point);
const hasR = /r/.test(point);
let newLeft = 0;
let newTop = 0;
// 四个角的点
if (point.length === 2) {
newLeft = hasL ? 0 : width;
newTop = hasT ? 0 : height;
} else {
// 上下两点的点,宽度居中
if (hasT || hasB) {
newLeft = width / 2;
newTop = hasT ? 0 : height;
}
// 左右两边的点,高度居中
if (hasL || hasR) {
newLeft = hasL ? 0 : width;
newTop = Math.floor(height / 2);
}
}
const eleStyle = {
marginLeft: hasR ? '-4px' : '-4px',
marginTop: '-4px',
left: `${newLeft}px`,
top: `${newTop}px`,
cursor: cursors && (cursors as any)[point],
};
return eleStyle;
};
/**
* @description 更具不同的八个点,显示不同的cursor属性值
*/
const getCursor = (curComponent: templateDataType) => {
const rotate = mod360(Number(curComponent?.style?.rotate)); // 取余 360
const result = Object.create({});
let lastMatchIndex = -1; // 从上一个命中的角度的索引开始匹配下一个,降低时间复杂度
pointList.forEach((point) => {
const angle = mod360(initialAngle[point] + rotate);
const len = angleToCursor.length;
while (true) {
lastMatchIndex = (lastMatchIndex + 1) % len;
const angleLimit = angleToCursor[lastMatchIndex];
if (angle < 23 || angle >= 338) {
result[point] = 'nw-resize';
return;
}
if (angleLimit.start <= angle && angle < angleLimit.end) {
result[point] = angleLimit.cursor + '-resize';
return;
}
}
});
return result;
};
/**
* @description 八个点,每个点按下的事件
*/
const handleMouseDownOnPoint = (point: pointType, e: MergeEvent) => {
e.stopPropagation();
e.preventDefault();
dispatch(isClickComponentAction(true));
const style = { ...defaultStyle } as Required<MergeCSSProperties>;
// 组件宽高比
const proportion = Number(style.width) / Number(style.height);
// 组件中心点
const center = {
x: Number(style.left) + Number(style.width) / 2,
y: Number(style.top) + Number(style.height) / 2,
};
// 获取画布位移信息
const editorRectInfo = $('#editor').getBoundingClientRect();
// 获取 point 与实际拖动基准点的差值
const pointRect = e.target.getBoundingClientRect();
// 当前点击圆点相对于画布的中心坐标
const curPoint = {
x: Math.round(pointRect.left - editorRectInfo.left + e.target.offsetWidth / 2),
y: Math.round(pointRect.top - editorRectInfo.top + e.target.offsetHeight / 2),
};
// 获取对称点的坐标
const symmetricPoint = {
x: center.x - (curPoint.x - center.x),
y: center.y - (curPoint.y - center.y),
};
// 是否需要保存快照
let needSave = false;
let isFirst = true;
const move = (moveEvent: MouseEvent) => {
// 第一次点击时也会触发 move,所以会有“刚点击组件但未移动,组件的大小却改变了”的情况发生
// 因此第一次点击时不触发 move 事件
if (isFirst) {
isFirst = false;
return;
}
needSave = true;
const curPositon = {
x: moveEvent.clientX - editorRectInfo.left,
y: moveEvent.clientY - editorRectInfo.top,
};
calculateComponentPositonAndSize(point, style, curPositon, proportion, false, {
center,
curPoint,
symmetricPoint,
});
dispatch(setShapeStyleAction(style));
};
const up = () => {
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
needSave && dispatch(recordSnapshot());
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
};
/**
* @description 图形旋转
*/
const handleRotate: React.MouseEventHandler<HTMLDivElement> = (e) => {
e.preventDefault();
e.stopPropagation();
dispatch(isClickComponentAction(true));
const pos = { ...defaultStyle };
const startY = e.clientY;
const startX = e.clientX;
const startRotate = Number(pos.rotate);
// 获取元素中心点位置
const rect = currentElement.current!.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
// 旋转前的角度
const rotateDegreeBefore = Math.atan2(startY - centerY, startX - centerX) / (Math.PI / 180);
// 如果元素没有移动,则不保存快照
let hasMove = false;
const move = (moveEvent: MouseEvent) => {
hasMove = true;
const curX = moveEvent.clientX;
const curY = moveEvent.clientY;
// 旋转后的角度
const rotateDegreeAfter = Math.atan2(curY - centerY, curX - centerX) / (Math.PI / 180);
// 获取旋转的角度值
pos.rotate = startRotate + rotateDegreeAfter - rotateDegreeBefore;
// 修改当前组件样式
dispatch(setShapeStyleAction(pos));
};
const up = () => {
hasMove && dispatch(recordSnapshot());
document.removeEventListener('mousemove', move);
document.removeEventListener('mouseup', up);
// 根据旋转角度重新获取光标位置,cursor
setCursors(getCursor(element));
};
document.addEventListener('mousemove', move);
document.addEventListener('mouseup', up);
};
const handleClick: React.MouseEventHandler<HTMLDivElement> = (e) => {
// 阻止向父组件冒泡
e.stopPropagation();
e.preventDefault();
dispatch(hideContextMenuAction());
};
return (
<div
className={`${styles.shape} ${active ? styles.shapeActive : null}`}
style={style}
onMouseDown={handleMouseDownOnShape}
onClick={handleClick}
ref={currentElement}
>
{active && (
<>
<ReloadOutlined className={styles.shapeRotate} onMouseDown={handleRotate} />
{pointList.map((item, index) => (
<div
key={index}
className={styles.shapePoint}
style={getPointStyle(item)}
onMouseDown={(e: any) => handleMouseDownOnPoint(item, e)}
></div>
))}
</>
)}
{children}
</div>
);
})
Example #23
Source File: index.tsx From fe-v5 with Apache License 2.0 | 4 votes |
function Chart(props: Props) {
const { t } = useTranslation();
const { options, barControl, rightBar, title } = props;
const [refreshing, setRefreshing] = useState(false);
const chartRef = useRef(null);
const location = useLocation();
const { metric, description = '', tags, range, limit, idents, classpath_id, classpath_prefix, prome_ql, yplotline, xplotline, step } = options;
const [privateTags, setPrivateTags] = useState<TagType[]>([]);
const [multi, setMulti] = useState(false);
const [sort, setSort] = useState<'desc' | 'asc'>('desc');
const [tooltipFormat, setTooltipFormat] = useState<'origin' | 'short'>('origin');
const [instance, setInstance] =
useState<{
destroy: Function;
update: Function;
options: {
yAxis: object;
xAxis: object;
};
} | null>(null); // transfer Param and RangeItem into timestamp
const formatDate = (r?: Range) => {
let newR = r || range;
if (newR) {
if (isAbsoluteRange(newR)) {
const { start, end } = newR;
return {
start,
end,
};
} else {
return generateTimeStampRange(newR);
}
}
return {
start: 0,
end: 0,
};
};
const { start, end } = formatDate(range);
const initChart = (privateTags: TagType[] = []) => {
let newTags = privateTags;
if (tags && tags.length > 0) {
newTags = tags.concat(newTags);
}
let params = Array.isArray(metric)
? metric.map((item) => {
return {
metric: item,
classpath_id,
classpath_prefix: classpath_prefix === undefined ? undefined : classpath_prefix ? 1 : 0,
prome_ql,
tags: newTags && newTags.length > 0 ? newTags : undefined,
idents,
};
})
: Array.isArray(prome_ql)
? prome_ql.map((item) => {
return {
metric,
classpath_id,
classpath_prefix: classpath_prefix === undefined ? undefined : classpath_prefix ? 1 : 0,
prome_ql: item,
tags: newTags && newTags.length > 0 ? newTags : undefined,
idents,
};
})
: [
{
metric,
classpath_id,
classpath_prefix: classpath_prefix ? 1 : 0,
prome_ql,
tags: newTags && newTags.length > 0 ? newTags : undefined,
idents,
},
];
// GetData({
// params,
// start,
// end,
// limit,
// step,
// }).then((data) => {
// const dataY: DataSource[] = [];
// data.dat.forEach((dataItem) => {
// dataY.push({
// name: dataItem.metric,
// data: dataItem.values.map((item) => item.v),
// });
// });
// const series: Array<any> = [];
// data.dat.forEach((dataItem) => {
// const { metric, values, tags } = dataItem;
// const seriesData = values.map((item) => {
// return {
// timestamp: item.t * 1000,
// value: item.v,
// };
// });
// series.push({
// name: (metric ? `【${metric}】` : '') + tags,
// data: seriesData,
// });
// });
// // if (chartRef.current) {
// // // @ts-ignore
// // chartRef.current.innerHTML = '';
// // }
// let graphOption = instance
// ? {
// series: series,
// tooltip: {
// precision: tooltipFormat,
// shared: multi,
// sharedSortDirection: sort,
// },
// // 必须xAxis和yAxis必须将属性返回
// yAxis: {
// ...instance.options.yAxis,
// plotLines: yplotline
// ? [
// {
// value: yplotline,
// color: 'red',
// },
// ]
// : undefined,
// },
// xAxis: {
// ...instance.options.xAxis,
// plotLines: xplotline
// ? [
// {
// value: xplotline * 1000,
// color: 'red',
// },
// ]
// : undefined,
// },
// }
// : {
// timestamp: 'x',
// xkey: 'timestamp',
// ykey: 'value',
// chart: {
// renderTo: chartRef.current,
// },
// yAxis: {
// plotLines: yplotline
// ? [
// {
// value: yplotline,
// color: 'red',
// },
// ]
// : undefined,
// },
// xAxis: {
// plotLines: xplotline
// ? [
// {
// value: xplotline * 1000,
// color: 'red',
// },
// ]
// : undefined,
// },
// series: series,
// tooltip: {
// precision: tooltipFormat,
// shared: multi,
// sharedSortDirection: sort,
// },
// };
// if (instance) {
// instance.update(graphOption);
// } else {
// setInstance(new TsGraph(graphOption));
// }
// });
};
useEffect(() => {
initChart(privateTags);
}, [options, multi, sort, tooltipFormat]);
// each chart is mounted once, when props and state change, the instance will update.
// so below hook only run once.
useEffect(() => {
return () => {
instance && instance.destroy();
};
}, [instance]);
const handleRefresh = (e) => {
if (refreshing) return;
setRefreshing(true);
initChart(privateTags); //需要将选择的过滤器传进去
setTimeout(() => {
setRefreshing(false);
}, 1000);
};
const handleChartTagsChange = (e: TagType[]) => {
setPrivateTags(e);
initChart(e);
};
const handleMultiChange = (e) => {
setMulti(e.target.checked);
};
const handleOrderSortChange = (bool) => {
setSort(bool ? 'desc' : 'asc');
};
const handleTooltipFormat = (e) => {
setTooltipFormat(e.target.checked ? 'short' : 'origin');
};
const renderMultiOrSort = (
<>
<Tooltip title={t('tooltip中展示所有曲线的值')}>
<Checkbox onChange={handleMultiChange}>Multi</Checkbox>
</Tooltip>
<Tooltip
title={
<>
<span>{t('SI格式化:')}</span>
<a type='link' href='https://en.wikipedia.org/wiki/Metric_prefix#List_of_SI_prefixes' target='_blank'>
{t('文档')}
</a>
</>
}
>
<Checkbox onChange={handleTooltipFormat}>Format</Checkbox>
</Tooltip>
<OrderSort onChange={handleOrderSortChange} />
</>
);
return (
<div className='chart-wrapper'>
{(title || rightBar || description || metric) && (
<div className='chart-title'>
{title ? (
<div className='chart-title-label'>{title}</div>
) : (
<div className='chart-title-label'>
{metric} {description}
</div>
)}
<div className='chart-title-right-bar'>
{rightBar}
{!location.pathname.startsWith('/chart/') && (
<Button
type='link'
size='small'
onClick={async (e) => {
e.preventDefault();
let { dat: ids } = await SetTmpChartData([{ configs: JSON.stringify({ title, options, barControl }) }]);
window.open('/chart/' + ids);
}}
>
<ShareAltOutlined />
</Button>
)}
</div>
</div>
)}
{!barControl && (
<div className='chart-filter'>
<ReloadOutlined className='refresh' spin={refreshing} onClick={handleRefresh} />
{renderMultiOrSort}
{!prome_ql && (
<TagFilterForChart
options={{
...options,
start,
end,
idents,
}}
onChange={handleChartTagsChange}
/>
)}
</div>
)}
{barControl === 'multiOrSort' && <div className='chart-filter'>{renderMultiOrSort}</div>}
<div ref={chartRef} className='chart-content'></div>
</div>
);
}
Example #24
Source File: HTTPFlowTable.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
HTTPFlowTable: React.FC<HTTPFlowTableProp> = (props) => {
const [data, setData, getData] = useGetState<HTTPFlow[]>([])
const [params, setParams] = useState<YakQueryHTTPFlowRequest>(
props.params || {SourceType: "mitm"}
)
const [pagination, setPagination] = useState<PaginationSchema>({
Limit: OFFSET_LIMIT,
Order: "desc",
OrderBy: "created_at",
Page: 1
});
// const [autoReload, setAutoReload, getAutoReload] = useGetState(false);
const autoReloadRef = useRef<boolean>(false);
const autoReload = autoReloadRef.current;
const setAutoReload = (b: boolean) => {
autoReloadRef.current = b
};
const getAutoReload = () => autoReloadRef.current;
const [total, setTotal] = useState<number>(0)
const [loading, setLoading] = useState(false)
const [selected, setSelected, getSelected] = useGetState<HTTPFlow>()
const [_lastSelected, setLastSelected, getLastSelected] = useGetState<HTTPFlow>()
const [compareLeft, setCompareLeft] = useState<CompateData>({content: '', language: 'http'})
const [compareRight, setCompareRight] = useState<CompateData>({content: '', language: 'http'})
const [compareState, setCompareState] = useState(0)
const [tableContentHeight, setTableContentHeight, getTableContentHeight] = useGetState<number>(0);
// 用于记录适合
const [_scrollY, setScrollYRaw, getScrollY] = useGetState(0)
const setScrollY = useThrottleFn(setScrollYRaw, {wait: 300}).run
// 如果这个大于等于 0 ,就 Lock 住,否则忽略
const [_trigger, setLockedScroll, getLockedScroll] = useGetState(-1);
const lockScrollTimeout = (size: number, timeout: number) => {
setLockedScroll(size)
setTimeout(() => setLockedScroll(-1), timeout)
}
const tableRef = useRef(null)
const ref = useHotkeys('ctrl+r, enter', e => {
const selected = getSelected()
if (selected) {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {
isHttps: selected?.IsHTTPS,
request: new Buffer(selected.Request).toString()
}
})
}
})
// 使用上下箭头
useHotkeys("up", () => {
setLastSelected(getSelected())
const data = getData();
if (data.length <= 0) {
return
}
if (!getSelected()) {
setSelected(data[0])
return
}
const expected = parseInt(`${parseInt(`${(getSelected()?.Id as number)}`) + 1}`);
// 如果上点的话,应该是选择更新的内容
for (let i = 0; i < data.length; i++) {
let current = parseInt(`${data[i]?.Id}`);
if (current === expected) {
setSelected(data[i])
return
}
}
setSelected(undefined)
})
useHotkeys("down", () => {
setLastSelected(getSelected())
const data = getData();
if (data.length <= 0) {
return
}
if (!getSelected()) {
setSelected(data[0])
return
}
// 如果上点的话,应该是选择更新的内容
for (let i = 0; i < data.length; i++) {
if (data[i]?.Id == (getSelected()?.Id as number) - 1) {
setSelected(data[i])
return
}
}
setSelected(undefined)
})
// 向主页发送对比数据
useEffect(() => {
if (compareLeft.content) {
const params = {info: compareLeft, type: 1}
setCompareState(compareState === 0 ? 1 : 0)
ipcRenderer.invoke("add-data-compare", params)
}
}, [compareLeft])
useEffect(() => {
if (compareRight.content) {
const params = {info: compareRight, type: 2}
setCompareState(compareState === 0 ? 2 : 0)
ipcRenderer.invoke("add-data-compare", params)
}
}, [compareRight])
const update = useMemoizedFn((
page?: number,
limit?: number,
order?: string,
orderBy?: string,
sourceType?: string,
noLoading?: boolean
) => {
const paginationProps = {
Page: page || 1,
Limit: limit || pagination.Limit,
Order: order || "desc",
OrderBy: orderBy || "id"
}
if (!noLoading) {
setLoading(true)
// setAutoReload(false)
}
// yakQueryHTTPFlow({
// SourceType: sourceType, ...params,
// Pagination: {...paginationProps},
// })
ipcRenderer
.invoke("QueryHTTPFlows", {
SourceType: sourceType,
...params,
Pagination: {...paginationProps}
})
.then((rsp: YakQueryHTTPFlowResponse) => {
setData((rsp?.Data || []))
setPagination(rsp.Pagination)
setTotal(rsp.Total)
})
.catch((e: any) => {
failed(`query HTTP Flow failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 300))
})
const getNewestId = useMemoizedFn(() => {
let max = 0;
(getData() || []).forEach(e => {
const id = parseInt(`${e.Id}`)
if (id >= max) {
max = id
}
})
return max
})
const getOldestId = useMemoizedFn(() => {
if (getData().length <= 0) {
return 0
}
let min = parseInt(`${getData()[0].Id}`);
(getData() || []).forEach(e => {
const id = parseInt(`${e.Id}`)
if (id <= min) {
min = id
}
})
return min
})
// 第一次启动的时候加载一下
useEffect(() => {
update(1)
}, [])
const scrollTableTo = useMemoizedFn((size: number) => {
if (!tableRef || !tableRef.current) return
const table = tableRef.current as unknown as {
scrollTop: (number) => any,
scrollLeft: (number) => any,
}
table.scrollTop(size)
})
const scrollUpdateTop = useDebounceFn(useMemoizedFn(() => {
const paginationProps = {
Page: 1,
Limit: OFFSET_STEP,
Order: "desc",
OrderBy: "id"
}
const offsetId = getNewestId()
console.info("触顶:", offsetId)
// 查询数据
ipcRenderer
.invoke("QueryHTTPFlows", {
SourceType: "mitm",
...params,
AfterId: offsetId, // 用于计算增量的
Pagination: {...paginationProps}
})
.then((rsp: YakQueryHTTPFlowResponse) => {
const offsetDeltaData = (rsp?.Data || [])
if (offsetDeltaData.length <= 0) {
// 没有增量数据
return
}
setLoading(true)
let offsetData = offsetDeltaData.concat(data);
if (offsetData.length > MAX_ROW_COUNT) {
offsetData = offsetData.splice(0, MAX_ROW_COUNT)
}
setData(offsetData);
scrollTableTo((offsetDeltaData.length + 1) * ROW_HEIGHT)
})
.catch((e: any) => {
failed(`query HTTP Flow failed: ${e}`)
})
.finally(() => setTimeout(() => setLoading(false), 200))
}), {wait: 600, leading: true, trailing: false}).run
const scrollUpdateButt = useDebounceFn(useMemoizedFn((tableClientHeight: number) => {
const paginationProps = {
Page: 1,
Limit: OFFSET_STEP,
Order: "desc",
OrderBy: "id"
}
const offsetId = getOldestId();
console.info("触底:", offsetId)
// 查询数据
ipcRenderer
.invoke("QueryHTTPFlows", {
SourceType: "mitm",
...params,
BeforeId: offsetId, // 用于计算增量的
Pagination: {...paginationProps}
})
.then((rsp: YakQueryHTTPFlowResponse) => {
const offsetDeltaData = (rsp?.Data || [])
if (offsetDeltaData.length <= 0) {
// 没有增量数据
return
}
setLoading(true)
const originDataLength = data.length;
let offsetData = data.concat(offsetDeltaData);
let metMax = false
const originOffsetLength = offsetData.length;
if (originOffsetLength > MAX_ROW_COUNT) {
metMax = true
offsetData = offsetData.splice(originOffsetLength - MAX_ROW_COUNT, MAX_ROW_COUNT)
}
setData(offsetData);
setTimeout(() => {
if (!metMax) {
// 没有丢结果的裁剪问题
scrollTableTo((originDataLength + 1) * ROW_HEIGHT - tableClientHeight)
} else {
// 丢了结果之后的裁剪计算
const a = originOffsetLength - offsetDeltaData.length;
scrollTableTo((originDataLength + 1 + MAX_ROW_COUNT - originOffsetLength) * ROW_HEIGHT - tableClientHeight)
}
}, 50)
})
.catch((e: any) => {
failed(`query HTTP Flow failed: ${e}`)
}).finally(() => setTimeout(() => setLoading(false), 60))
}), {wait: 600, leading: true, trailing: false}).run
const sortFilter = useMemoizedFn((column: string, type: any) => {
const keyRelation: any = {
UpdatedAt: "updated_at",
BodyLength: "body_length",
StatusCode: "status_code"
}
if (column && type) {
update(1, OFFSET_LIMIT, type, keyRelation[column])
} else {
update(1, OFFSET_LIMIT)
}
})
// 这是用来设置选中坐标的,不需要做防抖
useEffect(() => {
if (!getLastSelected() || !getSelected()) {
return
}
const lastSelected = getLastSelected() as HTTPFlow;
const up = parseInt(`${lastSelected?.Id}`) < parseInt(`${selected?.Id}`)
// if (up) {
// console.info("up")
// } else {
// console.info("down")
// }
// console.info(lastSelected.Id, selected?.Id)
const screenRowCount = Math.floor(getTableContentHeight() / ROW_HEIGHT) - 1
if (!autoReload) {
let count = 0;
const data = getData();
for (let i = 0; i < data.length; i++) {
if (data[i].Id != getSelected()?.Id) {
count++
} else {
break
}
}
let minCount = count
if (minCount < 0) {
minCount = 0
}
const viewHeightMin = getScrollY() + tableContentHeight
const viewHeightMax = getScrollY() + tableContentHeight * 2
const minHeight = minCount * ROW_HEIGHT;
const maxHeight = minHeight + tableContentHeight
const maxHeightBottom = minHeight + tableContentHeight + 3 * ROW_HEIGHT
// console.info("top: ", minHeight, "maxHeight: ", maxHeight, "maxHeightBottom: ", maxHeightBottom)
// console.info("viewTop: ", viewHeightMin, "viewButtom: ", viewHeightMax)
if (maxHeight < viewHeightMin) {
// 往下滚动
scrollTableTo(minHeight)
return
}
if (maxHeightBottom > viewHeightMax) {
// 上滚动
const offset = minHeight - (screenRowCount - 2) * ROW_HEIGHT;
// console.info(screenRowCount, minHeight, minHeight - (screenRowCount - 1) * ROW_HEIGHT)
if (offset > 0) {
scrollTableTo(offset)
}
return
}
}
}, [selected])
// 给设置做防抖
useDebounceEffect(() => {
props.onSelected && props.onSelected(selected)
}, [selected], {wait: 400, trailing: true, leading: true})
useEffect(() => {
if (autoReload) {
const id = setInterval(() => {
update(1, undefined, "desc", undefined, undefined, true)
}, 1000)
return () => {
clearInterval(id)
}
}
}, [autoReload])
return (
// <AutoCard bodyStyle={{padding: 0, margin: 0}} bordered={false}>
<div ref={ref as Ref<any>} tabIndex={-1}
style={{width: "100%", height: "100%", overflow: "hidden"}}
>
<ReactResizeDetector
onResize={(width, height) => {
if (!width || !height) {
return
}
setTableContentHeight(height - 38)
}}
handleWidth={true} handleHeight={true} refreshMode={"debounce"} refreshRate={50}/>
{!props.noHeader && (
<PageHeader
title={"HTTP History"}
subTitle={
<Space>
{"所有相关请求都在这里"}
<Button
icon={<ReloadOutlined/>}
type={"link"}
onClick={(e) => {
update(1)
}}
/>
</Space>
}
extra={[
<Space>
<Form.Item label={"选择 HTTP History 类型"} style={{marginBottom: 0}}>
<Select
mode={"multiple"}
value={params.SourceType}
style={{minWidth: 200}}
onChange={(e) => {
setParams({...params, SourceType: e})
setLoading(true)
setTimeout(() => {
update(1, undefined, undefined, undefined, e)
}, 200)
}}
>
<Select.Option value={"mitm"}>mitm: 中间人劫持</Select.Option>
<Select.Option value={"fuzzer"}>
fuzzer: 模糊测试分析
</Select.Option>
</Select>
</Form.Item>
<Popconfirm
title={"确定想要删除所有记录吗?不可恢复"}
onConfirm={(e) => {
ipcRenderer.invoke("delete-http-flows-all")
setLoading(true)
info("正在删除...如自动刷新失败请手动刷新")
setTimeout(() => {
update(1)
if (props.onSelected) props.onSelected(undefined)
}, 400)
}}
>
<Button danger={true}>清除全部历史记录?</Button>
</Popconfirm>
</Space>
]}
/>
)}
<Row style={{margin: "5px 0 5px 5px"}}>
<Col span={12}>
<Space>
<span>HTTP History</span>
<Button
icon={<ReloadOutlined/>}
type={"link"}
size={"small"}
onClick={(e) => {
update(1, undefined, "desc")
}}
/>
{/* <Space>
自动刷新:
<Switch size={"small"} checked={autoReload} onChange={setAutoReload}/>
</Space> */}
<Input.Search
placeholder={"URL关键字"}
enterButton={true}
size={"small"}
style={{width: 170}}
value={params.SearchURL}
onChange={(e) => {
setParams({...params, SearchURL: e.target.value})
}}
onSearch={(v) => {
update(1)
}}
/>
{props.noHeader && (
<Popconfirm
title={"确定想要删除所有记录吗?不可恢复"}
onConfirm={(e) => {
ipcRenderer.invoke("delete-http-flows-all")
setLoading(true)
info("正在删除...如自动刷新失败请手动刷新")
setCompareLeft({content: '', language: 'http'})
setCompareRight({content: '', language: 'http'})
setCompareState(0)
setTimeout(() => {
update(1)
if (props.onSelected) props.onSelected(undefined)
}, 400)
}}
>
<Button danger={true} size={"small"}>
删除历史记录
</Button>
</Popconfirm>
)}
{/*{autoReload && <Tag color={"green"}>自动刷新中...</Tag>}*/}
</Space>
</Col>
<Col span={12} style={{textAlign: "right"}}>
<Tag>{total} Records</Tag>
</Col>
</Row>
<TableResizableColumn
tableRef={tableRef}
virtualized={true}
className={"httpFlowTable"}
loading={loading}
columns={[
{
dataKey: "Id",
width: 80,
headRender: () => "序号",
cellRender: ({rowData, dataKey, ...props}: any) => {
return `${rowData[dataKey] <= 0 ? "..." : rowData[dataKey]}`
}
},
{
dataKey: "Method",
width: 70,
headRender: (params1: any) => {
return (
<div
style={{display: "flex", justifyContent: "space-between"}}
>
方法
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"搜索方法"}
params={params}
setParams={setParams}
filterName={"Methods"}
autoCompletions={["GET", "POST", "HEAD"]}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.Methods ? undefined : "gray",
}}
type={!!params.Methods ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
// return (
// <Tag color={"geekblue"} style={{marginRight: 20}}>
// {rowData[dataKey]}
// </Tag>
// )
return rowData[dataKey]
}
},
{
dataKey: "StatusCode",
width: 100,
sortable: true,
headRender: () => {
return (
<div
style={{display: "inline-flex"}}
>
状态码
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"搜索状态码"}
params={params}
setParams={setParams}
filterName={"StatusCode"}
autoCompletions={[
"200",
"300-305",
"400-404",
"500-502",
"200-299",
"300-399",
"400-499"
]}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.StatusCode ? undefined : "gray",
}}
type={!!params.StatusCode ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<div style={{color: StatusCodeToColor(rowData[dataKey])}}>
{rowData[dataKey] === 0 ? "" : rowData[dataKey]}
</div>
)
}
},
{
dataKey: "Url",
resizable: true,
headRender: () => {
return (
<div
style={{display: "flex", justifyContent: "space-between"}}
>
URL
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"搜索URL关键字"}
params={params}
setParams={setParams}
filterName={"SearchURL"}
pureString={true}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.SearchURL ? undefined : "gray",
}}
type={!!params.SearchURL ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
if (rowData.IsPlaceholder) {
return <div style={{color: "#888585"}}>{"滚轮上滑刷新..."}</div>
}
return (
<div style={{width: "100%", display: "flex"}}>
<div className='resize-ellipsis' title={rowData.Url}>
{!params.SearchURL ? (
rowData.Url
) : (
rowData.Url
)}
</div>
</div>
)
},
width: 600
},
{
dataKey: "HtmlTitle",
width: 120,
resizable: true,
headRender: () => {
return "Title"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData[dataKey] ? rowData[dataKey] : ""
}
},
{
dataKey: "Tags",
width: 120,
resizable: true,
headRender: () => {
return "Tags"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData[dataKey] ? (
`${rowData[dataKey]}`.split("|").filter(i => !i.startsWith("YAKIT_COLOR_")).join(", ")
) : ""
}
},
{
dataKey: "IPAddress",
width: 140, resizable: true,
headRender: () => {
return "IP"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return rowData[dataKey] ? rowData[dataKey] : ""
}
},
{
dataKey: "BodyLength",
width: 120,
sortable: true,
headRender: () => {
return (
<div style={{display: "inline-block", position: "relative"}}>
响应长度
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"是否存在Body?"}
params={params}
setParams={setParams}
filterName={"HaveBody"}
pureBool={true}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.HaveBody ? undefined : "gray",
}}
type={!!params.HaveBody ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<div style={{width: 100}}>
{/* 1M 以上的话,是红色*/}
{rowData.BodyLength !== -1 ?
(<div style={{color: rowData.BodyLength > 1000000 ? "red" : undefined}}>
{rowData.BodySizeVerbose
? rowData.BodySizeVerbose
: rowData.BodyLength}
</div>)
:
(<div></div>)
}
</div>
)
}
},
// {
// dataKey: "UrlLength",
// width: 90,
// headRender: () => {
// return "URL 长度"
// },
// cellRender: ({rowData, dataKey, ...props}: any) => {
// const len = (rowData.Url || "").length
// return len > 0 ? <div>{len}</div> : "-"
// }
// },
{
dataKey: "GetParamsTotal",
width: 65,
align: "center",
headRender: () => {
return (
<div
style={{display: "flex", justifyContent: "space-between"}}
>
参数
<Popover
placement='bottom'
trigger='click'
content={
params &&
setParams && (
<HTTLFlowFilterDropdownForms
label={"过滤是否存在基础参数"}
params={params}
setParams={setParams}
filterName={"HaveCommonParams"}
pureBool={true}
submitFilter={() => update(1)}
/>
)
}
>
<Button
style={{
paddingLeft: 4, paddingRight: 4, marginLeft: 4,
color: !!params.HaveCommonParams ? undefined : "gray",
}}
type={!!params.HaveCommonParams ? "primary" : "link"} size={"small"}
icon={<SearchOutlined/>}
/>
</Popover>
</div>
)
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return (
<Space>
{(rowData.GetParamsTotal > 0 ||
rowData.PostParamsTotal > 0) && <CheckOutlined/>}
</Space>
)
}
},
{
dataKey: "ContentType",
resizable: true, width: 80,
headRender: () => {
return "响应类型"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
let contentTypeFixed = rowData.ContentType.split(";")
.map((el: any) => el.trim())
.filter((i: any) => !i.startsWith("charset"))
.join(",") || "-"
if (contentTypeFixed.includes("/")) {
const contentTypeFixedNew = contentTypeFixed.split("/").pop()
if (!!contentTypeFixedNew) {
contentTypeFixed = contentTypeFixedNew
}
}
return (
<div>
{contentTypeFixed === "null" ? "" : contentTypeFixed}
</div>
)
}
},
{
dataKey: "UpdatedAt",
sortable: true,
width: 110,
headRender: () => {
return "请求时间"
},
cellRender: ({rowData, dataKey, ...props}: any) => {
return <Tooltip
title={rowData[dataKey] === 0 ? "" : formatTimestamp(rowData[dataKey])}
>
{rowData[dataKey] === 0 ? "" : formatTime(rowData[dataKey])}
</Tooltip>
}
},
{
dataKey: "operate",
width: 90,
headRender: () => "操作",
cellRender: ({rowData}: any) => {
if (!rowData.Hash) return <></>
return (
<a
onClick={(e) => {
let m = showDrawer({
width: "80%",
content: onExpandHTTPFlow(
rowData,
() => m.destroy()
)
})
}}
>
详情
</a>
)
}
}
]}
data={autoReload ? data : [TableFirstLinePlaceholder].concat(data)}
autoHeight={tableContentHeight <= 0}
height={tableContentHeight}
sortFilter={sortFilter}
renderRow={(children: ReactNode, rowData: any) => {
if (rowData)
return (
<div
id='http-flow-row'
ref={(node) => {
const color =
rowData.Hash === selected?.Hash ?
"rgba(78, 164, 255, 0.4)" :
rowData.Tags.indexOf("YAKIT_COLOR") > -1 ?
TableRowColor(rowData.Tags.split("|").pop().split('_').pop().toUpperCase()) :
"#ffffff"
if (node) {
if (color) node.style.setProperty("background-color", color, "important")
else node.style.setProperty("background-color", "#ffffff")
}
}}
style={{height: "100%"}}
>
{children}
</div>
)
return children
}}
onRowContextMenu={(rowData: HTTPFlow | any, event: React.MouseEvent) => {
if (rowData) {
setSelected(rowData);
}
showByCursorMenu(
{
content: [
{
title: '发送到 Web Fuzzer',
onClick: () => {
ipcRenderer.invoke("send-to-tab", {
type: "fuzzer",
data: {
isHttps: rowData.IsHTTPS,
request: new Buffer(rowData.Request).toString("utf8")
}
})
}
},
{
title: '发送到 数据包扫描',
onClick: () => {
ipcRenderer
.invoke("GetHTTPFlowByHash", {Hash: rowData.Hash})
.then((i: HTTPFlow) => {
ipcRenderer.invoke("send-to-packet-hack", {
request: i.Request,
ishttps: i.IsHTTPS,
response: i.Response
})
})
.catch((e: any) => {
failed(`Query Response failed: ${e}`)
})
}
},
{
title: '复制 URL',
onClick: () => {
callCopyToClipboard(rowData.Url)
},
},
{
title: '复制为 Yak PoC 模版', onClick: () => {
},
subMenuItems: [
{
title: "数据包 PoC 模版", onClick: () => {
const flow = rowData as HTTPFlow;
if (!flow) return;
generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
callCopyToClipboard(code)
}, RequestToYakCodeTemplate.Ordinary)
}
},
{
title: "批量检测 PoC 模版", onClick: () => {
const flow = rowData as HTTPFlow;
if (!flow) return;
generateYakCodeByRequest(flow.IsHTTPS, flow.Request, code => {
callCopyToClipboard(code)
}, RequestToYakCodeTemplate.Batch)
}
},
]
},
{
title: '标注颜色',
subMenuItems: availableColors.map(i => {
return {
title: i.title,
render: i.render,
onClick: () => {
const flow = rowData as HTTPFlow
if (!flow) {
return
}
const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
existedTags.push(`YAKIT_COLOR_${i.color.toUpperCase()}`)
ipcRenderer.invoke("SetTagForHTTPFlow", {
Id: flow.Id, Hash: flow.Hash,
Tags: existedTags,
}).then(() => {
info(`设置 HTTPFlow 颜色成功`)
if (!autoReload) {
setData(data.map(item => {
if (item.Hash === flow.Hash) {
item.Tags = `YAKIT_COLOR_${i.color.toUpperCase()}`
return item
}
return item
}))
}
})
}
}
}),
onClick: () => {
}
},
{
title: '移除颜色',
onClick: () => {
const flow = rowData as HTTPFlow
if (!flow) return
const existedTags = flow.Tags ? flow.Tags.split("|").filter(i => !!i && !i.startsWith("YAKIT_COLOR_")) : []
existedTags.pop()
ipcRenderer.invoke("SetTagForHTTPFlow", {
Id: flow.Id, Hash: flow.Hash,
Tags: existedTags,
}).then(() => {
info(`清除 HTTPFlow 颜色成功`)
if (!autoReload) {
setData(data.map(item => {
if (item.Hash === flow.Hash) {
item.Tags = ""
return item
}
return item
}))
}
})
return
},
},
{
title: "发送到对比器", onClick: () => {
},
subMenuItems: [
{
title: '发送到对比器左侧',
onClick: () => {
setCompareLeft({
content: new Buffer(rowData.Request).toString("utf8"),
language: 'http'
})
},
disabled: [false, true, false][compareState]
},
{
title: '发送到对比器右侧',
onClick: () => {
setCompareRight({
content: new Buffer(rowData.Request).toString("utf8"),
language: 'http'
})
},
disabled: [false, false, true][compareState]
}
]
},
]
},
event.clientX,
event.clientY
)
}}
onRowClick={(rowDate: any) => {
if (!rowDate.Hash) return
if (rowDate.Hash !== selected?.Hash) {
setSelected(rowDate)
} else {
// setSelected(undefined)
}
}}
onScroll={(scrollX, scrollY) => {
setScrollY(scrollY)
// 防止无数据触发加载
if (data.length === 0 && !getAutoReload()) {
setAutoReload(true)
return
}
// 根据页面展示内容决定是否自动刷新
let contextHeight = (data.length + 1) * ROW_HEIGHT // +1 是要把表 title 算进去
let offsetY = scrollY + tableContentHeight;
if (contextHeight < tableContentHeight) {
setAutoReload(true)
return
}
setAutoReload(false)
// 向下刷新数据
if (contextHeight <= offsetY) {
setAutoReload(false)
scrollUpdateButt(tableContentHeight)
return
}
// 锁住滚轮
if (getLockedScroll() > 0 && getLockedScroll() >= scrollY) {
if (scrollY === getLockedScroll()) {
return
}
// scrollTableTo(getLockedScroll())
return
}
const toTop = scrollY <= 0;
if (toTop) {
lockScrollTimeout(ROW_HEIGHT, 600)
scrollUpdateTop()
}
}}
/>
</div>
// </AutoCard>
)
}
Example #25
Source File: DataTable.tsx From fe-v5 with Apache License 2.0 | 4 votes |
function DataTable<T>(props: IDataTableProps<T>, ref: any) {
const prefixCls = `${props.prefixCls || 'dantd'}-data-table`;
const filterType = props.filterType || 'half';
const isUrlLoad = useRef<string>();
const t = intlZhMap;
const {
fetchOptions = {},
searchParams,
showFilter = true,
showReloadBtn = true,
showQueryOptionBtns = true,
showQueryCollapseButton = true,
isQuerySearchOnChange = true,
showBodyBg = false,
queryFormProps = {},
url,
queryMode = 'default',
reloadBtnPos = 'right',
searchPos = 'full',
reloadBtnType = 'icon',
queryFormColumns,
pageParams,
antProps = {
rowKey: 'id',
},
} = props;
const tableClassName = classNames(prefixCls, props.className);
const tableContentClassName = classNames({
[`${prefixCls}-table-content`]: true,
[`${prefixCls}-table-content-noborder`]: props.hideContentBorder,
});
const tableBodyCls = classNames({
[`${prefixCls}-body`]: !!queryFormColumns || showBodyBg,
[`${prefixCls}-body-compact`]: queryMode === 'compact',
});
const tableQueryCls = classNames({
[`${prefixCls}-query`]: !!queryFormColumns,
[`${prefixCls}-query-compact`]: queryMode === 'compact',
});
// hooks
const columnsMap = useMemo(() => {
const result = {} as any;
if (props.columns && props.columns.length > 0) {
props.columns.forEach((columnItem) => {
if (columnItem.dataIndex) {
result[columnItem.dataIndex as string] = {
...columnItem,
value: null,
};
}
});
}
return result;
}, [props.columns]);
const [queryFormValues, setQueryFormValues] = useState<any>({});
const [isQueryInit, setQueryInit] = useState(false);
const isPageChangeNoSearch = useRef<boolean>(false);
const filterSearchInputRef = useRef({}) as any;
const clearFiltersRef = useRef({}) as any;
const [dataSource, setDataSource] = useState([]);
const [loading, setLoading] = useState(false);
const [sorterState, sorterDispatch] = useReducer(sorterReducer, {});
const [showRightHeader, setShowRightHeader] = useState<boolean>(false);
const [searchQuery, setSearchQuery] = useState();
const rowSelection = props.rowSelection;
const selectRowNum = rowSelection
? rowSelection.selectedRowKeys && rowSelection.selectedRowKeys.length
: -1;
const sorterNames = {
ascend: t('table.sort.ascend'),
descend: t('table.sort.descend'),
};
const showTotal = (total: number) => {
return `${t('table.total.prefix')} ${total} ${t('table.total.suffix')}`;
};
const showSearch = !!searchParams;
// 是否展示长条搜索区
const showFullSearch = showSearch && searchPos === 'full';
// 搜索按钮展示的位置
const showReloadBtn2SearchRight =
searchPos === 'full' && showReloadBtn && reloadBtnPos === 'right';
const showReloadBtn2FilterRight =
(!showSearch || searchPos !== 'full') &&
showReloadBtn &&
reloadBtnPos === 'right';
const showReloadBtn2FilterLeft = showReloadBtn && reloadBtnPos === 'left';
const searchPlaceholder = searchParams
? searchParams.placeholder
: t('table.search.placeholder');
const [paginationState, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
...pageParams,
});
const [columnsState, columnsDispatch] = useReducer(
columnsReducer,
columnsMap,
);
const debouncedSearchQuery = useDebounce(searchQuery, 300);
const debouncedQueryFormValues = useDebounce(
JSON.stringify(queryFormValues),
300,
);
const pageStr = JSON.stringify({
pageSize: paginationState.pageSize,
current: paginationState.current,
});
const tableStateStr = JSON.stringify({
...columnsState,
...sorterState,
});
// TODO 支持监听 customQuery
useEffect(() => {
if (url && isUrlLoad.current !== url) {
let fetchParams = getAllFetchParams();
fetchData(fetchParams);
setTimeout(() => {
isUrlLoad.current = url;
});
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [url]);
useEffect(() => {
if (!isUrlLoad.current) {
return;
}
let fetchParams = getAllFetchParams();
fetchParams[paginationState.curPageName] = 1;
if (searchParams && debouncedSearchQuery && debouncedSearchQuery.trim()) {
fetchParams[searchParams.apiName] = debouncedSearchQuery.trim();
}
if (props.onSearch) {
// 使用用户自定义的search回调
props.onSearch(
debouncedSearchQuery
? debouncedSearchQuery.trim()
: debouncedSearchQuery,
);
}
fetchData(fetchParams);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedSearchQuery]);
// 监听queryform
useEffect(() => {
if (!isUrlLoad.current) {
return;
}
if (
!queryFormColumns ||
(queryFormValues && Object.keys(queryFormValues).length === 0)
) {
return;
}
let fetchParams = getAllFetchParams();
fetchParams[paginationState.curPageName] = 1;
fetchData(fetchParams);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedQueryFormValues]);
useEffect(() => {
if (!isUrlLoad.current) {
return;
}
let fetchParams = getAllFetchParams();
fetchData(fetchParams);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tableStateStr]);
useEffect(() => {
if (!isUrlLoad.current || !paginationState.pageSize) {
return;
}
if (isPageChangeNoSearch.current) {
isPageChangeNoSearch.current = false;
return;
}
let fetchParams = getAllFetchParams();
fetchData(fetchParams);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageStr]);
async function fetchData(params?: any) {
let fetchUrl = url;
let customFetchParams = null as any;
if (fetchUrl.indexOf('?') !== -1) {
const fetchArr = fetchUrl.split('?');
fetchUrl = fetchArr[0];
customFetchParams = queryString.parse(fetchArr[1], {
arrayFormat: 'comma',
});
}
let fetchParams = {
[pageParams.curPageName]: params.current,
[pageParams.pageSizeName]: params.pageSize,
};
if (customFetchParams) {
fetchParams = {
...customFetchParams,
...fetchParams,
};
}
// api起始页从0开始,参数需要减1
if (pageParams.startPage === 0) {
fetchParams[pageParams.curPageName] -= 1;
}
props.columns.forEach((columnItem) => {
if (columnItem.dataIndex && params[columnItem.dataIndex]) {
fetchParams[columnItem.dataIndex] = params[columnItem.dataIndex];
}
if (columnItem.apiSearch && params[columnItem.apiSearch.name]) {
fetchParams[columnItem.apiSearch.name] =
params[columnItem.apiSearch.name];
}
if (columnItem.apiFilter && params[columnItem.apiFilter.name]) {
fetchParams[columnItem.apiFilter.name] =
params[columnItem.apiFilter.name];
}
if (columnItem.apiSorter && params[columnItem.apiSorter.name]) {
fetchParams[columnItem.apiSorter.name] =
params[columnItem.apiSorter.name];
}
});
if (searchParams && params[searchParams.apiName]) {
fetchParams[searchParams.apiName] = params[searchParams.apiName];
}
if (
!!queryFormColumns &&
queryFormValues &&
Object.keys(queryFormValues).length > 0
) {
fetchParams = {
...queryFormValues,
...fetchParams,
};
}
if (props.customQueryCallback) {
fetchParams = props.customQueryCallback({
...fetchParams,
});
}
fetchUrl = `${fetchUrl}?${queryString.stringify(fetchParams, {
arrayFormat: 'comma',
})}`;
if (props.customFetchUrlCallback) {
fetchUrl = props.customFetchUrlCallback(fetchUrl);
}
setLoading(true);
let transportOptions = {
...fetchOptions,
};
if (fetchOptions.method && fetchOptions.method.toLowerCase() === 'post') {
transportOptions.data = fetchParams;
}
const res = request(fetchUrl, transportOptions);
props.rowSelection &&
props.rowSelection.onChange &&
props.rowSelection.onChange([]);
if ((res.status < 200 || res.status >= 300) && props.apiErrorCallback) {
props.apiErrorCallback(res);
setLoading(false);
} else {
res
.then(async (res) => {
let callbackData = res;
if (props.apiCallback) {
callbackData = props.apiCallback(res);
}
if (props.apiAsyncCallback) {
callbackData = await props.apiAsyncCallback(res);
}
setDataSource(callbackData.data);
const tmpPagination = {
...paginationState,
total: callbackData.total,
};
setPagination(tmpPagination);
setLoading(false);
})
.catch(() => {
if (props.apiErrorCallback) {
props.apiErrorCallback(res);
}
setLoading(false);
});
}
}
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
if (!isUrlLoad.current) {
return;
}
if (Object.keys(sorter).length > 0 && sorter.column) {
sorterDispatch({
type: 'update',
value: {
...sorter,
column: {
apiSorter: sorter.column.apiSorter,
},
},
});
} else {
sorterDispatch({
type: 'update',
value: {},
});
}
Object.entries(filters).forEach(([filterKey, filterValue]) => {
if (columnsMap[filterKey].filters) {
columnsDispatch({
type: 'update',
dataIndex: filterKey,
updateType: 'filter',
value: filterValue,
});
}
});
isPageChangeNoSearch.current = false;
setPagination(pagination);
checkRightHeader(filters, sorter);
};
const handleFilterSearch = useCallback(
(
selectedKeys: React.ReactText[] | undefined,
confirm: (() => void) | undefined,
dataIndex: string | number,
) => {
if (confirm) {
confirm();
}
if (selectedKeys && dataIndex) {
columnsDispatch({
type: 'update',
dataIndex,
updateType: 'search',
value: selectedKeys[0],
});
}
},
[],
);
const handleFilterSearchReset = useCallback(
(
clearFilters: ((selectedKeys: string[]) => void) | undefined,
dataIndex: string | number | undefined,
) => {
if (clearFilters) {
clearFilters([]);
}
if (dataIndex) {
columnsDispatch({
type: 'update',
dataIndex,
});
}
},
[],
);
const handleFilterClear = (columnValue: IColumnsReducerValue) => {
columnsDispatch({
type: 'update',
dataIndex: columnValue.dataIndex,
value: [],
});
const tmpFilters = Object.values(columnsState).map((filterItem: any) => {
if (filterItem.dataIndex === columnValue.dataIndex) {
return {
[filterItem.dataIndex]: [],
};
}
return {
[filterItem.dataIndex]: filterItem.value || [],
};
});
checkRightHeader(tmpFilters, sorterState, columnsState);
};
const handleFilterSearchClear = (columnValue: IColumnsReducerValue) => {
columnsDispatch({
type: 'update',
dataIndex: columnValue.dataIndex,
});
if (
clearFiltersRef.current[columnValue.dataIndex] &&
clearFiltersRef.current[columnValue.dataIndex].clearFilters
) {
clearFiltersRef.current[columnValue.dataIndex].clearFilters([]);
}
checkRightHeader(null, sorterState, {
...columnsState,
[columnValue.dataIndex]: {
title: columnsState[columnValue.dataIndex].title,
dataIndex: columnsState[columnValue.dataIndex].dataIndex,
value: null,
},
});
};
const handleClearAll = () => {
sorterDispatch({ type: 'clear' });
columnsDispatch({ type: 'clear', initialState: columnsMap });
setShowRightHeader(false);
setTimeout(() => {
Object.values(clearFiltersRef.current).forEach((filterItem: any) => {
if (filterItem && filterItem.clearFilters) {
filterItem.clearFilters([]);
}
});
});
};
const handleSortClear = () => {
sorterDispatch({ type: 'clear' });
checkRightHeader(null, {});
};
const handleQueryFormInit = (data) => {
if (!isQueryInit) {
setQueryFormValues(data);
setQueryInit(true);
}
};
const handleQueryFormChange = (data) => {
isPageChangeNoSearch.current = true;
setPagination({
...paginationState,
current: 1,
});
setQueryFormValues(data);
};
const handleQueryFormReset = (data) => {
isPageChangeNoSearch.current = true;
setPagination({
...paginationState,
current: 1,
});
setQueryFormValues(data);
};
const handleQuerySearch = (data) => {
if (paginationState.current > 1) {
// 这次修改分页参数
isPageChangeNoSearch.current = true;
setPagination({
...paginationState,
current: 1,
});
setQueryFormValues(data);
} else {
setQueryFormValues(data);
// let fetchParams = getAllFetchParams();
// fetchData(fetchParams);
}
};
const getAllFetchParams = () => {
let fetchParams = {
...paginationState,
};
// columns sort
if (sorterState && sorterState.order) {
fetchParams[sorterState.column.apiSorter.name] =
sorterState.column.apiSorter[sorterState.order];
}
Object.values(columnsState).forEach((column: any) => {
const filterKey = column.dataIndex;
const filterVal = column.value;
// columns filter
if (column.apiFilter && filterVal) {
let filterName = columnsMap[filterKey].apiFilter
? columnsMap[filterKey].apiFilter.name
: filterKey;
fetchParams[filterName] = filterVal;
if (column.apiFilter.callback) {
fetchParams[filterName] =
columnsMap[filterKey].apiFilter.callback(filterVal);
}
}
// columns search
if (column.apiSearch && filterVal) {
const filterName = columnsMap[filterKey].apiSearch
? columnsMap[filterKey].apiSearch.name
: filterKey;
fetchParams[filterName] = Array.isArray(filterVal)
? filterVal[0]
: filterVal;
}
});
// query search
if (searchParams && searchQuery) {
fetchParams[searchParams.apiName] = searchQuery;
}
// queryform
if (
queryFormColumns ||
(queryFormValues && Object.keys(queryFormValues).length > 0)
) {
fetchParams = {
...queryFormValues,
...fetchParams,
};
}
return fetchParams;
};
const handleReload = () => {
// pages
let fetchParams = getAllFetchParams();
fetchData(fetchParams);
};
const handleSearchChange = (e) => {
const query = e.target.value;
setSearchQuery(query);
// 使用组件默认的search回调
setPagination({
...paginationState,
current: 1,
});
};
const checkRightHeader = (filters?: any, sorter?: any, search?: any) => {
const checkSorter = sorter || sorterState;
const checkFilters = filters || columnsState;
const checkSearch = search || columnsState;
const isSearch = Object.values(checkSearch).some((columnItem: any) => {
return !!(columnItem.type === 'search' && columnItem.value);
});
let isFilter = false;
if (filters) {
isFilter = Object.values(checkFilters).some((columnItem: any) => {
return columnItem && columnItem.length > 0;
});
} else {
isFilter = Object.values(checkFilters).some((columnItem: any) => {
return !!(
columnItem.type === 'filter' &&
columnItem.value &&
columnItem.value.length > 0
);
});
}
const isSorter = !!checkSorter.column;
const res = isSearch || isFilter || isSorter;
setShowRightHeader(res);
};
const isSearch = Object.values(columnsState).some((columnItem: any) => {
return !!(columnItem.type === 'search' && columnItem.value);
});
const isFilter = Object.values(columnsState).some((columnItem: any) => {
return !!(
columnItem.type === 'filter' &&
columnItem.value &&
columnItem.value.length > 0
);
});
useImperativeHandle(ref, () => ({
handleReload,
goToFirstPage: () => {
setPagination({
...paginationState,
current: 1,
});
},
}));
const renderColumns = () => {
return props.columns.map((columnItem) => {
const currentItem = _.cloneDeep(columnItem);
// filter
if (currentItem.apiFilter) {
currentItem.filterIcon = () => <FilterOutlined />;
currentItem.filteredValue =
columnsState[columnItem.dataIndex as string].value;
}
// // sort
if (currentItem.apiSorter) {
currentItem.sortOrder =
sorterState.columnKey === currentItem.dataIndex && sorterState.order;
}
// Search
if (currentItem.apiSearch) {
currentItem.filterIcon = () => <SearchOutlined />;
currentItem.onFilterDropdownVisibleChange = (visible: boolean) => {
if (
visible &&
filterSearchInputRef.current &&
filterSearchInputRef.current.select
) {
setTimeout(() => {
if (
filterSearchInputRef.current &&
filterSearchInputRef.current.select
) {
filterSearchInputRef.current.select();
}
return null;
});
}
};
currentItem.filterDropdown = ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => {
clearFiltersRef.current[currentItem.dataIndex as string] = {
clearFilters,
};
return (
<div style={{ padding: 8 }}>
<Input
data-testid='filter-search-input'
autoFocus={true}
ref={(node) => {
filterSearchInputRef.current = node;
}}
placeholder={t('table.filter.search.placeholder')}
value={selectedKeys && selectedKeys[0]}
onChange={(e) => {
if (setSelectedKeys) {
return setSelectedKeys(
e.target.value ? [e.target.value] : [],
);
}
return [];
}}
onPressEnter={() =>
handleFilterSearch(
selectedKeys,
confirm,
currentItem.dataIndex as string,
)
}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Button
type='primary'
onClick={() =>
handleFilterSearch(
selectedKeys,
confirm,
currentItem.dataIndex as string,
)
}
icon='search'
size='small'
data-testid='search-btn-ok'
style={{ width: 90, marginRight: 8 }}
>
{t('table.filter.search.btn.ok')}
</Button>
<Button
onClick={() =>
handleFilterSearchReset(clearFilters, currentItem.dataIndex)
}
size='small'
style={{ width: 90 }}
>
{t('table.filter.search.btn.cancel')}
</Button>
</div>
);
};
let searchWords: any[] = [];
const tmpStateValue =
columnsState[currentItem.dataIndex as string].value;
if (typeof tmpStateValue === 'string') {
searchWords = [tmpStateValue];
}
if (
Array.isArray(tmpStateValue) &&
typeof tmpStateValue[0] === 'string'
) {
searchWords = [tmpStateValue[0]];
}
currentItem.onFilter = (value, record: any) => {
return record[currentItem.dataIndex as string]
.toString()
.toLowerCase()
.includes(value.toLowerCase());
};
if (!currentItem.render) {
currentItem.render = (value, row, index) => {
if (currentItem.searchRender) {
return currentItem.searchRender(
value,
row,
index,
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={searchWords}
autoEscape
textToHighlight={String(value)}
/>,
);
} else {
return (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={searchWords}
autoEscape
textToHighlight={String(value)}
/>
);
}
};
}
}
return currentItem;
});
};
const renderRightHeader = (params) => {
if (!showFilter) {
return null;
}
return (
<>
<div>
<b
style={{
marginTop: 4,
display: 'inline-block',
}}
>
{t('table.filter.header.title')}
</b>
</div>
{(isSearch || isFilter) &&
Object.values(columnsState as IColumnsReducerState).map(
(columnValue) => {
if (columnValue.type === 'search' && columnValue.value) {
return (
<div
key={`search-header-${columnValue.dataIndex}`}
className={`${params.headerClsPrefix}-item`}
>
<Tooltip
title={
<span>
{columnValue.title}
{':'}
{columnValue.value}
</span>
}
>
<Button
size='small'
className='table-header-item-btn'
onClick={() => handleFilterSearchClear(columnValue)}
>
<span className='table-header-item-btn-content'>
{columnValue.title}
{':'}
{columnValue.value}
</span>
<CloseOutlined />
</Button>
</Tooltip>
</div>
);
}
if (
columnValue.type === 'filter' &&
columnValue.value &&
columnValue.value.length > 0
) {
const mappings = columnsMap[columnValue.dataIndex]
? columnsMap[columnValue.dataIndex].filters
: [];
const mapping = mappings.reduce((acc, cur) => {
return {
...acc,
[cur.value]: [cur.text],
};
}, {});
const newValues = columnValue.value.map((valItem) => {
return mapping && mapping[valItem]
? mapping[valItem]
: valItem;
});
return (
<div
key={`search-header-${columnValue.dataIndex}`}
className={`${params.headerClsPrefix}-item`}
>
<Tooltip
title={
<span>
{columnValue.title}
{':'}
{newValues.join(',')}
</span>
}
>
<Button
size='small'
className='table-header-item-btn'
onClick={() => handleFilterClear(columnValue)}
>
<span className='table-header-item-btn-content'>
{columnValue.title}
{':'}
{newValues.join(',')}
</span>
<CloseOutlined />
</Button>
</Tooltip>
</div>
);
}
return null;
},
)}
{sorterState.columnKey && sorterState.column && (
<div className={`${params.headerClsPrefix}-item`}>
<Tooltip
title={
<span>
{columnsMap[sorterState.columnKey].title}
{`: ${sorterNames[sorterState.order as TSorterNames]}`}
</span>
}
>
<Button
size='small'
className='table-header-item-btn'
onClick={handleSortClear}
>
<span className='table-header-item-btn-content'>
{columnsMap[sorterState.columnKey].title}
{`: ${sorterNames[sorterState.order as TSorterNames]}`}
</span>
<CloseOutlined />
</Button>
</Tooltip>
</div>
)}
<div className={`${params.headerClsPrefix}-item`}>
<Button
size='small'
type='link'
data-testid='btn-clearall'
onClick={handleClearAll}
>
{t('table.filter.header.btn.clear')}
</Button>
</div>
</>
);
};
const renderSearch = () => {
return (
<Tooltip placement='topLeft' title={searchPlaceholder}>
<Input
data-testid='search-input'
prefix={
reloadBtnPos === 'right' && (
<SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
)
}
suffix={
reloadBtnPos === 'left' && (
<SearchOutlined style={{ color: 'rgba(0,0,0,.25)' }} />
)
}
allowClear={true}
value={searchQuery}
onChange={handleSearchChange}
placeholder={searchPlaceholder}
// suffix={<Icon onClick={handleSearchClick} type="search" />}
/>
</Tooltip>
);
};
const renderReloadBtn = () => {
if (reloadBtnType === 'icon') {
const reloadBtnCls = classNames({
[`${prefixCls}-header-loadbtn`]: true,
[`${prefixCls}-header-loadbtn-icon`]: true,
[`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
[`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
});
return (
<ReloadOutlined
onClick={handleReload}
spin={loading}
className={reloadBtnCls}
type='reload'
/>
);
}
if (reloadBtnType === 'btn') {
const reloadBtnCls = classNames({
[`${prefixCls}-header-loadbtn`]: true,
[`${prefixCls}-header-loadbtn-btn`]: true,
[`${prefixCls}-header-loadbtn-right`]: reloadBtnPos === 'right',
[`${prefixCls}-header-loadbtn-left`]: reloadBtnPos === 'left',
});
return (
<Button
className={reloadBtnCls}
loading={loading}
icon={<ReloadOutlined />}
data-testid='reload-btn'
onClick={handleReload}
/>
);
}
};
return (
<ConfigProvider {...props.antConfig}>
<div className={tableClassName} style={props.style}>
{queryFormColumns && (
<div className={tableQueryCls}>
<QueryForm
onChange={
isQuerySearchOnChange
? handleQueryFormChange
: handleQueryFormInit
}
onReset={handleQueryFormReset}
onSearch={handleQuerySearch}
columns={queryFormColumns}
showOptionBtns={showQueryOptionBtns}
showCollapseButton={showQueryCollapseButton}
{...queryFormProps}
/>
</div>
)}
<div className={tableBodyCls}>
{!!props.tableTitle && <h3> {props.tableTitle} </h3>}
{props.customHeader && (
<div
data-testid='custom-header'
className={`${prefixCls}-header-custom`}
>
{props.customHeader}
</div>
)}
{showFullSearch && (
<Row type='flex' className={`${prefixCls}-header-search`}>
{showSearch ? renderSearch() : <div></div>}
{showReloadBtn2SearchRight && renderReloadBtn()}
</Row>
)}
{/* 自定义格式 */}
{filterType === 'flex' && (
<div
style={{
display: 'flex',
justifyContent: 'flex-start',
marginBottom: '10px',
}}
>
{showReloadBtn2FilterLeft && renderReloadBtn()}
{props.leftHeader}
</div>
)}
{filterType === 'none' && searchPos !== 'right' && (
<Row className={`${prefixCls}-header-filter`}>
{showReloadBtn2FilterLeft && renderReloadBtn()}
<Col
data-testid='left-header'
className={classNames(
`${prefixCls}-header-filter-left`,
props.leftHeader !== undefined &&
`${prefixCls}-header-filter-left-minh`,
)}
>
{props.leftHeader}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
{filterType === 'none' && showSearch && searchPos === 'right' && (
<Row className={`${prefixCls}-header-filter`}>
<Col
data-testid='left-header'
className={classNames(
`${prefixCls}-header-filter-left`,
props.leftHeader !== undefined &&
`${prefixCls}-header-filter-left-minh`,
)}
span={14}
>
{showReloadBtn2FilterLeft && renderReloadBtn()}
{props.leftHeader}
</Col>
<Col
data-testid='right-header'
className={`${prefixCls}-header-filter-right`}
span={10}
>
{renderSearch()}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
{filterType === 'line' && (
<Row className={`${prefixCls}-header-filter`}>
{showReloadBtn2FilterLeft && renderReloadBtn()}
<Col
data-testid='right-header'
className={`${prefixCls}-header-filter-line`}
span={24}
>
{showRightHeader &&
renderRightHeader({
headerClsPrefix: `${prefixCls}-header-filter-line`,
})}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
{filterType === 'half' && (
<Row className={`${prefixCls}-header-filter`}>
{showReloadBtn2FilterLeft && renderReloadBtn()}
<Col
data-testid='left-header'
className={classNames(
`${prefixCls}-header-filter-left`,
props.leftHeader !== undefined &&
`${prefixCls}-header-filter-left-minh`,
)}
span={12}
>
{props.leftHeader}
</Col>
<Col
data-testid='right-header'
className={`${prefixCls}-header-filter-right`}
span={12}
>
{showRightHeader &&
renderRightHeader({
headerClsPrefix: `${prefixCls}-header-filter-right`,
})}
</Col>
{showReloadBtn2FilterRight && renderReloadBtn()}
</Row>
)}
<div className={`${prefixCls}-table-wrapper`}>
<div className={tableContentClassName}>
<Table
loading={loading}
columns={renderColumns()}
dataSource={dataSource}
bordered={false}
rowSelection={rowSelection}
onChange={handleTableChange}
pagination={{
showTotal: showTotal,
showSizeChanger: true,
showQuickJumper: true,
pageSizeOptions: pageSizeOptions,
...props.pagination,
...paginationState,
}}
{...antProps}
/>
{rowSelection && selectRowNum !== undefined && selectRowNum > 0 && (
<div className={`${prefixCls}-table-content-select-num`}>
{t('table.select.num')}
{selectRowNum}
</div>
)}
</div>
</div>
</div>
</div>
</ConfigProvider>
);
}
Example #26
Source File: index.tsx From Aragorn with MIT License | 4 votes |
FileManage = () => {
const {
state: {
uploaderProfiles,
configuration: { defaultUploaderProfileId }
}
} = useAppContext();
const [windowHeight, setWindowHeight] = useState(window.innerHeight);
useEffect(() => {
function handleResize() {
setWindowHeight(window.innerHeight);
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
const { id } = useParams<{ id: string }>();
const [hasFileManageFeature, setHasFileManageFeature] = useState(false);
const [uploaderProfile, setUploaderProfile] = useState({} as UploaderProfile);
useEffect(() => {
const currentId = id || defaultUploaderProfileId;
setCurrentProfile(currentId as string);
}, []);
useEffect(() => {
if (uploaderProfile?.id) {
getList();
}
}, [uploaderProfile]);
const [list, setList] = useState([] as ListFile[]);
const [listLoading, setListLoading] = useState(false);
const getList = (directoryPath?: string) => {
setListLoading(true);
ipcRenderer.send('file-list-get', uploaderProfile.id, directoryPath);
};
const [dirPath, setDirPath] = useState([] as string[]);
useEffect(() => {
function handleListGetReply(_, res?: FileListResponse) {
setListLoading(false);
if (res === undefined) {
setHasFileManageFeature(false);
setList([]);
message.info(`${uploaderProfile.uploaderName}暂不支持文件管理功能`);
return;
}
setHasFileManageFeature(true);
if (res.success) {
setList(res.data);
} else {
message.error(`文件列表获取失败 ${res.desc || ''}`);
}
}
function handleFileDeleteReply(_, res?: DeleteFileResponse) {
if (res === undefined) {
return;
}
if (res.success) {
message.success({ content: '文件删除成功', key: 'file-manage-delete' });
getList(dirPath.join('/'));
} else {
message.error({ content: `文件删除失败 ${res.desc || ''}`, key: 'file-manage-delete' });
}
}
function handleFileUploadReply() {
getList(dirPath.join('/'));
}
function handleDirectoryCreateReply(_, res?: CreateDirectoryResponse) {
if (res === undefined) {
return;
}
if (res.success) {
message.success('目录创建成功');
setModalVisible(false);
getList(dirPath.join('/'));
} else {
message.error(`目录创建失败 ${res.desc || ''}`);
}
}
function handleExportReplay(_, res) {
setExportLoading(false);
if (res) {
shell.showItemInFolder(res);
setRowKeys([]);
setSelectRows([]);
}
}
ipcRenderer.on('file-list-get-reply', handleListGetReply);
ipcRenderer.on('file-delete-reply', handleFileDeleteReply);
ipcRenderer.on('file-upload-reply', handleFileUploadReply);
ipcRenderer.on('directory-create-reply', handleDirectoryCreateReply);
ipcRenderer.on('export-reply', handleExportReplay);
return () => {
ipcRenderer.removeListener('file-list-get-reply', handleListGetReply);
ipcRenderer.removeListener('file-delete-reply', handleFileDeleteReply);
ipcRenderer.removeListener('file-upload-reply', handleFileUploadReply);
ipcRenderer.removeListener('directory-create-reply', handleDirectoryCreateReply);
ipcRenderer.removeListener('export-reply', handleExportReplay);
};
}, [uploaderProfile, dirPath]);
const handleNameClick = (record: ListFile) => {
if (record.type === 'directory') {
const newPath = [...dirPath, formatFileName(record.name)];
setDirPath(newPath);
getList(newPath.join('/'));
} else {
clipboard.writeText(record.url as string);
message.success('链接已复制到粘贴板');
}
};
const handlePathClick = (index: number) => {
if (index === -1) {
setDirPath([]);
getList();
} else {
const newPath = dirPath.slice(0, index + 1);
setDirPath(newPath);
getList(newPath.join('/'));
}
};
const setCurrentProfile = (uploaderProfileId: string) => {
setDirPath([]);
const uploaderProfile = uploaderProfiles.find(item => item.id === uploaderProfileId);
setUploaderProfile(uploaderProfile as UploaderProfile);
};
const formatFileName = (name: string) => {
if (dirPath.length > 0) {
const pathPrefix = dirPath.join('/') + '/';
return name.split(pathPrefix).pop() || '';
} else {
return name;
}
};
const [selectRowKeys, setRowKeys] = useState([] as string[]);
const [selectRows, setSelectRows] = useState([] as ListFile[]);
const handleTableRowChange = (selectedRowKeys, selectedRows: ListFile[]) => {
setRowKeys(selectedRowKeys);
setSelectRows(selectedRows);
};
const handleRefresh = () => {
getList(dirPath.join('/'));
};
const handleBatchDelete = () => {
Modal.confirm({
title: '确认删除',
onOk: () => {
const names = selectRows.map(item => [...dirPath, formatFileName(item.name)].join('/'));
message.info({ content: '正在删除,请稍后...', key: 'file-manage-delete' });
ipcRenderer.send('file-delete', uploaderProfile.id, names);
}
});
};
const handleDelete = (record: ListFile) => {
let name = record.name;
Modal.confirm({
title: '确认删除',
content: name,
onOk: () => {
let name = record.name;
if (record.type === 'directory') {
name = `${[...dirPath, record.name].join('/')}/`;
} else {
name = [...dirPath, formatFileName(record.name)].join('/');
}
message.info({ content: '正在删除,请稍后...', key: 'file-manage-delete' });
ipcRenderer.send('file-delete', uploaderProfile.id, [name]);
}
});
};
const uploadRef = useRef<HTMLInputElement>(null);
const handleFileUpload = (event: React.FormEvent<HTMLInputElement>) => {
const fileList = event.currentTarget.files || [];
const filesPath = Array.from(fileList).map(file => file.path);
const pathPrefix = dirPath.join('/');
ipcRenderer.send('file-upload', uploaderProfile.id, filesPath, pathPrefix);
event.currentTarget.value = '';
};
const [modalVisible, setModalVisible] = useState(false);
const [form] = Form.useForm();
const handleCreateDirectory = () => {
form.validateFields().then(values => {
ipcRenderer.send('directory-create', uploaderProfile.id, values?.directoryPath || '');
});
};
const handleDownload = (record: ListFile) => {
ipcRenderer.send('file-download', record.name, record.url);
};
const [exportLoading, setExportLoading] = useState(false);
const handleExport = () => {
const data = selectRows.map(item => {
const fileNameArr = item.name.split('.');
fileNameArr.pop();
return {
name: fileNameArr.join('.'),
url: item.url
};
});
setExportLoading(true);
ipcRenderer.send('export', data);
};
const columns: ColumnsType<ListFile> = [
{
title: '文件名',
dataIndex: 'name',
ellipsis: true,
render: (val: string, record: ListFile) => (
<div style={{ display: 'flex', alignItems: 'center' }}>
{record.type === 'directory' ? (
<FolderFilled style={{ fontSize: 16 }} />
) : (
<FileOutlined style={{ fontSize: 16 }} />
)}
{record.type === 'directory' ? (
<a
title={val}
onClick={() => handleNameClick(record)}
className="table-filename"
style={{ marginLeft: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{formatFileName(val)}
</a>
) : (
<Popover
placement="topLeft"
content={() =>
/(jpg|png|gif|jpeg)$/.test(val) ? (
<Image
style={{ maxWidth: 500 }}
src={record.url}
fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAADDCAYAAADQvc6UAAABRWlDQ1BJQ0MgUHJvZmlsZQAAKJFjYGASSSwoyGFhYGDIzSspCnJ3UoiIjFJgf8LAwSDCIMogwMCcmFxc4BgQ4ANUwgCjUcG3awyMIPqyLsis7PPOq3QdDFcvjV3jOD1boQVTPQrgSkktTgbSf4A4LbmgqISBgTEFyFYuLykAsTuAbJEioKOA7DkgdjqEvQHEToKwj4DVhAQ5A9k3gGyB5IxEoBmML4BsnSQk8XQkNtReEOBxcfXxUQg1Mjc0dyHgXNJBSWpFCYh2zi+oLMpMzyhRcASGUqqCZ16yno6CkYGRAQMDKMwhqj/fAIcloxgHQqxAjIHBEugw5sUIsSQpBobtQPdLciLEVJYzMPBHMDBsayhILEqEO4DxG0txmrERhM29nYGBddr//5/DGRjYNRkY/l7////39v///y4Dmn+LgeHANwDrkl1AuO+pmgAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAwqADAAQAAAABAAAAwwAAAAD9b/HnAAAHlklEQVR4Ae3dP3PTWBSGcbGzM6GCKqlIBRV0dHRJFarQ0eUT8LH4BnRU0NHR0UEFVdIlFRV7TzRksomPY8uykTk/zewQfKw/9znv4yvJynLv4uLiV2dBoDiBf4qP3/ARuCRABEFAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghggQAQZQKAnYEaQBAQaASKIAQJEkAEEegJmBElAoBEgghgg0Aj8i0JO4OzsrPv69Wv+hi2qPHr0qNvf39+iI97soRIh4f3z58/u7du3SXX7Xt7Z2enevHmzfQe+oSN2apSAPj09TSrb+XKI/f379+08+A0cNRE2ANkupk+ACNPvkSPcAAEibACyXUyfABGm3yNHuAECRNgAZLuYPgEirKlHu7u7XdyytGwHAd8jjNyng4OD7vnz51dbPT8/7z58+NB9+/bt6jU/TI+AGWHEnrx48eJ/EsSmHzx40L18+fLyzxF3ZVMjEyDCiEDjMYZZS5wiPXnyZFbJaxMhQIQRGzHvWR7XCyOCXsOmiDAi1HmPMMQjDpbpEiDCiL358eNHurW/5SnWdIBbXiDCiA38/Pnzrce2YyZ4//59F3ePLNMl4PbpiL2J0L979+7yDtHDhw8vtzzvdGnEXdvUigSIsCLAWavHp/+qM0BcXMd/q25n1vF57TYBp0a3mUzilePj4+7k5KSLb6gt6ydAhPUzXnoPR0dHl79WGTNCfBnn1uvSCJdegQhLI1vvCk+fPu2ePXt2tZOYEV6/fn31dz+shwAR1sP1cqvLntbEN9MxA9xcYjsxS1jWR4AIa2Ibzx0tc44fYX/16lV6NDFLXH+YL32jwiACRBiEbf5KcXoTIsQSpzXx4N28Ja4BQoK7rgXiydbHjx/P25TaQAJEGAguWy0+2Q8PD6/Ki4R8EVl+bzBOnZY95fq9rj9zAkTI2SxdidBHqG9+skdw43borCXO/ZcJdraPWdv22uIEiLA4q7nvvCug8WTqzQveOH26fodo7g6uFe/a17W3+nFBAkRYENRdb1vkkz1CH9cPsVy/jrhr27PqMYvENYNlHAIesRiBYwRy0V+8iXP8+/fvX11Mr7L7ECueb/r48eMqm7FuI2BGWDEG8cm+7G3NEOfmdcTQw4h9/55lhm7DekRYKQPZF2ArbXTAyu4kDYB2YxUzwg0gi/41ztHnfQG26HbGel/crVrm7tNY+/1btkOEAZ2M05r4FB7r9GbAIdxaZYrHdOsgJ/wCEQY0J74TmOKnbxxT9n3FgGGWWsVdowHtjt9Nnvf7yQM2aZU/TIAIAxrw6dOnAWtZZcoEnBpNuTuObWMEiLAx1HY0ZQJEmHJ3HNvGCBBhY6jtaMoEiJB0Z29vL6ls58vxPcO8/zfrdo5qvKO+d3Fx8Wu8zf1dW4p/cPzLly/dtv9Ts/EbcvGAHhHyfBIhZ6NSiIBTo0LNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiECRCjUbEPNCRAhZ6NSiAARCjXbUHMCRMjZqBQiQIRCzTbUnAARcjYqhQgQoVCzDTUnQIScjUohAkQo1GxDzQkQIWejUogAEQo121BzAkTI2agUIkCEQs021JwAEXI2KoUIEKFQsw01J0CEnI1KIQJEKNRsQ80JECFno1KIABEKNdtQcwJEyNmoFCJAhELNNtScABFyNiqFCBChULMNNSdAhJyNSiEC/wGgKKC4YMA4TAAAAABJRU5ErkJggg=="
/>
) : (
val
)
}
trigger="hover"
>
<a
title={val}
onClick={() => handleNameClick(record)}
className="table-filename"
style={{ marginLeft: 10, overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{formatFileName(val)}
</a>
</Popover>
)}
</div>
)
},
{
title: '文件大小',
dataIndex: 'size',
ellipsis: true,
width: 120,
render: val => (val ? filesize(val) : '-')
},
{
title: '更新时间',
dataIndex: 'lastModified',
ellipsis: true,
width: 200,
render: val => (val ? dayjs(val).format('YYYY-MM-DD HH:mm:ss') : '-')
},
{
title: '操作',
width: 120,
render: (_, record) => (
<Space>
{record.type !== 'directory' && (
<>
<DownloadOutlined onClick={() => handleDownload(record)} />
<CopyOutlined onClick={() => handleNameClick(record)} />
</>
)}
<DeleteOutlined onClick={() => handleDelete(record)} />
</Space>
)
}
];
return (
<div className="storage-page">
<header>
<span>文件管理</span>
<Divider />
</header>
<Space style={{ marginBottom: 10 }}>
<Select style={{ minWidth: 120 }} value={uploaderProfile?.id} onChange={setCurrentProfile}>
{uploaderProfiles.map(item => (
<Select.Option key={item.name} value={item.id}>
{item.name}
</Select.Option>
))}
</Select>
<Button
title="上传"
icon={<UploadOutlined />}
disabled={!hasFileManageFeature}
type="primary"
onClick={() => {
uploadRef.current?.click();
}}
/>
<Button title="刷新" icon={<ReloadOutlined />} disabled={!hasFileManageFeature} onClick={handleRefresh} />
<Button
title="创建文件夹"
icon={<FolderAddOutlined />}
disabled={!hasFileManageFeature}
onClick={() => {
setModalVisible(true);
}}
/>
<Button
title="导出"
icon={<ExportOutlined />}
disabled={selectRows.length === 0}
onClick={handleExport}
loading={exportLoading}
/>
<Button title="删除" icon={<DeleteOutlined />} disabled={selectRows.length === 0} onClick={handleBatchDelete} />
</Space>
<Breadcrumb style={{ marginBottom: 10 }}>
<Breadcrumb.Item>
<a onClick={() => handlePathClick(-1)}>全部文件</a>
</Breadcrumb.Item>
{dirPath.map((item, index) => (
<Breadcrumb.Item key={item}>
<a onClick={() => handlePathClick(index)}>{item}</a>
</Breadcrumb.Item>
))}
</Breadcrumb>
<div className="table-wrapper">
<Table
size="small"
rowKey="name"
scroll={{ y: windowHeight - 270 }}
dataSource={list}
columns={columns}
pagination={{
size: 'small',
defaultPageSize: 100,
pageSizeOptions: ['50', '100', '200'],
hideOnSinglePage: true
}}
loading={listLoading}
rowSelection={{
onChange: handleTableRowChange,
selectedRowKeys: selectRowKeys,
getCheckboxProps: record => ({ disabled: record?.type === 'directory' })
}}
/>
</div>
<input ref={uploadRef} type="file" multiple hidden onChange={handleFileUpload} />
<Modal
title="创建目录"
visible={modalVisible}
onCancel={() => setModalVisible(false)}
onOk={handleCreateDirectory}
destroyOnClose={true}
>
<Form form={form} preserve={false}>
<Form.Item
label="目录名称"
name="directoryPath"
rules={[{ required: true }, { pattern: domainPathRegExp, message: '目录名不能以 / 开头或结尾' }]}
>
<Input autoFocus />
</Form.Item>
</Form>
</Modal>
</div>
);
}
Example #27
Source File: index.tsx From nanolooker with MIT License | 4 votes |
Node: React.FC = () => {
const { t } = useTranslation();
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const {
uptime: { seconds },
} = useUptime();
const {
version: { node_vendor },
} = useVersion();
const {
nodeStatus: {
memory: { total = 0 } = {},
nodeStats: { cpu = 0, memory = 0 } = {},
},
getNodeStatus,
isLoading: isNodeStatusLoading,
} = React.useContext(NodeStatusContext);
const [formattedMemory, setFormattedMemory] = React.useState(formatBytes(0));
const [formattedTotal, setFormattedTotal] = React.useState(formatBytes(0));
const refreshNode = async () => {
setIsLoading(true);
await refreshActionDelay(getNodeStatus);
setIsLoading(false);
};
const opacity = isLoading ? 0.5 : 1;
React.useEffect(() => {
setFormattedMemory(formatBytes(memory));
}, [memory]);
React.useEffect(() => {
setFormattedTotal(formatBytes(total));
}, [total]);
return (
<Card
size="small"
title={t("common.node")}
extra={
<Tooltip title={t("pages.status.reload")}>
<Button
type="primary"
icon={<ReloadOutlined />}
size="small"
onClick={refreshNode}
loading={isLoading}
/>
</Tooltip>
}
>
<LoadingStatistic
title={t("pages.status.version")}
value={node_vendor}
isLoading={!node_vendor}
style={{ opacity }}
/>
<LoadingStatistic
title={t("pages.status.uptime")}
tooltip={t("tooltips.uptime")}
value={secondsToTime(seconds || 0)}
isLoading={!node_vendor}
style={{ opacity }}
/>
<LoadingStatistic
title={t("pages.status.cpuUsage")}
value={new BigNumber(cpu).decimalPlaces(2).toNumber()}
suffix="%"
isLoading={isNodeStatusLoading}
style={{ opacity }}
/>
<LoadingStatistic
title={t("pages.status.memory")}
value={`${new BigNumber(formattedMemory.value).toFormat(
2,
)} / ${new BigNumber(formattedTotal.value).toFormat(2)}`}
suffix={formattedTotal.suffix}
isLoading={isNodeStatusLoading}
style={{ opacity }}
/>
</Card>
);
}
Example #28
Source File: VizOperationMenu.tsx From datart with Apache License 2.0 | 4 votes |
VizOperationMenu: FC<{
onShareLinkClick?;
onDownloadDataLinkClick?;
onSaveAsVizs?;
onReloadData?;
onAddToDashBoard?;
onPublish?;
onRecycleViz?;
allowDownload?: boolean;
allowShare?: boolean;
allowManage?: boolean;
isArchived?: boolean;
}> = memo(
({
onShareLinkClick,
onDownloadDataLinkClick,
onSaveAsVizs,
onReloadData,
onAddToDashBoard,
onPublish,
allowDownload,
allowShare,
allowManage,
isArchived,
onRecycleViz,
}) => {
const t = useI18NPrefix(`viz.action`);
const tg = useI18NPrefix(`global`);
const moreActionMenu = () => {
const menus: any[] = [];
if (onReloadData) {
menus.push(
<Menu.Item
key="reloadData"
icon={<ReloadOutlined />}
onClick={onReloadData}
>
{t('syncData')}
</Menu.Item>,
<Menu.Divider key={'reloadDataLine'} />,
);
}
if (allowManage && onSaveAsVizs) {
menus.push(
<Menu.Item key="saveAs" icon={<CopyFilled />} onClick={onSaveAsVizs}>
{tg('button.saveAs')}
</Menu.Item>,
);
}
if (allowManage && onSaveAsVizs) {
menus.push(
<Menu.Item
key="addToDash"
icon={<FileAddOutlined />}
onClick={() => onAddToDashBoard(true)}
>
{t('addToDash')}
</Menu.Item>,
<Menu.Divider key="addToDashLine" />,
);
}
if (allowShare && onShareLinkClick) {
menus.push(
<Menu.Item
key="shareLink"
icon={<ShareAltOutlined />}
onClick={onShareLinkClick}
>
{t('share.shareLink')}
</Menu.Item>,
);
}
if (allowDownload && onDownloadDataLinkClick) {
menus.push(
<Menu.Item key="downloadData" icon={<CloudDownloadOutlined />}>
<Popconfirm
placement="left"
title={t('common.confirm')}
onConfirm={() => {
onDownloadDataLinkClick(DownloadFileType.Excel);
}}
okText={t('common.ok')}
cancelText={t('common.cancel')}
>
{t('share.downloadData')}
</Popconfirm>
</Menu.Item>,
<Menu.Item key="downloadPDF" icon={<CloudDownloadOutlined />}>
<Popconfirm
placement="left"
title={t('common.confirm')}
onConfirm={() => {
onDownloadDataLinkClick(DownloadFileType.Pdf);
}}
okText={t('common.ok')}
cancelText={t('common.cancel')}
>
{t('share.downloadPDF')}
</Popconfirm>
</Menu.Item>,
<Menu.Item key="downloadPicture" icon={<CloudDownloadOutlined />}>
<Popconfirm
placement="left"
title={t('common.confirm')}
onConfirm={() => {
onDownloadDataLinkClick(DownloadFileType.Image);
}}
okText={t('common.ok')}
cancelText={t('common.cancel')}
>
{t('share.downloadPicture')}
</Popconfirm>
</Menu.Item>,
<Menu.Divider />,
<Menu.Divider key="downloadDataLine" />,
);
}
if (allowManage && !isArchived && onPublish) {
menus.push(
<Menu.Item
key="publish"
icon={<VerticalAlignBottomOutlined />}
onClick={onPublish}
>
{t('unpublish')}
</Menu.Item>,
);
}
if (allowManage && onRecycleViz) {
menus.push(
<Menu.Item key="delete" icon={<DeleteOutlined />}>
<Popconfirm
title={tg('operation.archiveConfirm')}
onConfirm={onRecycleViz}
>
{tg('button.archive')}
</Popconfirm>
</Menu.Item>,
);
}
return <Menu>{menus}</Menu>;
};
return <StyleVizOperationMenu>{moreActionMenu()}</StyleVizOperationMenu>;
},
)
Example #29
Source File: ExtrinsicRecords.tsx From subscan-multisig-react with Apache License 2.0 | 4 votes |
/* -----------------------------------extrinsic tabs------------------------------------ */
// eslint-disable-next-line complexity
export function ExtrinsicRecords() {
const { networkConfig } = useApi();
const { t } = useTranslation();
const { account: multiAddress } = useParams<{ account: string }>();
const { multisigAccount, inProgress, confirmedAccount, cancelledAccount, queryInProgress, loadingInProgress } =
useMultisigContext();
const [tabKey, setTabKey] = useState('inProgress');
const [confirmedPage, setConfirmedPage] = useState(1);
const [cancelledPage, setCancelledPage] = useState(1);
const [first, setFirst] = useState(true);
const [fetchConfimed, { data: confirmedData, loading: loadingConfirmed }] = useManualQuery<MultisigRecordsQueryRes>(
MULTISIG_RECORD_QUERY,
{
variables: {
account: multiAddress,
status: 'confirmed',
offset: (confirmedPage - 1) * 10,
limit: 10,
},
}
);
const [fetchCancelled, { data: cancelledData, loading: loadingCancelled }] = useManualQuery<MultisigRecordsQueryRes>(
MULTISIG_RECORD_QUERY,
{
variables: {
account: multiAddress,
status: 'cancelled',
offset: (cancelledPage - 1) * 10,
limit: 10,
},
}
);
// eslint-disable-next-line complexity
useEffect(() => {
if (!loadingInProgress && confirmedAccount !== undefined && first) {
setFirst(false);
if (inProgress.length === 0 && confirmedAccount > 0) {
setTabKey('confirmed');
}
}
}, [loadingInProgress, confirmedAccount, first, inProgress]);
useEffect(() => {
if (networkConfig?.api?.subql) {
fetchConfimed();
fetchCancelled();
}
}, [networkConfig, fetchCancelled, fetchConfimed]);
useEffect(() => {
if (networkConfig?.api?.subql) {
fetchConfimed();
}
}, [confirmedPage, fetchConfimed, networkConfig]);
useEffect(() => {
if (networkConfig?.api?.subql) {
fetchCancelled();
}
}, [cancelledPage, fetchCancelled, networkConfig]);
// eslint-disable-next-line complexity
const handleChangeTab = (key: string) => {
setTabKey(key);
if (key === 'inProgress') {
queryInProgress();
} else if (key === 'confirmed') {
if (networkConfig?.api?.subql) {
fetchConfimed();
}
} else if (key === 'cancelled') {
if (networkConfig?.api?.subql) {
fetchCancelled();
}
}
};
const refreshData = () => {
queryInProgress();
if (networkConfig?.api?.subql) {
fetchConfimed();
fetchCancelled();
}
};
return (
<div className="relative">
<div className="lg:absolute lg:right-2 lg:top-2 cursor-pointer z-50" onClick={refreshData}>
<ReloadOutlined />
</div>
<Tabs activeKey={tabKey} onChange={handleChangeTab}>
<TabPane
tab={
<Space>
<span>{t('multisig.In Progress')}</span>
<span>{inProgress.length}</span>
</Space>
}
key="inProgress"
>
{multisigAccount?.address ? (
<Entries
source={inProgress}
account={multisigAccount}
loading={loadingInProgress}
totalCount={inProgress.length}
/>
) : (
<Spin className="w-full mt-4" />
)}
</TabPane>
{networkConfig?.api?.subql && (
<>
<TabPane
tab={
<Space>
<span>{t('multisig.Confirmed Extrinsic')}</span>
<span>{confirmedAccount}</span>
</Space>
}
key="confirmed"
>
<ConfirmedOrCancelled
nodes={confirmedData?.multisigRecords?.nodes || []}
loading={loadingConfirmed}
account={multisigAccount}
multiAddress={multiAddress}
isConfirmed
totalCount={confirmedData?.multisigRecords?.totalCount || 0}
currentPage={confirmedPage}
onChangePage={setConfirmedPage}
/>
</TabPane>
<TabPane
tab={
<Space>
<span>{t('multisig.Cancelled Extrinsic')}</span>
<span>{cancelledAccount}</span>
</Space>
}
key="cancelled"
>
<ConfirmedOrCancelled
nodes={cancelledData?.multisigRecords?.nodes || []}
loading={loadingCancelled}
account={multisigAccount}
multiAddress={multiAddress}
isConfirmed={false}
totalCount={cancelledData?.multisigRecords?.totalCount || 0}
currentPage={cancelledPage}
onChangePage={setCancelledPage}
/>
</TabPane>
</>
)}
</Tabs>
</div>
);
}