Setup improvements (#3504)

* Improve setup redirects
* Add network database warning
* Add cache directory to setup
This commit is contained in:
DingDongSoLong4 2023-03-06 23:28:19 +02:00 committed by GitHub
parent 42fde9bc9f
commit 71e1451c94
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 168 additions and 47 deletions

View file

@ -6,6 +6,8 @@ input SetupInput {
databaseFile: String!
"""Empty to indicate default"""
generatedLocation: String!
"""Empty to indicate default"""
cacheLocation: String!
}
enum StreamingResolutionEnum {

View file

@ -100,6 +100,8 @@ type SetupInput struct {
DatabaseFile string `json:"databaseFile"`
// Empty to indicate default
GeneratedLocation string `json:"generatedLocation"`
// Empty to indicate default
CacheLocation string `json:"cacheLocation"`
}
type Manager struct {
@ -588,6 +590,9 @@ func setSetupDefaults(input *SetupInput) {
if input.GeneratedLocation == "" {
input.GeneratedLocation = filepath.Join(configDir, "generated")
}
if input.CacheLocation == "" {
input.CacheLocation = filepath.Join(configDir, "cache")
}
if input.DatabaseFile == "" {
input.DatabaseFile = filepath.Join(configDir, "stash-go.sqlite")
@ -633,6 +638,17 @@ func (s *Manager) Setup(ctx context.Context, input SetupInput) error {
s.Config.Set(config.Generated, input.GeneratedLocation)
}
// create the cache directory if it does not exist
if !c.HasOverride(config.Cache) {
if exists, _ := fsutil.DirExists(input.CacheLocation); !exists {
if err := os.Mkdir(input.CacheLocation, 0755); err != nil {
return fmt.Errorf("error creating cache directory: %v", err)
}
}
s.Config.Set(config.Cache, input.CacheLocation)
}
// set the configuration
if !c.HasOverride(config.Database) {
s.Config.Set(config.Database, input.DatabaseFile)

View file

@ -1,5 +1,11 @@
import React, { Suspense, useEffect, useState } from "react";
import { Route, Switch, useRouteMatch } from "react-router-dom";
import {
Route,
Switch,
useHistory,
useLocation,
useRouteMatch,
} from "react-router-dom";
import { IntlProvider, CustomFormats } from "react-intl";
import { Helmet } from "react-helmet";
import cloneDeep from "lodash-es/cloneDeep";
@ -30,7 +36,7 @@ import { InteractiveProvider } from "./hooks/Interactive/context";
import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
import { IUIConfig } from "./core/config";
import { releaseNotes } from "./docs/en/ReleaseNotes";
import { getPlatformURL, getBaseURL } from "./core/createClient";
import { getPlatformURL } from "./core/createClient";
import { lazyComponent } from "./utils/lazyComponent";
const Performers = lazyComponent(
@ -126,6 +132,8 @@ export const App: React.FC = () => {
setLocale();
}, [language]);
const location = useLocation();
const history = useHistory();
const setupMatch = useRouteMatch(["/setup", "/migrate"]);
// redirect to setup or migrate as needed
@ -134,27 +142,24 @@ export const App: React.FC = () => {
return;
}
const baseURL = getBaseURL();
const { status } = systemStatusData.systemStatus;
if (
window.location.pathname !== baseURL + "setup" &&
systemStatusData.systemStatus.status === GQL.SystemStatusEnum.Setup
location.pathname !== "/setup" &&
status === GQL.SystemStatusEnum.Setup
) {
// redirect to setup page
const newURL = new URL("setup", window.location.origin + baseURL);
window.location.href = newURL.toString();
history.push("/setup");
}
if (
window.location.pathname !== baseURL + "migrate" &&
systemStatusData.systemStatus.status ===
GQL.SystemStatusEnum.NeedsMigration
location.pathname !== "/migrate" &&
status === GQL.SystemStatusEnum.NeedsMigration
) {
// redirect to setup page
const newURL = new URL("migrate", window.location.origin + baseURL);
window.location.href = newURL.toString();
// redirect to migrate page
history.push("/migrate");
}
}, [systemStatusData]);
}, [systemStatusData, setupMatch, history, location]);
function maybeRenderNavbar() {
// don't render navbar for setup views
@ -200,7 +205,7 @@ export const App: React.FC = () => {
}
function maybeRenderReleaseNotes() {
if (setupMatch || config.loading || config.error) {
if (setupMatch || !systemStatusData || config.loading || config.error) {
return;
}

View file

@ -116,6 +116,14 @@ export const SettingsConfigurationPanel: React.FC = () => {
onChange={(v) => saveGeneral({ generatedPath: v })}
/>
<StringSetting
id="cache-path"
headingID="config.general.cache_path_head"
subHeadingID="config.general.cache_location"
value={general.cachePath ?? undefined}
onChange={(v) => saveGeneral({ cachePath: v })}
/>
<StringSetting
id="scrapers-path"
headingID="config.general.scrapers_path.heading"
@ -132,14 +140,6 @@ export const SettingsConfigurationPanel: React.FC = () => {
onChange={(v) => saveGeneral({ metadataPath: v })}
/>
<StringSetting
id="cache-path"
headingID="config.general.cache_path_head"
subHeadingID="config.general.cache_location"
value={general.cachePath ?? undefined}
onChange={(v) => saveGeneral({ cachePath: v })}
/>
<StringSetting
id="custom-performer-image-location"
headingID="config.ui.performers.options.image_location.heading"

View file

@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState } from "react";
import { Button, Card, Container, Form } from "react-bootstrap";
import { useIntl, FormattedMessage } from "react-intl";
import { getBaseURL } from "src/core/createClient";
import { useHistory } from "react-router-dom";
import * as GQL from "src/core/generated-graphql";
import { useSystemStatus, mutateMigrate } from "src/core/StashService";
import { migrationNotes } from "src/docs/en/MigrationNotes";
@ -15,6 +15,7 @@ export const Migrate: React.FC = () => {
const [migrateError, setMigrateError] = useState("");
const intl = useIntl();
const history = useHistory();
// if database path includes path separators, then this is passed through
// to the migration path. Extract the base name of the database file.
@ -109,8 +110,7 @@ export const Migrate: React.FC = () => {
systemStatus.systemStatus.status !== GQL.SystemStatusEnum.NeedsMigration
) {
// redirect to main page
const newURL = new URL("/", window.location.toString());
window.location.href = newURL.toString();
history.push("/");
return <LoadingIndicator />;
}
@ -122,8 +122,7 @@ export const Migrate: React.FC = () => {
backupPath: backupPath ?? "",
});
const newURL = new URL("", window.location.origin + getBaseURL());
window.location.href = newURL.toString();
history.push("/");
} catch (e) {
if (e instanceof Error) setMigrateError(e.message ?? e.toString());
setMigrateLoading(false);

View file

@ -14,7 +14,7 @@ import {
useConfigureUI,
useSystemStatus,
} from "src/core/StashService";
import { Link } from "react-router-dom";
import { Link, useHistory } from "react-router-dom";
import { ConfigurationContext } from "src/hooks/Config";
import StashConfiguration from "../Settings/StashConfiguration";
import { Icon } from "../Shared/Icon";
@ -37,14 +37,18 @@ export const Setup: React.FC = () => {
const [configLocation, setConfigLocation] = useState("");
const [stashes, setStashes] = useState<GQL.StashConfig[]>([]);
const [showStashAlert, setShowStashAlert] = useState(false);
const [generatedLocation, setGeneratedLocation] = useState("");
const [databaseFile, setDatabaseFile] = useState("");
const [generatedLocation, setGeneratedLocation] = useState("");
const [cacheLocation, setCacheLocation] = useState("");
const [loading, setLoading] = useState(false);
const [setupError, setSetupError] = useState("");
const intl = useIntl();
const history = useHistory();
const [showGeneratedDialog, setShowGeneratedDialog] = useState(false);
const [showGeneratedSelectDialog, setShowGeneratedSelectDialog] =
useState(false);
const [showCacheSelectDialog, setShowCacheSelectDialog] = useState(false);
const { data: systemStatus, loading: statusLoading } = useSystemStatus();
@ -233,20 +237,20 @@ export const Setup: React.FC = () => {
);
}
function onGeneratedClosed(d?: string) {
function onGeneratedSelectClosed(d?: string) {
if (d) {
setGeneratedLocation(d);
}
setShowGeneratedDialog(false);
setShowGeneratedSelectDialog(false);
}
function maybeRenderGeneratedSelectDialog() {
if (!showGeneratedDialog) {
if (!showGeneratedSelectDialog) {
return;
}
return <FolderSelectDialog onClose={onGeneratedClosed} />;
return <FolderSelectDialog onClose={onGeneratedSelectClosed} />;
}
function maybeRenderGenerated() {
@ -279,7 +283,64 @@ export const Setup: React.FC = () => {
<Button
variant="secondary"
className="text-input"
onClick={() => setShowGeneratedDialog(true)}
onClick={() => setShowGeneratedSelectDialog(true)}
>
<Icon icon={faEllipsisH} />
</Button>
</InputGroup.Append>
</InputGroup>
</Form.Group>
);
}
}
function onCacheSelectClosed(d?: string) {
if (d) {
setCacheLocation(d);
}
setShowCacheSelectDialog(false);
}
function maybeRenderCacheSelectDialog() {
if (!showCacheSelectDialog) {
return;
}
return <FolderSelectDialog onClose={onCacheSelectClosed} />;
}
function maybeRenderCache() {
if (!configuration?.general.cachePath) {
return (
<Form.Group id="cache">
<h3>
<FormattedMessage id="setup.paths.where_can_stash_store_cache_files" />
</h3>
<p>
<FormattedMessage
id="setup.paths.where_can_stash_store_cache_files_description"
values={{
code: (chunks: string) => <code>{chunks}</code>,
}}
/>
</p>
<InputGroup>
<Form.Control
className="text-input"
value={cacheLocation}
placeholder={intl.formatMessage({
id: "setup.paths.path_to_cache_directory_empty_for_default",
})}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setCacheLocation(e.currentTarget.value)
}
/>
<InputGroup.Append>
<Button
variant="secondary"
className="text-input"
onClick={() => setShowCacheSelectDialog(true)}
>
<Icon icon={faEllipsisH} />
</Button>
@ -328,6 +389,13 @@ export const Setup: React.FC = () => {
code: (chunks: string) => <code>{chunks}</code>,
}}
/>
<br />
<FormattedMessage
id="setup.paths.where_can_stash_store_its_database_warning"
values={{
strong: (chunks: string) => <strong>{chunks}</strong>,
}}
/>
</p>
<Form.Control
className="text-input"
@ -341,6 +409,7 @@ export const Setup: React.FC = () => {
/>
</Form.Group>
{maybeRenderGenerated()}
{maybeRenderCache()}
</section>
<section className="mt-5">
<div className="d-flex justify-content-center">
@ -404,6 +473,7 @@ export const Setup: React.FC = () => {
configLocation,
databaseFile,
generatedLocation,
cacheLocation,
stashes,
});
// Set lastNoteSeen to hide release notes dialog
@ -473,6 +543,20 @@ export const Setup: React.FC = () => {
</code>
</dd>
</dl>
<dl>
<dt>
<FormattedMessage id="setup.confirm.cache_directory" />
</dt>
<dd>
<code>
{cacheLocation !== ""
? cacheLocation
: intl.formatMessage({
id: "setup.confirm.default_cache_location",
})}
</code>
</dd>
</dl>
</section>
<section className="mt-5">
<div className="d-flex justify-content-center">
@ -592,7 +676,7 @@ export const Setup: React.FC = () => {
<section className="mt-5">
<div className="d-flex justify-content-center">
<Link to="/settings?tab=library">
<Button variant="success mx-2 p-5" onClick={() => goBack(2)}>
<Button variant="success mx-2 p-5">
<FormattedMessage id="actions.finish" />
</Button>
</Link>
@ -616,12 +700,12 @@ export const Setup: React.FC = () => {
}
if (
step === 0 &&
systemStatus &&
systemStatus.systemStatus.status !== GQL.SystemStatusEnum.Setup
) {
// redirect to main page
const newURL = new URL("/", window.location.toString());
window.location.href = newURL.toString();
history.push("/");
return <LoadingIndicator />;
}
@ -654,6 +738,7 @@ export const Setup: React.FC = () => {
return (
<Container>
{maybeRenderGeneratedSelectDialog()}
{maybeRenderCacheSelectDialog()}
<h1 className="text-center">
<FormattedMessage id="setup.stash_setup_wizard" />
</h1>

View file

@ -331,14 +331,22 @@ export const mutateSetup = (input: GQL.SetupInput) =>
client.mutate<GQL.SetupMutation>({
mutation: GQL.SetupDocument,
variables: { input },
refetchQueries: getQueryNames([GQL.ConfigurationDocument]),
update: deleteCache([GQL.ConfigurationDocument]),
refetchQueries: getQueryNames([
GQL.ConfigurationDocument,
GQL.SystemStatusDocument,
]),
update: deleteCache([GQL.ConfigurationDocument, GQL.SystemStatusDocument]),
});
export const mutateMigrate = (input: GQL.MigrateInput) =>
client.mutate<GQL.MigrateMutation>({
mutation: GQL.MigrateDocument,
variables: { input },
refetchQueries: getQueryNames([
GQL.ConfigurationDocument,
GQL.SystemStatusDocument,
]),
update: deleteCache([GQL.ConfigurationDocument, GQL.SystemStatusDocument]),
});
export const useDirectory = (path?: string) =>

View file

@ -120,8 +120,8 @@ export const createClient = () => {
const platformUrl = getPlatformURL();
const wsPlatformUrl = getPlatformURL(true);
const url = `${platformUrl.toString()}graphql`;
const wsUrl = `${wsPlatformUrl.toString()}graphql`;
const url = `${platformUrl}graphql`;
const wsUrl = `${wsPlatformUrl}graphql`;
const httpLink = createUploadLink({ uri: url });
@ -140,7 +140,7 @@ export const createClient = () => {
if (networkError && (networkError as ServerError).statusCode === 401) {
// redirect to login page
const newURL = new URL(
`${window.STASH_BASE_URL}login`,
`${getBaseURL()}login`,
window.location.toString()
);
newURL.searchParams.append("returnURL", window.location.href);

View file

@ -334,7 +334,7 @@
"heading": "Scrapers Path"
},
"scraping": "Scraping",
"sqlite_location": "File location for the SQLite database (requires restart)",
"sqlite_location": "File location for the SQLite database (requires restart). WARNING: storing the database on a different system to where the Stash server is run from (i.e. over the network) is unsupported!",
"video_ext_desc": "Comma-delimited list of file extensions that will be identified as videos.",
"video_ext_head": "Video Extensions",
"video_head": "Video"
@ -1027,8 +1027,10 @@
"setup": {
"confirm": {
"almost_ready": "We're almost ready to complete the configuration. Please confirm the following settings. You can click back to change anything incorrect. If everything looks good, click Confirm to create your system.",
"cache_directory": "Cache directory",
"configuration_file_location": "Configuration file location:",
"database_file_path": "Database file path",
"default_cache_location": "<path containing configuration file>/cache",
"default_db_location": "<path containing configuration file>/stash-go.sqlite",
"default_generated_content_location": "<path containing configuration file>/generated",
"generated_directory": "Generated directory",
@ -1064,12 +1066,16 @@
},
"paths": {
"database_filename_empty_for_default": "database filename (empty for default)",
"description": "Next up, we need to determine where to find your porn collection, where to store the stash database and generated files. These settings can be changed later if needed.",
"description": "Next up, we need to determine where to find your porn collection, and where to store the Stash database, generated files and cache files. These settings can be changed later if needed.",
"path_to_cache_directory_empty_for_default": "path to cache directory (empty for default)",
"path_to_generated_directory_empty_for_default": "path to generated directory (empty for default)",
"set_up_your_paths": "Set up your paths",
"stash_alert": "No library paths have been selected. No media will be able to be scanned into Stash. Are you sure?",
"where_can_stash_store_cache_files": "Where can Stash store cache files?",
"where_can_stash_store_cache_files_description": "In order for some functionality like HLS live transcoding to function, Stash requires a cache directory for temporary files. By default, Stash will create a <code>cache</code> directory within the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
"where_can_stash_store_its_database": "Where can Stash store its database?",
"where_can_stash_store_its_database_description": "Stash uses an sqlite database to store your porn metadata. By default, this will be created as <code>stash-go.sqlite</code> in the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) filename.",
"where_can_stash_store_its_database_description": "Stash uses an SQLite database to store your porn metadata. By default, this will be created as <code>stash-go.sqlite</code> in the directory containing your config file. If you want to change this, please enter an absolute or relative (to the current working directory) filename.",
"where_can_stash_store_its_database_warning": "WARNING: storing the database on a different system to where Stash is run from (e.g. storing the database on a NAS while running the Stash server on another computer) is <strong>unsupported</strong>! SQLite is not intended for use across a network, and attempting to do so can very easily cause your entire database to become corrupted.",
"where_can_stash_store_its_generated_content": "Where can Stash store its generated content?",
"where_can_stash_store_its_generated_content_description": "In order to provide thumbnails, previews and sprites, Stash generates images and videos. This also includes transcodes for unsupported file formats. By default, Stash will create a <code>generated</code> directory within the directory containing your config file. If you want to change where this generated media will be stored, please enter an absolute or relative (to the current working directory) path. Stash will create this directory if it does not already exist.",
"where_is_your_porn_located": "Where is your porn located?",