This commit is contained in:
Gykes 2025-12-04 03:04:27 +02:00 committed by GitHub
commit c602de5b78
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 66 additions and 11 deletions

View file

@ -394,6 +394,9 @@ input ConfigInterfaceInput {
customLocales: String
customLocalesEnabled: Boolean
"When true, disables all customizations (plugins, CSS, JavaScript, locales) for troubleshooting"
disableCustomizations: Boolean
"Interface language"
language: String
@ -467,6 +470,9 @@ type ConfigInterfaceResult {
customLocales: String
customLocalesEnabled: Boolean
"When true, disables all customizations (plugins, CSS, JavaScript, locales) for troubleshooting"
disableCustomizations: Boolean
"Interface language"
language: String

View file

@ -515,6 +515,8 @@ func (r *mutationResolver) ConfigureInterface(ctx context.Context, input ConfigI
r.setConfigBool(config.CustomLocalesEnabled, input.CustomLocalesEnabled)
r.setConfigBool(config.DisableCustomizations, input.DisableCustomizations)
if input.DisableDropdownCreate != nil {
ddc := input.DisableDropdownCreate
r.setConfigBool(config.DisableDropdownCreatePerformer, ddc.Performer)

View file

@ -156,6 +156,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
javascriptEnabled := config.GetJavascriptEnabled()
customLocales := config.GetCustomLocales()
customLocalesEnabled := config.GetCustomLocalesEnabled()
disableCustomizations := config.GetDisableCustomizations()
language := config.GetLanguage()
handyKey := config.GetHandyKey()
scriptOffset := config.GetFunscriptOffset()
@ -183,6 +184,7 @@ func makeConfigInterfaceResult() *ConfigInterfaceResult {
JavascriptEnabled: &javascriptEnabled,
CustomLocales: &customLocales,
CustomLocalesEnabled: &customLocalesEnabled,
DisableCustomizations: &disableCustomizations,
Language: &language,
ImageLightbox: &imageLightboxOptions,

View file

@ -421,7 +421,7 @@ func cssHandler(c *config.Config) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
var paths []string
if c.GetCSSEnabled() {
if c.GetCSSEnabled() && !c.GetDisableCustomizations() {
// search for custom.css in current directory, then $HOME/.stash
fn := c.GetCSSPath()
exists, _ := fsutil.FileExists(fn)
@ -439,7 +439,7 @@ func javascriptHandler(c *config.Config) func(w http.ResponseWriter, r *http.Req
return func(w http.ResponseWriter, r *http.Request) {
var paths []string
if c.GetJavascriptEnabled() {
if c.GetJavascriptEnabled() && !c.GetDisableCustomizations() {
// search for custom.js in current directory, then $HOME/.stash
fn := c.GetJavascriptPath()
exists, _ := fsutil.FileExists(fn)
@ -457,7 +457,7 @@ func customLocalesHandler(c *config.Config) func(w http.ResponseWriter, r *http.
return func(w http.ResponseWriter, r *http.Request) {
buffer := bytes.Buffer{}
if c.GetCustomLocalesEnabled() {
if c.GetCustomLocalesEnabled() && !c.GetDisableCustomizations() {
// search for custom-locales.json in current directory, then $HOME/.stash
path := c.GetCustomLocalesPath()
exists, _ := fsutil.FileExists(path)

View file

@ -194,6 +194,7 @@ const (
CSSEnabled = "cssenabled"
JavascriptEnabled = "javascriptenabled"
CustomLocalesEnabled = "customlocalesenabled"
DisableCustomizations = "disable_customizations"
ShowScrubber = "show_scrubber"
showScrubberDefault = true
@ -1457,6 +1458,13 @@ func (i *Config) GetCustomLocalesEnabled() bool {
return i.getBool(CustomLocalesEnabled)
}
// GetDisableCustomizations returns true if all customizations (plugins, custom CSS,
// custom JavaScript, and custom locales) should be disabled. This is useful for
// troubleshooting issues without permanently disabling individual customizations.
func (i *Config) GetDisableCustomizations() bool {
return i.getBool(DisableCustomizations)
}
func (i *Config) GetHandyKey() string {
return i.getString(HandyKey)
}

View file

@ -92,6 +92,7 @@ fragment ConfigInterfaceData on ConfigInterfaceResult {
javascriptEnabled
customLocales
customLocalesEnabled
disableCustomizations
language
imageLightbox {
slideshowDelay

View file

@ -351,7 +351,12 @@ export const App: React.FC = () => {
formats={intlFormats}
>
<ToastProvider>
<PluginsLoader>
<PluginsLoader
disableCustomizations={
config.data?.configuration?.interface?.disableCustomizations ??
false
}
>
<AppContainer>
<ConfigurationProvider configuration={config.data!.configuration}>
{maybeRenderReleaseNotes()}

View file

@ -300,6 +300,16 @@ export const SettingsInterfacePanel: React.FC = PatchComponent(
/>
</SettingSection>
<SettingSection headingID="config.ui.troubleshooting.heading">
<BooleanSetting
id="disable-customizations"
headingID="config.ui.troubleshooting.disable_customizations.heading"
subHeadingID="config.ui.troubleshooting.disable_customizations.description"
checked={iface.disableCustomizations ?? undefined}
onChange={(v) => saveInterface({ disableCustomizations: v })}
/>
</SettingSection>
<SettingSection headingID="config.ui.scene_wall.heading">
<BooleanSetting
id="wall-show-title"

View file

@ -611,6 +611,13 @@
"heading": "Custom CSS",
"option_label": "Custom CSS enabled"
},
"troubleshooting": {
"heading": "Troubleshooting",
"disable_customizations": {
"heading": "Disable customizations",
"description": "Temporarily disables all plugins, custom CSS, custom JavaScript, and custom locales. Use this to troubleshoot issues without permanently disabling individual customizations. A page reload is required for changes to take effect."
}
},
"custom_javascript": {
"description": "Page must be reloaded for changes to take effect. There is no guarantee of compatibility between custom Javascript and future releases of Stash.",
"heading": "Custom Javascript",

View file

@ -59,7 +59,8 @@ function sortPlugins(plugins: PluginList) {
// load all plugins and their dependencies
// returns true when all plugins are loaded, regardess of success or failure
function useLoadPlugins() {
// if disableCustomizations is true, skip loading plugins entirely
function useLoadPlugins(disableCustomizations?: boolean) {
const {
data: plugins,
loading: pluginsLoading,
@ -74,6 +75,12 @@ function useLoadPlugins() {
}, [plugins?.plugins, pluginsLoading, pluginsError]);
const pluginJavascripts = useMemoOnce(() => {
// Skip loading plugin JS if customizations are disabled.
// Note: We check inside useMemoOnce rather than early-returning from useLoadPlugins
// to comply with React's rules of hooks - hooks must be called unconditionally.
if (disableCustomizations) {
return [[], true];
}
return [
uniq(
sortedPlugins
@ -83,9 +90,12 @@ function useLoadPlugins() {
),
!!sortedPlugins && !pluginsLoading && !pluginsError,
];
}, [sortedPlugins, pluginsLoading, pluginsError]);
}, [sortedPlugins, pluginsLoading, pluginsError, disableCustomizations]);
const pluginCSS = useMemoOnce(() => {
if (disableCustomizations) {
return [[], true];
}
return [
uniq(
sortedPlugins
@ -95,7 +105,7 @@ function useLoadPlugins() {
),
!!sortedPlugins && !pluginsLoading && !pluginsError,
];
}, [sortedPlugins, pluginsLoading, pluginsError]);
}, [sortedPlugins, pluginsLoading, pluginsError, disableCustomizations]);
const pluginJavascriptLoaded = useScript(
pluginJavascripts ?? [],
@ -109,11 +119,15 @@ function useLoadPlugins() {
};
}
export const PluginsLoader: React.FC<React.PropsWithChildren<{}>> = ({
children,
}) => {
interface IPluginsLoaderProps {
disableCustomizations?: boolean;
}
export const PluginsLoader: React.FC<
React.PropsWithChildren<IPluginsLoaderProps>
> = ({ disableCustomizations, children }) => {
const Toast = useToast();
const { loading: loaded, error } = useLoadPlugins();
const { loading: loaded, error } = useLoadPlugins(disableCustomizations);
useEffect(() => {
if (error) {