@ant-design/icons#MinusCircleOutlined TypeScript Examples
The following examples show how to use
@ant-design/icons#MinusCircleOutlined.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.tsx From drip-table with MIT License | 6 votes |
public renderFormItem(item: unknown, index: number) {
return (
<div className={styles['array-component-form-container']} key={index}>
<div className={styles['array-component-left-container']}>
{ (this.props.schema['ui:props']?.items as DTGComponentPropertySchema[])
.map((schema, i) => this.renderAttributeItem(schema, i, index)) }
</div>
<div className={styles['array-component-right-container']}>
<Button
icon={<PlusCircleOutlined />}
shape="circle"
size="small"
onClick={() => {
const currentValue = this.props.value?.slice() || [];
currentValue.splice(index + 1, 0, { paramName: '', prefix: '', suffix: '' });
this.props.onChange?.(currentValue);
}}
/>
<Button
icon={<MinusCircleOutlined />}
shape="circle"
size="small"
onClick={() => {
const currentValue = this.props.value?.slice() || [];
currentValue.splice(index, 1);
this.props.onChange?.(currentValue);
}}
/>
</div>
</div>
);
}
Example #2
Source File: index.tsx From ant-simple-draw with MIT License | 6 votes |
TaskList: FC<{ keyName: string }> = memo(({ keyName }) => {
return (
<>
<Form.List name={[keyName, 'list']}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8, justifyContent: 'space-around' }}
align="baseline"
>
<Form.Item
{...restField}
name={[name, 'check']}
style={{ marginBottom: '16px' }}
valuePropName="checked"
>
<Checkbox></Checkbox>
</Form.Item>
<Form.Item {...restField} name={[name, 'text']} style={{ marginBottom: '16px' }}>
<Input />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} title="移除" />
</Space>
))}
<Form.Item style={{ marginBottom: '16px' }}>
<Button onClick={() => add()} block icon={<PlusOutlined />}>
添加数据
</Button>
</Form.Item>
</>
)}
</Form.List>
</>
);
})
Example #3
Source File: tagItem.tsx From fe-v5 with Apache License 2.0 | 5 votes |
TagItem: React.FC<Itag> = ({ field, remove, form }) => {
const { t } = useTranslation();
const [valuePlaceholder, setValuePlaceholder] = useState<string>('');
const [funcCur, setfuncCur] = useState('==');
useEffect(() => {
const tags = form.getFieldValue('tags');
funcChange(tags[field.name].func);
}, [field]);
const funcChange = (val) => {
let text = '';
if (val === 'in') {
text = '可以输入多个值,用回车分割';
} else if (val === '=~') {
text = '请输入正则表达式匹配标签value';
}
setfuncCur(val);
setValuePlaceholder(text);
};
return (
<>
<Row gutter={[10, 10]} style={{ marginBottom: '10px' }}>
<Col span={5}>
<Form.Item style={{ marginBottom: 0 }} name={[field.name, 'key']} fieldKey={[field.name, 'key']} rules={[{ required: true, message: t('key不能为空') }]}>
<Input placeholder={t('请输入屏蔽事件标签key')} />
</Form.Item>
</Col>
<Col span={3}>
<Form.Item style={{ marginBottom: 0 }} name={[field.name, 'func']} fieldKey={[field.name, 'func']} initialValue='=='>
<Select suffixIcon={<CaretDownOutlined />} onChange={funcChange}>
<Option value='=='>==</Option>
<Option value='=~'>=~</Option>
<Option value='in'>in</Option>
</Select>
</Form.Item>
</Col>
<Col span={15}>
<Form.Item style={{ marginBottom: 0 }} name={[field.name, 'value']} fieldKey={[field.name, 'value']} rules={[{ required: true, message: t('value不能为空') }]}>
{funcCur == 'in' ? (
<Select mode='tags' open={false} style={{ width: '100%' }} placeholder={t(valuePlaceholder)} tokenSeparators={[' ']}></Select>
) : (
<Input className='ant-input' placeholder={t(valuePlaceholder)} />
)}
</Form.Item>
</Col>
<Col>
<MinusCircleOutlined style={{ marginTop: '8px' }} onClick={() => remove(field.name)} />
</Col>
</Row>
</>
);
}
Example #4
Source File: NotificationsSettings.tsx From office-hours with GNU General Public License v3.0 | 5 votes |
function DeviceNotifPanel() {
const thisEndpoint = useThisDeviceEndpoint();
const { data: profile, mutate } = useSWR(`api/v1/profile`, async () =>
API.profile.index()
);
const thisDesktopNotif = profile?.desktopNotifs?.find(
(dn) => dn.endpoint === thisEndpoint
);
return (
<div>
<DeviceAddHeader>
<h3>Your Devices</h3>
{!thisDesktopNotif && (
<Tooltip
title={
getNotificationState() ===
NotificationStates.browserUnsupported &&
"Browser does not support notifications. Please use Chrome or Firefox, and not Incognito Mode."
}
>
<Button
onClick={async () => {
const canNotify = await requestNotificationPermission();
if (canNotify === NotificationStates.notAllowed) {
message.warning("Please allow notifications in this browser");
}
if (canNotify === NotificationStates.granted) {
await registerNotificationSubscription();
mutate();
}
}}
disabled={
getNotificationState() === NotificationStates.browserUnsupported
}
style={{ marginBottom: "4px" }}
>
Add This Device
</Button>
</Tooltip>
)}
</DeviceAddHeader>
<List
bordered
dataSource={profile.desktopNotifs}
locale={{ emptyText: "No Devices Registered To Receive Notifications" }}
renderItem={(device: DesktopNotifPartial) => (
<List.Item
actions={[
<MinusCircleOutlined
style={{ fontSize: "20px" }}
key={0}
onClick={async () => {
await API.notif.desktop.unregister(device.id);
mutate();
}}
/>,
]}
>
<List.Item.Meta
title={renderDeviceInfo(device, device.endpoint === thisEndpoint)}
description={`Registered ${device.createdAt.toLocaleDateString()}`}
/>
</List.Item>
)}
/>
</div>
);
}
Example #5
Source File: RelationTypeFilter.tsx From datart with Apache License 2.0 | 5 votes |
ConditionCol: FC<{
condition: ChartFilterCondition;
fields: ChartDataSectionField[];
onRelationChange: (relation?: ChartFilterCondition) => void;
}> = ({ condition, fields, onRelationChange }) => {
const onAddSubRelation = () => {
const newCondition = new ConditionBuilder(condition)
.setOperator(FilterRelationType.AND)
.asRelation(condition.value, [
new ChartFilterCondition(),
new ChartFilterCondition(),
]);
onRelationChange(newCondition);
};
const onDeleteRelation = () => {
onRelationChange(undefined);
};
const handleOperatorChange = operator => {
const newCondition = new ConditionBuilder(condition)
.setOperator(operator)
.asRelation();
onRelationChange(newCondition);
};
const handleConditionChange = value => {
const newCondition = new ConditionBuilder(condition)
.setValue(value)
.asRelation();
onRelationChange(newCondition);
};
if (condition.type === FilterConditionType.Relation) {
return (
<StyledRelationCol>
<Row>
<OperatorCol
operator={condition.operator}
onOperatorChange={handleOperatorChange}
/>
<MinusCircleOutlined onClick={onDeleteRelation} />
</Row>
<Row>
<RelationTypeFilter
condition={condition}
fields={fields}
showOperator={false}
onRelationChange={onRelationChange}
/>
</Row>
</StyledRelationCol>
);
}
return (
<StyledRelationCol>
<Row>
<Select
value={condition?.value as string}
onChange={handleConditionChange}
>
{fields.map(c => (
<Select.Option value={c.colName}>
{getColumnRenderName(c)}
</Select.Option>
))}
</Select>
<BranchesOutlined onClick={onAddSubRelation} />
</Row>
</StyledRelationCol>
);
}
Example #6
Source File: index.tsx From fe-v5 with Apache License 2.0 | 5 votes |
export default function index({ targets }) {
const namePrefix = ['overrides'];
return (
<Form.List name={namePrefix}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => {
return (
<Panel
isInner
header='override'
extra={
<Space>
<PlusCircleOutlined
onClick={() => {
add({
type: 'special',
});
}}
/>
{fields.length > 1 && (
<MinusCircleOutlined
onClick={() => {
remove(name);
}}
/>
)}
</Space>
}
>
<Form.Item label='查询条件名称' {...restField} name={[name, 'matcher', 'value']}>
<Select suffixIcon={<CaretDownOutlined />} allowClear>
{_.map(targets, (target) => {
return (
<Select.Option key={target.refId} value={target.refId}>
{target.refId}
</Select.Option>
);
})}
</Select>
</Form.Item>
<ValueMappings preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'valueMappings']} />
<StandardOptions preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'standardOptions']} />
</Panel>
);
})}
</>
)}
</Form.List>
);
}
Example #7
Source File: index.tsx From imove with MIT License | 5 votes |
VisualFormItem: React.FC<IVisualFormItemProps> = (props) => {
const { type, desc } = props;
return (
<React.Fragment>
<div className={styles.itemHeader}>
<span className={styles.itemTitleText}>{type}</span>
<span className={styles.itemDescText}>{desc}</span>
</div>
<Form.List name={type}>
{(fields, { add, remove }) => (
<React.Fragment>
{fields.map((field) => (
<Space key={field.key} align={'baseline'}>
<Form.Item
{...field}
label={'key'}
name={[field.name, 'key']}
fieldKey={[field.fieldKey, 'key']}
>
<Input />
</Form.Item>
<Form.Item
{...field}
label={'value'}
name={[field.name, 'value']}
fieldKey={[field.fieldKey, 'value']}
>
<Input />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Space>
))}
<Form.Item>
<Button
block={true}
type={'dashed'}
icon={<PlusOutlined />}
onClick={() => add()}
>
新增
</Button>
</Form.Item>
</React.Fragment>
)}
</Form.List>
</React.Fragment>
);
}
Example #8
Source File: index.tsx From fe-v5 with Apache License 2.0 | 5 votes |
export default function index({ targets }) {
const namePrefix = ['overrides'];
return (
<Form.List name={namePrefix}>
{(fields, { add, remove }) =>
fields.map(({ key, name, ...restField }) => {
return (
<Panel
key={key}
isInner
header='override'
extra={
<Space>
<PlusCircleOutlined
onClick={() => {
add({
type: 'special',
});
}}
/>
{fields.length > 1 && (
<MinusCircleOutlined
onClick={() => {
remove(name);
}}
/>
)}
</Space>
}
>
<Form.Item label='查询条件名称' {...restField} name={[name, 'matcher', 'value']}>
<Select suffixIcon={<CaretDownOutlined />} allowClear>
{_.map(targets, (target) => {
return (
<Select.Option key={target.refId} value={target.refId}>
{target.refId}
</Select.Option>
);
})}
</Select>
</Form.Item>
<ValueMappings preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'valueMappings']} />
<StandardOptions preNamePrefix={namePrefix} namePrefix={[name, 'properties', 'standardOptions']} />
</Panel>
);
})
}
</Form.List>
);
}
Example #9
Source File: tagItem.tsx From fe-v5 with Apache License 2.0 | 5 votes |
TagItem: React.FC<Itag> = ({ field, remove, form }) => {
const { t } = useTranslation();
const [valuePlaceholder, setValuePlaceholder] = useState<string>('');
const [funcCur, setfuncCur] = useState('==');
useEffect(() => {
const tags = form.getFieldValue('tags');
funcChange(tags[field.name].func);
}, []);
const funcChange = (val) => {
let text = '';
if (val === 'in') {
text = '可以输入多个值,用回车分割';
} else if (val === '=~') {
text = '请输入正则表达式匹配标签value';
}
setfuncCur(val);
setValuePlaceholder(text);
};
return (
<>
<Row gutter={[10, 10]} style={{ marginBottom: '10px' }}>
<Col span={5}>
<Form.Item style={{ marginBottom: 0 }} name={[field.name, 'key']} fieldKey={[field.name, 'key']} rules={[{ required: true, message: t('key不能为空') }]}>
<Input placeholder={t('请输入订阅事件标签key')} />
</Form.Item>
</Col>
<Col span={3}>
<Form.Item style={{ marginBottom: 0 }} name={[field.name, 'func']} fieldKey={[field.name, 'func']} initialValue='=='>
<Select suffixIcon={<CaretDownOutlined />} onChange={funcChange}>
<Option value='=='>==</Option>
<Option value='=~'>=~</Option>
<Option value='in'>in</Option>
</Select>
</Form.Item>
</Col>
<Col span={15}>
<Form.Item style={{ marginBottom: 0 }} name={[field.name, 'value']} fieldKey={[field.name, 'value']} rules={[{ required: true, message: t('value不能为空') }]}>
{funcCur == 'in' ? (
<Select mode='tags' open={false} style={{ width: '100%' }} placeholder={t(valuePlaceholder)} tokenSeparators={[' ']}></Select>
) : (
<Input className='ant-input' placeholder={t(valuePlaceholder)} />
)}
</Form.Item>
</Col>
<Col>
<MinusCircleOutlined style={{ marginTop: '8px' }} onClick={() => remove(field.name)} />
</Col>
</Row>
</>
);
}
Example #10
Source File: index.tsx From ant-simple-draw with MIT License | 5 votes |
OlUl: FC<{ keyName: string; form: FormInstance<Store>; showEditPropsData: any }> = memo(
({ keyName, form, showEditPropsData }) => {
const [type, setType] = useState<string | undefined>(undefined);
const ulType = [
{ label: '小圆点', value: 'disc' },
{ label: '空心圆圈', value: 'circle' },
{ label: '小方块', value: 'square' },
];
const olType = [
{ label: '1', value: '1' },
{ label: 'A', value: 'A' },
{ label: 'I', value: 'I' },
];
useEffect(() => {
if (showEditPropsData[keyName]) {
// 将数据流的数据同步一下
setType(showEditPropsData[keyName].type);
}
}, [showEditPropsData, keyName]);
const selectData = useMemo(() => {
return type === 'ol' ? olType : ulType;
}, [type]);
return (
<>
<Form.Item label={null} name={[keyName, 'type']} style={{ marginBottom: '16px' }}>
<Radio.Group
onChange={(val) => {
setType(val.target.value);
form.setFieldsValue({
[keyName]: {
attrType: undefined,
},
});
}}
>
<Radio value={'ol'}>有序列表</Radio>
<Radio value={'ul'}>无序列表</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label={'序列'} name={[keyName, 'attrType']} style={{ marginBottom: '16px' }}>
<Selects data={selectData} valKey="value" valName="label" />
</Form.Item>
<Form.List name={[keyName, 'list']}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => (
<Space
key={key}
style={{ display: 'flex', marginBottom: 8, justifyContent: 'space-around' }}
align="baseline"
>
<Form.Item {...restField} name={[name, 'text']} style={{ marginBottom: '16px' }}>
<Input />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} title="移除" />
</Space>
))}
<Form.Item style={{ marginBottom: '16px' }}>
<Button onClick={() => add()} block icon={<PlusOutlined />}>
添加数据
</Button>
</Form.Item>
</>
)}
</Form.List>
</>
);
},
)
Example #11
Source File: index.tsx From fe-v5 with Apache License 2.0 | 4 votes |
UserForm = React.forwardRef<ReactNode, UserAndPasswordFormProps>((props, ref) => {
const { t } = useTranslation();
const { userId } = props;
const [form] = Form.useForm();
const [initialValues, setInitialValues] = useState<User>();
const [loading, setLoading] = useState<boolean>(true);
const [contactsList, setContactsList] = useState<ContactsItem[]>([]);
const [roleList, setRoleList] = useState<{ name: string; note: string }[]>([]);
useImperativeHandle(ref, () => ({
form: form,
}));
useEffect(() => {
if (userId) {
getUserInfoDetail(userId);
} else {
setLoading(false);
}
getContacts();
getRoles().then((res) => setRoleList(res));
}, []);
const getContacts = () => {
getNotifyChannels().then((data: Array<ContactsItem>) => {
setContactsList(data);
});
};
const getUserInfoDetail = (id: string) => {
getUserInfo(id).then((data: User) => {
let contacts: Array<Contacts> = [];
if (data.contacts) {
Object.keys(data.contacts).forEach((item: string) => {
let val: Contacts = {
key: item,
value: data.contacts[item],
};
contacts.push(val);
});
}
setInitialValues(
Object.assign({}, data, {
contacts,
}),
);
setLoading(false);
});
};
return !loading ? (
<Form {...layout} form={form} initialValues={initialValues} preserve={false}>
{!userId && (
<Form.Item
label={t('用户名')}
name='username'
rules={[
{
required: true,
message: t('用户名不能为空!'),
},
]}
>
<Input />
</Form.Item>
)}
<Form.Item label={t('显示名')} name='nickname'>
<Input />
</Form.Item>
{!userId && (
<>
<Form.Item
name='password'
label={t('密码')}
rules={[
{
required: true,
message: t('请输入密码!'),
},
]}
hasFeedback
>
<Input.Password />
</Form.Item>
<Form.Item
name='confirm'
label={t('确认密码')}
dependencies={['password']}
hasFeedback
rules={[
{
required: true,
message: t('请确认密码!'),
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('密码不一致!'));
},
}),
]}
>
<Input.Password />
</Form.Item>
</>
)}
<Form.Item
label={t('角色')}
name='roles'
rules={[
{
required: true,
message: t('角色不能为空!'),
},
]}
>
<Select mode='multiple'>
{roleList.map((item, index) => (
<Option value={item.name} key={index}>
<div>
<div>{item.name}</div>
<div style={{ color: '#8c8c8c' }}>{item.note}</div>
</div>
</Option>
))}
</Select>
</Form.Item>
<Form.Item label={t('邮箱')} name='email'>
<Input />
</Form.Item>
<Form.Item label={t('手机')} name='phone'>
<Input />
</Form.Item>
<Form.Item label={t('更多联系方式')}>
<Form.List name='contacts'>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{
display: 'flex',
}}
align='baseline'
>
<Form.Item
style={{
width: '180px',
}}
{...restField}
name={[name, 'key']}
fieldKey={[fieldKey, 'key']}
rules={[
{
required: true,
message: t('联系方式不能为空'),
},
]}
>
<Select suffixIcon={<CaretDownOutlined />} placeholder={t('请选择联系方式')}>
{contactsList.map((item, index) => (
<Option value={item.key} key={index}>
{item.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
{...restField}
style={{
width: '170px',
}}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[
{
required: true,
message: t('值不能为空'),
},
]}
>
<Input placeholder={t('请输入值')} />
</Form.Item>
<MinusCircleOutlined className='control-icon-normal' onClick={() => remove(name)} />
</Space>
))}
<PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
</>
)}
</Form.List>
</Form.Item>
</Form>
) : null;
})
Example #12
Source File: Form.tsx From fe-v5 with Apache License 2.0 | 4 votes |
function FormCpt(props: ModalWrapProps & IProps) {
const { t } = useTranslation();
const { action, visible, initialValues, destroy, range, onOk, admin } = props;
const [form] = Form.useForm();
const [labels, setLabels] = useState<string[]>([]);
const [filteredLabels, setFilteredLabels] = useState<string[]>([]);
const [previewVisible, setPreviewVisible] = useState(false);
const [previewLoading, setPreviewLoading] = useState(false);
const [previewData, setPreviewData] = useState([]);
const [activeKey, setActiveKey] = useState('form');
const getLablesOptions = (_labels) => {
return _.map(_labels, (label) => {
return (
<Select.Option key={label} value={label}>
{label}
</Select.Option>
);
});
};
useEffect(() => {
getLabels('', range).then((res) => {
setLabels(res);
setFilteredLabels(res);
});
}, [JSON.stringify(range)]);
return (
<Modal
className='n9e-metric-views-modal'
title={
<Tabs className='custom-import-title' activeKey={activeKey} onChange={setActiveKey}>
<TabPane tab={titleMap[action]} key='form' />
{action === 'add' && <TabPane tab='导入快捷视图' key='import' />}
</Tabs>
}
visible={visible}
onCancel={() => {
destroy();
}}
onOk={() => {
form.validateFields().then((values) => {
let _values = _.cloneDeep(values);
if (activeKey === 'form') {
_values.dynamicLabels = _.map(_values.dynamicLabels, (item) => {
return {
label: item,
value: '',
};
});
_values.dimensionLabels = _.map(_values.dimensionLabels, (item) => {
return {
label: item,
value: '',
};
});
}
if (activeKey === 'import') {
try {
const config = JSON.parse(values.import);
_values = {
name: values.name,
cate: values.cate,
...config,
};
} catch (e) {
console.log(e);
return;
}
}
const { name, cate } = _values;
const configs = JSON.stringify(_.omit(_values, ['name', 'cate']));
const data: any = {
name,
cate: cate ? 0 : 1,
configs,
};
if (action === 'add') {
addMetricView(data).then((res) => {
message.success('添加成功');
onOk(res);
destroy();
});
} else if (action === 'edit') {
data.id = initialValues.id;
updateMetricView(data).then(() => {
message.success('修改成功');
onOk();
destroy();
});
}
});
}}
>
{activeKey === 'form' && (
<Form
layout='vertical'
initialValues={
initialValues || {
cate: false,
}
}
form={form}
onValuesChange={(changedValues, allValues) => {
if (changedValues.filters) {
const filtersStr = getFiltersStr(allValues.filters);
getLabels(`${filtersStr ? `{${filtersStr}}` : ''}`, range).then((res) => {
setFilteredLabels(res);
});
}
}}
>
<Form.Item label='视图名称' name='name' rules={[{ required: true }]}>
<Input />
</Form.Item>
{admin && (
<Form.Item label='是否公开' name='cate' rules={[{ required: true }]} valuePropName='checked'>
<Switch />
</Form.Item>
)}
<Form.List name='filters'>
{(fields, { add, remove }) => (
<>
<div style={{ paddingBottom: 8 }}>
前置过滤条件{' '}
<PlusCircleOutlined
onClick={() => {
add({
oper: '=',
});
}}
/>
</div>
{fields.map(({ key, name }) => {
return (
<Space key={key}>
<Form.Item name={[name, 'label']} rules={[{ required: true }]}>
<Select suffixIcon={<CaretDownOutlined />} allowClear showSearch style={{ width: 170 }}>
{getLablesOptions(labels)}
</Select>
</Form.Item>
<Form.Item name={[name, 'oper']} rules={[{ required: true }]}>
<Select suffixIcon={<CaretDownOutlined />} style={{ width: 60 }}>
<Select.Option value='='>=</Select.Option>
<Select.Option value='!='>!=</Select.Option>
<Select.Option value='=~'>=~</Select.Option>
<Select.Option value='!~'>!~</Select.Option>
</Select>
</Form.Item>
<Form.Item name={[name, 'value']} rules={[{ required: true }]}>
<Input style={{ width: 200 }} />
</Form.Item>
<Form.Item>
<MinusCircleOutlined
onClick={() => {
remove(name);
}}
/>
</Form.Item>
</Space>
);
})}
</>
)}
</Form.List>
<Form.Item label='动态过滤标签' name='dynamicLabels'>
<Select allowClear showSearch mode='multiple'>
{getLablesOptions(filteredLabels)}
</Select>
</Form.Item>
<Form.Item label='展开维度标签' name='dimensionLabels' rules={[{ required: true }]}>
<Select allowClear showSearch mode='multiple'>
{getLablesOptions(filteredLabels)}
</Select>
</Form.Item>
<div style={{ textAlign: 'right', marginBottom: 10 }}>
<Button
onClick={() => {
const values = form.getFieldsValue();
setPreviewVisible(true);
setPreviewLoading(true);
const filtersStr = getFiltersStr(values.filters);
const _labels = _.compact(_.concat(values.dynamicLabels, values.dimensionLabels));
const requests = _.map(_labels, (item) => {
return getLabelValues(item, range, filtersStr ? `{${filtersStr}}` : '');
});
Promise.all(requests).then((res) => {
const data = _.map(_labels, (item, idx) => {
return {
label: item,
values: res[idx],
};
});
setPreviewData(data);
setPreviewLoading(false);
});
}}
>
预览
</Button>
</div>
{previewVisible && (
<Table
size='small'
rowKey='label'
columns={[
{
title: 'Lable Key',
dataIndex: 'label',
},
{
title: 'Lable Value 数量',
dataIndex: 'values',
render: (text) => {
return text.length;
},
},
{
title: 'Lable Value 样例',
dataIndex: 'values',
render: (text) => {
return (
<Tooltip
placement='right'
title={
<div>
{_.map(text, (item) => {
return <div key={item}>{item}</div>;
})}
</div>
}
>{`${_.head(text)}...`}</Tooltip>
);
},
},
]}
dataSource={previewData}
loading={previewLoading}
/>
)}
</Form>
)}
{activeKey === 'import' && (
<Form
form={form}
preserve={false}
layout='vertical'
initialValues={
initialValues || {
cate: false,
}
}
>
<Form.Item label='视图名称' name='name' rules={[{ required: true }]}>
<Input />
</Form.Item>
{admin && (
<Form.Item label='是否公开' name='cate' rules={[{ required: true }]} valuePropName='checked'>
<Switch />
</Form.Item>
)}
<Form.Item
label='配置JSON:'
name='import'
rules={[
{
required: true,
message: t('请输入配置'),
validateTrigger: 'trigger',
},
]}
>
<Input.TextArea className='code-area' placeholder={t('请输入配置')} rows={4} />
</Form.Item>
</Form>
)}
</Modal>
);
}
Example #13
Source File: editModal.tsx From fe-v5 with Apache License 2.0 | 4 votes |
editModal: React.FC<Props> = ({ isModalVisible, editModalFinish }) => {
const { t, i18n } = useTranslation();
const [form] = Form.useForm();
const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);
const [contactList, setInitContactList] = useState([]);
const [notifyGroups, setNotifyGroups] = useState([]);
const [field, setField] = useState<string>('cluster');
const [refresh, setRefresh] = useState(true);
useEffect(() => {
getNotifyChannel();
getGroups('');
return () => {};
}, []);
const enableDaysOfWeekOptions = [t('周日'), t('周一'), t('周二'), t('周三'), t('周四'), t('周五'), t('周六')].map((v, i) => {
return <Option value={String(i)} key={i}>{`${v}`}</Option>;
});
const contactListCheckboxes = contactList.map((c: { key: string; label: string }) => (
<Checkbox value={c.key} key={c.label}>
{c.label}
</Checkbox>
));
const notifyGroupsOptions = notifyGroups.map((ng: any) => (
<Option value={ng.id} key={ng.id}>
{ng.name}
</Option>
));
const getNotifyChannel = async () => {
const res = await getNotifiesList();
let contactList = res || [];
setInitContactList(contactList);
};
const getGroups = async (str) => {
const res = await getTeamInfoList({ query: str });
const data = res.dat || res;
setNotifyGroups(data || []);
};
const debounceFetcher = useCallback(debounce(getGroups, 800), []);
const modelOk = () => {
form.validateFields().then(async (values) => {
const data = { ...values };
switch (values.field) {
case 'enable_time':
data.enable_stime = values.enable_time[0].format('HH:mm');
data.enable_etime = values.enable_time[1].format('HH:mm');
delete data.enable_time;
break;
case 'disabled':
data.disabled = !values.enable_status ? 1 : 0;
delete data.enable_status;
break;
case 'enable_in_bg':
data.enable_in_bg = values.enable_in_bg ? 1 : 0;
break;
case 'callbacks':
data.callbacks = values.callbacks.map((item) => item.url);
break;
case 'notify_recovered':
data.notify_recovered = values.notify_recovered ? 1 : 0;
break;
default:
break;
}
delete data.field;
Object.keys(data).forEach((key) => {
// 因为功能上有清除备注的需求,需要支持传空
if (data[key] === undefined) {
data[key] = '';
}
if (Array.isArray(data[key])) {
data[key] = data[key].join(' ');
}
});
editModalFinish(true, data);
});
};
const editModalClose = () => {
editModalFinish(false);
};
const fieldChange = (val) => {
setField(val);
};
return (
<>
<Modal
title={t('批量更新')}
visible={isModalVisible}
onOk={modelOk}
onCancel={() => {
editModalClose();
}}
>
<Form
{...layout}
form={form}
className='strategy-form'
layout={refresh ? 'horizontal' : 'horizontal'}
initialValues={{
prom_eval_interval: 15,
disabled: 0, // 0:立即启用 1:禁用
enable_status: true, // true:立即启用 false:禁用
notify_recovered: 1, // 1:启用
enable_time: [moment('00:00', 'HH:mm'), moment('23:59', 'HH:mm')],
cluster: clusterList[0] || 'Default', // 生效集群
enable_days_of_week: ['1', '2', '3', '4', '5', '6', '0'],
field: 'cluster',
}}
>
<Form.Item
label={t('字段:')}
name='field'
rules={[
{
required: false,
},
]}
>
<Select suffixIcon={<CaretDownOutlined />} style={{ width: '100%' }} onChange={fieldChange}>
{fields.map((item) => (
<Option key={item.id} value={item.field}>
{item.name}
</Option>
))}
</Select>
</Form.Item>
{(() => {
switch (field) {
case 'note':
return (
<>
<Form.Item
label={t('改为:')}
name='note'
rules={[
{
required: false,
},
]}
>
<Input placeholder={t('请输入规则备注')} />
</Form.Item>
</>
);
case 'runbook_url':
return (
<>
<Form.Item label={t('改为:')} name='runbook_url'>
<Input />
</Form.Item>
</>
);
case 'cluster':
return (
<>
<Form.Item
label={t('改为:')}
name='cluster'
rules={[
{
required: false,
message: t('生效集群不能为空'),
},
]}
>
<Select suffixIcon={<CaretDownOutlined />}>
{clusterList?.map((item) => (
<Option value={item} key={item}>
{item}
</Option>
))}
</Select>
</Form.Item>
</>
);
case 'severity':
return (
<>
<Form.Item
label={t('改为:')}
name='severity'
initialValue={2}
rules={[
{
required: false,
message: t('告警级别不能为空'),
},
]}
>
<Radio.Group>
<Radio value={1}>{t('一级报警')}</Radio>
<Radio value={2}>{t('二级报警')}</Radio>
<Radio value={3}>{t('三级报警')}</Radio>
</Radio.Group>
</Form.Item>
</>
);
case 'disabled':
return (
<>
<Form.Item
label={t('改为:')}
name='enable_status'
rules={[
{
required: false,
message: t('立即启用不能为空'),
},
]}
valuePropName='checked'
>
<Switch />
</Form.Item>
</>
);
case 'enable_in_bg':
return (
<>
<Form.Item label={t('改为:')} name='enable_in_bg' valuePropName='checked'>
<SwitchWithLabel label='根据告警事件中的ident归属关系判断' />
</Form.Item>
</>
);
case 'prom_eval_interval':
return (
<>
<Form.Item
label={t('改为:')}
rules={[
{
required: false,
message: t('执行频率不能为空'),
},
]}
>
<Space>
<Form.Item style={{ marginBottom: 0 }} name='prom_eval_interval' initialValue={15} wrapperCol={{ span: 10 }}>
<InputNumber
min={1}
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
秒
<Tooltip title={t(`每隔${form.getFieldValue('prom_eval_interval')}秒,把PromQL作为查询条件,去查询后端存储,如果查到了数据就表示当次有监控数据触发了规则`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
</>
);
case 'prom_for_duration':
return (
<>
<Form.Item
label={t('改为:')}
rules={[
{
required: false,
message: t('持续时长不能为空'),
},
]}
>
<Space>
<Form.Item style={{ marginBottom: 0 }} name='prom_for_duration' initialValue={60} wrapperCol={{ span: 10 }}>
<InputNumber min={0} />
</Form.Item>
秒
<Tooltip
title={t(
`通常持续时长大于执行频率,在持续时长内按照执行频率多次执行PromQL查询,每次都触发才生成告警;如果持续时长置为0,表示只要有一次PromQL查询触发阈值,就生成告警`,
)}
>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
</>
);
case 'notify_channels':
return (
<>
<Form.Item label={t('改为:')} name='notify_channels'>
<Checkbox.Group>{contactListCheckboxes}</Checkbox.Group>
</Form.Item>
</>
);
case 'notify_groups':
return (
<>
<Form.Item label={t('改为:')} name='notify_groups'>
<Select mode='multiple' showSearch optionFilterProp='children' filterOption={false} onSearch={(e) => debounceFetcher(e)} onBlur={() => getGroups('')}>
{notifyGroupsOptions}
</Select>
</Form.Item>
</>
);
case 'notify_recovered':
return (
<>
<Form.Item label={t('改为:')} name='notify_recovered' valuePropName='checked'>
<Switch />
</Form.Item>
</>
);
case 'recover_duration':
return (
<>
<Form.Item label={t('改为:')}>
<Space>
<Form.Item
style={{ marginBottom: 0 }}
name='recover_duration'
initialValue={0}
wrapperCol={{ span: 10 }}
rules={[
{
required: false,
message: t('留观时长不能为空'),
},
]}
>
<InputNumber
min={0}
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
秒
<Tooltip title={t(`持续${form.getFieldValue('recover_duration') || 0}秒没有再次触发阈值才发送恢复通知`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
</>
);
case 'notify_repeat_step':
return (
<>
<Form.Item label={t('改为:')}>
<Space>
<Form.Item
style={{ marginBottom: 0 }}
name='notify_repeat_step'
initialValue={60}
wrapperCol={{ span: 10 }}
rules={[
{
required: false,
message: t('重复发送频率不能为空'),
},
]}
>
<InputNumber
min={0}
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
分钟
<Tooltip title={t(`如果告警持续未恢复,间隔${form.getFieldValue('notify_repeat_step')}分钟之后重复提醒告警接收组的成员`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
</>
);
case 'callbacks':
return (
<>
<Form.Item label={t('改为:')}>
<Form.List name='callbacks' initialValue={[{}]}>
{(fields, { add, remove }, { errors }) => (
<>
{fields.map((field, index) => (
<Row gutter={[10, 0]} key={field.key}>
<Col span={22}>
<Form.Item name={[field.name, 'url']}>
<Input />
</Form.Item>
</Col>
<Col span={1}>
<MinusCircleOutlined className='control-icon-normal' onClick={() => remove(field.name)} />
</Col>
</Row>
))}
<PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
</>
)}
</Form.List>
</Form.Item>
</>
);
case 'append_tags':
return (
<>
<Form.Item label='附加标签' name='append_tags' rules={[{ required: false, message: '请填写至少一项标签!' }, isValidFormat]}>
<Select mode='tags' tokenSeparators={[' ']} open={false} placeholder={'标签格式为 key=value ,使用回车或空格分隔'} tagRender={tagRender} />
</Form.Item>
</>
);
case 'enable_time':
return (
<>
<Form.Item
label={t('改为:')}
name='enable_days_of_week'
rules={[
{
required: false,
message: t('生效时间不能为空'),
},
]}
>
<Select mode='tags'>{enableDaysOfWeekOptions}</Select>
</Form.Item>
<Form.Item
name='enable_time'
{...tailLayout}
rules={[
{
required: false,
message: t('生效时间不能为空'),
},
]}
>
<TimePicker.RangePicker
format='HH:mm'
onChange={(val, val2) => {
form.setFieldsValue({
enable_stime: val2[0],
enable_etime: val2[1],
});
}}
/>
</Form.Item>
</>
);
default:
return null;
}
})()}
</Form>
</Modal>
</>
);
}
Example #14
Source File: operateForm.tsx From fe-v5 with Apache License 2.0 | 4 votes |
operateForm: React.FC<Props> = ({ type, detail = {} }) => {
const { t, i18n } = useTranslation();
const history = useHistory(); // 创建的时候默认选中的值
const [form] = Form.useForm();
const { clusters: clusterList } = useSelector<RootState, CommonStoreState>((state) => state.common);
const { curBusiItem } = useSelector<RootState, CommonStoreState>((state) => state.common);
const [contactList, setInitContactList] = useState([]);
const [notifyGroups, setNotifyGroups] = useState<any[]>([]);
const [initVal, setInitVal] = useState<any>({});
const [refresh, setRefresh] = useState(true);
useEffect(() => {
getNotifyChannel();
getGroups('');
return () => {};
}, []);
useEffect(() => {
const data = {
...detail,
enable_time: detail?.enable_stime ? [detail.enable_stime, detail.enable_etime] : [],
enable_status: detail?.disabled === undefined ? true : !detail?.disabled,
};
setInitVal(data);
if (type == 1) {
const groups = (detail.notify_groups_obj ? detail.notify_groups_obj.filter((item) => !notifyGroups.find((i) => item.id === i.id)) : []).concat(notifyGroups);
setNotifyGroups(groups);
}
}, [JSON.stringify(detail)]);
const enableDaysOfWeekOptions = [t('周日'), t('周一'), t('周二'), t('周三'), t('周四'), t('周五'), t('周六')].map((v, i) => {
return <Option value={String(i)} key={i}>{`${v}`}</Option>;
});
const contactListCheckboxes = contactList.map((c: { key: string; label: string }) => (
<Checkbox value={c.key} key={c.label}>
{c.label}
</Checkbox>
));
const notifyGroupsOptions = notifyGroups.map((ng: any) => (
<Option value={String(ng.id)} key={ng.id}>
{ng.name}
</Option>
));
const getNotifyChannel = async () => {
const res = await getNotifiesList();
let contactList = res || [];
setInitContactList(contactList);
};
const getGroups = async (str) => {
const res = await getTeamInfoList({ query: str });
const data = res.dat || res;
const combineData = (detail.notify_groups_obj ? detail.notify_groups_obj.filter((item) => !data.find((i) => item.id === i.id)) : []).concat(data);
setNotifyGroups(combineData || []);
};
const addSubmit = () => {
form.validateFields().then(async (values) => {
const res = await prometheusQuery({ query: values.prom_ql }, values.cluster);
if (res.error) {
notification.error({
message: res.error,
});
return false;
}
const callbacks = values.callbacks.map((item) => item.url);
const data = {
...values,
enable_stime: values.enable_time[0].format('HH:mm'),
enable_etime: values.enable_time[1].format('HH:mm'),
disabled: !values.enable_status ? 1 : 0,
notify_recovered: values.notify_recovered ? 1 : 0,
enable_in_bg: values.enable_in_bg ? 1 : 0,
callbacks,
};
let reqBody,
method = 'Post';
if (type === 1) {
reqBody = data;
method = 'Put';
const res = await EditStrategy(reqBody, curBusiItem.id, detail.id);
if (res.err) {
message.error(res.error);
} else {
message.success(t('编辑成功!'));
history.push('/alert-rules');
}
} else {
reqBody = [data];
const { dat } = await addOrEditStrategy(reqBody, curBusiItem.id, method);
let errorNum = 0;
const msg = Object.keys(dat).map((key) => {
dat[key] && errorNum++;
return dat[key];
});
if (!errorNum) {
message.success(`${type === 2 ? t('告警规则克隆成功') : t('告警规则创建成功')}`);
history.push('/alert-rules');
} else {
message.error(t(msg));
}
}
});
};
const debounceFetcher = useCallback(debounce(getGroups, 800), []);
return (
<div className='operate_con'>
<Form
{...layout}
form={form}
className='strategy-form'
layout={refresh ? 'horizontal' : 'horizontal'}
initialValues={{
prom_eval_interval: 15,
prom_for_duration: 60,
severity: 2,
disabled: 0, // 0:立即启用 1:禁用 待修改
// notify_recovered: 1, // 1:启用
cluster: clusterList[0] || 'Default', // 生效集群
enable_days_of_week: ['1', '2', '3', '4', '5', '6', '0'],
...detail,
enable_in_bg: detail?.enable_in_bg === 1,
enable_time: detail?.enable_stime ? [moment(detail.enable_stime, 'HH:mm'), moment(detail.enable_etime, 'HH:mm')] : [moment('00:00', 'HH:mm'), moment('23:59', 'HH:mm')],
enable_status: detail?.disabled === undefined ? true : !detail?.disabled,
notify_recovered: detail?.notify_recovered === 1 || detail?.notify_recovered === undefined ? true : false, // 1:启用 0:禁用
recover_duration: detail?.recover_duration || 0,
callbacks: !!detail?.callbacks
? detail.callbacks.map((item) => ({
url: item,
}))
: [{}],
}}
>
<Space direction='vertical' style={{ width: '100%' }}>
<Card title={t('基本配置')}>
<Form.Item
label={t('规则标题:')}
name='name'
rules={[
{
required: true,
message: t('规则标题不能为空'),
},
]}
>
<Input placeholder={t('请输入规则标题')} />
</Form.Item>
<Form.Item
label={t('规则备注:')}
name='note'
rules={[
{
required: false,
},
]}
>
<Input placeholder={t('请输入规则备注')} />
</Form.Item>
<Form.Item
label={t('告警级别')}
name='severity'
rules={[
{
required: true,
message: t('告警级别不能为空'),
},
]}
>
<Radio.Group>
<Radio value={1}>{t('一级报警')}</Radio>
<Radio value={2}>{t('二级报警')}</Radio>
<Radio value={3}>{t('三级报警')}</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label={t('生效集群')}
name='cluster'
rules={[
{
required: true,
message: t('生效集群不能为空'),
},
]}
>
<Select suffixIcon={<CaretDownOutlined />}>
{clusterList?.map((item) => (
<Option value={item} key={item}>
{item}
</Option>
))}
</Select>
</Form.Item>
<AdvancedWrap>
<AbnormalDetection form={form} />
</AdvancedWrap>
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.cluster !== curValues.cluster}>
{() => {
return (
<Form.Item label='PromQL' className={'Promeql-content'} required>
<Form.Item name='prom_ql' validateTrigger={['onBlur']} trigger='onChange' rules={[{ required: true, message: t('请输入PromQL') }]}>
<PromQLInput
url='/api/n9e/prometheus'
headers={{
'X-Cluster': form.getFieldValue('cluster'),
Authorization: `Bearer ${localStorage.getItem('access_token') || ''}`,
}}
/>
</Form.Item>
</Form.Item>
);
}}
</Form.Item>
<Form.Item required label={t('执行频率')}>
<Space>
<Form.Item
style={{ marginBottom: 0 }}
name='prom_eval_interval'
initialValue={15}
wrapperCol={{ span: 10 }}
rules={[
{
required: true,
message: t('执行频率不能为空'),
},
]}
>
<InputNumber
min={1}
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
秒
<Tooltip title={t(`每隔${form.getFieldValue('prom_eval_interval')}秒,把PromQL作为查询条件,去查询后端存储,如果查到了数据就表示当次有监控数据触发了规则`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
<Form.Item
required
label={t('持续时长')}
rules={[
{
required: true,
message: t('持续时长不能为空'),
},
]}
>
<Space>
<Form.Item style={{ marginBottom: 0 }} name='prom_for_duration' wrapperCol={{ span: 10 }}>
<InputNumber min={0} />
</Form.Item>
秒
<Tooltip
title={t(
`通常持续时长大于执行频率,在持续时长内按照执行频率多次执行PromQL查询,每次都触发才生成告警;如果持续时长置为0,表示只要有一次PromQL查询触发阈值,就生成告警`,
)}
>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
<Form.Item label='附加标签' name='append_tags' rules={[{ required: false, message: '请填写至少一项标签!' }, isValidFormat]}>
<Select mode='tags' tokenSeparators={[' ']} open={false} placeholder={'标签格式为 key=value ,使用回车或空格分隔'} tagRender={tagRender} />
</Form.Item>
<Form.Item label={t('预案链接')} name='runbook_url'>
<Input />
</Form.Item>
</Card>
<Card title={t('生效配置')}>
<Form.Item
label={t('立即启用')}
name='enable_status'
rules={[
{
required: true,
message: t('立即启用不能为空'),
},
]}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label={t('生效时间')}
name='enable_days_of_week'
rules={[
{
required: true,
message: t('生效时间不能为空'),
},
]}
>
<Select mode='tags'>{enableDaysOfWeekOptions}</Select>
</Form.Item>
<Form.Item
name='enable_time'
{...tailLayout}
rules={[
{
required: true,
message: t('生效时间不能为空'),
},
]}
>
<TimePicker.RangePicker
format='HH:mm'
onChange={(val, val2) => {
form.setFieldsValue({
enable_stime: val2[0],
enable_etime: val2[1],
});
}}
/>
</Form.Item>
<Form.Item label={t('仅在本业务组生效')} name='enable_in_bg' valuePropName='checked'>
<SwitchWithLabel label='根据告警事件中的ident归属关系判断' />
</Form.Item>
</Card>
<Card title={t('通知配置')}>
<Form.Item label={t('通知媒介')} name='notify_channels'>
<Checkbox.Group>{contactListCheckboxes}</Checkbox.Group>
</Form.Item>
<Form.Item label={t('告警接收组')} name='notify_groups'>
<Select mode='multiple' showSearch optionFilterProp='children' filterOption={false} onSearch={(e) => debounceFetcher(e)} onBlur={() => getGroups('')}>
{notifyGroupsOptions}
</Select>
</Form.Item>
<Form.Item label={t('启用恢复通知')}>
<Space>
<Form.Item name='notify_recovered' valuePropName='checked' style={{ marginBottom: 0 }}>
<Switch />
</Form.Item>
<Tooltip title={t(`告警恢复时也发送通知`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
<Form.Item label={t('留观时长')} required>
<Space>
<Form.Item style={{ marginBottom: 0 }} name='recover_duration' initialValue={0} wrapperCol={{ span: 10 }}>
<InputNumber
min={0}
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
秒
<Tooltip title={t(`持续${form.getFieldValue('recover_duration')}秒没有再次触发阈值才发送恢复通知`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
<Form.Item label={t('重复发送频率')} required>
<Space>
<Form.Item
style={{ marginBottom: 0 }}
name='notify_repeat_step'
initialValue={60}
wrapperCol={{ span: 10 }}
rules={[
{
required: true,
message: t('重复发送频率不能为空'),
},
]}
>
<InputNumber
min={0}
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
分钟
<Tooltip title={t(`如果告警持续未恢复,间隔${form.getFieldValue('notify_repeat_step')}分钟之后重复提醒告警接收组的成员`)}>
<QuestionCircleFilled />
</Tooltip>
</Space>
</Form.Item>
<Form.Item label={t('回调地址')}>
<Form.List name='callbacks' initialValue={[{}]}>
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Row gutter={[10, 0]} key={field.key}>
<Col span={22}>
<Form.Item name={[field.name, 'url']} fieldKey={[field.fieldKey, 'url']}>
<Input />
</Form.Item>
</Col>
<Col span={1}>
<MinusCircleOutlined className='control-icon-normal' onClick={() => remove(field.name)} />
</Col>
</Row>
))}
<PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
</>
)}
</Form.List>
</Form.Item>
</Card>
<Form.Item
// {...tailLayout}
style={{
marginTop: 20,
}}
>
<Button type='primary' onClick={addSubmit} style={{ marginRight: '8px' }}>
{type === 1 ? t('编辑') : type === 2 ? t('克隆') : t('创建')}
</Button>
{type === 1 && (
<Button
danger
style={{ marginRight: '8px' }}
onClick={() => {
Modal.confirm({
title: t('是否删除该告警规则?'),
onOk: () => {
deleteStrategy([detail.id], curBusiItem.id).then(() => {
message.success(t('删除成功'));
history.push('/alert-rules');
});
},
onCancel() {},
});
}}
>
{t('删除')}
</Button>
)}
<Button
onClick={() => {
history.push('/alert-rules');
}}
>
{t('取消')}
</Button>
</Form.Item>
</Space>
</Form>
</div>
);
}
Example #15
Source File: ContactSyncedAutocompleteFields.tsx From condo with MIT License | 4 votes |
ContactSyncedAutocompleteFields: React.FC<IContactSyncedAutocompleteFieldsProps> = ({ refetch, initialQuery, initialValue, onChange, onChecked, checked, contacts, displayMinusButton, onClickMinusButton }) => {
const intl = useIntl()
const NamePlaceholder = intl.formatMessage({ id: 'contact.Contact.ContactsEditor.Name.placeholder' })
const [value, setValue] = useState(initialValue)
const searchSuggestions = useCallback(
async (query) => {
return refetch({
where: { ...initialQuery, ...query },
})
},
[initialQuery, refetch],
)
const debouncedSearch = useMemo(
() => {
return debounce(searchSuggestions, DEBOUNCE_TIMEOUT_IN_MS)
},
[searchSuggestions],
)
const searchContactByPhone = useCallback(async (query) => {
const contactName = get(value, 'name', undefined)
await debouncedSearch({
phone_contains_i: query,
name_contains_i: contactName,
})
}, [debouncedSearch, value])
const searchContactByName = useCallback(async (query) => {
const contactPhone = get(value, 'phone', undefined)
await debouncedSearch({
name_contains_i: query,
phone_contains_i: contactPhone,
})
}, [debouncedSearch, value])
const handleSelectContact = (value: string, option: OptionProps) => {
setValueAndTriggerOnChange(option.item)
}
const handleChangeContact = (field) => (fieldValue) => {
const newValue = {
...value,
[field]: fieldValue,
}
setValueAndTriggerOnChange(newValue)
}
const setValueAndTriggerOnChange = (contact) => {
setValue(contact)
onChange(contact)
}
const handleClearContact = () => {
setValue(null)
}
const handleChecked = () => {
onChecked && onChecked()
}
const renderOptionsBy = useCallback((prop) =>
contacts.map(contact => ({
value: contact[prop],
item: contact,
}))
, [contacts])
const phoneOptions = useMemo(() => renderOptionsBy('phone'), [renderOptionsBy])
const nameOptions = useMemo(() => renderOptionsBy('name'), [renderOptionsBy])
return (
<>
<Col span={10}>
<AutoComplete
allowClear
value={get(value, 'phone')}
options={phoneOptions}
onSelect={handleSelectContact}
onSearch={searchContactByPhone}
onChange={handleChangeContact('phone')}
onClear={handleClearContact}
style={{ width: '100%' }}
>
<PhoneInput
block
compatibilityWithAntAutoComplete={true}
/>
</AutoComplete>
</Col>
<Col span={10}>
<AutoComplete
allowClear
placeholder={NamePlaceholder}
value={get(value, 'name')}
options={nameOptions}
onSelect={handleSelectContact}
onSearch={searchContactByName}
onChange={handleChangeContact('name')}
onClear={handleClearContact}
style={{ width: '100%' }}
/>
</Col>
<Col span={2}>
{onChecked && (
<Radio
onClick={handleChecked}
checked={checked}
style={{ marginTop: '8px' }}
/>
)}
</Col>
<Col span={2}>
{displayMinusButton && (
<MinusCircleOutlined
style={{
color: colors.black,
fontSize: '21px',
marginTop: '9px',
marginLeft: '-4px',
}}
onClick={onClickMinusButton}
/>
)}
</Col>
</>
)
}
Example #16
Source File: index.tsx From dashboard with Apache License 2.0 | 4 votes |
AutoReply: React.FC<AutoReplyProps> = (props) => {
const {welcomeMsg, setWelcomeMsg, isFetchDone} = props;
const [modalVisible, setModalVisible] = useState(false);
const [attachments, setAttachments] = useState<Attachment[]>([]);
const [currentIndex, setCurrentIndex] = useState<number>(0);
const [currentMode, setCurrentMode] = useState<MsgType>('image');
const [linkFetching, setLinkFetching] = useState(false);
const [content, setContent] = useState('');
const contentRef = useRef<React.RefObject<HTMLElement>>();
const imageModalFormRef = useRef<FormInstance>();
const linkModalFormRef = useRef<FormInstance>();
const miniAppModalFormRef = useRef<FormInstance>();
const UploadFileFn = async (req: UploadRequestOption, ref: MutableRefObject<any | undefined>, inputName: string) => {
const file = req.file as File;
if (!file.name) {
message.error('非法参数');
return;
}
const hide = message.loading('上传中');
try {
const res = await GetSignedURL(file.name)
const data = res.data as GetSignedURLResult
if (res.code === 0) {
const uploadRes = (await fetch(data.upload_url, {
method: 'PUT',
body: file
}));
hide();
if (uploadRes.ok && ref) {
ref.current?.setFieldsValue({[inputName]: data.download_url});
return;
}
message.error('上传图片失败');
return;
}
hide();
message.error('获取上传地址失败');
return;
} catch (e) {
message.error('上传图片失败');
console.log(e);
}
};
useEffect(() => {
const formData = itemDataToFormData(welcomeMsg);
setAttachments(formData.attachments || []);
setContent(formData.text || '');
}, [isFetchDone]);
useEffect(() => {
setWelcomeMsg({
text: content || '',
attachments: attachments || [],
});
}, [content, attachments]);
return (
<>
<div className={styles.replyEditor}>
<div className={'preview-container'}>
<div className={styles.replyEditorPreview}>
<img src={phoneImage} className='bg'/>
<div className='content'>
<ul className='reply-list'>
{content && (
<li><img
src={avatarDefault}/>
<div className='msg text' dangerouslySetInnerHTML={{__html: content}}/>
</li>
)}
{attachments && attachments.length > 0 && (
attachments.map((attachment) => {
if (attachment.msgtype === 'image') {
return (
<li key={attachment.id}>
<img src={avatarDefault}/>
<div className={`msg image`}>
<img src={attachment.image?.pic_url}/>
</div>
</li>
);
}
if (attachment.msgtype === 'link') {
return (
<li key={attachment.id}>
<img src={avatarDefault}/>
<div className='msg link'><p className='title'>{attachment.link?.title}</p>
<div className='link-inner'><p
className='desc'>{attachment.link?.desc}</p>
<img src={attachment.link?.picurl}/>
</div>
</div>
</li>
);
}
if (attachment.msgtype === 'miniprogram') {
return (
<li key={attachment.id}>
<img src={avatarDefault}/>
<div className='msg miniprogram'>
<p className='m-title'>
<IconFont
type={'icon-weixin-mini-app'}
style={{marginRight: 4, fontSize: 14}}
/>
{attachment.miniprogram?.title}
</p>
<img src={attachment.miniprogram?.pic_media_id}/>
<p className='l-title'>
<IconFont type={'icon-weixin-mini-app'} style={{marginRight: 4}}/>
小程序
</p>
</div>
</li>
);
}
return '';
})
)}
</ul>
</div>
</div>
</div>
<div className='text-area-container'>
<div className={styles.msgTextareaContainer} style={{border: 'none'}}>
{props.enableQuickInsert && (
<div className='insert-btn '>
<span
className='clickable no-select'
onClick={() => {
setContent(`${content}[客户昵称]`);
}}
>[插入客户昵称]</span>
</div>
)}
<div className='textarea-container '>
<ContentEditable
// @ts-ignore
innerRef={contentRef}
onKeyDown={(event) => {
if (event.key === 'Enter') {
document.execCommand('insertLineBreak');
event.preventDefault();
}
}}
className={'textarea'}
html={content}
onChange={(e) => {
setContent(e.target.value);
}}/>
<div className='flex-row align-side'>
<p className='text-cnt'>{content.length}/600</p>
</div>
</div>
</div>
</div>
<div className='option-area-container'>
{attachments && attachments.length > 0 && (
<ReactSortable handle={'.draggable-button'} tag='ul' className={'select-msg-options'} list={attachments} setList={setAttachments}>
{attachments.map((attachment, index) => (
<li key={attachment.id} className='flex-row'>
<span>
<MinusCircleOutlined
onClick={() => {
const items = [...attachments];
items.splice(index, 1);
setAttachments(items);
}}
/>
【{msgTypes[attachment.msgtype]}】:
<span
className='col-1'>{attachment?.name}</span>
</span>
<span className='d-action-container'>
<EditOutlined
onClick={() => {
setCurrentMode(attachment.msgtype);
imageModalFormRef.current?.setFieldsValue(attachment.image);
linkModalFormRef.current?.setFieldsValue(attachment.link);
miniAppModalFormRef.current?.setFieldsValue(attachment.miniprogram);
setCurrentIndex(index);
setModalVisible(true);
}}
/>
<DragOutlined
className={'draggable-button'}
style={{cursor: 'grabbing'}}
/>
</span>
</li>
))}
</ReactSortable>
)}
<div className='option-container'>
<Dropdown
placement='topLeft'
trigger={['click']}
overlay={(
<Menu style={{minWidth: 120}}>
<Menu.Item
key={'image'}
icon={<FileImageOutlined/>}
onClick={() => {
setCurrentMode('image');
setCurrentIndex(attachments.length);
imageModalFormRef.current?.resetFields();
setModalVisible(true);
}}
>
图片
</Menu.Item>
<Menu.Item
key={'link'}
icon={<LinkOutlined/>}
onClick={() => {
setCurrentMode('link');
setCurrentIndex(attachments.length);
setModalVisible(true);
}}
>
链接
</Menu.Item>
<Menu.Item
key={'miniApp'}
icon={<IconFont type={'icon-weixin-mini-app'}/>}
onClick={() => {
setCurrentMode('miniprogram');
setCurrentIndex(attachments.length);
setModalVisible(true);
}}
>
小程序
</Menu.Item>
</Menu>
)}
>
<a className='ant-dropdown-link' onClick={e => e.preventDefault()}>
<PlusCircleOutlined/> 添加附件
</a>
</Dropdown>
</div>
</div>
</div>
<ModalForm
formRef={imageModalFormRef}
className={'dialog from-item-label-100w'}
layout={'horizontal'}
width={'560px'}
visible={currentMode === 'image' && modalVisible}
onVisibleChange={setModalVisible}
onFinish={async (params: { title: string, pic_url: string, msgtype: MsgType }) => {
attachments[currentIndex] = {
id: new Date().getTime().toString(),
msgtype: params.msgtype,
name: params.title,
image: {...params},
};
setAttachments(attachments);
return true;
}}
>
<h2 className='dialog-title'> 添加图片附件 </h2>
<ProForm.Item initialValue={'image'} name={'msgtype'} noStyle={true}>
<input type={'hidden'}/>
</ProForm.Item>
<ProFormText
name='title'
label='图片名称'
placeholder={'请输入图片名称'}
width='md'
rules={[
{
required: true,
message: '请输入图片名称!',
},
]}
/>
<Form.Item
label='上传图片'
name='pic_url'
rules={[
{
required: true,
message: '请上传图片!',
},
]}
>
<ImageUploader
customRequest={async (req) => {
await UploadFileFn(req, imageModalFormRef, 'pic_url')
}}
/>
</Form.Item>
</ModalForm>
<ModalForm
formRef={linkModalFormRef}
className={'dialog from-item-label-100w'}
layout={'horizontal'}
width={'560px'}
visible={currentMode === 'link' && modalVisible}
onVisibleChange={setModalVisible}
onFinish={async (params) => {
attachments[currentIndex] = {
id: new Date().getTime().toString(),
msgtype: params.msgtype,
name: params.title,
// @ts-ignore
link: {...params},
};
setAttachments(attachments);
return true;
}}
>
<Spin spinning={linkFetching}>
<h2 className='dialog-title'> 添加链接附件 </h2>
<ProForm.Item initialValue={'link'} name={'msgtype'} noStyle={true}>
<input type={'hidden'}/>
</ProForm.Item>
<ProFormText
name='url'
label='链接地址'
width='md'
fieldProps={{
disabled: linkFetching,
addonAfter: (
<Tooltip title="点击抓取远程链接,自动填充标题,描述,图片">
<div
onClick={async () => {
setLinkFetching(true);
const res = await ParseURL(linkModalFormRef.current?.getFieldValue('url'))
setLinkFetching(false);
if (res.code !== 0) {
message.error(res.message);
} else {
message.success('解析链接成功');
linkModalFormRef?.current?.setFieldsValue({
customer_link_enable: 1,
title: res.data.title,
desc: res.data.desc,
picurl: res.data.img_url,
})
}
}}
style={{
cursor: "pointer",
width: 32,
height: 30,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<SyncOutlined/>
</div>
</Tooltip>
)
}}
rules={[
{
required: true,
message: '请输入链接地址',
},
{
type: 'url',
message: '请填写正确的的URL,必须是http或https开头',
},
]}
/>
<ProFormSwitch
label={'高级设置'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='customer_link_enable'
tooltip={'开启后可以自定义链接所有信息'}
/>
<ProFormDependency name={['customer_link_enable']}>
{({customer_link_enable}) => {
if (customer_link_enable) {
return (
<>
<ProFormText
name='title'
label='链接标题'
width='md'
rules={[
{
required: true,
message: '请输入链接标题',
},
]}
/>
<ProFormTextArea
name='desc'
label='链接描述'
width='md'
/>
<Form.Item
label='链接封面'
name='picurl'
rules={[
{
required: true,
message: '请上传链接图片!',
},
]}
>
<ImageUploader
customRequest={async (req) => {
await UploadFileFn(req, linkModalFormRef, 'picurl')
}}
/>
</Form.Item>
</>
);
}
return <></>;
}}
</ProFormDependency>
</Spin>
</ModalForm>
<ModalForm
formRef={miniAppModalFormRef}
className={'dialog from-item-label-100w'}
layout={'horizontal'}
width={'560px'}
labelCol={{
md: 6,
}}
visible={currentMode === 'miniprogram' && modalVisible}
onVisibleChange={setModalVisible}
onFinish={async (params) => {
attachments[currentIndex] = {
id: new Date().getTime().toString(),
msgtype: params.msgtype,
name: params.title,
// @ts-ignore
miniprogram: {...params},
};
setAttachments(attachments);
return true;
}}
>
<h2 className='dialog-title'> 添加小程序附件 </h2>
<Alert
showIcon={true}
type='info'
message={
'请填写企业微信后台绑定的小程序id和路径,否则会造成发送失败'
}
style={{marginBottom: 20}}
/>
<ProForm.Item initialValue={'miniprogram'} name={'msgtype'} noStyle={true}>
<input type={'hidden'}/>
</ProForm.Item>
<ProFormText
name='title'
label='小程序标题'
width='md'
rules={[
{
required: true,
message: '请输入链接标题',
},
]}
/>
<ProFormText
// 帮助指引
name='app_id'
label='小程序AppID'
width='md'
rules={[
{
required: true,
message: '请输入小程序AppID',
},
]}
/>
<ProFormText
name='page'
label='小程序路径'
width='md'
rules={[
{
required: true,
message: '请输入小程序路径',
},
]}
/>
<Form.Item
label='小程序封面'
name='pic_media_id'
rules={[
{
required: true,
message: '请小程序封面!',
},
]}
>
<ImageUploader
customRequest={async (req) => {
await UploadFileFn(req, miniAppModalFormRef, 'pic_media_id')
}}
/>
</Form.Item>
</ModalForm>
</>
);
}
Example #17
Source File: index.tsx From surveyo with Apache License 2.0 | 4 votes |
function SingleChoiceQuestionField({question, updateQuestion, options}: any) {
return (
<Form name="dynamic_form_item" {...formItemLayoutWithOutLabel}>
<Form.List name="names">
{(fields, {add, remove}) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item
{...(index === 0
? formItemLayout
: formItemLayoutWithOutLabel)}
label={index === 0 ? 'Options' : ''}
required={false}
key={field.key}
>
<div>
<Row>
<Col span={16}>
<div>
<Radio style={radioStyle} value={1}>
<Form.Item
{...field}
validateTrigger={['onChange', 'onBlur']}
rules={[
{
required: true,
whitespace: true,
message: 'Please input option',
},
]}
noStyle
>
<Input
placeholder="Please input option"
style={{width: '60%'}}
value={options[index]}
onChange={e => {
let newOptions = [...options];
newOptions[index] = e.target.value;
updateQuestion({
...question,
options: newOptions,
});
}}
/>
</Form.Item>
</Radio>
</div>
</Col>
<Col span={8}>
<div>
{fields.length > 1 ? (
<MinusCircleOutlined
className="dynamic-delete-button"
style={{margin: '0 8px'}}
onClick={() => {
remove(field.name);
let newOptions = update(options, {
$splice: [[field.name, 1]],
});
updateQuestion({
...question,
options: newOptions,
});
}}
/>
) : null}
</div>
</Col>
</Row>
</div>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
updateQuestion({
...question,
options: [...options, ''],
});
}}
style={{width: '60%'}}
>
<PlusOutlined /> Add option
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</Form>
);
}
Example #18
Source File: CreateNewUserForm.tsx From wildduck-ui with MIT License | 4 votes |
CreateNewUserForm: React.FC = () => {
const { mutate, isSuccess, data } = useCreateUser();
const history = useHistory();
if (isSuccess && !_.get(data, 'data.error')) {
history.push(`${getBasePath()}/users`);
}
const [form] = Form.useForm();
const onFinish = (values: any) => {
const mailboxes = {};
const metaData = {};
const userDetails: any = _.reduce(
values,
(acc, value, index) => {
if (!_.isEmpty(value) || value === false || value === true) {
if (index === 'sent' || index === 'junk' || index === 'drafts' || index === 'trash') {
_.set(mailboxes, `${index}`, value);
} else if (index === 'metaData') {
value.forEach((data: any) => {
_.set(metaData, `${data.key}`, data.value);
});
} else {
_.set(acc, `${index}`, value);
}
}
return acc;
},
{},
);
_.set(userDetails, 'mailboxes', mailboxes);
_.set(userDetails, 'metaData', metaData);
mutate(userDetails);
};
const handleReset = () => {
form.resetFields();
};
return (
<Form
form={form}
name='basic'
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
initialValues={{
remember: true,
emptyAddress: false,
hashedPassword: false,
allowUnsafe: false,
requirePasswordChange: false,
addTagsToAddress: false,
uploadSentMessages: false,
encryptMessages: false,
encryptForwarded: false,
spamLevel: 50,
sent: 'Sent Mail',
junk: 'Junk',
drafts: 'Draft',
trash: 'Trash',
}}
onFinish={onFinish}
>
<Row>
<Col offset={20}>
<Form.Item>
<Space size='middle'>
<Button type='primary' htmlType='submit'>
Create
</Button>
<Button type='default' htmlType='button' onClick={handleReset}>
Reset
</Button>
</Space>
</Form.Item>
</Col>
</Row>
<Row style={{ paddingTop: 20 }}>
<Col span={10}>
<Form.Item
label='Username'
name='username'
rules={[
{
required: true,
message: 'Please input your username!',
},
]}
tooltip={userTooltip.username}
>
<Input />
</Form.Item>
<Form.Item
label='Name'
name='name'
rules={[
{
required: true,
message: 'Please input your name!',
},
]}
tooltip={userTooltip.name}
>
<Input />
</Form.Item>
<Form.Item
label='Email address'
name='address'
rules={[
{
required: false,
message: 'Please input your address!',
},
]}
tooltip={userTooltip.address}
>
<Input />
</Form.Item>
<Form.Item
label='Password'
name='password'
rules={[
{
required: true,
message: 'Please input your password!',
},
]}
>
<Input.Password minLength={6} />
</Form.Item>
<Form.Item
label='Empty Address'
name='emptyAddress'
tooltip={userTooltip.emptyAddress}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label='Hashed Password'
name='hashedPassword'
tooltip={userTooltip.hashedPassword}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label='Required Password Change'
name='requirePasswordChange'
tooltip={userTooltip.requirePasswordChange}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label='Allow Unsafe'
name='allowUnsafe'
tooltip={userTooltip.allowUnsafe}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Tags' name='tags' tooltip={userTooltip.tags}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Enter Tags' />
</Form.Item>
<Form.Item
label='Add Tags to Address'
name='addTagsToAddress'
tooltip={userTooltip.addTagsToAddress}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Retention' name='retention' tooltip={userTooltip.retention}>
<Input />
</Form.Item>
<Form.Item
label='Upload sent messages'
name='uploadSentMessages'
tooltip={userTooltip.uploadSentMessages}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Public PGP Key' name='pubKey' tooltip={userTooltip.pubKey}>
<Input />
</Form.Item>
<Form.Item
label='Encrypt Messages'
name='encryptMessages'
tooltip={userTooltip.encryptMessages}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label='Encrypt Forwarded'
name='encryptForwarded'
tooltip={userTooltip.encryptForwarded}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Meta Data' name='metaData' tooltip={userTooltip.metaData}>
<Form.List name='metaData'>
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Space
key={field.key}
style={{ display: 'flex', marginBottom: 8 }}
align='baseline'
>
<Form.Item
{...field}
name={[field.name, 'key']}
fieldKey={[field.fieldKey, 'key']}
rules={[{ required: true, message: 'Missing Key' }]}
>
<Input placeholder='Key' />
</Form.Item>
<Form.Item
{...field}
name={[field.name, 'value']}
fieldKey={[field.fieldKey, 'value']}
rules={[{ required: true, message: 'Missing Value' }]}
>
<Input placeholder='Value' />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Space>
))}
<Form.Item>
<Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
Add field
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
</Col>
<Col span={10} offset={1}>
<Form.Item label='Targets' name='targets' tooltip={userTooltip.targets}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Enter Targets' />
</Form.Item>
<Form.Item label='Spam Level' name='spamLevel' tooltip={userTooltip.spamLevel}>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Quota' name='quota' tooltip={userTooltip.quota}>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Recipients' name='recipients' tooltip={userTooltip.recipients}>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Forwards' name='forwards' tooltip={userTooltip.forwards}>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Max Upload for imap ' name='imapMaxUpload' tooltip={userTooltip.imapMaxUpload}>
<Input type={'number'} />
</Form.Item>
<Form.Item
label='Max Download for imap'
name='imapMaxDownload'
tooltip={userTooltip.imapMaxDownload}
>
<Input type={'number'} />
</Form.Item>
<Form.Item
label='Max Connections for imap'
name='imapMaxConnections'
tooltip={userTooltip.imapMaxConnections}
>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Max messages from MX' name='receivedMax' tooltip={userTooltip.receivedMax}>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Mailboxes' name='mailboxes' tooltip={userTooltip.mailboxes}>
<Form.Item label='Sent Mail' name='sent' tooltip={userTooltip.sent}>
<Input />
</Form.Item>
<Form.Item label='Junk' name='junk' tooltip={userTooltip.junk}>
<Input />
</Form.Item>
<Form.Item label='Drafts' name='drafts' tooltip={userTooltip.drafts}>
<Input />
</Form.Item>
<Form.Item label='Trash' name='trash' tooltip={userTooltip.trash}>
<Input />
</Form.Item>
</Form.Item>
<Form.Item
label='Disabled Scopes'
name='disabledScopes'
rules={[
{
required: false,
message: 'List of scopes that are disabled for this user ("imap", "pop3", "smtp")',
},
]}
tooltip={userTooltip.disabledScopes}
>
<Select mode='tags' style={{ width: '100%' }} placeholder='Select Scopes to disable'>
{_.map(['imap', 'pop3', 'smtp'], (tag) => {
return (
<Option key={tag} value={tag}>
{tag}
</Option>
);
})}
</Select>
</Form.Item>
<Form.Item label='Whitelist' name='fromWhitelist' tooltip={userTooltip.fromWhitelist}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Enter addresses to whitelist' />
</Form.Item>
</Col>
</Row>
</Form>
);
}
Example #19
Source File: UserDetailsForm.tsx From wildduck-ui with MIT License | 4 votes |
UserDetailsForm: React.FC<any> = (props: any) => {
const { data } = props;
const [form] = Form.useForm();
const { mutate } = useUpdateUserDetails();
const onFinish = (values: any) => {
const metaData = {};
const details: any = _.reduce(
values,
(acc, value, index) => {
if (!_.isUndefined(value) || value === false || value === true) {
if (index === 'metaData') {
value.forEach((eachMetaData: any) => {
_.set(metaData, `${eachMetaData.key}`, eachMetaData.value);
});
} else if (
index === 'quota' ||
index === 'imapDownload' ||
index === 'pop3Download' ||
index === 'imapUpload'
) {
_.set(acc, `${index}`, value * 1024000);
} else {
_.set(acc, `${index}`, value);
}
}
return acc;
},
{},
);
_.set(details, 'metaData', metaData);
mutate({
userId: _.get(data, 'id'),
userDetails: details,
});
};
const handleReset = () => {
form.resetFields();
};
return _.isUndefined(data) ? null : (
<Form
form={form}
name='basic'
labelCol={{ span: 12 }}
wrapperCol={{ span: 12 }}
initialValues={{
remember: true,
...data,
allowUnsafe: false,
hashedPassword: false,
uploadSentMessages: false,
disable2fa: false,
retention: _.get(data, 'retention', false) || 0,
quota: _.round(_.get(data, 'limits.quota.allowed', 0) / 1024000, 2),
recipients: _.get(data, 'limits.recipients.allowed', 0),
forwards: _.get(data, 'limits.forwards.allowed', 0),
receivedMax: _.get(data, 'limits.received.allowed', 0),
imapMaxDownload: _.round(_.get(data, 'limits.imapDownload.allowed', 0) / 1024000, 2),
imapMaxConnections: _.get(data, 'limits.imapMaxConnections.allowed', 0),
imapMaxUpload: _.round(_.get(data, 'limits.imapUpload.allowed', 0) / 1024000, 2),
metaData: _.map(_.get(data, 'metaData', {}), (value, key) => {
return { key: key, value: value };
}),
}}
onFinish={onFinish}
>
<Row>
<Col offset={20}>
<Form.Item>
<Space size='middle'>
<Button type='primary' htmlType='submit'>
Save
</Button>
<Button type='default' htmlType='button' onClick={handleReset}>
Reset
</Button>
</Space>
</Form.Item>
</Col>
</Row>
<Row style={{ paddingTop: 20 }}>
<Col span={10}>
<Form.Item label='Name' name='name' tooltip={userTooltip.name}>
<Input />
</Form.Item>
<Form.Item label='Password' name='password'>
<Input.Password />
</Form.Item>
<Form.Item
label='Hash the Password'
name='hashedPassword'
tooltip={userTooltip.hashedPassword}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label='Allow Unsafe'
name='allowUnsafe'
tooltip={userTooltip.allowUnsafe}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Tags' name='tags' tooltip={userTooltip.tags}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Enter Tags' />
</Form.Item>
<Form.Item label='Retention' name='retention' tooltip={userTooltip.retention}>
<Input type='number' min={0} />
</Form.Item>
<Form.Item
label='Upload sent messages'
name='uploadSentMessages'
tooltip={userTooltip.uploadSentMessages}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Public PGP Key' name='pubKey' tooltip={userTooltip.pubKey}>
<Input />
</Form.Item>
<Form.Item
label='Encrypt Messages'
name='encryptMessages'
tooltip={userTooltip.encryptMessages}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item
label='Encrypt Forwarded'
name='encryptForwarded'
tooltip={userTooltip.encryptForwarded}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Meta Data' tooltip={userTooltip.metaData}>
<Form.List name='metaData'>
{(fields, { add, remove }) => (
<>
{fields.map((field) => (
<Space
key={field.key}
style={{ display: 'flex', marginBottom: 8 }}
align='baseline'
>
<Form.Item
{...field}
name={[field.name, 'key']}
fieldKey={[field.fieldKey, 'key']}
rules={[{ required: true, message: 'Missing Key' }]}
>
<Input placeholder='Key' />
</Form.Item>
<Form.Item
{...field}
name={[field.name, 'value']}
fieldKey={[field.fieldKey, 'value']}
rules={[{ required: true, message: 'Missing Value' }]}
>
<Input placeholder='Value' />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(field.name)} />
</Space>
))}
<Form.Item>
<Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
Add field
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
<Form.Item label='Targets' name='targets' tooltip={userTooltip.targets}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Enter Targets' />
</Form.Item>
</Col>
<Col span={10} offset={1}>
<Form.Item label='Spam Level' name='spamLevel' tooltip={userTooltip.spamLevel}>
<Input type={'number'} max={100} min={0} />
</Form.Item>
<Form.Item label='Quota' name='quota' tooltip={userTooltip.quota}>
<Input type={'number'} addonAfter='MB' />
</Form.Item>
<Form.Item label='Max Recipients / Day' name='recipients' tooltip={userTooltip.recipients}>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Current Forwards / Day' name='forwards' tooltip={userTooltip.forwards}>
<Input type={'number'} />
</Form.Item>
<Form.Item
label='Current Max Upload for imap '
name='imapMaxUpload'
tooltip={userTooltip.imapMaxUpload}
>
<Input type={'number'} addonAfter='MB' />
</Form.Item>
<Form.Item
label='Current Max Download for imap '
name='imapMaxDownload'
tooltip={userTooltip.imapMaxDownload}
>
<Input type={'number'} addonAfter='MB' />
</Form.Item>
<Form.Item
label='Current Max Connections for imap'
name='imapMaxConnections'
tooltip={userTooltip.imapMaxConnections}
>
<Input type={'number'} />
</Form.Item>
<Form.Item
label='Current Max messages from MX'
name='receivedMax'
tooltip={userTooltip.receivedMax}
>
<Input type={'number'} />
</Form.Item>
<Form.Item label='Disabled Scope' name='disabledScopes' tooltip={userTooltip.disabledScopes}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Select Scopes to disable'>
{_.map(['imap', 'pop3', 'smtp'], (tag) => {
return (
<Option key={tag} value={tag}>
{tag}
</Option>
);
})}
</Select>
</Form.Item>
<Form.Item
label='Disable User'
name='disabled'
tooltip={userTooltip.disabled}
valuePropName='checked'
>
<Switch />
</Form.Item>
<Form.Item label='Whitelist' name='fromWhitelist' tooltip={userTooltip.fromWhitelist}>
<Select mode='tags' style={{ width: '100%' }} placeholder='Enter Whitelist' />
</Form.Item>
<Form.Item label='Suspend' name='suspended' tooltip={userTooltip.suspended} valuePropName='checked'>
<Switch />
</Form.Item>
</Col>
</Row>
</Form>
);
}
Example #20
Source File: index.tsx From fe-v5 with Apache License 2.0 | 4 votes |
TeamForm = React.forwardRef<ReactNode, TeamProps>((props, ref) => {
const { t } = useTranslation();
const { businessId, action } = props;
const [form] = Form.useForm();
const [userTeam, setUserTeam] = useState<Team[]>([]);
const [initialValues, setInitialValues] = useState({
label_enable: false,
label_value: '',
members: [{ perm_flag: true }],
name: '',
});
const [loading, setLoading] = useState<boolean>(true);
const [refresh, setRefresh] = useState(true);
useImperativeHandle(ref, () => ({
form: form,
}));
useEffect(() => {
if (businessId && action === ActionType.EditBusiness) {
getTeamInfoDetail(businessId);
} else {
setLoading(false);
}
}, []);
const getTeamInfoDetail = (id: string) => {
getBusinessTeamInfo(id).then((data: { name: string; label_enable: number; label_value: string; user_groups: { perm_flag: string; user_group: { id: number } }[] }) => {
setInitialValues({
name: data.name,
label_enable: data.label_enable === 1,
label_value: data.label_value,
members: data.user_groups.map((item) => ({
perm_flag: item.perm_flag === 'rw',
user_group_id: item.user_group.id,
})),
});
setLoading(false);
});
};
useEffect(() => {
getList('');
}, []);
const getList = (str: string) => {
getTeamInfoList({ query: str }).then((res) => {
setUserTeam(res.dat);
});
};
const debounceFetcher = useCallback(debounce(getList, 800), []);
return !loading ? (
<Form {...layout} form={form} initialValues={initialValues} preserve={false} layout={refresh ? 'horizontal' : 'horizontal'}>
{action !== ActionType.AddBusinessMember && (
<>
<Form.Item
label={t('业务组名称')}
name='name'
rules={[
{
required: true,
message: t('业务组名称不能为空!'),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t('作为标签使用')}
name='label_enable'
valuePropName='checked'
tooltip={{ title: '系统会自动把业务组的英文标识作为标签附到该业务组下辖监控对象的时序数据上', getPopupContainer: () => document.body }}
>
<Switch />
</Form.Item>
<Form.Item noStyle shouldUpdate={(prevValues, curValues) => prevValues.label_enable !== curValues.label_enable}>
{({ getFieldValue }) => {
return (
getFieldValue('label_enable') && (
<Form.Item
label={t('英文标识')}
name='label_value'
rules={[
{
required: true,
},
]}
tooltip={{
title: (
<span>
尽量用英文,不能与其他业务组标识重复,系统会自动生成 <Tag color='blue'>busigroup={form.getFieldValue('label_value')}</Tag> 的标签
</span>
),
getPopupContainer: () => document.body,
}}
>
<Input
onChange={(val) => {
setRefresh(!refresh);
}}
/>
</Form.Item>
)
);
}}
</Form.Item>
</>
)}
{(action === ActionType.CreateBusiness || action === ActionType.AddBusinessMember) && (
<Form.Item
label={t('团队')}
required
// tooltip={{
// title: '默认可读勾选可写',
// }}
>
<Form.List name='members'>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space key={key} style={{ display: 'flex', marginBottom: 8 }} align='baseline'>
<Form.Item
style={{ width: 450 }}
{...restField}
name={[name, 'user_group_id']}
fieldKey={[fieldKey, 'user_group_id']}
rules={[{ required: true, message: t('业务组团队不能为空!') }]}
>
<Select
suffixIcon={<CaretDownOutlined />}
style={{ width: '100%' }}
filterOption={false}
onSearch={(e) => debounceFetcher(e)}
showSearch
onBlur={() => getList('')}
>
{userTeam.map((team) => (
<Option key={team.id} value={team.id}>
{team.name}
</Option>
))}
</Select>
</Form.Item>
<Form.Item {...restField} name={[name, 'perm_flag']} fieldKey={[fieldKey, 'perm_flag']} valuePropName='checked'>
<Switch checkedChildren='读写' unCheckedChildren='只读' />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button type='dashed' onClick={() => add()} block icon={<PlusOutlined />}>
添加团队
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form.Item>
)}
</Form>
) : null;
})
Example #21
Source File: chartConfigModal.tsx From fe-v5 with Apache License 2.0 | 4 votes |
// 新增图表和编辑图表均在此组件
export default function ChartConfigModal(props: Props) {
const { t } = useTranslation();
const { busiId, groupId, show, onVisibleChange, initialValue, variableConfig, cluster, id } = props;
const layout = initialValue?.configs.layout;
const [innerVariableConfig, setInnerVariableConfig] = useState<VariableType | undefined>(variableConfig);
const [chartForm] = Form.useForm();
const [initialQL, setInitialQL] = useState([{ PromQL: '' }]);
const [legend, setLegend] = useState<boolean>(initialValue?.configs.legend || false);
const [step, setStep] = useState<number | null>(null);
const [highLevelConfig, setHighLevelConfig] = useState<HighLevelConfigType>(
initialValue?.configs.highLevelConfig || {
shared: true,
sharedSortDirection: 'desc',
precision: 'short',
formatUnit: 1000,
},
);
const [range, setRange] = useState<Range>({
start: 0,
end: 0,
});
useEffect(() => {
if (initialValue) {
chartForm.setFieldsValue(initialValue.configs);
setInitialQL(initialValue.configs.QL);
}
}, [initialValue]);
const handleAddChart = async (e) => {
try {
await chartForm.validateFields();
let formData: ChartConfig = Object.assign(
chartForm.getFieldsValue(),
{ legend, highLevelConfig },
{
version: 1, // Temporarily, hardcode 1
layout,
},
);
if (initialValue && initialValue.id) {
await updateCharts(busiId, [
{
configs: formData,
weight: 0,
group_id: groupId,
id: initialValue.id,
},
]);
} else {
await createChart(busiId, {
configs: JSON.stringify(formData),
weight: 0,
group_id: groupId,
});
}
onVisibleChange(true);
} catch (errorInfo) {
console.log('Failed:', errorInfo);
}
};
const PromqlEditorField = ({ onChange = (e: any) => {}, value = '', fields, remove, add, index, name }) => {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<PromqlEditor
xCluster='Default'
onChange={onChange}
value={value}
style={{
width: '310px',
// flex: 1,
}}
/>
{fields.length > 1 ? (
<MinusCircleOutlined
style={{ marginLeft: 10 }}
onClick={() => {
remove(name);
}}
/>
) : null}
{index === fields.length - 1 && (
<PlusCircleOutlined
style={{ marginLeft: 10 }}
onClick={() => {
add();
}}
/>
)}
</div>
);
};
const handleVariableChange = (value) => {
setInnerVariableConfig(value);
};
const aggrFuncMenu = (
<Menu
onClick={(sort) => {
setHighLevelConfig({ ...highLevelConfig, sharedSortDirection: (sort as { key: 'desc' | 'asc' }).key });
}}
selectedKeys={[highLevelConfig.sharedSortDirection]}
>
<Menu.Item key='desc'>desc</Menu.Item>
<Menu.Item key='asc'>asc</Menu.Item>
</Menu>
);
const precisionMenu = (
<Menu
onClick={(precision) => {
const precisionKey = isNaN(Number(precision.key)) ? precision.key : Number(precision.key);
setHighLevelConfig({ ...highLevelConfig, formatUnit: precisionKey as 1024 | 1000 | 'humantime' });
}}
selectedKeys={[String(highLevelConfig.formatUnit)]}
>
<Menu.Item key={'1000'}>Ki, Mi, Gi by 1000</Menu.Item>
<Menu.Item key={'1024'}>Ki, Mi, Gi by 1024</Menu.Item>
<Menu.Item key={'humantime'}>Human time duration</Menu.Item>
</Menu>
);
const formatUnitInfoMap = {
1024: 'Ki, Mi, Gi by 1024',
1000: 'Ki, Mi, Gi by 1000',
humantime: 'Human time duration',
};
const getContent = () => {
const aggrFuncMenu = (
<Menu
onClick={(sort) => {
setHighLevelConfig({ ...highLevelConfig, sharedSortDirection: (sort as { key: 'desc' | 'asc' }).key });
}}
selectedKeys={[highLevelConfig.sharedSortDirection]}
>
<Menu.Item key='desc'>desc</Menu.Item>
<Menu.Item key='asc'>asc</Menu.Item>
</Menu>
);
const precisionMenu = (
<Menu
onClick={(precision) => {
const precisionKey = isNaN(Number(precision.key)) ? precision.key : Number(precision.key);
setHighLevelConfig({ ...highLevelConfig, formatUnit: precisionKey as 1024 | 1000 | 'humantime' });
}}
selectedKeys={[String(highLevelConfig.formatUnit)]}
>
<Menu.Item key={'1000'}>Ki, Mi, Gi by 1000</Menu.Item>
<Menu.Item key={'1024'}>Ki, Mi, Gi by 1024</Menu.Item>
<Menu.Item key={'humantime'}>Human time duration</Menu.Item>
</Menu>
);
return (
<div>
<Checkbox
checked={highLevelConfig.shared}
onChange={(e) => {
setHighLevelConfig({ ...highLevelConfig, shared: e.target.checked });
}}
>
Multi Series in Tooltip, order value
</Checkbox>
<Dropdown overlay={aggrFuncMenu}>
<a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
{highLevelConfig.sharedSortDirection} <DownOutlined />
</a>
</Dropdown>
<br />
<Checkbox
checked={legend}
onChange={(e) => {
setLegend(e.target.checked);
}}
>
Show Legend
</Checkbox>
<br />
<Checkbox
checked={highLevelConfig.precision === 'short'}
onChange={(e) => {
setHighLevelConfig({ ...highLevelConfig, precision: e.target.checked ? 'short' : 'origin' });
}}
>
Value format with:{' '}
</Checkbox>
<Dropdown overlay={precisionMenu}>
<a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
{formatUnitInfoMap[highLevelConfig.formatUnit]} <DownOutlined />
</a>
</Dropdown>
</div>
);
};
return (
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center' }}>
<div>{initialValue ? t('编辑图表') : t('新建图表')}</div>
<div style={{ flex: 1, display: 'flex', justifyContent: 'flex-end', alignItems: 'center', fontSize: 12, lineHeight: '20px' }}>
<DateRangePicker onChange={(e) => setRange(e)} />
<Resolution onChange={(v) => setStep(v)} initialValue={step} />
<CloseOutlined
style={{ fontSize: 18 }}
onClick={() => {
onVisibleChange(false);
}}
/>
</div>
</div>
}
width={900}
visible={show}
destroyOnClose={true}
onOk={handleAddChart}
closable={false}
onCancel={() => {
onVisibleChange(false);
}}
>
<Form {...layout} form={chartForm} preserve={false}>
<Row>
<Col span={12}>
<VariableConfig onChange={handleVariableChange} value={innerVariableConfig} editable={false} cluster={cluster} range={range} id={id} />
<br />
<Form.Item
label={t('标题')}
name='name'
labelCol={{
span: 4,
}}
wrapperCol={{
span: 20,
}}
rules={[
{
required: true,
message: t('图表名称'),
},
]}
>
<Input />
</Form.Item>
<Form.Item label={t('下钻链接')} name='link' labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
<Input />
</Form.Item>
<Form.Item
wrapperCol={{
span: 24,
}}
style={{
marginBottom: '0px',
}}
>
<Form.List name='QL' initialValue={initialQL}>
{(fields, { add, remove }, { errors }) => {
return (
<>
{fields.length ? (
fields.map(({ key, name, fieldKey, ...restField }, index) => {
return (
<div key={name + fieldKey}>
<Form.Item
label='PromQL'
name={[name, 'PromQL']}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 20,
}}
validateTrigger={['onBlur']}
rules={[
{
required: true,
message: t('请输入PromQL'),
},
]}
>
<PromqlEditorField key={name + fieldKey} name={name} fields={fields} index={index} remove={remove} add={add} />
</Form.Item>
<Form.Item
label='Legend'
name={[name, 'Legend']}
tooltip={{
getPopupContainer: () => document.body,
title:
'Controls the name of the time series, using name or pattern. For example {{hostname}} will be replaced with label value for the label hostname.',
}}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 20,
}}
>
<Input />
</Form.Item>
</div>
);
})
) : (
<PlusCircleOutlined
onClick={() => {
add();
}}
/>
)}
<Form.ErrorList errors={errors} />
</>
);
}}
</Form.List>
</Form.Item>
<Row>
<Col span={11}>
<Form.Item label={t('预警值')} name='yplotline1' labelCol={{ span: 9 }} wrapperCol={{ span: 16 }}>
<InputNumber />
</Form.Item>
</Col>
<Col span={12} offset={1}>
<Form.Item label={t('危险值')} name='yplotline2' labelCol={{ span: 7 }} wrapperCol={{ span: 20 }}>
<InputNumber />
</Form.Item>
</Col>
{/* <Col span={23} offset={1}>
<Form.Item>
<Checkbox
checked={highLevelConfig.shared}
onChange={(e) => {
setHighLevelConfig({ ...highLevelConfig, shared: e.target.checked });
}}
>
Multi Series in Tooltip, order value
</Checkbox>
<Dropdown overlay={aggrFuncMenu}>
<a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
{highLevelConfig.sharedSortDirection} <DownOutlined />
</a>
</Dropdown>
</Form.Item>
</Col>
<Col span={23} offset={1}>
<Form.Item>
<Checkbox
checked={legend}
onChange={(e) => {
setLegend(e.target.checked);
}}
>
Show Legend
</Checkbox>
</Form.Item>
</Col>
<Col span={23} offset={1}>
<Form.Item>
<Checkbox
checked={highLevelConfig.precision === 'short'}
onChange={(e) => {
setHighLevelConfig({ ...highLevelConfig, precision: e.target.checked ? 'short' : 'origin' });
}}
>
Value format with:{' '}
</Checkbox>
<Dropdown overlay={precisionMenu}>
<a className='ant-dropdown-link' onClick={(e) => e.preventDefault()}>
{formatUnitInfoMap[highLevelConfig.formatUnit]} <DownOutlined />
</a>
</Dropdown>
</Form.Item>
</Col> */}
</Row>
</Col>
<Col span={12}>
<Form.Item
wrapperCol={{ span: 22, offset: 2 }}
shouldUpdate={(prevValues, curValues) =>
prevValues.QL !== curValues.QL || prevValues.multi === curValues.multi || prevValues.legend === curValues.legend || prevValues.format === curValues.format
}
>
{({ getFieldsValue }) => {
const { QL = [], yplotline1, yplotline2 } = getFieldsValue();
const promqls = QL.filter((item) => item && item.PromQL).map((item) =>
innerVariableConfig ? replaceExpressionVars(item.PromQL, innerVariableConfig, innerVariableConfig.var.length, id) : item.PromQL,
);
const legendTitleFormats = QL.map((item) => item && item.Legend);
return (
<div className={legend ? 'graph-container graph-container-hasLegend' : 'graph-container'}>
<div className='graph-header' style={{ height: '35px', lineHeight: '35px', display: 'flex', justifyContent: 'space-between' }}>
<div>预览图表</div>
<div className='graph-extra'>
<span className='graph-operationbar-item' key='info'>
<Popover placement='left' content={getContent()} trigger='click' autoAdjustOverflow={false} getPopupContainer={() => document.body}>
<Button className='' type='link' size='small' onClick={(e) => e.preventDefault()}>
<SettingOutlined />
</Button>
</Popover>
</span>
</div>
</div>
<Graph
showHeader={false}
graphConfigInnerVisible={false}
highLevelConfig={highLevelConfig}
data={{
yAxis: {
plotLines: [
{
value: yplotline1 ? yplotline1 : undefined,
color: 'orange',
},
{
value: yplotline2 ? yplotline2 : undefined,
color: 'red',
},
],
},
legend: legend,
step,
range,
promqls,
legendTitleFormats,
}}
/>
</div>
);
// ) : null;
}}
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
);
}
Example #22
Source File: info.tsx From fe-v5 with Apache License 2.0 | 4 votes |
export default function Info() {
const { t } = useTranslation();
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [contactsList, setContactsList] = useState<ContactsItem[]>([]);
let { profile } = useSelector<RootState, accountStoreState>((state) => state.account);
const [selectAvatar, setSelectAvatar] = useState<string>(profile.portrait || '/image/avatar1.png');
const [customAvatar, setCustomAvatar] = useState('');
const dispatch = useDispatch();
useEffect(() => {
const { id, nickname, email, phone, contacts, portrait } = profile;
form.setFieldsValue({
nickname,
email,
phone,
contacts,
});
if (portrait.startsWith('http')) {
setCustomAvatar(portrait);
}
}, [profile]);
useEffect(() => {
getNotifyChannels().then((data: Array<ContactsItem>) => {
setContactsList(data);
});
}, []);
const handleSubmit = async () => {
try {
await form.validateFields();
updateProfile();
} catch (err) {
console.log(t('输入有误'), err);
}
};
const handleOk = () => {
if (customAvatar) {
if (!customAvatar.startsWith('http')) {
message.error(t('自定义头像需以http开头'));
return;
}
fetch(customAvatar, { mode: 'no-cors' })
.then((res) => {
setIsModalVisible(false);
handleSubmit();
})
.catch((err) => {
message.error(t('自定义头像') + err);
});
} else {
setIsModalVisible(false);
handleSubmit();
}
};
const handleCancel = () => {
setIsModalVisible(false);
};
const updateProfile = () => {
const { nickname, email, phone, moreContacts } = form.getFieldsValue();
let { contacts } = form.getFieldsValue();
if (moreContacts && moreContacts.length > 0) {
moreContacts.forEach((item) => {
const { key, value } = item;
if (key && value) {
if (contacts) {
contacts[key] = value;
} else {
contacts = {
[key]: value,
};
}
}
});
}
for (let key in contacts) {
if (!contacts[key]) {
delete contacts[key];
}
}
dispatch({
type: 'account/updateProfile',
data: {
...profile,
portrait: customAvatar || selectAvatar,
nickname,
email,
phone,
contacts,
},
});
message.success(t('信息保存成功'));
};
const avatarList = new Array(8).fill(0).map((_, i) => i + 1);
const handleImgClick = (i) => {
setSelectAvatar(`/image/avatar${i}.png`);
};
return (
<>
<Form form={form} layout='vertical'>
<Row
gutter={16}
style={{
marginBottom: '24px',
}}
>
<Col span={20}>
<Row
gutter={16}
style={{
marginBottom: '24px',
}}
>
<Col span={4}>
<div>
<label>{t('用户名')}:</label>
<span>{profile.username}</span>
</div>
</Col>
<Col span={4}>
<div>
<label>{t('角色')}:</label>
<span>{profile.roles.join(', ')}</span>
</div>
</Col>
</Row>
<Form.Item label={<span>{t('显示名')}:</span>} name='nickname'>
<Input placeholder={t('请输入显示名')} />
</Form.Item>
<Form.Item label={<span>{t('邮箱')}:</span>} name='email'>
<Input placeholder={t('请输入邮箱')} />
</Form.Item>
<Form.Item label={<span>{t('手机')}:</span>} name='phone'>
<Input placeholder={t('请输入手机号')} />
</Form.Item>
{profile.contacts &&
Object.keys(profile.contacts)
.sort()
.map((key, i) => {
let contact = contactsList.find((item) => item.key === key);
return (
<>
{contact ? (
<Form.Item label={contact.label + ':'} name={['contacts', key]} key={i}>
<Input placeholder={`${t('请输入')}${key}`} />
</Form.Item>
) : null}
</>
);
})}
<Form.Item label={t('更多联系方式')}>
<Form.List name='moreContacts'>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, fieldKey, ...restField }) => (
<Space
key={key}
style={{
display: 'flex',
}}
align='baseline'
>
<Form.Item
style={{
width: '180px',
}}
{...restField}
name={[name, 'key']}
fieldKey={[fieldKey, 'key']}
rules={[
{
required: true,
message: t('联系方式不能为空'),
},
]}
>
<Select suffixIcon={<CaretDownOutlined />} placeholder={t('请选择联系方式')}>
{contactsList.map((item, index) => (
<Option value={item.key} key={index}>
{item.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item
{...restField}
style={{
width: '330px',
}}
name={[name, 'value']}
fieldKey={[fieldKey, 'value']}
rules={[
{
required: true,
message: t('值不能为空'),
},
]}
>
<Input placeholder={t('请输入值')} />
</Form.Item>
<MinusCircleOutlined className='control-icon-normal' onClick={() => remove(name)} />
</Space>
))}
<PlusCircleOutlined className='control-icon-normal' onClick={() => add()} />
</>
)}
</Form.List>
</Form.Item>
<Form.Item>
<Button type='primary' onClick={handleSubmit}>
{t('确认修改')}
</Button>
</Form.Item>
</Col>
<Col span={4}>
<div className='avatar'>
<img src={profile.portrait || '/image/avatar1.png'} />
<Button type='primary' className='update-avatar' onClick={() => setIsModalVisible(true)}>
{t('更换头像')}
</Button>
</div>
</Col>
</Row>
</Form>
<Modal title={t('更换头像')} visible={isModalVisible} onOk={handleOk} onCancel={handleCancel} wrapClassName='avatar-modal'>
<div className='avatar-content'>
{avatarList.map((i) => {
return (
<div key={i} className={`/image/avatar${i}.png` === selectAvatar ? 'avatar active' : 'avatar'} onClick={() => handleImgClick(i)}>
<img src={`/image/avatar${i}.png`} />
</div>
);
})}
</div>
<Input addonBefore={<span>{t('头像URL')}:</span>} onChange={(e) => setCustomAvatar(e.target.value)} value={customAvatar} />
</Modal>
</>
);
}
Example #23
Source File: index.tsx From metaplex with Apache License 2.0 | 4 votes |
InfoStep = (props: {
attributes: IMetadataExtension;
files: File[];
isCollection: boolean;
setIsCollection: (val: boolean) => void;
setAttributes: (attr: IMetadataExtension) => void;
confirm: () => void;
}) => {
const { image } = useArtworkFiles(props.files, props.attributes);
const [form] = Form.useForm();
const { isCollection, setIsCollection } = props;
const [selectedCollection, setSelectedCollection] = useState<
Array<SafetyDepositDraft>
>([]);
const artistFilter = useCallback(
(i: SafetyDepositDraft) =>
!(i.metadata.info.data.creators || []).some((c: Creator) => !c.verified),
[],
);
useEffect(() => {
if (selectedCollection.length) {
props.setAttributes({
...props.attributes,
collection: selectedCollection[0].metadata.info.mint,
});
}
}, [selectedCollection]);
return (
<>
<Row className="call-to-action">
<h2>Describe your item</h2>
<p>
Provide detailed description of your creative process to engage with
your audience.
</p>
</Row>
<Row className="content-action" justify="space-around">
<Col>
{props.attributes.image && (
<ArtCard
image={image}
animationURL={props.attributes.animation_url}
category={props.attributes.properties?.category}
name={props.attributes.name}
symbol={props.attributes.symbol}
small={true}
artView={!(props.files.length > 1)}
className="art-create-card"
/>
)}
</Col>
<Col className="section" style={{ minWidth: 300 }}>
<label className="action-field">
<span className="field-title">Title</span>
<Input
autoFocus
className="input"
placeholder="Max 50 characters"
maxLength={50}
allowClear
value={props.attributes.name}
onChange={info =>
props.setAttributes({
...props.attributes,
name: info.target.value,
})
}
/>
</label>
<label className="action-field">
<span className="field-title">Symbol</span>
<Input
className="input"
placeholder="Max 10 characters"
maxLength={10}
allowClear
value={props.attributes.symbol}
onChange={info =>
props.setAttributes({
...props.attributes,
symbol: info.target.value,
})
}
/>
</label>
<label className="action-field direction-row">
<Checkbox
checked={isCollection}
onChange={val => {
setIsCollection(val.target.checked);
}}
/>
<span className="field-title" style={{ marginLeft: '10px' }}>
Is parent collection?
</span>
</label>
{!isCollection && (
<label className="action-field">
<span className="field-title">Collection</span>
<ArtSelector
filter={artistFilter}
selected={selectedCollection}
setSelected={items => {
setSelectedCollection(items);
}}
allowMultiple={false}
>
Select NFT
</ArtSelector>
</label>
)}
<label className="action-field">
<span className="field-title">Description</span>
<Input.TextArea
className="input textarea"
placeholder="Max 500 characters"
maxLength={500}
value={props.attributes.description}
onChange={info =>
props.setAttributes({
...props.attributes,
description: info.target.value,
})
}
allowClear
/>
</label>
<label className="action-field">
<span className="field-title">Maximum Supply</span>
{!isCollection ? (
<InputNumber
placeholder="Quantity"
value={props.attributes.properties.maxSupply}
onChange={(val: number) => {
props.setAttributes({
...props.attributes,
properties: {
...props.attributes.properties,
maxSupply: val,
},
});
}}
className="royalties-input"
/>
) : (
0
)}
</label>
<label className="action-field">
<span className="field-title">Attributes</span>
</label>
<Form name="dynamic_attributes" form={form} autoComplete="off">
<Form.List name="attributes">
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name }) => (
<Space key={key} align="baseline">
<Form.Item name={[name, 'trait_type']} hasFeedback>
<Input placeholder="trait_type (Optional)" />
</Form.Item>
<Form.Item
name={[name, 'value']}
rules={[{ required: true, message: 'Missing value' }]}
hasFeedback
>
<Input placeholder="value" />
</Form.Item>
<Form.Item name={[name, 'display_type']} hasFeedback>
<Input placeholder="display_type (Optional)" />
</Form.Item>
<MinusCircleOutlined onClick={() => remove(name)} />
</Space>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => add()}
block
icon={<PlusOutlined />}
>
Add attribute
</Button>
</Form.Item>
</>
)}
</Form.List>
</Form>
</Col>
</Row>
<Row>
<Button
type="primary"
size="large"
onClick={() => {
form.validateFields().then(values => {
const nftAttributes = values.attributes;
// value is number if possible
for (const nftAttribute of nftAttributes || []) {
const newValue = Number(nftAttribute.value);
if (!isNaN(newValue)) {
nftAttribute.value = newValue;
}
}
console.log('Adding NFT attributes:', nftAttributes);
props.setAttributes({
...props.attributes,
attributes: nftAttributes,
});
props.confirm();
});
}}
className="action-btn"
>
Continue to royalties
</Button>
</Row>
</>
);
}
Example #24
Source File: EventsList.tsx From jitsu with MIT License | 4 votes |
EventsList: React.FC<{
type: EventType
filterOptions: FilterOption[]
}> = ({ type, filterOptions }) => {
const statusOptions = [
{ label: "All", value: null },
{ label: "Error", value: "error" },
]
const listInnerRef = useRef()
const location = useLocation()
const params = new URLSearchParams(location.search)
const [autoReload, setAutoReload] = useState(true)
const [selectedEvent, setSelectedEvent] = useState(null)
const [events, setEvents] = useState<Event[]>([])
const [filteredEvents, setFilteredEvents] = useState<Event[]>([])
const [term, setTerm] = useState(params.get("q"))
const [idFilter, setIdFilter] = useState(
filterOptions.find(f => f.value === params.get("id"))?.value ?? filterOptions[0]?.value
)
const [statusFilter, setStatusFilter] = useState(
statusOptions.find(f => f.value === params.get("status"))?.value ?? statusOptions[0]?.value
)
const [reloadCount, setReloadCount] = useState(0)
const services = useServices()
const history = useHistory()
const destinationsMap: Record<string, DestinationData> = destinationsStore.listIncludeHidden.reduce((index, dst) => {
index[dst._uid] = dst
return index
}, {})
useEffect(() => {
if (!idFilter) {
history.push({ search: null })
return
}
let queryParams = omitBy({ type, id: idFilter, status: statusFilter }, isNull)
if (term) {
queryParams["q"] = term
}
history.push({ search: new URLSearchParams(queryParams).toString() })
}, [idFilter, statusFilter, term])
const { data, error } = useLoaderAsObject(() => {
if (!idFilter) {
return null
}
const ids = type === EventType.Destination ? `${services.activeProject.id}.${idFilter}` : idFilter
setSelectedEvent(null)
return services.backendApiClient
.get(
`/events/cache?project_id=${services.activeProject.id}&limit=500&namespace=${type}&ids=${ids}&status=${
statusFilter ?? ""
}`,
{ proxy: true }
)
.then(events => {
return { events, id: idFilter }
})
}, [idFilter, statusFilter, reloadCount])
useEffect(() => {
const interval = setInterval(() => {
if (!autoReload || selectedEvent) {
return
}
setReloadCount(reloadCount + 1)
}, 15000)
return () => clearInterval(interval)
}, [autoReload, selectedEvent, reloadCount])
const filterByTerm = (events, term) => {
return term ? events.filter(i => JSON.stringify(i.rawJson).indexOf(term) !== -1) : events
}
const search = term => {
setTerm(term)
setFilteredEvents(filterByTerm(events, term))
}
useEffect(() => {
const initialEvents = error || !data ? [] : processEvents(type, data)
setEvents(initialEvents)
setFilteredEvents(filterByTerm(initialEvents, term))
}, [error, data])
if (!filterOptions.length) {
return <NoDataFlowing showHint={true} />
}
const filters = (
<>
<div className={`mb-6 flex ${styles.filters}`}>
<SelectFilter
className="mr-5"
label={type === EventType.Token ? "API Key" : "Destination"}
initialValue={idFilter}
options={filterOptions}
onChange={option => {
setIdFilter(option.value)
}}
/>
<SelectFilter
className="mr-5"
label="Status"
initialValue={statusFilter}
options={statusOptions}
onChange={option => {
setStatusFilter(option.value)
}}
/>
<Button
size="large"
type="primary"
className={styles.reloadBtn}
onClick={() => {
setReloadCount(count => count + 1)
}}
>
<ReloadOutlined /> Reload
</Button>
</div>
<Input className="w-full" placeholder="Filter" value={term} onChange={e => search(e.target.value)} />
</>
)
const eventStatusMessage = event => {
const error = event.status === EventStatus.Error
const skip = event.status === EventStatus.Skip
if (type === EventType.Token) {
if (skip) {
return `Skip`
}
return error ? event.rawJson.error ?? "Error" : "Success"
}
return error
? "Failed - at least one destination load is failed"
: skip
? "Skipped - event was not sent to destination"
: "Success - successfully sent to destination"
}
if (error) {
return (
<div className="w-full">
{filters}
<CenteredError error={error} />
</div>
)
} else if (!data) {
return (
<div className="w-full">
{filters}
<CenteredSpin />
</div>
)
}
const { last_minute_limited, cache_capacity_per_interval, interval_seconds } = data?.events
const alert =
last_minute_limited > 0 ? (
<div className="mt-4">
<Alert
message={`This isn't a full list of all events. Jitsu doesn't cache all events, but the only ${cache_capacity_per_interval} event per ${interval_seconds} seconds. Other ${last_minute_limited} events from the last minute have been being processed and stored to the destinations but haven't been saved into the cache.`}
type="warning"
/>
</div>
) : null
const onScroll = () => {
if (!listInnerRef.current) {
return
}
const { scrollTop } = listInnerRef.current
const startAutoReload = scrollTop === 0
if (startAutoReload === autoReload) {
return
}
setAutoReload(startAutoReload)
}
return (
<>
{filters}
{alert}
<div
className={`mt-3 transition-all duration-300 ${styles.autoReloadInfo} ${
autoReload && !selectedEvent ? "" : "opacity-0"
}`}
>
<ReloadOutlined spin={true} /> Auto reload is enabled. <a onClick={() => setAutoReload(false)}>Disable</a>
</div>
<div className={styles.eventsList} ref={listInnerRef} onScroll={onScroll}>
{!filteredEvents.length ? <NoDataFlowing showHint={false} /> : null}
{filteredEvents.map(event => {
const active = event.eventId === selectedEvent
return (
<div key={event.eventId}>
<div
className={`overflow-hidden w-full flex flex-row border-b border-secondaryText border-opacity-50 items-center cursor-pointer h-12 ${
selectedEvent === event.eventId ? "bg-bgSecondary" : "hover:bg-bgComponent"
}`}
key="header"
onClick={() => setSelectedEvent(active ? null : event.eventId)}
>
<div className="w-6 flex items-center justify-center px-3 text-lg" key="icon">
<Tooltip title={eventStatusMessage(event)}>
{event.status === EventStatus.Error ? (
<ExclamationCircleOutlined className="text-error" />
) : event.status === EventStatus.Pending || event.status === EventStatus.Skip ? (
<MinusCircleOutlined className="text-warning" />
) : (
<CheckCircleOutlined className="text-success" />
)}
</Tooltip>
</div>
<div
className={`text-xxs whitespace-nowrap text-secondaryText px-1 ${styles.timestampColumn}`}
key="time"
>
<div>{event.timestamp.format("YYYY-MM-DD HH:mm:ss")} UTC</div>
<div className="text-xxs">{event.timestamp.fromNow()}</div>
</div>
<div
className="pl-4 text-3xs text-secondaryText font-monospace overflow-hidden overflow-ellipsis h-12 leading-4 flex-shrink"
key="json"
>
{event.rawJson.malformed ? event.rawJson.malformed : JSON.stringify(event.rawJson, null, 2)}
</div>
<div
className={cn(
"w-12 text-testPale flex items-center justify-center px-2 text-xl transition-transform duration-500",
styles.expandBtn,
active && "transform rotate-90"
)}
key="expand"
>
<RightCircleOutlined />
</div>
</div>
<div key="details">
{active && <EventsView event={event} allDestinations={destinationsMap} className="pb-6" />}
</div>
</div>
)
})}
</div>
</>
)
}
Example #25
Source File: index.tsx From jetlinks-ui-antd with MIT License | 4 votes |
Save: React.FC<Props> = props => {
const { form: { getFieldDecorator }, form } = props;
const [bridgeConfigs, setBridgeConfigs] = useState<any>([]);
const [accessConfig, setAccessConfig] = useState({});
const [productList, setProductList] = useState([]);
const [protocolSupport, setProtocolSupport] = useState([]);
const [productKeyList, setProductKeyList] = useState([]);
const [deviceList, setDeviceList] = useState<any>([]);
const [serveIdList, setServeIdList] = useState([]);
const [regionIdList] = useState(['cn-qingdao', 'cn-beijing', 'cn-zhangjiakou', 'cn-huhehaote', 'cn-wulanchabu', 'cn-hangzhou', 'cn-shanghai', 'cn-shenzhen', 'cn-heyuan', 'cn-guangzhou', 'cn-chengdu']);
useEffect(() => {
setBridgeConfigs(props.data.bridgeConfigs || [
{
serverId: "",
bridgeProductKey: "",
bridgeDeviceName: "",
bridgeDeviceSecret: "",
http2Endpoint: ""
}
]);
setAccessConfig(props.data?.accessConfig || {
regionId: "",
apiEndpoint: "",
authEndpoint: "",
accessKeyId: "",
accessSecret: "",
productKey: ""
});
apis.aliyun.getNodesList().then(res => {
if (res.status === 200) {
setServeIdList(res.result)
}
});
apis.aliyun.productList({}).then(res => {
if (res.status === 200) {
setProductList(res.result)
}
});
apis.aliyun.protocolSupport().then(res => {
if (res.status === 200) {
setProtocolSupport(res.result)
}
});
if (props.data.accessConfig) {
getBridge(props.data?.accessConfig);
let item = props.data?.accessConfig;
props.data.bridgeConfigs.map((i: any, index: number) => {
let param = {
regionId: item.regionId,
accessSecret: item.accessSecret,
apiEndpoint: item.apiEndpoint,
authEndpoint: item.authEndpoint,
accessKeyId: item.accessKeyId,
productKey: i.bridgeProductKey
};
apis.aliyun.getDevices(param).then(res => {
if (res.status === 200) {
deviceList[index] = res.result?.data || [];
setDeviceList([...deviceList])
}
})
})
}
}, []);
const saveData = () => {
form.validateFields((err, fileValue) => {
if (err) return;
apis.aliyun.save(fileValue).then(res => {
if (res.status === 200) {
props.save();
}
})
})
};
const getBridge = (params: any) => {
if (params.regionId !== '' && params.accessSecret !== '' && params.apiEndpoint !== '' && params.authEndpoint !== '' && params.accessKeyId !== '') {
let param = {
regionId: params.regionId,
accessSecret: params.accessSecret,
apiEndpoint: params.apiEndpoint,
authEndpoint: params.authEndpoint,
accessKeyId: params.accessKeyId,
};
apis.aliyun.getProducts(param).then(res => {
if (res.status === 200) {
setProductKeyList(res.result?.data || [])
}
})
}
};
return (
<Modal
width='60VW'
title={props.data.id ? "编辑产品" : "添加产品"}
visible
okText="确定"
cancelText="取消"
onOk={() => { saveData() }}
onCancel={() => props.close()}
>
<div>
<Form layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }}>
<Row justify="space-around" gutter={24}>
<Col span={12}>
<Form.Item label="产品ID" >
{getFieldDecorator('id', {
initialValue: props.data?.id,
rules: [{ required: false, message: '请选择' }],
})(
<Select placeholder="请选择" allowClear showSearch
optionFilterProp="value"
>
{productList && productList.map((i: any, index: number) => {
return <Select.Option key={index} value={i.id}>{i.id}</Select.Option>
})}
</Select>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="产品名称">
{getFieldDecorator('name', {
initialValue: props.data?.name,
rules: [{ required: true, message: '请输入名称' }],
})(<Input placeholder="请输入名称" />)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="编解码协议">
{getFieldDecorator('codecProtocol', {
initialValue: props.data?.codecProtocol,
rules: [{ required: true, message: '请选择' }],
})(<Select placeholder="请选择" showSearch>
{protocolSupport && protocolSupport.map((i: any, index: number) => {
return <Select.Option key={index} value={i.id}>{i.name}</Select.Option>
})}
</Select>)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="说明">
{getFieldDecorator('description', {
initialValue: props.data?.description,
rules: [{ required: false, message: '请输入' }],
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
</Row>
<Divider orientation="left" dashed><div style={{ fontWeight: 'bold' }}>认证信息配置</div></Divider>
<Row justify="start" gutter={24}>
<Col span={12}>
<Form.Item label={
<span>
区域ID <Tooltip title="地域和可用区">
<QuestionCircleOutlined onClick={() => {
window.open('https://help.aliyun.com/document_detail/40654.html')
}} />
</Tooltip>
</span>
} >
{getFieldDecorator('accessConfig.regionId', {
initialValue: accessConfig?.regionId,
rules: [{ required: true, message: '请选择' }],
})(
<AutoComplete placeholder="本地服务ID"
dataSource={regionIdList}
filterOption={(inputValue, option) =>
option?.props?.children?.toUpperCase()?.indexOf(inputValue.toUpperCase()) !== -1
}
onBlur={(value) => {
if (value) {
let temp = form.getFieldValue('accessConfig.productKey');
form.setFieldsValue({
accessConfig: {
apiEndpoint: `https://iot.${value}.aliyuncs.com`,
authEndpoint: `https://iot-auth.${value}.aliyuncs.com/auth/bridge`,
http2Endpoint: `https://${temp}.iot-as-http2.${value}.aliyuncs.com`,
}
});
let params = form.getFieldValue('accessConfig');
getBridge({
regionId: value,
accessSecret: params.accessSecret,
apiEndpoint: params.apiEndpoint,
authEndpoint: params.authEndpoint,
accessKeyId: params.accessKeyId,
})
}
}}
>
</AutoComplete>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="API接口地址">
{getFieldDecorator('accessConfig.apiEndpoint', { //https://iot.cn-shanghai.aliyuncs.com
initialValue: accessConfig?.apiEndpoint,
rules: [{ required: true, message: '请输入' }],
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="认证接口地址">
{getFieldDecorator('accessConfig.authEndpoint', { //https://iot-auth.cn-shanghai.aliyuncs.com/auth/bridge
initialValue: accessConfig?.authEndpoint,
rules: [{ required: true, message: '请输入' }],
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="accessKey">
{getFieldDecorator('accessConfig.accessKeyId', {
initialValue: accessConfig?.accessKeyId,
rules: [{ required: true, message: '请输入' }],
})(<Input placeholder="请输入" onBlur={(e) => {
let params = form.getFieldValue('accessConfig');
getBridge({
regionId: params.regionId,
accessSecret: params.accessSecret,
apiEndpoint: params.apiEndpoint,
authEndpoint: params.authEndpoint,
accessKeyId: e.target.value,
})
}} />)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="accessSecret">
{getFieldDecorator('accessConfig.accessSecret', {
initialValue: accessConfig?.accessSecret,
rules: [{ required: true, message: '请输入' }],
})(<Input placeholder="请输入" onBlur={(e) => {
let params = form.getFieldValue('accessConfig');
getBridge({
regionId: params.regionId,
accessSecret: e.target.value,
apiEndpoint: params.apiEndpoint,
authEndpoint: params.authEndpoint,
accessKeyId: params.accessKeyId,
})
}} />)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="ProductKey">
{getFieldDecorator('accessConfig.productKey', {
initialValue: accessConfig?.productKey,
rules: [{ required: true, message: '请输入' }],
})(
<AutoComplete placeholder="请选择" allowClear>
{productKeyList && productKeyList.map((i: any, index: number) => {
return <AutoComplete.Option key={index} value={i.productKey}>{`${i.productKey}(${i.productName})`}</AutoComplete.Option>
})}
</AutoComplete>
// <Select placeholder="请选择" allowClear>
// {productKeyList && productKeyList.map((i: any, index: number) => {
// return <Select.Option key={index} value={i.productKey}>{`${i.productKey}(${i.productName})`}</Select.Option>
// })}
// </Select>
)}
</Form.Item>
</Col>
</Row>
<Divider orientation="left" dashed><div style={{ fontWeight: 'bold' }}>网桥配置</div></Divider>
{
bridgeConfigs.map((item: any, index: number) => {
return (
<div key={index} style={{ backgroundColor: 'rgba(192,192,192,0.1)', marginBottom: '10px', paddingTop: '20px' }}>
<div style={{ width: "90%", marginLeft: '5%' }}>网桥: {index + 1}</div>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<div style={{ width: "90%" }}>
<Row gutter={0} justify="start">
<Col span={12}>
<Form.Item label="本地服务ID">
{getFieldDecorator(`bridgeConfigs[${index}].serverId`, {
initialValue: item.serverId || undefined,
rules: [{ required: true, message: '本地服务ID' }],
})(<AutoComplete placeholder="本地服务ID">
{serveIdList && serveIdList.map((i: any, index: number) => {
return <AutoComplete.Option key={index} value={i.id}>{i.id}</AutoComplete.Option>
})}
</AutoComplete>)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="ProductKey">
{getFieldDecorator(`bridgeConfigs[${index}].bridgeProductKey`, {
initialValue: item.bridgeProductKey || undefined,
rules: [{ required: true, message: '网桥ProductKey' }],
})(
<AutoComplete placeholder="请选择" allowClear
onBlur={(value: SelectValue) => {
let temp = form.getFieldValue('accessConfig.regionId');
let bridge = form.getFieldValue('bridgeConfigs');
bridge[index].http2Endpoint = `https://${value}.iot-as-http2.${temp}.aliyuncs.com`;
form.setFieldsValue({
bridgeConfigs: bridge
});
let config = form.getFieldValue('accessConfig');
if (config.regionId !== '' && config.apiEndpoint !== '' &&
config.authEndpoint !== '' && config.accessKeyId !== '' && config.accessSecret !== '' && value !== '') {
apis.aliyun.getDevices({
regionId: config.regionId,
accessSecret: config.accessSecret,
apiEndpoint: config.apiEndpoint,
productKey: value,
authEndpoint: config.authEndpoint,
accessKeyId: config.accessKeyId,
}).then(res => {
if (res.status === 200) {
deviceList[index] = res.result?.data || [];
setDeviceList([...deviceList])
}
})
}
}}>
{productKeyList && productKeyList.map((i: any, index: number) => {
return <AutoComplete.Option key={index} value={i.productKey}>{`${i.productKey}(${i.productName})`}</AutoComplete.Option>
})}
</AutoComplete>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="DeviceName">
{getFieldDecorator(`bridgeConfigs[${index}].bridgeDeviceName`, {
initialValue: item.bridgeDeviceName || undefined,
rules: [{ required: true, message: '网桥DeviceName' }],
})(
<AutoComplete placeholder="网桥DeviceName" allowClear onBlur={(value: SelectValue) => {
let secret = '';
if (value !== '' && value !== undefined) {
let data: any[] = deviceList[index].filter((i: any) => {
return i.deviceName === value
});
if (data.length > 0) {
secret = data[0].deviceSecret
}
}
let bridge = form.getFieldValue('bridgeConfigs');
bridge[index].bridgeDeviceSecret = secret;
form.setFieldsValue({
bridgeConfigs: bridge
})
}}>
{deviceList && deviceList.length > 0 && deviceList[index] && deviceList[index].length > 0 && deviceList[index].map((i: any, index: number) => {
return <AutoComplete.Option key={index} value={i.deviceName}>{i.deviceName}</AutoComplete.Option>
})}
</AutoComplete>
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="DeviceSecret">
{getFieldDecorator(`bridgeConfigs[${index}].bridgeDeviceSecret`, {
initialValue: item.bridgeDeviceSecret || undefined,
rules: [{ required: true, message: '网桥DeviceSecret' }],
})(
<Input placeholder="请输入" readOnly />
)}
</Form.Item>
</Col>
<Col span={12}>
<Form.Item label="HTTP2接口地址">
{getFieldDecorator(`bridgeConfigs[${index}].http2Endpoint`, { //https://a1WEHOY5PU7.iot-as-http2.cn-shanghai.aliyuncs.com
initialValue: item.http2Endpoint || undefined,
rules: [{ required: true, message: '请输入' }],
})(<Input placeholder="请输入" />)}
</Form.Item>
</Col>
</Row>
</div>
<div style={{ width: "10%", display: 'flex', justifyContent: 'center', marginTop: '45px' }}>
<Tooltip title="删除">
<MinusCircleOutlined
onClick={() => {
bridgeConfigs.splice(index, 1);
setBridgeConfigs([...bridgeConfigs]);
}}
/>
</Tooltip>
</div>
</div>
</div>
)
})
}
<Button icon="plus" type="link"
onClick={() => {
setBridgeConfigs([...bridgeConfigs, {
serverId: "",
bridgeProductKey: "",
bridgeDeviceName: "",
bridgeDeviceSecret: "",
http2Endpoint: ""
}]);
setDeviceList([...deviceList, {}])
}}
>添加</Button>
</Form>
</div>
</Modal>
)
}
Example #26
Source File: DynamicFormItemV2.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
LegacyDynamicFormItemV2 = forwardRef(
(
props: LegacyDynamicFormItemV2Props,
ref: Ref<LegacyDynamicFormItemV2Ref>
): React.ReactElement => {
const {
value,
columns,
onChange,
onAdd,
onRemove,
hideRemoveButton,
disabledRemoveButton,
hideAddButton,
disabledAddButton,
} = props;
const { t } = useTranslation(NS_FORMS);
const [form] = Form.useForm();
useImperativeHandle(ref, () => ({
validateFields: form.validateFields,
}));
useEffect(() => {
form.setFieldsValue({ [FORM_LIST_NAME]: value });
}, [value]);
const handleValuesChange = (
changedValues: DynamicFormValue,
allValues: DynamicFormValue
): void => {
onChange?.(allValues?.[FORM_LIST_NAME]);
};
const hasLabel = useMemo(
() => columns.some((column) => column.label),
[columns]
);
const defaultValues = useMemo(
() =>
columns.reduce(
(pre, cur) => ({ ...pre, [cur.name]: cur.defaultValue }),
{}
),
[columns]
);
return (
<div className={style.dynamicForm}>
<Form
form={form}
layout={"vertical"}
initialValues={value}
onValuesChange={handleValuesChange}
>
<Form.List name={FORM_LIST_NAME}>
{(fields, { add, remove }) => (
<>
{fields.map(({ key, name, ...restField }) => {
const rowValue = value?.[name];
return (
<Row key={key} className={style.row} gutter={12}>
{columns?.map((column) => (
<Col
key={column.name}
style={{ flex: column.flex ?? "1", minWidth: 0 }}
>
<ColumnComponent
hasLabel={hasLabel}
rowIndex={name}
column={column}
formValue={value}
field={{ key, name, ...restField }}
/>
</Col>
))}
<Col>
<Button
type="link"
className={classNames(style.removeRowBtn, [
{
[style.hidden]: getRealValue(hideRemoveButton, [
rowValue,
name,
]),
},
])}
disabled={getRealValue(disabledRemoveButton, [
rowValue,
name,
])}
onClick={() => {
const index = name;
const curValue =
form.getFieldValue(FORM_LIST_NAME)?.[index];
remove(index);
onRemove?.({ detail: curValue, index });
}}
>
<MinusCircleOutlined />
</Button>
</Col>
</Row>
);
})}
<Button
className={classNames(style.addRowBtn, [
{
[style.displayNone]: getRealValue(hideAddButton, [value]),
},
])}
disabled={getRealValue(disabledAddButton, [value])}
type="dashed"
onClick={() => {
const index = fields.length;
add(defaultValues);
onAdd?.({ detail: defaultValues, index });
}}
icon={<PlusOutlined />}
>
{t(`${NS_FORMS}:${K.ADD}`)}
</Button>
</>
)}
</Form.List>
</Form>
</div>
);
}
)
Example #27
Source File: DynamicCommonItem.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
export function CommonItem(
props: PropsWithChildren<CommonItemProps>,
ref: any
): React.ReactElement {
const { getFieldValue, setFieldsValue, validateFields } =
props.formElement.formUtils;
const [, forceUpdate] = useState();
const defaultRow = props.columns.reduce((obj: any, column) => {
obj[column.name] = column.defaultValue;
return obj;
}, {});
const encrypted = React.useRef([]);
const [rows, setRows] = useState<RowProps[]>(() => {
const value = getFieldValue(props.name) ?? props.value;
if (!value) {
return [];
}
encrypted.current = cloneDeep(value);
const columnMap = keyBy(props.columns, "name");
for (const row of value) {
for (const [k, v] of Object.entries(row)) {
if (columnMap[k]?.encrypt) {
row[k] = decrypt(v as string);
}
}
}
return value;
});
const [trackRows, setTrackRows] = useState(() => {
return Array(rows.length)
.fill(0)
.map(() => uniqueId());
});
const handleAdd = () => {
setTrackRows((trackRows) => [...trackRows, uniqueId()]);
setRows([...rows, defaultRow]);
// important! notify form to detect changes
setFieldsValue({
[props.name]: [...rows, defaultRow],
});
props.onAdd?.();
};
const handleRemove = (data: RowProps, index: number) => {
const filter = rows.filter((row, i) => index !== i);
// important! notify form to detect changes
setFieldsValue({
[props.name]: filter,
});
setRows(filter);
setTrackRows((trackRows) => trackRows.filter((_, i) => index !== i));
props.onRemove?.(data);
encrypted.current = filter;
props.onChange?.(encrypted.current);
};
const batchChange = (row, index) => {
const newRows = cloneDeep(rows);
newRows[index] = row;
setRows(newRows);
props.onChange?.(newRows);
setFieldsValue({
[props.name]: [...newRows],
});
};
const handleChange = (value: string, name: string, index: number) => {
const newRows = cloneDeep(rows);
newRows[index][name] = value;
setRows(newRows);
// todo(ice): notify with encrypted data
const rawData = cloneDeep(newRows);
const cols = props.columns.filter((col) => col.encrypt);
rawData.forEach((row) => {
cols.forEach((col) => {
if (row[col.name]) {
row[col.name] = encrypt(row[col.name]);
}
});
});
encrypted.current = rawData;
props.onChange?.(rawData);
};
const headers = (
<Row gutter={14} type="flex" className={style.headRow}>
{props.columns.map((item, index) => (
<Col key={index} style={{ flex: item.flex ?? 1 }}>
{item.label}
</Col>
))}
</Row>
);
useEffect(() => {
props.emitChangeOnInit && props.onChange?.(encrypted.current);
}, []);
useEffect(() => {
props.columns.forEach((column) => {
const triggerValidate = column.rules?.some(
(rule: BuiltInValidatorRule) => rule.uniq
);
if (triggerValidate) {
rows.forEach((item, rowIndex) => {
// 有值的时候才做唯一性校验
item[column.name] &&
validateFields([`${props.name}[${rowIndex}].${column.name}`]);
});
}
});
}, [rows]);
useEffect(() => {
const editedValue = props.manualEditedValue;
if (editedValue) {
setRows(editedValue);
setTrackRows(
Array(editedValue.length)
.fill(0)
.map(() => uniqueId())
);
setFieldsValue({
[props.name]: editedValue,
});
}
}, [props.manualEditedValue]);
const uniqValidator = (
rule: any,
value: string,
callback: (v?: boolean) => void
) => {
const reg = /\[(\d+)\]\.(\w+)/;
const [, rowIndex, name] = reg.exec(rule.field);
const valueList = rows
.filter((_, index) => index !== Number(rowIndex))
.map((item) => item[name]);
if (value && valueList.includes(value)) {
callback(false);
} else {
callback();
}
forceUpdate({});
};
const columns = props.columns.map((item) => {
const rules = (item.rules ?? []).map((rule: BuiltInValidatorRule) => {
if (rule.uniq) {
return {
validator: uniqValidator,
message: rule.message,
};
}
return rule;
});
return {
...item,
rules,
};
});
const hiddenDeleteBtnAtOneRow = props.oneRowRequired && rows.length === 1;
return (
<div
ref={ref}
className={classNames(style.dynamicForm, style.showBackground)}
>
{headers}
{rows.map((row, index) => {
return (
<Row type="flex" gutter={12} key={trackRows[index]}>
{React.cloneElement(props.children as React.ReactElement, {
rowIndex: index,
columns,
srcObjectId: props?.srcObjectId,
row: row,
prefixId: `${props.name}[${index}]`,
form: props.formElement.formUtils,
batchChange: (row) => {
batchChange(row, index);
},
onChange: (value: string, name: string) =>
handleChange(value, name, index),
})}
<Col className={style.removeColumn}>
{!props.hideDeleteButton && !hiddenDeleteBtnAtOneRow && (
<Button
type="link"
disabled={
props.disabledDeleteButton ||
props.rowDisabledhandler?.(row, index)
}
className={style.operatorBtn}
onClick={() => {
handleRemove(row, index);
}}
>
<MinusCircleOutlined />
</Button>
)}
</Col>
</Row>
);
})}
<Row gutter={12} className={style.addButton}>
<Col span={24}>
{!props.hideAddButton && (
<Button
style={{ width: "100%" }}
type="dashed"
onClick={() => handleAdd()}
disabled={props.disabledAddButton}
>
<PlusOutlined />
{i18n.t(`${NS_FORMS}:${K.ADD}`)}
</Button>
)}
</Col>
</Row>
</div>
);
}
Example #28
Source File: DynamicCommonItem.spec.tsx From next-basics with GNU General Public License v3.0 | 4 votes |
describe("DynamicCommonItem", () => {
const TestInput = (props: any) => {
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
props.onChange?.(value, "name");
};
return <Input onChange={handleChange} />;
};
it("should render empty row default", () => {
const props = {
name: "dynamic",
columns: [
{
name: "name",
label: "名称",
rules: [{ required: true, message: "这个是必填项" }],
},
{ name: "port", label: "端口" },
{ name: "type", label: "类型" },
],
formElement: {
formUtils: {
getFieldValue: jest.fn(),
},
} as any,
};
const wrapper = shallow(
<DynamicCommonItem {...props}>
<TestInput />
</DynamicCommonItem>
);
expect(wrapper.find(TestInput).length).toEqual(0);
});
it("should work with add/remove item", () => {
const setFieldsValueMock = jest.fn();
const props = {
name: "dynamic",
columns: [
{
name: "name",
label: "名称",
rules: [{ required: true, message: "这个是必填项" }],
},
{ name: "port", label: "端口" },
{ name: "type", label: "类型" },
],
formElement: {
formUtils: {
getFieldValue: () => [
{ name: "jack", port: "80", type: "tcp" },
{ name: "lucy", port: "50", type: "udp" },
],
setFieldsValue: setFieldsValueMock,
},
} as any,
onRemove: jest.fn(),
onChange: jest.fn(),
};
const wrapper = shallow(
<DynamicCommonItem {...props}>
<TestInput />
</DynamicCommonItem>
);
wrapper.find(TestInput).at(0).invoke("onChange")("abc", "name");
expect(props.onChange).toBeCalledWith([
{ name: "abc", port: "80", type: "tcp" },
{ name: "lucy", port: "50", type: "udp" },
]);
wrapper.find({ type: "link" }).at(1).simulate("click");
expect(setFieldsValueMock).toHaveBeenCalledWith({
dynamic: [{ name: "abc", port: "80", type: "tcp" }],
});
wrapper.find({ type: "dashed" }).simulate("click");
expect(setFieldsValueMock).toHaveBeenCalledWith({
dynamic: [
{ name: "abc", port: "80", type: "tcp" },
{ name: undefined, port: undefined, type: undefined },
],
});
expect(wrapper.find(MinusCircleOutlined)).toHaveLength(2);
expect(wrapper.find(PlusOutlined)).toHaveLength(1);
wrapper.setProps({
hideAddButton: true,
hideDeleteButton: true,
});
expect(wrapper.find(MinusCircleOutlined)).toHaveLength(0);
expect(wrapper.find(PlusOutlined)).toHaveLength(0);
});
it("custom Validator", async () => {
const props = {
columns: [
{
name: "name",
label: "名称",
rules: [
{ required: true, message: "这个是必填项" },
{ uniq: true, message: "名称必须唯一" },
],
},
{ name: "port", label: "端口" },
{ name: "type", label: "类型" },
],
formElement: {
formUtils: {
getFieldValue: () => [
{ name: "jack", port: "80", type: "tcp" },
{ name: "jack", port: "90", type: "udp" },
],
setFieldsValue: jest.fn(),
getFieldDecorator: () => (comp: React.Component) => comp,
validateFields: jest.fn(),
},
} as any,
onRemove: jest.fn(),
onChange: jest.fn(),
};
const wrapper = mount(
<DynamicCommonItem {...props}>
<TestInput />
</DynamicCommonItem>
);
wrapper.find(Input).at(0).simulate("change", {
target: "lucy",
});
const ValidatorFn = jest.fn();
const customValidator = wrapper.find(TestInput).at(0).prop("columns")[0]
.rules[1].validator;
act(() => {
customValidator({ field: "dynamic[0].name" }, "jack", ValidatorFn);
});
expect(ValidatorFn).toHaveBeenCalled();
});
it("should set dynamic value", () => {
const setFieldsValueMock = jest.fn();
const props = {
name: "dynamic",
columns: [
{
name: "name",
label: "名称",
rules: [{ required: true, message: "这个是必填项" }],
},
{ name: "port", label: "端口" },
{ name: "type", label: "类型" },
],
formElement: {
formUtils: {
getFieldValue: jest.fn(),
setFieldsValue: setFieldsValueMock,
},
} as any,
};
const wrapper = mount(
<DynamicCommonItem {...props}>
<TestInput />
</DynamicCommonItem>
);
wrapper.setProps({
manualEditedValue: [{ name: "test", port: "70", type: "udp" }],
});
expect(setFieldsValueMock).toHaveBeenCalledWith({
dynamic: [{ name: "test", port: "70", type: "udp" }],
});
});
it("hidden delete button if the dynamic value only one element", () => {
const props = {
name: "dynamic",
oneRowRequired: true,
columns: [
{
name: "name",
label: "名称",
rules: [{ required: true, message: "这个是必填项" }],
},
{ name: "port", label: "端口" },
{ name: "type", label: "类型" },
],
formElement: {
formUtils: {
getFieldValue: () => [
{ name: "jack", port: "80", type: "tcp" },
{ name: "lucy", port: "50", type: "udp" },
],
setFieldsValue: jest.fn(),
},
} as any,
};
const wrapper = shallow(
<DynamicCommonItem {...props}>
<TestInput />
</DynamicCommonItem>
);
expect(wrapper.find(MinusCircleOutlined)).toHaveLength(2);
wrapper.find({ type: "link" }).at(0).simulate("click");
wrapper.update();
expect(wrapper.find(MinusCircleOutlined)).toHaveLength(0);
});
it("disabled removed button of the specified row ", () => {
const props = {
name: "dynamic",
rowDisabledhandler: (row: any, index: number) => index === 0,
columns: [
{
name: "name",
label: "名称",
rules: [{ required: true, message: "这个是必填项" }],
},
{ name: "port", label: "端口" },
{ name: "type", label: "类型" },
],
formElement: {
formUtils: {
getFieldValue: () => [
{ name: "jack", port: "80", type: "tcp" },
{ name: "lucy", port: "50", type: "udp" },
],
setFieldsValue: jest.fn(),
},
} as any,
};
const wrapper = shallow(
<DynamicCommonItem {...props}>
<TestInput />
</DynamicCommonItem>
);
expect(wrapper.find({ type: "link" }).at(0).prop("disabled")).toEqual(true);
});
});
Example #29
Source File: Icon.tsx From html2sketch with MIT License | 4 votes |
IconSymbol: FC = () => {
return (
<Row>
{/*<CaretUpOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/1.CaretUpOutlined'}*/}
{/*/>*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.MailOutlined'}*/}
{/*/>*/}
{/*<StepBackwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
{/*<StepForwardOutlined*/}
{/* className="icon"*/}
{/* symbolName={'1.General/2.Icons/2.StepBackwardOutlined'}*/}
{/*/>*/}
<StepForwardOutlined />
<ShrinkOutlined />
<ArrowsAltOutlined />
<DownOutlined />
<UpOutlined />
<LeftOutlined />
<RightOutlined />
<CaretUpOutlined />
<CaretDownOutlined />
<CaretLeftOutlined />
<CaretRightOutlined />
<VerticalAlignTopOutlined />
<RollbackOutlined />
<FastBackwardOutlined />
<FastForwardOutlined />
<DoubleRightOutlined />
<DoubleLeftOutlined />
<VerticalLeftOutlined />
<VerticalRightOutlined />
<VerticalAlignMiddleOutlined />
<VerticalAlignBottomOutlined />
<ForwardOutlined />
<BackwardOutlined />
<EnterOutlined />
<RetweetOutlined />
<SwapOutlined />
<SwapLeftOutlined />
<SwapRightOutlined />
<ArrowUpOutlined />
<ArrowDownOutlined />
<ArrowLeftOutlined />
<ArrowRightOutlined />
<LoginOutlined />
<LogoutOutlined />
<MenuFoldOutlined />
<MenuUnfoldOutlined />
<BorderBottomOutlined />
<BorderHorizontalOutlined />
<BorderInnerOutlined />
<BorderOuterOutlined />
<BorderLeftOutlined />
<BorderRightOutlined />
<BorderTopOutlined />
<BorderVerticleOutlined />
<PicCenterOutlined />
<PicLeftOutlined />
<PicRightOutlined />
<RadiusBottomleftOutlined />
<RadiusBottomrightOutlined />
<RadiusUpleftOutlined />
<RadiusUprightOutlined />
<FullscreenOutlined />
<FullscreenExitOutlined />
<QuestionOutlined />
<PauseOutlined />
<MinusOutlined />
<PauseCircleOutlined />
<InfoOutlined />
<CloseOutlined />
<ExclamationOutlined />
<CheckOutlined />
<WarningOutlined />
<IssuesCloseOutlined />
<StopOutlined />
<EditOutlined />
<CopyOutlined />
<ScissorOutlined />
<DeleteOutlined />
<SnippetsOutlined />
<DiffOutlined />
<HighlightOutlined />
<AlignCenterOutlined />
<AlignLeftOutlined />
<AlignRightOutlined />
<BgColorsOutlined />
<BoldOutlined />
<ItalicOutlined />
<UnderlineOutlined />
<StrikethroughOutlined />
<RedoOutlined />
<UndoOutlined />
<ZoomInOutlined />
<ZoomOutOutlined />
<FontColorsOutlined />
<FontSizeOutlined />
<LineHeightOutlined />
<SortAscendingOutlined />
<SortDescendingOutlined />
<DragOutlined />
<OrderedListOutlined />
<UnorderedListOutlined />
<RadiusSettingOutlined />
<ColumnWidthOutlined />
<ColumnHeightOutlined />
<AreaChartOutlined />
<PieChartOutlined />
<BarChartOutlined />
<DotChartOutlined />
<LineChartOutlined />
<RadarChartOutlined />
<HeatMapOutlined />
<FallOutlined />
<RiseOutlined />
<StockOutlined />
<BoxPlotOutlined />
<FundOutlined />
<SlidersOutlined />
<AndroidOutlined />
<AppleOutlined />
<WindowsOutlined />
<IeOutlined />
<ChromeOutlined />
<GithubOutlined />
<AliwangwangOutlined />
<DingdingOutlined />
<WeiboSquareOutlined />
<WeiboCircleOutlined />
<TaobaoCircleOutlined />
<Html5Outlined />
<WeiboOutlined />
<TwitterOutlined />
<WechatOutlined />
<AlipayCircleOutlined />
<TaobaoOutlined />
<SkypeOutlined />
<FacebookOutlined />
<CodepenOutlined />
<CodeSandboxOutlined />
<AmazonOutlined />
<GoogleOutlined />
<AlipayOutlined />
<AntDesignOutlined />
<AntCloudOutlined />
<ZhihuOutlined />
<SlackOutlined />
<SlackSquareOutlined />
<BehanceSquareOutlined />
<DribbbleOutlined />
<DribbbleSquareOutlined />
<InstagramOutlined />
<YuqueOutlined />
<AlibabaOutlined />
<YahooOutlined />
<RedditOutlined />
<SketchOutlined />
<AccountBookOutlined />
<AlertOutlined />
<ApartmentOutlined />
<ApiOutlined />
<QqOutlined />
<MediumWorkmarkOutlined />
<GitlabOutlined />
<MediumOutlined />
<GooglePlusOutlined />
<AppstoreAddOutlined />
<AppstoreOutlined />
<AudioOutlined />
<AudioMutedOutlined />
<AuditOutlined />
<BankOutlined />
<BarcodeOutlined />
<BarsOutlined />
<BellOutlined />
<BlockOutlined />
<BookOutlined />
<BorderOutlined />
<BranchesOutlined />
<BuildOutlined />
<BulbOutlined />
<CalculatorOutlined />
<CalendarOutlined />
<CameraOutlined />
<CarOutlined />
<CarryOutOutlined />
<CiCircleOutlined />
<CiOutlined />
<CloudOutlined />
<ClearOutlined />
<ClusterOutlined />
<CodeOutlined />
<CoffeeOutlined />
<CompassOutlined />
<CompressOutlined />
<ContactsOutlined />
<ContainerOutlined />
<ControlOutlined />
<CopyrightCircleOutlined />
<CopyrightOutlined />
<CreditCardOutlined />
<CrownOutlined />
<CustomerServiceOutlined />
<DashboardOutlined />
<DatabaseOutlined />
<DeleteColumnOutlined />
<DeleteRowOutlined />
<DisconnectOutlined />
<DislikeOutlined />
<DollarCircleOutlined />
<DollarOutlined />
<DownloadOutlined />
<EllipsisOutlined />
<EnvironmentOutlined />
<EuroCircleOutlined />
<EuroOutlined />
<ExceptionOutlined />
<ExpandAltOutlined />
<ExpandOutlined />
<ExperimentOutlined />
<ExportOutlined />
<EyeOutlined />
<FieldBinaryOutlined />
<FieldNumberOutlined />
<FieldStringOutlined />
<DesktopOutlined />
<DingtalkOutlined />
<FileAddOutlined />
<FileDoneOutlined />
<FileExcelOutlined />
<FileExclamationOutlined />
<FileOutlined />
<FileImageOutlined />
<FileJpgOutlined />
<FileMarkdownOutlined />
<FilePdfOutlined />
<FilePptOutlined />
<FileProtectOutlined />
<FileSearchOutlined />
<FileSyncOutlined />
<FileTextOutlined />
<FileUnknownOutlined />
<FileWordOutlined />
<FilterOutlined />
<FireOutlined />
<FlagOutlined />
<FolderAddOutlined />
<FolderOutlined />
<FolderOpenOutlined />
<ForkOutlined />
<FormatPainterOutlined />
<FrownOutlined />
<FunctionOutlined />
<FunnelPlotOutlined />
<GatewayOutlined />
<GifOutlined />
<GiftOutlined />
<GlobalOutlined />
<GoldOutlined />
<GroupOutlined />
<HddOutlined />
<HeartOutlined />
<HistoryOutlined />
<HomeOutlined />
<HourglassOutlined />
<IdcardOutlined />
<ImportOutlined />
<InboxOutlined />
<InsertRowAboveOutlined />
<InsertRowBelowOutlined />
<InsertRowLeftOutlined />
<InsertRowRightOutlined />
<InsuranceOutlined />
<InteractionOutlined />
<KeyOutlined />
<LaptopOutlined />
<LayoutOutlined />
<LikeOutlined />
<LineOutlined />
<LinkOutlined />
<Loading3QuartersOutlined />
<LoadingOutlined />
<LockOutlined />
<MailOutlined />
<ManOutlined />
<MedicineBoxOutlined />
<MehOutlined />
<MenuOutlined />
<MergeCellsOutlined />
<MessageOutlined />
<MobileOutlined />
<MoneyCollectOutlined />
<MonitorOutlined />
<MoreOutlined />
<NodeCollapseOutlined />
<NodeExpandOutlined />
<NodeIndexOutlined />
<NotificationOutlined />
<NumberOutlined />
<PaperClipOutlined />
<PartitionOutlined />
<PayCircleOutlined />
<PercentageOutlined />
<PhoneOutlined />
<PictureOutlined />
<PoundCircleOutlined />
<PoundOutlined />
<PoweroffOutlined />
<PrinterOutlined />
<ProfileOutlined />
<ProjectOutlined />
<PropertySafetyOutlined />
<PullRequestOutlined />
<PushpinOutlined />
<QrcodeOutlined />
<ReadOutlined />
<ReconciliationOutlined />
<RedEnvelopeOutlined />
<ReloadOutlined />
<RestOutlined />
<RobotOutlined />
<RocketOutlined />
<SafetyCertificateOutlined />
<SafetyOutlined />
<ScanOutlined />
<ScheduleOutlined />
<SearchOutlined />
<SecurityScanOutlined />
<SelectOutlined />
<SendOutlined />
<SettingOutlined />
<ShakeOutlined />
<ShareAltOutlined />
<ShopOutlined />
<ShoppingCartOutlined />
<ShoppingOutlined />
<SisternodeOutlined />
<SkinOutlined />
<SmileOutlined />
<SolutionOutlined />
<SoundOutlined />
<SplitCellsOutlined />
<StarOutlined />
<SubnodeOutlined />
<SyncOutlined />
<TableOutlined />
<TabletOutlined />
<TagOutlined />
<TagsOutlined />
<TeamOutlined />
<ThunderboltOutlined />
<ToTopOutlined />
<ToolOutlined />
<TrademarkCircleOutlined />
<TrademarkOutlined />
<TransactionOutlined />
<TrophyOutlined />
<UngroupOutlined />
<UnlockOutlined />
<UploadOutlined />
<UsbOutlined />
<UserAddOutlined />
<UserDeleteOutlined />
<UserOutlined />
<UserSwitchOutlined />
<UsergroupAddOutlined />
<UsergroupDeleteOutlined />
<VideoCameraOutlined />
<WalletOutlined />
<WifiOutlined />
<BorderlessTableOutlined />
<WomanOutlined />
<BehanceOutlined />
<DropboxOutlined />
<DeploymentUnitOutlined />
<UpCircleOutlined />
<DownCircleOutlined />
<LeftCircleOutlined />
<RightCircleOutlined />
<UpSquareOutlined />
<DownSquareOutlined />
<LeftSquareOutlined />
<RightSquareOutlined />
<PlayCircleOutlined />
<QuestionCircleOutlined />
<PlusCircleOutlined />
<PlusSquareOutlined />
<MinusSquareOutlined />
<MinusCircleOutlined />
<InfoCircleOutlined />
<ExclamationCircleOutlined />
<CloseCircleOutlined />
<CloseSquareOutlined />
<CheckCircleOutlined />
<CheckSquareOutlined />
<ClockCircleOutlined />
<FormOutlined />
<DashOutlined />
<SmallDashOutlined />
<YoutubeOutlined />
<CodepenCircleOutlined />
<AliyunOutlined />
<PlusOutlined />
<LinkedinOutlined />
<AimOutlined />
<BugOutlined />
<CloudDownloadOutlined />
<CloudServerOutlined />
<CloudSyncOutlined />
<CloudUploadOutlined />
<CommentOutlined />
<ConsoleSqlOutlined />
<EyeInvisibleOutlined />
<FileGifOutlined />
<DeliveredProcedureOutlined />
<FieldTimeOutlined />
<FileZipOutlined />
<FolderViewOutlined />
<FundProjectionScreenOutlined />
<FundViewOutlined />
<MacCommandOutlined />
<PlaySquareOutlined />
<OneToOneOutlined />
<RotateLeftOutlined />
<RotateRightOutlined />
<SaveOutlined />
<SwitcherOutlined />
<TranslationOutlined />
<VerifiedOutlined />
<VideoCameraAddOutlined />
<WhatsAppOutlined />
{/*</Col>*/}
</Row>
);
}