mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 12:24:38 +03:00
390 lines
12 KiB
TypeScript
390 lines
12 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import { Button, Form, InputGroup } from "react-bootstrap";
|
|
import * as GQL from "src/core/generated-graphql";
|
|
import { StashService } from "src/core/StashService";
|
|
import { useToast } from "src/hooks";
|
|
import { Icon, LoadingIndicator } from "src/components/Shared";
|
|
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
|
|
|
export const SettingsConfigurationPanel: React.FC = () => {
|
|
const Toast = useToast();
|
|
// Editing config state
|
|
const [stashes, setStashes] = useState<string[]>([]);
|
|
const [databasePath, setDatabasePath] = useState<string | undefined>(
|
|
undefined
|
|
);
|
|
const [generatedPath, setGeneratedPath] = useState<string | undefined>(
|
|
undefined
|
|
);
|
|
const [maxTranscodeSize, setMaxTranscodeSize] = useState<
|
|
GQL.StreamingResolutionEnum | undefined
|
|
>(undefined);
|
|
const [maxStreamingTranscodeSize, setMaxStreamingTranscodeSize] = useState<
|
|
GQL.StreamingResolutionEnum | undefined
|
|
>(undefined);
|
|
const [username, setUsername] = useState<string | undefined>(undefined);
|
|
const [password, setPassword] = useState<string | undefined>(undefined);
|
|
const [logFile, setLogFile] = useState<string | undefined>();
|
|
const [logOut, setLogOut] = useState<boolean>(true);
|
|
const [logLevel, setLogLevel] = useState<string>("Info");
|
|
const [logAccess, setLogAccess] = useState<boolean>(true);
|
|
const [excludes, setExcludes] = useState<string[]>([]);
|
|
|
|
const { data, error, loading } = StashService.useConfiguration();
|
|
|
|
const [updateGeneralConfig] = StashService.useConfigureGeneral({
|
|
stashes,
|
|
databasePath,
|
|
generatedPath,
|
|
maxTranscodeSize,
|
|
maxStreamingTranscodeSize,
|
|
username,
|
|
password,
|
|
logFile,
|
|
logOut,
|
|
logLevel,
|
|
logAccess,
|
|
excludes
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!data?.configuration || error) return;
|
|
|
|
const conf = data.configuration;
|
|
if (conf.general) {
|
|
setStashes(conf.general.stashes ?? []);
|
|
setDatabasePath(conf.general.databasePath);
|
|
setGeneratedPath(conf.general.generatedPath);
|
|
setMaxTranscodeSize(conf.general.maxTranscodeSize ?? undefined);
|
|
setMaxStreamingTranscodeSize(
|
|
conf.general.maxStreamingTranscodeSize ?? undefined
|
|
);
|
|
setUsername(conf.general.username);
|
|
setPassword(conf.general.password);
|
|
setLogFile(conf.general.logFile ?? undefined);
|
|
setLogOut(conf.general.logOut);
|
|
setLogLevel(conf.general.logLevel);
|
|
setLogAccess(conf.general.logAccess);
|
|
setExcludes(conf.general.excludes);
|
|
}
|
|
}, [data, error]);
|
|
|
|
function onStashesChanged(directories: string[]) {
|
|
setStashes(directories);
|
|
}
|
|
|
|
function excludeRegexChanged(idx: number, value: string) {
|
|
const newExcludes = excludes.map((regex, i) => {
|
|
const ret = idx !== i ? regex : value;
|
|
return ret;
|
|
});
|
|
setExcludes(newExcludes);
|
|
}
|
|
|
|
function excludeRemoveRegex(idx: number) {
|
|
const newExcludes = excludes.filter((_regex, i) => i !== idx);
|
|
|
|
setExcludes(newExcludes);
|
|
}
|
|
|
|
function excludeAddRegex() {
|
|
const demo = "sample\\.mp4$";
|
|
const newExcludes = excludes.concat(demo);
|
|
|
|
setExcludes(newExcludes);
|
|
}
|
|
|
|
async function onSave() {
|
|
try {
|
|
const result = await updateGeneralConfig();
|
|
// eslint-disable-next-line no-console
|
|
console.log(result);
|
|
Toast.success({ content: "Updated config" });
|
|
} catch (e) {
|
|
Toast.error(e);
|
|
}
|
|
}
|
|
|
|
const transcodeQualities = [
|
|
GQL.StreamingResolutionEnum.Low,
|
|
GQL.StreamingResolutionEnum.Standard,
|
|
GQL.StreamingResolutionEnum.StandardHd,
|
|
GQL.StreamingResolutionEnum.FullHd,
|
|
GQL.StreamingResolutionEnum.FourK,
|
|
GQL.StreamingResolutionEnum.Original
|
|
].map(resolutionToString);
|
|
|
|
function resolutionToString(r: GQL.StreamingResolutionEnum | undefined) {
|
|
switch (r) {
|
|
case GQL.StreamingResolutionEnum.Low:
|
|
return "240p";
|
|
case GQL.StreamingResolutionEnum.Standard:
|
|
return "480p";
|
|
case GQL.StreamingResolutionEnum.StandardHd:
|
|
return "720p";
|
|
case GQL.StreamingResolutionEnum.FullHd:
|
|
return "1080p";
|
|
case GQL.StreamingResolutionEnum.FourK:
|
|
return "4k";
|
|
case GQL.StreamingResolutionEnum.Original:
|
|
return "Original";
|
|
}
|
|
|
|
return "Original";
|
|
}
|
|
|
|
function translateQuality(quality: string) {
|
|
switch (quality) {
|
|
case "240p":
|
|
return GQL.StreamingResolutionEnum.Low;
|
|
case "480p":
|
|
return GQL.StreamingResolutionEnum.Standard;
|
|
case "720p":
|
|
return GQL.StreamingResolutionEnum.StandardHd;
|
|
case "1080p":
|
|
return GQL.StreamingResolutionEnum.FullHd;
|
|
case "4k":
|
|
return GQL.StreamingResolutionEnum.FourK;
|
|
case "Original":
|
|
return GQL.StreamingResolutionEnum.Original;
|
|
}
|
|
|
|
return GQL.StreamingResolutionEnum.Original;
|
|
}
|
|
|
|
if (error) return <h1>{error.message}</h1>;
|
|
if (!data?.configuration || loading) return <LoadingIndicator />;
|
|
|
|
return (
|
|
<>
|
|
<h4>Library</h4>
|
|
<Form.Group>
|
|
<Form.Group id="stashes">
|
|
<h6>Stashes</h6>
|
|
<FolderSelect
|
|
directories={stashes}
|
|
onDirectoriesChanged={onStashesChanged}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Directory locations to your content
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group id="database-path">
|
|
<h6>Database Path</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
defaultValue={databasePath}
|
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setDatabasePath(e.currentTarget.value)}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
File location for the SQLite database (requires restart)
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group id="generated-path">
|
|
<h6>Generated Path</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
defaultValue={generatedPath}
|
|
onChange={(e: React.FormEvent<HTMLInputElement>) => setGeneratedPath(e.currentTarget.value)}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Directory location for the generated files (scene markers, scene
|
|
previews, sprites, etc)
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group>
|
|
<h6>Excluded Patterns</h6>
|
|
<Form.Group>
|
|
{excludes &&
|
|
excludes.map((regexp, i) => (
|
|
<InputGroup>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
value={regexp}
|
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
|
excludeRegexChanged(i, e.currentTarget.value)
|
|
}
|
|
/>
|
|
<InputGroup.Append>
|
|
<Button
|
|
variant="danger"
|
|
onClick={() => excludeRemoveRegex(i)}
|
|
>
|
|
<Icon icon="minus" />
|
|
</Button>
|
|
</InputGroup.Append>
|
|
</InputGroup>
|
|
))}
|
|
</Form.Group>
|
|
<Button className="minimal" onClick={() => excludeAddRegex()}>
|
|
<Icon icon="plus" />
|
|
</Button>
|
|
<Form.Text>
|
|
<a
|
|
href="https://github.com/stashapp/stash/wiki/Exclude-file-configuration"
|
|
rel="noopener noreferrer"
|
|
target="_blank"
|
|
>
|
|
<span>
|
|
Regexps of files/paths to exclude from Scan and add to Clean
|
|
</span>
|
|
<Icon icon="question-circle" />
|
|
</a>
|
|
</Form.Text>
|
|
</Form.Group>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<Form.Group>
|
|
<h4>Video</h4>
|
|
<Form.Group id="transcode-size">
|
|
<h6>Maximum transcode size</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
as="select"
|
|
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
|
setMaxTranscodeSize(translateQuality(event.currentTarget.value))
|
|
}
|
|
value={resolutionToString(maxTranscodeSize)}
|
|
>
|
|
{transcodeQualities.map(q => (
|
|
<option key={q} value={q}>
|
|
{q}
|
|
</option>
|
|
))}
|
|
</Form.Control>
|
|
<Form.Text className="text-muted">
|
|
Maximum size for generated transcodes
|
|
</Form.Text>
|
|
</Form.Group>
|
|
<Form.Group id="streaming-transcode-size">
|
|
<h6>Maximum streaming transcode size</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
as="select"
|
|
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
|
setMaxStreamingTranscodeSize(
|
|
translateQuality(event.currentTarget.value)
|
|
)
|
|
}
|
|
value={resolutionToString(maxStreamingTranscodeSize)}
|
|
>
|
|
{transcodeQualities.map(q => (
|
|
<option key={q} value={q}>
|
|
{q}
|
|
</option>
|
|
))}
|
|
</Form.Control>
|
|
<Form.Text className="text-muted">
|
|
Maximum size for transcoded streams
|
|
</Form.Text>
|
|
</Form.Group>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<Form.Group>
|
|
<h4>Authentication</h4>
|
|
<Form.Group id="username">
|
|
<h6>Username</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
defaultValue={username}
|
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
|
setUsername(e.currentTarget.value)
|
|
}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Username to access Stash. Leave blank to disable user authentication
|
|
</Form.Text>
|
|
</Form.Group>
|
|
<Form.Group id="password">
|
|
<h6>Password</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
type="password"
|
|
defaultValue={password}
|
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
|
setPassword(e.currentTarget.value)
|
|
}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Password to access Stash. Leave blank to disable user authentication
|
|
</Form.Text>
|
|
</Form.Group>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<h4>Logging</h4>
|
|
<Form.Group id="log-file">
|
|
<h6>Log file</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
defaultValue={logFile}
|
|
onChange={(e: React.FormEvent<HTMLInputElement>) =>
|
|
setLogFile(e.currentTarget.value)
|
|
}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Path to the file to output logging to. Blank to disable file logging.
|
|
Requires restart.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group>
|
|
<Form.Check
|
|
id="log-terminal"
|
|
checked={logOut}
|
|
label="Log to terminal"
|
|
onChange={() => setLogOut(!logOut)}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Logs to the terminal in addition to a file. Always true if file
|
|
logging is disabled. Requires restart.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<Form.Group id="log-level">
|
|
<h6>Log Level</h6>
|
|
<Form.Control
|
|
className="col col-sm-6"
|
|
as="select"
|
|
onChange={(event: React.FormEvent<HTMLSelectElement>) =>
|
|
setLogLevel(event.currentTarget.value)
|
|
}
|
|
value={logLevel}
|
|
>
|
|
{["Debug", "Info", "Warning", "Error"].map(o => (
|
|
<option key={o} value={o}>
|
|
{o}
|
|
</option>
|
|
))}
|
|
</Form.Control>
|
|
</Form.Group>
|
|
|
|
<Form.Group>
|
|
<Form.Check
|
|
id="log-http"
|
|
checked={logAccess}
|
|
label="Log http access"
|
|
onChange={() => setLogAccess(!logAccess)}
|
|
/>
|
|
<Form.Text className="text-muted">
|
|
Logs http access to the terminal. Requires restart.
|
|
</Form.Text>
|
|
</Form.Group>
|
|
|
|
<hr />
|
|
|
|
<Button variant="primary" onClick={() => onSave()}>
|
|
Save
|
|
</Button>
|
|
</>
|
|
);
|
|
};
|