Files
goproxy/examples/plugin/admin/web_admin.go
DarkiT 7affdc79c6 update: 2023-03-15
1. 添加URL重写器
2. 添加插件系统
2025-03-14 00:30:20 +08:00

461 lines
13 KiB
Go

package main
import (
"context"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"runtime"
"strconv"
"strings"
"github.com/darkit/goproxy/examples/plugin"
)
// 全局插件管理器
var pm *plugin.PluginManager
// AdminHandler 处理管理界面主页
func AdminHandler(w http.ResponseWriter, r *http.Request) {
// HTML模板
tmpl := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>插件管理系统</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.plugin-card {
border: 1px solid #ddd;
padding: 15px;
margin-bottom: 20px;
border-radius: 5px;
background-color: #f9f9f9;
}
.plugin-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.plugin-title { margin: 0; color: #333; }
.plugin-version { color: #888; font-size: 0.9em; }
.plugin-author { color: #888; font-size: 0.9em; }
.plugin-desc { margin: 10px 0; }
.plugin-status {
display: inline-block;
padding: 5px 10px;
border-radius: 3px;
font-size: 0.8em;
}
.status-enabled { background-color: #dff0d8; color: #3c763d; }
.status-disabled { background-color: #f2dede; color: #a94442; }
.plugin-actions { margin-top: 15px; }
button {
padding: 5px 10px;
margin-right: 5px;
border: none;
border-radius: 3px;
cursor: pointer;
font-size: 0.9em;
}
.btn-enable { background-color: #5cb85c; color: white; }
.btn-disable { background-color: #d9534f; color: white; }
.btn-config { background-color: #5bc0de; color: white; }
.config-section {
margin-top: 10px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 3px;
}
.config-item {
margin: 5px 0;
display: flex;
}
.config-key {
font-weight: bold;
width: 150px;
}
.system-info {
background-color: #e9ecef;
padding: 10px;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
</head>
<body>
<h1>插件管理系统</h1>
<div class="system-info">
<h3>系统信息</h3>
<p>操作系统: {{.OS}}</p>
<p>动态加载支持: {{if .DynamicLoadingSupported}}{{else}}{{end}}</p>
<p>插件目录: {{.PluginsDir}}</p>
<p>已加载插件数量: {{len .Plugins}}</p>
</div>
<h2>已安装插件</h2>
{{range .Plugins}}
<div class="plugin-card">
<div class="plugin-header">
<div>
<h3 class="plugin-title">{{.Name}}</h3>
<span class="plugin-version">版本: {{.Version}}</span>
<span class="plugin-author">作者: {{.Author}}</span>
</div>
<div>
{{if .Enabled}}
<span class="plugin-status status-enabled">已启用</span>
{{else}}
<span class="plugin-status status-disabled">已禁用</span>
{{end}}
</div>
</div>
<div class="plugin-desc">{{.Description}}</div>
{{if .Config}}
<div class="config-section">
<h4>配置:</h4>
{{range $key, $value := .Config}}
<div class="config-item">
<div class="config-key">{{$key}}:</div>
<div class="config-value">{{$value}}</div>
</div>
{{end}}
</div>
{{end}}
<div class="plugin-actions">
{{if .Enabled}}
<a href="/disable/{{.Name}}"><button class="btn-disable">禁用</button></a>
{{else}}
<a href="/enable/{{.Name}}"><button class="btn-enable">启用</button></a>
{{end}}
<a href="/config/{{.Name}}"><button class="btn-config">配置</button></a>
</div>
</div>
{{end}}
{{if not .Plugins}}
<p>没有已安装的插件。</p>
{{end}}
<h2>操作</h2>
<form action="/reload" method="post">
<button type="submit">重新加载插件</button>
</form>
</body>
</html>
`
// 解析模板
t, err := template.New("admin").Parse(tmpl)
if err != nil {
http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
return
}
// 准备数据
data := struct {
Plugins []plugin.PluginInfo
OS string
DynamicLoadingSupported bool
PluginsDir string
}{
Plugins: pm.GetPluginInfos(),
OS: strings.Title(strings.ToLower(fmt.Sprintf("%s", strings.Split(runtime.GOOS, "/")[0]))),
DynamicLoadingSupported: pm.IsDynamicLoadingSupported(),
PluginsDir: pluginsDir,
}
// 渲染模板
if err := t.Execute(w, data); err != nil {
http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
}
}
// EnablePluginHandler 启用插件
func EnablePluginHandler(w http.ResponseWriter, r *http.Request) {
pluginName := r.URL.Path[len("/enable/"):]
if err := pm.EnablePlugin(pluginName); err != nil {
http.Error(w, fmt.Sprintf("启用插件失败: %v", err), http.StatusInternalServerError)
return
}
// 尝试初始化和启动插件
ctx := context.Background()
plugin, exists := pm.GetPlugin(pluginName)
if exists && plugin.IsEnabled() {
config, _ := pm.GetPluginConfig(pluginName)
if err := plugin.Init(ctx, config); err != nil {
http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
return
}
if err := plugin.Start(ctx); err != nil {
http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
return
}
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// DisablePluginHandler 禁用插件
func DisablePluginHandler(w http.ResponseWriter, r *http.Request) {
pluginName := r.URL.Path[len("/disable/"):]
// 先停止插件
ctx := context.Background()
plugin, exists := pm.GetPlugin(pluginName)
if exists && plugin.IsEnabled() {
if err := plugin.Stop(ctx); err != nil {
http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
return
}
}
// 禁用插件
if err := pm.DisablePlugin(pluginName); err != nil {
http.Error(w, fmt.Sprintf("禁用插件失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// ConfigPluginHandler 配置插件
func ConfigPluginHandler(w http.ResponseWriter, r *http.Request) {
pluginName := r.URL.Path[len("/config/"):]
if r.Method == http.MethodPost {
// 处理配置表单提交
if err := r.ParseForm(); err != nil {
http.Error(w, fmt.Sprintf("解析表单失败: %v", err), http.StatusBadRequest)
return
}
// 收集配置项
config := make(map[string]interface{})
for key, values := range r.PostForm {
if key == "pluginName" {
continue // 跳过插件名称字段
}
if len(values) > 0 {
// 尝试将值转换为适当的类型
value := values[0]
// 布尔值处理
if value == "true" {
config[key] = true
} else if value == "false" {
config[key] = false
} else if strings.Contains(value, ".") {
// 尝试解析为浮点数
if f, err := strconv.ParseFloat(value, 64); err == nil {
config[key] = f
} else {
config[key] = value
}
} else if i, err := strconv.Atoi(value); err == nil {
// 尝试解析为整数
config[key] = i
} else {
// 默认为字符串
config[key] = value
}
}
}
// 保存配置
if err := pm.SetPluginConfig(pluginName, config); err != nil {
http.Error(w, fmt.Sprintf("保存配置失败: %v", err), http.StatusInternalServerError)
return
}
// 重定向回主页
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// 获取插件信息和配置
info, config := getPluginInfo(pluginName)
if info == nil {
http.Error(w, "插件不存在", http.StatusNotFound)
return
}
// 配置表单模板
tmpl := `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>配置 {{.Info.Name}}</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1 { color: #333; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="number"] { width: 100%; padding: 8px; box-sizing: border-box; }
input[type="checkbox"] { margin-right: 5px; }
button { padding: 10px 15px; background-color: #4CAF50; color: white; border: none; cursor: pointer; }
button:hover { background-color: #45a049; }
.back-link { margin-top: 20px; display: block; }
</style>
</head>
<body>
<h1>配置 {{.Info.Name}}</h1>
<form action="/config/{{.Info.Name}}" method="post">
<input type="hidden" name="pluginName" value="{{.Info.Name}}">
{{range $key, $value := .Config}}
<div class="form-group">
<label for="{{$key}}">{{$key}}:</label>
{{if eq (printf "%T" $value) "bool"}}
<input type="checkbox" id="{{$key}}" name="{{$key}}" {{if $value}}checked{{end}}>
{{else if eq (printf "%T" $value) "float64"}}
<input type="number" id="{{$key}}" name="{{$key}}" value="{{$value}}" step="0.01">
{{else if eq (printf "%T" $value) "int"}}
<input type="number" id="{{$key}}" name="{{$key}}" value="{{$value}}">
{{else}}
<input type="text" id="{{$key}}" name="{{$key}}" value="{{$value}}">
{{end}}
</div>
{{end}}
<button type="submit">保存配置</button>
</form>
<a href="/" class="back-link">返回插件列表</a>
</body>
</html>
`
// 解析模板
t, err := template.New("config").Parse(tmpl)
if err != nil {
http.Error(w, fmt.Sprintf("模板解析错误: %v", err), http.StatusInternalServerError)
return
}
// 准备数据
data := struct {
Info *plugin.PluginInfo
Config map[string]interface{}
}{
Info: info,
Config: config,
}
// 渲染模板
if err := t.Execute(w, data); err != nil {
http.Error(w, fmt.Sprintf("模板渲染错误: %v", err), http.StatusInternalServerError)
}
}
// ReloadPluginsHandler 重新加载插件
func ReloadPluginsHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Redirect(w, r, "/", http.StatusSeeOther)
return
}
// 停止所有插件
ctx := context.Background()
if err := pm.StopPlugins(ctx); err != nil {
http.Error(w, fmt.Sprintf("停止插件失败: %v", err), http.StatusInternalServerError)
return
}
// 重新加载插件
if err := pm.LoadPlugins(); err != nil {
http.Error(w, fmt.Sprintf("加载插件失败: %v", err), http.StatusInternalServerError)
return
}
// 初始化和启动插件
if err := pm.InitPlugins(ctx); err != nil {
http.Error(w, fmt.Sprintf("初始化插件失败: %v", err), http.StatusInternalServerError)
return
}
if err := pm.StartPlugins(ctx); err != nil {
http.Error(w, fmt.Sprintf("启动插件失败: %v", err), http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusSeeOther)
}
// API接口处理器 - 返回插件列表
func APIPluginsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
plugins := pm.GetPluginInfos()
if err := json.NewEncoder(w).Encode(plugins); err != nil {
http.Error(w, fmt.Sprintf("编码JSON失败: %v", err), http.StatusInternalServerError)
}
}
// getPluginInfo 获取插件信息和配置
func getPluginInfo(name string) (*plugin.PluginInfo, map[string]interface{}) {
plugins := pm.GetPluginInfos()
for _, info := range plugins {
if info.Name == name {
config, _ := pm.GetPluginConfig(name)
return &info, config
}
}
return nil, nil
}
// 插件目录
var pluginsDir = "./plugins"
func main3() {
// 创建插件管理器
pm = plugin.NewPluginManager(pluginsDir)
// 加载插件
if err := pm.LoadPlugins(); err != nil {
log.Fatalf("加载插件失败: %v", err)
}
// 初始化并启动插件
ctx := context.Background()
if err := pm.InitPlugins(ctx); err != nil {
log.Printf("初始化插件失败: %v", err)
}
if err := pm.StartPlugins(ctx); err != nil {
log.Printf("启动插件失败: %v", err)
}
// 注册处理器
http.HandleFunc("/", AdminHandler)
http.HandleFunc("/enable/", EnablePluginHandler)
http.HandleFunc("/disable/", DisablePluginHandler)
http.HandleFunc("/config/", ConfigPluginHandler)
http.HandleFunc("/reload", ReloadPluginsHandler)
http.HandleFunc("/api/plugins", APIPluginsHandler)
// 启动Web服务器
log.Println("管理界面启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}