mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-03 06:07:20 +02:00
Added working plugin manager prototype
- Added experimental plugin UI proxy - Added plugin icon loader - Added plugin table renderer
This commit is contained in:
parent
dd4df0b4db
commit
bddff0cf2f
BIN
example/plugins/helloworld/icon.png
Normal file
BIN
example/plugins/helloworld/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
example/plugins/helloworld/icon.psd
Normal file
BIN
example/plugins/helloworld/icon.psd
Normal file
Binary file not shown.
@ -235,6 +235,13 @@ func RegisterNetworkUtilsAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
authRouter.HandleFunc("/api/tools/fwdproxy/port", forwardProxy.HandlePort)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func RegisterPluginAPIs(authRouter *auth.RouterDef) {
|
||||||
|
authRouter.HandleFunc("/api/plugins/list", pluginManager.HandleListPlugins)
|
||||||
|
authRouter.HandleFunc("/api/plugins/enable", pluginManager.HandleEnablePlugin)
|
||||||
|
authRouter.HandleFunc("/api/plugins/disable", pluginManager.HandleDisablePlugin)
|
||||||
|
authRouter.HandleFunc("/api/plugins/icon", pluginManager.HandleLoadPluginIcon)
|
||||||
|
}
|
||||||
|
|
||||||
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
|
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
|
||||||
func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
func RegisterAuthAPIs(requireAuth bool, targetMux *http.ServeMux) {
|
||||||
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
targetMux.HandleFunc("/api/auth/login", authAgent.HandleLogin)
|
||||||
@ -340,6 +347,7 @@ func initAPIs(targetMux *http.ServeMux) {
|
|||||||
RegisterNetworkUtilsAPIs(authRouter)
|
RegisterNetworkUtilsAPIs(authRouter)
|
||||||
RegisterACMEAndAutoRenewerAPIs(authRouter)
|
RegisterACMEAndAutoRenewerAPIs(authRouter)
|
||||||
RegisterStaticWebServerAPIs(authRouter)
|
RegisterStaticWebServerAPIs(authRouter)
|
||||||
|
RegisterPluginAPIs(authRouter)
|
||||||
|
|
||||||
//Account Reset
|
//Account Reset
|
||||||
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
targetMux.HandleFunc("/api/account/reset", HandleAdminAccountResetEmail)
|
||||||
|
83
src/mod/plugins/handler.go
Normal file
83
src/mod/plugins/handler.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleListPlugins handles the request to list all loaded plugins
|
||||||
|
func (m *Manager) HandleListPlugins(w http.ResponseWriter, r *http.Request) {
|
||||||
|
plugins, err := m.ListLoadedPlugins()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
js, err := json.Marshal(plugins)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) HandleLoadPluginIcon(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pluginID, err := utils.GetPara(r, "plugin_id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "plugin_id not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin, err := m.GetPluginByID(pluginID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the icon.png exists under plugin root directory
|
||||||
|
expectedIconPath := filepath.Join(plugin.RootDir, "icon.png")
|
||||||
|
if !utils.FileExists(expectedIconPath) {
|
||||||
|
http.ServeContent(w, r, "no_img.png", time.Now(), bytes.NewReader(noImg))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.ServeFile(w, r, expectedIconPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) HandleEnablePlugin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pluginID, err := utils.PostPara(r, "plugin_id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "plugin_id not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.EnablePlugin(pluginID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) HandleDisablePlugin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pluginID, err := utils.PostPara(r, "plugin_id")
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "plugin_id not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = m.DisablePlugin(pluginID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
@ -4,12 +4,15 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *Manager) StartPlugin(pluginID string) error {
|
func (m *Manager) StartPlugin(pluginID string) error {
|
||||||
@ -77,18 +80,49 @@ func (m *Manager) StartPlugin(pluginID string) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
//Create a UI forwarder if the plugin has UI
|
||||||
|
err = m.StartUIHandlerForPlugin(thisPlugin, pluginConfiguration.Port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Store the cmd object so it can be accessed later for stopping the plugin
|
// Store the cmd object so it can be accessed later for stopping the plugin
|
||||||
plugin.(*Plugin).Process = cmd
|
plugin.(*Plugin).process = cmd
|
||||||
plugin.(*Plugin).Enabled = true
|
plugin.(*Plugin).Enabled = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StartUIHandlerForPlugin starts a UI handler for the plugin
|
||||||
|
func (m *Manager) StartUIHandlerForPlugin(targetPlugin *Plugin, pluginListeningPort int) error {
|
||||||
|
// Create a dpcore object to reverse proxy the plugin ui
|
||||||
|
pluginUIRelPath := targetPlugin.Spec.UIPath
|
||||||
|
if !strings.HasPrefix(pluginUIRelPath, "/") {
|
||||||
|
pluginUIRelPath = "/" + pluginUIRelPath
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginUIURL, err := url.Parse("http://127.0.0.1:" + strconv.Itoa(pluginListeningPort) + pluginUIRelPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if targetPlugin.Spec.UIPath != "" {
|
||||||
|
targetPlugin.uiProxy = dpcore.NewDynamicProxyCore(
|
||||||
|
pluginUIURL,
|
||||||
|
"",
|
||||||
|
&dpcore.DpcoreOptions{
|
||||||
|
IgnoreTLSVerification: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
m.LoadedPlugins.Store(targetPlugin.Spec.ID, targetPlugin)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) handlePluginSTDOUT(pluginID string, line string) {
|
func (m *Manager) handlePluginSTDOUT(pluginID string, line string) {
|
||||||
thisPlugin, err := m.GetPluginByID(pluginID)
|
thisPlugin, err := m.GetPluginByID(pluginID)
|
||||||
processID := -1
|
processID := -1
|
||||||
if thisPlugin.Process != nil && thisPlugin.Process.Process != nil {
|
if thisPlugin.process != nil && thisPlugin.process.Process != nil {
|
||||||
// Get the process ID of the plugin
|
// Get the process ID of the plugin
|
||||||
processID = thisPlugin.Process.Process.Pid
|
processID = thisPlugin.process.Process.Pid
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.Log("[unknown:"+strconv.Itoa(processID)+"] "+line, err)
|
m.Log("[unknown:"+strconv.Itoa(processID)+"] "+line, err)
|
||||||
@ -104,16 +138,19 @@ func (m *Manager) StopPlugin(pluginID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
thisPlugin := plugin.(*Plugin)
|
thisPlugin := plugin.(*Plugin)
|
||||||
thisPlugin.Process.Process.Signal(os.Interrupt)
|
thisPlugin.process.Process.Signal(os.Interrupt)
|
||||||
go func() {
|
go func() {
|
||||||
//Wait for 10 seconds for the plugin to stop gracefully
|
//Wait for 10 seconds for the plugin to stop gracefully
|
||||||
time.Sleep(10 * time.Second)
|
time.Sleep(10 * time.Second)
|
||||||
if thisPlugin.Process.ProcessState == nil || !thisPlugin.Process.ProcessState.Exited() {
|
if thisPlugin.process.ProcessState == nil || !thisPlugin.process.ProcessState.Exited() {
|
||||||
m.Log("Plugin "+thisPlugin.Spec.Name+" failed to stop gracefully, killing it", nil)
|
m.Log("Plugin "+thisPlugin.Spec.Name+" failed to stop gracefully, killing it", nil)
|
||||||
thisPlugin.Process.Process.Kill()
|
thisPlugin.process.Process.Kill()
|
||||||
} else {
|
} else {
|
||||||
m.Log("Plugin "+thisPlugin.Spec.Name+" background process stopped", nil)
|
m.Log("Plugin "+thisPlugin.Spec.Name+" background process stopped", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Remove the UI proxy
|
||||||
|
thisPlugin.uiProxy = nil
|
||||||
}()
|
}()
|
||||||
plugin.(*Plugin).Enabled = false
|
plugin.(*Plugin).Enabled = false
|
||||||
return nil
|
return nil
|
||||||
@ -125,7 +162,10 @@ func (m *Manager) PluginStillRunning(pluginID string) bool {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return plugin.(*Plugin).Process.ProcessState == nil
|
if plugin.(*Plugin).process == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return plugin.(*Plugin).process.ProcessState == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlockUntilAllProcessExited blocks until all the plugins processes have exited
|
// BlockUntilAllProcessExited blocks until all the plugins processes have exited
|
||||||
@ -134,7 +174,7 @@ func (m *Manager) BlockUntilAllProcessExited() {
|
|||||||
plugin := value.(*Plugin)
|
plugin := value.(*Plugin)
|
||||||
if m.PluginStillRunning(value.(*Plugin).Spec.ID) {
|
if m.PluginStillRunning(value.(*Plugin).Spec.ID) {
|
||||||
//Wait for the plugin to exit
|
//Wait for the plugin to exit
|
||||||
plugin.Process.Wait()
|
plugin.process.Wait()
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
BIN
src/mod/plugins/no_img.png
Normal file
BIN
src/mod/plugins/no_img.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.2 KiB |
BIN
src/mod/plugins/no_img.psd
Normal file
BIN
src/mod/plugins/no_img.psd
Normal file
Binary file not shown.
@ -2,12 +2,16 @@ package plugins
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"imuslab.com/zoraxy/mod/database"
|
"imuslab.com/zoraxy/mod/database"
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
"imuslab.com/zoraxy/mod/info/logger"
|
"imuslab.com/zoraxy/mod/info/logger"
|
||||||
"imuslab.com/zoraxy/mod/utils"
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
)
|
)
|
||||||
@ -15,8 +19,10 @@ import (
|
|||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
RootDir string //The root directory of the plugin
|
RootDir string //The root directory of the plugin
|
||||||
Spec *IntroSpect //The plugin specification
|
Spec *IntroSpect //The plugin specification
|
||||||
Process *exec.Cmd //The process of the plugin
|
|
||||||
Enabled bool //Whether the plugin is enabled
|
Enabled bool //Whether the plugin is enabled
|
||||||
|
|
||||||
|
uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI
|
||||||
|
process *exec.Cmd //The process of the plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManagerOptions struct {
|
type ManagerOptions struct {
|
||||||
@ -31,16 +37,22 @@ type Manager struct {
|
|||||||
Options *ManagerOptions
|
Options *ManagerOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed no_img.png
|
||||||
|
var noImg []byte
|
||||||
|
|
||||||
// NewPluginManager creates a new plugin manager
|
// NewPluginManager creates a new plugin manager
|
||||||
func NewPluginManager(options *ManagerOptions) *Manager {
|
func NewPluginManager(options *ManagerOptions) *Manager {
|
||||||
|
//Create plugin directory if not exists
|
||||||
if options.PluginDir == "" {
|
if options.PluginDir == "" {
|
||||||
options.PluginDir = "./plugins"
|
options.PluginDir = "./plugins"
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.FileExists(options.PluginDir) {
|
if !utils.FileExists(options.PluginDir) {
|
||||||
os.MkdirAll(options.PluginDir, 0755)
|
os.MkdirAll(options.PluginDir, 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Create database table
|
||||||
|
options.Database.NewTable("plugins")
|
||||||
|
|
||||||
return &Manager{
|
return &Manager{
|
||||||
LoadedPlugins: sync.Map{},
|
LoadedPlugins: sync.Map{},
|
||||||
Options: options,
|
Options: options,
|
||||||
@ -63,17 +75,18 @@ func (m *Manager) LoadPluginsFromDisk() error {
|
|||||||
m.Log("Failed to load plugin: "+filepath.Base(pluginPath), err)
|
m.Log("Failed to load plugin: "+filepath.Base(pluginPath), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
thisPlugin.RootDir = pluginPath
|
thisPlugin.RootDir = filepath.ToSlash(pluginPath)
|
||||||
m.LoadedPlugins.Store(thisPlugin.Spec.ID, thisPlugin)
|
m.LoadedPlugins.Store(thisPlugin.Spec.ID, thisPlugin)
|
||||||
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
|
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
|
||||||
|
|
||||||
//TODO: Move this to a separate function
|
// If the plugin was enabled, start it now
|
||||||
// Enable the plugin if it is enabled in the database
|
fmt.Println(m.GetPluginPreviousEnableState(thisPlugin.Spec.ID))
|
||||||
err = m.StartPlugin(thisPlugin.Spec.ID)
|
if m.GetPluginPreviousEnableState(thisPlugin.Spec.ID) {
|
||||||
if err != nil {
|
err = m.StartPlugin(thisPlugin.Spec.ID)
|
||||||
m.Log("Failed to enable plugin: "+thisPlugin.Spec.Name, err)
|
if err != nil {
|
||||||
|
m.Log("Failed to enable plugin: "+thisPlugin.Spec.Name, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,26 +108,48 @@ func (m *Manager) EnablePlugin(pluginID string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
//TODO: Add database record
|
m.Options.Database.Write("plugins", pluginID, true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisablePlugin disables a plugin
|
// DisablePlugin disables a plugin
|
||||||
func (m *Manager) DisablePlugin(pluginID string) error {
|
func (m *Manager) DisablePlugin(pluginID string) error {
|
||||||
err := m.StopPlugin(pluginID)
|
err := m.StopPlugin(pluginID)
|
||||||
//TODO: Add database record
|
m.Options.Database.Write("plugins", pluginID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPluginPreviousEnableState returns the previous enable state of a plugin
|
||||||
|
func (m *Manager) GetPluginPreviousEnableState(pluginID string) bool {
|
||||||
|
enableState := true
|
||||||
|
err := m.Options.Database.Read("plugins", pluginID, &enableState)
|
||||||
|
if err != nil {
|
||||||
|
//Default to true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return enableState
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListLoadedPlugins returns a list of loaded plugins
|
||||||
|
func (m *Manager) ListLoadedPlugins() ([]*Plugin, error) {
|
||||||
|
var plugins []*Plugin
|
||||||
|
m.LoadedPlugins.Range(func(key, value interface{}) bool {
|
||||||
|
plugin := value.(*Plugin)
|
||||||
|
plugins = append(plugins, plugin)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return plugins, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Terminate all plugins and exit
|
// Terminate all plugins and exit
|
||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
m.LoadedPlugins.Range(func(key, value interface{}) bool {
|
m.LoadedPlugins.Range(func(key, value interface{}) bool {
|
||||||
plugin := value.(*Plugin)
|
plugin := value.(*Plugin)
|
||||||
if plugin.Enabled {
|
if plugin.Enabled {
|
||||||
m.DisablePlugin(plugin.Spec.ID)
|
m.StopPlugin(plugin.Spec.ID)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
41
src/mod/plugins/uirouter.go
Normal file
41
src/mod/plugins/uirouter.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandlePluginUI handles the request to the plugin UI
|
||||||
|
// This function will route the request to the correct plugin UI handler
|
||||||
|
func (m *Manager) HandlePluginUI(pluginID string, w http.ResponseWriter, r *http.Request) {
|
||||||
|
plugin, err := m.GetPluginByID(pluginID)
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the plugin has UI
|
||||||
|
if plugin.Spec.UIPath == "" {
|
||||||
|
utils.SendErrorResponse(w, "Plugin does not have UI")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Check if the plugin has UI handler
|
||||||
|
if plugin.uiProxy == nil {
|
||||||
|
utils.SendErrorResponse(w, "Plugin does not have UI handler")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Call the plugin UI handler
|
||||||
|
plugin.uiProxy.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||||
|
UseTLS: false,
|
||||||
|
OriginalHost: r.Host,
|
||||||
|
ProxyDomain: r.Host,
|
||||||
|
NoCache: true,
|
||||||
|
PathPrefix: "/plugin.ui/" + pluginID,
|
||||||
|
Version: m.Options.SystemConst.ZoraxyVersion,
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
@ -58,6 +58,19 @@ func FSHandler(handler http.Handler) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//For Plugin Routing
|
||||||
|
if strings.HasPrefix(r.URL.Path, "/plugin.ui/") {
|
||||||
|
//Extract the plugin ID from the request path
|
||||||
|
parts := strings.Split(r.URL.Path, "/")
|
||||||
|
if len(parts) > 2 {
|
||||||
|
pluginID := parts[2]
|
||||||
|
pluginManager.HandlePluginUI(pluginID, w, r)
|
||||||
|
} else {
|
||||||
|
http.Error(w, "Invalid Usage", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//For WebSSH Routing
|
//For WebSSH Routing
|
||||||
//Example URL Path: /web.ssh/{{instance_uuid}}/*
|
//Example URL Path: /web.ssh/{{instance_uuid}}/*
|
||||||
if strings.HasPrefix(r.URL.Path, "/web.ssh/") {
|
if strings.HasPrefix(r.URL.Path, "/web.ssh/") {
|
||||||
|
@ -384,6 +384,10 @@ func ShutdownSeq() {
|
|||||||
if acmeAutoRenewer != nil {
|
if acmeAutoRenewer != nil {
|
||||||
acmeAutoRenewer.Close()
|
acmeAutoRenewer.Close()
|
||||||
}
|
}
|
||||||
|
//Close the plugin manager
|
||||||
|
SystemWideLogger.Println("Shutting down plugin manager")
|
||||||
|
pluginManager.Close()
|
||||||
|
|
||||||
//Remove the tmp folder
|
//Remove the tmp folder
|
||||||
SystemWideLogger.Println("Cleaning up tmp files")
|
SystemWideLogger.Println("Cleaning up tmp files")
|
||||||
os.RemoveAll("./tmp")
|
os.RemoveAll("./tmp")
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="standardContainer">
|
<div class="standardContainer">
|
||||||
<div class="ui basic segment">
|
<div class="ui basic segment">
|
||||||
<h2>Plugins Manager</h2>
|
<h2>Plugins</h2>
|
||||||
<p>Add custom features to Zoraxy</p>
|
<p>Custom features on Zoraxy</p>
|
||||||
</div>
|
</div>
|
||||||
<table class="ui celled table">
|
<table class="ui celled table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -9,30 +9,61 @@
|
|||||||
<th>Plugin Name</th>
|
<th>Plugin Name</th>
|
||||||
<th>Descriptions</th>
|
<th>Descriptions</th>
|
||||||
<th>Catergory</th>
|
<th>Catergory</th>
|
||||||
<th>Version</th>
|
|
||||||
<th>Author</th>
|
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody id="pluginTable">
|
||||||
<tr>
|
|
||||||
<td data-label="PluginName">{{plugin.name}}</td>
|
|
||||||
<td data-label="Descriptions">{{plugin.description}}</td>
|
|
||||||
<td data-label="Category">{{plugin.category}}</td>
|
|
||||||
<td data-label="Version">{{plugin.version}}</td>
|
|
||||||
<td data-label="Author">{{plugin.author}}</td>
|
|
||||||
<td data-label="Action">
|
|
||||||
<div class="ui toggle checkbox">
|
|
||||||
<input type="checkbox" name="enable">
|
|
||||||
</div>
|
|
||||||
<button class="ui basic small circular icon button"><i class="ui edit icon"></i></button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
function initiatePluginList(){
|
||||||
|
$.get(`/api/plugins/list`, function(data){
|
||||||
|
const tbody = $("#pluginTable");
|
||||||
|
tbody.empty();
|
||||||
|
|
||||||
|
data.forEach(plugin => {
|
||||||
|
let authorContact = plugin.Spec.author_contact;
|
||||||
|
if(!authorContact.startsWith('http')){
|
||||||
|
authorContact = `mailto:${authorContact}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let versionString = `v${plugin.Spec.version_major}.${plugin.Spec.version_minor}.${plugin.Spec.version_patch}`;
|
||||||
|
const row = `
|
||||||
|
<tr>
|
||||||
|
<td data-label="PluginName">
|
||||||
|
<h4 class="ui header">
|
||||||
|
<img src="/api/plugins/icon?plugin_id=${plugin.Spec.id}" class="ui image">
|
||||||
|
<div class="content">
|
||||||
|
${plugin.Spec.name}
|
||||||
|
<div class="sub header">${versionString} by <a href="${authorContact}" target="_blank">${plugin.Spec.author}</a></div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</td>
|
||||||
|
<td data-label="Descriptions">${plugin.Spec.description}<br>
|
||||||
|
<a href="${plugin.Spec.url}" target="_blank">${plugin.Spec.url}</a></td>
|
||||||
|
<td data-label="Category">${plugin.Spec.type==0?"Router":"Utilities"}</td>
|
||||||
|
<td data-label="Action">
|
||||||
|
<div class="ui toggle checkbox">
|
||||||
|
<input type="checkbox" name="enable" ${plugin.Enabled ? 'checked' : ''}>
|
||||||
|
</div>
|
||||||
|
<button class="ui basic small circular icon button" onclick="openPluginUI('${plugin.Spec.id}');"><i class="ui edit icon"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
tbody.append(row);
|
||||||
|
});
|
||||||
|
console.log(data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function openPluginUI(pluginid){
|
||||||
|
showSideWrapper(`/plugin.ui/${pluginid}/`);
|
||||||
|
}
|
||||||
|
|
||||||
|
initiatePluginList();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<div class="ui divider menudivider">Others</div>
|
<div class="ui divider menudivider">Others</div>
|
||||||
<a class="item" tag="plugins">
|
<a class="item" tag="plugins">
|
||||||
<i class="simplistic puzzle piece icon"></i> Plugins Manager
|
<i class="simplistic puzzle piece icon"></i> Plugins
|
||||||
</a>
|
</a>
|
||||||
<a class="item" tag="webserv">
|
<a class="item" tag="webserv">
|
||||||
<i class="simplistic globe icon"></i> Static Web Server
|
<i class="simplistic globe icon"></i> Static Web Server
|
||||||
|
Loading…
x
Reference in New Issue
Block a user