mirror of https://github.com/jetkvm/kvm.git
Add enable/disable button
This commit is contained in:
parent
3853b58613
commit
88f3e97011
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"}},
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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 }),
|
||||
}));
|
||||
|
|
Loading…
Reference in New Issue