Verify plugin config before starting plugin

Fix issue with SLF4J in plugins
This commit is contained in:
grimsi
2024-12-20 00:48:57 +01:00
parent c365574a92
commit 7b12ce1029
7 changed files with 77 additions and 18 deletions
@@ -39,6 +39,7 @@ export default function PluginManagement() {
<div className="flex flex-row flex-grow justify-between my-8">
<h2 className="text-xl font-bold">Notifications</h2>
</div>
<p>Notification plugins not yet supported.</p>
</div>
);
}
@@ -2,7 +2,7 @@ import React, {useEffect, useState} from "react";
import {Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader} from "@nextui-org/react";
import {toast} from "sonner";
import {Form, Formik} from "formik";
import {PluginConfigEndpoint} from "Frontend/generated/endpoints";
import {PluginConfigEndpoint, PluginManagementEndpoint} from "Frontend/generated/endpoints";
import PluginDto from "Frontend/generated/de/grimsi/gameyfin/core/plugins/management/PluginDto";
import PluginConfigElement from "Frontend/generated/de/grimsi/gameyfin/pluginapi/core/PluginConfigElement";
import Input from "Frontend/components/general/Input";
@@ -12,9 +12,10 @@ interface PluginDetailsModalProps {
plugin: PluginDto;
isOpen: boolean;
onOpenChange: () => void;
updatePlugin: (plugin: PluginDto) => void;
}
export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: PluginDetailsModalProps) {
export default function PluginDetailsModal({plugin, isOpen, onOpenChange, updatePlugin}: PluginDetailsModalProps) {
const [pluginConfigMeta, setPluginConfigMeta] = useState<(PluginConfigElement)[]>();
const [pluginConfig, setPluginConfig] = useState<Record<string, string>>();
@@ -32,6 +33,9 @@ export default function PluginDetailsModal({plugin, isOpen, onOpenChange}: Plugi
async function saveConfig(values: Record<string, string>) {
await PluginConfigEndpoint.setConfigEntries(plugin.id, values);
toast.success(`Configuration for ${plugin.name} saved!`);
let updatedPlugin = await PluginManagementEndpoint.getPlugin(plugin.id);
if (updatedPlugin === undefined) return;
updatePlugin(updatedPlugin);
}
return (
@@ -18,11 +18,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
if (response === undefined) return;
setConfigValid(response);
});
}, []);
function iconColor(state: PluginState | undefined): "white" | "default" {
return "default";
}
}, [pluginDetailsModal.isOpen]);
function borderColor(state: PluginState | undefined): "success" | "warning" | "danger" | "default" {
if (isDisabled(state)) return "warning";
@@ -37,7 +33,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
return "success";
case PluginState.DISABLED:
return "warning";
case PluginState.STOPPED:
case PluginState.FAILED:
return "danger";
default:
return "default";
@@ -101,6 +97,7 @@ export function PluginManagementCard({plugin, updatePlugin}: {
<PluginDetailsModal plugin={plugin}
isOpen={pluginDetailsModal.isOpen}
onOpenChange={pluginDetailsModal.onOpenChange}
updatePlugin={updatePlugin}
/>
</>
@@ -10,8 +10,14 @@ class DatabasePluginStatusProvider(
) : PluginStatusProvider {
override fun isPluginDisabled(pluginId: String): Boolean {
val pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
return pluginManagement?.enabled != true
var pluginManagement = pluginManagementRepository.findByIdOrNull(pluginId)
// If the plugin is unknown, persist it as enabled
if(pluginManagement == null) {
pluginManagement = pluginManagementRepository.save(PluginManagementEntry(pluginId = pluginId, enabled = true))
}
return pluginManagement.enabled != true
}
override fun disablePlugin(pluginId: String) {
@@ -63,11 +63,62 @@ class GameyfinPluginManager(
return pluginWrapper
}
override fun startPlugin(pluginId: String?): PluginState? {
if(pluginId == null) return PluginState.FAILED
// Validate config before starting the plugin
if (!validatePluginConfig(pluginId)) {
log.error { "Plugin $pluginId has invalid configuration" }
val pluginWrapper = getPlugin(pluginId)
pluginWrapper.pluginState = PluginState.FAILED
this.firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginWrapper.pluginState));
return pluginWrapper.pluginState
}
return super.startPlugin(pluginId)
}
override fun startPlugins() {
for (pluginWrapper in resolvedPlugins) {
val pluginState = pluginWrapper.pluginState
if (!pluginState.isDisabled && !pluginState.isStarted) {
// Validate config before starting the plugin
if (!validatePluginConfig(pluginWrapper.pluginId)) {
log.error { "Plugin ${pluginWrapper.pluginId} has invalid configuration" }
pluginWrapper.pluginState = PluginState.FAILED
firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginState))
return
}
try {
log.info { "${"Start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)}"}
pluginWrapper.plugin.start()
pluginWrapper.pluginState = PluginState.STARTED
pluginWrapper.failedException = null
startedPlugins.add(pluginWrapper)
} catch (e: LinkageError) {
pluginWrapper.pluginState = PluginState.FAILED
pluginWrapper.failedException = e
log.error { "${"Unable to start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)} $e"}
} catch (e: Exception) {
pluginWrapper.pluginState = PluginState.FAILED
pluginWrapper.failedException = e
log.error { "${"Unable to start plugin '{}'"} ${getPluginLabel(pluginWrapper.descriptor)} $e"}
} finally {
firePluginStateEvent(PluginStateEvent(this, pluginWrapper, pluginState))
}
}
}
}
fun restart(pluginId: String) {
val plugin = getPlugin(pluginId)?.plugin ?: return
plugin.stop()
stopPlugin(pluginId)
(plugin as GameyfinPlugin).loadConfig(getConfig(pluginId))
plugin.start()
startPlugin(pluginId)
}
fun validatePluginConfig(pluginId: String): Boolean {
+1 -5
View File
@@ -10,11 +10,7 @@ repositories {
dependencies {
// PF4J (shared)
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}") {
exclude(group = "org.slf4j")
}
api("org.slf4j:slf4j-api:2.0.16")
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
implementation(kotlin("stdlib"))
@@ -16,6 +16,8 @@ import kotlinx.serialization.json.*
import me.xdrop.fuzzywuzzy.FuzzySearch
import org.pf4j.Extension
import org.pf4j.PluginWrapper
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.Instant
@@ -66,6 +68,8 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
@Extension
class SteamMetadataProvider : GameMetadataProvider {
val log: Logger = LoggerFactory.getLogger(javaClass)
val client = HttpClient(CIO) {
install(ContentNegotiation) {
json()
@@ -89,7 +93,7 @@ class SteamPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper) {
val searchResult: SteamSearchResult = client.get(url).body()
searchResult.items
} catch (e: Exception) {
println(e.message)
log.error("Failed to search Steam store: ${e.message}")
emptyList()
}
}