Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
65 changes: 33 additions & 32 deletions models/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,45 @@ import (

// Resource represents a Pangolin resource
type Resource struct {
ID string `json:"id"` // Internal UUID (stable, never changes)
PangolinRouterID string `json:"pangolin_router_id"` // Pangolin's router ID (can change)
Host string `json:"host"`
ServiceID string `json:"service_id"`
OrgID string `json:"org_id"`
SiteID string `json:"site_id"`
Status string `json:"status"`
ID string `json:"id"` // Internal UUID (stable, never changes)
PangolinRouterID string `json:"pangolin_router_id"` // Pangolin's router ID (can change)
Host string `json:"host"`
ServiceID string `json:"service_id"`
OrgID string `json:"org_id"`
SiteID string `json:"site_id"`
Status string `json:"status"`

// HTTP router configuration
Entrypoints string `json:"entrypoints"`
Entrypoints string `json:"entrypoints"`

// TLS certificate configuration
TLSDomains string `json:"tls_domains"`
TLSDomains string `json:"tls_domains"`

// TCP SNI routing configuration
TCPEnabled bool `json:"tcp_enabled"`
TCPEntrypoints string `json:"tcp_entrypoints"`
TCPSNIRule string `json:"tcp_sni_rule"`
TCPEnabled bool `json:"tcp_enabled"`
TCPEntrypoints string `json:"tcp_entrypoints"`
TCPSNIRule string `json:"tcp_sni_rule"`

// Custom headers configuration
CustomHeaders string `json:"custom_headers"`
CustomHeaders string `json:"custom_headers"`

// Router priority configuration
RouterPriority int `json:"router_priority"`
RouterPriority int `json:"router_priority"`

// Source type for tracking data origin
SourceType string `json:"source_type"`
SourceType string `json:"source_type"`

// mTLS configuration
MTLSEnabled bool `json:"mtls_enabled"`
MTLSEnabled bool `json:"mtls_enabled"`

// TLS Hardening configuration (standalone, disabled when mTLS is active)
TLSHardeningEnabled bool `json:"tls_hardening_enabled"`

// Secure Headers configuration
SecureHeadersEnabled bool `json:"secure_headers_enabled"`

CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

// PangolinResource represents the format of a resource from Pangolin API
Expand All @@ -58,9 +58,10 @@ type PangolinResource struct {
// PangolinTraefikConfig represents the Traefik configuration from Pangolin API
type PangolinTraefikConfig struct {
HTTP struct {
Routers map[string]PangolinRouter `json:"routers"`
Services map[string]PangolinService `json:"services"`
Middlewares map[string]map[string]interface{} `json:"middlewares"`
Routers map[string]PangolinRouter `json:"routers"`
Services map[string]PangolinService `json:"services"`
Middlewares map[string]map[string]interface{} `json:"middlewares"`
ServersTransports map[string]interface{} `json:"serversTransports"`
} `json:"http"`
}

Expand Down Expand Up @@ -96,7 +97,7 @@ type PangolinServiceConfig struct {
type TraefikService struct {
Name string `json:"name"`
Provider string `json:"provider"`

// Service types - only one will be populated based on service type
LoadBalancer *struct {
Servers []struct {
Expand All @@ -108,7 +109,7 @@ type TraefikService struct {
Sticky interface{} `json:"sticky,omitempty"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"loadBalancer,omitempty"`

Weighted *struct {
Services []struct {
Name string `json:"name"`
Expand All @@ -117,21 +118,21 @@ type TraefikService struct {
Sticky interface{} `json:"sticky,omitempty"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"weighted,omitempty"`

Mirroring *struct {
Service string `json:"service"`
Mirrors []struct {
Service string `json:"service"`
Mirrors []struct {
Name string `json:"name"`
Percent int `json:"percent"`
} `json:"mirrors,omitempty"`
MaxBodySize *int `json:"maxBodySize,omitempty"`
MirrorBody *bool `json:"mirrorBody,omitempty"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"mirroring,omitempty"`

Failover *struct {
Service string `json:"service"`
Fallback string `json:"fallback"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"failover,omitempty"`
}
}
24 changes: 14 additions & 10 deletions services/config_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ type ProxiedTraefikConfig struct {

// HTTPConfig represents HTTP configuration section
type HTTPConfig struct {
Middlewares map[string]interface{} `json:"middlewares,omitempty"`
Routers map[string]interface{} `json:"routers,omitempty"`
Services map[string]interface{} `json:"services,omitempty"`
Middlewares map[string]interface{} `json:"middlewares,omitempty"`
Routers map[string]interface{} `json:"routers,omitempty"`
Services map[string]interface{} `json:"services,omitempty"`
ServersTransports map[string]interface{} `json:"serversTransports,omitempty"`
}

// TCPConfig represents TCP configuration section
Expand All @@ -53,12 +54,12 @@ type TLSConfig struct {
// OrderedRouter represents a Traefik HTTP router with fields in Pangolin's order.
// The JSON field order matches Pangolin API output for consistency.
type OrderedRouter struct {
EntryPoints []string `json:"entryPoints,omitempty"`
Middlewares []string `json:"middlewares,omitempty"`
Service string `json:"service,omitempty"`
Rule string `json:"rule,omitempty"`
Priority int `json:"priority,omitempty"`
TLS *OrderedTLSConfig `json:"tls,omitempty"`
EntryPoints []string `json:"entryPoints,omitempty"`
Middlewares []string `json:"middlewares,omitempty"`
Service string `json:"service,omitempty"`
Rule string `json:"rule,omitempty"`
Priority int `json:"priority,omitempty"`
TLS *OrderedTLSConfig `json:"tls,omitempty"`
}

// OrderedTLSConfig represents TLS config for a router with Pangolin's field order.
Expand Down Expand Up @@ -146,7 +147,7 @@ func NewConfigProxy(db *database.DB, configManager *ConfigManager, pangolinURL s
db: db,
configManager: configManager,
pangolinURL: pangolinURL,
httpClient: HTTPClientWithTimeout(10 * time.Second),
httpClient: HTTPClientWithTimeout(10 * time.Second),
cacheDuration: 5 * time.Second, // Match typical Traefik poll interval
}
}
Expand Down Expand Up @@ -266,6 +267,9 @@ func (cp *ConfigProxy) initializeConfigMaps(config *ProxiedTraefikConfig) {
if config.HTTP.Services == nil {
config.HTTP.Services = make(map[string]interface{})
}
if config.HTTP.ServersTransports == nil {
config.HTTP.ServersTransports = make(map[string]interface{})
}

if config.TCP == nil {
config.TCP = &TCPConfig{}
Expand Down
83 changes: 83 additions & 0 deletions services/config_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,89 @@ func TestConfigProxyCachesAndInvalidates(t *testing.T) {
}
}

func TestConfigProxyPreservesServersTransports(t *testing.T) {
db := newTestDB(t)
cm := newTestConfigManager(t)

server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"http": map[string]interface{}{
"middlewares": map[string]interface{}{},
"routers": map[string]interface{}{},
"services": map[string]interface{}{
"14-example-service": map[string]interface{}{
"loadBalancer": map[string]interface{}{
"servers": []map[string]interface{}{
{"url": "https://10.0.0.1:12345"},
},
"serversTransport": "14-transport",
},
},
},
"serversTransports": map[string]interface{}{
"14-transport": map[string]interface{}{
"serverName": "example.com",
"insecureSkipVerify": true,
},
},
},
})
}))
defer server.Close()

cp := NewConfigProxy(db, cm, server.URL)
cp.httpClient = server.Client()

config, err := cp.GetMergedConfig()
if err != nil {
t.Fatalf("GetMergedConfig() error = %v", err)
}

if config.HTTP == nil {
t.Fatal("config.HTTP is nil")
}

transportRaw, exists := config.HTTP.ServersTransports["14-transport"]
if !exists {
t.Fatalf("serversTransport %q was not preserved", "14-transport")
}

transport, ok := transportRaw.(map[string]interface{})
if !ok {
t.Fatalf("serversTransport has type %T, want map[string]interface{}", transportRaw)
}

if got, ok := transport["serverName"].(string); !ok || got != "example.com" {
t.Fatalf("serverName = %#v, want %q", transport["serverName"], "example.com")
}
if got, ok := transport["insecureSkipVerify"].(bool); !ok || !got {
t.Fatalf("insecureSkipVerify = %#v, want true", transport["insecureSkipVerify"])
}

serviceRaw, exists := config.HTTP.Services["14-example-service"]
if !exists {
t.Fatalf("service %q not found", "14-example-service")
}
service, ok := serviceRaw.(map[string]interface{})
if !ok {
t.Fatalf("service has type %T, want map[string]interface{}", serviceRaw)
}

loadBalancerRaw, exists := service["loadBalancer"]
if !exists {
t.Fatalf("service %q missing loadBalancer", "14-example-service")
}
loadBalancer, ok := loadBalancerRaw.(map[string]interface{})
if !ok {
t.Fatalf("loadBalancer has type %T, want map[string]interface{}", loadBalancerRaw)
}

if got, ok := loadBalancer["serversTransport"].(string); !ok || got != "14-transport" {
t.Fatalf("loadBalancer.serversTransport = %#v, want %q", loadBalancer["serversTransport"], "14-transport")
}
}

func TestConfigGeneratorWritesConfigFile(t *testing.T) {
db := newTestDB(t)
cm := newTestConfigManager(t)
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/settings/DataSourceSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export function DataSourceSettings() {
Data Source Settings
</DialogTitle>
<DialogDescription>
Configure your data source connections for Pangolin or Traefik API
Configure your data source connections for Pangolin or Traefik API --Traefik api will not function on pangolin stack/api,dont switch

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove incorrect “don’t switch” warning text

This description now tells users that the Traefik API “will not function” and explicitly says not to switch, which contradicts the data-source switching workflow in this component and backend support for a Traefik source. In environments where Traefik is intentionally used, this message will steer users away from the correct configuration path and can lead to avoidable connection/support issues.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This user-facing description appears to contain a developer note (--Traefik api will not function on pangolin stack/api,dont switch). This text is informal, contains a typo ('dont'), and could be confusing for users. It should be removed to maintain a professional and clear user interface. If a warning is necessary, it should be rephrased into a complete and grammatically correct sentence.

Suggested change
Configure your data source connections for Pangolin or Traefik API --Traefik api will not function on pangolin stack/api,dont switch
Configure your data source connections for Pangolin or Traefik API

</DialogDescription>
</DialogHeader>

Expand Down Expand Up @@ -178,7 +178,7 @@ export function DataSourceSettings() {
<Input
value={editUrl}
onChange={(e) => setEditUrl(e.target.value)}
placeholder="http://localhost:8080"
placeholder="http://gerbil:8080"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The placeholder text was changed to use a specific hostname, gerbil. This is likely a local development machine name and is not a suitable example for general users. It's better to revert to a generic placeholder like localhost or use a conventional example domain to avoid confusion.

Suggested change
placeholder="http://gerbil:8080"
placeholder="http://localhost:8080"

/>
</div>
<div className="grid grid-cols-2 gap-2">
Expand Down
Loading