Added more doc

- Added auto reload for doc engine
- Added helloworld example
This commit is contained in:
Toby Chui 2025-05-27 22:00:16 +08:00
parent a85bf82c3e
commit 29daa4402d
37 changed files with 2002 additions and 20 deletions

View File

@ -106,7 +106,6 @@ func optimizeCss(htmlContent []byte) ([]byte, error) {
endIndex += startIndex + 6
codeSegment := originalHTMLContent[startIndex : endIndex+7] // Include </code>
fmt.Println(">>>>", codeSegment)
if !strings.Contains(codeSegment, "class=") {
replacement := strings.Replace(codeSegment, "<code>", "<span class=\"ts-text is-code\">", 1)
replacement = strings.Replace(replacement, "</code>", "</span>", 1)

View File

@ -1,4 +1,9 @@
# What is Zoraxy Plugin?
Last Update: 25/05/2025
---
Zoraxy Plugin is a powerful extension feature designed to enhance the functionality of the Zoraxy system. It provides additional features and capabilities that are not part of the core system, allowing users to customize their experience and optimize performance. The plugin is built to be modular and flexible, enabling users to tailor their Zoraxy environment to meet specific needs.
Zoraxy plugins are distributed as binaries, and developers have the flexibility to choose whether to open source them or not **as the plugin library and interface are open source under the LGPL license**.
@ -7,7 +12,7 @@ There are two primary types of plugins:
- **Router plugins**: Involved with connections from HTTP proxy rules.
- **Utility plugins**: Provide user interfaces for various network features that operate independently of the Zoraxy core.
---
---
## How plugins are distributed & installed
Zoraxy plugins are distributed as platform-dependent binaries, tailored to specific operating systems and CPU architectures. These binaries follow a naming convention that includes the operating system, CPU architecture, and plugin name, such as `linux_amd64_foobar`, `windows_amd64_foobar.exe`, or `linux_arm64_foobar`.
@ -18,7 +23,7 @@ To manually install a plugin for testing, place the binary file into the `/plugi
For distribution, a plugin store system is used. The plugin store architecture is similar to the one built into the Arduino IDE, with a manager URL (a JSON file) listing all the plugins supported by that store. See the documentation section for more details on how to implement your own plugin store.
---
---
## Plugin vs Pull Request
The Zoraxy plugin was introduced to address specific use cases that enhance its functionality. It serves as an extension to the core Zoraxy system, providing additional features and capabilities while maintaining the integrity of the core system.

View File

@ -1,5 +1,9 @@
# Getting Started
Last Update: 25/05/2025
---
To start developing plugins, you will need the following installed on your computer
1. The source code of Zoraxy

View File

@ -0,0 +1,26 @@
# Installing Plugin
Last Update: 25/05/2025
---
### Install via Plugin Store
(Work in progress)
### Manual Install
The plugin shall be placed inside the `plugins/{{plugin_name}}/` directory where the binary executable name must be matching with the plugin name.
If you are on Linux, also make sure Zoraxy have the execution permission of the plugin. You can use the following command to enable execution of the plugin binary on Linux with the current user (Assume Zoraxy is run by the current user)
```bash
cd ./plugins/{{plugin_name}}/
chmod +x ./{{plugin_name}}
```
Sometime plugins might come with additional assets other than the binary file. If that is the case, extract all of the plugins content into the folder with the plugin's name.
After the folder structure is ready, restart Zoraxy. If you are using systemd for Zoraxy, use `sudo systemctl restart zoraxy` to restart Zoraxy via systemd service.

View File

@ -0,0 +1,40 @@
# Enable Plugins
Last Update: 25/05/2025
---
To enable and assign a plugin to a certain HTTP Proxy Rule, you will need to do the following steps
## 1. Create a tag for your HTTP Proxy Rules
Let say you want to enable debugger on some of your HTTP Proxy Rules. You can do that by first creating a tag in the tag editor. In the example below, we will be using the tag "debug". After adding the tag to the HTTP Proxy rule, you will see something like this.
![image-20250527193748456](img/3. Enable Plugins/image-20250527193748456.png)
---
## 2. Enable Plugin
Click on the "Enable" button on the plugin which you want to enable
![image-20250527193601017](img/3. Enable Plugins/image-20250527193601017.png)
---
## 3. Assign Plugin to HTTP Proxy Rule
Finally, select the tag that you just created in the dropdown menu
![image-20250527194052408](img/3. Enable Plugins/image-20250527194052408.png)
Afterward, you will see the plugin is attached to the target tag
![image-20250527195703464](img/3. Enable Plugins/image-20250527195703464.png)
It means the plugin is enabled on the HTTP proxy rule

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,5 +1,9 @@
# Plugin Architecture
Last Update: 25/05/2025
---
The Zoraxy Plugin uses a 3 steps approach to get information from plugin, setup the plugin and forward request to plugin. The name of the steps are partially referred from dbus designs as followings.
1. Introspect
@ -10,3 +14,4 @@ The overall flow looks like this.
![](img/1. Plugin Architecture/plugin_workflow.png)
This design make sure that the Zoraxy plugins do not depends on platform dependent implementations that uses, for example, unix socket. This also avoided protocol that require complex conversion to and from HTTP request (data structure) like gRPC, while making sure the plugin can be cross compile into different CPU architecture or OS environment with little to no effect on its performance.

View File

@ -1,5 +1,9 @@
# Introspect
Last Update: 25/05/2025
---
Introspect, similar to the one in dbus design, is used to get the information from plugin when Zoraxy starts (or manually triggered in development mode or force reload plugin list).
**This is a pre-defined structure where the plugin must provide to Zoraxy** when the plugin is being started with the `-introspect` flag.
@ -55,6 +59,12 @@ type IntroSpect struct {
}
```
The introspect provide Zoraxy the required information to start the plugin and how to interact with it. For more details on what those capture settings are for, see "Capture Mode" section.
---
## Introspect Manual Triggering
To manually test if the introspect return is correct, you can try using the `-introspect` flag on any Zoraxy plugin. You should be able to see an output like so.

View File

@ -0,0 +1,23 @@
# Plugin UI
Last Update: 25/05/2025
---
A plugin can optionally expose a Web UI interface for user configuration.
**It is generally recommended that a plugin have such UI exposed for easy configurations.** As plugin installed via plugin store provides limited ways for a user to configure the plugin, the plugin web UI will be the best way for user to setup your plugin.
## Plugin Web UI Access
If a plugin provide a Web UI endpoint for Zoraxy during the introspect process, a new item will be shown in the Plugins section on Zoraxy side menu. Below is an example of the Web UI of UPnP Port Forwarder plugin.
![image-20250527201750613](img/5. Plugin UI/image-20250527201750613.png)
## Front-end Developer Notes
The Web UI is implemented as a reverse proxy and embed in an iframe. So you do not need to handle CORS issues with the web UI (as it will be proxy internally by Zoraxy as exposed as something like a virtual directory mounted website).
However, the plugin web UI is exposed via the path `/plugin.ui/{{plugin_uuid}}/`, for example, `/plugin.ui/org.aroz.zoraxy.plugins.upnp/`. **When developing the plugin web UI, do not use absolute path for any resources used in the HTML file**, unless you are trying to re-use Zoraxy components like css or image elements stored in Zoraxy embedded web file system (e.g. `/img/logo.svg`).

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,325 @@
# Hello World!
Last Update: 25/05/2025
---
Let start with a really simple Hello World plugin. This only function of this plugin is to print "Hello World" in the plugin web UI.
---
## 1. Name your plugin
First things first, give your plugin a name. In this example, we are using the name "helloworld".
**Plugin name cannot contain space or special characters**, so you must use a file name that satisfy the requirement. No worry, the plugin file name is not the same as the plugin display name in the introspect.
---
## 2. Create the plugin folder
If your zoraxy root folder do not contains a folder named "plugins", it might implies that your Zoraxy is freshly clone from Github. **You will need to build and run it once to start working on your plugin**, so if you have a newly cloned source code of Zoraxy, do the followings.
```bash
git clone https://github.com/tobychui/zoraxy
cd src
go mod tidy
go build
sudo ./zoraxy
```
Afterward, create a plugin folder under your Zoraxy development environment that is exactly matching your plugin name. In the above example, the folder name should be "helloworld".
```bash
# Assume you are already inside the src/ folder
mkdir helloworld
cd ./helloworld
```
---
## 3. Create a go project
Similar to any Go project, you can start by creating a `main.go` file. Next, you would want to let the go compiler knows your plugin name so when generating a binary file, it knows what to name it. This can be done via using the `go mod init` command.
```bash
touch main.go
go mod init example.com/zoraxy/helloworld
ls
# After you are done, you should see the followings
# go.mod main.go
```
---
## 4. Copy the Zoraxy plugin lib from Zoraxy source code
Locate the Zoraxy plugin library from the Zoraxy source code. You can find the `zoraxy_plugin` Go module under `src/mod/plugins/zoraxy_plugin`
Copy the `zoraxy_plugin` folder from the Zoraxy source code mod folder into the your plugins mod folder. Let assume you use the same mod folder name as Zoraxy as `mod`, then your copied library path should be `plugins/helloword/mod/zoraxy_plugin`
```bash
mkdir ./mod
cp -r "mod/plugins/zoraxy_plugin" ./mod/
ls ./mod/zoraxy_plugin/
# You should see something like this (might be different in future versions)
# dev_webserver.go dynamic_router.go embed_webserver.go README.txt static_router.go zoraxy_plugin.go
```
---
## 5. Create a web resources folder
Lets create a www folder and put all our web resources, we need to create an `index.html` file as our plugin web ui homepage. This can be done by creating a HTML file in the www folder.
```bash
# Assuming you are currently in the src/plugins/helloworld/ folder
mkdir www
cd www
touch index.html
```
And here is an example `index.html` file that uses the Zoraxy internal resources like css and dark theme toggle mechanism. That csrf token template is not straightly needed in this example as helloworld plugin do not make any POST request to Zoraxy webmin interface, but it might come in handy later.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- CSRF token, if your plugin need to make POST request to backend -->
<meta name="zoraxy.csrf.Token" content="{{.csrfToken}}">
<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>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/main.css">
<title>Hello World</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background:none;
}
</style>
</head>
<body>
<!-- Dark theme script must be included after body tag-->
<link rel="stylesheet" href="/darktheme.css">
<script src="/script/darktheme.js"></script>
<div style="text-align: center;">
<h1>Hello World</h1>
<p>Welcome to your first Zoraxy plugin</p>
</div>
</body>
</html>
```
---
## 6. Creating a handler for Introspect
To create a handler for introspect, you can first start your plugin with a few constants.
1. Plugin ID, this must be unique. You can use a domain you own like `com.example.helloworld`
2. UI Path, for now we uses "/" as this plugin do not have any other endpoints, so we can use the whole root just for web UI
3. Web root, for trimming off from the embedded web folder so when user can visit your `index.html` by accessing `/` instead of needing to navigate to `/www`
After you have defined these constant, we can use `plugin.ServeAndRecvSpec` function to handle the handshake between Zoraxy and your plugin.
```go
const (
PLUGIN_ID = "com.example.helloworld"
UI_PATH = "/"
WEB_ROOT = "/www"
)
func main(){
runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
ID: "com.example.helloworld",
Name: "Hello World Plugin",
Author: "foobar",
AuthorContact: "admin@example.com",
Description: "A simple hello world plugin",
URL: "https://example.com",
Type: plugin.PluginType_Utilities,
VersionMajor: 1,
VersionMinor: 0,
VersionPatch: 0,
// As this is a utility plugin, we don't need to capture any traffic
// but only serve the UI, so we set the UI (relative to the plugin path) to "/"
UIPath: UI_PATH,
})
if err != nil {
//Terminate or enter standalone mode here
panic(err)
}
}
```
**Notes: If some post processing is needed between Introspect and Configure, you can use two seperate function to handle the first start and the second starting of your plugin. The "seperated version" of `ServeAndRecvSpec` is defined as ` ServeIntroSpect(pluginSpect *IntroSpect) ` and `RecvConfigureSpec() (*ConfigureSpec, error)`. See `zoraxy_plugin.go` for more information.**
---
## 7. Creating a web server from embedded web fs
After that, we need to create a web server to serve our plugin UI to Zoraxy via HTTP. This can be done via the `http.FileServer` but for simplicity and ease of upgrade, the Zoraxy plugin library provided an easy to use embedded web FS server API for plugin developers.
To use the Zoraxy plugin embedded web server, you first need to embed your web fs into Zoraxy as such.
```go
import (
_ "embed"
"fmt"
plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin"
)
//go:embed www/*
var content embed.FS
```
Then call to the `NewPluginEmbedUIRouter` to create a new UI router from the embedded Fs.
```go
// Create a new PluginEmbedUIRouter that will serve the UI from web folder
// The router will also help to handle the termination of the plugin when
// a user wants to stop the plugin via Zoraxy Web UI
embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH)
```
Here is the tricky part. since not all platform support cross process signaling, Zoraxy plugin uses HTTP request to request a plugin to shutdown. The `embedWebRouter` object has a function named `RegisterTerminateHandler` where you can easily use this function to register actions that needed to be done before shutdown.
```go
embedWebRouter.RegisterTerminateHandler(func() {
// Do cleanup here if needed
fmt.Println("Hello World Plugin Exited")
}, nil)
```
**Notes: This is a blocking function. That is why Zoraxy has a build-in timeout context where if the terminate request takes more than 3 seconds, the plugin process will be treated as "freezed" and forcefully terminated. So please make sure the terminate handler complete its shutdown procedures within 3 seconds.**
---
## 8. Register & Serve the Web UI
After you have created a embedded web router, you can register it to the UI PATH as follows.
```go
// Serve the hello world page in the www folder
http.Handle(UI_PATH, embedWebRouter.Handler())
fmt.Println("Hello World started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
if err != nil {
panic(err)
}
```
As this is just the standard golang net/http package, you can of course add more Function Handlers to it based on your needs. There are something that you need to know about adding API endpoints, we will discuss this in the later sections.
---
## 9. Build and Test
After saving the `main.go` file, you can now build your plugin with `go build`. It should generate the plugin in your platform architecture and OS. If you are on Linux, it will be `helloworld` and if you are on Windows, it will be `helloworld.exe`.
After you are done, restart Zoraxy and enable your plugin in the Plugin List. Now you can test and debug your plugin with your HTTP Proxy Rules. All the STDOUT and STDERR of your plugin will be forwarded to the STDOUT of Zoraxy as well as the log file.
**Tips**
You can also enable the Developer Option - Plugin Auto Reload function if you are too lazy to restart Zoraxy everytime the plugin binary changed.
![image-20250527210849767](img/1. Hello World/image-20250527210849767.png)
---
## 10. Full Code
This is the full code of the helloworld plugin main.go file.
```go
package main
import (
"embed"
_ "embed"
"fmt"
"net/http"
"strconv"
plugin "example.com/zoraxy/helloworld/mod/zoraxy_plugin"
)
const (
PLUGIN_ID = "com.example.helloworld"
UI_PATH = "/"
WEB_ROOT = "/www"
)
//go:embed www/*
var content embed.FS
func main() {
// Serve the plugin intro spect
// This will print the plugin intro spect and exit if the -introspect flag is provided
runtimeCfg, err := plugin.ServeAndRecvSpec(&plugin.IntroSpect{
ID: "com.example.helloworld",
Name: "Hello World Plugin",
Author: "foobar",
AuthorContact: "admin@example.com",
Description: "A simple hello world plugin",
URL: "https://example.com",
Type: plugin.PluginType_Utilities,
VersionMajor: 1,
VersionMinor: 0,
VersionPatch: 0,
// As this is a utility plugin, we don't need to capture any traffic
// but only serve the UI, so we set the UI (relative to the plugin path) to "/"
UIPath: UI_PATH,
})
if err != nil {
//Terminate or enter standalone mode here
panic(err)
}
// Create a new PluginEmbedUIRouter that will serve the UI from web folder
// The router will also help to handle the termination of the plugin when
// a user wants to stop the plugin via Zoraxy Web UI
embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &content, WEB_ROOT, UI_PATH)
embedWebRouter.RegisterTerminateHandler(func() {
// Do cleanup here if needed
fmt.Println("Hello World Plugin Exited")
}, nil)
// Serve the hello world page in the www folder
http.Handle(UI_PATH, embedWebRouter.Handler())
fmt.Println("Hello World started at http://127.0.0.1:" + strconv.Itoa(runtimeCfg.Port))
err = http.ListenAndServe("127.0.0.1:"+strconv.Itoa(runtimeCfg.Port), nil)
if err != nil {
panic(err)
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -3,9 +3,14 @@ module imuslab.com/zoraxy/docs
go 1.24.1
require (
github.com/PuerkitoBio/goquery v1.10.3 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b // indirect
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect
golang.org/x/net v0.40.0 // indirect
github.com/PuerkitoBio/goquery v1.10.3
github.com/fsnotify/fsnotify v1.9.0
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4
)
require (
github.com/andybalholm/cascadia v1.3.3 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
)

View File

@ -2,6 +2,8 @@ github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiU
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -48,6 +50,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

View File

@ -95,6 +95,12 @@
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
@ -130,6 +144,12 @@
<h1 id="what-is-zoraxy-plugin">
What is Zoraxy Plugin?
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
<p class="ts-text">
Zoraxy Plugin is a powerful extension feature designed to enhance the functionality of the Zoraxy system. It provides additional features and capabilities that are not part of the core system, allowing users to customize their experience and optimize performance. The plugin is built to be modular and flexible, enabling users to tailor their Zoraxy environment to meet specific needs.

View File

@ -95,6 +95,12 @@
<a class="item is-active" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
@ -130,6 +144,12 @@
<h1 id="getting-started">
Getting Started
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
<p class="ts-text">
To start developing plugins, you will need the following installed on your computer

View File

@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Installing Plugin | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item is-active" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="installing-plugin">
Installing Plugin
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h3 id="install-via-plugin-store">
Install via Plugin Store
</h3>
<p>
<p class="ts-text">
(Work in progress)
</p>
</p>
<h3 id="manual-install">
Manual Install
</h3>
<p>
<p class="ts-text">
The plugin shall be placed inside the
<span class="ts-text is-code">
plugins/{{plugin_name}}/
</span>
directory where the binary executable name must be matching with the plugin name.
</p>
</p>
<p>
<p class="ts-text">
If you are on Linux, also make sure Zoraxy have the execution permission of the plugin. You can use the following command to enable execution of the plugin binary on Linux with the current user (Assume Zoraxy is run by the current user)
</p>
</p>
<pre><code class="language-bash">cd ./plugins/{{plugin_name}}/
chmod +x ./{{plugin_name}}
</code></pre>
<p>
Sometime plugins might come with additional assets other than the binary file. If that is the case, extract all of the plugins content into the folder with the plugin&rsquo;s name.
</p>
<p>
<p class="ts-text">
After the folder structure is ready, restart Zoraxy. If you are using systemd for Zoraxy, use
<span class="ts-text is-code">
sudo systemctl restart zoraxy
</span>
to restart Zoraxy via systemd service.
</p>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

View File

@ -0,0 +1,233 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Enable Plugins | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item is-active" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="enable-plugins">
Enable Plugins
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
<p class="ts-text">
To enable and assign a plugin to a certain HTTP Proxy Rule, you will need to do the following steps
</p>
</p>
<h2 id="1-create-a-tag-for-your-http-proxy-rules">
1. Create a tag for your HTTP Proxy Rules
</h2>
<p>
Let say you want to enable debugger on some of your HTTP Proxy Rules. You can do that by first creating a tag in the tag editor. In the example below, we will be using the tag &ldquo;debug&rdquo;. After adding the tag to the HTTP Proxy rule, you will see something like this.
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/3. Enable Plugins/image-20250527193748456.png" alt="image-20250527193748456" />
</div>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="2-enable-plugin">
2. Enable Plugin
</h2>
<p>
Click on the &ldquo;Enable&rdquo; button on the plugin which you want to enable
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/3. Enable Plugins/image-20250527193601017.png" alt="image-20250527193601017" />
</div>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="3-assign-plugin-to-http-proxy-rule">
3. Assign Plugin to HTTP Proxy Rule
</h2>
<p>
<p class="ts-text">
Finally, select the tag that you just created in the dropdown menu
</p>
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/3. Enable Plugins/image-20250527194052408.png" alt="image-20250527194052408" />
</div>
</p>
<p>
<p class="ts-text">
Afterward, you will see the plugin is attached to the target tag
</p>
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/3. Enable Plugins/image-20250527195703464.png" alt="image-20250527195703464" />
</div>
</p>
<p>
<p class="ts-text">
It means the plugin is enabled on the HTTP proxy rule
</p>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -95,6 +95,12 @@
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
@ -130,6 +144,12 @@
<h1 id="plugin-architecture">
Plugin Architecture
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
<p class="ts-text">
The Zoraxy Plugin uses a 3 steps approach to get information from plugin, setup the plugin and forward request to plugin. The name of the steps are partially referred from dbus designs as followings.
@ -156,6 +176,11 @@
<img src="img/1. Plugin Architecture/plugin_workflow.png" alt="" />
</div>
</p>
<p>
<p class="ts-text">
This design make sure that the Zoraxy plugins do not depends on platform dependent implementations that uses, for example, unix socket. This also avoided protocol that require complex conversion to and from HTTP request (data structure) like gRPC, while making sure the plugin can be cross compile into different CPU architecture or OS environment with little to no effect on its performance.
</p>
</p>
</div>
<br>
<br>

View File

@ -95,6 +95,12 @@
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
@ -130,6 +144,12 @@
<h1 id="introspect">
Introspect
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
<p class="ts-text">
Introspect, similar to the one in dbus design, is used to get the information from plugin when Zoraxy starts (or manually triggered in development mode or force reload plugin list).
@ -203,6 +223,10 @@
SubscriptionsEvents map[string]string `json:&quot;subscriptions_events&quot;` //Subscriptions events of your plugin, see Zoraxy documentation for more details
}
</code></pre>
<p>
The introspect provide Zoraxy the required information to start the plugin and how to interact with it. For more details on what those capture settings are for, see &ldquo;Capture Mode&rdquo; section.
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="introspect-manual-triggering">
Introspect Manual Triggering
</h2>

View File

@ -95,6 +95,12 @@
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>

View File

@ -95,6 +95,12 @@
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item is-active" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>

View File

@ -0,0 +1,231 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Plugin UI | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item is-active" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="plugin-ui">
Plugin UI
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
<p class="ts-text">
A plugin can optionally expose a Web UI interface for user configuration.
</p>
</p>
<p>
<p class="ts-text">
<span class="ts-text is-heavy">
It is generally recommended that a plugin have such UI exposed for easy configurations.
</span>
As plugin installed via plugin store provides limited ways for a user to configure the plugin, the plugin web UI will be the best way for user to setup your plugin.
</p>
</p>
<h2 id="plugin-web-ui-access">
Plugin Web UI Access
</h2>
<p>
<p class="ts-text">
If a plugin provide a Web UI endpoint for Zoraxy during the introspect process, a new item will be shown in the Plugins section on Zoraxy side menu. Below is an example of the Web UI of UPnP Port Forwarder plugin.
</p>
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/5. Plugin UI/image-20250527201750613.png" alt="image-20250527201750613" />
</div>
</p>
<h2 id="front-end-developer-notes">
Front-end Developer Notes
</h2>
<p>
<p class="ts-text">
The Web UI is implemented as a reverse proxy and embed in an iframe. So you do not need to handle CORS issues with the web UI (as it will be proxy internally by Zoraxy as exposed as something like a virtual directory mounted website).
</p>
</p>
<p>
<p class="ts-text">
However, the plugin web UI is exposed via the path
<span class="ts-text is-code">
/plugin.ui/{{plugin_uuid}}/
</span>
, for example,
<span class="ts-text is-code">
/plugin.ui/org.aroz.zoraxy.plugins.upnp/
</span>
.
<span class="ts-text is-heavy">
When developing the plugin web UI, do not use absolute path for any resources used in the HTML file
</span>
, unless you are trying to re-use Zoraxy components like css or image elements stored in Zoraxy embedded web file system (e.g.
<span class="ts-text is-code">
/img/logo.svg
</span>
).
</p>
</p>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,643 @@
<!DOCTYPE html>
<html lang="en" class="is-white">
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/png" href="/favicon.png">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
Hello World | Zoraxy Documentation
</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/2.1.0/showdown.min.js" integrity="sha512-LhccdVNGe2QMEfI3x4DVV3ckMRe36TfydKss6mJpdHjNFiV07dFpS2xzeZedptKZrwxfICJpez09iNioiSZ3hA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- css -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/tocas-ui/5.0.2/tocas.min.js"></script>
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+TC:wght@400;500;700&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Code highlight -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/default.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<!-- additional languages -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/c.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/languages/xml.min.js"></script>
<script>
hljs.highlightAll();
</script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/tomorrow-night-bright.css">
<style>
#msgbox{
position: fixed;
bottom: 1em;
right: 1em;
z-index: 9999;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
dialog[open] {
animation: fadeIn 0.3s ease-in-out;
}
code{
border-radius: 0.5rem;
}
</style>
<script src="/html/assets/theme.js"></script>
</head>
<body>
<div class="ts-content">
<div class="ts-container">
<div style="float: right;">
<button class="ts-button is-icon" id="darkModeToggle">
<span class="ts-icon is-moon-icon"></span>
</button>
</div>
<div class="ts-tab is-pilled">
<a href="" class="item" style="user-select: none;">
<img id="sysicon" class="ts-image" style="height: 30px" white_src="/html/assets/logo.png" dark_src="/html/assets/logo_white.png" src="/html/assets/logo.png"></img>
</a>
<a href="#!" class="is-active item">
Documents
</a>
<a href="#!" class="item">
Examples
</a>
</div>
</div>
</div>
<div class="ts-divider"></div>
<div>
<div class="has-padded">
<div class="ts-grid mobile:is-stacked">
<div class="column is-4-wide">
<div class="ts-box">
<div class="ts-menu is-end-icon">
<a class="item">
Introduction
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/1. Introduction/1. What is Zoraxy Plugin.html">
What is Zoraxy Plugin
</a>
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/2. Architecture/1. Plugin Architecture.html">
Plugin Architecture
</a>
<a class="item" href="/html/2. Architecture/2. Introspect.html">
Introspect
</a>
<a class="item" href="/html/2. Architecture/3. Configure.html">
Configure
</a>
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item is-active" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item" href="/html/index.html">
index
</a>
</div>
</div>
</div>
<div class="column is-12-wide">
<div class="ts-box">
<div class="ts-container is-padded has-top-padded-large">
<h1 id="hello-world">
Hello World!
</h1>
<p>
<p class="ts-text">
Last Update: 25/05/2025
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<p>
Let start with a really simple Hello World plugin. This only function of this plugin is to print &ldquo;Hello World&rdquo; in the plugin web UI.
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="1-name-your-plugin">
1. Name your plugin
</h2>
<p>
First things first, give your plugin a name. In this example, we are using the name &ldquo;helloworld&rdquo;.
</p>
<p>
<p class="ts-text">
<span class="ts-text is-heavy">
Plugin name cannot contain space or special characters
</span>
, so you must use a file name that satisfy the requirement. No worry, the plugin file name is not the same as the plugin display name in the introspect.
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="2-create-the-plugin-folder">
2. Create the plugin folder
</h2>
<p>
If your zoraxy root folder do not contains a folder named &ldquo;plugins&rdquo;, it might implies that your Zoraxy is freshly clone from Github.
<span class="ts-text is-heavy">
You will need to build and run it once to start working on your plugin
</span>
, so if you have a newly cloned source code of Zoraxy, do the followings.
</p>
<pre><code class="language-bash">git clone https://github.com/tobychui/zoraxy
cd src
go mod tidy
go build
sudo ./zoraxy
</code></pre>
<p>
Afterward, create a plugin folder under your Zoraxy development environment that is exactly matching your plugin name. In the above example, the folder name should be &ldquo;helloworld&rdquo;.
</p>
<pre><code class="language-bash"># Assume you are already inside the src/ folder
mkdir helloworld
cd ./helloworld
</code></pre>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="3-create-a-go-project">
3. Create a go project
</h2>
<p>
<p class="ts-text">
Similar to any Go project, you can start by creating a
<span class="ts-text is-code">
main.go
</span>
file. Next, you would want to let the go compiler knows your plugin name so when generating a binary file, it knows what to name it. This can be done via using the
<span class="ts-text is-code">
go mod init
</span>
command.
</p>
</p>
<pre><code class="language-bash">touch main.go
go mod init example.com/zoraxy/helloworld
ls
# After you are done, you should see the followings
# go.mod main.go
</code></pre>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="4-copy-the-zoraxy-plugin-lib-from-zoraxy-source-code">
4. Copy the Zoraxy plugin lib from Zoraxy source code
</h2>
<p>
<p class="ts-text">
Locate the Zoraxy plugin library from the Zoraxy source code. You can find the
<span class="ts-text is-code">
zoraxy_plugin
</span>
Go module under
<span class="ts-text is-code">
src/mod/plugins/zoraxy_plugin
</span>
</p>
</p>
<p>
<p class="ts-text">
Copy the
<span class="ts-text is-code">
zoraxy_plugin
</span>
folder from the Zoraxy source code mod folder into the your plugins mod folder. Let assume you use the same mod folder name as Zoraxy as
<span class="ts-text is-code">
mod
</span>
, then your copied library path should be
<span class="ts-text is-code">
plugins/helloword/mod/zoraxy_plugin
</span>
</p>
</p>
<pre><code class="language-bash">mkdir ./mod
cp -r &quot;mod/plugins/zoraxy_plugin&quot; ./mod/
ls ./mod/zoraxy_plugin/
# You should see something like this (might be different in future versions)
# dev_webserver.go dynamic_router.go embed_webserver.go README.txt static_router.go zoraxy_plugin.go
</code></pre>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="5-create-a-web-resources-folder">
5. Create a web resources folder
</h2>
<p>
<p class="ts-text">
Lets create a www folder and put all our web resources, we need to create an
<span class="ts-text is-code">
index.html
</span>
file as our plugin web ui homepage. This can be done by creating a HTML file in the www folder.
</p>
</p>
<pre><code class="language-bash"># Assuming you are currently in the src/plugins/helloworld/ folder
mkdir www
cd www
touch index.html
</code></pre>
<p>
<p class="ts-text">
And here is an example
<span class="ts-text is-code">
index.html
</span>
file that uses the Zoraxy internal resources like css and dark theme toggle mechanism. That csrf token template is not straightly needed in this example as helloworld plugin do not make any POST request to Zoraxy webmin interface, but it might come in handy later.
</p>
</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;!-- CSRF token, if your plugin need to make POST request to backend --&gt;
&lt;meta name=&quot;zoraxy.csrf.Token&quot; content=&quot;{{.csrfToken}}&quot;&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;/script/semantic/semantic.min.css&quot;&gt;
&lt;script src=&quot;/script/jquery-3.6.0.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;/script/semantic/semantic.min.js&quot;&gt;&lt;/script&gt;
&lt;script src=&quot;/script/utils.js&quot;&gt;&lt;/script&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;/main.css&quot;&gt;
&lt;title&gt;Hello World&lt;/title&gt;
&lt;style&gt;
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
font-family: Arial, sans-serif;
background:none;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;!-- Dark theme script must be included after body tag--&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;/darktheme.css&quot;&gt;
&lt;script src=&quot;/script/darktheme.js&quot;&gt;&lt;/script&gt;
&lt;div style=&quot;text-align: center;&quot;&gt;
&lt;h1&gt;Hello World&lt;/h1&gt;
&lt;p&gt;Welcome to your first Zoraxy plugin&lt;/p&gt;
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="6-creating-a-handler-for-introspect">
6. Creating a handler for Introspect
</h2>
<p>
<p class="ts-text">
To create a handler for introspect, you can first start your plugin with a few constants.
</p>
</p>
<div class="ts-list is-ordered">
<div class="item">
Plugin ID, this must be unique. You can use a domain you own like
<span class="ts-text is-code">
com.example.helloworld
</span>
</div>
<div class="item">
UI Path, for now we uses &ldquo;/&rdquo; as this plugin do not have any other endpoints, so we can use the whole root just for web UI
</div>
<div class="item">
Web root, for trimming off from the embedded web folder so when user can visit your
<span class="ts-text is-code">
index.html
</span>
by accessing
<span class="ts-text is-code">
/
</span>
instead of needing to navigate to
<span class="ts-text is-code">
/www
</span>
</div>
</div>
<p>
<p class="ts-text">
After you have defined these constant, we can use
<span class="ts-text is-code">
plugin.ServeAndRecvSpec
</span>
function to handle the handshake between Zoraxy and your plugin.
</p>
</p>
<pre><code class="language-go">const (
PLUGIN_ID = &quot;com.example.helloworld&quot;
UI_PATH = &quot;/&quot;
WEB_ROOT = &quot;/www&quot;
)
func main(){
runtimeCfg, err := plugin.ServeAndRecvSpec(&amp;plugin.IntroSpect{
ID: &quot;com.example.helloworld&quot;,
Name: &quot;Hello World Plugin&quot;,
Author: &quot;foobar&quot;,
AuthorContact: &quot;admin@example.com&quot;,
Description: &quot;A simple hello world plugin&quot;,
URL: &quot;https://example.com&quot;,
Type: plugin.PluginType_Utilities,
VersionMajor: 1,
VersionMinor: 0,
VersionPatch: 0,
// As this is a utility plugin, we don't need to capture any traffic
// but only serve the UI, so we set the UI (relative to the plugin path) to &quot;/&quot;
UIPath: UI_PATH,
})
if err != nil {
//Terminate or enter standalone mode here
panic(err)
}
}
</code></pre>
<p>
<span class="ts-text is-heavy">
Notes: If some post processing is needed between Introspect and Configure, you can use two seperate function to handle the first start and the second starting of your plugin. The &ldquo;seperated version&rdquo; of
<span class="ts-text is-code">
ServeAndRecvSpec
</span>
is defined as
<span class="ts-text is-code">
ServeIntroSpect(pluginSpect *IntroSpect)
</span>
and
<span class="ts-text is-code">
RecvConfigureSpec() (*ConfigureSpec, error)
</span>
. See
<span class="ts-text is-code">
zoraxy_plugin.go
</span>
for more information.
</span>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="7-creating-a-web-server-from-embedded-web-fs">
7. Creating a web server from embedded web fs
</h2>
<p>
<p class="ts-text">
After that, we need to create a web server to serve our plugin UI to Zoraxy via HTTP. This can be done via the
<span class="ts-text is-code">
http.FileServer
</span>
but for simplicity and ease of upgrade, the Zoraxy plugin library provided an easy to use embedded web FS server API for plugin developers.
</p>
</p>
<p>
<p class="ts-text">
To use the Zoraxy plugin embedded web server, you first need to embed your web fs into Zoraxy as such.
</p>
</p>
<pre><code class="language-go">import (
_ &quot;embed&quot;
&quot;fmt&quot;
plugin &quot;example.com/zoraxy/helloworld/mod/zoraxy_plugin&quot;
)
//go:embed www/*
var content embed.FS
</code></pre>
<p>
<p class="ts-text">
Then call to the
<span class="ts-text is-code">
NewPluginEmbedUIRouter
</span>
to create a new UI router from the embedded Fs.
</p>
</p>
<pre><code class="language-go">// Create a new PluginEmbedUIRouter that will serve the UI from web folder
// The router will also help to handle the termination of the plugin when
// a user wants to stop the plugin via Zoraxy Web UI
embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &amp;content, WEB_ROOT, UI_PATH)
</code></pre>
<p>
<p class="ts-text">
Here is the tricky part. since not all platform support cross process signaling, Zoraxy plugin uses HTTP request to request a plugin to shutdown. The
<span class="ts-text is-code">
embedWebRouter
</span>
object has a function named
<span class="ts-text is-code">
RegisterTerminateHandler
</span>
where you can easily use this function to register actions that needed to be done before shutdown.
</p>
</p>
<pre><code class="language-go">embedWebRouter.RegisterTerminateHandler(func() {
// Do cleanup here if needed
fmt.Println(&quot;Hello World Plugin Exited&quot;)
}, nil)
</code></pre>
<p>
<span class="ts-text is-heavy">
Notes: This is a blocking function. That is why Zoraxy has a build-in timeout context where if the terminate request takes more than 3 seconds, the plugin process will be treated as &ldquo;freezed&rdquo; and forcefully terminated. So please make sure the terminate handler complete its shutdown procedures within 3 seconds.
</span>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="8-register-serve-the-web-ui">
8. Register &amp; Serve the Web UI
</h2>
<p>
<p class="ts-text">
After you have created a embedded web router, you can register it to the UI PATH as follows.
</p>
</p>
<pre><code class="language-go">// Serve the hello world page in the www folder
http.Handle(UI_PATH, embedWebRouter.Handler())
fmt.Println(&quot;Hello World started at http://127.0.0.1:&quot; + strconv.Itoa(runtimeCfg.Port))
err = http.ListenAndServe(&quot;127.0.0.1:&quot;+strconv.Itoa(runtimeCfg.Port), nil)
if err != nil {
panic(err)
}
</code></pre>
<p>
<p class="ts-text">
As this is just the standard golang net/http package, you can of course add more Function Handlers to it based on your needs. There are something that you need to know about adding API endpoints, we will discuss this in the later sections.
</p>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="9-build-and-test">
9. Build and Test
</h2>
<p>
<p class="ts-text">
After saving the
<span class="ts-text is-code">
main.go
</span>
file, you can now build your plugin with
<span class="ts-text is-code">
go build
</span>
. It should generate the plugin in your platform architecture and OS. If you are on Linux, it will be
<span class="ts-text is-code">
helloworld
</span>
and if you are on Windows, it will be
<span class="ts-text is-code">
helloworld.exe
</span>
.
</p>
</p>
<p>
<p class="ts-text">
After you are done, restart Zoraxy and enable your plugin in the Plugin List. Now you can test and debug your plugin with your HTTP Proxy Rules. All the STDOUT and STDERR of your plugin will be forwarded to the STDOUT of Zoraxy as well as the log file.
</p>
</p>
<p>
<p class="ts-text">
<span class="ts-text is-heavy">
Tips
</span>
</p>
</p>
<p>
<p class="ts-text">
You can also enable the Developer Option - Plugin Auto Reload function if you are too lazy to restart Zoraxy everytime the plugin binary changed.
</p>
</p>
<p>
<div class="ts-image is-rounded" style="max-width: 800px">
<img src="img/1. Hello World/image-20250527210849767.png" alt="image-20250527210849767" />
</div>
</p>
<div class="ts-divider has-top-spaced-large"></div>
<h2 id="10-full-code">
10. Full Code
</h2>
<p>
<p class="ts-text">
This is the full code of the helloworld plugin main.go file.
</p>
</p>
<pre><code class="language-go">package main
import (
&quot;embed&quot;
_ &quot;embed&quot;
&quot;fmt&quot;
&quot;net/http&quot;
&quot;strconv&quot;
plugin &quot;example.com/zoraxy/helloworld/mod/zoraxy_plugin&quot;
)
const (
PLUGIN_ID = &quot;com.example.helloworld&quot;
UI_PATH = &quot;/&quot;
WEB_ROOT = &quot;/www&quot;
)
//go:embed www/*
var content embed.FS
func main() {
// Serve the plugin intro spect
// This will print the plugin intro spect and exit if the -introspect flag is provided
runtimeCfg, err := plugin.ServeAndRecvSpec(&amp;plugin.IntroSpect{
ID: &quot;com.example.helloworld&quot;,
Name: &quot;Hello World Plugin&quot;,
Author: &quot;foobar&quot;,
AuthorContact: &quot;admin@example.com&quot;,
Description: &quot;A simple hello world plugin&quot;,
URL: &quot;https://example.com&quot;,
Type: plugin.PluginType_Utilities,
VersionMajor: 1,
VersionMinor: 0,
VersionPatch: 0,
// As this is a utility plugin, we don't need to capture any traffic
// but only serve the UI, so we set the UI (relative to the plugin path) to &quot;/&quot;
UIPath: UI_PATH,
})
if err != nil {
//Terminate or enter standalone mode here
panic(err)
}
// Create a new PluginEmbedUIRouter that will serve the UI from web folder
// The router will also help to handle the termination of the plugin when
// a user wants to stop the plugin via Zoraxy Web UI
embedWebRouter := plugin.NewPluginEmbedUIRouter(PLUGIN_ID, &amp;content, WEB_ROOT, UI_PATH)
embedWebRouter.RegisterTerminateHandler(func() {
// Do cleanup here if needed
fmt.Println(&quot;Hello World Plugin Exited&quot;)
}, nil)
// Serve the hello world page in the www folder
http.Handle(UI_PATH, embedWebRouter.Handler())
fmt.Println(&quot;Hello World started at http://127.0.0.1:&quot; + strconv.Itoa(runtimeCfg.Port))
err = http.ListenAndServe(&quot;127.0.0.1:&quot;+strconv.Itoa(runtimeCfg.Port), nil)
if err != nil {
panic(err)
}
}
</code></pre>
</div>
<br>
<br>
</div>
</div>
</div>
</div>
</div>
<div class="ts-container">
<div class="ts-divider"></div>
<div class="ts-content">
<div class="ts-text">
Zoraxy © tobychui
<span class="thisyear">
2025
</span>
</div>
</div>
</div>
<script>
$(".thisyear").text(new Date().getFullYear());
</script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -95,6 +95,12 @@
<a class="item" href="/html/1. Introduction/2. Getting Started.html">
Getting Started
</a>
<a class="item" href="/html/1. Introduction/3. Installing Plugin.html">
Installing Plugin
</a>
<a class="item" href="/html/1. Introduction/4. Enable Plugins.html">
Enable Plugins
</a>
</div>
<a class="item">
Architecture
@ -113,11 +119,19 @@
<a class="item" href="/html/2. Architecture/4. Capture Modes.html">
Capture Modes
</a>
<a class="item" href="/html/2. Architecture/5. Plugin UI.html">
Plugin UI
</a>
</div>
<a class="item">
Getting Started
Basic Examples
<span class="ts-icon is-caret-down-icon"></span>
</a>
<div class="ts-menu is-dense is-small is-horizontally-padded">
<a class="item" href="/html/3. Basic Examples/1. Hello World.html">
Hello World
</a>
</div>
<a class="item is-active" href="/html/index.html">
index
</a>

View File

@ -7,6 +7,6 @@
<title>Redirecting...</title>
</head>
<body>
<p>If you are not redirected automatically, follow this <a href="html/1.%20Introduction/1.%20Getting%20Started.html#!">link</a>.</p>
<p>If you are not redirected automatically, follow this <a href="html/">link</a>.</p>
</body>
</html>

View File

@ -17,6 +17,16 @@
"filename": "1. Introduction/2. Getting Started.md",
"title": "Getting Started",
"type": "file"
},
{
"filename": "1. Introduction/3. Installing Plugin.md",
"title": "Installing Plugin",
"type": "file"
},
{
"filename": "1. Introduction/4. Enable Plugins.md",
"title": "Enable Plugins",
"type": "file"
}
]
},
@ -44,13 +54,25 @@
"filename": "2. Architecture/4. Capture Modes.md",
"title": "Capture Modes",
"type": "file"
},
{
"filename": "2. Architecture/5. Plugin UI.md",
"title": "Plugin UI",
"type": "file"
}
]
},
{
"title": "Getting Started",
"path": "3. Getting Started",
"type": "folder"
"title": "Basic Examples",
"path": "3. Basic Examples",
"type": "folder",
"files": [
{
"filename": "3. Basic Examples/1. Hello World.md",
"title": "Hello World",
"type": "file"
}
]
},
{
"filename": "index.md",

View File

@ -5,6 +5,9 @@ import (
"fmt"
"log"
"net/http"
"time"
"github.com/fsnotify/fsnotify"
)
type FileInfo struct {
@ -17,6 +20,8 @@ type FileInfo struct {
var (
mode = flag.String("m", "web", "Mode to run the application: 'web' or 'build'")
root_url = flag.String("root", "/html/", "Root URL for the web server")
webserver_stop_chan = make(chan bool, 1)
)
func main() {
@ -27,12 +32,67 @@ func main() {
case "build":
build()
default:
go watchDocsChange()
fmt.Println("Running in web mode")
startWebServerInBackground()
select {}
}
}
func startWebServerInBackground() {
go func() {
server := &http.Server{Addr: ":8080", Handler: http.DefaultServeMux}
http.DefaultServeMux = http.NewServeMux()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir("./")).ServeHTTP(w, r)
})
go func() {
<-webserver_stop_chan
fmt.Println("Stopping server at :8080")
if err := server.Close(); err != nil {
log.Println("Error stopping server:", err)
}
}()
fmt.Println("Starting server at :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
}
func watchDocsChange() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
err = watcher.Add("./docs")
if err != nil {
log.Fatal(err)
}
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Remove|fsnotify.Rename) != 0 {
log.Println("Change detected in docs folder:", event)
webserver_stop_chan <- true
time.Sleep(1 * time.Second) // Allow server to stop gracefully
build()
startWebServerInBackground()
log.Println("Static html files regenerated")
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("Watcher error:", err)
}
}
}