import {API_URI, ARCHIVES_API, DEFAULT_LIMIT, emptyToNull, OTP_API, VIDEOS_API, ZIM_API} from "./components/Common"; import {toast} from "react-semantic-toasts-2"; import _ from "lodash"; function timeoutPromise(ms, promise) { // Create a timeout wrapper around a promise. If the timeout is reached, throw an error. Otherwise, return // the results of the promise. return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error("promise timeout")) }, ms); promise.then((res) => { clearTimeout(timeoutId); resolve(res); }, (err) => { clearTimeout(timeoutId); reject(err); }); }) } async function apiCall(url, method, body, ms = 60_000) { let init = {method}; if (body !== undefined) { init.body = JSON.stringify(body); } // Create a fetch promise, this will be awaited by the timeout. let promise = fetch(url, init); try { // await the response or error. let response = await timeoutPromise(ms, promise); if (200 <= response.status && response.status < 300) { // Request was successful. return response; } // Request encountered an error. let copy = response.clone(); const content = await copy.json(); console.debug('API error response json', content); const code = content['code']; if (response.status === 403 && code === 'WROL_MODE_ENABLED') { toast({ type: 'error', title: 'WROL Mode is enabled!', description: 'This functionality is disabled while WROL Mode is enabled!', time: 5000, }); } return response; } catch (e) { // Timeout, or some other exception. console.error(e); throw e; } } // Convenience functions for API calls. let apiGet = async (url) => { return await apiCall(url, 'GET') }; let apiDelete = async (url) => { return await apiCall(url, 'DELETE') }; let apiPost = async (url, body) => { return await apiCall(url, 'POST', body) }; let apiPut = async (url, body) => { return await apiCall(url, 'PUT', body) }; let apiPatch = async (url, body) => { return await apiCall(url, 'PATCH', body) }; export async function updateChannel(id, channel) { channel = emptyToNull(channel); return await apiPut(`${VIDEOS_API}/channels/${id}`, channel); } export async function createChannel(channel) { channel = emptyToNull(channel); return await apiPost(`${VIDEOS_API}/channels`, channel); } export async function deleteChannel(channelId) { const response = await apiDelete(`${VIDEOS_API}/channels/${channelId}`); if (response.status !== 204) { const content = await response.json(); console.error(content); throw Error(content); } return response; } export async function getChannels() { let response = await apiGet(`${VIDEOS_API}/channels`); if (response.status === 200) { return (await response.json())['channels']; } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Failed to get channels. See server logs.', time: 5000, }); } } export async function getChannel(id) { let response = await apiGet(`${VIDEOS_API}/channels/${id}`); return (await response.json())['channel']; } export async function searchVideos(offset, limit, channelId, searchStr, order_by, tagNames, headline) { // Build a search query to retrieve a list of videos from the API offset = parseInt(offset || 0); limit = parseInt(limit || DEFAULT_LIMIT); let body = {offset, limit, order_by: order_by || 'rank'}; if (searchStr) { body['search_str'] = searchStr; } if (channelId) { body['channel_id'] = parseInt(channelId); } if (tagNames) { body['tag_names'] = tagNames; } if (headline) { body['headline'] = true; } console.debug('searching videos', body); let response = await apiPost(`${VIDEOS_API}/search`, body); if (response.status === 200) { let data = await response.json(); return [data['file_groups'], data['totals']['file_groups']]; } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Failed to search videos. See server logs.', time: 5000, }); return [[], 0]; } } export async function getVideo(video_id) { let response = await apiGet(`${VIDEOS_API}/video/${video_id}`); let data = await response.json(); return [data['file_group'], data['prev'], data['next']]; } export async function deleteVideos(videoIds) { if (!videoIds || videoIds.length === 0) { toast({ type: 'error', title: 'Empty request', description: 'Unable to delete Videos because no IDs were passed.', time: 5000, }); } console.info(`Deleting Videos: ${videoIds}`); const i = videoIds.join(','); let response = await apiDelete(`${VIDEOS_API}/video/${i}`); if (response.status !== 204) { const content = response.json(); console.error(content); throw Error(content); } } export async function downloadVideoMetadata(videoUrl, destination) { return await postDownload( [videoUrl], 'video', null, null, null, destination, null, null, null, null, true, ); } export async function getDirectories(search_str) { let form_data = {'search_str': search_str || null}; let response = await apiPost(`${API_URI}/files/directories`, form_data); if (response.status === 200) { return await response.json(); } return []; } export async function getStatus() { let response = await apiGet(`${API_URI}/status`); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get server status', time: 5000, }); } } export async function getSettings() { let response = await apiGet(`${API_URI}/settings`); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get settings', time: 5000, }); } } export async function saveSettings(settings) { return await apiPatch(`${API_URI}/settings`, settings); } export async function getDownloads() { let response = await apiGet(`${API_URI}/download`); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get downloads', time: 5000, }); } } export async function validateRegex(regex) { let response = await apiPost(`${API_URI}/valid_regex`, {regex}); return (await response.json())['valid']; } export async function getVideosStatistics() { let response = await apiGet(`${VIDEOS_API}/statistics`); if (response.status === 200) { return (await response.json())['statistics']; } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get statistics', time: 5000, }); } } export async function downloadChannel(channelId) { let url = `${VIDEOS_API}/channels/download/${channelId}`; let response = await apiPost(url); if (response.status === 400) { const json = await response.json(); if (json['code'] === 'INVALID_DOWNLOAD') { toast({ type: 'error', title: 'Cannot Download!', description: 'This channel does not have a download record. Modify the frequency.', time: 5000, }); } } return response; } export async function refreshChannel(channelId) { let url = `${VIDEOS_API}/channels/refresh/${channelId}`; return await fetch(url, {method: 'POST'}); } export async function encryptOTP(otp, plaintext) { let body = {otp, plaintext}; let response = await apiPost(`${OTP_API}/encrypt_otp`, body); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Error!', description: 'Failed to encrypt OTP', time: 5000, }); } } export async function decryptOTP(otp, ciphertext) { let body = {otp, ciphertext}; let response = await apiPost(`${OTP_API}/decrypt_otp`, body); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Error!', description: 'Failed to decrypt OTP', time: 5000, }); } } export async function getCategories() { let response = await apiGet(`${API_URI}/inventory/categories`); if (response.status === 200) { return (await response.json())['categories']; } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get categories', time: 5000, }); } } export async function getBrands() { let response = await apiGet(`${API_URI}/inventory/brands`); if (response.status === 200) { return (await response.json())['brands']; } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get brands', time: 5000, }); } } export async function getInventories() { let response = await apiGet(`${API_URI}/inventory`); if (response.status === 200) { return (await response.json())['inventories']; } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get inventories', time: 5000, }); } } export async function getInventory(inventoryId) { let response = await apiGet(`${API_URI}/inventory/${inventoryId}`); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get inventory', time: 5000, }); } } export async function saveInventory(inventory) { return await apiPost(`${API_URI}/inventory`, inventory); } export async function updateInventory(inventoryId, inventory) { delete inventory['id']; return await apiPut(`${API_URI}/inventory/${inventoryId}`, inventory); } export async function deleteInventory(inventoryId) { return await apiDelete(`${API_URI}/inventory/${inventoryId}`); } export async function getItems(inventoryId) { let response = await apiGet(`${API_URI}/inventory/${inventoryId}/item`); return await response.json(); } export async function saveItem(inventoryId, item) { return await apiPost(`${API_URI}/inventory/${inventoryId}/item`, item); } export async function updateItem(itemId, item) { item = emptyToNull(item); return await apiPut(`${API_URI}/inventory/item/${itemId}`, item); } export async function deleteItems(itemIds) { let i = itemIds.join(','); await apiDelete(`${API_URI}/inventory/item/${i}`); } export async function deleteArchives(archiveIds) { if (!archiveIds || archiveIds.length === 0) { toast({ type: 'error', title: 'Empty request', description: 'Unable to delete Archives because no IDs were passed.', time: 5000, }); } console.log(`Deleting Archives: ${archiveIds}`); let i = archiveIds.join(','); const response = await apiDelete(`${API_URI}/archive/${i}`); if (response.status !== 204) { const content = await response.json(); console.debug(content); throw Error(content['error']); } } export async function searchArchives(offset, limit, domain, searchStr, order, tagNames, headline) { // Build a search query to retrieve a list of videos from the API offset = parseInt(offset || 0); limit = parseInt(limit || DEFAULT_LIMIT); let body = {offset, limit}; if (domain) { body['domain'] = domain; } if (searchStr) { body['search_str'] = searchStr; } if (order) { body['order_by'] = order; } if (tagNames) { body['tag_names'] = tagNames; } if (headline) { body['headline'] = true; } console.debug('searching archives', body); let response = await apiPost(`${ARCHIVES_API}/search`, body); if (response.status === 200) { let data = await response.json(); return [data['file_groups'], data['totals']['file_groups']]; } else { toast({ type: 'error', title: 'Unable to search archives', description: 'Cannot search archives. See server logs.', time: 5000, }); return [[], 0]; } } export async function fetchDomains() { let response = await apiGet(`${ARCHIVES_API}/domains`); if (response.status === 200) { let data = await response.json(); return [data['domains'], data['totals']['domains']]; } else { toast({ type: 'error', title: 'Domains Error', description: 'Unable to fetch Domains. See server logs.', time: 5000, }); } } export async function getArchive(archiveId) { const response = await apiGet(`${ARCHIVES_API}/${archiveId}`); if (response.status === 200) { const data = await response.json(); return [data['file_group'], data['history']]; } else { toast({ type: 'error', title: 'Archive Error', description: 'Unable to get Archive. See server logs.', time: 5000, }); } } export async function postDownload( urls, downloader, frequency, sub_downloader, excludedURLs, destination, tagNames, depth, suffix, max_pages, doNotDownload, ) { if (!downloader) { toast({ type: 'error', title: 'Failed to submit download', description: 'downloader is required, but was not provided', time: 5000, }) throw new Error('downloader is required, but was not provided'); } let body = { urls: urls, downloader: downloader, frequency: frequency || null, excluded_urls: excludedURLs, destination: destination || null, depth: depth || null, suffix: suffix || null, max_pages: max_pages || null, }; if (sub_downloader) { body['sub_downloader'] = sub_downloader; } if (tagNames) { body['tag_names'] = tagNames; } if (doNotDownload) { body['do_not_download'] = doNotDownload; } const response = await apiPost(`${API_URI}/download`, body); return response; } export async function killDownload(download_id) { let response = await apiPost(`${API_URI}/download/${download_id}/kill`); return response; } export async function restartDownload(download_id) { return await apiPost(`${API_URI}/download/${download_id}/restart`); } export async function killDownloads() { let response = await apiPost(`${API_URI}/download/kill`); return response; } export async function startDownloads() { let response = await apiPost(`${API_URI}/download/enable`); return response; } export async function getDownloaders() { try { let response = await apiGet(`${API_URI}/downloaders`); return await response.json(); } catch (e) { return {downloaders: []}; } } export async function deleteDownload(downloadId) { try { let response = await apiDelete(`${API_URI}/download/${downloadId}`); if (response.status !== 204) { toast({ type: 'error', title: 'Unable to delete', description: 'Unable to delete the download. See server logs.', time: 5000, }); } } catch (e) { return null; } } export async function filesSearch(offset, limit, searchStr, mimetypes, model, tagNames, headline, months, fromYear, toYear) { const body = {search_str: searchStr, offset: parseInt(offset), limit: parseInt(limit)}; if (mimetypes) { body['mimetypes'] = mimetypes; } if (model) { body['model'] = model; } if (tagNames) { body['tag_names'] = tagNames; } if (headline) { body['headline'] = true; } if (!_.isEmpty(months)) { body['months'] = months.map(i => parseInt(i)); } if (fromYear) { body['from_year'] = fromYear; } if (toYear) { body['to_year'] = toYear; } console.info('searching files', body); const response = await apiPost(`${API_URI}/files/search`, body); if (response.status === 200) { let data = await response.json(); let [file_groups, total] = [data['file_groups'], data['totals']['file_groups']]; return [file_groups, total]; } else { toast({ type: 'error', title: 'Unable to search files', description: 'Cannot search files. See server logs.', time: 5000, }); return [null, null]; } } export async function refreshFiles(paths) { let response; if (Array.isArray(paths) && paths.length > 0) { // Refresh user-selected files/directories only. const body = {paths}; console.info(`Refreshing: ${paths}`); response = await apiPost(`${API_URI}/files/refresh`, body); } else { console.info(`Refreshing all files`); response = await apiPost(`${API_URI}/files/refresh`); } return response; } export async function makeDirectory(path) { const body = {path: path}; try { await apiPost(`${API_URI}/files/directory`, body); } catch (e) { console.error(e); toast({ type: 'error', title: 'Unable to create directory', description: 'Failed to create directory. See server logs.', time: 5000, }); } } export async function getFiles(directories) { console.debug(`getFiles ${directories}`); let body = {directories: directories || []}; let response = await apiPost(`${API_URI}/files`, body); let {files} = await response.json(); return files; } export async function getFile(path) { let body = {file: path}; let response = await apiPost(`${API_URI}/files/file`, body); if (response.ok) { const {file} = await response.json(); return file; } else { throw new Error('Failed to get file data'); } } export async function deleteFile(paths) { let body = {paths: paths}; const response = await apiPost(`${API_URI}/files/delete`, body); if (response.status === 409) { const content = await response.json(); toast({ type: 'error', title: 'Delete error', description: content['message'], time: 5000, }); } } export async function fetchFilesProgress() { const response = await apiGet(`${API_URI}/files/refresh_progress`); if (response.status === 200) { const json = await response.json(); return json['progress']; } } export async function getHotspotStatus() { let response = await getSettings(); return response['hotspot_status']; } export async function setHotspot(on) { let response; if (on) { response = await apiPost(`${API_URI}/hotspot/on`); } else { response = await apiPost(`${API_URI}/hotspot/off`); } if (response.status === 204) { return null; } else { const content = await response.json(); const code = content['code']; if (code === 'NATIVE_ONLY') { toast({ type: 'error', title: 'Unsupported!', description: 'Hotspot is only supported outside Docker!', time: 5000, }); } else { toast({ type: 'error', title: 'Error!', description: 'Could not modify hotspot. See server logs.', time: 5000, }); } } } export async function getThrottleStatus() { let response = await getSettings(); return response['throttle_status']; } export async function setThrottle(on) { let response; if (on) { response = await apiPost(`${API_URI}/throttle/on`); } else { response = await apiPost(`${API_URI}/throttle/off`); } if (response.status === 204) { return null; } else { const content = await response.json(); const code = content['code']; if (code === 'NATIVE_ONLY') { toast({ type: 'error', title: 'Unsupported!', description: 'Throttle is only supported on a Raspberry Pi!', time: 5000, }); } else { toast({ type: 'error', title: 'Error!', description: 'Could not modify throttle. See server logs.', time: 5000, }); } } } export async function getMapImportStatus() { let response = await apiGet(`${API_URI}/map/files`); if (response.status === 200) { return await response.json(); } else { toast({ type: 'error', title: 'Unexpected server response', description: 'Could not get import status', time: 5000, }); } } export async function importMapFiles(paths) { let body = {'files': paths}; let response = await apiPost(`${API_URI}/map/import`, body); if (response.status === 204) { return null; } else { toast({ type: 'error', title: 'Error!', description: 'Could not start import! See server logs.', time: 5000, }); } } export async function clearCompletedDownloads() { let response = await apiPost(`${API_URI}/download/clear_completed`); if (response.status === 204) { return null } else { toast({ type: 'error', title: 'Error!', description: 'Could not clear completed downloads! See server logs.', time: 5000, }); } } export async function clearFailedDownloads() { let response = await apiPost(`${API_URI}/download/clear_failed`); if (response.status === 204) { return null } else { toast({ type: 'error', title: 'Error!', description: 'Could not clear failed downloads! See server logs.', time: 5000, }); } } export async function deleteOnceDownloads() { let response = await apiPost(`${API_URI}/download/delete_once`); if (response.status === 204) { return null } else { toast({ type: 'error', title: 'Error!', description: 'Could not delete once downloads! See server logs.', time: 5000, }); } } export async function retryOnceDownloads() { let response = await apiPost(`${API_URI}/download/retry_once`); if (response.status === 204) { return null } else { toast({ type: 'error', title: 'Error!', description: 'Could not retry once downloads! See server logs.', time: 5000, }); } } export async function getStatistics() { let response = await apiGet(`${API_URI}/statistics`); if (response.status === 200) { const contents = await response.json(); return contents; } else { toast({ type: 'error', title: 'Error!', description: 'Unable to get file statistics', time: 5000, }) } } export async function getEvents(after) { let uri = `${API_URI}/events/feed`; if (after) { uri = `${uri}?after=${encodeURIComponent(after)}` } let response = await apiGet(uri); if (response.status === 200) { return await response.json(); } } export async function getTags() { let uri = `${API_URI}/tag`; let response = await apiGet(uri); if (response.status === 200) { const body = await response.json(); return body['tags']; } } export async function tagFileGroup(fileGroup, name) { const body = {tag_name: name}; const {id, primary_path, path} = fileGroup; if (id) { body['file_group_id'] = id; } else if (primary_path) { body['file_group_primary_path'] = primary_path; } else if (path) { body['file_group_primary_path'] = path; } const uri = `${API_URI}/files/tag`; let response = await apiPost(uri, body); if (response.status !== 201) { console.error('Failed to add tag'); } } export async function untagFileGroup(fileGroup, name) { const body = {tag_name: name}; const {id, primary_path, path} = fileGroup; if (id) { body['file_group_id'] = id; } else if (primary_path) { body['file_group_primary_path'] = primary_path; } else if (path) { body['file_group_primary_path'] = path; } const uri = `${API_URI}/files/untag`; let response = await apiPost(uri, body) if (response.status !== 204) { console.error('Failed to remove tag'); } } export async function saveTag(name, color, id) { const body = {name: name, color: color}; let uri = `${API_URI}/tag`; if (id) { uri = `${uri}/${id}`; } let response = await apiPost(uri, body); if (id && response.status === 200) { toast({ type: 'info', title: 'Saved tag', description: `Saved tag: ${name}`, time: 2000, }); } else if (response.status === 201) { toast({ type: 'info', title: 'Created new tag', description: `Created new tag: ${name}`, time: 2000, }); } else { console.error('Failed to create new tag'); toast({ type: 'error', title: 'Error!', description: 'Unable to save tag', time: 5000, }) } } export async function deleteTag(id, name) { const uri = `${API_URI}/tag/${id}`; let response = await apiDelete(uri); if (response.status === 400) { const content = await response.json(); if (content['code'] === 'USED_TAG') { toast({ type: 'error', title: 'Error!', description: content['message'], time: 5000, }) } } else if (response.status !== 204) { console.error('Failed to delete tag'); toast({ type: 'error', title: 'Error!', description: `Unable to delete tag: ${name}`, time: 5000, }) } } export async function fetchFile(path) { const uri = `${API_URI}/files/file`; const body = {file: path}; const response = await apiPost(uri, body); if (response.status === 200) { const content = await response.json(); return content['file']; } else { console.error('Unable to fetch file dict! See client logs.'); } } export async function sendNotification(message, url) { const body = {message, url}; const response = await apiPost(`${API_URI}/notify`, body); if (response.status === 201) { toast({type: 'success', title: 'Shared', description: 'Your share was sent', time: 2000}); } else { toast({type: 'error', title: 'Error', description: 'Your share failed to send!', time: 5000}); console.error('Failed to share'); } } export async function searchDirectories(name) { const body = {name: name || ''}; const response = await apiPost(`${API_URI}/files/search_directories`, body); if (response.status === 204) { return []; } else if (response.status === 200) { const content = await response.json(); return content; } else { toast({type: 'error', title: 'Error', description: 'Failed to search directories!', time: 5000}); } } export async function renamePath(path, newName) { const body = {path, new_name: newName}; const response = await apiPost(`${API_URI}/files/rename`, body); if (response.status !== 204) { toast({type: 'error', title: 'Error', description: 'Failed to rename!', time: 5000}); } } export async function movePaths(destination, paths) { const body = {destination, paths}; const response = await apiPost(`${API_URI}/files/move`, body); if (response.status !== 204) { const content = await response.json(); toast({type: 'error', title: 'Error', description: content['api_error'], time: 5000}); } } export async function ignoreDirectory(directory) { const body = {path: directory}; const response = await apiPost(`${API_URI}/files/ignore_directory`, body); if (response.status !== 200) { toast({type: 'error', title: 'Error', description: 'Failed to ignore directory!', time: 5000}); } } export async function unignoreDirectory(directory) { const body = {path: directory}; const response = await apiPost(`${API_URI}/files/unignore_directory`, body); if (response.status !== 200) { toast({type: 'error', title: 'Error', description: 'Failed to unignore directory!', time: 5000}); } } export async function fetchZims() { const response = await apiGet(`${API_URI}/zim`); if (response.status === 200) { const content = await response.json(); return { zims: content['zims'], } } else { toast({type: 'error', title: 'Error', description: 'Cannot fetch Zims', time: 5000}); } } export async function fetchZimSubscriptions() { const response = await apiGet(`${API_URI}/zim/subscribe`); if (response.status === 200) { const content = await response.json(); return { subscriptions: content['subscriptions'], catalog: content['catalog'], iso_639_codes: content['iso_639_codes'], } } else { toast({type: 'error', title: 'Error', description: 'Cannot fetch Zim Subscriptions', time: 5000}); } } export async function searchZims(offset, limit, searchStr) { offset = parseInt(offset || 0); limit = parseInt(limit || DEFAULT_LIMIT); let body = {offset, limit}; if (searchStr) { body['search_str'] = searchStr; } console.debug('searching zims', body); let response = await apiPost(`${ZIM_API}/search`, body); if (response.status === 200) { let data = await response.json(); return [data['zims'],]; } else { toast({ type: 'error', title: 'Unable to search zims', description: 'Cannot search zims. See server logs.', time: 5000, }); return [[], 0]; } } export async function searchZim(offset, limit, searchStr, zimId, activeTags) { offset = parseInt(offset || 0); limit = parseInt(limit || DEFAULT_LIMIT); let body = {offset, limit, search_str: searchStr, tag_names: activeTags || []}; console.debug(`Searching Zim ${zimId} for: ${searchStr} tags: ${activeTags}`); let response = await apiPost(`${ZIM_API}/search/${zimId}`, body); if (response.status === 200) { const content = await response.json(); const zim = content['zim']; const searchLength = zim['search'].length; console.debug(`Got ${searchLength} results for Zim ${zimId}`); return content['zim']; } } export async function tagZimEntry(zim_id, zim_entry, name) { const body = {tag_name: name, zim_id: zim_id, zim_entry: zim_entry}; const uri = `${API_URI}/zim/tag`; let response = await apiPost(uri, body); if (response.status !== 201) { console.error('Failed to add tag'); } } export async function untagZimEntry(zim_id, zim_entry, name) { const body = {tag_name: name, zim_id: zim_id, zim_entry: zim_entry}; const uri = `${API_URI}/zim/untag`; let response = await apiPost(uri, body); if (response.status !== 201) { console.error('Failed to add tag'); } } export async function zimSubscribe(name, language) { const body = {name, language}; const response = await apiPost(`${API_URI}/zim/subscribe`, body); return response.status === 201; } export async function zimUnsubscribe(id) { const response = await apiDelete(`${API_URI}/zim/subscribe/${id}`); return response.status === 204; } export async function fetchDecoded(vinNumber) { const body = {vin_number: vinNumber}; const response = await apiPost(`${API_URI}/vin_number_decoder`, body); if (response.status === 200) { const content = await response.json(); return content['vin']; } else if (response.status === 400) { console.error('Invalid VIN Number'); return null; } } export async function searchSuggestions(search_str) { const body = {search_str}; const response = await apiPost(`${API_URI}/search_suggestions`, body); if (response.ok) { const content = await response.json(); return { channels: content.channels, domains: content.domains, } } } export async function searchEstimateFiles(search_str, tagNames, mimetypes, months, dateRange) { months = months ? months.map(i => parseInt(i)) : []; const body = {search_str, tag_names: tagNames, mimetypes, months}; if (dateRange) { body['from_year'] = dateRange[0]; body['to_year'] = dateRange[1]; } const response = await apiPost(`${API_URI}/search_file_estimates`, body); if (response.ok) { const content = await response.json(); return { fileGroups: content.file_groups, } } } export async function searchEstimateZims(search_str, tagNames) { const body = {search_str: search_str, tag_names: tagNames}; const response = await apiPost(`${API_URI}/zim/search_estimates`, body); if (response.ok) { const content = await response.json(); return { zimsEstimates: content.zims_estimates, } } } export async function getOutdatedZims() { const response = await apiGet(`${API_URI}/zim/outdated`); if (response.status === 200) { const content = await response.json(); return { outdated: content['outdated'], current: content['current'], } } else { toast({ type: 'error', title: 'Unable to fetch', description: 'Cannot fetch outdated Zims. See server logs.', time: 5000, }); } } export async function deleteOutdatedZims() { const response = await apiDelete(`${API_URI}/zim/outdated`); return response.status === 204; } export async function postShutdown() { const response = await apiPost(`${API_URI}/shutdown`); if (response.status !== 204) { // Return the error. return await response.json(); } return null } export async function postRestart() { const response = await apiPost(`${API_URI}/restart`); if (response.status !== 204) { // Return the error. return await response.json(); } return null }