mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Moving Configs to SettingsPage.
This commit is contained in:
parent
4d1f6dc20e
commit
d4fc3d1c07
6 changed files with 375 additions and 399 deletions
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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>;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ export const StatusContext = React.createContext({
|
|||
export const SettingsContext = React.createContext({
|
||||
settings: {},
|
||||
fetchSettings: null,
|
||||
saveSettings: null,
|
||||
pending: false,
|
||||
});
|
||||
|
||||
// `useQuery`
|
||||
|
|
|
|||
|
|
@ -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({});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue