mirror of
				https://github.com/tobychui/zoraxy.git
				synced 2025-11-04 07:54:12 +01:00 
			
		
		
		
	Added working plugin store prototype
- Added plugin install and remove api
This commit is contained in:
		@@ -237,6 +237,8 @@ func RegisterPluginAPIs(authRouter *auth.RouterDef) {
 | 
			
		||||
 | 
			
		||||
	authRouter.HandleFunc("/api/plugins/store/list", pluginManager.HandleListDownloadablePlugins)
 | 
			
		||||
	authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList)
 | 
			
		||||
	authRouter.HandleFunc("/api/plugins/store/install", pluginManager.HandleInstallPlugin)
 | 
			
		||||
	authRouter.HandleFunc("/api/plugins/store/uninstall", pluginManager.HandleUninstallPlugin)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Register the APIs for Auth functions, due to scoping issue some functions are defined here
 | 
			
		||||
 
 | 
			
		||||
@@ -274,13 +274,10 @@ func (m *Manager) StopPlugin(pluginID string) error {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Check if the plugin is still running
 | 
			
		||||
func (m *Manager) PluginStillRunning(pluginID string) bool {
 | 
			
		||||
func (m *Manager) PluginIsRunning(pluginID string) bool {
 | 
			
		||||
	plugin, err := m.GetPluginByID(pluginID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if plugin.process == nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return plugin.process.ProcessState == nil
 | 
			
		||||
	return plugin.IsRunning()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -58,6 +58,59 @@ func NewPluginManager(options *ManagerOptions) *Manager {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Reload all plugins from disk
 | 
			
		||||
func (m *Manager) ReloadPluginFromDisk() {
 | 
			
		||||
	//Check each of the current plugins if the directory exists
 | 
			
		||||
	//If not, remove the plugin from the loaded plugins list
 | 
			
		||||
	m.loadedPluginsMutex.Lock()
 | 
			
		||||
	for pluginID, plugin := range m.LoadedPlugins {
 | 
			
		||||
		if !utils.FileExists(plugin.RootDir) {
 | 
			
		||||
			m.Log("Plugin directory not found, removing plugin from runtime: "+pluginID, nil)
 | 
			
		||||
			delete(m.LoadedPlugins, pluginID)
 | 
			
		||||
			//Remove the plugin enable state from the database
 | 
			
		||||
			m.Options.Database.Delete("plugins", pluginID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m.loadedPluginsMutex.Unlock()
 | 
			
		||||
 | 
			
		||||
	//Scan the plugin directory for new plugins
 | 
			
		||||
	foldersInPluginDir, err := os.ReadDir(m.Options.PluginDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		m.Log("Failed to read plugin directory", err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, folder := range foldersInPluginDir {
 | 
			
		||||
		if folder.IsDir() {
 | 
			
		||||
			pluginPath := filepath.Join(m.Options.PluginDir, folder.Name())
 | 
			
		||||
			thisPlugin, err := m.LoadPluginSpec(pluginPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				m.Log("Failed to load plugin: "+filepath.Base(pluginPath), err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			//Check if the plugin id is already loaded into the runtime
 | 
			
		||||
			m.loadedPluginsMutex.RLock()
 | 
			
		||||
			_, ok := m.LoadedPlugins[thisPlugin.Spec.ID]
 | 
			
		||||
			m.loadedPluginsMutex.RUnlock()
 | 
			
		||||
			if ok {
 | 
			
		||||
				//Plugin already loaded, skip it
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			thisPlugin.RootDir = filepath.ToSlash(pluginPath)
 | 
			
		||||
			thisPlugin.staticRouteProxy = make(map[string]*dpcore.ReverseProxy)
 | 
			
		||||
			m.loadedPluginsMutex.Lock()
 | 
			
		||||
			m.LoadedPlugins[thisPlugin.Spec.ID] = thisPlugin
 | 
			
		||||
			m.loadedPluginsMutex.Unlock()
 | 
			
		||||
			m.Log("Added new plugin: "+thisPlugin.Spec.Name, nil)
 | 
			
		||||
 | 
			
		||||
			// The default state of the plugin is disabled, so no need to start it
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LoadPluginsFromDisk loads all plugins from the plugin directory
 | 
			
		||||
func (m *Manager) LoadPluginsFromDisk() error {
 | 
			
		||||
	// Load all plugins from the plugin directory
 | 
			
		||||
@@ -258,3 +311,8 @@ func (p *Plugin) HandleStaticRoute(w http.ResponseWriter, r *http.Request, longe
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsRunning checks if the plugin is currently running
 | 
			
		||||
func (p *Plugin) IsRunning() bool {
 | 
			
		||||
	return p.process != nil && p.process.Process != nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,14 @@
 | 
			
		||||
package plugins
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/sha256"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
@@ -89,6 +93,180 @@ func (m *Manager) ListDownloadablePlugins() []*DownloadablePlugin {
 | 
			
		||||
	return m.Options.DownloadablePluginCache
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InstallPlugin installs the given plugin by moving it to the PluginDir.
 | 
			
		||||
func (m *Manager) InstallPlugin(plugin *DownloadablePlugin) error {
 | 
			
		||||
	pluginDir := filepath.Join(m.Options.PluginDir, plugin.PluginIntroSpect.Name)
 | 
			
		||||
	pluginFile := plugin.PluginIntroSpect.Name
 | 
			
		||||
	if runtime.GOOS == "windows" {
 | 
			
		||||
		pluginFile += ".exe"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//Check if the plugin id already exists in runtime plugin map
 | 
			
		||||
	if _, ok := m.LoadedPlugins[plugin.PluginIntroSpect.ID]; ok {
 | 
			
		||||
		return fmt.Errorf("plugin already installed: %s", plugin.PluginIntroSpect.ID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Create the plugin directory if it doesn't exist
 | 
			
		||||
	err := os.MkdirAll(pluginDir, os.ModePerm)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to create plugin directory: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Download the plugin binary
 | 
			
		||||
	downloadURL, ok := plugin.DownloadURLs[runtime.GOOS+"_"+runtime.GOARCH]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("no download URL available for the current platform")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := http.Get(downloadURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to download plugin: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode != http.StatusOK {
 | 
			
		||||
		return fmt.Errorf("failed to download plugin: %s", resp.Status)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write the plugin binary to the plugin directory
 | 
			
		||||
	pluginPath := filepath.Join(pluginDir, pluginFile)
 | 
			
		||||
	out, err := os.Create(pluginPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to create plugin file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = io.Copy(out, resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		out.Close()
 | 
			
		||||
		return fmt.Errorf("failed to write plugin file: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make the plugin executable
 | 
			
		||||
	err = os.Chmod(pluginPath, 0755)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		out.Close()
 | 
			
		||||
		return fmt.Errorf("failed to set executable permissions: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Verify the checksum of the downloaded plugin binary
 | 
			
		||||
	checksums, err := plugin.ChecksumsSHA256.GetCurrentPlatformChecksum()
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		if !verifyChecksumForFile(pluginPath, checksums) {
 | 
			
		||||
			out.Close()
 | 
			
		||||
			return fmt.Errorf("checksum verification failed for plugin binary")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//Ok, also download the icon if exists
 | 
			
		||||
	if plugin.IconPath != "" {
 | 
			
		||||
		iconURL := strings.TrimSpace(plugin.IconPath)
 | 
			
		||||
		if iconURL != "" {
 | 
			
		||||
			resp, err := http.Get(iconURL)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("failed to download plugin icon: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
			defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
			//Save the icon to the plugin directory
 | 
			
		||||
			iconPath := filepath.Join(pluginDir, "icon.png")
 | 
			
		||||
			out, err := os.Create(iconPath)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("failed to create plugin icon file: %w", err)
 | 
			
		||||
			}
 | 
			
		||||
			defer out.Close()
 | 
			
		||||
 | 
			
		||||
			io.Copy(out, resp.Body)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	//Close the plugin exeutable
 | 
			
		||||
	out.Close()
 | 
			
		||||
 | 
			
		||||
	//Reload the plugin list
 | 
			
		||||
	m.ReloadPluginFromDisk()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UninstallPlugin uninstalls the plugin by removing its directory.
 | 
			
		||||
func (m *Manager) UninstallPlugin(pluginID string) error {
 | 
			
		||||
 | 
			
		||||
	//Stop the plugin process if it's running
 | 
			
		||||
	plugin, ok := m.LoadedPlugins[pluginID]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return fmt.Errorf("plugin not found: %s", pluginID)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if plugin.IsRunning() {
 | 
			
		||||
		err := m.StopPlugin(plugin.Spec.ID)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return fmt.Errorf("failed to stop plugin: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//Make sure the plugin process is stopped
 | 
			
		||||
	m.Options.Logger.PrintAndLog("plugin-manager", "Removing plugin in 3 seconds...", nil)
 | 
			
		||||
	time.Sleep(3 * time.Second)
 | 
			
		||||
 | 
			
		||||
	// Remove the plugin directory
 | 
			
		||||
	err := os.RemoveAll(plugin.RootDir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("failed to remove plugin directory: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//Reload the plugin list
 | 
			
		||||
	m.ReloadPluginFromDisk()
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCurrentPlatformChecksum returns the checksum for the current platform
 | 
			
		||||
func (c *Checksums) GetCurrentPlatformChecksum() (string, error) {
 | 
			
		||||
	switch runtime.GOOS {
 | 
			
		||||
	case "linux":
 | 
			
		||||
		switch runtime.GOARCH {
 | 
			
		||||
		case "amd64":
 | 
			
		||||
			return c.LinuxAmd64, nil
 | 
			
		||||
		case "386":
 | 
			
		||||
			return c.Linux386, nil
 | 
			
		||||
		case "arm":
 | 
			
		||||
			return c.LinuxArm, nil
 | 
			
		||||
		case "arm64":
 | 
			
		||||
			return c.LinuxArm64, nil
 | 
			
		||||
		case "mipsle":
 | 
			
		||||
			return c.LinuxMipsle, nil
 | 
			
		||||
		case "riscv64":
 | 
			
		||||
			return c.LinuxRiscv64, nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", fmt.Errorf("unsupported architecture: %s", runtime.GOARCH)
 | 
			
		||||
		}
 | 
			
		||||
	case "windows":
 | 
			
		||||
		switch runtime.GOARCH {
 | 
			
		||||
		case "amd64":
 | 
			
		||||
			return c.WindowsAmd64, nil
 | 
			
		||||
		default:
 | 
			
		||||
			return "", fmt.Errorf("unsupported architecture: %s", runtime.GOARCH)
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// VerifyChecksum verifies the checksum of the downloaded plugin binary.
 | 
			
		||||
func verifyChecksumForFile(filePath string, checksum string) bool {
 | 
			
		||||
	file, err := os.Open(filePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	defer file.Close()
 | 
			
		||||
 | 
			
		||||
	hash := sha256.New()
 | 
			
		||||
	if _, err := io.Copy(hash, file); err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	calculatedChecksum := fmt.Sprintf("%x", hash.Sum(nil))
 | 
			
		||||
 | 
			
		||||
	return calculatedChecksum == checksum
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
	Handlers for Plugin Store
 | 
			
		||||
*/
 | 
			
		||||
@@ -116,3 +294,63 @@ func (m *Manager) HandleResyncPluginList(w http.ResponseWriter, r *http.Request)
 | 
			
		||||
	}
 | 
			
		||||
	utils.SendOK(w)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HandleInstallPlugin is the handler for installing a plugin
 | 
			
		||||
func (m *Manager) HandleInstallPlugin(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if r.Method != http.MethodPost {
 | 
			
		||||
		utils.SendErrorResponse(w, "Method not allowed")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pluginID, err := utils.PostPara(r, "pluginID")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.SendErrorResponse(w, "pluginID is required")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Find the plugin info from cache
 | 
			
		||||
	var plugin *DownloadablePlugin
 | 
			
		||||
	for _, p := range m.Options.DownloadablePluginCache {
 | 
			
		||||
		if p.PluginIntroSpect.ID == pluginID {
 | 
			
		||||
			plugin = p
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if plugin == nil {
 | 
			
		||||
		utils.SendErrorResponse(w, "Plugin not found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Install the plugin (implementation depends on your system)
 | 
			
		||||
	err = m.InstallPlugin(plugin)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.SendErrorResponse(w, "Failed to install plugin: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	utils.SendOK(w)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HandleUninstallPlugin is the handler for uninstalling a plugin
 | 
			
		||||
func (m *Manager) HandleUninstallPlugin(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if r.Method != http.MethodPost {
 | 
			
		||||
		utils.SendErrorResponse(w, "Method not allowed")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pluginID, err := utils.PostPara(r, "pluginID")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.SendErrorResponse(w, "pluginID is required")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Uninstall the plugin (implementation depends on your system)
 | 
			
		||||
	err = m.UninstallPlugin(pluginID)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		utils.SendErrorResponse(w, "Failed to uninstall plugin: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	utils.SendOK(w)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -186,7 +186,7 @@
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
      <br>
 | 
			
		||||
      <button class="ui violet button" onclick="openPluginStore();"><i class="cart arrow down icon"></i>Get More Plugins!</button>
 | 
			
		||||
      <button class="ui basic violet button" onclick="openPluginStore();"><i class="download icon"></i>Plugin Store (Experimental)</button>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<script>
 | 
			
		||||
@@ -482,7 +482,10 @@ function initiatePluginList(){
 | 
			
		||||
          <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">
 | 
			
		||||
            <button onclick="getPluginInfo('${plugin.Spec.id}', this);" class="ui basic icon button" pluginid="${plugin.Spec.id}">
 | 
			
		||||
            <button onclick="uninstallPlugin('${plugin.Spec.id}', '${plugin.Spec.name}', this);" class="ui basic red icon button">
 | 
			
		||||
              <i class="trash icon"></i>
 | 
			
		||||
            </button>
 | 
			
		||||
             <button onclick="getPluginInfo('${plugin.Spec.id}', this);" class="ui basic icon button" pluginid="${plugin.Spec.id}">
 | 
			
		||||
              <i class="info circle icon"></i>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button onclick="stopPlugin('${plugin.Spec.id}', this);" class="ui basic button pluginEnableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? '' : 'style="display:none;"'}>
 | 
			
		||||
@@ -511,9 +514,6 @@ function initiatePluginList(){
 | 
			
		||||
 | 
			
		||||
initiatePluginList();
 | 
			
		||||
 | 
			
		||||
/* Tag Assignment */
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/* Plugin Lifecycle */
 | 
			
		||||
@@ -570,6 +570,28 @@ function openPluginStore(){
 | 
			
		||||
  showSideWrapper("snippet/pluginstore.html?t=" + Date.now(), true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function uninstallPlugin(pluginId, pluginName, btn=undefined) {
 | 
			
		||||
      if (confirm("Are you sure you want to remove " + pluginName + " plugin?")) {
 | 
			
		||||
          if (btn) {
 | 
			
		||||
              $(btn).html('<i class="spinner loading icon"></i>');
 | 
			
		||||
              $(btn).addClass('disabled');
 | 
			
		||||
          }
 | 
			
		||||
          $.cjax({
 | 
			
		||||
              url: '/api/plugins/store/uninstall',
 | 
			
		||||
              type: 'POST',
 | 
			
		||||
              data: { "pluginID": pluginId },
 | 
			
		||||
              success: function(data) {
 | 
			
		||||
                  if (data.error != undefined) {
 | 
			
		||||
                      msgbox(data.error, false);
 | 
			
		||||
                  } else {
 | 
			
		||||
                      msgbox(pluginName + " uninstalled successfully", true);
 | 
			
		||||
                      initiatePluginList();
 | 
			
		||||
                  }
 | 
			
		||||
              }
 | 
			
		||||
          });
 | 
			
		||||
      }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,10 @@
 | 
			
		||||
        <script src="../script/darktheme.js"></script>
 | 
			
		||||
        <br>
 | 
			
		||||
        <div class="ui container">
 | 
			
		||||
            <div class="ui warning message">
 | 
			
		||||
                <div class="header">Experimental Feature</div>
 | 
			
		||||
                <p>The Plugin Store is an experimental feature. Use it at your own risk.</p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="ui fluid search">
 | 
			
		||||
                <div class="ui fluid icon input">
 | 
			
		||||
                    <input id="searchInput" class="prompt" type="text" placeholder="Search plugins">
 | 
			
		||||
@@ -53,7 +57,7 @@
 | 
			
		||||
               
 | 
			
		||||
            </div>
 | 
			
		||||
            <button class="ui basic button" onclick="forceResyncPlugins();"><i class="ui green refresh icon"></i> Update Plugin List</button>
 | 
			
		||||
            <div class="ui divider"></div>
 | 
			
		||||
            <!-- <div class="ui divider"></div>
 | 
			
		||||
            <div class="ui basic segment advanceoptions">
 | 
			
		||||
                <div class="ui accordion advanceSettings">
 | 
			
		||||
                    <div class="title">
 | 
			
		||||
@@ -75,6 +79,7 @@
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        -->
 | 
			
		||||
            <div class="ui divider"></div>
 | 
			
		||||
            <div class="field" >
 | 
			
		||||
                <button class="ui basic button"  style="float: right;" onclick="closeThisWrapper();">Close</button>
 | 
			
		||||
@@ -126,7 +131,7 @@
 | 
			
		||||
                    const name = item.querySelector('.header').textContent.toLowerCase();
 | 
			
		||||
                    const description = item.querySelector('.description p').textContent.toLowerCase();
 | 
			
		||||
                    const author = item.querySelector('.meta span:nth-child(2)').textContent.toLowerCase();
 | 
			
		||||
                    const id = item.querySelector('.extra button').getAttribute('onclick').match(/'(.*?)'/)[1].toLowerCase();
 | 
			
		||||
                    const id = item.getAttribute('plugin_id').toLowerCase();
 | 
			
		||||
 | 
			
		||||
                    if (name.includes(query) || description.includes(query) || author.includes(query) || id.includes(query)) {
 | 
			
		||||
                        item.style.display = '';
 | 
			
		||||
@@ -145,6 +150,8 @@
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            function forceResyncPlugins() {
 | 
			
		||||
                parent.msgbox("Updating plugin list...", true);
 | 
			
		||||
                document.getElementById('searchInput').value = '';
 | 
			
		||||
                $.cjax({
 | 
			
		||||
                    url: '/api/plugins/store/resync',
 | 
			
		||||
                    type: 'POST',
 | 
			
		||||
@@ -181,52 +188,58 @@
 | 
			
		||||
                                <div class="meta">
 | 
			
		||||
                                    <span>Version: ${plugin.PluginIntroSpect.version_major}.${plugin.PluginIntroSpect.version_minor}.${plugin.PluginIntroSpect.version_patch}</span>
 | 
			
		||||
                                    <span>${plugin.PluginIntroSpect.author}</span>
 | 
			
		||||
                                    <span><a href="${plugin.PluginIntroSpect.url}">Website</a></span>
 | 
			
		||||
                                    <span><a href="${plugin.PluginIntroSpect.url}" target="_blank">Website</a></span>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="description">
 | 
			
		||||
                                    <p>${plugin.PluginIntroSpect.description}</p>
 | 
			
		||||
                                </div>
 | 
			
		||||
                                <div class="action">
 | 
			
		||||
                                    ${thisPluginIsInstalled 
 | 
			
		||||
                                        ? `<button class="ui basic circular red button" onclick="uninstallPlugin('${plugin.PluginIntroSpect.id}')"><i class="ui trash icon"></i> Remove</button>` 
 | 
			
		||||
                                        : `<button class="ui basic circular button" onclick="installPlugin('${plugin.PluginIntroSpect.id}')"><i class="ui download icon"></i> Install</button>`}
 | 
			
		||||
                                        ? `<button class="ui basic circular disabled button">Installed</button>` 
 | 
			
		||||
                                        : `<button class="ui basic circular button" onclick="installPlugin('${plugin.PluginIntroSpect.id}', this);",><i class="ui download icon"></i> Install</button>`}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    `;
 | 
			
		||||
                    $('#pluginList').append(item);
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                // Reapply search filter if there's a query in the search bar
 | 
			
		||||
                const searchQuery = document.getElementById('searchInput').value.toLowerCase();
 | 
			
		||||
                if (searchQuery.trim() !== '') {
 | 
			
		||||
                    searchPlugins();
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            /* Plugin Actions */
 | 
			
		||||
            function installPlugin(pluginId) {
 | 
			
		||||
            function installPlugin(pluginId, btn=undefined) {
 | 
			
		||||
                if (btn !== undefined) {
 | 
			
		||||
                    $(btn).addClass('loading').prop('disabled', true);
 | 
			
		||||
                }
 | 
			
		||||
                $.cjax({
 | 
			
		||||
                    url: '/api/plugins/store/install',
 | 
			
		||||
                    type: 'POST',
 | 
			
		||||
                    data: { pluginId },
 | 
			
		||||
                    data: { "pluginID": pluginId },
 | 
			
		||||
                    success: function(data) {
 | 
			
		||||
                        if (btn !== undefined) {
 | 
			
		||||
                            $(btn).removeClass('loading').prop('disabled', false);
 | 
			
		||||
                        }
 | 
			
		||||
                        if (data.error != undefined) {
 | 
			
		||||
                            parent.msgbox(data.error, false);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            parent.msgbox("Plugin installed successfully", true);
 | 
			
		||||
                            initStoreList();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            function uninstallPlugin(pluginId) {
 | 
			
		||||
                $.cjax({
 | 
			
		||||
                    url: '/api/plugins/store/uninstall',
 | 
			
		||||
                    type: 'POST',
 | 
			
		||||
                    data: { pluginId },
 | 
			
		||||
                    success: function(data) {
 | 
			
		||||
                        if (data.error != undefined) {
 | 
			
		||||
                            parent.msgbox(data.error, false);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            parent.msgbox("Plugin uninstalled successfully", true);
 | 
			
		||||
                            initStoreList();
 | 
			
		||||
                            //Also reload the parent plugin list
 | 
			
		||||
                            parent.initiatePluginList();
 | 
			
		||||
                        }
 | 
			
		||||
                    },
 | 
			
		||||
                    error: function() {
 | 
			
		||||
                        if (btn !== undefined) {
 | 
			
		||||
                            $(btn).removeClass('loading').prop('disabled', false);
 | 
			
		||||
                        }
 | 
			
		||||
                        parent.msgbox("An error occurred while installing the plugin", false);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user