mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 08:26:00 +01:00
Add support for disabling plugins (#4141)
* Move timestamp to own file * Backend changes * UI changes
This commit is contained in:
parent
e5af37efbc
commit
b6808dc714
18 changed files with 260 additions and 62 deletions
|
|
@ -33,6 +33,8 @@ models:
|
||||||
model: github.com/99designs/gqlgen/graphql.Int64
|
model: github.com/99designs/gqlgen/graphql.Int64
|
||||||
Timestamp:
|
Timestamp:
|
||||||
model: github.com/stashapp/stash/internal/api.Timestamp
|
model: github.com/stashapp/stash/internal/api.Timestamp
|
||||||
|
BoolMap:
|
||||||
|
model: github.com/stashapp/stash/internal/api.BoolMap
|
||||||
# define to force resolvers
|
# define to force resolvers
|
||||||
Image:
|
Image:
|
||||||
model: github.com/stashapp/stash/pkg/models.Image
|
model: github.com/stashapp/stash/pkg/models.Image
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,7 @@ mutation RunPluginTask(
|
||||||
) {
|
) {
|
||||||
runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)
|
runPluginTask(plugin_id: $plugin_id, task_name: $task_name, args: $args)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutation SetPluginsEnabled($enabledMap: BoolMap!) {
|
||||||
|
setPluginsEnabled(enabledMap: $enabledMap)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ query Plugins {
|
||||||
plugins {
|
plugins {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
enabled
|
||||||
description
|
description
|
||||||
url
|
url
|
||||||
version
|
version
|
||||||
|
|
@ -26,6 +27,7 @@ query PluginTasks {
|
||||||
plugin {
|
plugin {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
enabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,12 @@ type Mutation {
|
||||||
"Reload scrapers"
|
"Reload scrapers"
|
||||||
reloadScrapers: Boolean!
|
reloadScrapers: Boolean!
|
||||||
|
|
||||||
|
"""
|
||||||
|
Enable/disable plugins - enabledMap is a map of plugin IDs to enabled booleans.
|
||||||
|
Plugins not in the map are not affected.
|
||||||
|
"""
|
||||||
|
setPluginsEnabled(enabledMap: BoolMap!): Boolean!
|
||||||
|
|
||||||
"Run plugin task. Returns the job ID"
|
"Run plugin task. Returns the job ID"
|
||||||
runPluginTask(
|
runPluginTask(
|
||||||
plugin_id: ID!
|
plugin_id: ID!
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ type Plugin {
|
||||||
url: String
|
url: String
|
||||||
version: String
|
version: String
|
||||||
|
|
||||||
|
enabled: Boolean!
|
||||||
|
|
||||||
tasks: [PluginTask!]
|
tasks: [PluginTask!]
|
||||||
hooks: [PluginHook!]
|
hooks: [PluginHook!]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ scalar Timestamp
|
||||||
# generic JSON object
|
# generic JSON object
|
||||||
scalar Map
|
scalar Map
|
||||||
|
|
||||||
|
# string, boolean map
|
||||||
|
scalar BoolMap
|
||||||
|
|
||||||
scalar Any
|
scalar Any
|
||||||
|
|
||||||
scalar Int64
|
scalar Int64
|
||||||
|
|
|
||||||
38
internal/api/bool_map.go
Normal file
38
internal/api/bool_map.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
func MarshalBoolMap(val map[string]bool) graphql.Marshaler {
|
||||||
|
return graphql.WriterFunc(func(w io.Writer) {
|
||||||
|
err := json.NewEncoder(w).Encode(val)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalBoolMap(v interface{}) (map[string]bool, error) {
|
||||||
|
m, ok := v.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%T is not a map", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]bool)
|
||||||
|
for k, v := range m {
|
||||||
|
key := k
|
||||||
|
val, ok := v.(bool)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("key %s (%T) is not a bool", k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
result[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
@ -1,16 +1,7 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/99designs/gqlgen/graphql"
|
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
|
||||||
"github.com/stashapp/stash/pkg/models"
|
"github.com/stashapp/stash/pkg/models"
|
||||||
"github.com/stashapp/stash/pkg/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type BaseFile interface{}
|
type BaseFile interface{}
|
||||||
|
|
@ -18,47 +9,3 @@ type BaseFile interface{}
|
||||||
type GalleryFile struct {
|
type GalleryFile struct {
|
||||||
*models.BaseFile
|
*models.BaseFile
|
||||||
}
|
}
|
||||||
|
|
||||||
var ErrTimestamp = errors.New("cannot parse Timestamp")
|
|
||||||
|
|
||||||
func MarshalTimestamp(t time.Time) graphql.Marshaler {
|
|
||||||
if t.IsZero() {
|
|
||||||
return graphql.Null
|
|
||||||
}
|
|
||||||
|
|
||||||
return graphql.WriterFunc(func(w io.Writer) {
|
|
||||||
_, err := io.WriteString(w, strconv.Quote(t.Format(time.RFC3339Nano)))
|
|
||||||
if err != nil {
|
|
||||||
logger.Warnf("could not marshal timestamp: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func UnmarshalTimestamp(v interface{}) (time.Time, error) {
|
|
||||||
if tmpStr, ok := v.(string); ok {
|
|
||||||
if len(tmpStr) == 0 {
|
|
||||||
return time.Time{}, fmt.Errorf("%w: empty string", ErrTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch tmpStr[0] {
|
|
||||||
case '>', '<':
|
|
||||||
d, err := time.ParseDuration(tmpStr[1:])
|
|
||||||
if err != nil {
|
|
||||||
return time.Time{}, fmt.Errorf("%w: cannot parse %v-duration: %v", ErrTimestamp, tmpStr[0], err)
|
|
||||||
}
|
|
||||||
t := time.Now()
|
|
||||||
// Compute point in time:
|
|
||||||
if tmpStr[0] == '<' {
|
|
||||||
t = t.Add(-d)
|
|
||||||
} else {
|
|
||||||
t = t.Add(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils.ParseDateStringAsTime(tmpStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Time{}, fmt.Errorf("%w: not a string", ErrTimestamp)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/stashapp/stash/internal/manager"
|
"github.com/stashapp/stash/internal/manager"
|
||||||
|
"github.com/stashapp/stash/internal/manager/config"
|
||||||
"github.com/stashapp/stash/pkg/logger"
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
"github.com/stashapp/stash/pkg/plugin"
|
"github.com/stashapp/stash/pkg/plugin"
|
||||||
|
"github.com/stashapp/stash/pkg/sliceutil/stringslice"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *mutationResolver) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*plugin.PluginArgInput) (string, error) {
|
func (r *mutationResolver) RunPluginTask(ctx context.Context, pluginID string, taskName string, args []*plugin.PluginArgInput) (string, error) {
|
||||||
|
|
@ -22,3 +24,32 @@ func (r *mutationResolver) ReloadPlugins(ctx context.Context) (bool, error) {
|
||||||
|
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) SetPluginsEnabled(ctx context.Context, enabledMap map[string]bool) (bool, error) {
|
||||||
|
c := config.GetInstance()
|
||||||
|
|
||||||
|
existingDisabled := c.GetDisabledPlugins()
|
||||||
|
var newDisabled []string
|
||||||
|
|
||||||
|
// remove plugins that are no longer disabled
|
||||||
|
for _, disabledID := range existingDisabled {
|
||||||
|
if enabled, found := enabledMap[disabledID]; !enabled || !found {
|
||||||
|
newDisabled = append(newDisabled, disabledID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add plugins that are newly disabled
|
||||||
|
for pluginID, enabled := range enabledMap {
|
||||||
|
if !enabled {
|
||||||
|
newDisabled = stringslice.StrAppendUnique(newDisabled, pluginID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Set(config.DisabledPlugins, newDisabled)
|
||||||
|
|
||||||
|
if err := c.Write(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -295,6 +295,10 @@ func cssHandler(c *config.Instance, pluginCache *plugin.Cache) func(w http.Respo
|
||||||
var paths []string
|
var paths []string
|
||||||
|
|
||||||
for _, p := range pluginCache.ListPlugins() {
|
for _, p := range pluginCache.ListPlugins() {
|
||||||
|
if !p.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
paths = append(paths, p.UI.CSS...)
|
paths = append(paths, p.UI.CSS...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -318,6 +322,10 @@ func javascriptHandler(c *config.Instance, pluginCache *plugin.Cache) func(w htt
|
||||||
var paths []string
|
var paths []string
|
||||||
|
|
||||||
for _, p := range pluginCache.ListPlugins() {
|
for _, p := range pluginCache.ListPlugins() {
|
||||||
|
if !p.Enabled {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
paths = append(paths, p.UI.Javascript...)
|
paths = append(paths, p.UI.Javascript...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
57
internal/api/timestamp.go
Normal file
57
internal/api/timestamp.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
|
"github.com/stashapp/stash/pkg/logger"
|
||||||
|
"github.com/stashapp/stash/pkg/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrTimestamp = errors.New("cannot parse Timestamp")
|
||||||
|
|
||||||
|
func MarshalTimestamp(t time.Time) graphql.Marshaler {
|
||||||
|
if t.IsZero() {
|
||||||
|
return graphql.Null
|
||||||
|
}
|
||||||
|
|
||||||
|
return graphql.WriterFunc(func(w io.Writer) {
|
||||||
|
_, err := io.WriteString(w, strconv.Quote(t.Format(time.RFC3339Nano)))
|
||||||
|
if err != nil {
|
||||||
|
logger.Warnf("could not marshal timestamp: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalTimestamp(v interface{}) (time.Time, error) {
|
||||||
|
if tmpStr, ok := v.(string); ok {
|
||||||
|
if len(tmpStr) == 0 {
|
||||||
|
return time.Time{}, fmt.Errorf("%w: empty string", ErrTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tmpStr[0] {
|
||||||
|
case '>', '<':
|
||||||
|
d, err := time.ParseDuration(tmpStr[1:])
|
||||||
|
if err != nil {
|
||||||
|
return time.Time{}, fmt.Errorf("%w: cannot parse %v-duration: %v", ErrTimestamp, tmpStr[0], err)
|
||||||
|
}
|
||||||
|
t := time.Now()
|
||||||
|
// Compute point in time:
|
||||||
|
if tmpStr[0] == '<' {
|
||||||
|
t = t.Add(-d)
|
||||||
|
} else {
|
||||||
|
t = t.Add(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.ParseDateStringAsTime(tmpStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{}, fmt.Errorf("%w: not a string", ErrTimestamp)
|
||||||
|
}
|
||||||
|
|
@ -131,7 +131,8 @@ const (
|
||||||
PythonPath = "python_path"
|
PythonPath = "python_path"
|
||||||
|
|
||||||
// plugin options
|
// plugin options
|
||||||
PluginsPath = "plugins_path"
|
PluginsPath = "plugins_path"
|
||||||
|
DisabledPlugins = "plugins.disabled"
|
||||||
|
|
||||||
// i18n
|
// i18n
|
||||||
Language = "language"
|
Language = "language"
|
||||||
|
|
@ -722,6 +723,10 @@ func (i *Instance) GetPluginsPath() string {
|
||||||
return i.getString(PluginsPath)
|
return i.getString(PluginsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetDisabledPlugins() []string {
|
||||||
|
return i.getStringSlice(DisabledPlugins)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Instance) GetPythonPath() string {
|
func (i *Instance) GetPythonPath() string {
|
||||||
return i.getString(PythonPath)
|
return i.getString(PythonPath)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ type Plugin struct {
|
||||||
Tasks []*PluginTask `json:"tasks"`
|
Tasks []*PluginTask `json:"tasks"`
|
||||||
Hooks []*PluginHook `json:"hooks"`
|
Hooks []*PluginHook `json:"hooks"`
|
||||||
UI PluginUI `json:"ui"`
|
UI PluginUI `json:"ui"`
|
||||||
|
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PluginUI struct {
|
type PluginUI struct {
|
||||||
|
|
@ -48,6 +50,7 @@ type ServerConfig interface {
|
||||||
GetConfigPath() string
|
GetConfigPath() string
|
||||||
HasTLSConfig() bool
|
HasTLSConfig() bool
|
||||||
GetPluginsPath() string
|
GetPluginsPath() string
|
||||||
|
GetDisabledPlugins() []string
|
||||||
GetPythonPath() string
|
GetPythonPath() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,11 +125,39 @@ func loadPlugins(path string) ([]Config, error) {
|
||||||
return plugins, nil
|
return plugins, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c Cache) enabledPlugins() []Config {
|
||||||
|
disabledPlugins := c.config.GetDisabledPlugins()
|
||||||
|
|
||||||
|
var ret []Config
|
||||||
|
for _, p := range c.plugins {
|
||||||
|
disabled := stringslice.StrInclude(disabledPlugins, p.id)
|
||||||
|
|
||||||
|
if !disabled {
|
||||||
|
ret = append(ret, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Cache) pluginDisabled(id string) bool {
|
||||||
|
disabledPlugins := c.config.GetDisabledPlugins()
|
||||||
|
|
||||||
|
return stringslice.StrInclude(disabledPlugins, id)
|
||||||
|
}
|
||||||
|
|
||||||
// ListPlugins returns plugin details for all of the loaded plugins.
|
// ListPlugins returns plugin details for all of the loaded plugins.
|
||||||
func (c Cache) ListPlugins() []*Plugin {
|
func (c Cache) ListPlugins() []*Plugin {
|
||||||
|
disabledPlugins := c.config.GetDisabledPlugins()
|
||||||
|
|
||||||
var ret []*Plugin
|
var ret []*Plugin
|
||||||
for _, s := range c.plugins {
|
for _, s := range c.plugins {
|
||||||
ret = append(ret, s.toPlugin())
|
p := s.toPlugin()
|
||||||
|
|
||||||
|
disabled := stringslice.StrInclude(disabledPlugins, p.ID)
|
||||||
|
p.Enabled = !disabled
|
||||||
|
|
||||||
|
ret = append(ret, p)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
@ -135,7 +166,7 @@ func (c Cache) ListPlugins() []*Plugin {
|
||||||
// ListPluginTasks returns all runnable plugin tasks in all loaded plugins.
|
// ListPluginTasks returns all runnable plugin tasks in all loaded plugins.
|
||||||
func (c Cache) ListPluginTasks() []*PluginTask {
|
func (c Cache) ListPluginTasks() []*PluginTask {
|
||||||
var ret []*PluginTask
|
var ret []*PluginTask
|
||||||
for _, s := range c.plugins {
|
for _, s := range c.enabledPlugins() {
|
||||||
ret = append(ret, s.getPluginTasks(true)...)
|
ret = append(ret, s.getPluginTasks(true)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,6 +206,10 @@ func (c Cache) makeServerConnection(ctx context.Context) common.StashServerConne
|
||||||
func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName string, args []*PluginArgInput, progress chan float64) (Task, error) {
|
func (c Cache) CreateTask(ctx context.Context, pluginID string, operationName string, args []*PluginArgInput, progress chan float64) (Task, error) {
|
||||||
serverConnection := c.makeServerConnection(ctx)
|
serverConnection := c.makeServerConnection(ctx)
|
||||||
|
|
||||||
|
if c.pluginDisabled(pluginID) {
|
||||||
|
return nil, fmt.Errorf("plugin %s is disabled", pluginID)
|
||||||
|
}
|
||||||
|
|
||||||
// find the plugin and operation
|
// find the plugin and operation
|
||||||
plugin := c.getPlugin(pluginID)
|
plugin := c.getPlugin(pluginID)
|
||||||
|
|
||||||
|
|
@ -227,7 +262,7 @@ func (c Cache) ExecuteSceneUpdatePostHooks(ctx context.Context, input models.Sce
|
||||||
func (c Cache) executePostHooks(ctx context.Context, hookType HookTriggerEnum, hookContext common.HookContext) error {
|
func (c Cache) executePostHooks(ctx context.Context, hookType HookTriggerEnum, hookContext common.HookContext) error {
|
||||||
visitedPlugins := session.GetVisitedPlugins(ctx)
|
visitedPlugins := session.GetVisitedPlugins(ctx)
|
||||||
|
|
||||||
for _, p := range c.plugins {
|
for _, p := range c.enabledPlugins() {
|
||||||
hooks := p.getHooks(hookType)
|
hooks := p.getHooks(hookType)
|
||||||
// don't revisit a plugin we've already visited
|
// don't revisit a plugin we've already visited
|
||||||
// only log if there's hooks that we're skipping
|
// only log if there's hooks that we're skipping
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ import React, { useMemo } from "react";
|
||||||
import { Button } from "react-bootstrap";
|
import { Button } from "react-bootstrap";
|
||||||
import { FormattedMessage, useIntl } from "react-intl";
|
import { FormattedMessage, useIntl } from "react-intl";
|
||||||
import * as GQL from "src/core/generated-graphql";
|
import * as GQL from "src/core/generated-graphql";
|
||||||
import { mutateReloadPlugins, usePlugins } from "src/core/StashService";
|
import {
|
||||||
|
mutateReloadPlugins,
|
||||||
|
mutateSetPluginsEnabled,
|
||||||
|
usePlugins,
|
||||||
|
} from "src/core/StashService";
|
||||||
import { useToast } from "src/hooks/Toast";
|
import { useToast } from "src/hooks/Toast";
|
||||||
import TextUtils from "src/utils/text";
|
import TextUtils from "src/utils/text";
|
||||||
import { CollapseButton } from "../Shared/CollapseButton";
|
import { CollapseButton } from "../Shared/CollapseButton";
|
||||||
|
|
@ -16,7 +20,11 @@ export const SettingsPluginsPanel: React.FC = () => {
|
||||||
const Toast = useToast();
|
const Toast = useToast();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { data, loading } = usePlugins();
|
const [changedPluginID, setChangedPluginID] = React.useState<
|
||||||
|
string | undefined
|
||||||
|
>();
|
||||||
|
|
||||||
|
const { data, loading, refetch } = usePlugins();
|
||||||
|
|
||||||
async function onReloadPlugins() {
|
async function onReloadPlugins() {
|
||||||
await mutateReloadPlugins().catch((e) => Toast.error(e));
|
await mutateReloadPlugins().catch((e) => Toast.error(e));
|
||||||
|
|
@ -40,6 +48,39 @@ export const SettingsPluginsPanel: React.FC = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderEnableButton(pluginID: string, enabled: boolean) {
|
||||||
|
async function onClick() {
|
||||||
|
await mutateSetPluginsEnabled({ [pluginID]: !enabled }).catch((e) =>
|
||||||
|
Toast.error(e)
|
||||||
|
);
|
||||||
|
|
||||||
|
setChangedPluginID(pluginID);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button size="sm" onClick={onClick}>
|
||||||
|
<FormattedMessage
|
||||||
|
id={enabled ? "actions.disable" : "actions.enable"}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onReloadUI() {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function maybeRenderReloadUI(pluginID: string) {
|
||||||
|
if (pluginID === changedPluginID) {
|
||||||
|
return (
|
||||||
|
<Button size="sm" onClick={() => onReloadUI()}>
|
||||||
|
Reload UI
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function renderPlugins() {
|
function renderPlugins() {
|
||||||
const elements = (data?.plugins ?? []).map((plugin) => (
|
const elements = (data?.plugins ?? []).map((plugin) => (
|
||||||
<SettingGroup
|
<SettingGroup
|
||||||
|
|
@ -48,9 +89,16 @@ export const SettingsPluginsPanel: React.FC = () => {
|
||||||
heading: `${plugin.name} ${
|
heading: `${plugin.name} ${
|
||||||
plugin.version ? `(${plugin.version})` : undefined
|
plugin.version ? `(${plugin.version})` : undefined
|
||||||
}`,
|
}`,
|
||||||
|
className: !plugin.enabled ? "disabled" : undefined,
|
||||||
subHeading: plugin.description,
|
subHeading: plugin.description,
|
||||||
}}
|
}}
|
||||||
topLevel={renderLink(plugin.url ?? undefined)}
|
topLevel={
|
||||||
|
<>
|
||||||
|
{renderLink(plugin.url ?? undefined)}
|
||||||
|
{maybeRenderReloadUI(plugin.id)}
|
||||||
|
{renderEnableButton(plugin.id, plugin.enabled)}
|
||||||
|
</>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{renderPluginHooks(plugin.hooks ?? undefined)}
|
{renderPluginHooks(plugin.hooks ?? undefined)}
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|
@ -98,7 +146,7 @@ export const SettingsPluginsPanel: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return renderPlugins();
|
return renderPlugins();
|
||||||
}, [data?.plugins, intl]);
|
}, [data?.plugins, intl, Toast, changedPluginID, refetch]);
|
||||||
|
|
||||||
if (loading) return <LoadingIndicator />;
|
if (loading) return <LoadingIndicator />;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export const PluginTasks: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const taskPlugins = plugins.data.plugins.filter(
|
const taskPlugins = plugins.data.plugins.filter(
|
||||||
(p) => p.tasks && p.tasks.length > 0
|
(p) => p.enabled && p.tasks && p.tasks.length > 0
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2088,6 +2088,14 @@ export const mutateMigrate = (input: GQL.MigrateInput) =>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type BoolMap = { [key: string]: boolean };
|
||||||
|
|
||||||
|
export const mutateSetPluginsEnabled = (enabledMap: BoolMap) =>
|
||||||
|
client.mutate<GQL.SetPluginsEnabledMutation>({
|
||||||
|
mutation: GQL.SetPluginsEnabledDocument,
|
||||||
|
variables: { enabledMap },
|
||||||
|
});
|
||||||
|
|
||||||
/// Tasks
|
/// Tasks
|
||||||
|
|
||||||
export const mutateMetadataScan = (input: GQL.ScanMetadataInput) =>
|
export const mutateMetadataScan = (input: GQL.ScanMetadataInput) =>
|
||||||
|
|
|
||||||
|
|
@ -34,12 +34,14 @@
|
||||||
"delete_file_and_funscript": "Delete file (and funscript)",
|
"delete_file_and_funscript": "Delete file (and funscript)",
|
||||||
"delete_generated_supporting_files": "Delete generated supporting files",
|
"delete_generated_supporting_files": "Delete generated supporting files",
|
||||||
"delete_stashid": "Delete StashID",
|
"delete_stashid": "Delete StashID",
|
||||||
|
"disable": "Disable",
|
||||||
"disallow": "Disallow",
|
"disallow": "Disallow",
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"download_anonymised": "Download anonymised",
|
"download_anonymised": "Download anonymised",
|
||||||
"download_backup": "Download Backup",
|
"download_backup": "Download Backup",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"edit_entity": "Edit {entityType}",
|
"edit_entity": "Edit {entityType}",
|
||||||
|
"enable": "Enable",
|
||||||
"encoding_image": "Encoding image",
|
"encoding_image": "Encoding image",
|
||||||
"export": "Export",
|
"export": "Export",
|
||||||
"export_all": "Export all…",
|
"export_all": "Export all…",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue