Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions gearbox/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ import (
// Import plugins - blank identifier triggers init() registration
_ "github.com/sarg3nt/gearbox/internal/plugins/alerts"
_ "github.com/sarg3nt/gearbox/internal/plugins/certificates"
dashboardPlugin "github.com/sarg3nt/gearbox/internal/plugins/dashboard"
_ "github.com/sarg3nt/gearbox/internal/plugins/dashboard"
_ "github.com/sarg3nt/gearbox/internal/plugins/haproxy"
_ "github.com/sarg3nt/gearbox/internal/plugins/logs"
_ "github.com/sarg3nt/gearbox/internal/plugins/metrics"
_ "github.com/sarg3nt/gearbox/internal/plugins/services"
Expand Down Expand Up @@ -371,11 +372,6 @@ func main() {
log.Fatalf("Failed to register core widgets: %v", err)
}

// Register HAProxy-specific widgets
if err := dashboardPlugin.RegisterHAProxyWidgets(widgetRegistry); err != nil {
log.Fatalf("Failed to register HAProxy widgets: %v", err)
}

dashboardHandler := handler.NewDashboardHandler(
dashboardStorage,
widgetRegistry,
Expand Down
10 changes: 9 additions & 1 deletion gearbox/internal/framework/handler/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,16 @@ func (h *DashboardHandler) ViewDashboard(w http.ResponseWriter, r *http.Request)
return
}

// Resolve server ID: use stored value, or fall back to first enabled server
serverID := h.boxID
if serverID == "" {
if boxes, err := h.db.GetEnabledBoxes(); err == nil && len(boxes) > 0 {
serverID = boxes[0].BoxID
}
}

// Render dashboard
content, err := h.renderer.Render(r.Context(), dash, h.boxID, h.userID)
content, err := h.renderer.Render(r.Context(), dash, serverID, h.userID)
if err != nil {
h.logger.Error("failed to render dashboard", "slug", slug, "error", err)
http.Error(w, "Failed to render dashboard", http.StatusInternalServerError)
Expand Down
11 changes: 11 additions & 0 deletions gearbox/internal/framework/templates/layouts/base.templ
Original file line number Diff line number Diff line change
Expand Up @@ -1443,6 +1443,12 @@ templ SidebarIconOSUpdates() {
</svg>
}

templ SidebarIconHAProxy() {
<svg class="w-6 h-6 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"></path>
</svg>
}

templ SidebarLinkWithBadge(href string, label string, icon templ.Component, currentPath string, badgeID string) {
<li>
if isActivePath(href, currentPath) {
Expand Down Expand Up @@ -1655,6 +1661,9 @@ templ OrderedIntegrationLinks(currentPath string) {
// If integrations list is empty (no server configured), show nothing
} else {
// Fallback to default order only if context doesn't exist at all (backwards compatibility)
if canViewIntegration(ctx, "haproxy") {
@SidebarLinkWithIntegration("/haproxy", "HAProxy", SidebarIconHAProxy(), currentPath, "haproxy")
}
if canViewIntegration(ctx, "metrics") {
@SidebarLinkWithIntegration("/history", "Metrics", SidebarIconMetrics(), currentPath, "metrics")
}
Expand Down Expand Up @@ -1682,6 +1691,8 @@ templ OrderedIntegrationLinks(currentPath string) {
// renderIntegrationLink renders a single integration link based on the integration name.
templ renderIntegrationLink(name string, currentPath string) {
switch name {
case "haproxy":
@SidebarLinkDraggable("/haproxy", "HAProxy", SidebarIconHAProxy(), currentPath, "haproxy")
case "metrics":
@SidebarLinkDraggable("/history", "Metrics", SidebarIconMetrics(), currentPath, "metrics")
case "logs":
Expand Down
5 changes: 5 additions & 0 deletions gearbox/internal/framework/widget/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ func (w *WidgetInstance) Render(ctx *WidgetRenderContext) (templ.Component, erro
}
}

// If widget config has no box_id set, use the server ID from the render context
if boxID, _ := w.Config["box_id"].(string); boxID == "" && ctx.ServerID != "" {
w.Config["box_id"] = ctx.ServerID
}

// Render the widget
return w.Definition.Renderer(ctx.Context, w.Config, data)
}
Expand Down
12 changes: 1 addition & 11 deletions gearbox/internal/plugins/haproxy/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func (p *Plugin) PredefinedDashboards() []plugin.DashboardDefinition {
DefaultEnabled: true,
YAML: `version: "1.0"
name: HAProxy Overview
description: HAProxy monitoring dashboard with status summary, backend grid, and system metrics
description: HAProxy monitoring dashboard with status summary and backend grid
created_by: plugin:haproxy
plugin_name: haproxy
editable: false
Expand Down Expand Up @@ -131,16 +131,6 @@ widgets:
default_collapsed: false
server_id: ""
show_filters: true
- id: system-metrics-1
type: system-metrics
position:
row: 3
column: 1
width: 12
height: auto
config:
server_id: ""
show_title: true
`,
},
}
Expand Down
92 changes: 0 additions & 92 deletions gearbox/internal/plugins/haproxy/widgets.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ func RegisterHAProxyWidgets(registry *widget.Registry) error {
widgets := []*widget.WidgetDefinition{
statusSummaryDoughnutsDefinition(),
backendStatusGridDefinition(),
systemMetricsWidgetDefinition(),
serviceStatusWidgetDefinition(),
certificateWarningsWidgetDefinition(),
}

for _, w := range widgets {
Expand Down Expand Up @@ -100,92 +97,3 @@ func backendStatusGridDefinition() *widget.WidgetDefinition {
}
}

// systemMetricsWidgetDefinition creates the System Metrics widget.
// Shows CPU, Memory, Disk, and Network metrics.
func systemMetricsWidgetDefinition() *widget.WidgetDefinition {
return &widget.WidgetDefinition{
Type: "system-metrics",
Name: "System Metrics",
Description: "CPU, memory, disk, and network metrics with real-time updates",
Category: "system-monitoring",
PluginName: "metrics",
Icon: "cpu",
ConfigSchema: widget.ConfigSchema{
Properties: map[string]widget.Property{
"box_id": {
Type: "string",
Description: "Server ID to monitor (optional, uses current server if not specified)",
},
"show_title": {
Type: "boolean",
Description: "Show section title",
Default: true,
},
},
Required: []string{},
},
Renderer: func(ctx context.Context, config map[string]any, data any) (templ.Component, error) {
boxID := getStringFromConfig(config, "box_id", "")
return SystemMetricsWidgetHTMX(boxID), nil
},
}
}

// serviceStatusWidgetDefinition creates the Service Status widget.
// Shows status of HAProxy, gearbox-agent, nftables, and fail2ban services.
func serviceStatusWidgetDefinition() *widget.WidgetDefinition {
return &widget.WidgetDefinition{
Type: "service-status",
Name: "Service Status",
Description: "Status indicators for critical system services",
Category: "system-monitoring",
PluginName: "services",
Icon: "shield",
ConfigSchema: widget.ConfigSchema{
Properties: map[string]widget.Property{
"box_id": {
Type: "string",
Description: "Server ID to monitor (optional, uses current server if not specified)",
},
},
Required: []string{},
},
Renderer: func(ctx context.Context, config map[string]any, data any) (templ.Component, error) {
boxID := getStringFromConfig(config, "box_id", "")
// Service status is included in the metrics widget, so just show metrics
return SystemMetricsWidgetHTMX(boxID), nil
},
}
}

// certificateWarningsWidgetDefinition creates the Certificate Warnings widget.
// Shows warnings for expired or expiring SSL/TLS certificates.
func certificateWarningsWidgetDefinition() *widget.WidgetDefinition {
return &widget.WidgetDefinition{
Type: "certificate-warnings",
Name: "Certificate Warnings",
Description: "Warnings for expired or expiring SSL/TLS certificates",
Category: "security-monitoring",
PluginName: "certificates",
Icon: "alert-triangle",
ConfigSchema: widget.ConfigSchema{
Properties: map[string]widget.Property{
"box_id": {
Type: "string",
Description: "Server ID to monitor (optional, uses current server if not specified)",
},
"hide_when_empty": {
Type: "boolean",
Description: "Hide widget when there are no warnings",
Default: true,
},
},
Required: []string{},
},
Renderer: func(ctx context.Context, config map[string]any, data any) (templ.Component, error) {
// Certificate warnings are loaded via JavaScript on the overview page
// For now, return an empty component - this will be implemented with proper data sources later
return templ.Raw("<!-- Certificate warnings loaded via JavaScript -->"), nil
},
}
}
20 changes: 0 additions & 20 deletions gearbox/internal/plugins/haproxy/widgets_htmx.templ
Original file line number Diff line number Diff line change
Expand Up @@ -45,23 +45,3 @@ templ HAProxyBackendGridWidgetHTMX(serverID string) {
</div>
}

// SystemMetricsWidgetHTMX renders a placeholder that loads via HTMX
templ SystemMetricsWidgetHTMX(serverID string) {
<div
class="system-metrics-widget"
hx-get={ fmt.Sprintf("/htmx/%s/metrics", serverID) }
hx-trigger="load"
hx-swap="innerHTML"
>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-300 dark:border-slate-600 p-6">
<div class="animate-pulse">
<div class="h-4 bg-gray-300 dark:bg-slate-700 rounded w-1/3 mb-4"></div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="h-24 bg-gray-300 dark:bg-slate-700 rounded"></div>
<div class="h-24 bg-gray-300 dark:bg-slate-700 rounded"></div>
<div class="h-24 bg-gray-300 dark:bg-slate-700 rounded"></div>
</div>
</div>
</div>
</div>
}
Loading