mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
290 lines
8.8 KiB
JavaScript
290 lines
8.8 KiB
JavaScript
/*
|
|
* Copyright (c) 2026 by Christian Kellner.
|
|
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
|
*/
|
|
|
|
import { useState } from 'react';
|
|
|
|
import { transform } from '../../../../../services/transformer/notificationAdapterTransformer';
|
|
import { xhrPost } from '../../../../../services/xhr';
|
|
import Help from './NotificationHelpDisplay';
|
|
import { useSelector } from '../../../../../services/state/store';
|
|
import { Banner, Button, Form, Modal, Select, Switch } from '@douyinfe/semi-ui-19';
|
|
|
|
import './NotificationAdapterMutator.less';
|
|
import { useScreenWidth } from '../../../../../hooks/screenWidth.js';
|
|
import { useTranslation } from '../../../../../services/i18n/i18n.jsx';
|
|
|
|
const sortAdapter = (a, b) => {
|
|
if (a.name < b.name) {
|
|
return -1;
|
|
}
|
|
if (a.name > b.name) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
const validate = (selectedAdapter, t) => {
|
|
const results = [];
|
|
for (let uiElement of Object.values(selectedAdapter.fields || [])) {
|
|
if (uiElement.value == null && !uiElement.optional && uiElement.type !== 'boolean') {
|
|
results.push(t('notification.validationAllMandatory'));
|
|
continue;
|
|
}
|
|
if (uiElement.type === 'boolean' && typeof uiElement.value !== 'boolean') {
|
|
uiElement.value = false;
|
|
}
|
|
if (uiElement.type === 'number') {
|
|
const numberValue = parseFloat(uiElement.value);
|
|
if (isNaN(numberValue) || numberValue < 0) {
|
|
results.push(t('notification.validationNumberField'));
|
|
continue;
|
|
}
|
|
}
|
|
if (uiElement.type === 'boolean' && typeof uiElement.value !== 'boolean') {
|
|
results.push(t('notification.validationBooleanField'));
|
|
continue;
|
|
}
|
|
if (typeof uiElement.value === 'string' && uiElement.value.length === 0 && !uiElement.optional) {
|
|
results.push(t('notification.validationAllMandatory'));
|
|
}
|
|
}
|
|
|
|
return [...new Set(results)];
|
|
};
|
|
|
|
function spreadPrefilledAdapterWithValues(prefilled, fields) {
|
|
if (prefilled != null && fields != null) {
|
|
Object.keys(fields).forEach((fieldKey) => {
|
|
prefilled.fields[fieldKey].value = fields[fieldKey];
|
|
});
|
|
}
|
|
}
|
|
|
|
export default function NotificationAdapterMutator({
|
|
title,
|
|
description,
|
|
onVisibilityChanged,
|
|
visible = false,
|
|
selected = [],
|
|
editNotificationAdapter,
|
|
onData,
|
|
} = {}) {
|
|
const t = useTranslation();
|
|
const adapter = useSelector((state) => state.notificationAdapter);
|
|
|
|
const preFilledSelectedAdapter =
|
|
editNotificationAdapter == null
|
|
? null
|
|
: adapter.filter((a) => a != null).find((a) => a.id === editNotificationAdapter.id);
|
|
|
|
spreadPrefilledAdapterWithValues(preFilledSelectedAdapter, editNotificationAdapter?.fields);
|
|
|
|
const [selectedAdapter, setSelectedAdapter] = useState(preFilledSelectedAdapter);
|
|
const [validationMessage, setValidationMessage] = useState(null);
|
|
const [successMessage, setSuccessMessage] = useState(null);
|
|
|
|
const width = useScreenWidth();
|
|
const isMobile = width <= 850;
|
|
|
|
const onSubmit = (doStore) => {
|
|
if (doStore) {
|
|
const validationResults = validate(selectedAdapter, t);
|
|
if (validationResults.length > 0) {
|
|
setValidationMessage(validationResults.join('<br/>'));
|
|
return;
|
|
}
|
|
|
|
onData(
|
|
transform({
|
|
id: selectedAdapter.id,
|
|
name: selectedAdapter.name,
|
|
fields: selectedAdapter.fields || {},
|
|
}),
|
|
);
|
|
|
|
setSelectedAdapter(null);
|
|
onVisibilityChanged(false);
|
|
} else {
|
|
setSelectedAdapter(null);
|
|
onVisibilityChanged(false);
|
|
}
|
|
};
|
|
|
|
const onTry = () => {
|
|
setValidationMessage(null);
|
|
setSuccessMessage(null);
|
|
|
|
const validationResults = validate(selectedAdapter, t);
|
|
if (validationResults.length > 0) {
|
|
setValidationMessage(validationResults.join('<br/>'));
|
|
return;
|
|
}
|
|
|
|
xhrPost('/api/jobs/notificationAdapter/try', {
|
|
id: selectedAdapter.id,
|
|
fields: {
|
|
...selectedAdapter.fields,
|
|
},
|
|
})
|
|
.then(() => {
|
|
setSuccessMessage(t('notification.trySuccess'));
|
|
})
|
|
.catch((error) => setValidationMessage(t('notification.tryError', { error: error.json.message })));
|
|
};
|
|
|
|
const setValue = (selectedAdapter, uiElement, key, value) => {
|
|
uiElement.value = value;
|
|
|
|
setSelectedAdapter({
|
|
...selectedAdapter,
|
|
fields: {
|
|
...selectedAdapter.fields,
|
|
[key]: {
|
|
...selectedAdapter.fields[key],
|
|
value,
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
const getFieldsFor = (selectedAdapter_) => {
|
|
const selectedAdapter = Object.assign({}, selectedAdapter_);
|
|
|
|
return Object.keys(selectedAdapter.fields || []).map((key) => {
|
|
const uiElement = selectedAdapter.fields[key];
|
|
|
|
return (
|
|
<Form key={key}>
|
|
{uiElement.type === 'boolean' ? (
|
|
<div>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
<Switch
|
|
checked={uiElement.value || false}
|
|
onChange={(checked) => {
|
|
setValue(selectedAdapter, uiElement, key, checked);
|
|
}}
|
|
/>
|
|
{uiElement.label}
|
|
</div>
|
|
{uiElement.description && (
|
|
<div className="semi-form-field-extra" style={{ marginTop: '4px' }}>
|
|
{uiElement.description}
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
<Form.Input
|
|
style={{ width: '100%' }}
|
|
field={uiElement.label}
|
|
type={uiElement.type}
|
|
initValue={uiElement.value ?? ''}
|
|
placeholder={uiElement.label}
|
|
label={uiElement.label}
|
|
extraText={uiElement.description}
|
|
onChange={(value) => {
|
|
setValue(selectedAdapter, uiElement, key, value);
|
|
}}
|
|
/>
|
|
)}
|
|
</Form>
|
|
);
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
title={title != null ? title : t('notification.defaultTitle')}
|
|
visible={visible}
|
|
style={{ width: isMobile ? '95%' : '50rem' }}
|
|
onCancel={() => onSubmit(false)}
|
|
footer={
|
|
<div>
|
|
<Button type="secondary" disabled={selectedAdapter == null} style={{ float: 'left' }} onClick={onTry}>
|
|
{t('notification.try')}
|
|
</Button>
|
|
<Button theme="light" type="tertiary" onClick={() => onSubmit(false)}>
|
|
{t('notification.cancel')}
|
|
</Button>
|
|
<Button theme="solid" type="primary" onClick={() => onSubmit(true)}>
|
|
{t('notification.save')}
|
|
</Button>
|
|
</div>
|
|
}
|
|
>
|
|
{description != null ? <p>{description}</p> : <p>{t('notification.description')}</p>}
|
|
|
|
<Select
|
|
filter
|
|
placeholder={t('notification.selectPlaceholder')}
|
|
className="providerMutator__fields"
|
|
value={selectedAdapter == null ? '' : selectedAdapter.id}
|
|
optionList={adapter
|
|
.filter((a) => a != null)
|
|
.map((a) => {
|
|
return {
|
|
value: a.id,
|
|
label: a.name,
|
|
};
|
|
})
|
|
//filter out those, that have already been selected
|
|
.filter((option) =>
|
|
editNotificationAdapter != null
|
|
? true
|
|
: selected.find((selectedOption) => selectedOption.id === option.value) == null,
|
|
)
|
|
.sort(sortAdapter)}
|
|
onChange={(value) => {
|
|
setSuccessMessage(null);
|
|
setValidationMessage(null);
|
|
const selectedAdapter = adapter.find((a) => a.id === value);
|
|
setSelectedAdapter(Object.assign({}, selectedAdapter));
|
|
}}
|
|
/>
|
|
<br />
|
|
<br />
|
|
{selectedAdapter != null && (
|
|
<>
|
|
<i>{selectedAdapter.description}</i>
|
|
<br />
|
|
<br />
|
|
{selectedAdapter.readme != null && <Help readme={selectedAdapter.readme} />}
|
|
<br />
|
|
|
|
{validationMessage != null && (
|
|
<Banner
|
|
fullMode={false}
|
|
type="danger"
|
|
closeIcon={null}
|
|
title={
|
|
<div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
|
|
{t('notification.errorTitle')}
|
|
</div>
|
|
}
|
|
style={{ marginBottom: '1rem' }}
|
|
description={<p dangerouslySetInnerHTML={{ __html: validationMessage }} />}
|
|
/>
|
|
)}
|
|
{successMessage != null && (
|
|
<Banner
|
|
fullMode={false}
|
|
type="success"
|
|
closeIcon={null}
|
|
title={
|
|
<div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
|
|
{t('notification.successTitle')}
|
|
</div>
|
|
}
|
|
style={{ marginBottom: '1rem' }}
|
|
description={<p dangerouslySetInnerHTML={{ __html: successMessage }} />}
|
|
/>
|
|
)}
|
|
|
|
{getFieldsFor(selectedAdapter)}
|
|
</>
|
|
)}
|
|
</Modal>
|
|
);
|
|
}
|