Add enable/disable button

This commit is contained in:
tutman96 2025-01-04 17:18:25 +00:00
parent 3853b58613
commit 88f3e97011
6 changed files with 204 additions and 15 deletions

View File

@ -1,5 +1,7 @@
package plugin
import "fmt"
type PluginInstall struct {
Enabled bool `json:"enabled"`
@ -29,3 +31,21 @@ func (p *PluginInstall) GetManifest() (*PluginManifest, error) {
func (p *PluginInstall) GetExtractedFolder() string {
return p.ExtractedVersions[p.Version]
}
func (p *PluginInstall) GetStatus() (*PluginStatus, error) {
manifest, err := p.GetManifest()
if err != nil {
return nil, fmt.Errorf("failed to get plugin manifest: %v", err)
}
status := "stopped"
if p.Enabled {
status = "running"
}
return &PluginStatus{
PluginManifest: *manifest,
Enabled: p.Enabled,
Status: status,
}, nil
}

View File

@ -135,25 +135,35 @@ func RpcPluginInstall(name string, version string) error {
func RpcPluginList() ([]PluginStatus, error) {
plugins := make([]PluginStatus, 0, len(pluginDatabase.Plugins))
for pluginName, plugin := range pluginDatabase.Plugins {
manifest, err := plugin.GetManifest()
status, err := plugin.GetStatus()
if err != nil {
return nil, fmt.Errorf("failed to get plugin manifest for %s: %v", pluginName, err)
return nil, fmt.Errorf("failed to get plugin status for %s: %v", pluginName, err)
}
status := "stopped"
if plugin.Enabled {
status = "running"
}
plugins = append(plugins, PluginStatus{
PluginManifest: *manifest,
Enabled: plugin.Enabled,
Status: status,
})
plugins = append(plugins, *status)
}
return plugins, nil
}
func RpcUpdateConfig(name string, enabled bool) (*PluginStatus, error) {
pluginInstall, ok := pluginDatabase.Plugins[name]
if !ok {
return nil, fmt.Errorf("plugin not found: %s", name)
}
pluginInstall.Enabled = enabled
pluginDatabase.Plugins[name] = pluginInstall
if err := pluginDatabase.Save(); err != nil {
return nil, fmt.Errorf("failed to save plugin database: %v", err)
}
status, err := pluginInstall.GetStatus()
if err != nil {
return nil, fmt.Errorf("failed to get plugin status for %s: %v", name, err)
}
return status, nil
}
func readManifest(extractFolder string) (*PluginManifest, error) {
manifestPath := path.Join(extractFolder, "manifest.json")
manifestFile, err := os.Open(manifestPath)

View File

@ -559,4 +559,5 @@ var rpcHandlers = map[string]RPCHandler{
"pluginExtract": {Func: plugin.RpcPluginExtract, Params: []string{"filename"}},
"pluginInstall": {Func: plugin.RpcPluginInstall, Params: []string{"name", "version"}},
"pluginList": {Func: plugin.RpcPluginList},
"pluginUpdateConfig": {Func: plugin.RpcUpdateConfig, Params: []string{"name", "enabled"}},
}

View File

@ -0,0 +1,127 @@
import { PluginStatus } from "@/hooks/stores";
import Modal from "@components/Modal";
import AutoHeight from "@components/AutoHeight";
import { GridCard } from "@components/Card";
import LogoBlueIcon from "@/assets/logo-blue.svg";
import LogoWhiteIcon from "@/assets/logo-white.svg";
import { ViewHeader } from "./MountMediaDialog";
import { Button } from "./Button";
import { useJsonRpc } from "@/hooks/useJsonRpc";
import { useCallback, useEffect, useState } from "react";
export default function PluginConfigureModal({
plugin,
open,
setOpen,
}: {
plugin: PluginStatus | null;
open: boolean;
setOpen: (open: boolean) => void;
}) {
return (
<Modal open={!!plugin && open} onClose={() => setOpen(false)}>
<Dialog plugin={plugin} setOpen={setOpen} />
</Modal>
)
}
function Dialog({ plugin, setOpen }: { plugin: PluginStatus | null, setOpen: (open: boolean) => void }) {
const [send] = useJsonRpc();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setLoading(false);
}, [plugin])
const updatePlugin = useCallback((enabled: boolean) => {
if (!plugin) return;
if (!enabled) {
if (!window.confirm("Are you sure you want to disable this plugin?")) {
return;
}
}
setLoading(true);
send("pluginUpdateConfig", { name: plugin.name, enabled }, resp => {
if ("error" in resp) {
setError(resp.error.message);
return
}
setOpen(false);
});
}, [send, plugin, setOpen])
return (
<AutoHeight>
<div className="mx-auto max-w-4xl px-4 transition-all duration-300 ease-in-out">
<GridCard cardClassName="relative w-full text-left pointer-events-auto">
<div className="p-4">
<div className="flex flex-col items-start justify-start space-y-4 text-left">
<img
src={LogoBlueIcon}
alt="JetKVM Logo"
className="h-[24px] dark:hidden block"
/>
<img
src={LogoWhiteIcon}
alt="JetKVM Logo"
className="h-[24px] dark:block hidden dark:!mt-0"
/>
<div className="w-full space-y-4">
<div className="flex items-center justify-between w-full">
<ViewHeader title="Plugin Configuration" description={`Configure the ${plugin?.name} plugin`} />
<div>
{/* Enable/Disable toggle */}
<Button
size="MD"
theme={plugin?.enabled ? "danger" : "light"}
text={plugin?.enabled ? "Disable Plugin" : "Enable Plugin"}
loading={loading}
onClick={() => {
updatePlugin(!plugin?.enabled);
}}
/>
</div>
</div>
<div
className="space-y-2 opacity-0 animate-fadeIn"
style={{
animationDuration: "0.7s",
}}
>
{error && <p className="text-red-500 dark:text-red-400">{error}</p>}
<p className="text-sm text-gray-500 dark:text-gray-400 py-10">
TODO: Plugin configuration goes here
</p>
<div
className="flex items-end w-full opacity-0 animate-fadeIn"
style={{
animationDuration: "0.7s",
animationDelay: "0.1s",
}}
>
<div className="flex justify-end w-full space-x-2">
<Button
size="MD"
theme="light"
text="Back"
disabled={loading}
onClick={() => {
setOpen(false);
}}
/>
</div>
</div>
</div>
</div>
</div>
</div>
</GridCard>
</div>
</AutoHeight>
)
}

View File

@ -4,6 +4,7 @@ import { PluginStatus, usePluginStore, useUiStore } from "@/hooks/stores";
import { useCallback, useEffect, useState } from "react";
import { cx } from "@/cva.config";
import UploadPluginModal from "@components/UploadPluginDialog";
import PluginConfigureModal from "./PluginConfigureDialog";
function PluginListStatusIcon({ plugin }: { plugin: PluginStatus }) {
let classNames = "bg-slate-500 border-slate-600";
@ -29,7 +30,11 @@ export default function PluginList() {
setIsPluginUploadModalOpen,
setPluginUploadModalView,
plugins,
setPlugins
setPlugins,
pluginConfigureModalOpen,
setPluginConfigureModalOpen,
configuringPlugin,
setConfiguringPlugin,
} = usePluginStore();
const sidebarView = useUiStore(state => state.sidebarView);
@ -74,7 +79,10 @@ export default function PluginList() {
size="SM"
theme="light"
text="Settings"
onClick={() => console.log("Settings clicked")}
onClick={() => {
setConfiguringPlugin(plugin);
setPluginConfigureModalOpen(true);
}}
/>
</div>
</li>
@ -82,6 +90,17 @@ export default function PluginList() {
</ul>
</div>
<PluginConfigureModal
open={pluginConfigureModalOpen}
setOpen={(open) => {
setPluginConfigureModalOpen(open);
if (!open) {
updatePlugins();
}
}}
plugin={configuringPlugin}
/>
<div className="flex items-center gap-x-2">
<Button
size="SM"

View File

@ -557,6 +557,12 @@ interface PluginState {
plugins: PluginStatus[];
setPlugins: (plugins: PluginStatus[]) => void;
pluginConfigureModalOpen: boolean;
setPluginConfigureModalOpen: (isOpen: boolean) => void;
configuringPlugin: PluginStatus | null;
setConfiguringPlugin: (plugin: PluginStatus | null) => void;
}
export const usePluginStore = create<PluginState>(set => ({
@ -574,4 +580,10 @@ export const usePluginStore = create<PluginState>(set => ({
plugins: [],
setPlugins: plugins => set({ plugins }),
pluginConfigureModalOpen: false,
setPluginConfigureModalOpen: isOpen => set({ pluginConfigureModalOpen: isOpen }),
configuringPlugin: null,
setConfiguringPlugin: plugin => set({ configuringPlugin: plugin }),
}));