2021-05-30 09:37:45 +02:00
|
|
|
import React from 'react';
|
|
|
|
|
|
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
|
|
|
|
2023-03-20 08:52:13 +01:00
|
|
|
import { Divider, Input, Radio, TimePicker, Button, RadioGroup } from '@douyinfe/semi-ui';
|
|
|
|
|
import { InputNumber } from '@douyinfe/semi-ui';
|
2021-05-30 09:37:45 +02:00
|
|
|
import Headline from '../../components/headline/Headline';
|
|
|
|
|
import { xhrPost } from '../../services/xhr';
|
2022-03-27 19:42:58 +02:00
|
|
|
import { SegmentPart } from '../../components/segment/SegmentPart';
|
2023-03-20 08:52:13 +01:00
|
|
|
import { Banner, Toast } from '@douyinfe/semi-ui';
|
|
|
|
|
import { IconSave, IconCalendar, IconKey, IconRefresh, IconSignal } from '@douyinfe/semi-icons';
|
2021-05-30 09:37:45 +02:00
|
|
|
import './GeneralSettings.less';
|
|
|
|
|
|
2023-03-20 08:52:13 +01:00
|
|
|
function formatFromTimestamp(ts) {
|
|
|
|
|
const date = new Date(ts);
|
|
|
|
|
return `${date.getHours()}:${date.getMinutes() > 9 ? date.getMinutes() : '0' + date.getMinutes()}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatFromTBackend(time) {
|
|
|
|
|
if (time == null || time.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
const date = new Date();
|
|
|
|
|
const split = time.split(':');
|
|
|
|
|
date.setHours(split[0]);
|
|
|
|
|
date.setMinutes(split[1]);
|
|
|
|
|
return date.getTime();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const GeneralSettings = function GeneralSettings() {
|
2021-05-30 09:37:45 +02:00
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
const [loading, setLoading] = React.useState(true);
|
|
|
|
|
|
|
|
|
|
const settings = useSelector((state) => state.generalSettings.settings);
|
|
|
|
|
|
|
|
|
|
const [interval, setInterval] = React.useState('');
|
|
|
|
|
const [port, setPort] = React.useState('');
|
|
|
|
|
const [scrapingAntApiKey, setScrapingAntApiKey] = React.useState('');
|
2022-06-13 08:10:30 +02:00
|
|
|
const [scrapingAntProxy, setScrapingAntProxy] = React.useState('');
|
2021-05-30 09:37:45 +02:00
|
|
|
const [workingHourFrom, setWorkingHourFrom] = React.useState(null);
|
|
|
|
|
const [workingHourTo, setWorkingHourTo] = React.useState(null);
|
2022-12-19 21:10:00 +01:00
|
|
|
React.useEffect(() => {
|
|
|
|
|
async function init() {
|
|
|
|
|
await dispatch.generalSettings.getGeneralSettings();
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
2023-03-20 08:52:13 +01:00
|
|
|
|
2022-12-19 21:10:00 +01:00
|
|
|
init();
|
2021-05-30 09:37:45 +02:00
|
|
|
}, []);
|
|
|
|
|
|
2022-12-19 21:10:00 +01:00
|
|
|
React.useEffect(() => {
|
|
|
|
|
async function init() {
|
|
|
|
|
setInterval(settings?.interval);
|
|
|
|
|
setPort(settings?.port);
|
|
|
|
|
setScrapingAntApiKey(settings?.scrapingAnt?.apiKey);
|
|
|
|
|
setWorkingHourFrom(settings?.workingHours?.from);
|
|
|
|
|
setWorkingHourTo(settings?.workingHours?.to);
|
|
|
|
|
setScrapingAntProxy(settings?.scrapingAnt?.proxy || 'datacenter');
|
|
|
|
|
}
|
2023-03-20 08:52:13 +01:00
|
|
|
|
2022-12-19 21:10:00 +01:00
|
|
|
init();
|
2021-05-30 09:37:45 +02:00
|
|
|
}, [settings]);
|
|
|
|
|
|
|
|
|
|
const nullOrEmpty = (val) => val == null || val.length === 0;
|
|
|
|
|
|
|
|
|
|
const throwMessage = (message, type) => {
|
2023-03-20 08:52:13 +01:00
|
|
|
if (type === 'error') {
|
|
|
|
|
Toast.error(message);
|
|
|
|
|
} else {
|
|
|
|
|
Toast.success(message);
|
|
|
|
|
}
|
2021-05-30 09:37:45 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onStore = async () => {
|
|
|
|
|
if (nullOrEmpty(interval)) {
|
|
|
|
|
throwMessage('Interval may not be empty.', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (nullOrEmpty(port)) {
|
|
|
|
|
throwMessage('Port may not be empty.', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (
|
|
|
|
|
(!nullOrEmpty(workingHourFrom) && nullOrEmpty(workingHourTo)) ||
|
|
|
|
|
(nullOrEmpty(workingHourFrom) && !nullOrEmpty(workingHourTo))
|
|
|
|
|
) {
|
|
|
|
|
throwMessage('Working hours to and from must be set if either to or from has been set before.', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await xhrPost('/api/admin/generalSettings', {
|
|
|
|
|
interval,
|
|
|
|
|
port,
|
|
|
|
|
scrapingAnt: {
|
|
|
|
|
apiKey: scrapingAntApiKey,
|
2022-06-13 08:10:30 +02:00
|
|
|
proxy: scrapingAntProxy,
|
2021-05-30 09:37:45 +02:00
|
|
|
},
|
|
|
|
|
workingHours: {
|
|
|
|
|
from: workingHourFrom,
|
|
|
|
|
to: workingHourTo,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} catch (exception) {
|
|
|
|
|
console.error(exception);
|
|
|
|
|
throwMessage('Error while trying to store settings.', 'error');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
throwMessage('Settings stored successfully. You MUST restart Fredy.', 'success');
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
{!loading && (
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
<Headline text="General Settings" />
|
2023-03-20 08:52:13 +01:00
|
|
|
<Banner
|
|
|
|
|
fullMode={false}
|
|
|
|
|
type="info"
|
|
|
|
|
closeIcon={null}
|
|
|
|
|
title={<div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>Info</div>}
|
|
|
|
|
style={{ marginBottom: '1rem' }}
|
|
|
|
|
description="If you change any settings, you must restart Fredy afterwards."
|
|
|
|
|
/>
|
|
|
|
|
<div>
|
2021-05-30 09:37:45 +02:00
|
|
|
<SegmentPart
|
|
|
|
|
name="Interval"
|
|
|
|
|
helpText="Interval in minutes for running queries against the configured services."
|
2023-03-20 08:52:13 +01:00
|
|
|
Icon={IconRefresh}
|
2021-05-30 09:37:45 +02:00
|
|
|
>
|
2023-03-20 08:52:13 +01:00
|
|
|
<InputNumber
|
|
|
|
|
min={0}
|
|
|
|
|
max={1440}
|
2021-05-30 09:37:45 +02:00
|
|
|
placeholder="Interval in minutes"
|
2023-03-20 08:52:13 +01:00
|
|
|
value={interval}
|
|
|
|
|
formatter={(value) => `${value}`.replace(/\D/g, '')}
|
|
|
|
|
onChange={(value) => setInterval(value)}
|
|
|
|
|
suffix={'minutes'}
|
2021-05-30 09:37:45 +02:00
|
|
|
/>
|
|
|
|
|
</SegmentPart>
|
2023-03-20 08:52:13 +01:00
|
|
|
<Divider margin="1rem" />
|
|
|
|
|
<SegmentPart name="Port" helpText="Port on which Fredy is running." Icon={IconSignal}>
|
|
|
|
|
<InputNumber
|
|
|
|
|
min={0}
|
|
|
|
|
max={99999}
|
2021-05-30 09:37:45 +02:00
|
|
|
placeholder="Port"
|
2023-03-20 08:52:13 +01:00
|
|
|
value={port}
|
|
|
|
|
formatter={(value) => `${value}`.replace(/\D/g, '')}
|
|
|
|
|
onChange={(value) => setPort(value)}
|
2021-05-30 09:37:45 +02:00
|
|
|
/>
|
|
|
|
|
</SegmentPart>
|
2023-03-20 08:52:13 +01:00
|
|
|
<Divider margin="1rem" />
|
2021-05-30 09:37:45 +02:00
|
|
|
<SegmentPart
|
|
|
|
|
name="ScrapingAnt Api Key"
|
|
|
|
|
helpText="The api key for ScrapingAnt is used to be able to scrape Immoscout."
|
2023-03-20 08:52:13 +01:00
|
|
|
Icon={IconKey}
|
2021-05-30 09:37:45 +02:00
|
|
|
>
|
2023-03-20 08:52:13 +01:00
|
|
|
<Input
|
2021-05-30 09:37:45 +02:00
|
|
|
type="text"
|
|
|
|
|
placeholder="ScrapingAnt Api Key"
|
2023-03-20 08:52:13 +01:00
|
|
|
value={scrapingAntApiKey}
|
|
|
|
|
onChange={(val) => setScrapingAntApiKey(val)}
|
2021-05-30 09:37:45 +02:00
|
|
|
/>
|
|
|
|
|
</SegmentPart>
|
2023-03-20 08:52:13 +01:00
|
|
|
<Divider margin="1rem" />
|
2022-06-13 08:10:30 +02:00
|
|
|
<SegmentPart
|
|
|
|
|
name="ScrapingAnt proxy settings"
|
|
|
|
|
helpText="Scraping ant provides different proxies."
|
2023-03-20 08:52:13 +01:00
|
|
|
Icon={IconKey}
|
2022-06-13 08:10:30 +02:00
|
|
|
>
|
2023-03-20 08:52:13 +01:00
|
|
|
<Banner
|
|
|
|
|
fullMode={false}
|
|
|
|
|
type="info"
|
|
|
|
|
closeIcon={null}
|
|
|
|
|
title={
|
|
|
|
|
<div style={{ fontWeight: 600, fontSize: '14px', lineHeight: '20px' }}>
|
|
|
|
|
ScrapingAnt is needed to scrape Immoscout. ScrapingAnt itself is using 2 different types of proxies
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
style={{ marginBottom: '1rem' }}
|
|
|
|
|
description={
|
|
|
|
|
<div>
|
|
|
|
|
<h4>Datacenter-Proxy</h4>
|
|
|
|
|
Proxy server located in one of the datacenters across the world. Datacenter proxies are slower and
|
|
|
|
|
more likely to fail, but they are cheaper. A call with a datacenter proxy cost 10 credits.
|
|
|
|
|
<h4>Residential-Proxy</h4>
|
|
|
|
|
High-quality proxy server located in one of the real people houses across the world. Datacenter
|
2024-06-12 13:52:28 +02:00
|
|
|
proxies are faster and more likely to success, but they are more expensive.
|
2023-03-20 08:52:13 +01:00
|
|
|
<br />
|
|
|
|
|
<br />
|
|
|
|
|
<b>
|
|
|
|
|
On the free tier, you have 10.000 credits, so chose your option wisely. Keep in mind, only
|
|
|
|
|
successful calls will be charged.
|
|
|
|
|
</b>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
/>
|
2022-06-13 08:10:30 +02:00
|
|
|
|
2023-03-20 08:52:13 +01:00
|
|
|
<RadioGroup value={scrapingAntProxy} onChange={(e) => setScrapingAntProxy(e.target.value)}>
|
|
|
|
|
<Radio name="datacenter" value="datacenter" checked={scrapingAntProxy === 'datacenter'}>
|
|
|
|
|
Datacenter proxy
|
|
|
|
|
</Radio>
|
|
|
|
|
<Radio name="residential" value="residential" checked={scrapingAntProxy === 'residential'}>
|
|
|
|
|
Residential proxy
|
|
|
|
|
</Radio>
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
</SegmentPart>
|
|
|
|
|
<Divider margin="1rem" />
|
2021-05-30 09:37:45 +02:00
|
|
|
<SegmentPart
|
|
|
|
|
name="Working hours"
|
|
|
|
|
helpText="During this hours, Fredy will search for new apartments. If nothing is configured, Fredy will search around the clock."
|
2023-03-20 08:52:13 +01:00
|
|
|
Icon={IconCalendar}
|
2021-05-30 09:37:45 +02:00
|
|
|
>
|
|
|
|
|
<div className="generalSettings__timePickerContainer">
|
2023-03-20 08:52:13 +01:00
|
|
|
<TimePicker
|
|
|
|
|
format={'HH:mm'}
|
|
|
|
|
insetLabel="From"
|
|
|
|
|
value={formatFromTBackend(workingHourFrom)}
|
|
|
|
|
placeholder=""
|
|
|
|
|
onChange={(val) => {
|
|
|
|
|
setWorkingHourFrom(val == null ? null : formatFromTimestamp(val));
|
|
|
|
|
}}
|
2021-05-30 09:37:45 +02:00
|
|
|
/>
|
2023-03-20 08:52:13 +01:00
|
|
|
<TimePicker
|
|
|
|
|
format={'HH:mm'}
|
|
|
|
|
insetLabel="Until"
|
|
|
|
|
value={formatFromTBackend(workingHourTo)}
|
|
|
|
|
placeholder=""
|
|
|
|
|
onChange={(val) => {
|
|
|
|
|
setWorkingHourTo(val == null ? null : formatFromTimestamp(val));
|
|
|
|
|
}}
|
2021-05-30 09:37:45 +02:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</SegmentPart>
|
2023-03-20 08:52:13 +01:00
|
|
|
<Divider margin="1rem" />
|
|
|
|
|
<Button type="primary" theme="solid" onClick={onStore} icon={<IconSave />}>
|
|
|
|
|
Save
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2021-05-30 09:37:45 +02:00
|
|
|
</React.Fragment>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default GeneralSettings;
|