mirror of
https://github.com/stashapp/stash.git
synced 2025-12-17 04:14:39 +03:00
Config for stash-box instances (#748)
This commit is contained in:
@@ -22,6 +22,11 @@ fragment ConfigGeneralData on ConfigGeneralResult {
|
||||
excludes
|
||||
scraperUserAgent
|
||||
scraperCDPPath
|
||||
stashBoxes {
|
||||
name
|
||||
endpoint
|
||||
api_key
|
||||
}
|
||||
}
|
||||
|
||||
fragment ConfigInterfaceData on ConfigInterfaceResult {
|
||||
|
||||
@@ -69,6 +69,8 @@ input ConfigGeneralInput {
|
||||
scraperUserAgent: String
|
||||
"""Scraper CDP path. Path to chrome executable or remote address"""
|
||||
scraperCDPPath: String
|
||||
"""Stash-box instances used for tagging"""
|
||||
stashBoxes: [StashBoxInput!]!
|
||||
}
|
||||
|
||||
type ConfigGeneralResult {
|
||||
@@ -118,6 +120,8 @@ type ConfigGeneralResult {
|
||||
scraperUserAgent: String
|
||||
"""Scraper CDP path. Path to chrome executable or remote address"""
|
||||
scraperCDPPath: String
|
||||
"""Stash-box instances used for tagging"""
|
||||
stashBoxes: [StashBox!]!
|
||||
}
|
||||
|
||||
input ConfigInterfaceInput {
|
||||
|
||||
11
graphql/schema/types/stash-box.graphql
Normal file
11
graphql/schema/types/stash-box.graphql
Normal file
@@ -0,0 +1,11 @@
|
||||
type StashBox {
|
||||
endpoint: String!
|
||||
api_key: String!
|
||||
name: String!
|
||||
}
|
||||
|
||||
input StashBoxInput {
|
||||
endpoint: String!
|
||||
api_key: String!
|
||||
name: String!
|
||||
}
|
||||
@@ -130,6 +130,13 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co
|
||||
refreshScraperCache = true
|
||||
}
|
||||
|
||||
if input.StashBoxes != nil {
|
||||
if err := config.ValidateStashBoxes(input.StashBoxes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Set(config.StashBoxes, input.StashBoxes)
|
||||
}
|
||||
|
||||
if err := config.Write(); err != nil {
|
||||
return makeConfigGeneralResult(), err
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
|
||||
Excludes: config.GetExcludes(),
|
||||
ScraperUserAgent: &scraperUserAgent,
|
||||
ScraperCDPPath: &scraperCDPPath,
|
||||
StashBoxes: config.GetStashBoxes(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
@@ -67,6 +68,9 @@ const ScrapersPath = "scrapers_path"
|
||||
const ScraperUserAgent = "scraper_user_agent"
|
||||
const ScraperCDPPath = "scraper_cdp_path"
|
||||
|
||||
// stash-box options
|
||||
const StashBoxes = "stash_boxes"
|
||||
|
||||
// plugin options
|
||||
const PluginsPath = "plugins_path"
|
||||
|
||||
@@ -198,6 +202,12 @@ func GetScraperCDPPath() string {
|
||||
return viper.GetString(ScraperCDPPath)
|
||||
}
|
||||
|
||||
func GetStashBoxes() []*models.StashBox {
|
||||
var boxes []*models.StashBox
|
||||
_ = viper.UnmarshalKey(StashBoxes, &boxes)
|
||||
return boxes
|
||||
}
|
||||
|
||||
func GetDefaultPluginsPath() string {
|
||||
// default to the same directory as the config file
|
||||
fn := filepath.Join(GetConfigPath(), "plugins")
|
||||
@@ -332,6 +342,21 @@ func ValidateCredentials(username string, password string) bool {
|
||||
return username == authUser && err == nil
|
||||
}
|
||||
|
||||
func ValidateStashBoxes(boxes []*models.StashBoxInput) error {
|
||||
isMulti := len(boxes) > 1
|
||||
|
||||
for _, box := range boxes {
|
||||
if box.APIKey == "" {
|
||||
return errors.New("Stash-box API Key cannot be blank")
|
||||
} else if box.Endpoint == "" {
|
||||
return errors.New("Stash-box Endpoint cannot be blank")
|
||||
} else if isMulti && box.Name == "" {
|
||||
return errors.New("Stash-box Name cannot be blank")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxSessionAge gets the maximum age for session cookies, in seconds.
|
||||
// Session cookie expiry times are refreshed every request.
|
||||
func GetMaxSessionAge() int {
|
||||
|
||||
@@ -5,6 +5,9 @@ import { useConfiguration, useConfigureGeneral } from "src/core/StashService";
|
||||
import { useToast } from "src/hooks";
|
||||
import { Icon, LoadingIndicator } from "src/components/Shared";
|
||||
import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect";
|
||||
import StashBoxConfiguration, {
|
||||
IStashBoxInstance,
|
||||
} from "./StashBoxConfiguration";
|
||||
|
||||
export const SettingsConfigurationPanel: React.FC = () => {
|
||||
const Toast = useToast();
|
||||
@@ -54,6 +57,7 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
const [scraperCDPPath, setScraperCDPPath] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [stashBoxes, setStashBoxes] = useState<IStashBoxInstance[]>([]);
|
||||
|
||||
const { data, error, loading } = useConfiguration();
|
||||
|
||||
@@ -82,6 +86,14 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
excludes,
|
||||
scraperUserAgent,
|
||||
scraperCDPPath,
|
||||
stashBoxes: stashBoxes.map(
|
||||
(b) =>
|
||||
({
|
||||
name: b?.name ?? "",
|
||||
api_key: b?.api_key ?? "",
|
||||
endpoint: b?.endpoint ?? "",
|
||||
} as GQL.StashBoxInput)
|
||||
),
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
@@ -114,6 +126,14 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
setExcludes(conf.general.excludes);
|
||||
setScraperUserAgent(conf.general.scraperUserAgent ?? undefined);
|
||||
setScraperCDPPath(conf.general.scraperCDPPath ?? undefined);
|
||||
setStashBoxes(
|
||||
conf.general.stashBoxes.map((box, i) => ({
|
||||
name: box?.name ?? undefined,
|
||||
endpoint: box.endpoint,
|
||||
api_key: box.api_key,
|
||||
index: i,
|
||||
})) ?? []
|
||||
);
|
||||
}
|
||||
}, [data, error]);
|
||||
|
||||
@@ -547,6 +567,11 @@ export const SettingsConfigurationPanel: React.FC = () => {
|
||||
</Form.Group>
|
||||
</Form.Group>
|
||||
|
||||
<hr />
|
||||
<Form.Group>
|
||||
<h4>Stash-box integration</h4>
|
||||
<StashBoxConfiguration boxes={stashBoxes} saveBoxes={setStashBoxes} />
|
||||
</Form.Group>
|
||||
<hr />
|
||||
|
||||
<Form.Group>
|
||||
|
||||
137
ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx
Normal file
137
ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx
Normal file
@@ -0,0 +1,137 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button, Form, InputGroup } from "react-bootstrap";
|
||||
import { Icon } from "src/components/Shared";
|
||||
|
||||
interface IInstanceProps {
|
||||
instance: IStashBoxInstance;
|
||||
onSave: (instance: IStashBoxInstance) => void;
|
||||
onDelete: (id: number) => void;
|
||||
isMulti: boolean;
|
||||
}
|
||||
|
||||
const Instance: React.FC<IInstanceProps> = ({
|
||||
instance,
|
||||
onSave,
|
||||
onDelete,
|
||||
isMulti,
|
||||
}) => {
|
||||
const handleInput = (key: string, value: string) => {
|
||||
const newObj = {
|
||||
...instance,
|
||||
[key]: value,
|
||||
};
|
||||
onSave(newObj);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Group className="row no-gutters">
|
||||
<InputGroup className="col">
|
||||
<Form.Control
|
||||
placeholder="Name"
|
||||
className="text-input col-3 stash-box-name"
|
||||
value={instance?.name}
|
||||
isValid={!isMulti || (instance?.name?.length ?? 0) > 0}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
handleInput("name", e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<Form.Control
|
||||
placeholder="GraphQL endpoint"
|
||||
className="text-input col-3 stash-box-endpoint"
|
||||
value={instance?.endpoint}
|
||||
isValid={(instance?.endpoint?.length ?? 0) > 0}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
handleInput("endpoint", e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<Form.Control
|
||||
placeholder="API key"
|
||||
className="text-input col-3 stash-box-apikey"
|
||||
value={instance?.api_key}
|
||||
isValid={(instance?.api_key?.length ?? 0) > 0}
|
||||
onInput={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
handleInput("api_key", e.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<InputGroup.Append>
|
||||
<Button
|
||||
className=""
|
||||
variant="danger"
|
||||
title="Delete"
|
||||
onClick={() => onDelete(instance.index)}
|
||||
>
|
||||
<Icon icon="minus" />
|
||||
</Button>
|
||||
</InputGroup.Append>
|
||||
</InputGroup>
|
||||
</Form.Group>
|
||||
);
|
||||
};
|
||||
|
||||
interface IStashBoxConfigurationProps {
|
||||
boxes: IStashBoxInstance[];
|
||||
saveBoxes: (boxes: IStashBoxInstance[]) => void;
|
||||
}
|
||||
|
||||
export interface IStashBoxInstance {
|
||||
name?: string;
|
||||
endpoint?: string;
|
||||
api_key?: string;
|
||||
index: number;
|
||||
}
|
||||
|
||||
export const StashBoxConfiguration: React.FC<IStashBoxConfigurationProps> = ({
|
||||
boxes,
|
||||
saveBoxes,
|
||||
}) => {
|
||||
const [index, setIndex] = useState(1000);
|
||||
|
||||
const handleSave = (instance: IStashBoxInstance) =>
|
||||
saveBoxes(
|
||||
boxes.map((box) => (box.index === instance.index ? instance : box))
|
||||
);
|
||||
const handleDelete = (id: number) =>
|
||||
saveBoxes(boxes.filter((box) => box.index !== id));
|
||||
const handleAdd = () => {
|
||||
saveBoxes([...boxes, { index }]);
|
||||
setIndex(index + 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Group>
|
||||
<h6>Stash-box Endpoints</h6>
|
||||
{boxes.length > 0 && (
|
||||
<div className="row no-gutters">
|
||||
<h6 className="col-3 ml-1">Name</h6>
|
||||
<h6 className="col-3 ml-1">Endpoint</h6>
|
||||
<h6 className="col-3 ml-1">API Key</h6>
|
||||
</div>
|
||||
)}
|
||||
{boxes.map((instance) => (
|
||||
<Instance
|
||||
instance={instance}
|
||||
onSave={handleSave}
|
||||
onDelete={handleDelete}
|
||||
key={instance.index}
|
||||
isMulti={boxes.length > 1}
|
||||
/>
|
||||
))}
|
||||
<Button
|
||||
className="minimal"
|
||||
title="Add stash-box instance"
|
||||
onClick={handleAdd}
|
||||
>
|
||||
<Icon icon="plus" />
|
||||
</Button>
|
||||
<Form.Text className="text-muted">
|
||||
Stash-box facilitates automated tagging of scenes and performers based
|
||||
on fingerprints and filenames.
|
||||
<br />
|
||||
Endpoint and API key can be found on your account page on the stash-box
|
||||
instance. Names are required when more than one instance is added.
|
||||
</Form.Text>
|
||||
</Form.Group>
|
||||
);
|
||||
};
|
||||
|
||||
export default StashBoxConfiguration;
|
||||
Reference in New Issue
Block a user