Moving Configs to SettingsPage.

This commit is contained in:
Roland 2024-10-03 13:35:06 -06:00
parent 4d1f6dc20e
commit d4fc3d1c07
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
6 changed files with 375 additions and 399 deletions

View file

@ -271,7 +271,30 @@ export async function getStatus() {
export async function getSettings() {
const response = await apiGet(`${API_URI}/settings`);
if (response.ok) {
return await response.json();
const content = await response.json();
return {
archive_destination: content.archive_destination,
download_manager_disabled: content.download_manager_disabled,
download_manager_stopped: content.download_manager_stopped,
download_on_startup: content.download_on_startup,
download_timeout: content.download_timeout,
hotspot_device: content.hotspot_device,
hotspot_on_startup: content.hotspot_on_startup,
hotspot_password: content.hotspot_password,
hotspot_ssid: content.hotspot_ssid,
hotspot_status: content.hotspot_status,
ignore_outdated_zims: content.ignore_outdated_zims,
ignored_directories: content.ignored_directories,
log_level: content.log_level,
map_destination: content.map_destination,
nav_color: content.nav_color,
media_directory: content.media_directory,
throttle_on_startup: content.throttle_on_startup,
throttle_status: content.throttle_status,
videos_destination: content.videos_destination,
wrol_mode: content.wrol_mode,
zims_destination: content.zims_destination,
}
} else {
const message = await getErrorMessage(response, 'Could not get settings.');
toast({

View file

@ -8,7 +8,6 @@ import {Header, Loader, Segment} from "../Theme";
import {DownloadsPage} from "./Downloads";
import {SettingsPage} from "./Settings";
import {StatusPage} from "./Status";
import {ConfigsPage} from "./Configs";
class WROLMode extends React.Component {
@ -72,7 +71,6 @@ export default function AdminRoute() {
{text: 'Downloads', to: '/admin', key: 'admin', end: true},
{text: 'Settings', to: '/admin/settings', key: 'settings'},
{text: 'Status', to: '/admin/status', key: 'status'},
{text: 'Configs', to: '/admin/configs', key: 'configs'},
{text: 'WROL Mode', to: '/admin/wrol', key: 'wrol'},
];
@ -82,7 +80,6 @@ export default function AdminRoute() {
<Route path='/' exact element={<DownloadsPage/>}/>
<Route path='settings' exact element={<SettingsPage/>}/>
<Route path='status' exact element={<StatusPage/>}/>
<Route path='configs' exact element={<ConfigsPage/>}/>
<Route path='wrol' exact element={<WROLMode/>}/>
</Routes>
</PageContainer>

View file

@ -66,7 +66,7 @@ function ConfigTableRow({config, importConfig, saveConfig, fetchConfigs}) {
</TableRow>
}
function ConfigsTable({configs, loading, importConfig, saveConfig, fetchConfigs}) {
export function ConfigsTable({configs, loading, importConfig, saveConfig, fetchConfigs}) {
let body = <TableRow>
<TableCell colSpan={4}><TableRowPlaceholder/></TableCell>
</TableRow>
@ -109,40 +109,3 @@ function ConfigsTable({configs, loading, importConfig, saveConfig, fetchConfigs}
</TableBody>
</Table>
}
export function ConfigsPage() {
const {configs, loading, importConfig, saveConfig, fetchConfigs} = useConfigs();
React.useEffect(() => {
fetchConfigs();
}, []);
const refreshButton = <APIButton
icon='refresh'
onClick={fetchConfigs}
/>;
const refreshPopupButton = <Popup
content='Check if configs are valid'
on='hover'
trigger={refreshButton}
/>;
return <PageContainer>
<Grid columns={2}>
<GridRow>
<GridColumn>
<Header as='h1'>Configs</Header>
</GridColumn>
<GridColumn textAlign='right'>{refreshPopupButton}</GridColumn>
</GridRow>
</Grid>
<ConfigsTable
configs={configs}
loading={loading}
importConfig={importConfig}
saveConfig={saveConfig}
fetchConfigs={fetchConfigs}
/>
</PageContainer>
}

View file

@ -1,5 +1,5 @@
import React from "react";
import {getSettings, postRestart, postShutdown, saveSettings} from "../../api";
import {postRestart, postShutdown} from "../../api";
import {
Button,
Divider,
@ -12,6 +12,7 @@ import {
Modal,
ModalContent,
ModalHeader,
Popup,
Segment
} from "../Theme";
import {ButtonGroup, Container, Dimmer, Dropdown, GridColumn, GridRow, Icon, Input} from "semantic-ui-react";
@ -26,10 +27,11 @@ import {
WROLModeMessage
} from "../Common";
import QRCode from "react-qr-code";
import {useDockerized} from "../../hooks/customHooks";
import {useConfigs, useDockerized} from "../../hooks/customHooks";
import {toast} from "react-semantic-toasts-2";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
import {SettingsContext} from "../../contexts/contexts";
import {ConfigsTable} from "./Configs";
export function ShutdownButton() {
const dockerized = useDockerized();
@ -168,389 +170,373 @@ function toApiLogLevel(logLevel) {
}[logLevel]
}
export class SettingsPage extends React.Component {
export function SettingsPage() {
const [disabled, setDisabled] = React.useState(false);
const [editSpecialDirectories, setEditSpecialDirectories] = React.useState(false);
const [qrCodeValue, setQrCodeValue] = React.useState('');
const [qrOpen, setQrOpen] = React.useState(false);
const [ready, setReady] = React.useState(false);
const [pendingSave, setPendingSave] = React.useState(false);
constructor(props) {
super(props);
this.state = {
disabled: false,
hotspot_encryption: 'WPA',
edit_special_directories: false,
pending: false,
qrCodeValue: '',
qrOpen: false,
ready: false,
// Get current settings from the API.
const {settings, saveSettings, fetchSettings} = React.useContext(SettingsContext);
// Used to track changes to the settings form between saves/loads.
const [state, setState] = React.useState({});
archive_destination: null,
download_on_startup: null,
download_timeout: null,
hotspot_device: null,
hotspot_password: null,
hotspot_ssid: null,
hotspot_status: null,
ignore_outdated_zims: null,
log_level: null,
map_destination: null,
navColor: null,
throttle_on_startup: null,
throttle_status: null,
videos_destination: null,
zims_destination: null,
}
const {configs, loading, importConfig, saveConfig, fetchConfigs} = useConfigs();
this.handleSubmit = this.handleSubmit.bind(this);
this.handleHotspotChange = this.handleHotspotChange.bind(this);
}
React.useEffect(() => {
fetchConfigs();
}, []);
async componentDidMount() {
const localFetchSettings = async () => {
console.debug('fetching settings...');
try {
const settings = await getSettings();
this.setState({
ready: true,
archive_destination: settings.archive_destination,
disabled: settings.wrol_mode,
download_on_startup: settings.download_on_startup,
download_timeout: settings.download_timeout || '',
hotspot_device: settings.hotspot_device,
hotspot_password: settings.hotspot_password,
hotspot_ssid: settings.hotspot_ssid,
hotspot_status: settings.hotspot_status,
ignore_outdated_zims: settings.ignore_outdated_zims,
log_level: fromApiLogLevel(settings.log_level),
map_destination: settings.map_destination,
navColor: settings.nav_color || 'violet',
throttle_on_startup: settings.throttle_on_startup,
throttle_status: settings.throttle_status,
videos_destination: settings.videos_destination,
zims_destination: settings.zims_destination,
}, this.handleHotspotChange);
await fetchSettings();
console.debug('successfully fetched settings');
} catch (e) {
console.error(e);
this.setState({ready: undefined});
}
}
async handleSubmit(callback) {
this.setState({disabled: true, pending: true});
let settings = {
archive_destination: this.state.archive_destination,
download_on_startup: this.state.download_on_startup,
download_timeout: this.state.download_timeout ? parseInt(this.state.download_timeout) : 0,
hotspot_device: this.state.hotspot_device,
hotspot_password: this.state.hotspot_password,
hotspot_ssid: this.state.hotspot_ssid,
ignore_outdated_zims: this.state.ignore_outdated_zims,
log_level: toApiLogLevel(this.state.log_level),
map_destination: this.state.map_destination,
nav_color: this.state.navColor,
throttle_on_startup: this.state.throttle_on_startup,
videos_destination: this.state.videos_destination,
zims_destination: this.state.zims_destination,
}
try {
const response = await saveSettings(settings);
if (response.status !== 204) {
throw Error('Failed to save settings');
}
} catch (e) {
toast({
type: 'error',
title: 'Failed',
description: 'Failed to save settings.',
time: 5000,
});
console.error('failed to fetch settings!');
throw e;
} finally {
this.setState({disabled: false, pending: false});
if (callback) {
callback();
}
}
}
handleInputChange = async (e, name, value) => {
const localSaveSettings = async (newSettings) => {
setDisabled(true);
setPendingSave(true);
newSettings.download_timeout = parseInt(newSettings.download_timeout);
newSettings.log_level = toApiLogLevel(newSettings.log_level);
try {
await saveSettings(newSettings);
await localFetchSettings();
} finally {
setDisabled(false);
setPendingSave(false);
}
}
const handleHotspotChange = async () => {
let {hotspot_ssid, hotspot_encryption, hotspot_password} = state;
// Special string which allows a mobile device to connect to a specific Wi-Fi.
setQrCodeValue(`WIFI:S:${hotspot_ssid};T:${hotspot_encryption};P:${hotspot_password};;`);
}
React.useEffect(() => {
console.debug('settings changed, replacing state...');
setReady(settings ? true : undefined);
setState({
// Only these settings can be changed on the SettingsPage.
archive_destination: settings.archive_destination,
download_on_startup: settings.download_on_startup,
download_timeout: settings.download_timeout,
hotspot_device: settings.hotspot_device,
hotspot_on_startup: settings.hotspot_on_startup,
hotspot_password: settings.hotspot_password,
hotspot_ssid: settings.hotspot_ssid,
ignore_outdated_zims: settings.ignore_outdated_zims,
log_level: fromApiLogLevel(settings.log_level),
map_destination: settings.map_destination,
nav_color: settings.nav_color,
media_directory: settings.media_directory,
throttle_on_startup: settings.throttle_on_startup,
videos_destination: settings.videos_destination,
zims_destination: settings.zims_destination,
});
}, [JSON.stringify(settings)]);
React.useEffect(() => {
handleHotspotChange();
}, [state.hotspot_ssid, state.hotspot_password, state.hotspot_device]);
const handleInputChange = async (e, name, value) => {
if (e) {
e.preventDefault()
}
this.setState({[name]: value});
setState({...state, [name]: value});
}
handleHotspotChange = async () => {
let {hotspot_ssid, hotspot_encryption, hotspot_password} = this.state;
// Special string which allows a mobile device to connect to a specific Wi-Fi.
let qrCodeValue = `WIFI:S:${hotspot_ssid};T:${hotspot_encryption};P:${hotspot_password};;`;
this.setState({qrCodeValue});
}
handleTimeoutChange = async (e, name, value) => {
const handleTimeoutChange = async (e, name, value) => {
if (e) {
e.preventDefault()
}
// Restrict timeout to numbers.
value = value.replace(/[^\d]/, '');
this.setState({[name]: value});
setState({...state, [name]: value});
}
handleQrOpen = async (e) => {
const handleQrOpen = async (e) => {
e.preventDefault();
this.setState({qrOpen: true});
setQrOpen(true);
}
render() {
const controlSegment = <Segment>
<Header as='h3'>Control WROLPi</Header>
<RestartButton/>
<ShutdownButton/>
</Segment>;
const qrButton = <Button icon style={{marginBottom: '1em'}}>
<Icon name='qrcode' size='big'/>
</Button>;
if (this.state.ready === false) {
return <>
<Loader active inline='centered'/>
{controlSegment}
</>
} else if (this.state.ready === undefined) {
return <ErrorMessage>Unable to fetch settings</ErrorMessage>
}
const navColorOptions = Object.keys(semanticUIColorMap).map(i => {
return {key: i, value: i, text: i.charAt(0).toUpperCase() + i.slice(1)}
});
const {
archive_destination,
disabled,
download_on_startup,
download_timeout,
edit_special_directories,
hotspot_device,
hotspot_password,
hotspot_ssid,
ignore_outdated_zims,
log_level,
map_destination,
navColor,
pending,
qrCodeValue,
throttle_on_startup,
videos_destination,
zims_destination,
} = this.state;
const mediaDirectoryLabel = `${settings.media_directory}/`;
const qrButton = <Button icon style={{marginBottom: '1em'}}><Icon name='qrcode' size='big'/></Button>;
let body;
if (ready === true) {
// Settings have been fetched, display form.
body = <>
<p>Any changes will be written to <i>{settings.media_directory}/config/wrolpi.yaml</i>.</p>
const navColorOptions = Object.keys(semanticUIColorMap).map(i => {
return {key: i, value: i, text: i.charAt(0).toUpperCase() + i.slice(1)}
});
<Form id="settings">
<div style={{margin: '0.5em'}}>
<Toggle
label='Download on Startup'
disabled={disabled || state.download_on_startup === null}
checked={state.download_on_startup === true}
onChange={checked => handleInputChange(null, 'download_on_startup', checked)}
/>
</div>
return <SettingsContext.Consumer>{({settings, fetchSettings}) => {
const mediaDirectoryLabel = `${settings.media_directory}/`;
return <Container fluid>
<WROLModeMessage content='Settings are disabled because WROL Mode is enabled.'/>
<div style={{margin: '0.5em'}}>
<Toggle
label='CPU Power-save on Startup'
disabled={disabled || state.throttle_on_startup === null}
checked={state.throttle_on_startup === true}
onChange={checked => handleInputChange(null, 'throttle_on_startup', checked)}
/>
</div>
<Segment>
<HotspotToggle/>
<ThrottleToggle/>
</Segment>
<div style={{margin: '0.5em'}}>
<Toggle
label='Ignore outdated Zims'
disabled={disabled || state.ignore_outdated_zims === null}
checked={state.ignore_outdated_zims === true}
onChange={checked => handleInputChange(null, 'ignore_outdated_zims', checked)}
/>
</div>
<Segment>
<Header as='h2'>Settings</Header>
<p>Any changes will be written to <i>{settings.media_directory}/config/wrolpi.yaml</i>.</p>
<Form id="settings">
<div style={{margin: '0.5em'}}>
<Toggle
label='Download on Startup'
disabled={disabled || download_on_startup === null}
checked={download_on_startup === true}
onChange={checked => this.handleInputChange(null, 'download_on_startup', checked)}
/>
</div>
<div style={{margin: '0.5em'}}>
<Toggle
label='CPU Power-save on Startup'
disabled={disabled || throttle_on_startup === null}
checked={throttle_on_startup === true}
onChange={checked => this.handleInputChange(null, 'throttle_on_startup', checked)}
/>
</div>
<div style={{margin: '0.5em'}}>
<Toggle
label='Ignore outdated Zims'
disabled={disabled || ignore_outdated_zims === null}
checked={ignore_outdated_zims === true}
onChange={checked => this.handleInputChange(null, 'ignore_outdated_zims', checked)}
/>
</div>
<FormGroup inline>
<FormInput
label={<>
<b>Download Timeout</b>
<HelpPopup content='Downloads will be stopped after this many seconds have elapsed.
<FormGroup inline>
<FormInput
label={<>
<b>Download Timeout</b>
<HelpPopup content='Downloads will be stopped after this many seconds have elapsed.
Downloads will never timeout if this is empty.'/>
</>}
value={download_timeout}
disabled={disabled || download_timeout === null}
onChange={(e, d) => this.handleTimeoutChange(e, 'download_timeout', d.value)}
</>}
value={state.download_timeout}
disabled={disabled || state.download_timeout === null}
onChange={(e, i) => handleTimeoutChange(e, 'download_timeout', i.value)}
/>
</FormGroup>
<FormGroup inline>
<FormInput
label='Hotspot SSID'
value={state.hotspot_ssid}
disabled={disabled || state.hotspot_ssid === null}
onChange={(e, i) => setState({...state, hotspot_ssid: i.value})}
/>
<FormInput
label='Hotspot Password'
disabled={disabled || state.hotspot_password === null}
value={state.hotspot_password}
onChange={(e, i) => setState({...state, hotspot_password: i.value})}
/>
<FormInput
label='Hotspot Device'
disabled={disabled || state.hotspot_password === null}
value={state.hotspot_device}
onChange={(e, i) => handleInputChange(e, 'hotspot_device', i.value)}
/>
</FormGroup>
<Modal closeIcon
onClose={() => setQrOpen(false)}
onOpen={handleQrOpen}
open={qrOpen}
trigger={qrButton}
>
<ModalHeader>
Scan this code to join the hotspot
</ModalHeader>
<ModalContent>
<div style={{display: 'inline-block', backgroundColor: '#ffffff', padding: '1em'}}>
<QRCode value={qrCodeValue} size={300}/>
</div>
</ModalContent>
</Modal>
<br/>
<label htmlFor='log_levels_input'>Log Level: {logLevelToName(state.log_level)}</label>
<br/>
<input type='range'
id='log_levels_input'
list='log_levels'
min='1'
max='5'
value={state.log_level}
onChange={e => setState({...state, log_level: parseInt(e.target.value)})}
style={{marginBottom: '1em'}}
/>
<datalist id='log_levels'>
<option value='1'>Critical</option>
<option value='2'>Warning</option>
<option value='3'>Info</option>
<option value='4'>Debug</option>
<option value='5'>All</option>
</datalist>
<br/>
<ButtonGroup>
<Button color={state.navColor} onClick={e => e.preventDefault()}>Navbar Color</Button>
<Dropdown
className='button icon'
floating
options={navColorOptions}
onChange={(e, {value}) => setState({...state, navColor: value})}
value={state.navColor}
/>
</ButtonGroup>
<Divider/>
<Header as='h3'>Special Directories</Header>
<p>WROLPi will save files to these directories (any video files will be saved to
the <i>videos</i> by default, etc.).</p>
<Grid stackable>
<GridRow columns={2}>
<GridColumn>
<FormField>
<label>Archive Directory</label>
<Input
label={mediaDirectoryLabel}
value={state.archive_destination}
disabled={!editSpecialDirectories}
onChange={(e, d) => handleInputChange(e, 'archive_destination', d.value)}
/>
</FormField>
</GridColumn>
<GridColumn>
<FormField>
<label>Videos Directory</label>
<Input
label={mediaDirectoryLabel}
value={state.videos_destination}
disabled={!editSpecialDirectories}
onChange={(e, d) => handleInputChange(e, 'videos_destination', d.value)}
/>
</FormField>
</GridColumn>
</GridRow>
<GridRow columns={2}>
<GridColumn>
<FormField>
<label>Map Directory</label>
<Input
label={mediaDirectoryLabel}
value={state.map_destination}
disabled={!editSpecialDirectories}
onChange={(e, d) => handleInputChange(e, 'map_destination', d.value)}
/>
</FormField>
</GridColumn>
<GridColumn>
<FormField>
<label>Zims Directory</label>
<Input
label={mediaDirectoryLabel}
value={state.zims_destination}
disabled={!editSpecialDirectories}
onChange={(e, d) => handleInputChange(e, 'zims_destination', d.value)}
/>
</FormField>
</GridColumn>
</GridRow>
<GridRow columns={1}>
<GridColumn>
<Toggle
label='Edit Directories'
disabled={disabled}
checked={editSpecialDirectories === true}
onChange={checked => setEditSpecialDirectories(checked)}
/>
</FormGroup>
</GridColumn>
</GridRow>
</Grid>
<FormGroup inline>
<FormInput
label='Hotspot SSID'
value={hotspot_ssid}
disabled={disabled || hotspot_ssid === null}
onChange={(e, d) => this.setState({hotspot_ssid: d.value}, this.handleHotspotChange)}
/>
<FormInput
label='Hotspot Password'
disabled={disabled || hotspot_password === null}
value={hotspot_password}
onChange={(e, d) => this.setState({hotspot_password: d.value}, this.handleHotspotChange)}
/>
<FormInput
label='Hotspot Device'
disabled={disabled || hotspot_password === null}
value={hotspot_device}
onChange={(e, d) => this.handleInputChange(e, 'hotspot_device', d.value)}
/>
</FormGroup>
<Divider/>
<Modal closeIcon
onClose={() => this.setState({qrOpen: false})}
onOpen={this.handleQrOpen}
open={this.state.qrOpen}
trigger={qrButton}
>
<ModalHeader>
Scan this code to join the hotspot
</ModalHeader>
<ModalContent>
<div style={{display: 'inline-block', backgroundColor: '#ffffff', padding: '1em'}}>
<QRCode value={qrCodeValue} size={300}/>
</div>
</ModalContent>
</Modal>
<APIButton
color='violet'
size='big'
onClick={() => localSaveSettings(state)}
obeyWROLMode={true}
disabled={disabled}
>Save</APIButton>
<br/>
<Dimmer active={pendingSave}>
<Loader active={pendingSave} size='large'/>
</Dimmer>
<label htmlFor='log_levels_input'>Log Level: {logLevelToName(log_level)}</label>
<br/>
<input type='range'
id='log_levels_input'
list='log_levels'
min='1'
max='5'
value={log_level}
onChange={e => this.setState({'log_level': parseInt(e.target.value)})}
style={{marginBottom: '1em'}}
/>
<datalist id='log_levels'>
<option value='1'>Critical</option>
<option value='2'>Warning</option>
<option value='3'>Info</option>
<option value='4'>Debug</option>
<option value='5'>All</option>
</datalist>
<br/>
<ButtonGroup>
<Button color={navColor} onClick={e => e.preventDefault()}>Navbar Color</Button>
<Dropdown
className='button icon'
floating
options={navColorOptions}
onChange={(e, {value}) => this.setState({navColor: value})}
value={navColor}
/>
</ButtonGroup>
<Divider/>
<Header as='h3'>Special Directories</Header>
<p>WROLPi will save files to these directories (any video files will be saved to
the <i>videos</i> by default, etc.).</p>
<Grid stackable>
<GridRow columns={2}>
<GridColumn>
<FormField>
<label>Archive Directory</label>
<Input
label={mediaDirectoryLabel}
value={archive_destination}
disabled={!edit_special_directories}
onChange={(e, d) => this.handleInputChange(e, 'archive_destination', d.value)}
/>
</FormField>
</GridColumn>
<GridColumn>
<FormField>
<label>Videos Directory</label>
<Input
label={mediaDirectoryLabel}
value={videos_destination}
disabled={!edit_special_directories}
onChange={(e, d) => this.handleInputChange(e, 'videos_destination', d.value)}
/>
</FormField>
</GridColumn>
</GridRow>
<GridRow columns={2}>
<GridColumn>
<FormField>
<label>Map Directory</label>
<Input
label={mediaDirectoryLabel}
value={map_destination}
disabled={!edit_special_directories}
onChange={(e, d) => this.handleInputChange(e, 'map_destination', d.value)}
/>
</FormField>
</GridColumn>
<GridColumn>
<FormField>
<label>Zims Directory</label>
<Input
label={mediaDirectoryLabel}
value={zims_destination}
disabled={!edit_special_directories}
onChange={(e, d) => this.handleInputChange(e, 'zims_destination', d.value)}
/>
</FormField>
</GridColumn>
</GridRow>
<GridRow columns={1}>
<GridColumn>
<Toggle
label='Edit Directories'
disabled={disabled}
checked={edit_special_directories === true}
onChange={checked => this.handleInputChange(null, 'edit_special_directories', checked)}
/>
</GridColumn>
</GridRow>
</Grid>
<Divider/>
<APIButton
color='violet'
size='big'
onClick={() => this.handleSubmit(fetchSettings)}
obeyWROLMode={true}
disabled={disabled}
>Save</APIButton>
<Dimmer active={pending}>
<Loader active={pending} size='large'/>
</Dimmer>
</Form>
</Segment>
{controlSegment}
</Container>;
}
}</SettingsContext.Consumer>
</Form>
</>
} else if (ready === false) {
body = <Loader active inline='centered'/>
} else {
body = <ErrorMessage>Unable to fetch settings</ErrorMessage>
}
const refreshButton = <APIButton
icon='refresh'
onClick={fetchConfigs}
/>;
const refreshPopupButton = <Popup
content='Check if configs are valid'
on='hover'
trigger={refreshButton}
/>;
const controlSegment = <Segment>
<Header as='h3'>Control WROLPi</Header>
<HotspotToggle/>
<ThrottleToggle/>
<RestartButton/>
<ShutdownButton/>
</Segment>;
const configsSegment = <Segment>
<Grid columns={2}>
<GridRow>
<GridColumn>
<Header as='h2'>Configs</Header>
</GridColumn>
<GridColumn textAlign='right'>{refreshPopupButton}</GridColumn>
</GridRow>
</Grid>
<p>Configs control a WROLPi, they are the source of truth. Any changes to a config will be applied to the
database when import (typically at startup).</p>
<ConfigsTable
configs={configs}
loading={loading}
importConfig={importConfig}
saveConfig={saveConfig}
fetchConfigs={fetchConfigs}
/>
</Segment>;
return <Container fluid>
<WROLModeMessage content='Settings are disabled because WROL Mode is enabled.'/>
{controlSegment}
<Segment>
<Header as='h2'>Settings</Header>
{body}
</Segment>
{configsSegment}
</Container>;
}

View file

@ -11,6 +11,8 @@ export const StatusContext = React.createContext({
export const SettingsContext = React.createContext({
settings: {},
fetchSettings: null,
saveSettings: null,
pending: false,
});
// `useQuery`

View file

@ -19,6 +19,7 @@ import {
getVideosStatistics,
postImportConfig,
postSaveConfig,
saveSettings,
searchArchives,
searchChannels,
searchDirectories,
@ -787,8 +788,10 @@ export const useSearchDirectories = (value) => {
export const useSettings = () => {
const [settings, setSettings] = useState({});
const [pending, setPending] = useState(false);
const fetchSettings = async () => {
setPending(true);
try {
setSettings(await getSettings())
} catch (e) {
@ -799,6 +802,8 @@ export const useSettings = () => {
description: 'Could not get directories',
time: 5000,
});
} finally {
setPending(false);
}
}
@ -806,7 +811,15 @@ export const useSettings = () => {
fetchSettings();
}, []);
return {settings, fetchSettings};
return {settings, pending, fetchSettings, saveSettings};
}
export const useSettingsInterval = () => {
const {settings, pending, fetchSettings, saveSettings} = useSettings();
useRecurringTimeout(fetchSettings, 1000 * 10);
return {settings, pending, fetchSettings, saveSettings};
}
export const useMediaDirectory = () => {
@ -815,14 +828,6 @@ export const useMediaDirectory = () => {
return settings['media_directory'];
}
export const useSettingsInterval = () => {
const {settings, fetchSettings} = useSettings();
useRecurringTimeout(fetchSettings, 1000 * 10);
return {settings, fetchSettings};
}
export const useStatus = () => {
const [status, setStatus] = useState({});