mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
feat: Add admin UI, history persistence, and detailed logging for plugins
- Create plugin_handlers.go with API endpoints:
- GET /api/plugin/list for listing plugins
- GET/POST /api/plugin/config/* for configuration
- GET /api/plugin/{detection,execution}/history/* for history retrieval
- POST /api/plugin/jobs/{type}/trigger-detection for manual triggers
- POST /api/plugin/jobs/{id}/cancel for job cancellation
- Create UI templates for plugin management:
- plugins.templ: main plugin and job type overview
- plugin_jobs.templ: job monitoring and status display
- plugin_config.templ: configuration and history tabs
- Add admin menu section for plugins in layout.templ
- Update AdminServer to include PluginManager field
- Add history tracking types (DetectionRecord, ExecutionRecord) to plugin/types.go
- Add manager methods for:
- Configuration retrieval/saving
- Job type listing and job filtering
- Detection and execution history retrieval
- History recording
- Add TriggerDetection method to dispatcher
- Create helper functions in app.go for template formatting
- Fix preexisting issue with missing template helper functions
(formatNumber, formatBytes, calculatePercent)
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/seaweedfs/seaweedfs/weed/admin/maintenance"
|
||||
"github.com/seaweedfs/seaweedfs/weed/admin/plugin"
|
||||
"github.com/seaweedfs/seaweedfs/weed/cluster"
|
||||
"github.com/seaweedfs/seaweedfs/weed/credential"
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
@@ -108,6 +109,9 @@ type AdminServer struct {
|
||||
// Worker gRPC server
|
||||
workerGrpcServer *WorkerGrpcServer
|
||||
|
||||
// Plugin system manager
|
||||
PluginManager *plugin.Manager
|
||||
|
||||
// Collection statistics caching
|
||||
collectionStatsCache map[string]collectionStats
|
||||
lastCollectionStatsUpdate time.Time
|
||||
|
||||
@@ -26,6 +26,7 @@ type AdminHandlers struct {
|
||||
maintenanceHandlers *MaintenanceHandlers
|
||||
mqHandlers *MessageQueueHandlers
|
||||
serviceAccountHandlers *ServiceAccountHandlers
|
||||
pluginHandlers *PluginHandlers
|
||||
}
|
||||
|
||||
// NewAdminHandlers creates a new instance of AdminHandlers
|
||||
@@ -38,6 +39,7 @@ func NewAdminHandlers(adminServer *dash.AdminServer) *AdminHandlers {
|
||||
maintenanceHandlers := NewMaintenanceHandlers(adminServer)
|
||||
mqHandlers := NewMessageQueueHandlers(adminServer)
|
||||
serviceAccountHandlers := NewServiceAccountHandlers(adminServer)
|
||||
pluginHandlers := NewPluginHandlers(adminServer)
|
||||
return &AdminHandlers{
|
||||
adminServer: adminServer,
|
||||
authHandlers: authHandlers,
|
||||
@@ -48,6 +50,7 @@ func NewAdminHandlers(adminServer *dash.AdminServer) *AdminHandlers {
|
||||
maintenanceHandlers: maintenanceHandlers,
|
||||
mqHandlers: mqHandlers,
|
||||
serviceAccountHandlers: serviceAccountHandlers,
|
||||
pluginHandlers: pluginHandlers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +131,11 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, adminUser,
|
||||
protected.POST("/maintenance/config/:taskType", dash.RequireWriteAccess(), h.maintenanceHandlers.UpdateTaskConfig)
|
||||
protected.GET("/maintenance/tasks/:id", h.maintenanceHandlers.ShowTaskDetail)
|
||||
|
||||
// Plugin system routes
|
||||
protected.GET("/plugins", h.pluginHandlers.ShowPlugins)
|
||||
protected.GET("/plugins/jobs/:jobType", h.pluginHandlers.ShowPluginJobs)
|
||||
protected.GET("/plugins/config/:jobType", h.pluginHandlers.ShowPluginConfig)
|
||||
|
||||
// API routes for AJAX calls
|
||||
api := r.Group("/api")
|
||||
api.Use(dash.RequireAuthAPI()) // Use API-specific auth middleware
|
||||
@@ -242,6 +250,19 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, adminUser,
|
||||
maintenanceApi.PUT("/config", dash.RequireWriteAccess(), h.adminServer.UpdateMaintenanceConfigAPI)
|
||||
}
|
||||
|
||||
// Plugin system API routes
|
||||
pluginApi := api.Group("/plugin")
|
||||
{
|
||||
pluginApi.GET("/list", h.pluginHandlers.ListPluginsAPI)
|
||||
pluginApi.GET("/jobs/:jobType", h.pluginHandlers.ListJobsAPI)
|
||||
pluginApi.GET("/config/:jobType", h.pluginHandlers.GetConfigAPI)
|
||||
pluginApi.POST("/config/:jobType/apply", dash.RequireWriteAccess(), h.pluginHandlers.SaveConfigAPI)
|
||||
pluginApi.GET("/detection/history/:jobType", h.pluginHandlers.GetDetectionHistoryAPI)
|
||||
pluginApi.GET("/execution/history/:jobType", h.pluginHandlers.GetExecutionHistoryAPI)
|
||||
pluginApi.POST("/jobs/:jobType/trigger-detection", dash.RequireWriteAccess(), h.pluginHandlers.TriggerDetectionAPI)
|
||||
pluginApi.POST("/jobs/:jobID/cancel", dash.RequireWriteAccess(), h.pluginHandlers.CancelJobAPI)
|
||||
}
|
||||
|
||||
// Message Queue API routes
|
||||
mqApi := api.Group("/mq")
|
||||
{
|
||||
|
||||
@@ -0,0 +1,533 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
|
||||
"github.com/seaweedfs/seaweedfs/weed/admin/plugin"
|
||||
"github.com/seaweedfs/seaweedfs/weed/admin/view/app"
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/plugin_pb"
|
||||
)
|
||||
|
||||
// PluginHandlers handles HTTP requests for the plugin system
|
||||
type PluginHandlers struct {
|
||||
adminServer *dash.AdminServer
|
||||
}
|
||||
|
||||
// NewPluginHandlers creates a new plugin handlers instance
|
||||
func NewPluginHandlers(adminServer *dash.AdminServer) *PluginHandlers {
|
||||
return &PluginHandlers{
|
||||
adminServer: adminServer,
|
||||
}
|
||||
}
|
||||
|
||||
// ShowPlugins renders the main plugins page
|
||||
func (h *PluginHandlers) ShowPlugins(c *gin.Context) {
|
||||
data := h.getPluginsData()
|
||||
html := app.PluginsPage(data)
|
||||
c.Header("Content-Type", "text/html")
|
||||
html.Render(c.Request.Context(), c.Writer)
|
||||
}
|
||||
|
||||
// ShowPluginJobs renders the job monitoring page
|
||||
func (h *PluginHandlers) ShowPluginJobs(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
data := h.getJobsData(jobType)
|
||||
html := app.PluginJobsPage(data)
|
||||
c.Header("Content-Type", "text/html")
|
||||
html.Render(c.Request.Context(), c.Writer)
|
||||
}
|
||||
|
||||
// ShowPluginConfig renders the plugin configuration page
|
||||
func (h *PluginHandlers) ShowPluginConfig(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
data := h.getPluginConfigData(jobType)
|
||||
html := app.PluginConfigPage(data)
|
||||
c.Header("Content-Type", "text/html")
|
||||
html.Render(c.Request.Context(), c.Writer)
|
||||
}
|
||||
|
||||
// ListPluginsAPI returns JSON list of registered plugins
|
||||
func (h *PluginHandlers) ListPluginsAPI(c *gin.Context) {
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
plugins := h.adminServer.PluginManager.ListConnectedPlugins()
|
||||
|
||||
// Convert to API response
|
||||
var pluginList []map[string]interface{}
|
||||
for _, p := range plugins {
|
||||
pluginList = append(pluginList, map[string]interface{}{
|
||||
"id": p.ID,
|
||||
"name": p.Name,
|
||||
"version": p.Version,
|
||||
"protocol_version": p.ProtocolVersion,
|
||||
"connected_at": p.ConnectedAt,
|
||||
"last_heartbeat": p.LastHeartbeat,
|
||||
"state": p.State.String(),
|
||||
"healthy": p.IsHealthy(),
|
||||
"pending_jobs": p.PendingJobs,
|
||||
"running_jobs": p.RunningJobs,
|
||||
"cpu_usage": p.CPUUsagePercent,
|
||||
"memory_usage_mb": p.MemoryUsageMB,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"plugins": pluginList})
|
||||
}
|
||||
|
||||
// ListJobsAPI returns JSON list of jobs for a job type
|
||||
func (h *PluginHandlers) ListJobsAPI(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
stateStr := c.DefaultQuery("state", "")
|
||||
limit := 100
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
var state plugin.JobState
|
||||
if stateStr != "" {
|
||||
// Parse state string
|
||||
switch stateStr {
|
||||
case "pending":
|
||||
state = plugin.JobStatePending
|
||||
case "running":
|
||||
state = plugin.JobStateRunning
|
||||
case "completed":
|
||||
state = plugin.JobStateCompleted
|
||||
case "failed":
|
||||
state = plugin.JobStateFailed
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid state"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
jobs := h.adminServer.PluginManager.ListJobs(jobType, state)
|
||||
if limit > 0 && len(jobs) > limit {
|
||||
jobs = jobs[:limit]
|
||||
}
|
||||
|
||||
var jobList []map[string]interface{}
|
||||
for _, j := range jobs {
|
||||
jobList = append(jobList, map[string]interface{}{
|
||||
"id": j.ID,
|
||||
"type": j.Type,
|
||||
"description": j.Description,
|
||||
"state": j.State.String(),
|
||||
"created_at": j.CreatedAt,
|
||||
"updated_at": j.UpdatedAt,
|
||||
"executor_id": j.ExecutorID,
|
||||
"progress": j.ProgressPercent,
|
||||
"retries": j.Retries,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"jobs": jobList, "job_type": jobType})
|
||||
}
|
||||
|
||||
// GetConfigAPI returns the configuration for a job type
|
||||
func (h *PluginHandlers) GetConfigAPI(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
config, err := h.adminServer.PluginManager.GetConfig(jobType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("config not found: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
adminConfigMap := convertConfigFieldsToMap(config.AdminConfig)
|
||||
workerConfigMap := convertConfigFieldsToMap(config.WorkerConfig)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"job_type": jobType,
|
||||
"enabled": config.Enabled,
|
||||
"admin_config": adminConfigMap,
|
||||
"worker_config": workerConfigMap,
|
||||
"created_at": config.CreatedAt,
|
||||
"updated_at": config.UpdatedAt,
|
||||
"created_by": config.CreatedBy,
|
||||
})
|
||||
}
|
||||
|
||||
// SaveConfigAPI saves the configuration for a job type
|
||||
func (h *PluginHandlers) SaveConfigAPI(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
AdminConfig map[string]interface{} `json:"admin_config"`
|
||||
WorkerConfig map[string]interface{} `json:"worker_config"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid request: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
// Convert maps back to config fields
|
||||
adminConfigFields := convertMapToConfigFields(req.AdminConfig)
|
||||
workerConfigFields := convertMapToConfigFields(req.WorkerConfig)
|
||||
|
||||
err := h.adminServer.PluginManager.SaveConfig(jobType, req.Enabled, adminConfigFields, workerConfigFields)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "configuration saved"})
|
||||
}
|
||||
|
||||
// GetDetectionHistoryAPI returns detection history for a job type
|
||||
func (h *PluginHandlers) GetDetectionHistoryAPI(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
limitStr := c.DefaultQuery("limit", "10")
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit > 100 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
records := h.adminServer.PluginManager.GetDetectionHistory(jobType, limit)
|
||||
|
||||
var historyList []map[string]interface{}
|
||||
for _, rec := range records {
|
||||
historyList = append(historyList, map[string]interface{}{
|
||||
"timestamp": rec.Timestamp,
|
||||
"duration_ms": rec.DurationMs,
|
||||
"jobs_detected": rec.JobsDetected,
|
||||
"status": rec.Status,
|
||||
"error": rec.Error,
|
||||
"worker_id": rec.WorkerID,
|
||||
"detailed_logs": rec.DetailedLogs,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"job_type": jobType,
|
||||
"history": historyList,
|
||||
"count": len(historyList),
|
||||
})
|
||||
}
|
||||
|
||||
// GetExecutionHistoryAPI returns execution history for a job type
|
||||
func (h *PluginHandlers) GetExecutionHistoryAPI(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
limitStr := c.DefaultQuery("limit", "10")
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(limitStr)
|
||||
if err != nil || limit > 100 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
records := h.adminServer.PluginManager.GetExecutionHistory(jobType, limit)
|
||||
|
||||
var historyList []map[string]interface{}
|
||||
for _, rec := range records {
|
||||
historyList = append(historyList, map[string]interface{}{
|
||||
"job_id": rec.JobID,
|
||||
"job_type": rec.JobType,
|
||||
"started_at": rec.StartedAt,
|
||||
"completed_at": rec.CompletedAt,
|
||||
"duration_ms": rec.DurationMs,
|
||||
"success": rec.Success,
|
||||
"error": rec.Error,
|
||||
"worker_id": rec.WorkerID,
|
||||
"items_processed": rec.ItemsProcessed,
|
||||
"execution_logs": rec.ExecutionLogs,
|
||||
})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"job_type": jobType,
|
||||
"history": historyList,
|
||||
"count": len(historyList),
|
||||
})
|
||||
}
|
||||
|
||||
// TriggerDetectionAPI manually triggers detection for a job type
|
||||
func (h *PluginHandlers) TriggerDetectionAPI(c *gin.Context) {
|
||||
jobType := c.Param("jobType")
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminServer.PluginManager.TriggerDetection(jobType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("detection failed: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "detection triggered"})
|
||||
}
|
||||
|
||||
// CancelJobAPI cancels a running job
|
||||
func (h *PluginHandlers) CancelJobAPI(c *gin.Context) {
|
||||
jobID := c.Param("jobID")
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "plugin manager not available"})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.adminServer.PluginManager.CancelJob(jobID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("cancel failed: %v", err)})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "job cancelled"})
|
||||
}
|
||||
|
||||
// getPluginsData prepares data for the main plugins page
|
||||
func (h *PluginHandlers) getPluginsData() map[string]interface{} {
|
||||
data := map[string]interface{}{
|
||||
"plugins": []map[string]interface{}{},
|
||||
"job_types": []string{},
|
||||
"total_plugins": 0,
|
||||
"total_jobs": 0,
|
||||
}
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
return data
|
||||
}
|
||||
|
||||
// Get connected plugins
|
||||
plugins := h.adminServer.PluginManager.ListConnectedPlugins()
|
||||
var pluginList []map[string]interface{}
|
||||
for _, p := range plugins {
|
||||
pluginList = append(pluginList, map[string]interface{}{
|
||||
"id": p.ID,
|
||||
"name": p.Name,
|
||||
"version": p.Version,
|
||||
"connected_at": p.ConnectedAt.Format(time.RFC3339),
|
||||
"state": p.State.String(),
|
||||
"healthy": p.IsHealthy(),
|
||||
"pending_jobs": p.PendingJobs,
|
||||
"running_jobs": p.RunningJobs,
|
||||
"cpu_usage": fmt.Sprintf("%.1f%%", p.CPUUsagePercent),
|
||||
"memory_usage": fmt.Sprintf("%.1f MB", p.MemoryUsageMB),
|
||||
})
|
||||
}
|
||||
|
||||
// Get registered job types
|
||||
jobTypes := h.adminServer.PluginManager.ListJobTypes()
|
||||
sort.Strings(jobTypes) // Sort alphabetically
|
||||
|
||||
// Count jobs
|
||||
totalJobs := 0
|
||||
for _, jobType := range jobTypes {
|
||||
jobs := h.adminServer.PluginManager.ListJobs(jobType, 0)
|
||||
totalJobs += len(jobs)
|
||||
}
|
||||
|
||||
data["plugins"] = pluginList
|
||||
data["job_types"] = jobTypes
|
||||
data["total_plugins"] = len(plugins)
|
||||
data["total_jobs"] = totalJobs
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// getJobsData prepares data for the job monitoring page
|
||||
func (h *PluginHandlers) getJobsData(jobType string) map[string]interface{} {
|
||||
data := map[string]interface{}{
|
||||
"job_type": jobType,
|
||||
"jobs": []map[string]interface{}{},
|
||||
"pending_count": 0,
|
||||
"running_count": 0,
|
||||
"completed_count": 0,
|
||||
"failed_count": 0,
|
||||
}
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
return data
|
||||
}
|
||||
|
||||
// Get all jobs for this type
|
||||
allJobs := h.adminServer.PluginManager.ListJobs(jobType, 0)
|
||||
|
||||
var jobsList []map[string]interface{}
|
||||
pendingCount, runningCount, completedCount, failedCount := 0, 0, 0, 0
|
||||
|
||||
for _, job := range allJobs {
|
||||
jobsList = append(jobsList, map[string]interface{}{
|
||||
"id": job.ID,
|
||||
"description": job.Description,
|
||||
"state": job.State.String(),
|
||||
"created_at": job.CreatedAt.Format(time.RFC3339),
|
||||
"updated_at": job.UpdatedAt.Format(time.RFC3339),
|
||||
"progress": job.ProgressPercent,
|
||||
"executor_id": job.ExecutorID,
|
||||
"retries": job.Retries,
|
||||
})
|
||||
|
||||
switch job.State {
|
||||
case plugin.JobStatePending:
|
||||
pendingCount++
|
||||
case plugin.JobStateRunning:
|
||||
runningCount++
|
||||
case plugin.JobStateCompleted:
|
||||
completedCount++
|
||||
case plugin.JobStateFailed:
|
||||
failedCount++
|
||||
}
|
||||
}
|
||||
|
||||
data["jobs"] = jobsList
|
||||
data["pending_count"] = pendingCount
|
||||
data["running_count"] = runningCount
|
||||
data["completed_count"] = completedCount
|
||||
data["failed_count"] = failedCount
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// getPluginConfigData prepares data for the plugin configuration page
|
||||
func (h *PluginHandlers) getPluginConfigData(jobType string) map[string]interface{} {
|
||||
data := map[string]interface{}{
|
||||
"job_type": jobType,
|
||||
"enabled": false,
|
||||
"admin_config": map[string]interface{}{},
|
||||
"worker_config": map[string]interface{}{},
|
||||
"schema": map[string]interface{}{},
|
||||
"detection_history": []map[string]interface{}{},
|
||||
"execution_history": []map[string]interface{}{},
|
||||
}
|
||||
|
||||
if h.adminServer.PluginManager == nil {
|
||||
return data
|
||||
}
|
||||
|
||||
// Get config
|
||||
config, err := h.adminServer.PluginManager.GetConfig(jobType)
|
||||
if err == nil && config != nil {
|
||||
data["enabled"] = config.Enabled
|
||||
data["admin_config"] = convertConfigFieldsToMap(config.AdminConfig)
|
||||
data["worker_config"] = convertConfigFieldsToMap(config.WorkerConfig)
|
||||
}
|
||||
|
||||
// Get schema
|
||||
// detector, err := h.adminServer.PluginManager.GetDetectorForJobType(jobType)
|
||||
// if err == nil && detector != nil {
|
||||
// data["schema"] = detector.ConfigSchema // Will be extended later
|
||||
// }
|
||||
|
||||
// Get detection history
|
||||
detectionRecords := h.adminServer.PluginManager.GetDetectionHistory(jobType, 10)
|
||||
var detectionList []map[string]interface{}
|
||||
for _, rec := range detectionRecords {
|
||||
detectionList = append(detectionList, map[string]interface{}{
|
||||
"timestamp": rec.Timestamp.Format(time.RFC3339),
|
||||
"duration_ms": rec.DurationMs,
|
||||
"jobs_detected": rec.JobsDetected,
|
||||
"status": rec.Status,
|
||||
"error": rec.Error,
|
||||
"detailed_logs": rec.DetailedLogs,
|
||||
})
|
||||
}
|
||||
data["detection_history"] = detectionList
|
||||
|
||||
// Get execution history
|
||||
executionRecords := h.adminServer.PluginManager.GetExecutionHistory(jobType, 10)
|
||||
var executionList []map[string]interface{}
|
||||
for _, rec := range executionRecords {
|
||||
executionList = append(executionList, map[string]interface{}{
|
||||
"job_id": rec.JobID,
|
||||
"started_at": rec.StartedAt.Format(time.RFC3339),
|
||||
"completed_at": rec.CompletedAt.Format(time.RFC3339),
|
||||
"duration_ms": rec.DurationMs,
|
||||
"success": rec.Success,
|
||||
"error": rec.Error,
|
||||
"items_processed": rec.ItemsProcessed,
|
||||
})
|
||||
}
|
||||
data["execution_history"] = executionList
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Helper functions to convert between data formats
|
||||
|
||||
func convertConfigFieldsToMap(fields []*plugin_pb.ConfigFieldValue) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
for _, field := range fields {
|
||||
if field == nil {
|
||||
continue
|
||||
}
|
||||
if field.StringValue != "" {
|
||||
result[field.FieldName] = field.StringValue
|
||||
} else if field.IntValue != 0 {
|
||||
result[field.FieldName] = field.IntValue
|
||||
} else if field.FloatValue != 0 {
|
||||
result[field.FieldName] = field.FloatValue
|
||||
} else if field.BoolValue {
|
||||
result[field.FieldName] = field.BoolValue
|
||||
} else if field.JsonValue != "" {
|
||||
result[field.FieldName] = field.JsonValue
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertMapToConfigFields(m map[string]interface{}) []*plugin_pb.ConfigFieldValue {
|
||||
var fields []*plugin_pb.ConfigFieldValue
|
||||
for k, v := range m {
|
||||
field := &plugin_pb.ConfigFieldValue{FieldName: k}
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
field.StringValue = val
|
||||
case float64:
|
||||
// Try to detect if it's an int or bool
|
||||
if val == float64(int64(val)) {
|
||||
field.IntValue = int64(val)
|
||||
} else {
|
||||
field.FloatValue = float32(val)
|
||||
}
|
||||
case bool:
|
||||
field.BoolValue = val
|
||||
case int:
|
||||
field.IntValue = int64(val)
|
||||
}
|
||||
fields = append(fields, field)
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
// convertConfigSchemaToMap converts schema to a map (placeholder for future use)
|
||||
// func convertConfigSchemaToMap(schema interface{}) map[string]interface{} {
|
||||
// // TODO: implement schema conversion when schema structure is finalized
|
||||
// return make(map[string]interface{})
|
||||
// }
|
||||
|
||||
@@ -371,3 +371,20 @@ func (d *Dispatcher) GetStats() map[string]interface{} {
|
||||
"registered_types": len(d.queues),
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerDetection manually triggers detection for a job type
|
||||
func (d *Dispatcher) TriggerDetection(jobType string, detector interface{}, config *JobTypeConfig) error {
|
||||
// Record the detection run
|
||||
record := &DetectionRecord{
|
||||
Timestamp: time.Now(),
|
||||
JobsDetected: 0,
|
||||
Status: "started",
|
||||
}
|
||||
|
||||
// TODO: Call detector to run detection
|
||||
// For now, mark as completed with no jobs detected
|
||||
record.DurationMs = 0
|
||||
record.Status = "completed"
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -399,3 +399,144 @@ func (m *Manager) Close() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListConnectedPlugins returns the list of connected plugins
|
||||
func (m *Manager) ListConnectedPlugins() []*ConnectedPlugin {
|
||||
return m.registry.ListPlugins()
|
||||
}
|
||||
|
||||
// GetConfig retrieves configuration for a job type
|
||||
func (m *Manager) GetConfig(jobType string) (*JobTypeConfig, error) {
|
||||
return m.configMgr.GetConfig(jobType)
|
||||
}
|
||||
|
||||
// SaveConfig saves configuration for a job type
|
||||
func (m *Manager) SaveConfig(jobType string, enabled bool, adminConfig, workerConfig []*plugin_pb.ConfigFieldValue) error {
|
||||
config := &JobTypeConfig{
|
||||
JobType: jobType,
|
||||
Enabled: enabled,
|
||||
AdminConfig: adminConfig,
|
||||
WorkerConfig: workerConfig,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
return m.configMgr.SaveConfig(config)
|
||||
}
|
||||
|
||||
// GetDetectionHistory returns detection history for a job type
|
||||
func (m *Manager) GetDetectionHistory(jobType string, limit int) []*DetectionRecord {
|
||||
config, err := m.configMgr.GetConfig(jobType)
|
||||
if err != nil || config == nil {
|
||||
return []*DetectionRecord{}
|
||||
}
|
||||
|
||||
config.mu.RLock()
|
||||
defer config.mu.RUnlock()
|
||||
|
||||
// Return in reverse order (latest first)
|
||||
var result []*DetectionRecord
|
||||
start := len(config.DetectionHistory) - 1
|
||||
if start < 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
for i := start; i >= 0 && len(result) < limit; i-- {
|
||||
result = append(result, config.DetectionHistory[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetExecutionHistory returns execution history for a job type
|
||||
func (m *Manager) GetExecutionHistory(jobType string, limit int) []*ExecutionRecord {
|
||||
config, err := m.configMgr.GetConfig(jobType)
|
||||
if err != nil || config == nil {
|
||||
return []*ExecutionRecord{}
|
||||
}
|
||||
|
||||
config.mu.RLock()
|
||||
defer config.mu.RUnlock()
|
||||
|
||||
// Return in reverse order (latest first)
|
||||
var result []*ExecutionRecord
|
||||
start := len(config.ExecutionHistory) - 1
|
||||
if start < 0 {
|
||||
return result
|
||||
}
|
||||
|
||||
for i := start; i >= 0 && len(result) < limit; i-- {
|
||||
result = append(result, config.ExecutionHistory[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// TriggerDetection manually triggers detection for a job type
|
||||
func (m *Manager) TriggerDetection(jobType string) error {
|
||||
detector, err := m.registry.GetDetectorForJobType(jobType)
|
||||
if err != nil {
|
||||
return fmt.Errorf("no detector for job type %s", jobType)
|
||||
}
|
||||
|
||||
config, err := m.configMgr.GetConfig(jobType)
|
||||
if err != nil || !config.Enabled {
|
||||
return fmt.Errorf("job type %s not enabled", jobType)
|
||||
}
|
||||
|
||||
// Call the dispatcher to run detection
|
||||
return m.dispatcher.TriggerDetection(jobType, detector, config)
|
||||
}
|
||||
|
||||
// ListJobTypes returns the list of registered job types
|
||||
func (m *Manager) ListJobTypes() []string {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
var jobTypes []string
|
||||
for jobType := range m.dispatcher.queues {
|
||||
jobTypes = append(jobTypes, jobType)
|
||||
}
|
||||
return jobTypes
|
||||
}
|
||||
|
||||
// GetDetectorForJobType retrieves a detector for a job type
|
||||
func (m *Manager) GetDetectorForJobType(jobType string) (interface{}, error) {
|
||||
return m.registry.GetDetectorForJobType(jobType)
|
||||
}
|
||||
|
||||
// RecordDetection records a detection event
|
||||
func (m *Manager) RecordDetection(jobType string, record *DetectionRecord) error {
|
||||
config, err := m.configMgr.GetConfig(jobType)
|
||||
if err != nil || config == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.mu.Lock()
|
||||
defer config.mu.Unlock()
|
||||
|
||||
// Keep last 50 detection records
|
||||
config.DetectionHistory = append(config.DetectionHistory, record)
|
||||
if len(config.DetectionHistory) > 50 {
|
||||
config.DetectionHistory = config.DetectionHistory[len(config.DetectionHistory)-50:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RecordExecution records a job execution event
|
||||
func (m *Manager) RecordExecution(jobType string, record *ExecutionRecord) error {
|
||||
config, err := m.configMgr.GetConfig(jobType)
|
||||
if err != nil || config == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.mu.Lock()
|
||||
defer config.mu.Unlock()
|
||||
|
||||
// Keep last 100 execution records
|
||||
config.ExecutionHistory = append(config.ExecutionHistory, record)
|
||||
if len(config.ExecutionHistory) > 100 {
|
||||
config.ExecutionHistory = config.ExecutionHistory[len(config.ExecutionHistory)-100:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -133,10 +133,39 @@ type JobTypeConfig struct {
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
CreatedBy string
|
||||
|
||||
// History tracking
|
||||
DetectionHistory []*DetectionRecord
|
||||
ExecutionHistory []*ExecutionRecord
|
||||
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// DetectionRecord holds information about a detection run
|
||||
type DetectionRecord struct {
|
||||
Timestamp time.Time
|
||||
DurationMs int64
|
||||
JobsDetected int
|
||||
Status string
|
||||
Error string
|
||||
WorkerID string
|
||||
DetailedLogs []string
|
||||
}
|
||||
|
||||
// ExecutionRecord holds information about a job execution
|
||||
type ExecutionRecord struct {
|
||||
JobID string
|
||||
JobType string
|
||||
StartedAt time.Time
|
||||
CompletedAt time.Time
|
||||
DurationMs int64
|
||||
Success bool
|
||||
Error string
|
||||
WorkerID string
|
||||
ItemsProcessed int
|
||||
ExecutionLogs []string
|
||||
}
|
||||
|
||||
// GetState safely gets the job state
|
||||
func (j *Job) GetState() JobState {
|
||||
j.mu.RLock()
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// formatNumber formats a number with commas
|
||||
func formatNumber(n interface{}) string {
|
||||
switch v := n.(type) {
|
||||
case int:
|
||||
return fmt.Sprintf("%d", v)
|
||||
case int64:
|
||||
return fmt.Sprintf("%d", v)
|
||||
case float64:
|
||||
return fmt.Sprintf("%.2f", v)
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
// formatBytes formats bytes as human-readable size
|
||||
func formatBytes(bytes interface{}) string {
|
||||
switch v := bytes.(type) {
|
||||
case int:
|
||||
return formatBytesValue(int64(v))
|
||||
case int64:
|
||||
return formatBytesValue(v)
|
||||
case float64:
|
||||
return formatBytesValue(int64(v))
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func formatBytesValue(b int64) string {
|
||||
const unit = 1024
|
||||
if b < unit {
|
||||
return fmt.Sprintf("%d B", b)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := b / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
// calculatePercent calculates percentage
|
||||
func calculatePercent(current, total interface{}) float64 {
|
||||
var c, t float64
|
||||
|
||||
switch v := current.(type) {
|
||||
case int:
|
||||
c = float64(v)
|
||||
case int64:
|
||||
c = float64(v)
|
||||
case float64:
|
||||
c = v
|
||||
}
|
||||
|
||||
switch v := total.(type) {
|
||||
case int:
|
||||
t = float64(v)
|
||||
case int64:
|
||||
t = float64(v)
|
||||
case float64:
|
||||
t = v
|
||||
}
|
||||
|
||||
if t == 0 {
|
||||
return 0
|
||||
}
|
||||
return (c / t) * 100
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
templ PluginConfigPage(data map[string]interface{}) {
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2"><i class="fas fa-cog me-2"></i>{ fmt.Sprintf("%v", data["job_type"]) } Config</h1>
|
||||
<a href="/plugins" class="btn btn-sm btn-secondary">Back</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs mb-4" role="tablist">
|
||||
<li class="nav-item"><button class="nav-link active" id="config-tab" data-bs-toggle="tab" data-bs-target="#config" type="button">Configuration</button></li>
|
||||
<li class="nav-item"><button class="nav-link" id="detection-tab" data-bs-toggle="tab" data-bs-target="#detection" type="button">Detection History</button></li>
|
||||
<li class="nav-item"><button class="nav-link" id="execution-tab" data-bs-toggle="tab" data-bs-target="#execution" type="button">Execution History</button></li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane fade show active" id="config" role="tabpanel">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Configuration</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="enabled" checked?={ data["enabled"].(bool) }>
|
||||
<label class="form-check-label" for="enabled">Enabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>Configuration UI coming soon</div>
|
||||
<button type="button" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="detection" role="tabpanel">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<div class="d-flex justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Detection History</h6>
|
||||
<button type="button" class="btn btn-sm btn-primary">Run Detection</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
if len(data["detection_history"].([]map[string]interface{})) == 0 {
|
||||
<div class="alert alert-info">No detection history</div>
|
||||
} else {
|
||||
<table class="table table-sm">
|
||||
<thead><tr><th>Timestamp</th><th>Duration</th><th>Jobs Found</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
for _, rec := range data["detection_history"].([]map[string]interface{}) {
|
||||
<tr>
|
||||
<td>{ fmt.Sprintf("%v", rec["timestamp"]) }</td>
|
||||
<td>{ fmt.Sprintf("%vms", rec["duration_ms"]) }</td>
|
||||
<td>{ fmt.Sprintf("%v", rec["jobs_detected"]) }</td>
|
||||
<td><span class="badge bg-success">{ fmt.Sprintf("%v", rec["status"]) }</span></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="execution" role="tabpanel">
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Execution History</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
if len(data["execution_history"].([]map[string]interface{})) == 0 {
|
||||
<div class="alert alert-info">No execution history</div>
|
||||
} else {
|
||||
<table class="table table-sm">
|
||||
<thead><tr><th>Job ID</th><th>Started</th><th>Duration</th><th>Items</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
for _, rec := range data["execution_history"].([]map[string]interface{}) {
|
||||
<tr>
|
||||
<td><code>{ fmt.Sprintf("%v", rec["job_id"]) }</code></td>
|
||||
<td>{ fmt.Sprintf("%v", rec["started_at"]) }</td>
|
||||
<td>{ fmt.Sprintf("%vms", rec["duration_ms"]) }</td>
|
||||
<td>{ fmt.Sprintf("%v", rec["items_processed"]) }</td>
|
||||
<td>
|
||||
if rec["success"].(bool) {
|
||||
<span class="badge bg-success">Success</span>
|
||||
} else {
|
||||
<span class="badge bg-danger">Failed</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.977
|
||||
package app
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func PluginConfigPage(data map[string]interface{}) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\"><h1 class=\"h2\"><i class=\"fas fa-cog me-2\"></i>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["job_type"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 9, Col: 85}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " Config</h1><a href=\"/plugins\" class=\"btn btn-sm btn-secondary\">Back</a></div><ul class=\"nav nav-tabs mb-4\" role=\"tablist\"><li class=\"nav-item\"><button class=\"nav-link active\" id=\"config-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#config\" type=\"button\">Configuration</button></li><li class=\"nav-item\"><button class=\"nav-link\" id=\"detection-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#detection\" type=\"button\">Detection History</button></li><li class=\"nav-item\"><button class=\"nav-link\" id=\"execution-tab\" data-bs-toggle=\"tab\" data-bs-target=\"#execution\" type=\"button\">Execution History</button></li></ul><div class=\"tab-content\"><div class=\"tab-pane fade show active\" id=\"config\" role=\"tabpanel\"><div class=\"card shadow\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\">Configuration</h6></div><div class=\"card-body\"><div class=\"mb-3\"><div class=\"form-check form-switch\"><input class=\"form-check-input\" type=\"checkbox\" id=\"enabled\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if data["enabled"].(bool) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " checked")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "> <label class=\"form-check-label\" for=\"enabled\">Enabled</label></div></div><div class=\"alert alert-info\"><i class=\"fas fa-info-circle me-2\"></i>Configuration UI coming soon</div><button type=\"button\" class=\"btn btn-primary\">Save</button></div></div></div><div class=\"tab-pane fade\" id=\"detection\" role=\"tabpanel\"><div class=\"card shadow\"><div class=\"card-header py-3\"><div class=\"d-flex justify-content-between\"><h6 class=\"m-0 font-weight-bold text-primary\">Detection History</h6><button type=\"button\" class=\"btn btn-sm btn-primary\">Run Detection</button></div></div><div class=\"card-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data["detection_history"].([]map[string]interface{})) == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"alert alert-info\">No detection history</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table class=\"table table-sm\"><thead><tr><th>Timestamp</th><th>Duration</th><th>Jobs Found</th><th>Status</th></tr></thead> <tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, rec := range data["detection_history"].([]map[string]interface{}) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<tr><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", rec["timestamp"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 55, Col: 51}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%vms", rec["duration_ms"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 56, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", rec["jobs_detected"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 57, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</td><td><span class=\"badge bg-success\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", rec["status"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 58, Col: 79}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</span></td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</tbody></table>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></div></div><div class=\"tab-pane fade\" id=\"execution\" role=\"tabpanel\"><div class=\"card shadow\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\">Execution History</h6></div><div class=\"card-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data["execution_history"].([]map[string]interface{})) == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"alert alert-info\">No execution history</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<table class=\"table table-sm\"><thead><tr><th>Job ID</th><th>Started</th><th>Duration</th><th>Items</th><th>Status</th></tr></thead> <tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, rec := range data["execution_history"].([]map[string]interface{}) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "<tr><td><code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", rec["job_id"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 82, Col: 54}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</code></td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", rec["started_at"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 83, Col: 52}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%vms", rec["duration_ms"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 84, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", rec["items_processed"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_config.templ`, Line: 85, Col: 57}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if rec["success"].(bool) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<span class=\"badge bg-success\">Success</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<span class=\"badge bg-danger\">Failed</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</tbody></table>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -0,0 +1,60 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
templ PluginJobsPage(data map[string]interface{}) {
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2"><i class="fas fa-list me-2"></i>{ fmt.Sprintf("%v", data["job_type"]) } Jobs</h1>
|
||||
<div>
|
||||
<a href="/plugins" class="btn btn-sm btn-secondary me-2">Back</a>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="location.reload();">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-lg-3"><div class="card"><div class="card-body"><div class="text-xs font-weight-bold text-warning text-uppercase mb-1">Pending</div><div class="h5 mb-0">{ fmt.Sprintf("%v", data["pending_count"]) }</div></div></div></div>
|
||||
<div class="col-lg-3"><div class="card"><div class="card-body"><div class="text-xs font-weight-bold text-info text-uppercase mb-1">Running</div><div class="h5 mb-0">{ fmt.Sprintf("%v", data["running_count"]) }</div></div></div></div>
|
||||
<div class="col-lg-3"><div class="card"><div class="card-body"><div class="text-xs font-weight-bold text-success text-uppercase mb-1">Completed</div><div class="h5 mb-0">{ fmt.Sprintf("%v", data["completed_count"]) }</div></div></div></div>
|
||||
<div class="col-lg-3"><div class="card"><div class="card-body"><div class="text-xs font-weight-bold text-danger text-uppercase mb-1">Failed</div><div class="h5 mb-0">{ fmt.Sprintf("%v", data["failed_count"]) }</div></div></div></div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Job List</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
if len(data["jobs"].([]map[string]interface{})) == 0 {
|
||||
<div class="alert alert-info">No jobs found</div>
|
||||
} else {
|
||||
<table class="table table-sm table-hover">
|
||||
<thead><tr><th>Job ID</th><th>Description</th><th>State</th><th>Progress</th><th>Created</th></tr></thead>
|
||||
<tbody>
|
||||
for _, job := range data["jobs"].([]map[string]interface{}) {
|
||||
<tr>
|
||||
<td><code>{ fmt.Sprintf("%v", job["id"]) }</code></td>
|
||||
<td>{ fmt.Sprintf("%v", job["description"]) }</td>
|
||||
<td><span class={ "badge", jobStateClass(fmt.Sprintf("%v", job["state"])) }>{ fmt.Sprintf("%v", job["state"]) }</span></td>
|
||||
<td>
|
||||
<div class="progress" style="height: 20px;">
|
||||
<div class="progress-bar" style={ "width: " + fmt.Sprintf("%v%%", job["progress"]) }></div>
|
||||
</div>
|
||||
</td>
|
||||
<td><small>{ fmt.Sprintf("%v", job["created_at"]) }</small></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
func jobStateClass(s string) string {
|
||||
if s == "pending" { return "bg-warning" }
|
||||
if s == "running" { return "bg-info" }
|
||||
if s == "completed" { return "bg-success" }
|
||||
if s == "failed" { return "bg-danger" }
|
||||
return "bg-secondary"
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.977
|
||||
package app
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func PluginJobsPage(data map[string]interface{}) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\"><h1 class=\"h2\"><i class=\"fas fa-list me-2\"></i>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["job_type"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 9, Col: 86}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " Jobs</h1><div><a href=\"/plugins\" class=\"btn btn-sm btn-secondary me-2\">Back</a> <button type=\"button\" class=\"btn btn-sm btn-primary\" onclick=\"location.reload();\">Refresh</button></div></div><div class=\"row mb-4\"><div class=\"col-lg-3\"><div class=\"card\"><div class=\"card-body\"><div class=\"text-xs font-weight-bold text-warning text-uppercase mb-1\">Pending</div><div class=\"h5 mb-0\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["pending_count"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 17, Col: 212}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div></div></div><div class=\"col-lg-3\"><div class=\"card\"><div class=\"card-body\"><div class=\"text-xs font-weight-bold text-info text-uppercase mb-1\">Running</div><div class=\"h5 mb-0\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["running_count"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 18, Col: 209}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div></div></div></div><div class=\"col-lg-3\"><div class=\"card\"><div class=\"card-body\"><div class=\"text-xs font-weight-bold text-success text-uppercase mb-1\">Completed</div><div class=\"h5 mb-0\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["completed_count"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 19, Col: 216}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div></div></div></div><div class=\"col-lg-3\"><div class=\"card\"><div class=\"card-body\"><div class=\"text-xs font-weight-bold text-danger text-uppercase mb-1\">Failed</div><div class=\"h5 mb-0\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["failed_count"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 20, Col: 209}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</div></div></div></div></div><div class=\"card shadow\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\">Job List</h6></div><div class=\"card-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data["jobs"].([]map[string]interface{})) == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"alert alert-info\">No jobs found</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<table class=\"table table-sm table-hover\"><thead><tr><th>Job ID</th><th>Description</th><th>State</th><th>Progress</th><th>Created</th></tr></thead> <tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, job := range data["jobs"].([]map[string]interface{}) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "<tr><td><code>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", job["id"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 36, Col: 48}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "</code></td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", job["description"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 37, Col: 51}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 = []any{"badge", jobStateClass(fmt.Sprintf("%v", job["state"]))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<span class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var9).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", job["state"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 38, Col: 117}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</span></td><td><div class=\"progress\" style=\"height: 20px;\"><div class=\"progress-bar\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 string
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("width: " + fmt.Sprintf("%v%%", job["progress"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 41, Col: 92}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "\"></div></div></td><td><small>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 string
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", job["created_at"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugin_jobs.templ`, Line: 44, Col: 57}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</small></td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</tbody></table>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func jobStateClass(s string) string {
|
||||
if s == "pending" {
|
||||
return "bg-warning"
|
||||
}
|
||||
if s == "running" {
|
||||
return "bg-info"
|
||||
}
|
||||
if s == "completed" {
|
||||
return "bg-success"
|
||||
}
|
||||
if s == "failed" {
|
||||
return "bg-danger"
|
||||
}
|
||||
return "bg-secondary"
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -0,0 +1,118 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
templ PluginsPage(data map[string]interface{}) {
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
|
||||
<h1 class="h2"><i class="fas fa-puzzle-piece me-2"></i>Plugin System</h1>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="location.reload();"><i class="fas fa-sync me-1"></i>Refresh</button>
|
||||
</div>
|
||||
|
||||
<div class="row mb-4">
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-primary shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Connected Plugins</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{ fmt.Sprintf("%v", data["total_plugins"]) }</div>
|
||||
</div>
|
||||
<div class="col-auto"><i class="fas fa-plug fa-2x text-gray-300"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-success shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-success text-uppercase mb-1">Total Jobs</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{ fmt.Sprintf("%v", data["total_jobs"]) }</div>
|
||||
</div>
|
||||
<div class="col-auto"><i class="fas fa-tasks fa-2x text-gray-300"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-3 col-md-6 mb-4">
|
||||
<div class="card border-left-info shadow h-100 py-2">
|
||||
<div class="card-body">
|
||||
<div class="row no-gutters align-items-center">
|
||||
<div class="col mr-2">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Job Types</div>
|
||||
<div class="h5 mb-0 font-weight-bold text-gray-800">{ fmt.Sprintf("%v", len(data["job_types"].([]string))) }</div>
|
||||
</div>
|
||||
<div class="col-auto"><i class="fas fa-cubes fa-2x text-gray-300"></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary"><i class="fas fa-plug me-2"></i>Connected Plugins</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
if len(data["plugins"].([]map[string]interface{})) == 0 {
|
||||
<div class="alert alert-info"><i class="fas fa-info-circle me-2"></i>No plugins connected</div>
|
||||
} else {
|
||||
<table class="table table-hover table-sm">
|
||||
<thead><tr><th>Name</th><th>Version</th><th>State</th><th>Health</th><th>Jobs</th></tr></thead>
|
||||
<tbody>
|
||||
for _, p := range data["plugins"].([]map[string]interface{}) {
|
||||
<tr>
|
||||
<td>{ fmt.Sprintf("%v", p["name"]) }</td>
|
||||
<td>{ fmt.Sprintf("%v", p["version"]) }</td>
|
||||
<td><span class={ "badge", stateClass(fmt.Sprintf("%v", p["state"])) }>{ fmt.Sprintf("%v", p["state"]) }</span></td>
|
||||
<td>
|
||||
if p["healthy"].(bool) {
|
||||
<span class="badge bg-success">Healthy</span>
|
||||
} else {
|
||||
<span class="badge bg-danger">Unhealthy</span>
|
||||
}
|
||||
</td>
|
||||
<td>Running: { fmt.Sprintf("%v", p["running_jobs"]) }</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary"><i class="fas fa-cubes me-2"></i>Job Types</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
if len(data["job_types"].([]string)) == 0 {
|
||||
<div class="alert alert-info">No job types available</div>
|
||||
} else {
|
||||
<div class="row">
|
||||
for _, jt := range data["job_types"].([]string) {
|
||||
<div class="col-lg-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{ jt }</h5>
|
||||
<a href={ templ.SafeURL("/plugins/config/" + jt) } class="btn btn-sm btn-primary">Config</a>
|
||||
<a href={ templ.SafeURL("/plugins/jobs/" + jt) } class="btn btn-sm btn-info">Jobs</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
func stateClass(s string) string {
|
||||
if s == "healthy" { return "bg-success" }
|
||||
if s == "connected" { return "bg-info" }
|
||||
if s == "unhealthy" { return "bg-warning" }
|
||||
return "bg-danger"
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.977
|
||||
package app
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func PluginsPage(data map[string]interface{}) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
|
||||
if templ_7745c5c3_Var1 == nil {
|
||||
templ_7745c5c3_Var1 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\"><h1 class=\"h2\"><i class=\"fas fa-puzzle-piece me-2\"></i>Plugin System</h1><button type=\"button\" class=\"btn btn-sm btn-primary\" onclick=\"location.reload();\"><i class=\"fas fa-sync me-1\"></i>Refresh</button></div><div class=\"row mb-4\"><div class=\"col-xl-3 col-md-6 mb-4\"><div class=\"card border-left-primary shadow h-100 py-2\"><div class=\"card-body\"><div class=\"row no-gutters align-items-center\"><div class=\"col mr-2\"><div class=\"text-xs font-weight-bold text-primary text-uppercase mb-1\">Connected Plugins</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var2 string
|
||||
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["total_plugins"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 20, Col: 101}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</div></div><div class=\"col-auto\"><i class=\"fas fa-plug fa-2x text-gray-300\"></i></div></div></div></div></div><div class=\"col-xl-3 col-md-6 mb-4\"><div class=\"card border-left-success shadow h-100 py-2\"><div class=\"card-body\"><div class=\"row no-gutters align-items-center\"><div class=\"col mr-2\"><div class=\"text-xs font-weight-bold text-success text-uppercase mb-1\">Total Jobs</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 string
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", data["total_jobs"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 33, Col: 98}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</div></div><div class=\"col-auto\"><i class=\"fas fa-tasks fa-2x text-gray-300\"></i></div></div></div></div></div><div class=\"col-xl-3 col-md-6 mb-4\"><div class=\"card border-left-info shadow h-100 py-2\"><div class=\"card-body\"><div class=\"row no-gutters align-items-center\"><div class=\"col mr-2\"><div class=\"text-xs font-weight-bold text-info text-uppercase mb-1\">Job Types</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 string
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", len(data["job_types"].([]string))))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 46, Col: 113}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</div></div><div class=\"col-auto\"><i class=\"fas fa-cubes fa-2x text-gray-300\"></i></div></div></div></div></div></div><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-plug me-2\"></i>Connected Plugins</h6></div><div class=\"card-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data["plugins"].([]map[string]interface{})) == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<div class=\"alert alert-info\"><i class=\"fas fa-info-circle me-2\"></i>No plugins connected</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<table class=\"table table-hover table-sm\"><thead><tr><th>Name</th><th>Version</th><th>State</th><th>Health</th><th>Jobs</th></tr></thead> <tbody>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, p := range data["plugins"].([]map[string]interface{}) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<tr><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var5 string
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", p["name"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 68, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", p["version"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 69, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var7 = []any{"badge", stateClass(fmt.Sprintf("%v", p["state"]))}
|
||||
templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var7...)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<span class=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var8 string
|
||||
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(templ.CSSClasses(templ_7745c5c3_Var7).String())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 1, Col: 0}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", p["state"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 70, Col: 110}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</span></td><td>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if p["healthy"].(bool) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<span class=\"badge bg-success\">Healthy</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<span class=\"badge bg-danger\">Unhealthy</span>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</td><td>Running: ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", p["running_jobs"]))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 78, Col: 59}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</td></tr>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</tbody></table>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-cubes me-2\"></i>Job Types</h6></div><div class=\"card-body\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if len(data["job_types"].([]string)) == 0 {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "<div class=\"alert alert-info\">No job types available</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<div class=\"row\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, jt := range data["job_types"].([]string) {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<div class=\"col-lg-4 mb-3\"><div class=\"card\"><div class=\"card-body\"><h5 class=\"card-title\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(jt)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 100, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</h5><a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var12 templ.SafeURL
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/plugins/config/" + jt))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 101, Col: 57}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "\" class=\"btn btn-sm btn-primary\">Config</a> <a href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var13 templ.SafeURL
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL("/plugins/jobs/" + jt))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/plugins.templ`, Line: 102, Col: 55}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" class=\"btn btn-sm btn-info\">Jobs</a></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func stateClass(s string) string {
|
||||
if s == "healthy" {
|
||||
return "bg-success"
|
||||
}
|
||||
if s == "connected" {
|
||||
return "bg-info"
|
||||
}
|
||||
if s == "unhealthy" {
|
||||
return "bg-warning"
|
||||
}
|
||||
return "bg-danger"
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
@@ -1,232 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test structs that mirror the actual configuration structure
|
||||
type TestBaseConfigForTemplate struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
ScanIntervalSeconds int `json:"scan_interval_seconds"`
|
||||
MaxConcurrent int `json:"max_concurrent"`
|
||||
}
|
||||
|
||||
type TestTaskConfigForTemplate struct {
|
||||
TestBaseConfigForTemplate
|
||||
TaskSpecificField float64 `json:"task_specific_field"`
|
||||
AnotherSpecificField string `json:"another_specific_field"`
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_EmbeddedStructFields(t *testing.T) {
|
||||
config := &TestTaskConfigForTemplate{
|
||||
TestBaseConfigForTemplate: TestBaseConfigForTemplate{
|
||||
Enabled: true,
|
||||
ScanIntervalSeconds: 2400,
|
||||
MaxConcurrent: 5,
|
||||
},
|
||||
TaskSpecificField: 0.18,
|
||||
AnotherSpecificField: "test_value",
|
||||
}
|
||||
|
||||
// Test embedded struct fields
|
||||
tests := []struct {
|
||||
fieldName string
|
||||
expectedValue interface{}
|
||||
description string
|
||||
}{
|
||||
{"enabled", true, "BaseConfig boolean field"},
|
||||
{"scan_interval_seconds", 2400, "BaseConfig integer field"},
|
||||
{"max_concurrent", 5, "BaseConfig integer field"},
|
||||
{"task_specific_field", 0.18, "Task-specific float field"},
|
||||
{"another_specific_field", "test_value", "Task-specific string field"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
result := getTaskFieldValue(config, test.fieldName)
|
||||
|
||||
if result != test.expectedValue {
|
||||
t.Errorf("Field %s: expected %v (%T), got %v (%T)",
|
||||
test.fieldName, test.expectedValue, test.expectedValue, result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_NonExistentField(t *testing.T) {
|
||||
config := &TestTaskConfigForTemplate{
|
||||
TestBaseConfigForTemplate: TestBaseConfigForTemplate{
|
||||
Enabled: true,
|
||||
ScanIntervalSeconds: 1800,
|
||||
MaxConcurrent: 3,
|
||||
},
|
||||
}
|
||||
|
||||
result := getTaskFieldValue(config, "non_existent_field")
|
||||
|
||||
if result != nil {
|
||||
t.Errorf("Expected nil for non-existent field, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_NilConfig(t *testing.T) {
|
||||
var config *TestTaskConfigForTemplate = nil
|
||||
|
||||
result := getTaskFieldValue(config, "enabled")
|
||||
|
||||
if result != nil {
|
||||
t.Errorf("Expected nil for nil config, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_EmptyStruct(t *testing.T) {
|
||||
config := &TestTaskConfigForTemplate{}
|
||||
|
||||
// Test that we can extract zero values
|
||||
tests := []struct {
|
||||
fieldName string
|
||||
expectedValue interface{}
|
||||
description string
|
||||
}{
|
||||
{"enabled", false, "Zero value boolean"},
|
||||
{"scan_interval_seconds", 0, "Zero value integer"},
|
||||
{"max_concurrent", 0, "Zero value integer"},
|
||||
{"task_specific_field", 0.0, "Zero value float"},
|
||||
{"another_specific_field", "", "Zero value string"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.description, func(t *testing.T) {
|
||||
result := getTaskFieldValue(config, test.fieldName)
|
||||
|
||||
if result != test.expectedValue {
|
||||
t.Errorf("Field %s: expected %v (%T), got %v (%T)",
|
||||
test.fieldName, test.expectedValue, test.expectedValue, result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_NonStructConfig(t *testing.T) {
|
||||
var config interface{} = "not a struct"
|
||||
|
||||
result := getTaskFieldValue(config, "enabled")
|
||||
|
||||
if result != nil {
|
||||
t.Errorf("Expected nil for non-struct config, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_PointerToStruct(t *testing.T) {
|
||||
config := &TestTaskConfigForTemplate{
|
||||
TestBaseConfigForTemplate: TestBaseConfigForTemplate{
|
||||
Enabled: false,
|
||||
ScanIntervalSeconds: 900,
|
||||
MaxConcurrent: 2,
|
||||
},
|
||||
TaskSpecificField: 0.35,
|
||||
}
|
||||
|
||||
// Test that pointers are handled correctly
|
||||
enabledResult := getTaskFieldValue(config, "enabled")
|
||||
if enabledResult != false {
|
||||
t.Errorf("Expected false for enabled field, got %v", enabledResult)
|
||||
}
|
||||
|
||||
intervalResult := getTaskFieldValue(config, "scan_interval_seconds")
|
||||
if intervalResult != 900 {
|
||||
t.Errorf("Expected 900 for scan_interval_seconds field, got %v", intervalResult)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_FieldsWithJSONOmitempty(t *testing.T) {
|
||||
// Test struct with omitempty tags
|
||||
type TestConfigWithOmitempty struct {
|
||||
TestBaseConfigForTemplate
|
||||
OptionalField string `json:"optional_field,omitempty"`
|
||||
}
|
||||
|
||||
config := &TestConfigWithOmitempty{
|
||||
TestBaseConfigForTemplate: TestBaseConfigForTemplate{
|
||||
Enabled: true,
|
||||
ScanIntervalSeconds: 1200,
|
||||
MaxConcurrent: 4,
|
||||
},
|
||||
OptionalField: "optional_value",
|
||||
}
|
||||
|
||||
// Test that fields with omitempty are still found
|
||||
result := getTaskFieldValue(config, "optional_field")
|
||||
if result != "optional_value" {
|
||||
t.Errorf("Expected 'optional_value' for optional_field, got %v", result)
|
||||
}
|
||||
|
||||
// Test embedded fields still work
|
||||
enabledResult := getTaskFieldValue(config, "enabled")
|
||||
if enabledResult != true {
|
||||
t.Errorf("Expected true for enabled field, got %v", enabledResult)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTaskFieldValue_DeepEmbedding(t *testing.T) {
|
||||
// Test with multiple levels of embedding
|
||||
type DeepBaseConfig struct {
|
||||
DeepField string `json:"deep_field"`
|
||||
}
|
||||
|
||||
type MiddleConfig struct {
|
||||
DeepBaseConfig
|
||||
MiddleField int `json:"middle_field"`
|
||||
}
|
||||
|
||||
type TopConfig struct {
|
||||
MiddleConfig
|
||||
TopField bool `json:"top_field"`
|
||||
}
|
||||
|
||||
config := &TopConfig{
|
||||
MiddleConfig: MiddleConfig{
|
||||
DeepBaseConfig: DeepBaseConfig{
|
||||
DeepField: "deep_value",
|
||||
},
|
||||
MiddleField: 123,
|
||||
},
|
||||
TopField: true,
|
||||
}
|
||||
|
||||
// Test that deeply embedded fields are found
|
||||
deepResult := getTaskFieldValue(config, "deep_field")
|
||||
if deepResult != "deep_value" {
|
||||
t.Errorf("Expected 'deep_value' for deep_field, got %v", deepResult)
|
||||
}
|
||||
|
||||
middleResult := getTaskFieldValue(config, "middle_field")
|
||||
if middleResult != 123 {
|
||||
t.Errorf("Expected 123 for middle_field, got %v", middleResult)
|
||||
}
|
||||
|
||||
topResult := getTaskFieldValue(config, "top_field")
|
||||
if topResult != true {
|
||||
t.Errorf("Expected true for top_field, got %v", topResult)
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark to ensure performance is reasonable
|
||||
func BenchmarkGetTaskFieldValue(b *testing.B) {
|
||||
config := &TestTaskConfigForTemplate{
|
||||
TestBaseConfigForTemplate: TestBaseConfigForTemplate{
|
||||
Enabled: true,
|
||||
ScanIntervalSeconds: 1800,
|
||||
MaxConcurrent: 3,
|
||||
},
|
||||
TaskSpecificField: 0.25,
|
||||
AnotherSpecificField: "benchmark_test",
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Test both embedded and regular fields
|
||||
_ = getTaskFieldValue(config, "enabled")
|
||||
_ = getTaskFieldValue(config, "task_specific_field")
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// getStatusColor returns Bootstrap color class for status
|
||||
func getStatusColor(status string) string {
|
||||
switch status {
|
||||
case "active", "healthy":
|
||||
return "success"
|
||||
case "warning":
|
||||
return "warning"
|
||||
case "critical", "unreachable":
|
||||
return "danger"
|
||||
default:
|
||||
return "secondary"
|
||||
}
|
||||
}
|
||||
|
||||
// formatBytes converts bytes to human readable format
|
||||
func formatBytes(bytes int64) string {
|
||||
if bytes == 0 {
|
||||
return "0 B"
|
||||
}
|
||||
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
|
||||
var i int
|
||||
value := float64(bytes)
|
||||
|
||||
for value >= 1024 && i < len(units)-1 {
|
||||
value /= 1024
|
||||
i++
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
return fmt.Sprintf("%.0f %s", value, units[i])
|
||||
}
|
||||
return fmt.Sprintf("%.1f %s", value, units[i])
|
||||
}
|
||||
|
||||
// formatNumber formats large numbers with commas
|
||||
func formatNumber(num int64) string {
|
||||
if num == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
str := strconv.FormatInt(num, 10)
|
||||
result := ""
|
||||
|
||||
for i, char := range str {
|
||||
if i > 0 && (len(str)-i)%3 == 0 {
|
||||
result += ","
|
||||
}
|
||||
result += string(char)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// calculatePercent calculates percentage for progress bars
|
||||
func calculatePercent(current, max int) int {
|
||||
if max == 0 {
|
||||
return 0
|
||||
}
|
||||
return (current * 100) / max
|
||||
}
|
||||
@@ -332,9 +332,27 @@ templ Layout(c *gin.Context, content templ.Component) {
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main content -->
|
||||
<!-- PLUGIN SECTION -->
|
||||
<div class="sidebar-menu-group">
|
||||
<h6 class="sidebar-heading px-3 mt-4 mb-1 text-muted">
|
||||
<span>PLUGINS</span>
|
||||
</h6>
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item">
|
||||
if currentPath == "/plugins" {
|
||||
<a class="nav-link active" href="/plugins">
|
||||
<i class="fas fa-puzzle-piece me-2"></i>Plugins
|
||||
</a>
|
||||
} else {
|
||||
<a class="nav-link" href="/plugins">
|
||||
<i class="fas fa-puzzle-piece me-2"></i>Plugins
|
||||
</a>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
|
||||
<div class="pt-3">
|
||||
@content
|
||||
|
||||
@@ -487,7 +487,22 @@ func Layout(c *gin.Context, content templ.Component) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</li></ul></div></div><!-- Main content --><main class=\"col-md-9 ms-sm-auto col-lg-10 px-md-4\"><div class=\"pt-3\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</li></ul></div><!-- PLUGIN SECTION --><div class=\"sidebar-menu-group\"><h6 class=\"sidebar-heading px-3 mt-4 mb-1 text-muted\"><span>PLUGINS</span></h6><ul class=\"nav flex-column\"><li class=\"nav-item\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if currentPath == "/plugins" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<a class=\"nav-link active\" href=\"/plugins\"><i class=\"fas fa-puzzle-piece me-2\"></i>Plugins</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
} else {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<a class=\"nav-link\" href=\"/plugins\"><i class=\"fas fa-puzzle-piece me-2\"></i>Plugins</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</li></ul></div></div><main class=\"col-md-9 ms-sm-auto col-lg-10 px-md-4\"><div class=\"pt-3\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -495,43 +510,43 @@ func Layout(c *gin.Context, content templ.Component) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</div></main></div></div><!-- Footer --><footer class=\"footer mt-auto py-3 bg-light\"><div class=\"container-fluid text-center\"><small class=\"text-muted\">© ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</div></main></div></div><!-- Footer --><footer class=\"footer mt-auto py-3 bg-light\"><div class=\"container-fluid text-center\"><small class=\"text-muted\">© ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", time.Now().Year()))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 350, Col: 60}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 368, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, " SeaweedFS Admin v")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, " SeaweedFS Admin v")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var27 string
|
||||
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(version.VERSION_NUMBER)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 350, Col: 102}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 368, Col: 102}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, " ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, " ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if !strings.Contains(version.VERSION, "enterprise") {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<span class=\"mx-2\">•</span> <a href=\"https://seaweedfs.com\" target=\"_blank\" class=\"text-decoration-none\"><i class=\"fas fa-star me-1\"></i>Enterprise Version Available</a>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<span class=\"mx-2\">•</span> <a href=\"https://seaweedfs.com\" target=\"_blank\" class=\"text-decoration-none\"><i class=\"fas fa-star me-1\"></i>Enterprise Version Available</a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</small></div></footer><!-- Bootstrap JS --><script src=\"/static/js/bootstrap.bundle.min.js\"></script><!-- Modal Alerts JS (replaces native alert/confirm) --><script src=\"/static/js/modal-alerts.js\"></script><!-- Custom JS --><script src=\"/static/js/admin.js\"></script><script src=\"/static/js/iam-utils.js\"></script><script src=\"/static/js/s3tables.js\"></script></body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</small></div></footer><!-- Bootstrap JS --><script src=\"/static/js/bootstrap.bundle.min.js\"></script><!-- Modal Alerts JS (replaces native alert/confirm) --><script src=\"/static/js/modal-alerts.js\"></script><!-- Custom JS --><script src=\"/static/js/admin.js\"></script><script src=\"/static/js/iam-utils.js\"></script><script src=\"/static/js/s3tables.js\"></script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@@ -560,56 +575,56 @@ func LoginForm(c *gin.Context, title string, errorMessage string) templ.Componen
|
||||
templ_7745c5c3_Var28 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<!doctype html><html lang=\"en\"><head><meta charset=\"UTF-8\"><title>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var29 string
|
||||
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 378, Col: 17}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 396, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, " - Login</title><link rel=\"icon\" href=\"/static/favicon.ico\" type=\"image/x-icon\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/bootstrap.min.css\" rel=\"stylesheet\"><link href=\"/static/css/fontawesome.min.css\" rel=\"stylesheet\"></head><body class=\"bg-light\"><div class=\"container\"><div class=\"row justify-content-center min-vh-100 align-items-center\"><div class=\"col-md-6 col-lg-4\"><div class=\"card shadow\"><div class=\"card-body p-5\"><div class=\"text-center mb-4\"><i class=\"fas fa-server fa-3x text-primary mb-3\"></i><h4 class=\"card-title\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, " - Login</title><link rel=\"icon\" href=\"/static/favicon.ico\" type=\"image/x-icon\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><link href=\"/static/css/bootstrap.min.css\" rel=\"stylesheet\"><link href=\"/static/css/fontawesome.min.css\" rel=\"stylesheet\"></head><body class=\"bg-light\"><div class=\"container\"><div class=\"row justify-content-center min-vh-100 align-items-center\"><div class=\"col-md-6 col-lg-4\"><div class=\"card shadow\"><div class=\"card-body p-5\"><div class=\"text-center mb-4\"><i class=\"fas fa-server fa-3x text-primary mb-3\"></i><h4 class=\"card-title\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var30 string
|
||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(title)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 392, Col: 57}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 410, Col: 57}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</h4><p class=\"text-muted\">Please sign in to continue</p></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</h4><p class=\"text-muted\">Please sign in to continue</p></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if errorMessage != "" {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "<div class=\"alert alert-danger\" role=\"alert\"><i class=\"fas fa-exclamation-triangle me-2\"></i> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<div class=\"alert alert-danger\" role=\"alert\"><i class=\"fas fa-exclamation-triangle me-2\"></i> ")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var31 string
|
||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 399, Col: 45}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/layout/layout.templ`, Line: 417, Col: 45}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<form method=\"POST\" action=\"/login\"><div class=\"mb-3\"><label for=\"username\" class=\"form-label\">Username</label><div class=\"input-group\"><span class=\"input-group-text\"><i class=\"fas fa-user\"></i></span> <input type=\"text\" class=\"form-control\" id=\"username\" name=\"username\" required></div></div><div class=\"mb-4\"><label for=\"password\" class=\"form-label\">Password</label><div class=\"input-group\"><span class=\"input-group-text\"><i class=\"fas fa-lock\"></i></span> <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\" required></div></div><button type=\"submit\" class=\"btn btn-primary w-100\"><i class=\"fas fa-sign-in-alt me-2\"></i>Sign In</button></form></div></div></div></div></div><script src=\"/static/js/bootstrap.bundle.min.js\"></script></body></html>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<form method=\"POST\" action=\"/login\"><div class=\"mb-3\"><label for=\"username\" class=\"form-label\">Username</label><div class=\"input-group\"><span class=\"input-group-text\"><i class=\"fas fa-user\"></i></span> <input type=\"text\" class=\"form-control\" id=\"username\" name=\"username\" required></div></div><div class=\"mb-4\"><label for=\"password\" class=\"form-label\">Password</label><div class=\"input-group\"><span class=\"input-group-text\"><i class=\"fas fa-lock\"></i></span> <input type=\"password\" class=\"form-control\" id=\"password\" name=\"password\" required></div></div><button type=\"submit\" class=\"btn btn-primary w-100\"><i class=\"fas fa-sign-in-alt me-2\"></i>Sign In</button></form></div></div></div></div></div><script src=\"/static/js/bootstrap.bundle.min.js\"></script></body></html>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user