chore: git cache cleanup
This commit is contained in:
327
backend/internal/api/handlers/plugin_handler.go
Normal file
327
backend/internal/api/handlers/plugin_handler.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Wikid82/charon/backend/internal/logger"
|
||||
"github.com/Wikid82/charon/backend/internal/models"
|
||||
"github.com/Wikid82/charon/backend/internal/services"
|
||||
"github.com/Wikid82/charon/backend/pkg/dnsprovider"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PluginHandler handles plugin-related API endpoints.
|
||||
type PluginHandler struct {
|
||||
db *gorm.DB
|
||||
pluginLoader *services.PluginLoaderService
|
||||
}
|
||||
|
||||
// NewPluginHandler creates a new plugin handler.
|
||||
func NewPluginHandler(db *gorm.DB, pluginLoader *services.PluginLoaderService) *PluginHandler {
|
||||
return &PluginHandler{
|
||||
db: db,
|
||||
pluginLoader: pluginLoader,
|
||||
}
|
||||
}
|
||||
|
||||
// PluginInfo represents plugin information for API responses.
|
||||
type PluginInfo struct {
|
||||
ID uint `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Author string `json:"author,omitempty"`
|
||||
IsBuiltIn bool `json:"is_built_in"`
|
||||
Description string `json:"description,omitempty"`
|
||||
DocumentationURL string `json:"documentation_url,omitempty"`
|
||||
LoadedAt *string `json:"loaded_at,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ListPlugins returns all plugins (built-in and external).
|
||||
// @Summary List all DNS provider plugins
|
||||
// @Tags Plugins
|
||||
// @Produce json
|
||||
// @Success 200 {array} PluginInfo
|
||||
// @Router /admin/plugins [get]
|
||||
func (h *PluginHandler) ListPlugins(c *gin.Context) {
|
||||
var plugins []PluginInfo
|
||||
|
||||
// Get all registered providers from the registry
|
||||
registeredProviders := dnsprovider.Global().List()
|
||||
|
||||
// Create a map for quick lookup
|
||||
registeredMap := make(map[string]dnsprovider.ProviderPlugin)
|
||||
for _, p := range registeredProviders {
|
||||
registeredMap[p.Type()] = p
|
||||
}
|
||||
|
||||
// Add all registered providers (built-in and loaded external)
|
||||
for providerType, provider := range registeredMap {
|
||||
meta := provider.Metadata()
|
||||
|
||||
pluginInfo := PluginInfo{
|
||||
Type: providerType,
|
||||
Name: meta.Name,
|
||||
Version: meta.Version,
|
||||
Author: meta.Author,
|
||||
IsBuiltIn: meta.IsBuiltIn,
|
||||
Description: meta.Description,
|
||||
DocumentationURL: meta.DocumentationURL,
|
||||
Status: models.PluginStatusLoaded,
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// If it's an external plugin, try to get database record
|
||||
if !meta.IsBuiltIn {
|
||||
var dbPlugin models.Plugin
|
||||
if err := h.db.Where("type = ?", providerType).First(&dbPlugin).Error; err == nil {
|
||||
pluginInfo.ID = dbPlugin.ID
|
||||
pluginInfo.UUID = dbPlugin.UUID
|
||||
pluginInfo.Enabled = dbPlugin.Enabled
|
||||
pluginInfo.Status = dbPlugin.Status
|
||||
pluginInfo.Error = dbPlugin.Error
|
||||
pluginInfo.CreatedAt = dbPlugin.CreatedAt.Format("2006-01-02T15:04:05Z")
|
||||
pluginInfo.UpdatedAt = dbPlugin.UpdatedAt.Format("2006-01-02T15:04:05Z")
|
||||
if dbPlugin.LoadedAt != nil {
|
||||
loadedStr := dbPlugin.LoadedAt.Format("2006-01-02T15:04:05Z")
|
||||
pluginInfo.LoadedAt = &loadedStr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugins = append(plugins, pluginInfo)
|
||||
}
|
||||
|
||||
// Add external plugins that failed to load
|
||||
var failedPlugins []models.Plugin
|
||||
h.db.Where("status = ?", models.PluginStatusError).Find(&failedPlugins)
|
||||
|
||||
for _, dbPlugin := range failedPlugins {
|
||||
// Only add if not already in list
|
||||
found := false
|
||||
for _, p := range plugins {
|
||||
if p.Type == dbPlugin.Type {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
pluginInfo := PluginInfo{
|
||||
ID: dbPlugin.ID,
|
||||
UUID: dbPlugin.UUID,
|
||||
Name: dbPlugin.Name,
|
||||
Type: dbPlugin.Type,
|
||||
Enabled: dbPlugin.Enabled,
|
||||
Status: dbPlugin.Status,
|
||||
Error: dbPlugin.Error,
|
||||
Version: dbPlugin.Version,
|
||||
Author: dbPlugin.Author,
|
||||
IsBuiltIn: false,
|
||||
CreatedAt: dbPlugin.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
UpdatedAt: dbPlugin.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
if dbPlugin.LoadedAt != nil {
|
||||
loadedStr := dbPlugin.LoadedAt.Format("2006-01-02T15:04:05Z")
|
||||
pluginInfo.LoadedAt = &loadedStr
|
||||
}
|
||||
plugins = append(plugins, pluginInfo)
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, plugins)
|
||||
}
|
||||
|
||||
// GetPlugin returns details for a specific plugin.
|
||||
// @Summary Get plugin details
|
||||
// @Tags Plugins
|
||||
// @Produce json
|
||||
// @Param id path int true "Plugin ID"
|
||||
// @Success 200 {object} PluginInfo
|
||||
// @Router /admin/plugins/{id} [get]
|
||||
func (h *PluginHandler) GetPlugin(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid plugin ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var plugin models.Plugin
|
||||
if err := h.db.First(&plugin, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Plugin not found"})
|
||||
return
|
||||
}
|
||||
logger.Log().WithError(err).Error("Failed to get plugin")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get plugin"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get provider metadata if loaded
|
||||
var description, docURL string
|
||||
if provider, ok := dnsprovider.Global().Get(plugin.Type); ok {
|
||||
meta := provider.Metadata()
|
||||
description = meta.Description
|
||||
docURL = meta.DocumentationURL
|
||||
}
|
||||
|
||||
pluginInfo := PluginInfo{
|
||||
ID: plugin.ID,
|
||||
UUID: plugin.UUID,
|
||||
Name: plugin.Name,
|
||||
Type: plugin.Type,
|
||||
Enabled: plugin.Enabled,
|
||||
Status: plugin.Status,
|
||||
Error: plugin.Error,
|
||||
Version: plugin.Version,
|
||||
Author: plugin.Author,
|
||||
IsBuiltIn: false,
|
||||
Description: description,
|
||||
DocumentationURL: docURL,
|
||||
CreatedAt: plugin.CreatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
UpdatedAt: plugin.UpdatedAt.Format("2006-01-02T15:04:05Z"),
|
||||
}
|
||||
if plugin.LoadedAt != nil {
|
||||
loadedStr := plugin.LoadedAt.Format("2006-01-02T15:04:05Z")
|
||||
pluginInfo.LoadedAt = &loadedStr
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, pluginInfo)
|
||||
}
|
||||
|
||||
// EnablePlugin enables a disabled plugin.
|
||||
// @Summary Enable a plugin
|
||||
// @Tags Plugins
|
||||
// @Param id path int true "Plugin ID"
|
||||
// @Success 200 {object} gin.H
|
||||
// @Router /admin/plugins/{id}/enable [post]
|
||||
func (h *PluginHandler) EnablePlugin(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid plugin ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var plugin models.Plugin
|
||||
if err := h.db.First(&plugin, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Plugin not found"})
|
||||
return
|
||||
}
|
||||
logger.Log().WithError(err).Error("Failed to get plugin")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get plugin"})
|
||||
return
|
||||
}
|
||||
|
||||
if plugin.Enabled {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Plugin already enabled"})
|
||||
return
|
||||
}
|
||||
|
||||
// Update database
|
||||
if err := h.db.Model(&plugin).Update("enabled", true).Error; err != nil {
|
||||
logger.Log().WithError(err).Error("Failed to enable plugin")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to enable plugin"})
|
||||
return
|
||||
}
|
||||
|
||||
// Attempt to reload the plugin
|
||||
if err := h.pluginLoader.LoadPlugin(plugin.FilePath); err != nil {
|
||||
logger.Log().WithError(err).Warnf("Failed to reload enabled plugin: %s", plugin.Type)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Plugin enabled but failed to load. Check logs or restart server.",
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Log().Infof("Plugin enabled: %s", plugin.Type)
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Plugin %s enabled successfully", plugin.Name)})
|
||||
}
|
||||
|
||||
// DisablePlugin disables an active plugin.
|
||||
// @Summary Disable a plugin
|
||||
// @Tags Plugins
|
||||
// @Param id path int true "Plugin ID"
|
||||
// @Success 200 {object} gin.H
|
||||
// @Router /admin/plugins/{id}/disable [post]
|
||||
func (h *PluginHandler) DisablePlugin(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid plugin ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var plugin models.Plugin
|
||||
if err := h.db.First(&plugin, id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Plugin not found"})
|
||||
return
|
||||
}
|
||||
logger.Log().WithError(err).Error("Failed to get plugin")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get plugin"})
|
||||
return
|
||||
}
|
||||
|
||||
if !plugin.Enabled {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Plugin already disabled"})
|
||||
return
|
||||
}
|
||||
|
||||
// Check if any DNS providers are using this plugin
|
||||
var count int64
|
||||
h.db.Model(&models.DNSProvider{}).Where("provider_type = ?", plugin.Type).Count(&count)
|
||||
if count > 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": fmt.Sprintf("Cannot disable plugin: %d DNS provider(s) are using it", count),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Update database
|
||||
if err := h.db.Model(&plugin).Update("enabled", false).Error; err != nil {
|
||||
logger.Log().WithError(err).Error("Failed to disable plugin")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to disable plugin"})
|
||||
return
|
||||
}
|
||||
|
||||
// Unload from registry
|
||||
if err := h.pluginLoader.UnloadPlugin(plugin.Type); err != nil {
|
||||
logger.Log().WithError(err).Warnf("Failed to unload plugin: %s", plugin.Type)
|
||||
}
|
||||
|
||||
logger.Log().Infof("Plugin disabled: %s", plugin.Type)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": fmt.Sprintf("Plugin %s disabled successfully. Restart required for full unload.", plugin.Name),
|
||||
})
|
||||
}
|
||||
|
||||
// ReloadPlugins reloads all plugins from the plugin directory.
|
||||
// @Summary Reload all plugins
|
||||
// @Tags Plugins
|
||||
// @Success 200 {object} gin.H
|
||||
// @Router /admin/plugins/reload [post]
|
||||
func (h *PluginHandler) ReloadPlugins(c *gin.Context) {
|
||||
if err := h.pluginLoader.LoadAllPlugins(); err != nil {
|
||||
logger.Log().WithError(err).Error("Failed to reload plugins")
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload plugins", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
loadedPlugins := h.pluginLoader.ListLoadedPlugins()
|
||||
logger.Log().Infof("Reloaded %d plugins", len(loadedPlugins))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "Plugins reloaded successfully",
|
||||
"count": len(loadedPlugins),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user