Config for stash-box instances (#748)

This commit is contained in:
InfiniteTF
2020-09-14 09:13:35 +02:00
committed by GitHub
parent b527a8d137
commit 03f5e1a442
8 changed files with 215 additions and 0 deletions

View File

@@ -22,6 +22,11 @@ fragment ConfigGeneralData on ConfigGeneralResult {
excludes
scraperUserAgent
scraperCDPPath
stashBoxes {
name
endpoint
api_key
}
}
fragment ConfigInterfaceData on ConfigInterfaceResult {

View File

@@ -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 {

View File

@@ -0,0 +1,11 @@
type StashBox {
endpoint: String!
api_key: String!
name: String!
}
input StashBoxInput {
endpoint: String!
api_key: String!
name: String!
}

View File

@@ -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
}

View File

@@ -66,6 +66,7 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult {
Excludes: config.GetExcludes(),
ScraperUserAgent: &scraperUserAgent,
ScraperCDPPath: &scraperCDPPath,
StashBoxes: config.GetStashBoxes(),
}
}

View File

@@ -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 {

View File

@@ -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>

View 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;