setup vscode for consistent JS formatting
This commit is contained in:
parent
a641717245
commit
ec63e1cef7
15 changed files with 1675 additions and 1338 deletions
|
|
@ -1,90 +1,107 @@
|
|||
import { AddTorrentResponse, ListTorrentsResponse, RqbitAPI, TorrentDetails, TorrentStats, ErrorDetails } from "./rqbit-webui-src/api-types";
|
||||
import {
|
||||
AddTorrentResponse,
|
||||
ListTorrentsResponse,
|
||||
RqbitAPI,
|
||||
TorrentDetails,
|
||||
TorrentStats,
|
||||
ErrorDetails,
|
||||
} from "./rqbit-webui-src/api-types";
|
||||
|
||||
import { InvokeArgs, invoke } from "@tauri-apps/api/tauri"
|
||||
import { InvokeArgs, invoke } from "@tauri-apps/api/tauri";
|
||||
|
||||
interface InvokeErrorResponse {
|
||||
error_kind: string,
|
||||
human_readable: string,
|
||||
status: number,
|
||||
status_text: string,
|
||||
error_kind: string;
|
||||
human_readable: string;
|
||||
status: number;
|
||||
status_text: string;
|
||||
}
|
||||
|
||||
function errorToUIError(path: string): (e: InvokeErrorResponse) => Promise<never> {
|
||||
return (e: InvokeErrorResponse) => {
|
||||
console.log(e);
|
||||
let reason: ErrorDetails = {
|
||||
method: 'INVOKE',
|
||||
path: path,
|
||||
text: e.human_readable,
|
||||
status: e.status,
|
||||
statusText: e.status_text
|
||||
};
|
||||
return Promise.reject(reason);
|
||||
}
|
||||
function errorToUIError(
|
||||
path: string
|
||||
): (e: InvokeErrorResponse) => Promise<never> {
|
||||
return (e: InvokeErrorResponse) => {
|
||||
console.log(e);
|
||||
let reason: ErrorDetails = {
|
||||
method: "INVOKE",
|
||||
path: path,
|
||||
text: e.human_readable,
|
||||
status: e.status,
|
||||
statusText: e.status_text,
|
||||
};
|
||||
return Promise.reject(reason);
|
||||
};
|
||||
}
|
||||
|
||||
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);
|
||||
return result;
|
||||
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);
|
||||
return result;
|
||||
}
|
||||
|
||||
async function readFileAsBase64(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (event) {
|
||||
const base64String = (event?.target?.result as string)?.split(',')[1];
|
||||
if (base64String) {
|
||||
resolve(base64String);
|
||||
} else {
|
||||
reject(new Error('Failed to read file as base64.'));
|
||||
}
|
||||
};
|
||||
reader.onload = function (event) {
|
||||
const base64String = (event?.target?.result as string)?.split(",")[1];
|
||||
if (base64String) {
|
||||
resolve(base64String);
|
||||
} else {
|
||||
reject(new Error("Failed to read file as base64."));
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = function (error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
};
|
||||
reader.onerror = function (error) {
|
||||
console.log(error);
|
||||
reject(error);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
export const API: RqbitAPI = {
|
||||
listTorrents: async function (): Promise<ListTorrentsResponse> {
|
||||
return await invokeAPI<ListTorrentsResponse>("torrents_list");
|
||||
},
|
||||
getTorrentDetails: async function (id: number): Promise<TorrentDetails> {
|
||||
return await invokeAPI<TorrentDetails>("torrent_details", { id });
|
||||
},
|
||||
getTorrentStats: async function (id: number): Promise<TorrentStats> {
|
||||
return await invokeAPI<TorrentStats>("torrent_stats", { id });
|
||||
},
|
||||
uploadTorrent: async function (data, opts): Promise<AddTorrentResponse> {
|
||||
if (data instanceof File) {
|
||||
let contents = await readFileAsBase64(data);
|
||||
return await invokeAPI<AddTorrentResponse>("torrent_create_from_base64_file", {
|
||||
contents,
|
||||
opts: opts ?? {},
|
||||
});
|
||||
listTorrents: async function (): Promise<ListTorrentsResponse> {
|
||||
return await invokeAPI<ListTorrentsResponse>("torrents_list");
|
||||
},
|
||||
getTorrentDetails: async function (id: number): Promise<TorrentDetails> {
|
||||
return await invokeAPI<TorrentDetails>("torrent_details", { id });
|
||||
},
|
||||
getTorrentStats: async function (id: number): Promise<TorrentStats> {
|
||||
return await invokeAPI<TorrentStats>("torrent_stats", { id });
|
||||
},
|
||||
uploadTorrent: async function (data, opts): Promise<AddTorrentResponse> {
|
||||
if (data instanceof File) {
|
||||
let contents = await readFileAsBase64(data);
|
||||
return await invokeAPI<AddTorrentResponse>(
|
||||
"torrent_create_from_base64_file",
|
||||
{
|
||||
contents,
|
||||
opts: opts ?? {},
|
||||
}
|
||||
return await invokeAPI<AddTorrentResponse>("torrent_create_from_url", {
|
||||
url: data,
|
||||
opts: opts ?? {},
|
||||
});
|
||||
},
|
||||
pause: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_pause", { id });
|
||||
},
|
||||
start: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_start", { id });
|
||||
},
|
||||
forget: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_forget", { id });
|
||||
},
|
||||
delete: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_delete", { id });
|
||||
);
|
||||
}
|
||||
}
|
||||
return await invokeAPI<AddTorrentResponse>("torrent_create_from_url", {
|
||||
url: data,
|
||||
opts: opts ?? {},
|
||||
});
|
||||
},
|
||||
pause: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_pause", { id });
|
||||
},
|
||||
start: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_start", { id });
|
||||
},
|
||||
forget: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_forget", { id });
|
||||
},
|
||||
delete: function (id: number): Promise<void> {
|
||||
return invokeAPI<void>("torrent_action_delete", { id });
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,48 +3,48 @@ type Duration = string;
|
|||
type SocketAddr = string;
|
||||
|
||||
interface RqbitDesktopConfigDht {
|
||||
disable: boolean;
|
||||
disable_persistence: boolean;
|
||||
persistence_filename: PathLike;
|
||||
disable: boolean;
|
||||
disable_persistence: boolean;
|
||||
persistence_filename: PathLike;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigTcpListen {
|
||||
disable: boolean;
|
||||
min_port: number;
|
||||
max_port: number;
|
||||
disable: boolean;
|
||||
min_port: number;
|
||||
max_port: number;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigPersistence {
|
||||
disable: boolean;
|
||||
filename: PathLike;
|
||||
disable: boolean;
|
||||
filename: PathLike;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigPeerOpts {
|
||||
connect_timeout: Duration;
|
||||
read_write_timeout: Duration;
|
||||
connect_timeout: Duration;
|
||||
read_write_timeout: Duration;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigHttpApi {
|
||||
disable: boolean;
|
||||
listen_addr: SocketAddr;
|
||||
read_only: boolean;
|
||||
disable: boolean;
|
||||
listen_addr: SocketAddr;
|
||||
read_only: boolean;
|
||||
}
|
||||
|
||||
interface RqbitDesktopConfigUpnp {
|
||||
disable: boolean;
|
||||
disable: boolean;
|
||||
}
|
||||
|
||||
export interface RqbitDesktopConfig {
|
||||
default_download_location: PathLike;
|
||||
dht: RqbitDesktopConfigDht;
|
||||
tcp_listen: RqbitDesktopConfigTcpListen;
|
||||
upnp: RqbitDesktopConfigUpnp;
|
||||
persistence: RqbitDesktopConfigPersistence;
|
||||
peer_opts: RqbitDesktopConfigPeerOpts;
|
||||
http_api: RqbitDesktopConfigHttpApi;
|
||||
default_download_location: PathLike;
|
||||
dht: RqbitDesktopConfigDht;
|
||||
tcp_listen: RqbitDesktopConfigTcpListen;
|
||||
upnp: RqbitDesktopConfigUpnp;
|
||||
persistence: RqbitDesktopConfigPersistence;
|
||||
peer_opts: RqbitDesktopConfigPeerOpts;
|
||||
http_api: RqbitDesktopConfigHttpApi;
|
||||
}
|
||||
|
||||
export interface CurrentDesktopState {
|
||||
config: RqbitDesktopConfig | null,
|
||||
configured: boolean,
|
||||
}
|
||||
config: RqbitDesktopConfig | null;
|
||||
configured: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,310 +6,314 @@ import { invokeAPI } from "./api";
|
|||
import { ErrorDetails } from "./rqbit-webui-src/api-types";
|
||||
|
||||
const FormCheck: React.FC<{
|
||||
label: string,
|
||||
name: string,
|
||||
checked: boolean,
|
||||
onChange: (e: any) => void,
|
||||
disabled?: boolean,
|
||||
help?: string,
|
||||
label: string;
|
||||
name: string;
|
||||
checked: boolean;
|
||||
onChange: (e: any) => void;
|
||||
disabled?: boolean;
|
||||
help?: string;
|
||||
}> = ({ label, name, checked, onChange, disabled, help }) => {
|
||||
return <Form.Group as={Row} controlId={name} className="mb-3">
|
||||
<Form.Label className="col-4">{label}</Form.Label>
|
||||
<div className="col-8">
|
||||
<Form.Check
|
||||
type="switch"
|
||||
name={name}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{help && <div className="form-text">{help}</div>}
|
||||
return (
|
||||
<Form.Group as={Row} controlId={name} className="mb-3">
|
||||
<Form.Label className="col-4">{label}</Form.Label>
|
||||
<div className="col-8">
|
||||
<Form.Check
|
||||
type="switch"
|
||||
name={name}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{help && <div className="form-text">{help}</div>}
|
||||
</Form.Group>
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const FormInput: React.FC<{
|
||||
label: string,
|
||||
name: string,
|
||||
value: string | number,
|
||||
inputType: string,
|
||||
onChange: (e: any) => void,
|
||||
disabled?: boolean,
|
||||
help?: string
|
||||
label: string;
|
||||
name: string;
|
||||
value: string | number;
|
||||
inputType: string;
|
||||
onChange: (e: any) => void;
|
||||
disabled?: boolean;
|
||||
help?: string;
|
||||
}> = ({ label, name, value, inputType, onChange, disabled, help }) => {
|
||||
return <Form.Group as={Row} controlId={name} className="mb-3">
|
||||
<Form.Label className="col-4 col-form-label">{label}</Form.Label>
|
||||
<div className="col-8">
|
||||
<Form.Control
|
||||
type={inputType}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{help && <div className="form-text">{help}</div>}
|
||||
return (
|
||||
<Form.Group as={Row} controlId={name} className="mb-3">
|
||||
<Form.Label className="col-4 col-form-label">{label}</Form.Label>
|
||||
<div className="col-8">
|
||||
<Form.Control
|
||||
type={inputType}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
{help && <div className="form-text">{help}</div>}
|
||||
</Form.Group>
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export const ConfigModal: React.FC<{
|
||||
show: boolean,
|
||||
handleStartReconfigure: () => void,
|
||||
handleConfigured: (config: RqbitDesktopConfig) => void,
|
||||
handleCancel?: () => void,
|
||||
initialConfig: RqbitDesktopConfig,
|
||||
defaultConfig: RqbitDesktopConfig,
|
||||
}> = ({ show, handleStartReconfigure, handleConfigured, handleCancel, initialConfig, defaultConfig }) => {
|
||||
let [config, setConfig] = useState<RqbitDesktopConfig>(initialConfig);
|
||||
let [loading, setLoading] = useState<boolean>(false);
|
||||
show: boolean;
|
||||
handleStartReconfigure: () => void;
|
||||
handleConfigured: (config: RqbitDesktopConfig) => void;
|
||||
handleCancel?: () => void;
|
||||
initialConfig: RqbitDesktopConfig;
|
||||
defaultConfig: RqbitDesktopConfig;
|
||||
}> = ({
|
||||
show,
|
||||
handleStartReconfigure,
|
||||
handleConfigured,
|
||||
handleCancel,
|
||||
initialConfig,
|
||||
defaultConfig,
|
||||
}) => {
|
||||
let [config, setConfig] = useState<RqbitDesktopConfig>(initialConfig);
|
||||
let [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
const [error, setError] = useState<any | null>(null);
|
||||
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);
|
||||
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,
|
||||
}));
|
||||
}
|
||||
};
|
||||
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);
|
||||
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],
|
||||
}));
|
||||
}
|
||||
};
|
||||
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);
|
||||
handleStartReconfigure();
|
||||
setLoading(true);
|
||||
invokeAPI<{}>("config_change", { config }).then(
|
||||
() => {
|
||||
setLoading(false);
|
||||
handleConfigured(config);
|
||||
},
|
||||
(e: ErrorDetails) => {
|
||||
setLoading(false);
|
||||
setError({
|
||||
text: "Error saving configuration",
|
||||
details: e,
|
||||
});
|
||||
}
|
||||
)
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={show} size='xl' onHide={handleCancel}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Configure Rqbit desktop</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<ErrorComponent error={error}></ErrorComponent>
|
||||
<Tabs
|
||||
defaultActiveKey="home"
|
||||
id="rqbit-config"
|
||||
className="mb-3">
|
||||
|
||||
<Tab className="mb-3" eventKey="home" title="Home">
|
||||
<FormInput
|
||||
label="Default download folder"
|
||||
name="default_download_location"
|
||||
value={config.default_download_location}
|
||||
inputType="text"
|
||||
onChange={handleInputChange}
|
||||
help="Where to download torrents by default. You can override this per torrent."
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="dht" title="DHT">
|
||||
<legend>DHT config</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Enable DHT"
|
||||
name="dht.disable"
|
||||
checked={!config.dht.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="DHT is required to read magnet links. There's no good reason to disable it, unless you know what you are doing."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Enable DHT persistence"
|
||||
name="dht.disable_persistence"
|
||||
checked={!config.dht.disable_persistence}
|
||||
onChange={handleToggleChange}
|
||||
disabled={config.dht.disable}
|
||||
help="Enable to store DHT state in a file periodically. If disabled, DHT will bootstrap from scratch on restart."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Persistence filename"
|
||||
name="dht.persistence_filename"
|
||||
value={config.dht.persistence_filename}
|
||||
inputType="text"
|
||||
disabled={config.dht.disable}
|
||||
onChange={handleInputChange}
|
||||
help="The filename to store DHT state into"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="tcp_listen" title="TCP">
|
||||
<legend>TCP Listener config</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Listen on TCP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="Listen for torrent requests on TCP. Required for peers to be able to connect to you, mainly for uploading."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Advertise over UPnP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="Advertise your port over UPnP. This is required for peers to be able to connect to you from the internet. Will only work if your router has a static IP."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="number"
|
||||
label="Min port"
|
||||
name="tcp_listen.min_port"
|
||||
value={config.tcp_listen.min_port}
|
||||
disabled={config.tcp_listen.disable}
|
||||
onChange={handleInputChange}
|
||||
help="The min port to try to listen on. First successful is taken."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="number"
|
||||
label="Max port"
|
||||
name="tcp_listen.max_port"
|
||||
value={config.tcp_listen.max_port}
|
||||
disabled={config.tcp_listen.disable}
|
||||
onChange={handleInputChange}
|
||||
help="The max port to try to listen on."
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
|
||||
<Tab className="mb-3" eventKey="session_persistence" title="Session">
|
||||
<legend>Session persistence</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Enable persistence"
|
||||
name="persistence.disable"
|
||||
checked={!config.persistence.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="If you disable session persistence, rqbit won't remember the torrents you had before restart."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Persistence filename"
|
||||
name="persistence.filename"
|
||||
inputType="text"
|
||||
value={config.persistence.filename}
|
||||
onChange={handleInputChange}
|
||||
disabled={config.persistence.disable}
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="peer_opts" title="Peer options">
|
||||
<legend>Peer connection options</legend>
|
||||
|
||||
<FormInput
|
||||
label="Connect timeout (seconds)"
|
||||
inputType="number"
|
||||
name="peer_opts.connect_timeout"
|
||||
value={config.peer_opts.connect_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="How much to wait for outgoing connections to connect. Default is low to prefer faster peers."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Read/write timeout (seconds)"
|
||||
inputType="number"
|
||||
name="peer_opts.read_write_timeout"
|
||||
value={config.peer_opts.read_write_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="Peer socket read/write timeout."
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="http_api" title="HTTP API">
|
||||
<legend>HTTP API config</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Enable HTTP API"
|
||||
name="http_api.disable"
|
||||
checked={!config.http_api.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="If enabled you can access the HTTP API at the address below"
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Read only"
|
||||
name="http_api.read_only"
|
||||
checked={config.http_api.read_only}
|
||||
disabled={config.http_api.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="If enabled, only GET requests will be allowed through the API"
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Listen address"
|
||||
inputType="text"
|
||||
name="http_api.listen_addr"
|
||||
value={config.http_api.listen_addr}
|
||||
disabled={config.http_api.disable}
|
||||
onChange={handleInputChange}
|
||||
help={`You'll access the API at http://${config.http_api.listen_addr}`}
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
</Tabs>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
|
||||
{!!handleCancel &&
|
||||
<Button variant="secondary" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
}
|
||||
<Button variant="secondary" onClick={() => setConfig(defaultConfig)}>
|
||||
Reset to defaults
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleOkClick} disabled={loading}>
|
||||
OK
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
const handleOkClick = () => {
|
||||
setError(null);
|
||||
handleStartReconfigure();
|
||||
setLoading(true);
|
||||
invokeAPI<{}>("config_change", { config }).then(
|
||||
() => {
|
||||
setLoading(false);
|
||||
handleConfigured(config);
|
||||
},
|
||||
(e: ErrorDetails) => {
|
||||
setLoading(false);
|
||||
setError({
|
||||
text: "Error saving configuration",
|
||||
details: e,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal show={show} size="xl" onHide={handleCancel}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Configure Rqbit desktop</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<ErrorComponent error={error}></ErrorComponent>
|
||||
<Tabs defaultActiveKey="home" id="rqbit-config" className="mb-3">
|
||||
<Tab className="mb-3" eventKey="home" title="Home">
|
||||
<FormInput
|
||||
label="Default download folder"
|
||||
name="default_download_location"
|
||||
value={config.default_download_location}
|
||||
inputType="text"
|
||||
onChange={handleInputChange}
|
||||
help="Where to download torrents by default. You can override this per torrent."
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="dht" title="DHT">
|
||||
<legend>DHT config</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Enable DHT"
|
||||
name="dht.disable"
|
||||
checked={!config.dht.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="DHT is required to read magnet links. There's no good reason to disable it, unless you know what you are doing."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Enable DHT persistence"
|
||||
name="dht.disable_persistence"
|
||||
checked={!config.dht.disable_persistence}
|
||||
onChange={handleToggleChange}
|
||||
disabled={config.dht.disable}
|
||||
help="Enable to store DHT state in a file periodically. If disabled, DHT will bootstrap from scratch on restart."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Persistence filename"
|
||||
name="dht.persistence_filename"
|
||||
value={config.dht.persistence_filename}
|
||||
inputType="text"
|
||||
disabled={config.dht.disable}
|
||||
onChange={handleInputChange}
|
||||
help="The filename to store DHT state into"
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="tcp_listen" title="TCP">
|
||||
<legend>TCP Listener config</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Listen on TCP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="Listen for torrent requests on TCP. Required for peers to be able to connect to you, mainly for uploading."
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Advertise over UPnP"
|
||||
name="tcp_listen.disable"
|
||||
checked={!config.tcp_listen.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="Advertise your port over UPnP. This is required for peers to be able to connect to you from the internet. Will only work if your router has a static IP."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="number"
|
||||
label="Min port"
|
||||
name="tcp_listen.min_port"
|
||||
value={config.tcp_listen.min_port}
|
||||
disabled={config.tcp_listen.disable}
|
||||
onChange={handleInputChange}
|
||||
help="The min port to try to listen on. First successful is taken."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
inputType="number"
|
||||
label="Max port"
|
||||
name="tcp_listen.max_port"
|
||||
value={config.tcp_listen.max_port}
|
||||
disabled={config.tcp_listen.disable}
|
||||
onChange={handleInputChange}
|
||||
help="The max port to try to listen on."
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="session_persistence" title="Session">
|
||||
<legend>Session persistence</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Enable persistence"
|
||||
name="persistence.disable"
|
||||
checked={!config.persistence.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="If you disable session persistence, rqbit won't remember the torrents you had before restart."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Persistence filename"
|
||||
name="persistence.filename"
|
||||
inputType="text"
|
||||
value={config.persistence.filename}
|
||||
onChange={handleInputChange}
|
||||
disabled={config.persistence.disable}
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="peer_opts" title="Peer options">
|
||||
<legend>Peer connection options</legend>
|
||||
|
||||
<FormInput
|
||||
label="Connect timeout (seconds)"
|
||||
inputType="number"
|
||||
name="peer_opts.connect_timeout"
|
||||
value={config.peer_opts.connect_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="How much to wait for outgoing connections to connect. Default is low to prefer faster peers."
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Read/write timeout (seconds)"
|
||||
inputType="number"
|
||||
name="peer_opts.read_write_timeout"
|
||||
value={config.peer_opts.read_write_timeout}
|
||||
onChange={handleInputChange}
|
||||
help="Peer socket read/write timeout."
|
||||
/>
|
||||
</Tab>
|
||||
|
||||
<Tab className="mb-3" eventKey="http_api" title="HTTP API">
|
||||
<legend>HTTP API config</legend>
|
||||
|
||||
<FormCheck
|
||||
label="Enable HTTP API"
|
||||
name="http_api.disable"
|
||||
checked={!config.http_api.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="If enabled you can access the HTTP API at the address below"
|
||||
/>
|
||||
|
||||
<FormCheck
|
||||
label="Read only"
|
||||
name="http_api.read_only"
|
||||
checked={config.http_api.read_only}
|
||||
disabled={config.http_api.disable}
|
||||
onChange={handleToggleChange}
|
||||
help="If enabled, only GET requests will be allowed through the API"
|
||||
/>
|
||||
|
||||
<FormInput
|
||||
label="Listen address"
|
||||
inputType="text"
|
||||
name="http_api.listen_addr"
|
||||
value={config.http_api.listen_addr}
|
||||
disabled={config.http_api.disable}
|
||||
onChange={handleInputChange}
|
||||
help={`You'll access the API at http://${config.http_api.listen_addr}`}
|
||||
/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
{!!handleCancel && (
|
||||
<Button variant="secondary" onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="secondary" onClick={() => setConfig(defaultConfig)}>
|
||||
Reset to defaults
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleOkClick} disabled={loading}>
|
||||
OK
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { StrictMode } from "react";
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { APIContext } from "./rqbit-webui-src/rqbit-web";
|
||||
import { API } from "./api";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
|
@ -7,23 +7,29 @@ import { CurrentDesktopState, RqbitDesktopConfig } from "./configuration";
|
|||
import { RqbitDesktop } from "./rqbit-desktop";
|
||||
|
||||
async function get_version(): Promise<string> {
|
||||
return invoke<string>("get_version");
|
||||
return invoke<string>("get_version");
|
||||
}
|
||||
|
||||
async function get_default_config(): Promise<RqbitDesktopConfig> {
|
||||
return invoke<RqbitDesktopConfig>("config_default");
|
||||
return invoke<RqbitDesktopConfig>("config_default");
|
||||
}
|
||||
|
||||
async function get_current_config(): Promise<CurrentDesktopState> {
|
||||
return invoke<CurrentDesktopState>("config_current");
|
||||
return invoke<CurrentDesktopState>("config_current");
|
||||
}
|
||||
|
||||
Promise.all([get_version(), get_default_config(), get_current_config()]).then(([version, defaultConfig, currentState]) => {
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<APIContext.Provider value={API}>
|
||||
<RqbitDesktop version={version} defaultConfig={defaultConfig} currentState={currentState} />
|
||||
</APIContext.Provider>
|
||||
</StrictMode>
|
||||
Promise.all([get_version(), get_default_config(), get_current_config()]).then(
|
||||
([version, defaultConfig, currentState]) => {
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<StrictMode>
|
||||
<APIContext.Provider value={API}>
|
||||
<RqbitDesktop
|
||||
version={version}
|
||||
defaultConfig={defaultConfig}
|
||||
currentState={currentState}
|
||||
/>
|
||||
</APIContext.Provider>
|
||||
</StrictMode>
|
||||
);
|
||||
})
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,41 +3,49 @@ import { RqbitWebUI } from "./rqbit-webui-src/rqbit-web";
|
|||
import { CurrentDesktopState, RqbitDesktopConfig } from "./configuration";
|
||||
import { ConfigModal } from "./configure";
|
||||
|
||||
|
||||
export const RqbitDesktop: React.FC<{
|
||||
version: string,
|
||||
defaultConfig: RqbitDesktopConfig,
|
||||
currentState: CurrentDesktopState,
|
||||
version: string;
|
||||
defaultConfig: RqbitDesktopConfig;
|
||||
currentState: CurrentDesktopState;
|
||||
}> = ({ version, defaultConfig, currentState }) => {
|
||||
let [configured, setConfigured] = useState<boolean>(currentState.configured);
|
||||
let [config, setConfig] = useState<RqbitDesktopConfig>(currentState.config ?? defaultConfig);
|
||||
let [configurationOpened, setConfigurationOpened] = useState<boolean>(false);
|
||||
let [configured, setConfigured] = useState<boolean>(currentState.configured);
|
||||
let [config, setConfig] = useState<RqbitDesktopConfig>(
|
||||
currentState.config ?? defaultConfig
|
||||
);
|
||||
let [configurationOpened, setConfigurationOpened] = useState<boolean>(false);
|
||||
|
||||
return <>
|
||||
{configured && <RqbitWebUI title={`Rqbit Desktop v${version}`}></RqbitWebUI>}
|
||||
{configured && <a
|
||||
className="bi bi-sliders2 position-absolute top-0 start-0 p-3 text-primary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setConfigurationOpened(true);
|
||||
}}
|
||||
href="#"
|
||||
aria-label="Settings" />}
|
||||
<ConfigModal
|
||||
show={!configured || configurationOpened}
|
||||
handleStartReconfigure={() => {
|
||||
setConfigured(false);
|
||||
}}
|
||||
handleCancel={() => {
|
||||
setConfigurationOpened(false);
|
||||
}}
|
||||
handleConfigured={(config) => {
|
||||
setConfig(config);
|
||||
setConfigurationOpened(false);
|
||||
setConfigured(true);
|
||||
}}
|
||||
initialConfig={config}
|
||||
defaultConfig={defaultConfig}
|
||||
return (
|
||||
<>
|
||||
{configured && (
|
||||
<RqbitWebUI title={`Rqbit Desktop v${version}`}></RqbitWebUI>
|
||||
)}
|
||||
{configured && (
|
||||
<a
|
||||
className="bi bi-sliders2 position-absolute top-0 start-0 p-3 text-primary"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setConfigurationOpened(true);
|
||||
}}
|
||||
href="#"
|
||||
aria-label="Settings"
|
||||
/>
|
||||
)}
|
||||
<ConfigModal
|
||||
show={!configured || configurationOpened}
|
||||
handleStartReconfigure={() => {
|
||||
setConfigured(false);
|
||||
}}
|
||||
handleCancel={() => {
|
||||
setConfigurationOpened(false);
|
||||
}}
|
||||
handleConfigured={(config) => {
|
||||
setConfig(config);
|
||||
setConfigurationOpened(false);
|
||||
setConfigured(true);
|
||||
}}
|
||||
initialConfig={config}
|
||||
defaultConfig={defaultConfig}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue