@ant-design/icons#ExclamationCircleOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#ExclamationCircleOutlined.
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: utils.tsx From posthog-foss with MIT License | 7 votes |
export function successToast(title?: string, message?: string): void {
/**
* Shows a standardized success message.
* @param title Title message of the toast
* @param message Body message on the toast
*/
setTimeout(
() =>
toast.success(
<div data-attr="success-toast">
<h1>
<ExclamationCircleOutlined /> {title || 'Success!'}
</h1>
<p>{message || 'Your action was completed successfully.'}</p>
</div>
),
100
)
}
Example #2
Source File: App.tsx From pcap2socks-gui with MIT License | 6 votes |
stopConfirm = () => {
notification.close("network");
const stop = this.stop;
confirm({
title: "停止运行",
icon: <ExclamationCircleOutlined />,
content: "停止运行可能会导致游戏掉线,确认要继续操作吗?",
onOk() {
return stop();
},
onCancel() { },
});
};
Example #3
Source File: App.tsx From jitsu with MIT License | 6 votes |
RouteNotFound: React.FC = () => {
useEffect(() => {
currentPageHeaderStore.setBreadcrumbs("Not found")
})
return (
<div className="flex justify-center pt-12">
<Card bordered={false}>
<Card.Meta
description={
<>
This page does not exist. If you got here by clicking a link within Jitsu interface, try to contact us:{" "}
<Typography.Paragraph copyable={{ tooltips: false }} className="inline">
{"[email protected]"}
</Typography.Paragraph>
</>
}
avatar={
<span className="text-error">
<ExclamationCircleOutlined />
</span>
}
title={"Page Not Found"}
/>
</Card>
</div>
)
}
Example #4
Source File: BasicLayout.tsx From react_admin with MIT License | 6 votes |
BasicLayout: React.FC<Iprops> = (props) => {
const [collapsed, setCollapsed] = useState(false);
const onLogout = () => {
confirm({
title: "你确定要退出登录吗?",
icon: <ExclamationCircleOutlined />,
onOk() {
localStorage.removeItem("userinfo");
console.log(props.history.replace("/"));
},
onCancel() {},
});
};
return (
<Layout hasSider className="page-basic">
<MenuDefault collapsed={collapsed} />
<Layout className="site-layout">
<Header
onLogout={onLogout}
setColl={(coll) => setCollapsed(coll)}
collapsed={collapsed}
/>
<Bread />
<Content
className="site-layout-background"
style={{
margin: "24px 16px",
padding: 24,
minHeight: 280,
}}
>
<Routers location={props.location} />
</Content>
<Footer />
</Layout>
</Layout>
);
}
Example #5
Source File: index.tsx From metaplex with Apache License 2.0 | 6 votes |
TransactionErrorModal = ({
error = '',
onDismiss,
open,
modalView = true,
}: ITransactionErrorModal) => {
const modalBody = (
<div className="error-modal-content">
<div className="warning-icon">
<ExclamationCircleOutlined width={20} height={20} />
</div>
<h4>Transaction error</h4>
<div className="error-text">
Your transaction was not completed for{' '}
{error ? error : 'an unknown reason. Please try again.'}
</div>
<Button onClick={onDismiss}>Dismiss</Button>
</div>
);
if (modalView) {
return (
<Modal
className="transaction-error-modal"
centered
width={500}
mask={false}
visible={open}
onCancel={onDismiss}
footer={null}
closable
>
{modalBody}
</Modal>
);
}
return <div className="create-error-modal">{modalBody}</div>;
}
Example #6
Source File: index.tsx From fe-v5 with Apache License 2.0 | 6 votes |
export function deleteAlertEventsModal(busiId, ids: number[], onSuccess = () => {}) {
confirm({
title: '删除告警事件',
icon: <ExclamationCircleOutlined />,
content: '通常只有在确定监控数据永远不再上报的情况下(比如调整了监控数据标签,或者机器下线)才删除告警事件,因为相关告警事件永远无法自动恢复了,您确定要这么做吗?',
okText: '确认删除',
maskClosable: true,
okButtonProps: { danger: true },
zIndex: 1001,
onOk() {
return deleteAlertEvents(busiId, ids).then((res) => {
message.success('删除成功');
onSuccess();
});
},
onCancel() {},
});
}
Example #7
Source File: algo-node.tsx From XFlow with MIT License | 6 votes |
AlgoIcon: React.FC<IProps> = props => {
if (props.hide) {
return null
}
switch (props.status) {
case NsGraphStatusCommand.StatusEnum.PROCESSING:
return <RedoOutlined spin style={{ color: '#c1cdf7', fontSize: '16px' }} />
case NsGraphStatusCommand.StatusEnum.ERROR:
return <CloseCircleOutlined style={{ color: '#ff4d4f', fontSize: '16px' }} />
case NsGraphStatusCommand.StatusEnum.SUCCESS:
return <CheckCircleOutlined style={{ color: '#39ca74cc', fontSize: '16px' }} />
case NsGraphStatusCommand.StatusEnum.WARNING:
return <ExclamationCircleOutlined style={{ color: '#faad14', fontSize: '16px' }} />
case NsGraphStatusCommand.StatusEnum.DEFAULT:
return <InfoCircleOutlined style={{ color: '#d9d9d9', fontSize: '16px' }} />
default:
return null
}
}
Example #8
Source File: index.tsx From dnde with GNU General Public License v3.0 | 6 votes |
modalConfirmLoadLocalState = async (okCallback: () => void, cancelCallback: () => void) => {
return new Promise<boolean>((resolve, reject) => {
Modal.confirm({
title: 'Confirm',
icon: <ExclamationCircleOutlined />,
content: 'local save found do you want to load it?',
okText: 'restore',
cancelText: 'cancel',
onOk: () => {
okCallback();
resolve(true);
},
onCancel: () => {
cancelCallback();
resolve(false);
},
});
});
}
Example #9
Source File: Invites.tsx From posthog-foss with MIT License | 6 votes |
function makeActionsComponent(
deleteInvite: (invite: OrganizationInviteType) => void
): (_: any, invite: any) => JSX.Element {
return function ActionsComponent(_, invite: OrganizationInviteType): JSX.Element {
return (
<LemonButton
title="Cancel the invite"
compact
data-attr="invite-delete"
icon={<IconClose />}
status="danger"
onClick={() => {
invite.is_expired
? deleteInvite(invite)
: Modal.confirm({
title: `Do you want to cancel the invite for ${invite.target_email}?`,
icon: <ExclamationCircleOutlined />,
okText: 'Yes, cancel invite',
okType: 'danger',
onOk() {
deleteInvite(invite)
},
cancelText: 'No, keep invite',
})
}}
/>
)
}
}
Example #10
Source File: index.tsx From amiya with MIT License | 6 votes |
/**
* 注册【批量删除】事件
*/
registerAction('batch-delete', (props, _record, searchTable) => {
return {
icon: <DeleteOutlined />,
tableFooterExtraOnly: true,
onClick: () => {
let selection = searchTable?.selection || []
if (!selection.length) {
info(locale.action.noSelection)
return
}
if (searchTable?.deleteApi) {
Modal.confirm({
title: locale.action.deleteConfirmTitle,
content: `${locale.action.deleteConfirmBefore} ${selection.length} ${locale.action.deleteConfirmAfter}`,
icon: <ExclamationCircleOutlined />,
onOk: () => {
let params: Array<string> = selection.map((row: any) => getKey(row, searchTable?.rowKey))
searchTable?.deleteApi(params).then((data: any) => {
success(props.successMsg || locale.action.deleteConfirmBatchSuccess)
searchTable?.clearSelection()
searchTable?.tableRef.current.refresh()
// 请求完成回调
if (props.onFinish) {
props.onFinish({ data, params })
}
})
}
})
}
},
...props
}
})
Example #11
Source File: PersonalAPIKeys.tsx From posthog-foss with MIT License | 6 votes |
function RowActionsCreator(
deleteKey: (key: PersonalAPIKeyType) => void
): (personalAPIKey: PersonalAPIKeyType) => JSX.Element {
return function RowActions(personalAPIKey: PersonalAPIKeyType) {
return (
<Popconfirm
title={`Permanently delete key "${personalAPIKey.label}"?`}
okText="Delete Key"
okType="danger"
icon={<ExclamationCircleOutlined style={{ color: red.primary }} />}
placement="left"
onConfirm={() => {
deleteKey(personalAPIKey)
}}
>
<a className="text-danger">Delete</a>
</Popconfirm>
)
}
}
Example #12
Source File: component.tsx From visual-layout with MIT License | 5 votes |
Component: React.FC<{ page: PageService }> = ({ page }) => {
const [codeConfig, setCodeConfig] = useState(DEFAULT.codeConfig);
const [name, setName] = useState(DEFAULT.codeConfig.componentName);
useEffect(() => {
if (page) {
setCodeConfig(page.currentNode[0]?.codeConfig);
setName(page.currentNode[0]?.codeConfig.componentName);
}
// eslint-disable-next-line
}, [page?.currentNode, page?.currentNode[0]?.codeConfig]);
const updateIsComponent = (value: boolean) => {
if (value !== codeConfig.isComponent) {
page.setCodeConfig({
...codeConfig,
isComponent: value,
});
}
};
const updateComponentName = (e: React.FocusEvent<HTMLInputElement>) => {
if (e.target.value !== codeConfig.componentName) {
page.setCodeConfig({
...codeConfig,
componentName: e.target.value,
});
}
};
return (
<div className={styles.container}>
<div>
组件
<Tooltip placement="topLeft" title="生成独立代码文件">
<ExclamationCircleOutlined style={{ marginLeft: 5 }} />
</Tooltip>
</div>
<div className={styles.rightContainer}>
<Switch
checked={codeConfig?.isComponent}
onChange={value => updateIsComponent(value)}
/>
<Input
placeholder="组件名"
value={name}
onChange={e => setName(e.target.value)}
onBlur={e => updateComponentName(e)}
/>
</div>
</div>
);
}
Example #13
Source File: DestinationCard.tsx From jitsu with MIT License | 5 votes |
export function DestinationCard({ dst }: DestinationCardProps) {
const reference = destinationsReferenceMap[dst._type]
const rename = async (newName: string) => {
await flowResult(destinationsStore.patch(dst._uid, { displayName: newName }))
}
let deleteAction = () => {
Modal.confirm({
title: "Please confirm deletion of destination",
icon: <ExclamationCircleOutlined />,
content: "Are you sure you want to delete " + dst._id + " destination?",
okText: "Delete",
cancelText: "Cancel",
onOk: async () => {
try {
await flowResult(destinationsStore.delete(dst._uid))
await connectionsHelper.unconnectDeletedDestination(dst._uid)
} catch (errors) {
handleError(errors, "Unable to delete destination at this moment, please try later.")
}
},
})
}
let editLink = projectRoute(destinationPageRoutes.editExact, { id: dst._id })
const statLink = projectRoute(destinationPageRoutes.statisticsExact, { id: dst._id })
return (
<ConnectionCard
title={DestinationsUtils.getDisplayName(dst)}
icon={reference.ui.icon}
deleteAction={deleteAction}
editAction={editLink}
rename={rename}
menuOverlay={
<Menu>
<Menu.Item icon={<EditOutlined />}>
<NavLink to={editLink}>Edit</NavLink>
</Menu.Item>
<Menu.Item icon={<DeleteOutlined />} onClick={deleteAction}>
Delete
</Menu.Item>
<Menu.Item icon={<CodeOutlined />}>
<NavLink to={statLink}>Statistics</NavLink>
</Menu.Item>
</Menu>
}
subtitle={dst._formData?.mode ? <>mode: {dst._formData?.mode}</> : <>{reference.ui.title(dst)}</>}
status={
<Tooltip
overlay={
dst._connectionTestOk ? "Connection successful" : `Connection failed: ${dst._connectionErrorMessage}`
}
>
<Badge
size="default"
status={dst._connectionTestOk ? "success" : "error"}
text={
<>
<span className={`text-${dst._connectionTestOk ? "success" : "error"}`}>
{dst._connectionTestOk ? "Active" : "Connection test failed"}
</span>
{reference.deprecated ? <span className={"text-warning"}> (Deprecated)</span> : <></>}
</>
}
/>
</Tooltip>
}
/>
)
}
Example #14
Source File: DeleteCommentModal.tsx From foodie with MIT License | 5 votes |
DeleteCommentModal: React.FC<IProps> = (props) => {
const [isDeleting, setIsDeleting] = useState(false);
const [error, setError] = useState<IError | null>(null);
const dispatch = useDispatch();
const targetComment = useSelector((state: IRootReducer) => state.helper.targetComment);
const handleDeleteComment = async () => {
try {
setIsDeleting(true);
targetComment && await deleteComment(targetComment.id);
closeModal();
targetComment && props.deleteSuccessCallback(targetComment);
toast.dark('Comment successfully deleted.', {
progressStyle: { backgroundColor: '#4caf50' },
autoClose: 2000
});
} catch (e) {
setIsDeleting(false);
setError(e);
}
};
const closeModal = () => {
if (props.isOpen) {
props.closeModal();
dispatch(setTargetComment(null));
}
}
return (
<Modal
isOpen={props.isOpen}
onAfterOpen={props.onAfterOpen}
onRequestClose={closeModal}
contentLabel="Delete Comment"
className="modal"
shouldCloseOnOverlayClick={!isDeleting}
overlayClassName="modal-overlay"
>
<div className="relative">
<div
className="absolute right-2 top-2 p-1 rounded-full flex items-center justify-center cursor-pointer hover:bg-gray-200 dark:hover:bg-indigo-1100"
onClick={closeModal}
>
<CloseOutlined className="p-2 outline-none text-gray-500 dark:text-white" />
</div>
{error && (
<span className="block p-4 bg-red-100 text-red-500 w-full">
{error?.error?.message || 'Unable process request. Please try again.'}
</span>
)}
<div className="p-4 laptop:px-8">
<h2 className="dark:text-white">
<ExclamationCircleOutlined className="text-red-500 mr-2 pt-2" />
Delete Comment
</h2>
<p className="text-gray-600 my-4 dark:text-white">Are you sure you want to delete this comment?</p>
<div className="flex justify-between">
<button
className="button--muted !rounded-full dark:bg-indigo-1100 dark:text-white dark:hover:bg-indigo-1100"
onClick={closeModal}
>
Cancel
</button>
<button
className="button--danger"
disabled={isDeleting}
onClick={handleDeleteComment}
>
Delete
</button>
</div>
</div>
</div>
</Modal>
);
}
Example #15
Source File: handleHttpError.tsx From next-core with GNU General Public License v3.0 | 5 votes |
/**
* 处理 http 请求错误(使用 AntDesign 模态框弹出错误信息)。
*
* @param error - 错误对象。
*/
export function handleHttpError(
error: Error | HttpFetchError | HttpResponseError | HttpParseError
): ReturnType<ModalFunc> {
// Redirect to login page if not logged in.
if (isUnauthenticatedError(error) && !window.NO_AUTH_GUARD) {
// Do not show multiple confirm modals.
if (unauthenticatedConfirmModal) {
return;
}
unauthenticatedConfirmModal = Modal.confirm({
icon: <ExclamationCircleOutlined />,
content: <LoginTimeoutMessage />,
okText: i18next.t(`${NS_BRICK_KIT}:${K.MODAL_OK}`),
cancelText: i18next.t(`${NS_BRICK_KIT}:${K.MODAL_CANCEL}`),
onOk: () => {
const ssoEnabled = getRuntime().getFeatureFlags()["sso-enabled"];
const history = getHistory();
history.push(ssoEnabled ? "/sso-auth/login" : "/auth/login", {
from: {
...history.location,
state: undefined,
},
});
},
});
return;
}
return Modal.error({
title: i18next.t(`${NS_BRICK_KIT}:${K.REQUEST_FAILED}`),
content: (
<div
style={{
whiteSpace: "pre-wrap",
}}
>
{httpErrorToString(error)}
</div>
),
okText: i18next.t(`${NS_BRICK_KIT}:${K.MODAL_OK}`),
});
}
Example #16
Source File: RestoreAllMessagesModal.tsx From wildduck-ui with MIT License | 5 votes |
RestoreAllMessagesModal = () => {
const { setDateData, setIsModalVisible } = useActions(archiveLogic);
const { dateData, isModalVisible } = useValues(archiveLogic);
const { id }: any = useParams();
const { mutate } = useRestoreArchiveMessages();
const handleOk = () => {
mutate({
userId: id,
params: {
start: _.get(dateData, '0', moment('2000-01-01 00:00:00')).format(DATE_TIME_FORMAT),
end: _.get(dateData, '1', moment()).format(DATE_TIME_FORMAT),
},
});
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
setDateData([]);
};
return (
<Modal visible={isModalVisible} onOk={handleOk} onCancel={handleCancel}>
<Title level={4}>
<ExclamationCircleOutlined />
Are you sure you want to Restore all messages ?
</Title>
<p>Select the start and end date (by default it will restores all the mails).</p>
<RangePicker
disabledDate={(current) => current && current > moment().endOf('day')}
onChange={(value) => setDateData(value)}
showTime
defaultValue={
_.isEmpty(dateData) ? ('' as any) : [moment(_.get(dateData, '0')), moment(_.get(dateData, '1'))]
}
/>
</Modal>
);
}
Example #17
Source File: TeamMembers.tsx From posthog-foss with MIT License | 5 votes |
function ActionsComponent(member: FusedTeamMemberType): JSX.Element | null {
const { user } = useValues(userLogic)
const { currentTeam } = useValues(teamLogic)
const { removeMember } = useActions(teamMembersLogic)
if (!user) {
return null
}
function handleClick(): void {
Modal.confirm({
title: `${
member.user.uuid == user?.uuid
? 'Leave'
: `Remove ${member.user.first_name} (${member.user.email}) from`
} project ${currentTeam?.name}?`,
icon: <ExclamationCircleOutlined />,
okText: member.user.uuid == user?.uuid ? 'Leave' : 'Remove',
okType: 'danger',
onOk() {
removeMember({ member })
},
})
}
const allowDeletion =
// You can leave, but only project admins can remove others
((currentTeam?.effective_membership_level &&
currentTeam.effective_membership_level >= OrganizationMembershipLevel.Admin) ||
member.user.uuid === user.uuid) &&
// Only members without implicit access can leave or be removed
member.organization_level < MINIMUM_IMPLICIT_ACCESS_LEVEL
return allowDeletion ? (
<a className="text-danger" onClick={handleClick} data-attr="delete-team-membership">
{member.user.uuid !== user.uuid ? (
<CloseCircleOutlined title="Remove from project" />
) : (
<LogoutOutlined title="Leave project" />
)}
</a>
) : null
}
Example #18
Source File: index.tsx From fe-v5 with Apache License 2.0 | 4 votes |
Shield: React.FC = () => {
const { t } = useTranslation();
const history = useHistory();
const dispatch = useDispatch();
const [query, setQuery] = useState<string>('');
const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
const [bgid, setBgid] = useState(undefined);
const [clusters, setClusters] = useState<string[]>([]);
const [currentShieldDataAll, setCurrentShieldDataAll] = useState<Array<shieldItem>>([]);
const [currentShieldData, setCurrentShieldData] = useState<Array<shieldItem>>([]);
const [loading, setLoading] = useState<boolean>(false);
const columns: ColumnsType = [
{
title: t('集群'),
dataIndex: 'cluster',
render: (data) => {
return <div>{data}</div>;
},
},
{
title: t('标签'),
dataIndex: 'tags',
render: (text: any) => {
return (
<>
{text
? text.map((tag, index) => {
return tag ? (
// <ColorTag text={`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`} key={index}>
// </ColorTag>
<div key={index} style={{ lineHeight: '16px' }}>{`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`}</div>
) : null;
})
: ''}
</>
);
},
},
{
title: t('屏蔽原因'),
dataIndex: 'cause',
render: (text: string, record: shieldItem) => {
return (
<>
<Tooltip placement='topLeft' title={text}>
<div
style={{
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
lineHeight: '16px',
}}
>
{text}
</div>
</Tooltip>
by {record.create_by}
</>
);
},
},
{
title: t('屏蔽时间'),
dataIndex: 'btime',
render: (text: number, record: shieldItem) => {
return (
<div className='shield-time'>
<div>
{t('开始:')}
{dayjs(record?.btime * 1000).format('YYYY-MM-DD HH:mm:ss')}
</div>
<div>
{t('结束:')}
{dayjs(record?.etime * 1000).format('YYYY-MM-DD HH:mm:ss')}
</div>
</div>
);
},
},
// {
// title: t('创建人'),
// ellipsis: true,
// dataIndex: 'create_by',
// },
{
title: t('操作'),
width: '98px',
dataIndex: 'operation',
render: (text: undefined, record: shieldItem) => {
return (
<>
<div className='table-operator-area'>
<div
className='table-operator-area-normal'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
dispatch({
type: 'shield/setCurShieldData',
data: record,
});
curBusiItem?.id && history.push(`/alert-mutes/edit/${record.id}?mode=clone`);
}}
>
{t('克隆')}
</div>
<div
className='table-operator-area-warning'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
confirm({
title: t('确定删除该告警屏蔽?'),
icon: <ExclamationCircleOutlined />,
onOk: () => {
dismiss(record.id);
},
onCancel() {},
});
}}
>
{t('删除')}
</div>
</div>
</>
);
},
},
];
useEffect(() => {
getList();
}, [curBusiItem]);
useEffect(() => {
filterData();
}, [query, clusters, currentShieldDataAll]);
const dismiss = (id: number) => {
deleteShields({ ids: [id] }, curBusiItem.id).then((res) => {
refreshList();
if (res.err) {
message.success(res.err);
} else {
message.success(t('删除成功'));
}
});
};
const filterData = () => {
const data = JSON.parse(JSON.stringify(currentShieldDataAll));
const res = data.filter((item: shieldItem) => {
const tagFind = item.tags.find((tag) => {
return tag.key.indexOf(query) > -1 || tag.value.indexOf(query) > -1 || tag.func.indexOf(query) > -1;
});
return (item.cause.indexOf(query) > -1 || !!tagFind) && ((clusters && clusters?.indexOf(item.cluster) > -1) || clusters?.length === 0);
});
setCurrentShieldData(res || []);
};
const getList = async () => {
if (curBusiItem.id) {
setLoading(true);
const { success, dat } = await getShieldList({ id: curBusiItem.id });
if (success) {
setCurrentShieldDataAll(dat || []);
setLoading(false);
}
}
};
const refreshList = () => {
getList();
};
const onSearchQuery = (e) => {
let val = e.target.value;
setQuery(val);
};
const clusterChange = (data) => {
setClusters(data);
};
const busiChange = (data) => {
setBgid(data);
};
return (
<PageLayout title={t('屏蔽规则')} icon={<CloseCircleOutlined />} hideCluster>
<div className='shield-content'>
<LeftTree
busiGroup={{
// showNotGroupItem: true,
onChange: busiChange,
}}
></LeftTree>
{curBusiItem?.id ? (
<div className='shield-index'>
<div className='header'>
<div className='header-left'>
<RefreshIcon
className='strategy-table-search-left-refresh'
onClick={() => {
refreshList();
}}
/>
<ColumnSelect onClusterChange={(e) => setClusters(e)} />
<Input onPressEnter={onSearchQuery} className={'searchInput'} prefix={<SearchOutlined />} placeholder={t('搜索标签、屏蔽原因')} />
</div>
<div className='header-right'>
<Button
type='primary'
className='add'
ghost
onClick={() => {
history.push('/alert-mutes/add');
}}
>
{t('新增屏蔽规则')}
</Button>
</div>
</div>
<Table
rowKey='id'
// sticky
pagination={{
total: currentShieldData.length,
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => {
return `共 ${total} 条数据`;
},
pageSizeOptions: pageSizeOptionsDefault,
defaultPageSize: 30,
}}
loading={loading}
dataSource={currentShieldData}
columns={columns}
/>
</div>
) : (
<BlankBusinessPlaceholder text='屏蔽规则' />
)}
</div>
</PageLayout>
);
}
Example #19
Source File: AddFiltersForm.tsx From wildduck-ui with MIT License | 4 votes |
AddFiltersForm: React.FC<Props> = (props: Props) => {
const { setShowAddFilterForm, setFilterId } = useActions(filtersLogic);
const { filterId } = useValues(filtersLogic);
const [form] = Form.useForm();
const { confirm } = Modal;
const params: { id: string } = useParams();
const { data, isLoading } = useFilterDetails(params.id, filterId);
const { data: mailboxesList } = useMailboxes({ userId: params.id });
const { mutate: createFilter } = useCreateFilter();
const { mutate: updateFilter } = useUpdateFilter();
const onFinish = (values: any) => {
const query = _.pick(values, ['from', 'to', 'subject', 'listId', 'text', 'ha', 'size']);
const action = _.pick(values, ['seen', 'flag', 'delete', 'spam', 'mailbox', 'targets']);
const filter: any = _.pick(values, ['name', 'disabled']);
_.set(filter, 'query', query);
_.set(filter, 'action', action);
showConfirm(filter);
};
function showConfirm(filter: CreateFilterRequest) {
confirm({
title: 'Are you sure you want to save this Filter ?',
icon: <ExclamationCircleOutlined />,
onOk() {
props.action === 'create'
? createFilter({ userId: params.id, filterDetails: filter })
: updateFilter({ userId: params.id, filterId: filterId, filterDetails: filter });
},
});
}
const reset = () => {
form.resetFields();
};
useEffect(() => {
form.setFieldsValue({ ...data });
}, [data]);
const pageBreadcrumb = (
<Breadcrumb>
<Breadcrumb.Item>
<a
onClick={(event) => {
event.stopPropagation();
setShowAddFilterForm(false);
setFilterId('');
}}
>
Filters
</a>
</Breadcrumb.Item>
<Breadcrumb.Item>{props.action === 'create' ? 'Add Filter' : 'Update Filter'}</Breadcrumb.Item>
</Breadcrumb>
);
return (
<Page title={pageBreadcrumb} loading={isLoading}>
<Form wrapperCol={{ span: 6 }} labelCol={{ span: 3 }} form={form} onFinish={onFinish}>
<Form.Item label='Filter name' name='name'>
<Input placeholder='Name of the Filter' />
</Form.Item>
<Form.Item label='Query' tooltip={filtersTooltip.query}>
{_.map(['from', 'to', 'subject', 'listId', 'text'], (query) => {
return (
<Form.Item key={query} name={query} label={query} tooltip={_.get(filtersTooltip, query)}>
<Input />
</Form.Item>
);
})}
<Form.Item name='ha' label='has attachment' tooltip={filtersTooltip.ha} valuePropName='checked'>
<Switch />
</Form.Item>
<Form.Item name='size' label='size' tooltip={filtersTooltip.size}>
<Input />
</Form.Item>
</Form.Item>
<Form.Item label='Action' tooltip={filtersTooltip.action}>
{_.map(['seen', 'flag', 'delete', 'spam'], (query) => {
return (
<Form.Item
name={query}
key={query}
label={query}
tooltip={_.get(filtersTooltip, query)}
valuePropName='checked'
>
<Switch />
</Form.Item>
);
})}
<Form.Item name='mailbox' label='Mailbox' tooltip={filtersTooltip.mailbox}>
<Select showSearch>
{_.map(mailboxesList, (mailbox: any) => {
return (
<Select.Option value={mailbox?.id} key={mailbox?.name + mailbox?.id}>
{mailbox?.name}
</Select.Option>
);
})}
</Select>
</Form.Item>
<Form.Item name='targets' label='Targets' tooltip={filtersTooltip.targets}>
<Select mode='tags' placeholder='Enter Targets' />
</Form.Item>
</Form.Item>
<Form.Item
name='disabled'
label='Disable Filter'
tooltip={filtersTooltip.disabled}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item {...tailLayout}>
<Space size='middle'>
<Button type='default' htmlType='button' onClick={reset}>
Reset
</Button>
<Button type='primary' htmlType='submit'>
Save
</Button>
</Space>
</Form.Item>
</Form>
</Page>
);
}
Example #20
Source File: index.tsx From metaplex with Apache License 2.0 | 4 votes |
AdjustQuantitiesStep = ({
allowedAmountToRedeem,
distributionType,
weightByMetadataKey,
supplyByMetadataKey,
selectedItems,
setPackState,
isUnlimited,
}: AdjustQuantitiesStepProps): ReactElement => {
const availableDistributionTypes = [
...(isUnlimited ? [PackDistributionType.Unlimited] : []),
PackDistributionType.MaxSupply,
PackDistributionType.Fixed,
];
const shouldRenderSupplyInput =
distributionType !== PackDistributionType.Unlimited;
const shouldRenderWeightInput =
distributionType !== PackDistributionType.MaxSupply;
const handleRedeemAmountChange = (value: string): void => {
setPackState({
allowedAmountToRedeem: parseInt(value),
});
};
const handleDistributionChange = (
item: SafetyDepositDraft,
value: string,
inputType: InputType,
): void => {
const number = Number(value);
const pubKey = item.metadata.pubkey;
if (inputType === InputType.weight && number > 100) {
return;
}
if (inputType === InputType.weight) {
return setPackState({
weightByMetadataKey: {
...weightByMetadataKey,
[pubKey]: number,
},
});
}
const maxSupply = item.masterEdition?.info.maxSupply?.toNumber();
setPackState({
supplyByMetadataKey: {
...supplyByMetadataKey,
[pubKey]:
maxSupply !== undefined && number > maxSupply ? maxSupply : number,
},
});
};
const handleDistributionTypeChange = (type: PackDistributionType): void => {
setPackState({ distributionType: type });
};
return (
<div className="quantities-step-wrapper">
<p className="quantities-step-wrapper__title">
Set number of cards in pack
</p>
<p className="quantities-step-wrapper__subtitle">
Number of times user can redeem a card using a single voucher.
</p>
<Input
className="quantities-step-wrapper__input"
type="number"
value={allowedAmountToRedeem}
onChange={({ target: { value } }) => handleRedeemAmountChange(value)}
/>
<p className="quantities-step-wrapper__title">Select distribution type</p>
<div className="cards-select">
{availableDistributionTypes.map(type => (
<SelectCard
key={type}
title={DISTRIBUTION_TYPES_DATA[type].title}
subtitle={DISTRIBUTION_TYPES_DATA[type].subtitle}
isSelected={distributionType === type}
onClick={() => handleDistributionTypeChange(type)}
/>
))}
</div>
<div className="quantities-step-wrapper__table-titles">
{shouldRenderSupplyInput && <p>NUMBER OF NFTs</p>}
{shouldRenderWeightInput && (
<p className="redeem-weight">
REDEEM WEIGHT{' '}
<span>
— Weights must be between 1-100. 1 is least likely, 100 is most
likely.
</span>
</p>
)}
</div>
{Object.values(selectedItems).map(item => (
<ItemRow key={item.metadata.pubkey} item={item}>
<>
{shouldRenderSupplyInput && (
<div className="input-column">
<Input
type="number"
min={0}
max={item.masterEdition?.info.maxSupply?.toNumber()}
className={classNames({
'ant-error-input':
!supplyByMetadataKey[item.metadata.pubkey],
})}
value={supplyByMetadataKey[item.metadata.pubkey]}
onChange={({ target: { value } }) =>
handleDistributionChange(item, value, InputType.maxSupply)
}
/>
</div>
)}
{shouldRenderWeightInput && (
<div className="input-column">
<Input
type="number"
min={0}
max={100}
value={weightByMetadataKey[item.metadata.pubkey]}
onChange={({ target: { value } }) =>
handleDistributionChange(item, value, InputType.weight)
}
className={classNames({
'ant-error-input error-redeem':
!weightByMetadataKey[item.metadata.pubkey],
})}
/>
{!weightByMetadataKey[item.metadata.pubkey] && (
<div className="error-tooltip-container">
<Tooltip
overlayClassName="creat-pack-redeem-tooltip"
placement="top"
title="Weight must be between 1-100"
>
<ExclamationCircleOutlined className="input-info" />
</Tooltip>
</div>
)}
</div>
)}
</>
</ItemRow>
))}
</div>
);
}
Example #21
Source File: index.tsx From fe-v5 with Apache License 2.0 | 4 votes |
Shield: React.FC = () => {
const { t } = useTranslation();
const history = useHistory();
const [query, setQuery] = useState<string>('');
const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
const [bgid, setBgid] = useState(undefined);
const [clusters, setClusters] = useState<string[]>([]);
const [currentShieldDataAll, setCurrentShieldDataAll] = useState<Array<subscribeItem>>([]);
const [currentShieldData, setCurrentShieldData] = useState<Array<subscribeItem>>([]);
const [loading, setLoading] = useState<boolean>(false);
const columns: ColumnsType = [
{
title: t('集群'),
dataIndex: 'cluster',
render: (data) => {
return <div>{data}</div>;
},
},
{
title: t('告警规则'),
dataIndex: 'rule_name',
render: (data) => {
return <div>{data}</div>;
},
},
{
title: t('订阅标签'),
dataIndex: 'tags',
render: (text: any) => {
return (
<>
{text
? text.map((tag, index) => {
return tag ? <div key={index}>{`${tag.key} ${tag.func} ${tag.func === 'in' ? tag.value.split(' ').join(', ') : tag.value}`}</div> : null;
})
: ''}
</>
);
},
},
{
title: t('告警接收组'),
dataIndex: 'user_groups',
render: (text: string, record: subscribeItem) => {
return (
<>
{record.user_groups?.map((item) => (
<ColorTag text={item.name} key={item.id}></ColorTag>
))}
</>
);
},
},
{
title: t('编辑人'),
ellipsis: true,
dataIndex: 'update_by',
},
{
title: t('操作'),
width: '128px',
dataIndex: 'operation',
render: (text: undefined, record: subscribeItem) => {
return (
<>
<div className='table-operator-area'>
<div
className='table-operator-area-normal'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
curBusiItem?.id && history.push(`/alert-subscribes/edit/${record.id}`);
}}
>
{t('编辑')}
</div>
<div
className='table-operator-area-normal'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
curBusiItem?.id && history.push(`/alert-subscribes/edit/${record.id}?mode=clone`);
}}
>
{t('克隆')}
</div>
<div
className='table-operator-area-warning'
style={{
cursor: 'pointer',
display: 'inline-block',
}}
onClick={() => {
confirm({
title: t('确定删除该订阅规则?'),
icon: <ExclamationCircleOutlined />,
onOk: () => {
dismiss(record.id);
},
onCancel() {},
});
}}
>
{t('删除')}
</div>
</div>
</>
);
},
},
];
useEffect(() => {
getList();
}, [curBusiItem]);
useEffect(() => {
filterData();
}, [query, clusters, currentShieldDataAll]);
const dismiss = (id: number) => {
deleteSubscribes({ ids: [id] }, curBusiItem.id).then((res) => {
refreshList();
if (res.err) {
message.success(res.err);
} else {
message.success(t('删除成功'));
}
});
};
const filterData = () => {
const data = JSON.parse(JSON.stringify(currentShieldDataAll));
const res = data.filter((item: subscribeItem) => {
const tagFind = item?.tags?.find((tag) => {
return tag.key.indexOf(query) > -1 || tag.value.indexOf(query) > -1 || tag.func.indexOf(query) > -1;
});
const groupFind = item?.user_groups?.find((item) => {
return item?.name?.indexOf(query) > -1;
});
return (item?.rule_name?.indexOf(query) > -1 || !!tagFind || !!groupFind) && ((clusters && clusters?.indexOf(item.cluster) > -1) || clusters?.length === 0);
});
setCurrentShieldData(res || []);
};
const getList = async () => {
if (curBusiItem.id) {
setLoading(true);
const { success, dat } = await getSubscribeList({ id: curBusiItem.id });
if (success) {
setCurrentShieldDataAll(dat || []);
setLoading(false);
}
}
};
const refreshList = () => {
getList();
};
const onSearchQuery = (e) => {
let val = e.target.value;
setQuery(val);
};
const clusterChange = (data) => {
setClusters(data);
};
const busiChange = (data) => {
setBgid(data);
};
return (
<PageLayout title={t('订阅规则')} icon={<CopyOutlined />} hideCluster>
<div className='shield-content'>
<LeftTree
busiGroup={{
// showNotGroupItem: true,
onChange: busiChange,
}}
></LeftTree>
{curBusiItem?.id ? (
<div className='shield-index'>
<div className='header'>
<div className='header-left'>
<RefreshIcon
className='strategy-table-search-left-refresh'
onClick={() => {
refreshList();
}}
/>
<ColumnSelect onClusterChange={(e) => setClusters(e)} />
<Input onPressEnter={onSearchQuery} className={'searchInput'} prefix={<SearchOutlined />} placeholder={t('搜索规则、标签、接收组')} />
</div>
<div className='header-right'>
<Button
type='primary'
className='add'
ghost
onClick={() => {
history.push('/alert-subscribes/add');
}}
>
{t('新增订阅规则')}
</Button>
</div>
</div>
<Table
rowKey='id'
pagination={{
total: currentShieldData.length,
showQuickJumper: true,
showSizeChanger: true,
showTotal: (total) => {
return `共 ${total} 条数据`;
},
pageSizeOptions: pageSizeOptionsDefault,
defaultPageSize: 30,
}}
loading={loading}
dataSource={currentShieldData}
columns={columns}
/>
</div>
) : (
<BlankBusinessPlaceholder text='订阅规则' />
)}
</div>
</PageLayout>
);
}
Example #22
Source File: MainOperator.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
Main: React.FC<MainProp> = (props) => {
const [engineStatus, setEngineStatus] = useState<"ok" | "error">("ok")
const [status, setStatus] = useState<{ addr: string; isTLS: boolean }>()
const [collapsed, setCollapsed] = useState(false)
const [hideMenu, setHideMenu] = useState(false)
const [loading, setLoading] = useState(false)
const [menuItems, setMenuItems] = useState<MenuItemGroup[]>([])
const [routeMenuData, setRouteMenuData] = useState<MenuDataProps[]>(RouteMenuData)
const [notification, setNotification] = useState("")
const [pageCache, setPageCache] = useState<PageCache[]>([
{
verbose: "MITM",
route: Route.HTTPHacker,
singleNode: ContentByRoute(Route.HTTPHacker),
multipleNode: []
}
])
const [currentTabKey, setCurrentTabKey] = useState<string>(Route.HTTPHacker)
// 系统类型
const [system, setSystem] = useState<string>("")
useEffect(() => {
ipcRenderer.invoke('fetch-system-name').then((res) => setSystem(res))
}, [])
// yakit页面关闭是否二次确认提示
const [winCloseFlag, setWinCloseFlag] = useState<boolean>(true)
const [winCloseShow, setWinCloseShow] = useState<boolean>(false)
useEffect(() => {
ipcRenderer
.invoke("get-value", WindowsCloseFlag)
.then((flag: any) => setWinCloseFlag(flag === undefined ? true : flag))
}, [])
// 获取自定义菜单
const updateMenuItems = () => {
setLoading(true)
// Fetch User Defined Plugins
ipcRenderer
.invoke("GetAllMenuItem", {})
.then((data: { Groups: MenuItemGroup[] }) => {
setMenuItems(data.Groups)
})
.catch((e: any) => failed("Update Menu Item Failed"))
.finally(() => setTimeout(() => setLoading(false), 300))
// Fetch Official General Plugins
ipcRenderer
.invoke("QueryYakScript", {
Pagination: genDefaultPagination(1000),
IsGeneralModule: true,
Type: "yak"
} as QueryYakScriptRequest)
.then((data: QueryYakScriptsResponse) => {
const tabList: MenuDataProps[] = cloneDeep(RouteMenuData)
for (let item of tabList) {
if (item.subMenuData) {
if (item.key === Route.GeneralModule) {
const extraMenus: MenuDataProps[] = data.Data.map((i) => {
return {
icon: <EllipsisOutlined/>,
key: `plugin:${i.Id}`,
label: i.ScriptName,
} as unknown as MenuDataProps
})
item.subMenuData.push(...extraMenus)
}
item.subMenuData.sort((a, b) => a.label.localeCompare(b.label))
}
}
setRouteMenuData(tabList)
})
}
useEffect(() => {
updateMenuItems()
ipcRenderer.on("fetch-new-main-menu", (e) => {
updateMenuItems()
})
return () => {
ipcRenderer.removeAllListeners("fetch-new-main-menu")
}
}, [])
useEffect(() => {
if (engineStatus === "error") props.onErrorConfirmed && props.onErrorConfirmed()
}, [engineStatus])
// 整合路由对应名称
const pluginKey = (item: PluginMenuItem) => `plugin:${item.Group}:${item.YakScriptId}`;
const routeKeyToLabel = new Map<string, string>();
routeMenuData.forEach(k => {
(k.subMenuData || []).forEach(subKey => {
routeKeyToLabel.set(`${subKey.key}`, subKey.label)
})
routeKeyToLabel.set(`${k.key}`, k.label)
})
menuItems.forEach((k) => {
k.Items.forEach((value) => {
routeKeyToLabel.set(pluginKey(value), value.Verbose)
})
})
// Tabs Bar Operation Function
const getCacheIndex = (route: string) => {
const targets = pageCache.filter((i) => i.route === route)
return targets.length > 0 ? pageCache.indexOf(targets[0]) : -1
}
const addTabPage = useMemoizedFn(
(route: Route, nodeParams?: { time?: string; node: ReactNode; isRecord?: boolean }) => {
const filterPage = pageCache.filter((i) => i.route === route)
const filterPageLength = filterPage.length
if (singletonRoute.includes(route)) {
if (filterPageLength > 0) {
setCurrentTabKey(route)
} else {
const tabName = routeKeyToLabel.get(route) || `${route}`
setPageCache([
...pageCache,
{
verbose: tabName,
route: route,
singleNode: ContentByRoute(route),
multipleNode: []
}
])
setCurrentTabKey(route)
}
} else {
if (filterPageLength > 0) {
const tabName = routeKeyToLabel.get(route) || `${route}`
const tabId = `${route}-[${randomString(49)}]`
const time = new Date().getTime().toString()
const node: multipleNodeInfo = {
id: tabId,
verbose: `${tabName}-[${filterPage[0].multipleNode.length + 1}]`,
node: nodeParams && nodeParams.node ? nodeParams?.node || <></> : ContentByRoute(route),
time: nodeParams && nodeParams.node ? nodeParams?.time || time : time
}
const pages = pageCache.map((item) => {
if (item.route === route) {
item.multipleNode.push(node)
item.multipleCurrentKey = tabId
return item
}
return item
})
setPageCache([...pages])
setCurrentTabKey(route)
if (nodeParams && !!nodeParams.isRecord) addFuzzerList(nodeParams?.time || time)
} else {
const tabName = routeKeyToLabel.get(route) || `${route}`
const tabId = `${route}-[${randomString(49)}]`
const time = new Date().getTime().toString()
const node: multipleNodeInfo = {
id: tabId,
verbose: `${tabName}-[1]`,
node: nodeParams && nodeParams.node ? nodeParams?.node || <></> : ContentByRoute(route),
time: nodeParams && nodeParams.node ? nodeParams?.time || time : time
}
setPageCache([
...pageCache,
{
verbose: tabName,
route: route,
singleNode: undefined,
multipleNode: [node],
multipleCurrentKey: tabId
}
])
setCurrentTabKey(route)
if (nodeParams && !!nodeParams.isRecord) addFuzzerList(nodeParams?.time || time)
}
}
}
)
const menuAddPage = useMemoizedFn((route: Route) => {
if (route === "ignore") return
if (route === Route.HTTPFuzzer) {
const time = new Date().getTime().toString()
addTabPage(Route.HTTPFuzzer, {
time: time,
node: ContentByRoute(Route.HTTPFuzzer, undefined, {
system: system,
order: time
}),
isRecord: true
})
} else addTabPage(route as Route)
})
const removePage = (route: string) => {
const targetIndex = getCacheIndex(route)
if (targetIndex > 0 && pageCache[targetIndex - 1]) {
const targetCache = pageCache[targetIndex - 1]
setCurrentTabKey(targetCache.route)
}
if (targetIndex === 0 && pageCache[targetIndex + 1]) {
const targetCache = pageCache[targetIndex + 1]
setCurrentTabKey(targetCache.route)
}
if (targetIndex === 0 && pageCache.length === 1) setCurrentTabKey("")
setPageCache(pageCache.filter((i) => i.route !== route))
if (route === Route.HTTPFuzzer) delFuzzerList(1)
}
const updateCacheVerbose = (id: string, verbose: string) => {
const index = getCacheIndex(id)
if (index < 0) return
pageCache[index].verbose = verbose
setPageCache([...pageCache])
}
const setMultipleCurrentKey = useMemoizedFn((key: string, type: Route) => {
const arr = pageCache.map(item => {
if (item.route === type) {
item.multipleCurrentKey = key
return item
}
return item
})
setPageCache([...arr])
})
const removeMultipleNodePage = useMemoizedFn((key: string, type: Route) => {
const removeArr: multipleNodeInfo[] = pageCache.filter(item => item.route === type)[0]?.multipleNode || []
if (removeArr.length === 0) return
const nodes = removeArr.filter(item => item.id === key)
const time = nodes[0].time
let index = 0
for (let i in removeArr) {
if (removeArr[i].id === key) {
index = +i
break
}
}
if (index === 0 && removeArr.length === 1) {
removePage(type)
return
}
let current = ""
let filterArr: multipleNodeInfo[] = []
if (index > 0 && removeArr[index - 1]) {
current = removeArr[index - 1].id
filterArr = removeArr.filter(item => item.id !== key)
}
if (index === 0 && removeArr[index + 1]) {
current = removeArr[index + 1].id
filterArr = removeArr.filter(item => item.id !== key)
}
if (current) {
const arr = pageCache.map(item => {
if (item.route === type) {
item.multipleNode = [...filterArr]
item.multipleCurrentKey = current
return item
}
return item
})
setPageCache([...arr])
if (type === Route.HTTPFuzzer) delFuzzerList(2, time)
}
})
const removeOtherMultipleNodePage = useMemoizedFn((key: string, type: Route) => {
const removeArr: multipleNodeInfo[] = pageCache.filter(item => item.route === type)[0]?.multipleNode || []
if (removeArr.length === 0) return
const nodes = removeArr.filter(item => item.id === key)
const time = nodes[0].time
const arr = pageCache.map(item => {
if (item.route === type) {
item.multipleNode = [...nodes]
item.multipleCurrentKey = key
return item
}
return item
})
setPageCache([...arr])
if (type === Route.HTTPFuzzer) delFuzzerList(3, time)
})
// 全局记录鼠标坐标位置(为右键菜单提供定位)
const coordinateTimer = useRef<any>(null)
useEffect(() => {
document.onmousemove = (e) => {
const {screenX, screenY, clientX, clientY, pageX, pageY} = e
if (coordinateTimer.current) {
clearTimeout(coordinateTimer.current)
coordinateTimer.current = null
}
coordinateTimer.current = setTimeout(() => {
coordinate.screenX = screenX
coordinate.screenY = screenY
coordinate.clientX = clientX
coordinate.clientY = clientY
coordinate.pageX = pageX
coordinate.pageY = pageY
}, 50);
}
}, [])
// 全局注册快捷键功能
const documentKeyDown = useMemoizedFn((e: any) => {
// ctrl + w 关闭tab页面
if (e.code === "KeyW" && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
if (pageCache.length === 0) return
setLoading(true)
removePage(currentTabKey)
setTimeout(() => setLoading(false), 300);
return
}
})
useEffect(() => {
document.onkeydown = documentKeyDown
}, [])
// fuzzer本地缓存
const fuzzerList = useRef<Map<string, fuzzerInfoProp>>(new Map<string, fuzzerInfoProp>())
const saveFuzzerList = debounce(() => {
const historys: fuzzerInfoProp[] = []
fuzzerList.current.forEach((value) => historys.push(value))
historys.sort((a, b) => +a.time - +b.time)
const filters = historys.filter(item => (item.request || "").length < 1000000 && (item.request || "").length > 0)
ipcRenderer.invoke("set-value", FuzzerCache, JSON.stringify(filters.slice(-5)))
}, 500)
const fetchFuzzerList = useMemoizedFn(() => {
setLoading(true)
fuzzerList.current.clear()
ipcRenderer
.invoke("get-value", FuzzerCache)
.then((res: any) => {
const cache = JSON.parse(res || "[]")
for (let item of cache) {
const time = new Date().getTime().toString()
fuzzerList.current.set(time, {...item, time: time})
addTabPage(Route.HTTPFuzzer, {
time: time,
node:
ContentByRoute(
Route.HTTPFuzzer,
undefined,
{
isHttps: item.isHttps || false,
request: item.request || "",
fuzzerParams: item,
system: system,
order: time
}
)
})
}
})
.catch((e) => console.info(e))
.finally(() => setTimeout(() => setLoading(false), 300))
})
const addFuzzerList = (key: string, request?: string, isHttps?: boolean) => {
fuzzerList.current.set(key, {request, isHttps, time: key})
}
const delFuzzerList = (type: number, key?: string) => {
if (type === 1) fuzzerList.current.clear()
if (type === 2 && key) if (fuzzerList.current.has(key)) fuzzerList.current.delete(key)
if (type === 3 && key) {
const info = fuzzerList.current.get(key)
if (info) {
fuzzerList.current.clear()
fuzzerList.current.set(key, info)
}
}
saveFuzzerList()
}
const updateFuzzerList = (key: string, param: fuzzerInfoProp) => {
fuzzerList.current.set(key, param)
saveFuzzerList()
}
useEffect(() => {
ipcRenderer.on("fetch-fuzzer-setting-data", (e, res: any) => updateFuzzerList(res.key, JSON.parse(res.param)))
// 开发环境不展示fuzzer缓存
ipcRenderer.invoke("is-dev").then((flag) => {
if (!flag) fetchFuzzerList()
// fetchFuzzerList()
})
return () => ipcRenderer.removeAllListeners("fetch-fuzzer-setting-data")
}, [])
// 加载补全
useEffect(() => {
ipcRenderer.invoke("GetYakitCompletionRaw").then((data: { RawJson: Uint8Array }) => {
const completionJson = Buffer.from(data.RawJson).toString("utf8")
setCompletions(JSON.parse(completionJson) as CompletionTotal)
// success("加载 Yak 语言自动补全成功 / Load Yak IDE Auto Completion Finished")
})
}, [])
useEffect(() => {
ipcRenderer.invoke("yakit-connect-status").then((data) => {
setStatus(data)
})
ipcRenderer.on("client-engine-status-ok", (e, reason) => {
if (engineStatus !== "ok") setEngineStatus("ok")
})
ipcRenderer.on("client-engine-status-error", (e, reason) => {
if (engineStatus === "ok") setEngineStatus("error")
})
const updateEngineStatus = () => {
ipcRenderer
.invoke("engine-status")
.catch((e: any) => {
setEngineStatus("error")
})
.finally(() => {
})
}
let id = setInterval(updateEngineStatus, 3000)
return () => {
ipcRenderer.removeAllListeners("client-engine-status-error")
ipcRenderer.removeAllListeners("client-engine-status-ok")
clearInterval(id)
}
}, [])
useHotkeys("Ctrl+Alt+T", () => {
})
useEffect(() => {
ipcRenderer.invoke("query-latest-notification").then((e: string) => {
setNotification(e)
if (e) {
success(
<>
<Space direction={"vertical"}>
<span>来自于 yaklang.io 的通知</span>
<Button
type={"link"}
onClick={() => {
showModal({
title: "Notification",
content: (
<>
<MDEditor.Markdown source={e}/>
</>
)
})
}}
>
点击查看
</Button>
</Space>
</>
)
}
})
}, [])
// 新增数据对比页面
useEffect(() => {
ipcRenderer.on("main-container-add-compare", (e, params) => {
const newTabId = `${Route.DataCompare}-[${randomString(49)}]`;
const verboseNameRaw = routeKeyToLabel.get(Route.DataCompare) || `${Route.DataCompare}`;
addTabPage(Route.DataCompare, {node: ContentByRoute(Route.DataCompare, undefined, {system: system})})
// 区分新建对比页面还是别的页面请求对比的情况
ipcRenderer.invoke("created-data-compare")
})
return () => {
ipcRenderer.removeAllListeners("main-container-add-compare")
}
}, [pageCache])
// Global Sending Function(全局发送功能|通过发送新增功能页面)
const addFuzzer = useMemoizedFn((res: any) => {
const {isHttps, request} = res || {}
if (request) {
const time = new Date().getTime().toString()
addTabPage(Route.HTTPFuzzer, {
time: time,
node:
ContentByRoute(
Route.HTTPFuzzer,
undefined,
{
isHttps: isHttps || false,
request: request || "",
system: system,
order: time
}
)
})
addFuzzerList(time, request || "", isHttps || false)
}
})
const addScanPort = useMemoizedFn((res: any) => {
const {URL = ""} = res || {}
if (URL) {
addTabPage(Route.Mod_ScanPort, {
node: ContentByRoute(Route.Mod_ScanPort, undefined, {scanportParams: URL})
})
}
})
const addBrute = useMemoizedFn((res: any) => {
const {URL = ""} = res || {}
if (URL) {
addTabPage(Route.Mod_Brute, {
node: ContentByRoute(Route.Mod_Brute, undefined, {bruteParams: URL})
})
}
})
// 发送到专项漏洞检测modal-show变量
const [bugTestShow, setBugTestShow] = useState<boolean>(false)
const [bugList, setBugList] = useState<BugInfoProps[]>([])
const [bugTestValue, setBugTestValue] = useState<BugInfoProps[]>([])
const [bugUrl, setBugUrl] = useState<string>("")
const addBugTest = useMemoizedFn((type: number, res?: any) => {
const {URL = ""} = res || {}
if (type === 1 && URL) {
setBugUrl(URL)
ipcRenderer.invoke("get-value", CustomBugList)
.then((res: any) => {
setBugList(res ? JSON.parse(res) : [])
setBugTestShow(true)
})
.catch(() => {
})
}
if (type === 2) {
const filter = pageCache.filter(item => item.route === Route.PoC)
if (filter.length === 0) {
addTabPage(Route.PoC)
setTimeout(() => {
ipcRenderer.invoke("send-to-bug-test", {type: bugTestValue, data: bugUrl})
setBugTestValue([])
setBugUrl("")
}, 300);
} else {
ipcRenderer.invoke("send-to-bug-test", {type: bugTestValue, data: bugUrl})
setCurrentTabKey(Route.PoC)
setBugTestValue([])
setBugUrl("")
}
}
})
const addYakRunning = useMemoizedFn((res: any) => {
const {name = "", code = ""} = res || {}
const filter = pageCache.filter(item => item.route === Route.YakScript)
if (!name || !code) return false
if ((filter || []).length === 0) {
addTabPage(Route.YakScript)
setTimeout(() => {
ipcRenderer.invoke("send-to-yak-running", {name, code})
}, 300);
} else {
ipcRenderer.invoke("send-to-yak-running", {name, code})
setCurrentTabKey(Route.YakScript)
}
})
useEffect(() => {
ipcRenderer.on("fetch-send-to-tab", (e, res: any) => {
const {type, data = {}} = res
if (type === "fuzzer") addFuzzer(data)
if (type === "scan-port") addScanPort(data)
if (type === "brute") addBrute(data)
if (type === "bug-test") addBugTest(1, data)
if (type === "plugin-store") addYakRunning(data)
})
return () => {
ipcRenderer.removeAllListeners("fetch-send-to-tab")
}
}, [])
// Tabs Bar 组件
const closeAllCache = useMemoizedFn(() => {
Modal.confirm({
title: "确定要关闭所有 Tabs?",
content: "这样将会关闭所有进行中的进程",
onOk: () => {
delFuzzerList(1)
setPageCache([])
}
})
})
const closeOtherCache = useMemoizedFn((route: string) => {
Modal.confirm({
title: "确定要关闭除此之外所有 Tabs?",
content: "这样将会关闭所有进行中的进程",
onOk: () => {
const arr = pageCache.filter((i) => i.route === route)
setPageCache(arr)
if (route === Route.HTTPFuzzer) delFuzzerList(1)
}
})
})
const bars = (props: any, TabBarDefault: any) => {
return (
<TabBarDefault
{...props}
children={(barNode: React.ReactElement) => {
return (
<DropdownMenu
menu={{
data: [
{key: "all", title: "关闭所有Tabs"},
{key: "other", title: "关闭其他Tabs"}
]
}}
dropdown={{trigger: ["contextMenu"]}}
onClick={(key) => {
switch (key) {
case "all":
closeAllCache()
break
case "other":
closeOtherCache(barNode.key as Route)
break
default:
break
}
}}
>
{barNode}
</DropdownMenu>
)
}}
/>
)
}
return (
<Layout className="yakit-main-layout">
<AutoSpin spinning={loading}>
<Header className="main-laytou-header">
<Row>
<Col span={8}>
<Space>
<div style={{marginLeft: 18, textAlign: "center", height: 60}}>
<Image src={YakLogoBanner} preview={false} width={130} style={{marginTop: 6}}/>
</div>
<Divider type={"vertical"}/>
<YakVersion/>
<YakitVersion/>
{!hideMenu && (
<Button
style={{marginLeft: 4, color: "#207ee8"}}
type={"ghost"}
ghost={true}
onClick={(e) => {
setCollapsed(!collapsed)
}}
icon={collapsed ? <MenuUnfoldOutlined/> : <MenuFoldOutlined/>}
/>
)}
<Button
style={{marginLeft: 4, color: "#207ee8"}}
type={"ghost"}
ghost={true}
onClick={(e) => {
updateMenuItems()
}}
icon={<ReloadOutlined/>}
/>
</Space>
</Col>
<Col span={16} style={{textAlign: "right", paddingRight: 28}}>
<PerformanceDisplay/>
<RiskStatsTag professionalMode={true}/>
<Space>
{/* {status?.isTLS ? <Tag color={"green"}>TLS:通信已加密</Tag> : <Tag color={"red"}>通信未加密</Tag>} */}
{status?.addr && <Tag color={"geekblue"}>{status?.addr}</Tag>}
{/* <Tag color={engineStatus === "ok" ? "green" : "red"}>Yak 引擎状态:{engineStatus}</Tag> */}
<ReversePlatformStatus/>
<Dropdown forceRender={true} overlay={<Menu>
<Menu.Item key={"update"}>
<AutoUpdateYakModuleButton/>
</Menu.Item>
<Menu.Item key={"reverse-global"}>
<ConfigGlobalReverseButton/>
</Menu.Item>
</Menu>} trigger={["click"]}>
<Button icon={<SettingOutlined/>}>
配置
</Button>
</Dropdown>
<Button type={"link"} danger={true} icon={<PoweroffOutlined/>} onClick={() => {
if (winCloseFlag) setWinCloseShow(true)
else {
success("退出当前 Yak 服务器成功")
setEngineStatus("error")
}
}}/>
</Space>
</Col>
</Row>
</Header>
<Content
style={{
margin: 12,
backgroundColor: "#fff",
overflow: "auto"
}}
>
<Layout style={{height: "100%", overflow: "hidden"}}>
{!hideMenu && (
<Sider
style={{backgroundColor: "#fff", overflow: "auto"}}
collapsed={collapsed}
>
<Spin spinning={loading}>
<Space
direction={"vertical"}
style={{
width: "100%"
}}
>
<Menu
theme={"light"}
style={{}}
selectedKeys={[]}
mode={"inline"}
onSelect={(e) => {
if (e.key === "ignore") return
const flag = pageCache.filter(item => item.route === (e.key as Route)).length === 0
if (flag) menuAddPage(e.key as Route)
else setCurrentTabKey(e.key)
}}
>
{menuItems.map((i) => {
if (i.Group === "UserDefined") {
i.Group = "社区插件"
}
return (
<Menu.SubMenu icon={<EllipsisOutlined/>} key={i.Group}
title={i.Group}>
{i.Items.map((item) => {
if (item.YakScriptId > 0) {
return (
<MenuItem icon={<EllipsisOutlined/>}
key={`plugin:${item.Group}:${item.YakScriptId}`}>
<Text
ellipsis={{tooltip: true}}>{item.Verbose}</Text>
</MenuItem>
)
}
return (
<MenuItem icon={<EllipsisOutlined/>}
key={`batch:${item.Group}:${item.Verbose}:${item.MenuItemId}`}>
<Text
ellipsis={{tooltip: true}}>{item.Verbose}</Text>
</MenuItem>
)
})}
</Menu.SubMenu>
)
})}
{(routeMenuData || []).map((i) => {
if (i.subMenuData) {
return (
<Menu.SubMenu icon={i.icon} key={i.key} title={i.label}>
{(i.subMenuData || []).map((subMenu) => {
return (
<MenuItem icon={subMenu.icon} key={subMenu.key}
disabled={subMenu.disabled}>
<Text
ellipsis={{tooltip: true}}>{subMenu.label}</Text>
</MenuItem>
)
})}
</Menu.SubMenu>
)
}
return (
<MenuItem icon={i.icon} key={i.key} disabled={i.disabled}>
{i.label}
</MenuItem>
)
})}
</Menu>
</Space>
</Spin>
</Sider>
)}
<Content style={{
overflow: "hidden",
backgroundColor: "#fff",
marginLeft: 12,
height: "100%",
display: "flex",
flexFlow: "column"
}}>
<div style={{
padding: 12,
paddingTop: 8,
overflow: "hidden",
flex: "1",
display: "flex",
flexFlow: "column"
}}>
{pageCache.length > 0 ? (
<Tabs
style={{display: "flex", flex: "1"}}
tabBarStyle={{marginBottom: 8}}
className='main-content-tabs yakit-layout-tabs'
activeKey={currentTabKey}
onChange={setCurrentTabKey}
size={"small"}
type={"editable-card"}
renderTabBar={(props, TabBarDefault) => {
return bars(props, TabBarDefault)
}}
hideAdd={true}
onTabClick={(key, e) => {
const divExisted = document.getElementById("yakit-cursor-menu")
if (divExisted) {
const div: HTMLDivElement = divExisted as HTMLDivElement
const unmountResult = ReactDOM.unmountComponentAtNode(div)
if (unmountResult && div.parentNode) {
div.parentNode.removeChild(div)
}
}
}}
>
{pageCache.map((i) => {
return (
<Tabs.TabPane
forceRender={true}
key={i.route}
tab={i.verbose}
closeIcon={
<Space>
<Popover
trigger={"click"}
title={"修改名称"}
content={
<>
<Input
size={"small"}
defaultValue={i.verbose}
onBlur={(e) => updateCacheVerbose(i.route, e.target.value)}
/>
</>
}
>
<EditOutlined className='main-container-cion'/>
</Popover>
<CloseOutlined
className='main-container-cion'
onClick={() => removePage(i.route)}
/>
</Space>
}
>
<div
style={{
overflowY: NoScrollRoutes.includes(i.route)
? "hidden"
: "auto",
overflowX: "hidden",
height: "100%",
maxHeight: "100%"
}}
>
{i.singleNode ? (
i.singleNode
) : (
<MainTabs
currentTabKey={currentTabKey}
tabType={i.route}
pages={i.multipleNode}
currentKey={i.multipleCurrentKey || ""}
isShowAdd={true}
setCurrentKey={(key, type) => {
setMultipleCurrentKey(key, type as Route)
}}
removePage={(key, type) => {
removeMultipleNodePage(key, type as Route)
}}
removeOtherPage={(key, type) => {
removeOtherMultipleNodePage(key, type as Route)
}}
onAddTab={() => menuAddPage(i.route)}
></MainTabs>
)}
</div>
</Tabs.TabPane>
)
})}
</Tabs>
) : (
<></>
)}
</div>
</Content>
</Layout>
</Content>
</AutoSpin>
<Modal
visible={winCloseShow}
onCancel={() => setWinCloseShow(false)}
footer={[
<Button key='link' onClick={() => setWinCloseShow(false)}>
取消
</Button>,
<Button key='back' type='primary' onClick={() => {
success("退出当前 Yak 服务器成功")
setEngineStatus("error")
}}>
退出
</Button>
]}
>
<div style={{height: 40}}>
<ExclamationCircleOutlined style={{fontSize: 22, color: "#faad14"}}/>
<span style={{fontSize: 18, marginLeft: 15}}>提示</span>
</div>
<p style={{fontSize: 15, marginLeft: 37}}>是否要退出yakit操作界面,一旦退出,界面内打开内容除fuzzer页外都会销毁</p>
<div style={{marginLeft: 37}}>
<Checkbox
defaultChecked={!winCloseFlag}
value={!winCloseFlag}
onChange={() => {
setWinCloseFlag(!winCloseFlag)
ipcRenderer.invoke("set-value", WindowsCloseFlag, false)
}}
></Checkbox>
<span style={{marginLeft: 8}}>不再出现该提示信息</span>
</div>
</Modal>
<Modal
visible={bugTestShow}
onCancel={() => setBugTestShow(false)}
footer={[
<Button key='link' onClick={() => setBugTestShow(false)}>
取消
</Button>,
<Button key='back' type='primary' onClick={() => {
if ((bugTestValue || []).length === 0) return failed("请选择类型后再次提交")
addBugTest(2)
setBugTestShow(false)
}}>
确定
</Button>
]}
>
<ItemSelects
item={{
label: "专项漏洞类型",
style: {marginTop: 20}
}}
select={{
allowClear: true,
data: BugList.concat(bugList) || [],
optText: "title",
optValue: "key",
value: (bugTestValue || [])[0]?.key,
onChange: (value, option: any) => setBugTestValue(value ? [{
key: option?.key,
title: option?.title
}] : [])
}}
></ItemSelects>
</Modal>
</Layout>
)
}
Example #23
Source File: ChartEditor.tsx From datart with Apache License 2.0 | 4 votes |
ChartEditor: FC<ChartEditorProps> = ({
originChart,
orgId,
container,
dataChartId,
chartType,
defaultViewId,
widgetId,
onClose,
onSaveInWidget,
onSaveInDataChart,
}) => {
const saveFormContextValue = useSaveFormContext();
const { actions } = useWorkbenchSlice();
const dispatch = useDispatch();
const dataset = useSelector(datasetsSelector);
const dataview = useSelector(currentDataViewSelector);
const chartConfig = useSelector(chartConfigSelector);
const shadowChartConfig = useSelector(shadowChartConfigSelector);
const backendChart = useSelector(backendChartSelector);
const aggregation = useSelector(aggregationSelector);
const availableSourceFunctions = useSelector(selectAvailableSourceFunctions);
const [chart, setChart] = useState<IChart>();
const drillOptionRef = useRef<IChartDrillOption>();
const [allowQuery, setAllowQuery] = useState<boolean>(false);
const history = useHistory();
const addVizFn = useAddViz({
showSaveForm: saveFormContextValue.showSaveForm,
});
const tg = useI18NPrefix('global');
const expensiveQuery = useMemo(() => {
try {
return dataview?.config
? Boolean(JSON.parse(dataview.config).expensiveQuery)
: false;
} catch (error) {
console.log(error);
return false;
}
}, [dataview]);
useMount(
() => {
if (
(container === 'dataChart' && !dataChartId) ||
(container === 'widget' && !originChart)
) {
// Note: add default chart if new to editor
const currentChart = ChartManager.instance().getDefaultChart();
handleChartChange(currentChart);
}
if (container === 'dataChart') {
dispatch(
initWorkbenchAction({
backendChartId: dataChartId,
orgId,
}),
);
} else {
// container === 'widget'
if (chartType === 'widgetChart') {
dispatch(
initWorkbenchAction({
orgId,
backendChart: originChart as ChartDTO,
}),
);
if (!originChart) {
dispatch(actions.updateChartAggregation(true));
}
} else {
// chartType === 'dataChart'
dispatch(
initWorkbenchAction({
orgId,
backendChartId: dataChartId,
}),
);
}
}
},
() => {
dispatch(actions.resetWorkbenchState({}));
},
);
useEffect(() => {
if (backendChart?.config?.chartGraphId) {
const currentChart = ChartManager.instance().getById(
backendChart?.config?.chartGraphId,
);
registerChartEvents(currentChart);
setChart(currentChart);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [backendChart?.config?.chartGraphId]);
useEffect(() => {
if (!isEmptyArray(chartConfig?.datas) && !drillOptionRef.current) {
drillOptionRef.current = getChartDrillOption(chartConfig?.datas);
}
}, [chartConfig?.datas, drillOptionRef]);
useEffect(() => {
if (dataview?.sourceId) {
dispatch(fetchAvailableSourceFunctions({ sourceId: dataview.sourceId }));
}
}, [dataview?.sourceId, dispatch]);
const resetOriginalComputedFields = useCallback(
config => {
const index = config?.datas?.findIndex(
v => v.type === ChartDataSectionType.GROUP,
);
if (index !== undefined) {
const groupRows = config?.datas?.[index]?.rows;
if (groupRows) {
const dateLevelComputedFields = groupRows.filter(
v =>
v.category === ChartDataViewFieldCategory.DateLevelComputedField,
);
const computedFields = getRuntimeComputedFields(
dateLevelComputedFields,
'',
dataview?.computedFields,
chartConfig,
);
dispatch(
workbenchSlice.actions.updateCurrentDataViewComputedFields(
computedFields,
),
);
}
}
},
[chartConfig, dataview?.computedFields, dispatch],
);
const registerChartEvents = useCallback(
chart => {
chart?.registerMouseEvents([
{
name: 'click',
callback: param => {
if (
drillOptionRef.current?.isSelectedDrill &&
!drillOptionRef.current.isBottomLevel
) {
const option = drillOptionRef.current;
option.drillDown(param.data.rowData);
drillOptionRef.current = option;
handleDrillOptionChange?.(option);
return;
}
if (
param.componentType === 'table' &&
param.seriesType === 'paging-sort-filter'
) {
dispatch(
refreshDatasetAction({
sorter: {
column: param?.seriesName!,
operator: param?.value?.direction,
aggOperator: param?.value?.aggOperator,
},
pageInfo: {
pageNo: param?.value?.pageNo,
},
}),
);
return;
}
if (param.seriesName === 'richText') {
dispatch(updateRichTextAction(param.value));
return;
}
},
},
]);
},
[dispatch],
);
const clearDataConfig = useCallback(() => {
const currentChart = chart?.meta?.id
? ChartManager.instance().getById(chart?.meta?.id)
: ChartManager.instance().getDefaultChart();
let targetChartConfig = CloneValueDeep(currentChart?.config);
registerChartEvents(currentChart);
setChart(currentChart);
const finalChartConfig = transferChartConfigs(
targetChartConfig,
targetChartConfig,
);
dispatch(workbenchSlice.actions.updateCurrentDataViewComputedFields([]));
dispatch(workbenchSlice.actions.updateShadowChartConfig({}));
dispatch(
workbenchSlice.actions.updateChartConfig({
type: ChartConfigReducerActionType.INIT,
payload: {
init: finalChartConfig,
},
}),
);
drillOptionRef.current = getChartDrillOption(
chartConfig?.datas,
drillOptionRef.current,
);
}, [dispatch, chart?.meta?.id, registerChartEvents, chartConfig?.datas]);
const handleChartChange = (c: IChart) => {
registerChartEvents(c);
setChart(c);
const targetChartConfig = CloneValueDeep(c.config);
const finalChartConfig = clearRuntimeDateLevelFieldsInChartConfig(
transferChartConfigs(targetChartConfig, shadowChartConfig || chartConfig),
);
resetOriginalComputedFields(finalChartConfig);
dispatch(
workbenchSlice.actions.updateChartConfig({
type: ChartConfigReducerActionType.INIT,
payload: {
init: finalChartConfig,
},
}),
);
drillOptionRef.current = getChartDrillOption(
finalChartConfig?.datas,
drillOptionRef.current,
);
if (!expensiveQuery) {
dispatch(refreshDatasetAction({ drillOption: drillOptionRef?.current }));
} else {
setAllowQuery(true);
}
};
const handleChartConfigChange = useCallback(
(type, payload) => {
if (expensiveQuery) {
dispatch(
workbenchSlice.actions.updateChartConfig({
type,
payload: payload,
}),
);
dispatch(workbenchSlice.actions.updateShadowChartConfig(null));
setAllowQuery(payload.needRefresh);
return true;
}
// generate runtime computed fields(date level)
if (
payload.value.type === ChartDataSectionType.GROUP ||
payload.value.type === ChartDataSectionType.MIXED
) {
const dateLevelComputedFields = payload.value.rows.filter(
v => v.category === ChartDataViewFieldCategory.DateLevelComputedField,
);
const replacedColName = payload.value.replacedColName;
const computedFields = getRuntimeComputedFields(
dateLevelComputedFields,
replacedColName,
dataview?.computedFields,
chartConfig,
);
if (replacedColName) {
payload = updateBy(payload, draft => {
delete draft.value.replacedColName;
});
}
if (
JSON.stringify(computedFields) !==
JSON.stringify(dataview?.computedFields)
) {
dispatch(
workbenchSlice.actions.updateCurrentDataViewComputedFields(
computedFields,
),
);
}
}
dispatch(
updateChartConfigAndRefreshDatasetAction({
type,
payload,
needRefresh: payload.needRefresh,
updateDrillOption: config => {
drillOptionRef.current = getChartDrillOption(
config?.datas,
drillOptionRef.current,
);
return drillOptionRef.current;
},
}),
);
},
[chartConfig, dispatch, expensiveQuery, dataview],
);
const handleDataViewChanged = useCallback(() => {
clearDataConfig();
}, [clearDataConfig]);
const handleAggregationState = useCallback(() => {
clearDataConfig();
}, [clearDataConfig]);
const buildDataChart = useCallback(() => {
const dataChartConfig: DataChartConfig = {
chartConfig: chartConfig!,
chartGraphId: chart?.meta.id!,
computedFields: dataview?.computedFields || [],
aggregation,
};
const dataChart: DataChart = {
id: dataChartId,
name: backendChart?.name || '',
viewId: dataview?.id || '',
orgId: orgId,
config: dataChartConfig,
status: 1,
description: '',
};
return dataChart;
}, [
backendChart?.name,
chart,
chartConfig,
dataChartId,
dataview,
orgId,
aggregation,
]);
const saveToWidget = useCallback(() => {
const dataChart = buildDataChart();
onSaveInWidget?.(chartType, dataChart, dataview);
}, [buildDataChart, chartType, dataview, onSaveInWidget]);
const saveChart = useCallback(async () => {
resetOriginalComputedFields(chartConfig);
if (container === 'dataChart') {
if (dataChartId) {
await dispatch(
updateChartAction({
name: backendChart?.name,
viewId: dataview?.id,
graphId: chart?.meta?.id,
chartId: dataChartId,
index: 0,
parentId: 0,
aggregation: aggregation,
}),
);
onSaveInDataChart?.(orgId, dataChartId);
} else {
try {
addVizFn({
vizType: 'DATACHART',
type: CommonFormTypes.Add,
visible: true,
initialValues: {
config: JSON.stringify({
aggregation,
chartConfig: chartConfig,
chartGraphId: chart?.meta?.id,
computedFields: dataview?.computedFields,
}),
viewId: dataview?.id,
avatar: chart?.meta?.id,
},
callback: folder => {
folder &&
history.push(`/organizations/${orgId}/vizs/${folder.relId}`);
},
});
} catch (error) {
throw error;
}
}
} else if (container === 'widget') {
if (chartType === 'widgetChart') {
saveToWidget();
} else {
// dataChart
confirm({
title: '保存修改后不能撤销,确定继续保存吗?',
icon: <ExclamationCircleOutlined />,
async onOk() {
dispatch(
updateChartAction({
name: backendChart?.name,
viewId: dataview?.id,
graphId: chart?.meta?.id,
chartId: dataChartId,
index: 0,
parentId: 0,
aggregation,
}),
);
saveToWidget();
},
onCancel() {
console.log('Cancel');
},
});
}
}
}, [
container,
dispatch,
backendChart?.name,
dataview?.id,
chart?.meta?.id,
dataChartId,
onSaveInDataChart,
orgId,
chartType,
saveToWidget,
aggregation,
addVizFn,
chartConfig,
dataview?.computedFields,
history,
resetOriginalComputedFields,
]);
const saveChartToDashBoard = useCallback(
(dashboardId, dashboardType) => {
const dataChart = buildDataChart();
try {
history.push({
pathname: `/organizations/${orgId}/vizs/${dashboardId}/boardEditor`,
state: {
widgetInfo: JSON.stringify({
chartType,
dataChart,
dataview,
dashboardType,
}),
},
});
} catch (error) {
throw error;
}
},
[history, buildDataChart, chartType, dataview, orgId],
);
const handleRefreshDataset = useCallback(async () => {
await dispatch(
refreshDatasetAction({ drillOption: drillOptionRef?.current }),
);
setAllowQuery(false);
}, [dispatch, drillOptionRef]);
const handleCreateDownloadDataTask = useCallback(async () => {
if (!dataview?.id) {
return;
}
const isWidget = dataChartId.includes('widget');
const builder = new ChartDataRequestBuilder(
dataview,
chartConfig?.datas,
chartConfig?.settings,
{},
true,
aggregation,
);
dispatch(
makeDownloadDataTask({
downloadParams: [
{
...builder.build(),
...{
analytics: dataChartId ? false : true,
vizName: backendChart?.name || 'chart',
vizId: isWidget ? widgetId : dataChartId,
vizType: isWidget ? 'widget' : 'dataChart',
},
},
],
fileName: backendChart?.name || 'chart',
downloadType: DownloadFileType.Excel,
resolve: () => {
dispatch(actions.setChartEditorDownloadPolling(true));
},
}),
);
}, [
aggregation,
backendChart?.name,
chartConfig?.datas,
chartConfig?.settings,
dataChartId,
dataview,
dispatch,
actions,
widgetId,
]);
const handleDrillOptionChange = (option: IChartDrillOption) => {
drillOptionRef.current = option;
dispatch(refreshDatasetAction({ drillOption: option }));
};
const handleDateLevelChange = (type, payload) => {
const rows = getRuntimeDateLevelFields(payload.value?.rows);
const dateLevelComputedFields = rows.filter(
v => v.category === ChartDataViewFieldCategory.DateLevelComputedField,
);
const replacedColName = payload.value.replacedColName;
const computedFields = getRuntimeComputedFields(
dateLevelComputedFields,
replacedColName,
dataview?.computedFields,
chartConfig,
);
dispatch(
workbenchSlice.actions.updateCurrentDataViewComputedFields(
computedFields,
),
);
dispatch(
updateChartConfigAndRefreshDatasetAction({
type,
payload,
needRefresh: payload.needRefresh,
updateDrillOption: config => {
drillOptionRef.current = getChartDrillOption(
config?.datas,
drillOptionRef.current,
);
return drillOptionRef.current;
},
}),
);
};
return (
<StyledChartWorkbenchPage>
<SaveFormContext.Provider value={saveFormContextValue}>
<ChartWorkbench
header={{
name: backendChart?.name || originChart?.name,
orgId,
container,
onSaveChart: saveChart,
onSaveChartToDashBoard: saveChartToDashBoard,
onGoBack: () => {
onClose?.();
},
onChangeAggregation: handleAggregationState,
}}
drillOption={drillOptionRef?.current}
aggregation={aggregation}
chart={chart}
dataset={dataset}
dataview={dataview}
chartConfig={chartConfig}
defaultViewId={defaultViewId}
expensiveQuery={expensiveQuery}
allowQuery={allowQuery}
availableSourceFunctions={availableSourceFunctions}
onChartChange={handleChartChange}
onChartConfigChange={handleChartConfigChange}
onChartDrillOptionChange={handleDrillOptionChange}
onDataViewChange={handleDataViewChanged}
onRefreshDataset={handleRefreshDataset}
onCreateDownloadDataTask={handleCreateDownloadDataTask}
onDateLevelChange={handleDateLevelChange}
/>
<SaveForm
width={400}
formProps={{
labelAlign: 'left',
labelCol: { offset: 1, span: 6 },
wrapperCol: { span: 15 },
}}
okText={tg('button.save')}
/>
</SaveFormContext.Provider>
</StyledChartWorkbenchPage>
);
}
Example #24
Source File: YakExecutor.tsx From yakit with GNU Affero General Public License v3.0 | 4 votes |
YakExecutor: React.FC<YakExecutorProp> = (props) => {
const [codePath, setCodePath] = useState<string>("")
const [loading, setLoading] = useState<boolean>(false)
const [fileList, setFileList] = useState<tabCodeProps[]>([])
const [tabList, setTabList] = useState<tabCodeProps[]>([])
const [activeTab, setActiveTab] = useState<string>("")
const [unTitleCount, setUnTitleCount] = useState(1)
const [hintShow, setHintShow] = useState<boolean>(false)
const [hintFile, setHintFile] = useState<string>("")
const [hintIndex, setHintIndex] = useState<number>(0)
const [renameHint, setRenameHint] = useState<boolean>(false)
const [renameIndex, setRenameIndex] = useState<number>(-1)
const [renameFlag, setRenameFlag] = useState<boolean>(false)
const [renameCache, setRenameCache] = useState<string>("")
const [fullScreen, setFullScreen] = useState<boolean>(false)
const [errors, setErrors] = useState<string[]>([])
const [executing, setExecuting] = useState(false)
const [outputEncoding, setOutputEncoding] = useState<"utf8" | "latin1">("utf8")
const xtermAsideRef = useRef(null)
const xtermRef = useRef(null)
const timer = useRef<any>(null)
const [extraParams, setExtraParams] = useState("")
// trigger for updating
const [triggerForUpdatingHistory, setTriggerForUpdatingHistory] = useState<any>(0)
const addFileTab = useMemoizedFn((res: any) => {
const {name, code} = res
const tab: tabCodeProps = {
tab: `${name}.yak`,
code: code,
suffix: "yak",
isFile: false
}
setActiveTab(`${tabList.length}`)
setTabList(tabList.concat([tab]))
setUnTitleCount(unTitleCount + 1)
})
useEffect(() => {
ipcRenderer.on("fetch-send-to-yak-running", (e, res: any) => addFileTab(res))
return () => ipcRenderer.removeAllListeners("fetch-send-to-yak-running")
}, [])
// 自动保存
const autoSave = useMemoizedFn(() => {
for (let tabInfo of tabList) {
if (tabInfo.isFile) {
ipcRenderer.invoke("write-file", {
route: tabInfo.route,
data: tabInfo.code
})
}
}
})
// 保存近期文件内的15个
const saveFiliList = useMemoizedFn(() => {
let files = cloneDeep(fileList).reverse()
files.splice(14)
files = files.reverse()
ipcRenderer.invoke("set-value", RecentFileList, files)
})
// 获取和保存近期打开文件信息,同时展示打开默认内容
useEffect(() => {
let time: any = null
let timer: any = null
setLoading(true)
ipcRenderer
.invoke("get-value", RecentFileList)
.then((value: any) => {
if ((value || []).length !== 0) {
setFileList(value)
} else {
const tab: tabCodeProps = {
tab: `Untitle-${unTitleCount}.yak`,
code: "# input your yak code\nprintln(`Hello Yak World!`)",
suffix: "yak",
isFile: false
}
setActiveTab(`${tabList.length}`)
setTabList([tab])
setUnTitleCount(unTitleCount + 1)
}
})
.catch(() => {})
.finally(() => {
setTimeout(() => setLoading(false), 300)
time = setInterval(() => {
autoSave()
}, 2000)
timer = setInterval(() => {
saveFiliList()
}, 5000)
})
return () => {
saveFiliList()
if (time) clearInterval(time)
if (timer) clearInterval(timer)
}
}, [])
// 全局监听重命名事件是否被打断
useEffect(() => {
document.onmousedown = (e) => {
// @ts-ignore
if (e.path[0].id !== "rename-input" && renameFlag) {
renameCode(renameIndex)
setRenameFlag(false)
}
}
}, [renameFlag])
// 打开文件
const addFile = useMemoizedFn((file: any) => {
const isExists = fileList.filter((item) => item.tab === file.name && item.route === file.path).length === 1
if (isExists) {
for (let index in tabList) {
const item = tabList[index]
if (item.tab === file.name && item.route === file.path) {
setActiveTab(`${index}`)
return false
}
}
}
ipcRenderer
.invoke("fetch-file-content", file.path)
.then((res) => {
const tab: tabCodeProps = {
tab: file.name,
code: res,
suffix: file.name.split(".").pop() === "yak" ? "yak" : "http",
isFile: true,
route: file.path,
extraParams: file.extraParams
}
setActiveTab(`${tabList.length}`)
if (!isExists) setFileList(fileList.concat([tab]))
setTabList(tabList.concat([tab]))
})
.catch(() => {
failed("无法获取该文件内容,请检查后后重试!")
const files = cloneDeep(fileList)
for (let i in files) if (files[i].route === file.path) files.splice(i, 1)
setFileList(files)
})
return false
})
// 新建文件
const newFile = useMemoizedFn(() => {
const tab: tabCodeProps = {
tab: `Untitle-${unTitleCount}.yak`,
code: "# input your yak code\nprintln(`Hello Yak World!`)",
suffix: "yak",
isFile: false
}
setActiveTab(`${tabList.length}`)
setTabList(tabList.concat([tab]))
setUnTitleCount(unTitleCount + 1)
})
//修改文件
const modifyCode = useMemoizedFn((value: string, index: number) => {
const tabs = cloneDeep(tabList)
tabs[index].code = value
setTabList(tabs)
})
// 保存文件
const saveCode = useMemoizedFn((info: tabCodeProps, index: number) => {
if (info.isFile) {
ipcRenderer.invoke("write-file", {
route: info.route,
data: info.code
})
} else {
ipcRenderer.invoke("show-save-dialog", `${codePath}${codePath ? '/' : ''}${info.tab}`).then((res) => {
if (res.canceled) return
const path = res.filePath
const name = res.name
ipcRenderer
.invoke("write-file", {
route: res.filePath,
data: info.code
})
.then(() => {
const suffix = name.split(".").pop()
var tabs = cloneDeep(tabList)
var active = null
tabs = tabs.filter((item) => item.route !== path)
tabs = tabs.map((item, index) => {
if (!item.route && item.tab === info.tab) {
active = index
item.tab = name
item.isFile = true
item.suffix = suffix === "yak" ? suffix : "http"
item.route = path
return item
}
return item
})
if (active !== null) setActiveTab(`${active}`)
setTabList(tabs)
const file: tabCodeProps = {
tab: name,
code: info.code,
isFile: true,
suffix: suffix === "yak" ? suffix : "http",
route: res.filePath,
extraParams: info.extraParams
}
for (let item of fileList) {
if (item.route === file.route) {
return
}
}
setFileList(fileList.concat([file]))
})
})
}
})
//关闭文件
const closeCode = useMemoizedFn((index, isFileList: boolean) => {
const tabInfo = isFileList ? fileList[+index] : tabList[+index]
if (isFileList) {
for (let i in tabList) {
if (tabList[i].tab === tabInfo.tab && tabList[i].route === tabInfo.route) {
const tabs = cloneDeep(tabList)
tabs.splice(i, 1)
setTabList(tabs)
setActiveTab(tabs.length >= 1 ? `0` : "")
}
}
const files = cloneDeep(fileList)
files.splice(+index, 1)
setFileList(files)
} else {
setActiveTab(index)
if (!tabInfo.isFile) {
setHintFile(tabInfo.tab)
setHintIndex(index)
setHintShow(true)
} else {
const tabs = cloneDeep(tabList)
tabs.splice(+index, 1)
setTabList(tabs)
setActiveTab(tabs.length >= 1 ? `0` : "")
}
}
})
// 关闭虚拟文件不保存
const ownCloseCode = useMemoizedFn(() => {
const tabs = cloneDeep(tabList)
tabs.splice(hintIndex, 1)
setTabList(tabs)
setHintShow(false)
setActiveTab(tabs.length >= 1 ? `0` : "")
})
// 删除文件
const delCode = useMemoizedFn((index) => {
const fileInfo = fileList[index]
ipcRenderer
.invoke("delelte-code-file", fileInfo.route)
.then(() => {
for (let i in tabList) {
if (tabList[i].tab === fileInfo.tab && tabList[i].route === fileInfo.route) {
const tabs = cloneDeep(tabList)
tabs.splice(i, 1)
setTabList(tabs)
setActiveTab(tabs.length >= 1 ? `0` : "")
}
}
const arr = cloneDeep(fileList)
arr.splice(index === undefined ? hintIndex : index, 1)
setFileList(arr)
})
.catch(() => {
failed("文件删除失败!")
})
})
//重命名操作
const renameCode = useMemoizedFn((index: number) => {
const tabInfo = fileList[index]
if (renameCache === tabInfo.tab) return
if (!renameCache) return
if (!tabInfo.route) return
const flagStr = tabInfo.route?.indexOf("/") > -1 ? "/" : "\\"
const routes = tabInfo.route?.split(flagStr)
routes?.pop()
ipcRenderer
.invoke("is-exists-file", routes?.concat([renameCache]).join(flagStr))
.then(() => {
const newRoute = routes?.concat([renameCache]).join(flagStr)
if (!tabInfo.route || !newRoute) return
renameFile(index, renameCache, tabInfo.route, newRoute)
})
.catch(() => {
setRenameHint(true)
})
})
// 重命名文件
const renameFile = useMemoizedFn(
(index: number, rename: string, oldRoute: string, newRoute: string, callback?: () => void) => {
ipcRenderer.invoke("rename-file", {old: oldRoute, new: newRoute}).then(() => {
const suffix = rename.split(".").pop()
var files = cloneDeep(fileList)
var tabs = cloneDeep(tabList)
var active = null
files = files.filter((item) => item.route !== newRoute)
tabs = tabs.filter((item) => item.route !== newRoute)
files = files.map((item) => {
if (item.route === oldRoute) {
item.tab = rename
item.suffix = suffix === "yak" ? suffix : "http"
item.route = newRoute
return item
}
return item
})
tabs = tabs.map((item, index) => {
if (item.route === oldRoute) {
active = index
item.tab = rename
item.suffix = suffix === "yak" ? suffix : "http"
item.route = newRoute
return item
}
return item
})
if (active !== null) setActiveTab(`${active}`)
setFileList(files)
setTabList(tabs)
if (callback) callback()
})
}
)
const fileFunction = (kind: string, index: string, isFileList: boolean) => {
const tabCodeInfo = isFileList ? fileList[index] : tabList[index]
switch (kind) {
case "own":
closeCode(index, isFileList)
return
case "other":
const tabInfo: tabCodeProps = cloneDeep(tabList[index])
for (let i in tabList) {
if (i !== index && !tabList[i].isFile) {
const arr: tabCodeProps[] =
+i > +index
? [tabInfo].concat(tabList.splice(+i, tabList.length))
: tabList.splice(+i, tabList.length)
const num = +i > +index ? 1 : 0
setActiveTab(`${num}`)
setTabList(arr)
setHintFile(arr[num].tab)
setHintIndex(num)
setHintShow(true)
return
}
}
const code = cloneDeep(tabList[index])
setTabList([code])
setActiveTab(`0`)
return
case "all":
for (let i in tabList) {
if (!tabList[i].isFile) {
const arr = tabList.splice(+i, tabList.length)
setActiveTab("0")
setTabList(arr)
setHintFile(arr[0].tab)
setHintIndex(0)
setHintShow(true)
return
}
}
setActiveTab("")
setTabList([])
return
case "remove":
closeCode(index, isFileList)
return
case "delete":
delCode(index)
return
case "rename":
setRenameIndex(+index)
setRenameFlag(true)
setRenameCache(tabCodeInfo.tab)
return
}
}
const openFileLayout = (file: any) => {
addFile(file)
}
useEffect(() => {
ipcRenderer.invoke("fetch-code-path")
.then((path: string) => {
ipcRenderer.invoke("is-exists-file", path)
.then(() => {
setCodePath("")
})
.catch(() => {
setCodePath(path)
})
})
}, [])
useEffect(() => {
if (tabList.length === 0) setFullScreen(false)
}, [tabList])
useEffect(() => {
if (!xtermRef) {
return
}
// let buffer = "";
ipcRenderer.on("client-yak-error", async (e: any, data) => {
failed(`FoundError: ${JSON.stringify(data)}`)
if (typeof data === "object") {
setErrors([...errors, `${JSON.stringify(data)}`])
} else if (typeof data === "string") {
setErrors([...errors, data])
} else {
setErrors([...errors, `${data}`])
}
})
ipcRenderer.on("client-yak-end", () => {
info("Yak 代码执行完毕")
setTriggerForUpdatingHistory(getRandomInt(100000))
setTimeout(() => {
setExecuting(false)
}, 300)
})
ipcRenderer.on("client-yak-data", async (e: any, data: ExecResult) => {
if (data.IsMessage) {
// alert(Buffer.from(data.Message).toString("utf8"))
}
if (data?.Raw) {
writeExecResultXTerm(xtermRef, data, outputEncoding)
// writeXTerm(xtermRef, Buffer.from(data.Raw).toString(outputEncoding).replaceAll("\n", "\r\n"))
// monacoEditorWrite(currentOutputEditor, )
}
})
return () => {
ipcRenderer.removeAllListeners("client-yak-data")
ipcRenderer.removeAllListeners("client-yak-end")
ipcRenderer.removeAllListeners("client-yak-error")
}
}, [xtermRef])
const bars = (props: any, TabBarDefault: any) => {
return (
<TabBarDefault
{...props}
children={(barNode: React.ReactElement) => {
return (
<Dropdown
overlay={CustomMenu(barNode.key, false, tabMenu, fileFunction)}
trigger={["contextMenu"]}
>
{barNode}
</Dropdown>
)
}}
/>
)
}
return (
<AutoCard
className={"yak-executor-body"}
// title={"Yak Runner"}
headStyle={{minHeight: 0}}
bodyStyle={{padding: 0, overflow: "hidden"}}
>
<div
style={{width: "100%", height: "100%", display: "flex", backgroundColor: "#E8E9E8"}}
tabIndex={0}
onKeyDown={(e) => {
if (e.keyCode === 78 && (e.ctrlKey || e.metaKey)) {
newFile()
}
if (e.keyCode === 83 && (e.ctrlKey || e.metaKey) && activeTab) {
saveCode(tabList[+activeTab], +activeTab)
}
}}
>
<div style={{width: `${fullScreen ? 0 : 15}%`}}>
<AutoSpin spinning={loading}>
<ExecutorFileList
lists={fileList}
activeFile={tabList[+activeTab]?.route || ""}
renameFlag={renameFlag}
renameIndex={renameIndex}
renameCache={renameCache}
setRenameCache={setRenameCache}
addFile={addFile}
newFile={newFile}
openFile={openFileLayout}
fileFunction={fileFunction}
/>
</AutoSpin>
</div>
<div style={{width: `${fullScreen ? 100 : 85}%`}} className='executor-right-body'>
{tabList.length > 0 && (
<ResizeBox
isVer
firstNode={
<Tabs
className={"right-editor"}
style={{height: "100%"}}
type='editable-card'
activeKey={activeTab}
hideAdd={true}
onChange={(activeTab) => setActiveTab(activeTab)}
onEdit={(key, event: "add" | "remove") => {
switch (event) {
case "remove":
closeCode(key, false)
return
case "add":
return
}
}}
renderTabBar={(props, TabBarDefault) => {
return bars(props, TabBarDefault)
}}
tabBarExtraContent={
tabList.length && (
<Space style={{marginRight: 5}} size={0}>
<Button
style={{height: 25}}
type={"link"}
size={"small"}
disabled={
tabList[+activeTab] && tabList[+activeTab].suffix !== "yak"
}
onClick={(e) => {
let m = showDrawer({
width: "60%",
placement: "left",
title: "选择你的 Yak 模块执行特定功能",
content: (
<>
<YakScriptManagerPage
type={"yak"}
onLoadYakScript={(s) => {
const tab: tabCodeProps = {
tab: `Untitle-${unTitleCount}.yak`,
code: s.Content,
suffix: "yak",
isFile: false
}
info(`加载 Yak 模块:${s.ScriptName}`)
xtermClear(xtermRef)
setActiveTab(`${tabList.length}`)
setTabList(tabList.concat([tab]))
setUnTitleCount(unTitleCount + 1)
m.destroy()
}}
/>
</>
)
})
}}
>
Yak脚本模板
</Button>
<Button
icon={
fullScreen ? (
<FullscreenExitOutlined style={{fontSize: 15}} />
) : (
<FullscreenOutlined style={{fontSize: 15}} />
)
}
type={"link"}
size={"small"}
style={{width: 30, height: 25}}
onClick={() => setFullScreen(!fullScreen)}
/>
<Popover
trigger={["click"]}
title={"设置命令行额外参数"}
placement="bottomRight"
content={
<Space style={{width: 400}}>
<div>yak {tabList[+activeTab]?.tab || "[file]"}</div>
<Divider type={"vertical"} />
<Paragraph
style={{width: 200, marginBottom: 0}}
editable={{
icon: <Space>
<EditOutlined />
<SaveOutlined onClick={(e) => {
e.stopPropagation()
tabList[+activeTab].extraParams = extraParams
setTabList(tabList)
if(tabList[+activeTab].isFile){
const files = fileList.map(item => {
if(item.route === tabList[+activeTab].route){
item.extraParams = extraParams
return item
}
return item
})
setFileList(files)
}
success("保存成功")
}}
/></Space>,
tooltip: '编辑/保存为该文件默认参数',
onChange: setExtraParams
}}
>
{extraParams}
</Paragraph>
</Space>
}
>
<Button type={"link"} icon={<EllipsisOutlined />} onClick={() => {
setExtraParams(tabList[+activeTab]?.extraParams || "")
}} />
</Popover>
{executing ? (
<Button
icon={<PoweroffOutlined style={{fontSize: 15}} />}
type={"link"}
danger={true}
size={"small"}
style={{width: 30, height: 25}}
onClick={() => ipcRenderer.invoke("cancel-yak")}
/>
) : (
<Button
icon={<CaretRightOutlined style={{fontSize: 15}} />}
type={"link"}
ghost={true}
size={"small"}
style={{width: 30, height: 25}}
disabled={
tabList[+activeTab] && tabList[+activeTab].suffix !== "yak"
}
onClick={() => {
setErrors([])
setExecuting(true)
ipcRenderer.invoke("exec-yak", {
Script: tabList[+activeTab].code,
Params: [],
RunnerParamRaw: extraParams
})
}}
/>
)}
</Space>
)
}
>
{tabList.map((item, index) => {
return (
<TabPane tab={item.tab} key={`${index}`}>
<div style={{height: "100%"}}>
<AutoSpin spinning={executing}>
<div style={{height: "100%"}}>
<YakEditor
type={item.suffix}
value={item.code}
setValue={(value) => {
modifyCode(value, index)
}}
/>
</div>
</AutoSpin>
</div>
</TabPane>
)
})}
</Tabs>
}
firstRatio='70%'
secondNode={
<div
ref={xtermAsideRef}
style={{
width: "100%",
height: "100%",
overflow: "hidden",
borderTop: "1px solid #dfdfdf"
}}
>
<Tabs
style={{height: "100%"}}
className={"right-xterm"}
size={"small"}
tabBarExtraContent={
<Space>
<SelectOne
formItemStyle={{marginBottom: 0}}
value={outputEncoding}
setValue={setOutputEncoding}
size={"small"}
data={[
{text: "GBxxx编码", value: "latin1"},
{text: "UTF-8编码", value: "utf8"}
]}
/>
<Button
size={"small"}
icon={<DeleteOutlined />}
type={"link"}
onClick={(e) => {
xtermClear(xtermRef)
}}
/>
</Space>
}
>
<TabPane
tab={<div style={{width: 50, textAlign: "center"}}>输出</div>}
key={"output"}
>
<div style={{width: "100%", height: "100%"}}>
<CVXterm
ref={xtermRef}
options={{
convertEol: true,
theme: {
foreground: "#536870",
background: "#E8E9E8",
cursor: "#536870",
black: "#002831",
brightBlack: "#001e27",
red: "#d11c24",
brightRed: "#bd3613",
green: "#738a05",
brightGreen: "#475b62",
yellow: "#a57706",
brightYellow: "#536870",
blue: "#2176c7",
brightBlue: "#708284",
magenta: "#c61c6f",
brightMagenta: "#5956ba",
cyan: "#259286",
brightCyan: "#819090",
white: "#eae3cb",
brightWhite: "#fcf4dc"
}
}}
/>
</div>
</TabPane>
<TabPane
tab={
<div style={{width: 50, textAlign: "center"}} key={"terminal"}>
终端(监修中)
</div>
}
disabled
>
<Terminal />
</TabPane>
</Tabs>
</div>
}
secondRatio='30%'
/>
)}
{tabList.length === 0 && (
<Empty className='right-empty' description={<p>请点击左侧打开或新建文件</p>}></Empty>
)}
</div>
<Modal
visible={hintShow}
onCancel={() => setHintShow(false)}
footer={[
<Button key='link' onClick={() => setHintShow(false)}>
取消
</Button>,
<Button key='submit' onClick={() => ownCloseCode()}>
不保存
</Button>,
<Button key='back' type='primary' onClick={() => saveCode(tabList[hintIndex], hintIndex)}>
保存
</Button>
]}
>
<div style={{height: 40}}>
<ExclamationCircleOutlined style={{fontSize: 22, color: "#faad14"}} />
<span style={{fontSize: 18, marginLeft: 15}}>文件未保存</span>
</div>
<p style={{fontSize: 15, marginLeft: 37}}>{`是否要保存${hintFile}里面的内容吗?`}</p>
</Modal>
<Modal
visible={renameHint}
onCancel={() => setHintShow(false)}
footer={[
<Button key='link' onClick={() => setRenameHint(false)}>
取消
</Button>,
<Button
key='back'
type='primary'
onClick={() => {
const oldRoute = tabList[renameIndex].route
if (!oldRoute) return
const flagStr = oldRoute?.indexOf("/") > -1 ? "/" : "\\"
const routes = oldRoute?.split(flagStr)
routes?.pop()
const newRoute = routes?.concat([renameCache]).join(flagStr)
if (!oldRoute || !newRoute) return
renameFile(renameIndex, renameCache, oldRoute, newRoute, () => {
setRenameHint(false)
})
}}
>
确定
</Button>
]}
>
<div style={{height: 40}}>
<ExclamationCircleOutlined style={{fontSize: 22, color: "#faad14"}} />
<span style={{fontSize: 18, marginLeft: 15}}>文件已存在</span>
</div>
<p style={{fontSize: 15, marginLeft: 37}}>{`是否要覆盖已存在的文件吗?`}</p>
</Modal>
</div>
</AutoCard>
)
}
Example #25
Source File: DeletePostModal.tsx From foodie with MIT License | 4 votes |
DeletePostModal: React.FC<IProps> = (props) => {
const [isDeleting, setIsDeleting] = useState(false);
const [error, setError] = useState<IError | null>(null);
const didMount = useDidMount();
const dispatch = useDispatch();
const { targetPost, isOpen } = useSelector((state: IRootReducer) => ({
targetPost: state.helper.targetPost,
isOpen: state.modal.isOpenDeletePost
}))
const handleDeletePost = async () => {
try {
setIsDeleting(true);
await deletePost(targetPost?.id as string);
didMount && setIsDeleting(false);
closeModal();
props.deleteSuccessCallback(targetPost?.id as string);
toast.dark('Post successfully deleted.', {
progressStyle: { backgroundColor: '#4caf50' }
});
} catch (e) {
if (didMount) {
setIsDeleting(false);
setError(e);
}
}
};
const closeModal = () => {
if (isOpen && !isDeleting) {
dispatch(setTargetPost(null));
dispatch(hideModal(EModalType.DELETE_POST));
}
}
return (
<Modal
isOpen={isOpen}
onAfterOpen={props.onAfterOpen}
onRequestClose={closeModal}
contentLabel="Example Modal"
className="modal"
shouldCloseOnOverlayClick={!isDeleting}
overlayClassName="modal-overlay"
>
<div className="relative">
<div
className="absolute right-2 top-2 p-1 rounded-full flex items-center justify-center cursor-pointer hover:bg-gray-200 dark:hover:bg-indigo-1100"
onClick={closeModal}
>
<CloseOutlined className="p-2 outline-none text-gray-500 dark:text-white" />
</div>
{error && (
<span className="p-4 bg-red-100 text-red-500 block">
{error?.error?.message || 'Unable to process your request.'}
</span>
)}
<div className="p-4 laptop:px-8">
<h2 className="dark:text-white">
<ExclamationCircleOutlined className="text-red-500 mr-2 pt-2" />
Delete Post
</h2>
<p className="text-gray-600 my-4 dark:text-gray-400">Are you sure you want to delete this post?</p>
<div className="flex justify-between">
<button
className="button--muted !rounded-full dark:bg-indigo-1100 dark:text-white dark:hover:bg-indigo-1100"
onClick={closeModal}
disabled={isDeleting}
>
Cancel
</button>
<button
className="button--danger"
disabled={isDeleting}
onClick={handleDeletePost}
>
Delete
</button>
</div>
</div>
</div>
</Modal>
);
}
Example #26
Source File: QueueListSharedComponents.tsx From office-hours with GNU General Public License v3.0 | 4 votes |
export function QueueInfoColumn({
queueId,
isStaff,
buttons,
}: QueueInfoColumnProps): ReactElement {
const { queue, mutateQueue } = useQueue(queueId);
const disableQueue = async () => {
await API.queues.disable(queueId);
await mutateQueue();
message.success("Successfully disabled queue: " + queue.room);
await Router.push("/");
};
const clearQueue = async () => {
await API.queues.clean(queueId);
await mutateQueue();
message.success("Successfully cleaned queue: " + queue.room);
};
const confirmDisable = () => {
confirm({
title: `Please Confirm!`,
icon: <ExclamationCircleOutlined />,
style: { whiteSpace: "pre-wrap" },
content: `Please confirm that you want to disable the queue: ${queue.room}.\n
This queue will no longer appear in the app, and any students currently in the queue will be removed.`,
onOk() {
disableQueue();
},
});
};
return (
<InfoColumnContainer>
<QueueInfo>
<QueueRoomGroup>
<QueueTitle data-cy="room-title">
{queue?.room} {queue?.isDisabled && <b>(disabled)</b>}
</QueueTitle>
{!queue.allowQuestions && (
<Tooltip title="This queue is no longer accepting questions">
<StopOutlined
data-cy="stopQuestions"
style={{ color: "red", fontSize: "24px", marginLeft: "8px" }}
/>
</Tooltip>
)}
</QueueRoomGroup>
{queue?.isProfessorQueue && (
<QueuePropertyRow>
<Tag color="blue">Professor Queue</Tag>
</QueuePropertyRow>
)}
</QueueInfo>
{queue?.notes && (
<QueuePropertyRow>
<NotificationOutlined />
<QueueText>
<Linkify
componentDecorator={(decoratedHref, decoratedText, key) => (
<a
target="_blank"
rel="noopener noreferrer"
href={decoratedHref}
key={key}
>
{decoratedText}
</a>
)}
>
<QueuePropertyText>{queue.notes}</QueuePropertyText>
</Linkify>
</QueueText>
</QueuePropertyRow>
)}
<QueueUpToDateInfo queueId={queueId} />
{buttons}
<StaffH2>Staff</StaffH2>
<TAStatuses queueId={queueId} />
{isStaff && (
<QueueManagementBox>
<Popconfirm
title={
"Are you sure you want to clear all students from the queue?"
}
okText="Yes"
cancelText="No"
placement="top"
arrowPointAtCenter={true}
onConfirm={clearQueue}
>
<ClearQueueButton>Clear Queue</ClearQueueButton>
</Popconfirm>
<DisableQueueButton
onClick={confirmDisable}
data-cy="queue-disable-button"
disabled={queue?.isDisabled}
>
{queue?.isDisabled ? `Queue deleted` : `Delete Queue`}
</DisableQueueButton>
</QueueManagementBox>
)}
</InfoColumnContainer>
);
}
Example #27
Source File: index.tsx From scorpio-h5-design with MIT License | 4 votes |
Component = function() {
const { queryAllWithComponent } = Model.useContainer();
const [componentDraw, setComponentDraw] = useState({
category: {},
component: {},
visible: false,
data: {},
});
const addComponentReq = useRequest(service.addComponent, {
manual: true,
});
const editComponentReq = useRequest(service.editComponent, {
manual: true,
});
const deleteComponentReq = useRequest(service.deleteComponent, {
manual: true,
});
const edit = function({ _id }: any) {
history.push(`/manage/component/detail?componentId=${_id}`);
};
const onCancel = function() {
setComponentDraw({
category: {},
component: {},
visible: false,
data: {},
});
};
const onSubmit = async function(values: any) {
// @ts-expect-error
if(!componentDraw.component._id){
await addComponentReq.run({
// @ts-expect-error
categoryId: componentDraw.category._id,
...values,
});
}else{
await editComponentReq.run({
// @ts-expect-error
_id: componentDraw.component._id,
...values,
});
}
setComponentDraw({
category: {},
component: {},
visible: false,
data: {},
});
await queryAllWithComponent.refresh();
};
const onAddComponent = function(category: any) {
setComponentDraw({
category,
component: {},
visible: true,
data: {},
});
};
const copyId = function(component: any) {
//
};
const dev = async function(component: any) {
// @ts-expect-error
if(!h5Lib[component._id]){
return message.error('组件不存在,请检查本地h5lib目录');
}
history.push(`/manage/component/detail?componentId=${component._id}`);
};
const onEditComponent = function(component: any, category: any){
setComponentDraw({
category,
visible: true,
data: {...component},
component,
});
};
const onDeleteComponent = function(component: any) {
confirm({
title: '确认删除此组件?',
icon: <ExclamationCircleOutlined />,
content: '请确保组件未被其他页面引用',
okText: '确认',
okType: 'danger',
cancelText: '取消',
onOk: async()=> {
await deleteComponentReq.run({
_id: component._id,
});
await queryAllWithComponent.refresh();
},
});
};
return (
<PageHeader
className="manage-component"
ghost={false}
onBack={() => null}
title="组件开发"
subTitle="开发流程:申请组件ID->h5Lib目录下新增组件->组件开发页面编辑、测试"
>
<Spin spinning={queryAllWithComponent.loading}>
<Tabs tabPosition='left'>
{
queryAllWithComponent.data.map((category: any) => (
<TabPane tab={<>{category.name}<Badge count={category.components.length} /></>} key={category._id}>
<Button
type="dashed"
block
className="manage-component-add-btn"
icon={<PlusOutlined />}
onClick={() => onAddComponent(category)}
>新增</Button>
{
category.components.map((component: any) => (
<Card
key={component._id}
className="manage-component-card"
// cover={
// component.cover ? <img
// alt="example"
// src={component.cover}
// /> : <Empty description={false} />
// }
actions={[
<Tooltip title={`ID:${component._id}`} key="id">
<Typography.Link onClick={() => copyId(component)}>ID</Typography.Link>
</Tooltip>,
<Typography.Link key="edit" onClick={() => dev(component)}>组件开发</Typography.Link>,
<Dropdown
key="more"
trigger={['click']}
overlay={
<Menu>
<Menu.Item key="0" onClick={() => { onEditComponent(component, category); }}>
编辑
</Menu.Item>
<Menu.Item key="1" onClick={() => { onDeleteComponent(component); }}>删除</Menu.Item>
</Menu>
}
>
<a className="ant-dropdown-link" onClick={(e) => e.preventDefault()}>
更多 <DownOutlined />
</a>
</Dropdown>,
]}
>
<div className="manage-component-card-img">
<img src={component.cover} />
</div>
<Paragraph ellipsis={{ rows: 2, expandable: true, symbol: 'more' }}>
{component.name}
</Paragraph>
</Card>
))
}
</TabPane>
))
}
</Tabs>
</Spin>
<FormRenderDrawer
type="add"
visible={componentDraw.visible}
onCancel={onCancel}
formSchema={formSchema}
onSubmit={onSubmit}
loading={addComponentReq.loading}
formData={componentDraw.data}
/>
</PageHeader>
);
}
Example #28
Source File: CoursePreferenceSettings.tsx From office-hours with GNU General Public License v3.0 | 4 votes |
export default function CoursePreferenceSettings(): ReactElement {
const { data: profile, mutate } = useSWR(`api/v1/profile`, async () =>
API.profile.index()
);
const formattedRoles = {
student: "Student",
ta: "TA",
professor: "Professor",
};
async function withdraw(course: UserCourse) {
await API.course.withdrawCourse(course.course.id);
message.success("Successfully withdrew from " + course.course.name);
await mutate();
await Router.push("/");
}
function showConfirm(courseId) {
const course = profile?.courses.find((c) => c.course.id === courseId);
confirm({
title: `Please Confirm!`,
icon: <ExclamationCircleOutlined />,
content: `Please confirm that you want to unenroll from ${
course.course.name
} as a ${
formattedRoles[course.role]
}. The only way to get back is by contacting a professor!`,
onOk() {
withdraw(course);
},
});
}
const InstructorCell = ({ courseId }: { courseId: number }) => {
const course = useCourse(courseId);
return <>{course.course?.coordinator_email}</>;
};
const columns = [
{
title: "Course name",
dataIndex: "name",
key: "name",
},
{
title: "Role",
dataIndex: "role",
key: "role",
},
{
title: "Instructor",
dataIndex: "instructor",
key: "instructor",
render: function createInstructorCell(courseId) {
return <InstructorCell courseId={courseId} />;
},
},
{
title: "",
key: "courseId",
dataIndex: "courseId",
render: function withdrawButton(courseId) {
return (
<Button
style={{ marginLeft: "20px" }}
type="primary"
shape="round"
onClick={() => {
showConfirm(courseId);
}}
danger
>
Withdraw
</Button>
);
},
},
];
function createCourseDataSource() {
return profile?.courses.map((c) => ({
key: c.course.id,
name: c.course.name,
role: formattedRoles[c.role],
instructor: c.course.id,
courseId: c.course.id,
}));
}
return (
profile && (
<div>
<HeaderTitle>
<h1>Course Preferences</h1>
</HeaderTitle>
<Table columns={columns} dataSource={createCourseDataSource()} />
</div>
)
);
}
Example #29
Source File: FormRender.tsx From ant-simple-draw with MIT License | 4 votes |
FormRender: FC<FormRenderType> = memo(
function FormRender({ editType, onSave, showEditPropsData, id }) {
const [form] = Form.useForm();
useEffect(() => {
form.setFieldsValue(showEditPropsData);
return () => {
form.resetFields();
};
}, [form, id]);
const onFinish = (values: Store) => {
onSave && onSave(values);
};
const handlechange = () => {
onFinish(form.getFieldsValue());
};
const colFun = (col: number | undefined) => (col ? col : 24);
return (
<Form form={form} name={`form_editor`} onFinish={onFinish} onValuesChange={handlechange}>
<Row gutter={[30, 0]}>
{editType.map((item, index) => {
return (
<React.Fragment key={item.key}>
{item.type === 'Number' && (
<Col span={colFun(item.col)}>
<AttrContainer border={item.border || false} title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<InputNumber style={{ width: item.width }} min={0} />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Background' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<BackGround />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Switch' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item
style={{ margin: '0' }}
label={item.name}
name={item.key}
valuePropName="checked"
labelAlign="left"
labelCol={{ span: 19 }}
>
<Switch />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Image' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item label={null} name={item.key} style={{ margin: '0' }}>
<ImgComponent />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Input' && (
<Col span={colFun(item.col)}>
<AttrContainer border={true} title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<Input />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'RichText' && (
<Col span={colFun(item.col)}>
<AttrContainer
border={false}
title={item.title}
containerStyle={{ padding: '0' }}
>
<Form.Item label={null} name={item.key} style={{ margin: '0' }}>
<WangEditor />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Radio' && (
<Col span={colFun(item.col)}>
<AttrContainer border={true} title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<Radio.Group>
{item.options!.map((items, index) => (
<Radio value={items.value} key={index}>
{items.label}
</Radio>
))}
</Radio.Group>
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Select' && (
<Col span={colFun(item.col)}>
<AttrContainer border={true} title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<Selects data={item.options!} valKey={'value'} valName={'label'} />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'TextArea' && (
<Col span={colFun(item.col)}>
<AttrContainer
border={false}
title={item.title}
containerStyle={{ padding: '0' }}
>
<Form.Item label={null} name={item.key} style={{ margin: '0' }}>
<TextArea autoSize={{ minRows: 2 }} showCount />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Color' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<Input type={'color'} />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Border' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item label={item.name} name={item.key} style={{ margin: '0' }}>
<Border />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Slider' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item
label={item.name}
name={item.key}
style={{ margin: '0' }}
initialValue={100}
>
<Slider tipFormatter={(val) => val + '%'} />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'FontStyle' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item label={null} name={item.key} style={{ margin: '0' }}>
<FontStyle />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'Padding' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item
label={item.name}
name={item.key}
style={{ margin: '0', display: 'flex', alignItems: 'center' }}
>
<Padding />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'BorderRadius' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item
label={
<div>
<span> {item.name}</span>
<Tooltip title="只有在背景,边框等样式下有效果">
<ExclamationCircleOutlined
style={{ color: '#9da3ac', paddingLeft: '3px', fontSize: '17px' }}
/>
</Tooltip>
</div>
}
name={item.key}
style={{ margin: '0', display: 'flex', alignItems: 'center' }}
>
<BorderRadius />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'TextShadow' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item
label={item.name}
name={item.key}
style={{ margin: '0', display: 'flex', alignItems: 'center' }}
>
<TextShadow />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'BoxShadow' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item
label={
<div>
<span> {item.name}</span>
<Tooltip title="只有在背景下有效果">
<ExclamationCircleOutlined
style={{ color: '#9da3ac', paddingLeft: '3px', fontSize: '17px' }}
/>
</Tooltip>
</div>
}
name={item.key}
style={{ margin: '0', display: 'flex', alignItems: 'center' }}
>
<BoxShadow />
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'OlUl' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item style={{ margin: '0' }}>
<OlUl
keyName={item.key}
form={form}
showEditPropsData={showEditPropsData}
/>
</Form.Item>
</AttrContainer>
</Col>
)}
{item.type === 'TaskList' && (
<Col span={colFun(item.col)}>
<AttrContainer title={item.title}>
<Form.Item style={{ margin: '0' }}>
<TaskList keyName={item.key} />
</Form.Item>
</AttrContainer>
</Col>
)}
</React.Fragment>
);
})}
</Row>
</Form>
);
},
(prevProps, nextProps) => {
if (prevProps.id !== nextProps.id) {
// 防止多次出发组件
return false;
} else {
return true;
}
},
)