diff --git a/internal/plugin/database.go b/internal/plugin/database.go index 2f9a89f..f97e748 100644 --- a/internal/plugin/database.go +++ b/internal/plugin/database.go @@ -4,10 +4,18 @@ import ( "encoding/json" "fmt" "os" + "sync" ) const databaseFile = pluginsFolder + "/plugins.json" +type PluginDatabase struct { + // Map with the plugin name as the key + Plugins map[string]PluginInstall `json:"plugins"` + + saveMutex sync.Mutex +} + var pluginDatabase = PluginDatabase{} func init() { diff --git a/internal/plugin/install.go b/internal/plugin/install.go new file mode 100644 index 0000000..c860aff --- /dev/null +++ b/internal/plugin/install.go @@ -0,0 +1,31 @@ +package plugin + +type PluginInstall struct { + Enabled bool `json:"enabled"` + + // Current active version of the plugin + Version string `json:"version"` + + // Map of a plugin version to the extracted directory + ExtractedVersions map[string]string `json:"extracted_versions"` + + manifest *PluginManifest +} + +func (p *PluginInstall) GetManifest() (*PluginManifest, error) { + if p.manifest != nil { + return p.manifest, nil + } + + manifest, err := readManifest(p.GetExtractedFolder()) + if err != nil { + return nil, err + } + + p.manifest = manifest + return manifest, nil +} + +func (p *PluginInstall) GetExtractedFolder() string { + return p.ExtractedVersions[p.Version] +} diff --git a/internal/plugin/plugin.go b/internal/plugin/plugin.go index 3a3318d..b841f01 100644 --- a/internal/plugin/plugin.go +++ b/internal/plugin/plugin.go @@ -132,6 +132,28 @@ func RpcPluginInstall(name string, version string) error { return nil } +func RpcPluginList() ([]PluginStatus, error) { + plugins := make([]PluginStatus, 0, len(pluginDatabase.Plugins)) + for pluginName, plugin := range pluginDatabase.Plugins { + manifest, err := plugin.GetManifest() + if err != nil { + return nil, fmt.Errorf("failed to get plugin manifest for %s: %v", pluginName, err) + } + + status := "stopped" + if plugin.Enabled { + status = "running" + } + + plugins = append(plugins, PluginStatus{ + PluginManifest: *manifest, + Enabled: plugin.Enabled, + Status: status, + }) + } + return plugins, nil +} + func readManifest(extractFolder string) (*PluginManifest, error) { manifestPath := path.Join(extractFolder, "manifest.json") manifestFile, err := os.Open(manifestPath) diff --git a/internal/plugin/type.go b/internal/plugin/type.go index 6f07c59..01d85a5 100644 --- a/internal/plugin/type.go +++ b/internal/plugin/type.go @@ -1,7 +1,5 @@ package plugin -import "sync" - type PluginManifest struct { ManifestVersion string `json:"manifest_version"` Name string `json:"name"` @@ -12,19 +10,8 @@ type PluginManifest struct { SystemMinVersion string `json:"system_min_version,omitempty"` } -type PluginInstall struct { - Enabled bool `json:"enabled"` - - // Current active version of the plugin - Version string `json:"version"` - - // Map of a plugin version to the extracted directory - ExtractedVersions map[string]string `json:"extracted_versions"` -} - -type PluginDatabase struct { - // Map with the plugin name as the key - Plugins map[string]PluginInstall `json:"plugins"` - - saveMutex sync.Mutex +type PluginStatus struct { + PluginManifest + Enabled bool `json:"enabled"` + Status string `json:"status"` } diff --git a/jsonrpc.go b/jsonrpc.go index 6ffdd87..9f3a9b2 100644 --- a/jsonrpc.go +++ b/jsonrpc.go @@ -558,4 +558,5 @@ var rpcHandlers = map[string]RPCHandler{ "pluginStartUpload": {Func: plugin.RpcPluginStartUpload, Params: []string{"filename", "size"}}, "pluginExtract": {Func: plugin.RpcPluginExtract, Params: []string{"filename"}}, "pluginInstall": {Func: plugin.RpcPluginInstall, Params: []string{"name", "version"}}, + "pluginList": {Func: plugin.RpcPluginList}, } diff --git a/ui/src/components/PluginList.tsx b/ui/src/components/PluginList.tsx new file mode 100644 index 0000000..947e618 --- /dev/null +++ b/ui/src/components/PluginList.tsx @@ -0,0 +1,107 @@ +import { useJsonRpc } from "@/hooks/useJsonRpc"; +import { Button } from "@components/Button"; +import { PluginStatus, usePluginStore, useUiStore } from "@/hooks/stores"; +import { useCallback, useEffect, useState } from "react"; +import { cx } from "@/cva.config"; +import UploadPluginModal from "@components/UploadPluginDialog"; + +function PluginListStatusIcon({ plugin }: { plugin: PluginStatus }) { + let classNames = "bg-slate-500 border-slate-600"; + if (plugin.enabled && plugin.status === "running") { + classNames = "bg-green-500 border-green-600"; + } else if (plugin.enabled && plugin.status === "stopped") { + classNames = "bg-red-500 border-red-600"; + } + + return ( +
{plugin.name}
+ +Tailscale
-https://github.com/tutman96/jetkvm-plugin-tailscale
-