mirror of
https://git.vectorsigma.ru/public/tubearchivist.git
synced 2026-08-04 19:09:29 +00:00
555 lines
20 KiB
TypeScript
555 lines
20 KiB
TypeScript
import Notifications from '../components/Notifications';
|
|
import SettingsNavigation from '../components/SettingsNavigation';
|
|
import Button from '../components/Button';
|
|
import PaginationDummy from '../components/PaginationDummy';
|
|
import { Fragment, useEffect, useState } from 'react';
|
|
import loadSchedule, { ScheduleResponseType } from '../api/loader/loadSchedule';
|
|
import loadAppriseNotification, {
|
|
AppriseNotificationType,
|
|
} from '../api/loader/loadAppriseNotification';
|
|
import deleteTaskSchedule from '../api/actions/deleteTaskSchedule';
|
|
import createTaskSchedule from '../api/actions/createTaskSchedule';
|
|
import createAppriseNotificationUrl, {
|
|
AppriseTaskNameType,
|
|
} from '../api/actions/createAppriseNotificationUrl';
|
|
import deleteAppriseNotificationUrl from '../api/actions/deleteAppriseNotificationUrl';
|
|
import { ApiError, ApiResponseType } from '../functions/APIClient';
|
|
|
|
const SettingsScheduling = () => {
|
|
const [refresh, setRefresh] = useState(false);
|
|
|
|
const [scheduleResponse, setScheduleResponse] = useState<ApiResponseType<ScheduleResponseType>>();
|
|
const [appriseNotification, setAppriseNotification] =
|
|
useState<ApiResponseType<AppriseNotificationType>>();
|
|
|
|
const [updateSubscribed, setUpdateSubscribed] = useState<string | undefined>();
|
|
const [downloadPending, setDownloadPending] = useState<string | undefined>();
|
|
const [checkReindex, setCheckReindex] = useState<string | undefined>();
|
|
const [checkReindexDays, setCheckReindexDays] = useState<number | undefined>();
|
|
const [thumbnailCheck, setThumbnailCheck] = useState<string | undefined>();
|
|
const [zipBackup, setZipBackup] = useState<string | undefined>();
|
|
const [zipBackupDays, setZipBackupDays] = useState<number | undefined>();
|
|
const [notificationUrl, setNotificationUrl] = useState<string | undefined>();
|
|
const [notificationTask, setNotificationTask] = useState<AppriseTaskNameType | string>('');
|
|
const [checkReindexError, setCheckReindexError] = useState<string | null>(null);
|
|
const [updateSubscribedError, setUpdateSubscribedError] = useState<string | null>(null);
|
|
const [downloadPendingError, setDownloadPendingError] = useState<string | null>(null);
|
|
const [thumnailCheckError, setThumnailCheckError] = useState<string | null>(null);
|
|
const [zipBackupError, setZipBackupError] = useState<string | null>(null);
|
|
|
|
const { data: scheduleResponseData } = scheduleResponse ?? {};
|
|
const { data: appriseNotificationData } = appriseNotification ?? {};
|
|
|
|
useEffect(() => {
|
|
(async () => {
|
|
if (refresh) {
|
|
const scheduleResponse = await loadSchedule();
|
|
const appriseNotificationResponse = await loadAppriseNotification();
|
|
|
|
setScheduleResponse(scheduleResponse);
|
|
setAppriseNotification(appriseNotificationResponse);
|
|
|
|
setRefresh(false);
|
|
}
|
|
})();
|
|
}, [refresh]);
|
|
|
|
useEffect(() => {
|
|
setRefresh(true);
|
|
}, []);
|
|
|
|
const groupedSchedules = Object.groupBy(scheduleResponseData || [], ({ name }) => name);
|
|
|
|
console.log(groupedSchedules);
|
|
|
|
const { update_subscribed, download_pending, run_backup, check_reindex, thumbnail_check } =
|
|
groupedSchedules;
|
|
|
|
const updateSubscribedSchedule = update_subscribed?.pop();
|
|
const downloadPendingSchedule = download_pending?.pop();
|
|
const runBackup = run_backup?.pop();
|
|
const checkReindexSchedule = check_reindex?.pop();
|
|
const thumbnailCheckSchedule = thumbnail_check?.pop();
|
|
|
|
return (
|
|
<>
|
|
<title>TA | Scheduling Settings</title>
|
|
<div className="boxed-content">
|
|
<SettingsNavigation />
|
|
<Notifications pageName={'all'} />
|
|
|
|
<div className="title-bar">
|
|
<h1>Scheduler Setup</h1>
|
|
<div className="settings-group">
|
|
<p>
|
|
Schedule settings expect a cron like format, where the first value is minute, second
|
|
is hour and third is day of the week.
|
|
</p>
|
|
<p>Examples:</p>
|
|
<ul>
|
|
<li>
|
|
<span className="settings-current">0 15 *</span>: Run task every day at 15:00 in the
|
|
afternoon.
|
|
</li>
|
|
<li>
|
|
<span className="settings-current">30 8 */2</span>: Run task every second day of the
|
|
week (Sun, Tue, Thu, Sat) at 08:30 in the morning.
|
|
</li>
|
|
<li>
|
|
<span className="settings-current">auto</span>: Sensible default.
|
|
</li>
|
|
</ul>
|
|
<p>Note:</p>
|
|
<ul>
|
|
<li>
|
|
Avoid an unnecessary frequent schedule to not get blocked by YouTube. For that
|
|
reason, the scheduler doesn't support schedules that trigger more than once per
|
|
hour.
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-group">
|
|
<h2>Rescan Subscriptions</h2>
|
|
<div className="settings-item">
|
|
<p>
|
|
Become a sponsor and join{' '}
|
|
<a href="https://members.tubearchivist.com/" target="_blank">
|
|
members.tubearchivist.com
|
|
</a>{' '}
|
|
to get access to <span className="settings-current">real time</span> notifications for
|
|
new videos uploaded by your favorite channels.
|
|
</p>
|
|
<p>
|
|
Current rescan schedule:{' '}
|
|
<span className="settings-current">
|
|
{!updateSubscribedSchedule && 'False'}
|
|
{updateSubscribedSchedule && (
|
|
<>
|
|
{updateSubscribedSchedule?.schedule}{' '}
|
|
<Button
|
|
label="Delete"
|
|
data-schedule="update_subscribed"
|
|
onClick={async () => {
|
|
await deleteTaskSchedule('update_subscribed');
|
|
|
|
setRefresh(true);
|
|
}}
|
|
className="danger-button"
|
|
/>
|
|
</>
|
|
)}
|
|
</span>
|
|
</p>
|
|
<p>Periodically rescan your subscriptions:</p>
|
|
|
|
<input
|
|
type="text"
|
|
value={updateSubscribed || updateSubscribedSchedule?.schedule || ''}
|
|
onChange={e => {
|
|
setUpdateSubscribed(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
try {
|
|
await createTaskSchedule('update_subscribed', {
|
|
schedule: updateSubscribed,
|
|
});
|
|
setUpdateSubscribedError('');
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
if (apiError.status && apiError.message) {
|
|
setUpdateSubscribedError(`Error ${apiError.status}: ${apiError.message}`);
|
|
} else {
|
|
setUpdateSubscribedError('An unexpected error occurred.');
|
|
}
|
|
}
|
|
setUpdateSubscribed('');
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
{updateSubscribedError && <p className="danger-zone">{updateSubscribedError}</p>}
|
|
</div>
|
|
</div>
|
|
<div className="settings-group">
|
|
<h2>Start Download</h2>
|
|
<div className="settings-item">
|
|
<p>
|
|
Current Download schedule:{' '}
|
|
<span className="settings-current">
|
|
{!download_pending && 'False'}
|
|
{downloadPendingSchedule && (
|
|
<>
|
|
{downloadPendingSchedule?.schedule}{' '}
|
|
<Button
|
|
label="Delete"
|
|
className="danger-button"
|
|
onClick={async () => {
|
|
await deleteTaskSchedule('download_pending');
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</span>
|
|
</p>
|
|
<p>Automatic video download schedule:</p>
|
|
|
|
<input
|
|
type="text"
|
|
value={downloadPending || downloadPendingSchedule?.schedule || ''}
|
|
onChange={e => {
|
|
setDownloadPending(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
try {
|
|
await createTaskSchedule('download_pending', {
|
|
schedule: downloadPending,
|
|
});
|
|
setDownloadPendingError('');
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
if (apiError.status && apiError.message) {
|
|
setDownloadPendingError(`Error ${apiError.status}: ${apiError.message}`);
|
|
} else {
|
|
setDownloadPendingError('An unexpected error occurred.');
|
|
}
|
|
}
|
|
setDownloadPending('');
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
{downloadPendingError && <p className="danger-zone">{downloadPendingError}</p>}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-group">
|
|
<h2>Refresh Metadata</h2>
|
|
<div className="settings-item">
|
|
<p>
|
|
Current Metadata refresh schedule:{' '}
|
|
<span className="settings-current">
|
|
{!checkReindexSchedule && 'False'}
|
|
{checkReindexSchedule && (
|
|
<>
|
|
{checkReindexSchedule?.schedule}{' '}
|
|
<Button
|
|
label="Delete"
|
|
className="danger-button"
|
|
onClick={async () => {
|
|
await deleteTaskSchedule('check_reindex');
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</span>
|
|
</p>
|
|
<p>Daily schedule to refresh metadata from YouTube:</p>
|
|
|
|
<input
|
|
type="text"
|
|
value={checkReindex || checkReindexSchedule?.schedule || ''}
|
|
onChange={e => {
|
|
setCheckReindex(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
try {
|
|
await createTaskSchedule('check_reindex', {
|
|
schedule: checkReindex,
|
|
});
|
|
setCheckReindexError('');
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
if (apiError.status && apiError.message) {
|
|
setCheckReindexError(`Error ${apiError.status}: ${apiError.message}`);
|
|
} else {
|
|
setCheckReindexError('An unexpected error occurred.');
|
|
}
|
|
}
|
|
setCheckReindex('');
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
{checkReindexError && <p className="danger-zone">{checkReindexError}</p>}
|
|
</div>
|
|
<div className="settings-item">
|
|
<p>
|
|
Current refresh for metadata older than x days:{' '}
|
|
<span className="settings-current">{checkReindexSchedule?.config?.days}</span>
|
|
</p>
|
|
<p>Refresh older than x days, recommended 90:</p>
|
|
|
|
<input
|
|
type="number"
|
|
value={checkReindexDays || checkReindexSchedule?.config?.days || 0}
|
|
onChange={e => {
|
|
setCheckReindexDays(Number(e.currentTarget.value));
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
await createTaskSchedule('check_reindex', {
|
|
config: {
|
|
days: checkReindexDays,
|
|
},
|
|
});
|
|
|
|
setCheckReindexDays(undefined);
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="settings-group">
|
|
<h2>Thumbnail Check</h2>
|
|
<div className="settings-item">
|
|
<p>
|
|
Current thumbnail check schedule:{' '}
|
|
<span className="settings-current">
|
|
{!thumbnailCheckSchedule && 'False'}
|
|
{thumbnailCheckSchedule && (
|
|
<>
|
|
{thumbnailCheckSchedule?.schedule}{' '}
|
|
<Button
|
|
label="Delete"
|
|
className="danger-button"
|
|
onClick={async () => {
|
|
await deleteTaskSchedule('thumbnail_check');
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</span>
|
|
</p>
|
|
<p>Periodically check and cleanup thumbnails:</p>
|
|
|
|
<input
|
|
type="text"
|
|
value={thumbnailCheck || thumbnailCheckSchedule?.schedule || ''}
|
|
onChange={e => {
|
|
setThumbnailCheck(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
try {
|
|
await createTaskSchedule('thumbnail_check', {
|
|
schedule: thumbnailCheck,
|
|
});
|
|
setThumnailCheckError('');
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
if (apiError.status && apiError.message) {
|
|
setThumnailCheckError(`Error ${apiError.status}: ${apiError.message}`);
|
|
} else {
|
|
setThumnailCheckError('An unexpected error occurred.');
|
|
}
|
|
}
|
|
setThumbnailCheck('');
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
{thumnailCheckError && <p className="danger-zone">{thumnailCheckError}</p>}
|
|
</div>
|
|
</div>
|
|
<div className="settings-group">
|
|
<h2>ZIP file index backup</h2>
|
|
<div className="settings-item">
|
|
<p>
|
|
<i>
|
|
Zip file backups are very slow for large archives and consistency is not guaranteed,
|
|
use snapshots instead. Make sure no other tasks are running when creating a Zip file
|
|
backup.
|
|
</i>
|
|
</p>
|
|
<p>
|
|
Current index backup schedule:{' '}
|
|
<span className="settings-current">
|
|
{!runBackup && 'False'}
|
|
{runBackup && (
|
|
<>
|
|
{runBackup.schedule}{' '}
|
|
<Button
|
|
label="Delete"
|
|
className="danger-button"
|
|
onClick={async () => {
|
|
await deleteTaskSchedule('run_backup');
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</span>
|
|
</p>
|
|
<p>Automatically backup metadata to a zip file:</p>
|
|
|
|
<input
|
|
type="text"
|
|
value={zipBackup || runBackup?.schedule || ''}
|
|
onChange={e => {
|
|
setZipBackup(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
try {
|
|
await createTaskSchedule('run_backup', {
|
|
schedule: zipBackup,
|
|
});
|
|
setZipBackupError('');
|
|
} catch (error) {
|
|
const apiError = error as ApiError;
|
|
if (apiError.status && apiError.message) {
|
|
setZipBackupError(`Error ${apiError.status}: ${apiError.message}`);
|
|
} else {
|
|
setZipBackupError('An unexpected error occurred.');
|
|
}
|
|
}
|
|
setZipBackup('');
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
{zipBackupError && <p className="danger-zone">{zipBackupError}</p>}
|
|
</div>
|
|
<div className="settings-item">
|
|
<p>
|
|
Current backup files to keep:{' '}
|
|
<span className="settings-current">{runBackup?.config?.rotate}</span>
|
|
</p>
|
|
<p>Max auto backups to keep:</p>
|
|
|
|
<input
|
|
type="number"
|
|
value={(zipBackupDays || runBackup?.config?.rotate)?.toString() || 0}
|
|
onChange={e => {
|
|
setZipBackupDays(Number(e.currentTarget.value));
|
|
}}
|
|
/>
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
await createTaskSchedule('run_backup', {
|
|
config: {
|
|
rotate: zipBackupDays,
|
|
},
|
|
});
|
|
|
|
setZipBackupDays(undefined);
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="settings-group">
|
|
<h2>Add Notification URL</h2>
|
|
<div className="settings-item">
|
|
{!appriseNotificationData && <p>No notifications stored</p>}
|
|
{appriseNotificationData && (
|
|
<>
|
|
<div className="description-text">
|
|
{Object.entries(appriseNotificationData)?.map(([key, { urls, title }]) => {
|
|
return (
|
|
<Fragment key={key}>
|
|
<h3>{title}</h3>
|
|
|
|
{urls.map((url: string) => {
|
|
return (
|
|
<p key={`${key}_${url}`}>
|
|
<span>{url} </span>
|
|
<Button
|
|
type="button"
|
|
className="danger-button"
|
|
label="Delete"
|
|
onClick={async () => {
|
|
await deleteAppriseNotificationUrl(key as AppriseTaskNameType);
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</p>
|
|
);
|
|
})}
|
|
</Fragment>
|
|
);
|
|
})}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
<div className="settings-item">
|
|
<p>
|
|
<i>
|
|
Send notification on completed tasks with the help of the{' '}
|
|
<a href="https://github.com/caronc/apprise" target="_blank">
|
|
Apprise
|
|
</a>{' '}
|
|
library.
|
|
</i>
|
|
</p>
|
|
|
|
<select
|
|
value={notificationTask}
|
|
onChange={e => {
|
|
setNotificationTask(e.currentTarget.value);
|
|
}}
|
|
>
|
|
<option value="">-- select task --</option>
|
|
<option value="update_subscribed">Rescan your Subscriptions</option>
|
|
<option value="extract_download">Add to download queue</option>
|
|
<option value="download_pending">Downloading</option>
|
|
<option value="check_reindex">Reindex Documents</option>
|
|
</select>
|
|
|
|
<input
|
|
type="text"
|
|
placeholder="Apprise notification URL"
|
|
value={notificationUrl || ''}
|
|
onChange={e => {
|
|
setNotificationUrl(e.currentTarget.value);
|
|
}}
|
|
/>
|
|
|
|
<Button
|
|
label="Save"
|
|
onClick={async () => {
|
|
await createAppriseNotificationUrl(
|
|
notificationTask as AppriseTaskNameType,
|
|
notificationUrl || '',
|
|
);
|
|
|
|
setRefresh(true);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<PaginationDummy />
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SettingsScheduling;
|