mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Added plugin info view
- Added plugin info view - Removed zerotier related start parameters - Added automatic tag filter reset on tab change in http proxy page
This commit is contained in:
parent
f8270e46c2
commit
0fdfda436b
@ -226,6 +226,7 @@ func RegisterPluginAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/plugins/enable", pluginManager.HandleEnablePlugin)
|
||||
authRouter.HandleFunc("/api/plugins/disable", pluginManager.HandleDisablePlugin)
|
||||
authRouter.HandleFunc("/api/plugins/icon", pluginManager.HandleLoadPluginIcon)
|
||||
authRouter.HandleFunc("/api/plugins/info", pluginManager.HandlePluginInfo)
|
||||
|
||||
authRouter.HandleFunc("/api/plugins/groups/list", pluginManager.HandleListPluginGroups)
|
||||
authRouter.HandleFunc("/api/plugins/groups/add", pluginManager.HandleAddPluginToGroup)
|
||||
|
@ -80,8 +80,6 @@ var (
|
||||
allowSshLoopback = flag.Bool("sshlb", false, "Allow loopback web ssh connection (DANGER)")
|
||||
allowMdnsScanning = flag.Bool("mdns", true, "Enable mDNS scanner and transponder")
|
||||
mdnsName = flag.String("mdnsname", "", "mDNS name, leave empty to use default (zoraxy_{node-uuid}.local)")
|
||||
ztAuthToken = flag.String("ztauth", "", "ZeroTier authtoken for the local node")
|
||||
ztAPIPort = flag.Int("ztport", 9993, "ZeroTier controller API port")
|
||||
runningInDocker = flag.Bool("docker", false, "Run Zoraxy in docker compatibility mode")
|
||||
acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||
|
@ -173,6 +173,28 @@ func (m *Manager) HandleListPlugins(w http.ResponseWriter, r *http.Request) {
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *Manager) HandlePluginInfo(w http.ResponseWriter, r *http.Request) {
|
||||
pluginID, err := utils.GetPara(r, "plugin_id")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "plugin_id not found")
|
||||
return
|
||||
}
|
||||
|
||||
plugin, err := m.GetPluginByID(pluginID)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
js, err := json.Marshal(plugin)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
}
|
||||
|
||||
func (m *Manager) HandleLoadPluginIcon(w http.ResponseWriter, r *http.Request) {
|
||||
pluginID, err := utils.GetPara(r, "plugin_id")
|
||||
if err != nil {
|
||||
|
@ -665,6 +665,9 @@
|
||||
//Bind on tab switch events
|
||||
tabSwitchEventBind["httprp"] = function(){
|
||||
listProxyEndpoints();
|
||||
|
||||
//Reset the tag filter
|
||||
$("#tagFilterDropdown").dropdown('set selected', "");
|
||||
}
|
||||
|
||||
/* Tags & Search */
|
||||
|
@ -63,6 +63,42 @@
|
||||
.selectedPluginItem.active .selectedIcon{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pluginAddRemoveButtons{
|
||||
border-left: 1px solid var(--divider_color);
|
||||
border-right: 1px solid var(--divider_color);
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.pluginAddRemoveButtons .mobileViewOnly{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.selectColTitle{
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.4em;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 780px) {
|
||||
.pluginAddRemoveButtons {
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-top: 1px solid var(--divider_color);
|
||||
border-bottom: 1px solid var(--divider_color);
|
||||
}
|
||||
|
||||
.pluginAddRemoveButtons .mobileViewOnly{
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pluginAddRemoveButtons .wideViewOnly{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div class="standardContainer">
|
||||
<div class="ui basic segment">
|
||||
@ -77,25 +113,38 @@
|
||||
Plugin Map
|
||||
<div class="sub header">Assigning a plugin to a tag will make the plugin available to the HTTP Proxy rule with the same tag.</div>
|
||||
</h4>
|
||||
<br>
|
||||
<div class="ui stackable grid">
|
||||
<div class="seven wide column">
|
||||
<!-- Selectable plugin list -->
|
||||
<div class="selectColTitle">Unassigned Plugins</div>
|
||||
<div id="selectablePluginList" class="ui relaxed divided list" style="border: 1px solid var(--divider_color);">
|
||||
<div class="item" style="pointer-events: none; user-select: none; opacity: 0.6;">
|
||||
<i class="ui arrow up icon"></i> Select a tag to view available plugins
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two wide column" style="display: flex; align-items: center; justify-content: center;">
|
||||
<div class="two wide column pluginAddRemoveButtons">
|
||||
<!-- Add and Remove button -->
|
||||
<div>
|
||||
<button id="removeSelectedPluginFromTagBtn" class="ui basic red icon button" title="Remove selected plugin from tag">
|
||||
<i class="left arrow icon"></i>
|
||||
</button>
|
||||
<br>
|
||||
<button id="addSelectedPluginTotagBtn" class="ui basic green icon button" title="Add selected plugin to tag" style="margin-top: 0.4em;">
|
||||
<i class="right arrow icon"></i>
|
||||
</button>
|
||||
<div class="wideViewOnly">
|
||||
<button class="ui basic red icon button removeSelectedPluginFromTagBtn" title="Remove selected plugin from tag">
|
||||
<i class="left arrow icon"></i>
|
||||
</button>
|
||||
<br>
|
||||
<button class="ui basic green icon button addSelectedPluginTotagBtn" title="Add selected plugin to tag" style="margin-top: 0.4em;">
|
||||
<i class="right arrow icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mobileViewOnly">
|
||||
<button class="ui basic red icon button removeSelectedPluginFromTagBtn" title="Remove selected plugin from tag">
|
||||
<i class="up arrow icon"></i>
|
||||
</button>
|
||||
<br>
|
||||
<button class="ui basic green icon button addSelectedPluginTotagBtn" title="Add selected plugin to tag" style="margin-top: 0.4em;">
|
||||
<i class="down arrow icon"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="seven wide column">
|
||||
@ -110,6 +159,7 @@
|
||||
</div>
|
||||
<button class="ui basic fluid button" onclick="loadTags();" style="margin-top: 0.4em;"><i class="ui green refresh icon"></i> Refresh Tag List</button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="selectColTitle">Assigned Plugins</div>
|
||||
<div id="selectedTagPluginList" class="ui relaxed divided list" style="border: 1px solid var(--divider_color);">
|
||||
<div class="item" style="pointer-events: none; user-select: none; opacity: 0.6;">
|
||||
<i class="ui arrow up icon"></i> Select a tag to view assigned plugins
|
||||
@ -117,6 +167,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="ui divider"></div>
|
||||
<h4 class="ui header">
|
||||
Plugin List
|
||||
@ -280,7 +331,7 @@ function bindEventsToSelectableItems(){
|
||||
|
||||
//Bind events for the buttons
|
||||
function bindTagAssignButtonEvents(){
|
||||
$("#addSelectedPluginTotagBtn").on("click", function(){
|
||||
$(".addSelectedPluginTotagBtn").on("click", function(){
|
||||
const selectedPlugin = $(".selectablePluginItem.active");
|
||||
const selectedTag = $("#pluginTagList").dropdown("get value");
|
||||
if (selectedPlugin.length == 0){
|
||||
@ -295,7 +346,7 @@ function bindTagAssignButtonEvents(){
|
||||
addPluginToTag(pluginId, selectedTag);
|
||||
});
|
||||
|
||||
$("#removeSelectedPluginFromTagBtn").on("click", function(){
|
||||
$(".removeSelectedPluginFromTagBtn").on("click", function(){
|
||||
const selectedPlugin = $(".selectedPluginItem.active");
|
||||
const selectedTag = $("#pluginTagList").dropdown("get value");
|
||||
if (selectedPlugin.length == 0){
|
||||
@ -429,14 +480,15 @@ function initiatePluginList(){
|
||||
<a href="${plugin.Spec.url}" target="_blank">${plugin.Spec.url}</a></td>
|
||||
<td data-label="Category">${plugin.Spec.type==0?"Router":"Utilities"}</td>
|
||||
<td data-label="Action">
|
||||
<div class="ui small basic buttons">
|
||||
<button onclick="stopPlugin('${plugin.Spec.id}', this);" class="ui button pluginEnableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? '' : 'style="display:none;"'}>
|
||||
<i class="red stop circle icon"></i> Stop
|
||||
</button>
|
||||
<button onclick="startPlugin('${plugin.Spec.id}', this);" class="ui button pluginDisableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? 'style="display:none;"' : ''}>
|
||||
<i class="green play circle icon"></i> Start
|
||||
</button>
|
||||
</div>
|
||||
<button onclick="getPluginInfo('${plugin.Spec.id}', this);" class="ui basic icon button" pluginid="${plugin.Spec.id}">
|
||||
<i class="info circle icon"></i>
|
||||
</button>
|
||||
<button onclick="stopPlugin('${plugin.Spec.id}', this);" class="ui basic button pluginEnableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? '' : 'style="display:none;"'}>
|
||||
<i class="red stop circle icon"></i> Stop
|
||||
</button>
|
||||
<button onclick="startPlugin('${plugin.Spec.id}', this);" class="ui basic button pluginDisableButton" pluginid="${plugin.Spec.id}" ${plugin.Enabled ? 'style="display:none;"' : ''}>
|
||||
<i class="green play circle icon"></i> Start
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
@ -505,6 +557,12 @@ function stopPlugin(pluginId, btn=undefined){
|
||||
});
|
||||
}
|
||||
|
||||
/* Plugin information */
|
||||
function getPluginInfo(pluginId, btn){
|
||||
let payload = encodeURIComponent(JSON.stringify({plugin_id: pluginId}));
|
||||
showSideWrapper("snippet/pluginInfo.html?t=" + Date.now() + "#" + payload);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
232
src/web/snippet/pluginInfo.html
Normal file
232
src/web/snippet/pluginInfo.html
Normal file
@ -0,0 +1,232 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- Notes: This should be open in its original path-->
|
||||
<meta charset="utf-8">
|
||||
<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>
|
||||
<style>
|
||||
.basic.segment.advanceoptions {
|
||||
background-color: var(--theme_advance);
|
||||
border-radius: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<link rel="stylesheet" href="../darktheme.css">
|
||||
<script src="../script/darktheme.js"></script>
|
||||
<br>
|
||||
<div class="ui container">
|
||||
<div class="ui header">
|
||||
<div class="content">
|
||||
Plugin Information
|
||||
<div class="sub header plugin_name"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui basic segment">
|
||||
<div class="ui centered aligned container"></div>
|
||||
<img id="plugin_image" src="" alt="Plugin Image" style="max-width: 100px;">
|
||||
</div>
|
||||
<div class="ui divider"></div>
|
||||
<b>IntroSpect</b>
|
||||
<p>The plugin registered the following information about itself</p>
|
||||
<table class="ui very basic celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IntroSpect</th>
|
||||
<th>Response</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td class="plugin_name"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Version</td>
|
||||
<td id="plugin_version"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Author</td>
|
||||
<td id="plugin_author"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td id="plugin_description"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Category</td>
|
||||
<td id="plugin_category"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td id="plugin_url"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contact</td>
|
||||
<td id="plugin_contact"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui divider"></div>
|
||||
<b>Runtime Values</b>
|
||||
<p>Zoraxy assigned the following settings for its runtime</p>
|
||||
<table class="ui very basic celled unstackable table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Properties</th>
|
||||
<th>Configuration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<td>Enabled</td>
|
||||
<td id="plugin_enabled"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Assigned Port</td>
|
||||
<td id="plugin_assigned_port"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Working Directory</td>
|
||||
<td id="plugin_root_dir"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="ui basic segment advanceoptions" style="margin-left: 0.4em; margin-right: 0.4em;">
|
||||
<div class="ui accordion advanceSettings">
|
||||
<div class="title">
|
||||
<i class="dropdown icon"></i>
|
||||
Developer Insights
|
||||
</div>
|
||||
<div class="content">
|
||||
<h4>Plugin IntroSpect Path Registration</h4>
|
||||
<p>The following static capture paths are registered by this plugin and will be intercepting all traffics that request these paths. <br>
|
||||
If dynamic capture path is not empty, all traffic headers will be forwarded to plugin for processing and optionally intercept.</p>
|
||||
<table class="ui basic celled unstackable table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Registered Static Capture Paths<br>
|
||||
<small>Paths (and subpaths) to be intercepted by this plugin</small></td>
|
||||
<td id="registered_static_capture_paths"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Static Capture Ingress<br>
|
||||
<small>Static intercept request ingress path</small></td>
|
||||
<td id="static_capture_ingress"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dynamic Capture Sniffing Path<br>
|
||||
<small>Request header ingress for intercept precheck</small></td>
|
||||
<td id="dynamic_capture_sniffing_path"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dynamic Capture Ingress<br>
|
||||
<small>Dynamic intercept request ingress path</small></td>
|
||||
<td id="dynamic_capture_ingress"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Registered UI Proxy Path<br>
|
||||
<small>The relative path of the web UI</small></td>
|
||||
<td id="registered_ui_proxy_path"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="ui divider"></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>
|
||||
<script>
|
||||
|
||||
//Get hash from windows
|
||||
let hash = window.location.hash;
|
||||
let payload = hash.substring(1);
|
||||
let pluginID = "";
|
||||
try{
|
||||
payload = JSON.parse(decodeURIComponent(payload));
|
||||
pluginID = payload.plugin_id;
|
||||
|
||||
getPluginInfo(pluginID);
|
||||
}catch(e){
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
|
||||
function getPluginInfo(pluginId){
|
||||
$.get(`/api/plugins/info?plugin_id=${pluginId}`, function(data){
|
||||
let authorContact = data.Spec.author_contact;
|
||||
if(!authorContact.startsWith('http')){
|
||||
authorContact = `mailto:${authorContact}`;
|
||||
}
|
||||
let versionString = `v${data.Spec.version_major}.${data.Spec.version_minor}.${data.Spec.version_patch}`;
|
||||
$(".plugin_name").text(data.Spec.name);
|
||||
$("#plugin_image").attr("src", `/api/plugins/icon?plugin_id=${data.Spec.id}`);
|
||||
$("#plugin_version").text(versionString);
|
||||
$("#plugin_author").html(`<a href="${authorContact}" target="_blank">${data.Spec.author}</a>`);
|
||||
$("#plugin_enabled").html(data.Enabled?`<i class="ui green check icon"></i>`:`<i class="ui red times icon"></i>`);
|
||||
$("#plugin_assigned_port").text(data.AssignedPort);
|
||||
$("#plugin_root_dir").text(data.RootDir + "/");
|
||||
$("#plugin_description").text(data.Spec.description);
|
||||
$("#plugin_category").text(data.Spec.type == 0 ? "Router" : "Utilities");
|
||||
$("#plugin_url").html(`<a href="${data.Spec.url}" target="_blank">${data.Spec.url}</a>`);
|
||||
$("#plugin_contact").html(`<a href="${authorContact}" target="_blank">${data.Spec.author_contact}</a>`);
|
||||
$("#plugin_info_modal").modal('show');
|
||||
|
||||
//Update the developer insights
|
||||
//Note that some paths might be empty string which means they are not registered
|
||||
let registeredStaticCapturePaths = data.Spec.static_capture_paths;
|
||||
if (registeredStaticCapturePaths == null || registeredStaticCapturePaths.length == 0) {
|
||||
registeredStaticCapturePaths = [{capture_path: "No static capture paths registered"}];
|
||||
}
|
||||
let capturePathsList = '<div class="ui list">';
|
||||
registeredStaticCapturePaths.forEach(function(path) {
|
||||
capturePathsList += `<div class="item">${path.capture_path}</div>`;
|
||||
});
|
||||
capturePathsList += '</ul>';
|
||||
$("#registered_static_capture_paths").html(capturePathsList);
|
||||
|
||||
|
||||
let staticCaptureIngress = data.Spec.static_capture_ingress;
|
||||
if (staticCaptureIngress == null || staticCaptureIngress == "") {
|
||||
staticCaptureIngress = "Not registered";
|
||||
}
|
||||
let dynamicCaptureSniffingPath = data.Spec.dynamic_capture_sniff;
|
||||
if (dynamicCaptureSniffingPath == null || dynamicCaptureSniffingPath == "") {
|
||||
dynamicCaptureSniffingPath = "Not registered";
|
||||
}
|
||||
let dynamicCaptureIngress = data.Spec.dynamic_capture_ingress;
|
||||
if (dynamicCaptureIngress == null || dynamicCaptureIngress == "") {
|
||||
dynamicCaptureIngress = "Not registered";
|
||||
}
|
||||
let registeredUIProxyPath = data.Spec.ui_path;
|
||||
if (registeredUIProxyPath == null || registeredUIProxyPath == "") {
|
||||
registeredUIProxyPath = "No UI registered";
|
||||
}
|
||||
|
||||
$("#static_capture_ingress").text(staticCaptureIngress);
|
||||
$("#dynamic_capture_sniffing_path").text(dynamicCaptureSniffingPath);
|
||||
$("#dynamic_capture_ingress").text(dynamicCaptureIngress);
|
||||
$("#registered_ui_proxy_path").text(registeredUIProxyPath);
|
||||
});
|
||||
}
|
||||
|
||||
$(".advanceSettings").accordion();
|
||||
|
||||
function closeThisWrapper(){
|
||||
parent.hideSideWrapper(true);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user