mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Added wip plugin store
- Added plugin store snippet - Added plugin list sync functions - Work in progress install / uninstall plugin function
This commit is contained in:
parent
36c2c9a00e
commit
6750c7fe3d
@ -234,6 +234,9 @@ func RegisterPluginAPIs(authRouter *auth.RouterDef) {
|
|||||||
authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup)
|
authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup)
|
||||||
authRouter.HandleFunc("/api/plugins/groups/remove", pluginManager.HandleRemovePluginFromGroup)
|
authRouter.HandleFunc("/api/plugins/groups/remove", pluginManager.HandleRemovePluginFromGroup)
|
||||||
authRouter.HandleFunc("/api/plugins/groups/deleteTag", pluginManager.HandleRemovePluginGroup)
|
authRouter.HandleFunc("/api/plugins/groups/deleteTag", pluginManager.HandleRemovePluginGroup)
|
||||||
|
|
||||||
|
authRouter.HandleFunc("/api/plugins/store/list", pluginManager.HandleListDownloadablePlugins)
|
||||||
|
authRouter.HandleFunc("/api/plugins/store/resync", pluginManager.HandleResyncPluginList)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -249,3 +249,5 @@ func (m *Manager) HandleDisablePlugin(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
utils.SendOK(w)
|
utils.SendOK(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Plugin Store */
|
||||||
|
@ -82,7 +82,7 @@ func (m *Manager) LoadPluginsFromDisk() error {
|
|||||||
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
|
m.Log("Loaded plugin: "+thisPlugin.Spec.Name, nil)
|
||||||
|
|
||||||
// If the plugin was enabled, start it now
|
// If the plugin was enabled, start it now
|
||||||
fmt.Println("Plugin enabled state", m.GetPluginPreviousEnableState(thisPlugin.Spec.ID))
|
//fmt.Println("Plugin enabled state", m.GetPluginPreviousEnableState(thisPlugin.Spec.ID))
|
||||||
if m.GetPluginPreviousEnableState(thisPlugin.Spec.ID) {
|
if m.GetPluginPreviousEnableState(thisPlugin.Spec.ID) {
|
||||||
err = m.StartPlugin(thisPlugin.Spec.ID)
|
err = m.StartPlugin(thisPlugin.Spec.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
118
src/mod/plugins/store.go
Normal file
118
src/mod/plugins/store.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/plugins/zoraxy_plugin"
|
||||||
|
"imuslab.com/zoraxy/mod/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Plugin Store
|
||||||
|
*/
|
||||||
|
|
||||||
|
// See https://github.com/aroz-online/zoraxy-official-plugins/blob/main/directories/index.json for the standard format
|
||||||
|
|
||||||
|
type Checksums struct {
|
||||||
|
LinuxAmd64 string `json:"linux_amd64"`
|
||||||
|
Linux386 string `json:"linux_386"`
|
||||||
|
LinuxArm string `json:"linux_arm"`
|
||||||
|
LinuxArm64 string `json:"linux_arm64"`
|
||||||
|
LinuxMipsle string `json:"linux_mipsle"`
|
||||||
|
LinuxRiscv64 string `json:"linux_riscv64"`
|
||||||
|
WindowsAmd64 string `json:"windows_amd64"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DownloadablePlugin struct {
|
||||||
|
IconPath string
|
||||||
|
PluginIntroSpect zoraxy_plugin.IntroSpect //Plugin introspect information
|
||||||
|
ChecksumsSHA256 Checksums //Checksums for the plugin binary
|
||||||
|
DownloadURLs map[string]string //Download URLs for different platforms
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Store Index List Sync */
|
||||||
|
//Update the plugin list from the plugin store URLs
|
||||||
|
func (m *Manager) UpdateDownloadablePluginList() error {
|
||||||
|
//Get downloadable plugins from each of the plugin store URLS
|
||||||
|
m.Options.DownloadablePluginCache = []*DownloadablePlugin{}
|
||||||
|
for _, url := range m.Options.PluginStoreURLs {
|
||||||
|
pluginList, err := m.getPluginListFromURL(url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get plugin list from %s: %w", url, err)
|
||||||
|
}
|
||||||
|
m.Options.DownloadablePluginCache = append(m.Options.DownloadablePluginCache, pluginList...)
|
||||||
|
}
|
||||||
|
|
||||||
|
m.Options.LastSuccPluginSyncTime = time.Now().Unix()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the plugin list from the URL
|
||||||
|
func (m *Manager) getPluginListFromURL(url string) ([]*DownloadablePlugin, error) {
|
||||||
|
//Get the plugin list from the URL
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("failed to get plugin list from %s: %s", url, resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginList []*DownloadablePlugin
|
||||||
|
content, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read plugin list from %s: %w", url, err)
|
||||||
|
}
|
||||||
|
content = []byte(strings.TrimSpace(string(content)))
|
||||||
|
|
||||||
|
err = json.Unmarshal(content, &pluginList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal plugin list from %s: %w", url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) ListDownloadablePlugins() []*DownloadablePlugin {
|
||||||
|
//List all downloadable plugins
|
||||||
|
if len(m.Options.DownloadablePluginCache) == 0 {
|
||||||
|
return []*DownloadablePlugin{}
|
||||||
|
}
|
||||||
|
return m.Options.DownloadablePluginCache
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handlers for Plugin Store
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (m *Manager) HandleListDownloadablePlugins(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//List all downloadable plugins
|
||||||
|
plugins := m.ListDownloadablePlugins()
|
||||||
|
js, _ := json.Marshal(plugins)
|
||||||
|
utils.SendJSONResponse(w, string(js))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleResyncPluginList is the handler for resyncing the plugin list from the plugin store URLs
|
||||||
|
func (m *Manager) HandleResyncPluginList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
//Make sure this function require csrf token
|
||||||
|
utils.SendErrorResponse(w, "Method not allowed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Resync the plugin list from the plugin store URLs
|
||||||
|
err := m.UpdateDownloadablePluginList()
|
||||||
|
if err != nil {
|
||||||
|
utils.SendErrorResponse(w, "Failed to resync plugin list: "+err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.SendOK(w)
|
||||||
|
}
|
52
src/mod/plugins/store_test.go
Normal file
52
src/mod/plugins/store_test.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package plugins
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateDownloadablePluginList(t *testing.T) {
|
||||||
|
mockManager := &Manager{
|
||||||
|
Options: &ManagerOptions{
|
||||||
|
DownloadablePluginCache: []*DownloadablePlugin{},
|
||||||
|
PluginStoreURLs: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
//Inject a mock URL for testing
|
||||||
|
mockManager.Options.PluginStoreURLs = []string{"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json"}
|
||||||
|
|
||||||
|
err := mockManager.UpdateDownloadablePluginList()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(mockManager.Options.DownloadablePluginCache) == 0 {
|
||||||
|
t.Fatalf("expected plugin cache to be updated, but it was empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mockManager.Options.LastSuccPluginSyncTime == 0 {
|
||||||
|
t.Fatalf("expected LastSuccPluginSyncTime to be updated, but it was not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetPluginListFromURL(t *testing.T) {
|
||||||
|
mockManager := &Manager{
|
||||||
|
Options: &ManagerOptions{
|
||||||
|
DownloadablePluginCache: []*DownloadablePlugin{},
|
||||||
|
PluginStoreURLs: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginList, err := mockManager.getPluginListFromURL("https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("expected no error, got %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pluginList) == 0 {
|
||||||
|
t.Fatalf("expected plugin list to be populated, but it was empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, plugin := range pluginList {
|
||||||
|
t.Logf("Plugin: %+v", plugin)
|
||||||
|
}
|
||||||
|
}
|
@ -29,10 +29,16 @@ type Plugin struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ManagerOptions struct {
|
type ManagerOptions struct {
|
||||||
|
/* Plugins */
|
||||||
PluginDir string //The directory where the plugins are stored
|
PluginDir string //The directory where the plugins are stored
|
||||||
PluginGroups map[string][]string //The plugin groups,key is the tag name and the value is an array of plugin IDs
|
PluginGroups map[string][]string //The plugin groups,key is the tag name and the value is an array of plugin IDs
|
||||||
PluginGroupsConfig string //The group / tag configuration file, if set the plugin groups will be loaded from this file
|
PluginGroupsConfig string //The group / tag configuration file, if set the plugin groups will be loaded from this file
|
||||||
|
|
||||||
|
/* Plugin Downloader */
|
||||||
|
PluginStoreURLs []string //The plugin store URLs, used to download the plugins
|
||||||
|
DownloadablePluginCache []*DownloadablePlugin //The cache for the downloadable plugins, key is the plugin ID and value is the DownloadablePlugin struct
|
||||||
|
LastSuccPluginSyncTime int64 //The last sync time for the plugin store URLs, used to check if the plugin store URLs need to be synced again
|
||||||
|
|
||||||
/* Runtime */
|
/* Runtime */
|
||||||
SystemConst *zoraxyPlugin.RuntimeConstantValue //The system constant value
|
SystemConst *zoraxyPlugin.RuntimeConstantValue //The system constant value
|
||||||
CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function
|
CSRFTokenGen func(*http.Request) string `json:"-"` //The CSRF token generator function
|
||||||
|
18
src/start.go
18
src/start.go
@ -1,7 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"imuslab.com/zoraxy/mod/auth/sso/authentik"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -10,6 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"imuslab.com/zoraxy/mod/auth/sso/authentik"
|
||||||
|
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
"imuslab.com/zoraxy/mod/access"
|
"imuslab.com/zoraxy/mod/access"
|
||||||
"imuslab.com/zoraxy/mod/acme"
|
"imuslab.com/zoraxy/mod/acme"
|
||||||
@ -322,6 +323,9 @@ func startupSequence() {
|
|||||||
ZoraxyUUID: nodeUUID,
|
ZoraxyUUID: nodeUUID,
|
||||||
DevelopmentBuild: DEVELOPMENT_BUILD,
|
DevelopmentBuild: DEVELOPMENT_BUILD,
|
||||||
},
|
},
|
||||||
|
PluginStoreURLs: []string{
|
||||||
|
"https://raw.githubusercontent.com/aroz-online/zoraxy-official-plugins/refs/heads/main/directories/index.json",
|
||||||
|
},
|
||||||
Database: sysdb,
|
Database: sysdb,
|
||||||
Logger: SystemWideLogger,
|
Logger: SystemWideLogger,
|
||||||
PluginGroupsConfig: CONF_PLUGIN_GROUPS,
|
PluginGroupsConfig: CONF_PLUGIN_GROUPS,
|
||||||
@ -330,9 +334,19 @@ func startupSequence() {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//Sync latest plugin list from the plugin store
|
||||||
|
go func() {
|
||||||
|
err = pluginManager.UpdateDownloadablePluginList()
|
||||||
|
if err != nil {
|
||||||
|
SystemWideLogger.PrintAndLog("plugin-manager", "Failed to sync plugin list from plugin store", err)
|
||||||
|
} else {
|
||||||
|
SystemWideLogger.PrintAndLog("plugin-manager", "Plugin list synced from plugin store", nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
err = pluginManager.LoadPluginsFromDisk()
|
err = pluginManager.LoadPluginsFromDisk()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
SystemWideLogger.PrintAndLog("Plugin Manager", "Failed to load plugins", err)
|
SystemWideLogger.PrintAndLog("plugin-manager", "Failed to load plugins", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Docker UX Optimizer */
|
/* Docker UX Optimizer */
|
||||||
|
@ -185,6 +185,8 @@
|
|||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<br>
|
||||||
|
<button class="ui violet button" onclick="openPluginStore();"><i class="cart arrow down icon"></i>Get More Plugins!</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -563,6 +565,11 @@ function getPluginInfo(pluginId, btn){
|
|||||||
showSideWrapper("snippet/pluginInfo.html?t=" + Date.now() + "#" + payload);
|
showSideWrapper("snippet/pluginInfo.html?t=" + Date.now() + "#" + payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openPluginStore(){
|
||||||
|
//Open plugin store in extended mode
|
||||||
|
showSideWrapper("snippet/pluginstore.html?t=" + Date.now(), true);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
@ -197,6 +197,12 @@ body.darkTheme .menubar{
|
|||||||
max-width: calc(80% - 1em);
|
max-width: calc(80% - 1em);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 478px) {
|
||||||
|
.sideWrapper.extendedMode {
|
||||||
|
max-width: calc(100% - 1em);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sideWrapper .content{
|
.sideWrapper .content{
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
258
src/web/snippet/pluginstore.html
Normal file
258
src/web/snippet/pluginstore.html
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<!-- Notes: This should be open in its original path-->
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
|
||||||
|
<title>Plugin Store</title>
|
||||||
|
<link rel="stylesheet" href="../script/semantic/semantic.min.css">
|
||||||
|
<script src="../script/jquery-3.6.0.min.js"></script>
|
||||||
|
<script src="../script/semantic/semantic.min.js"></script>
|
||||||
|
<script src="../script/utils.js"></script>
|
||||||
|
<style>
|
||||||
|
#pluginList{
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
height: 500px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.darkTheme #pluginList .header{
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.installablePlugin{
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.installablePlugin .action{
|
||||||
|
position: absolute;
|
||||||
|
top: 0.4em;
|
||||||
|
right: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
#pluginList .item .image {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<link rel="stylesheet" href="../darktheme.css">
|
||||||
|
<script src="../script/darktheme.js"></script>
|
||||||
|
<br>
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui fluid search">
|
||||||
|
<div class="ui fluid icon input">
|
||||||
|
<input id="searchInput" class="prompt" type="text" placeholder="Search plugins">
|
||||||
|
<i class="search icon"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divided items" id="pluginList">
|
||||||
|
|
||||||
|
</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 basic segment advanceoptions">
|
||||||
|
<div class="ui accordion advanceSettings">
|
||||||
|
<div class="title">
|
||||||
|
<i class="dropdown icon"></i>
|
||||||
|
Advance Settings
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<p>Plugin Store URLs</p>
|
||||||
|
<div class="ui form">
|
||||||
|
<div class="field">
|
||||||
|
<textarea id="pluginStoreURLs" rows="5"></textarea>
|
||||||
|
<label>Enter plugin store URLs, separating each URL with a new line</label>
|
||||||
|
</div>
|
||||||
|
<button class="ui basic button" onclick="savePluginStoreURLs()">
|
||||||
|
<i class="ui green save icon"></i>Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
<div class="field" >
|
||||||
|
<button class="ui basic button" style="float: right;" onclick="closeThisWrapper();">Close</button>
|
||||||
|
</div>
|
||||||
|
<br><br><br><br>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
let availablePlugins = [];
|
||||||
|
let installedPlugins = [];
|
||||||
|
$(".accordion").accordion();
|
||||||
|
|
||||||
|
function initStoreList(){
|
||||||
|
$.get("/api/plugins/list", function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
return;
|
||||||
|
}else{
|
||||||
|
installedPlugins = data || [];
|
||||||
|
console.log(installedPlugins);
|
||||||
|
}
|
||||||
|
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/store/list',
|
||||||
|
type: 'GET',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
}else{
|
||||||
|
availablePlugins = data || [];
|
||||||
|
populatePluginList(availablePlugins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
initStoreList();
|
||||||
|
|
||||||
|
/* Plugin Search */
|
||||||
|
function searchPlugins() {
|
||||||
|
const query = document.getElementById('searchInput').value.toLowerCase();
|
||||||
|
const items = document.querySelectorAll('#pluginList .item');
|
||||||
|
if (query.trim() === '') {
|
||||||
|
items.forEach(item => {
|
||||||
|
item.style.display = '';
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
items.forEach(item => {
|
||||||
|
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();
|
||||||
|
|
||||||
|
if (name.includes(query) || description.includes(query) || author.includes(query) || id.includes(query)) {
|
||||||
|
item.style.display = '';
|
||||||
|
} else {
|
||||||
|
item.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//Bind search function to input field and Enter key
|
||||||
|
document.getElementById('searchInput').addEventListener('input', searchPlugins);
|
||||||
|
document.getElementById('searchInput').addEventListener('keydown', function(event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
searchPlugins();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function forceResyncPlugins() {
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/store/resync',
|
||||||
|
type: 'POST',
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
} else {
|
||||||
|
parent.msgbox("Plugin list updated successfully", true);
|
||||||
|
initStoreList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Store */
|
||||||
|
function populatePluginList(plugins) {
|
||||||
|
const pluginList = document.getElementById('pluginList');
|
||||||
|
pluginList.innerHTML = ''; // Clear existing items
|
||||||
|
plugins.forEach(plugin => {
|
||||||
|
console.log(plugin);
|
||||||
|
let thisPluginIsInstalled = false;
|
||||||
|
installedPlugins.forEach(installedPlugin => {
|
||||||
|
if (installedPlugin.Spec.id == plugin.PluginIntroSpect.id) {
|
||||||
|
thisPluginIsInstalled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const item = `
|
||||||
|
<div class="item installablePlugin" plugin_id="${plugin.PluginIntroSpect.id}">
|
||||||
|
<div class="ui tiny image">
|
||||||
|
<img src="${plugin.IconPath}" alt="${plugin.PluginIntroSpect.name}">
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">${plugin.PluginIntroSpect.name}</div>
|
||||||
|
<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>
|
||||||
|
</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>`}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
$('#pluginList').append(item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin Actions */
|
||||||
|
function installPlugin(pluginId) {
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/store/install',
|
||||||
|
type: 'POST',
|
||||||
|
data: { pluginId },
|
||||||
|
success: function(data) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeThisWrapper(){
|
||||||
|
parent.hideSideWrapper(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
function savePluginManagerURLs() {
|
||||||
|
const urls = document.getElementById('pluginStoreURLs').value.split('\n').map(url => url.trim()).filter(url => url !== '');
|
||||||
|
console.log('Saving URLs:', urls);
|
||||||
|
// Add your logic to save the URLs here, e.g., send them to the server
|
||||||
|
$.cjax({
|
||||||
|
url: '/api/plugins/store/saveURLs',
|
||||||
|
type: 'POST',
|
||||||
|
data: { urls },
|
||||||
|
success: function(data) {
|
||||||
|
if (data.error != undefined) {
|
||||||
|
parent.msgbox(data.error, false);
|
||||||
|
} else {
|
||||||
|
parent.msgbox("URLs saved successfully", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user