rqbit desktop configuration before startup works

This commit is contained in:
Igor Katson 2023-12-06 15:29:11 +00:00
parent a3475784e9
commit ee996012af
No known key found for this signature in database
GPG key ID: B4EC22B66D61A3F5
5 changed files with 340 additions and 8 deletions

View file

@ -2,7 +2,7 @@ import { MouseEventHandler, RefObject, createContext, useContext, useEffect, use
import { ProgressBar, Button, Container, Row, Col, Alert, Modal, Form, Spinner } from 'react-bootstrap';
import { AddTorrentResponse, TorrentDetails, TorrentId, TorrentStats, ErrorDetails as ApiErrorDetails, STATE_INITIALIZING, STATE_LIVE, STATE_PAUSED, STATE_ERROR, RqbitAPI, AddTorrentOptions } from './api-types';
interface Error {
export interface Error {
text: string,
details?: ApiErrorDetails,
}
@ -409,7 +409,7 @@ const ErrorDetails = (props: { details: ApiErrorDetails | null | undefined }) =>
</>
}
const ErrorComponent = (props: { error: Error | null, remove?: () => void }) => {
export const ErrorComponent = (props: { error: Error | null, remove?: () => void }) => {
let { error, remove } = props;
if (error == null) {

View file

@ -23,7 +23,7 @@ function errorToUIError(path: string): (e: InvokeErrorResponse) => Promise<never
}
}
async function invokeAPI<Response>(name: string, params?: InvokeArgs): Promise<Response> {
export async function invokeAPI<Response>(name: string, params?: InvokeArgs): Promise<Response> {
console.log("invoking", name, params);
const result = await invoke<Response>(name, params).catch(errorToUIError(name));
console.log(result);

View file

@ -0,0 +1,45 @@
type PathLike = string;
type Duration = string;
type SocketAddr = string;
interface RqbitDesktopConfigDht {
disable: boolean;
disable_persistence: boolean;
persistence_filename: PathLike;
}
interface RqbitDesktopConfigTcpListen {
disable: boolean;
min_port: number;
max_port: number;
}
interface RqbitDesktopConfigPersistence {
disable: boolean;
filename: PathLike;
}
interface RqbitDesktopConfigPeerOpts {
connect_timeout: Duration;
read_write_timeout: Duration;
}
interface RqbitDesktopConfigHttpApi {
disable: boolean;
listen_addr: SocketAddr;
read_only: boolean;
}
interface RqbitDesktopConfigUpnp {
disable: boolean;
}
export interface RqbitDesktopConfig {
default_download_location: PathLike;
dht: RqbitDesktopConfigDht;
tcp_listen: RqbitDesktopConfigTcpListen;
upnp: RqbitDesktopConfigUpnp;
persistence: RqbitDesktopConfigPersistence;
peer_opts: RqbitDesktopConfigPeerOpts;
http_api: RqbitDesktopConfigHttpApi;
}

266
desktop/src/configure.tsx Normal file
View file

@ -0,0 +1,266 @@
import React, { useState } from "react";
import { RqbitDesktopConfig } from "./configuration";
import { Button, Form, Modal } from "react-bootstrap";
import { ErrorComponent } from "./rqbit-webui-src/rqbit-web";
import { invokeAPI } from "./api";
import { ErrorDetails } from "./rqbit-webui-src/api-types";
export const ConfigModal: React.FC<{
handleOk: (config: RqbitDesktopConfig) => void,
initialConfig: RqbitDesktopConfig,
}> = ({ handleOk, initialConfig }) => {
const [config, setConfig] = useState(initialConfig);
const [error, setError] = useState<any | null>(null);
const handleInputChange = (e: any) => {
const name: string = e.target.name;
const value: any = e.target.value;
const [mainField, subField] = name.split('.', 2);
if (subField) {
setConfig((prevConfig: any) => ({
...prevConfig,
[mainField]: {
...prevConfig[mainField],
[subField]: value,
},
}));
} else {
setConfig((prevConfig) => ({
...prevConfig,
[name]: value,
}));
}
};
const handleToggleChange = (e: any) => {
const name: string = e.target.name;
const [mainField, subField] = name.split('.', 2);
if (subField) {
setConfig((prevConfig: any) => ({
...prevConfig,
[mainField]: {
...prevConfig[mainField],
[subField]: !prevConfig[mainField][subField],
},
}));
} else {
setConfig((prevConfig: any) => ({
...prevConfig,
[name]: !prevConfig[name],
}));
}
};
const handleOkClick = () => {
setError(null);
invokeAPI<{}>("config_change", { config }).then(
() => handleOk(config),
(e: ErrorDetails) => {
setError({
text: "Error saving configuration",
details: e,
});
}
)
};
return (
<Modal show size='xl'>
<Modal.Header closeButton>
<Modal.Title>Configure Rqbit desktop</Modal.Title>
</Modal.Header>
<Modal.Body>
<ErrorComponent error={error}></ErrorComponent>
<Form>
<fieldset className="mb-3">
<Form.Group controlId="default_download_location">
<Form.Label>Default Download Location</Form.Label>
<Form.Control
type="text"
name="default_download_location"
value={config.default_download_location}
onChange={handleInputChange}
/>
</Form.Group>
</fieldset>
<fieldset className="mb-3">
<legend>DHT config</legend>
<Form.Group controlId="dht_disable">
<Form.Check
type="switch"
label="Disable DHT"
name="dht.disable"
checked={config.dht.disable}
onChange={handleToggleChange}
/>
</Form.Group>
<Form.Group controlId="dht_disable_persistence">
<Form.Check
type="switch"
label="Disable DHT Persistence"
name="dht.disable_persistence"
checked={config.dht.disable_persistence}
onChange={handleToggleChange}
/>
</Form.Group>
<Form.Group controlId="dht_persistence_filename">
<Form.Label>Persistence Filename</Form.Label>
<Form.Control
type="text"
name="dht.persistence_filename"
value={config.dht.persistence_filename}
onChange={handleInputChange}
/>
</Form.Group>
</fieldset>
<fieldset className="mb-3">
<legend>TCP Listener config</legend>
<Form.Group controlId="tcp_listen_disable">
<Form.Check
type="switch"
label="Disable TCP Listen"
name="tcp_listen.disable"
checked={config.tcp_listen.disable}
onChange={handleToggleChange}
/>
</Form.Group>
<Form.Group controlId="tcp_listen_min_port">
<Form.Label>TCP Listen Min Port</Form.Label>
<Form.Control
type="number"
name="tcp_listen.min_port"
value={config.tcp_listen.min_port}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="tcp_listen_max_port">
<Form.Label>TCP Listen Max Port</Form.Label>
<Form.Control
type="number"
name="tcp_listen.max_port"
value={config.tcp_listen.max_port}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="upnp_disable">
<Form.Check
type="switch"
label="Do not advertise TCP port over UPnP"
name="upnp.disable"
checked={config.upnp.disable}
onChange={handleToggleChange}
/>
</Form.Group>
</fieldset>
<fieldset className="mb-3">
<legend>Session persistence</legend>
<Form.Group controlId="persistence_disable">
<Form.Check
type="switch"
label="Disable Persistence"
name="persistence.disable"
checked={config.persistence.disable}
onChange={handleToggleChange}
/>
</Form.Group>
<Form.Group controlId="persistence_filename">
<Form.Label>Persistence Filename</Form.Label>
<Form.Control
type="text"
name="persistence.filename"
value={config.persistence.filename}
onChange={handleInputChange}
/>
</Form.Group>
</fieldset>
<fieldset className="mb-3">
<legend>Peer connection options</legend>
<Form.Group controlId="peer_opts_connect_timeout">
<Form.Label>Peer Options Connect Timeout</Form.Label>
<Form.Control
type="text"
name="peer_opts.connect_timeout"
value={config.peer_opts.connect_timeout}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="peer_opts_read_write_timeout">
<Form.Label>Peer Options Read Write Timeout</Form.Label>
<Form.Control
type="text"
name="peer_opts.read_write_timeout"
value={config.peer_opts.read_write_timeout}
onChange={handleInputChange}
/>
</Form.Group>
</fieldset>
<fieldset className="mb-3">
<legend>HTTP API config</legend>
<Form.Group controlId="http_api_disable">
<Form.Check
type="switch"
label="Disable HTTP API"
name="http_api.disable"
checked={config.http_api.disable}
onChange={handleToggleChange}
/>
</Form.Group>
<Form.Group controlId="http_api_listen_addr">
<Form.Label>HTTP API Listen Address</Form.Label>
<Form.Control
type="text"
name="http_api.listen_addr"
value={config.http_api.listen_addr}
onChange={handleInputChange}
/>
</Form.Group>
<Form.Group controlId="http_api_read_only">
<Form.Check
type="switch"
label="HTTP API Read Only"
name="http_api.read_only"
checked={config.http_api.read_only}
onChange={handleToggleChange}
/>
</Form.Group>
</fieldset>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={() => setConfig(initialConfig)}>
Reset to defaults
</Button>
<Button variant="primary" onClick={handleOkClick}>
OK
</Button>
</Modal.Footer>
</Modal>
);
};

View file

@ -1,19 +1,40 @@
import { StrictMode } from "react";
import { StrictMode, useState } from "react";
import ReactDOM from 'react-dom/client';
import { APIContext, RqbitWebUI } from "./rqbit-webui-src/rqbit-web";
import { API } from "./api";
import { invoke } from "@tauri-apps/api";
import { RqbitDesktopConfig } from "./configuration";
import { ConfigModal } from "./configure";
let version = invoke<string>("get_version").then((version) => {
async function get_version(): Promise<string> {
return invoke<string>("get_version");
}
async function get_default_config(): Promise<RqbitDesktopConfig> {
return invoke<RqbitDesktopConfig>("config_default");
}
const RqbitDesktop: React.FC<{
version: string,
defaultConfig: RqbitDesktopConfig,
}> = ({ version, defaultConfig }) => {
let [configured, setConfigured] = useState<boolean>(false);
if (configured) {
return <RqbitWebUI title={`Rqbit Desktop v${version}`}></RqbitWebUI>
}
return <ConfigModal handleOk={() => setConfigured(true)} initialConfig={defaultConfig}></ConfigModal>;
}
Promise.all([get_version(), get_default_config()]).then(([version, config]) => {
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<StrictMode>
<APIContext.Provider value={API}>
<RqbitWebUI title={`Rqbit Desktop v${version}`} />
<RqbitDesktop version={version} defaultConfig={config} />
</APIContext.Provider>
</StrictMode>
);
});
})