feat: integrate Grafana dashboards into BOC DashboardPage
- Add Infrastructure Health section with CPU/Memory/Disk panels - Add Service Status section with PM2/Docker panels - Create GrafanaPanel component for iframe embedding - Build passes successfully
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TenantContext key for storing tenant ID
|
||||
type TenantContextKey struct{}
|
||||
|
||||
// TenantConfig holds tenant configuration
|
||||
type TenantConfig struct {
|
||||
ID string
|
||||
Name string
|
||||
Slug string
|
||||
Domain string
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
// MultiTenancy middleware handles tenant identification and isolation
|
||||
func MultiTenancy(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Extract tenant from multiple sources (in priority order)
|
||||
tenantID := extractTenantID(r)
|
||||
|
||||
if tenantID == "" {
|
||||
http.Error(w, `{"error":"tenant not identified"}`, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Add tenant to context
|
||||
ctx := context.WithValue(r.Context(), TenantContextKey{}, tenantID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// extractTenantID tries multiple methods to identify tenant
|
||||
func extractTenantID(r *http.Request) string {
|
||||
// 1. Header (for API clients)
|
||||
if tenantID := r.Header.Get("X-Tenant-ID"); tenantID != "" {
|
||||
return tenantID
|
||||
}
|
||||
|
||||
// 2. Subdomain (e.g., landvex.boc.aamos.systems)
|
||||
host := r.Host
|
||||
if idx := strings.Index(host, "."); idx > 0 {
|
||||
subdomain := host[:idx]
|
||||
if subdomain != "www" && subdomain != "boc" {
|
||||
// Map subdomain to tenant ID
|
||||
return resolveSubdomain(subdomain)
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Query parameter (for testing/debugging)
|
||||
if tenantID := r.URL.Query().Get("tenant"); tenantID != "" {
|
||||
return tenantID
|
||||
}
|
||||
|
||||
// 4. JWT token claim (if authenticated)
|
||||
// This would be handled by auth middleware
|
||||
|
||||
// 5. Default tenant (for backward compatibility)
|
||||
return "default"
|
||||
}
|
||||
|
||||
// resolveSubdomain maps subdomain to tenant ID
|
||||
func resolveSubdomain(subdomain string) string {
|
||||
// In production, this would query the database
|
||||
// For now, use a simple mapping
|
||||
subdomainMap := map[string]string{
|
||||
"landvex": "11111111-1111-1111-1111-111111111111",
|
||||
"landvex-ab": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
|
||||
"quixzoom": "quixzoom-tenant-id",
|
||||
"aamos": "aamos-tenant-id",
|
||||
}
|
||||
|
||||
if id, ok := subdomainMap[subdomain]; ok {
|
||||
return id
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTenantID retrieves tenant ID from context
|
||||
func GetTenantID(ctx context.Context) string {
|
||||
if tenantID, ok := ctx.Value(TenantContextKey{}).(string); ok {
|
||||
return tenantID
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// TenantIsolation ensures all database queries are scoped to tenant
|
||||
func TenantIsolation(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tenantID := GetTenantID(r.Context())
|
||||
if tenantID == "" {
|
||||
http.Error(w, `{"error":"tenant isolation required"}`, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user