diff --git a/src/mod/plugins/forwarder.go b/src/mod/plugins/forwarder.go new file mode 100644 index 0000000..5089b27 --- /dev/null +++ b/src/mod/plugins/forwarder.go @@ -0,0 +1,26 @@ +package plugins + +import "net/http" + +/* + Forwarder.go + + This file handles the dynamic proxy routing forwarding + request to plugin capture path that handles the matching + request path registered when the plugin started +*/ + +func (m *Manager) GetHandlerPlugins(w http.ResponseWriter, r *http.Request) { + +} + +func (m *Manager) GetHandlerPluginsSubsets(w http.ResponseWriter, r *http.Request) { + +} + +func (p *Plugin) HandlePluginRoute(w http.ResponseWriter, r *http.Request) { + //Find the plugin that matches the request path + //If no plugin found, return 404 + //If found, forward the request to the plugin + +} diff --git a/src/mod/plugins/handler.go b/src/mod/plugins/handler.go index 0795313..fb1a651 100644 --- a/src/mod/plugins/handler.go +++ b/src/mod/plugins/handler.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" "path/filepath" + "sort" "time" "imuslab.com/zoraxy/mod/utils" @@ -18,6 +19,11 @@ func (m *Manager) HandleListPlugins(w http.ResponseWriter, r *http.Request) { return } + //Sort the plugin by its name + sort.Slice(plugins, func(i, j int) bool { + return plugins[i].Spec.Name < plugins[j].Spec.Name + }) + js, err := json.Marshal(plugins) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/src/mod/plugins/plugins.go b/src/mod/plugins/plugins.go index 6353ad1..cc51cd0 100644 --- a/src/mod/plugins/plugins.go +++ b/src/mod/plugins/plugins.go @@ -1,49 +1,23 @@ package plugins +/* + Zoraxy Plugin Manager + + This module is responsible for managing plugins + loading plugins from the disk + enable / disable plugins + and forwarding traffic to plugins +*/ + import ( "errors" - "net/http" "os" - "os/exec" "path/filepath" "sync" - _ "embed" - - "imuslab.com/zoraxy/mod/database" - "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" - "imuslab.com/zoraxy/mod/info/logger" - zoraxyPlugin "imuslab.com/zoraxy/mod/plugins/zoraxy_plugin" "imuslab.com/zoraxy/mod/utils" ) -type Plugin struct { - RootDir string //The root directory of the plugin - Spec *zoraxyPlugin.IntroSpect //The plugin specification - Enabled bool //Whether the plugin is enabled - - //Runtime - AssignedPort int //The assigned port for the plugin - uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI - process *exec.Cmd //The process of the plugin -} - -type ManagerOptions struct { - PluginDir string - SystemConst *zoraxyPlugin.RuntimeConstantValue - Database *database.Database - Logger *logger.Logger - CSRFTokenGen func(*http.Request) string //The CSRF token generator function -} - -type Manager struct { - LoadedPlugins sync.Map //Storing *Plugin - Options *ManagerOptions -} - -//go:embed no_img.png -var noImg []byte - // NewPluginManager creates a new plugin manager func NewPluginManager(options *ManagerOptions) *Manager { //Create plugin directory if not exists diff --git a/src/mod/plugins/typdef.go b/src/mod/plugins/typdef.go new file mode 100644 index 0000000..240742b --- /dev/null +++ b/src/mod/plugins/typdef.go @@ -0,0 +1,40 @@ +package plugins + +import ( + _ "embed" + "net/http" + "os/exec" + "sync" + + "imuslab.com/zoraxy/mod/database" + "imuslab.com/zoraxy/mod/dynamicproxy/dpcore" + "imuslab.com/zoraxy/mod/info/logger" + zoraxyPlugin "imuslab.com/zoraxy/mod/plugins/zoraxy_plugin" +) + +//go:embed no_img.png +var noImg []byte + +type Plugin struct { + RootDir string //The root directory of the plugin + Spec *zoraxyPlugin.IntroSpect //The plugin specification + Enabled bool //Whether the plugin is enabled + + //Runtime + AssignedPort int //The assigned port for the plugin + uiProxy *dpcore.ReverseProxy //The reverse proxy for the plugin UI + process *exec.Cmd //The process of the plugin +} + +type ManagerOptions struct { + PluginDir string + SystemConst *zoraxyPlugin.RuntimeConstantValue + Database *database.Database + Logger *logger.Logger + CSRFTokenGen func(*http.Request) string //The CSRF token generator function +} + +type Manager struct { + LoadedPlugins sync.Map //Storing *Plugin + Options *ManagerOptions +} diff --git a/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go b/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go index 1691591..f3865ea 100644 --- a/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go +++ b/src/mod/plugins/zoraxy_plugin/zoraxy_plugin.go @@ -79,9 +79,8 @@ type IntroSpect struct { Once plugin is enabled these rules always applies, no matter which HTTP Proxy rule it is enabled on This captures the whole traffic of Zoraxy - Notes: Will raise a warning on the UI when the user enables the plugin on a HTTP Proxy rule */ - GlobalCapturePath []CaptureRule `json:"global_capture_path"` //Global traffic capture path of your plugin + GlobalCapturePaths []CaptureRule `json:"global_capture_path"` //Global traffic capture path of your plugin GlobalCaptureIngress string `json:"global_capture_ingress"` //Global traffic capture ingress path of your plugin (e.g. /g_handler) /* @@ -90,20 +89,9 @@ type IntroSpect struct { Once the plugin is enabled on a given HTTP Proxy rule, these always applies */ - AlwaysCapturePath []CaptureRule `json:"always_capture_path"` //Always capture path of your plugin when enabled on a HTTP Proxy rule (e.g. /myapp) + AlwaysCapturePaths []CaptureRule `json:"always_capture_path"` //Always capture path of your plugin when enabled on a HTTP Proxy rule (e.g. /myapp) AlwaysCaptureIngress string `json:"always_capture_ingress"` //Always capture ingress path of your plugin when enabled on a HTTP Proxy rule (e.g. /a_handler) - /* - Dynamic Capture Settings - - Once the plugin is enabled on a given HTTP Proxy rule, - the plugin can capture the request and decided if the request - shall be handled by itself or let it pass through - - */ - DynmaicCaptureIngress string `json:"capture_path"` //Traffic capture path of your plugin (e.g. /capture) - DynamicHandleIngress string `json:"handle_path"` //Traffic handle path of your plugin (e.g. /handler) - /* UI Path for your plugin */ UIPath string `json:"ui_path"` //UI path of your plugin (e.g. /ui), will proxy the whole subpath tree to Zoraxy Web UI as plugin UI diff --git a/src/web/components/gan.html b/src/web/components/gan.html index 12402fa..4c89b49 100644 --- a/src/web/components/gan.html +++ b/src/web/components/gan.html @@ -3,6 +3,10 @@

Global Area Network

Virtual Network Hub that allows all networked devices to communicate as if they all reside in the same physical data center or cloud region

+
+ Deprecation Notice +

Global Area Network will be deprecating in v3.2.x and moved to Plugin

+
diff --git a/src/web/components/plugincontext.html b/src/web/components/plugincontext.html new file mode 100644 index 0000000..3e49a52 --- /dev/null +++ b/src/web/components/plugincontext.html @@ -0,0 +1,52 @@ +
+ +
+ \ No newline at end of file diff --git a/src/web/components/plugins.html b/src/web/components/plugins.html index a509beb..e230553 100644 --- a/src/web/components/plugins.html +++ b/src/web/components/plugins.html @@ -1,7 +1,11 @@

Plugins

-

Custom features on Zoraxy

+

Add custom features to your Zoraxy!

+
+
+
Experimental Feature
+

This feature is experimental and may not work as expected. Use with caution.

@@ -19,6 +23,29 @@ diff --git a/src/web/index.html b/src/web/index.html index 3975c15..9498308 100644 --- a/src/web/index.html +++ b/src/web/index.html @@ -78,9 +78,6 @@ SSO / Oauth - - Plugins - Static Web Server @@ -96,6 +93,15 @@ Utilities + + Plugins Manager + + + + + No Installed Plugins + + @@ -155,6 +161,12 @@
+ + +
+ + + @@ -246,7 +258,26 @@ if (window.location.hash.length > 1){ let tabID = window.location.hash.substr(1); - openTabById(tabID); + if (tabID.startsWith("{")) { + tabID = decodeURIComponent(tabID); + //Zoraxy v3.2.x plugin context window + try { + let parsedData = JSON.parse(tabID); + tabID = parsedData.tabID; + + //Open the plugin context window + if (tabID == "pluginContextWindow"){ + let pluginID = parsedData.pluginID; + let button = $("#pluginMenu").find(`[pluginid="${pluginID}"]`); + openTabById(tabID, button); + loadPluginUIContextIfAvailable(); + } + } catch (e) { + console.error("Invalid JSON data:", e); + } + }else{ + openTabById(tabID); + } }else{ openTabById("status"); } @@ -257,7 +288,7 @@ $("#mainmenu").find(".item").each(function(){ $(this).on("click", function(event){ let tabid = $(this).attr("tag"); - openTabById(tabid); + openTabById(tabid, $(this)); }); }); @@ -282,13 +313,19 @@ if ($(".sideWrapper").is(":visible")){ $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(false); } + + if ($("#pluginContextLoader").is(":visible")){ + $("#pluginContextLoader")[0].contentWindow.setDarkTheme(false); + } }else{ setDarkTheme(true); //Check if the snippet iframe is opened. If yes, set the dark theme to the iframe if ($(".sideWrapper").is(":visible")){ $(".sideWrapper iframe")[0].contentWindow.setDarkTheme(true); } - + if ($("#pluginContextLoader").is(":visible")){ + $("#pluginContextLoader")[0].contentWindow.setDarkTheme(true); + } } } @@ -307,8 +344,12 @@ //Select and open a tab by its tag id let tabSwitchEventBind = {}; //Bind event to tab switch by tabid - function openTabById(tabID){ - let targetBtn = getTabButtonById(tabID); + function openTabById(tabID, object=undefined){ + let targetBtn = object; + if (object == undefined){ + //Search tab by its tap id + targetBtn = getTabButtonById(tabID); + } if (targetBtn == undefined){ alert("Invalid tabid given"); return; @@ -329,7 +370,19 @@ },100) }); $('html,body').animate({scrollTop: 0}, 'fast'); - window.location.hash = tabID; + + if (tabID == "pluginContextWindow"){ + let statePayload = { + tabID: tabID, + pluginID: $(targetBtn).attr("pluginid") + } + + window.location.hash = JSON.stringify(statePayload); + loadPluginUIContextIfAvailable(); + }else{ + window.location.hash = tabID; + } + } $(window).on("resize", function(){