fix: resolve dashboard websocket 'disconnected' status
- Fixed status indicator UI mapping in websocket.js and index.html. - Added missing CSS for connection status indicator and pulse animation. - Made initial model registry fetch asynchronous to prevent blocking server startup. - Improved configuration loading to correctly handle LLM_PROXY__SERVER__PORT from environment.
This commit is contained in:
@@ -111,8 +111,10 @@ func Load() (*Config, error) {
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "__"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
// Explicitly bind top-level keys that might use double underscores in .env
|
||||
// Explicitly bind keys that might use double underscores in .env
|
||||
v.BindEnv("encryption_key", "LLM_PROXY__ENCRYPTION_KEY")
|
||||
v.BindEnv("server.port", "LLM_PROXY__SERVER__PORT")
|
||||
v.BindEnv("server.host", "LLM_PROXY__SERVER__HOST")
|
||||
|
||||
// Config file
|
||||
v.SetConfigName("config")
|
||||
@@ -133,6 +135,19 @@ func Load() (*Config, error) {
|
||||
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Debug Config: port from viper=%d, host from viper=%s\n", cfg.Server.Port, cfg.Server.Host)
|
||||
fmt.Printf("Debug Env: LLM_PROXY__SERVER__PORT=%s, LLM_PROXY__SERVER__HOST=%s\n", os.Getenv("LLM_PROXY__SERVER__PORT"), os.Getenv("LLM_PROXY__SERVER__HOST"))
|
||||
|
||||
// Manual overrides for nested keys which Viper doesn't always bind correctly with AutomaticEnv + SetEnvPrefix
|
||||
if port := os.Getenv("LLM_PROXY__SERVER__PORT"); port != "" {
|
||||
fmt.Sscanf(port, "%d", &cfg.Server.Port)
|
||||
fmt.Printf("Overriding port to %d from env\n", cfg.Server.Port)
|
||||
}
|
||||
if host := os.Getenv("LLM_PROXY__SERVER__HOST"); host != "" {
|
||||
cfg.Server.Host = host
|
||||
fmt.Printf("Overriding host to %s from env\n", cfg.Server.Host)
|
||||
}
|
||||
|
||||
// Validate encryption key
|
||||
if cfg.EncryptionKey == "" {
|
||||
return nil, fmt.Errorf("encryption key is required (LLM_PROXY__ENCRYPTION_KEY)")
|
||||
|
||||
@@ -32,13 +32,6 @@ type Server struct {
|
||||
func NewServer(cfg *config.Config, database *db.DB) *Server {
|
||||
router := gin.Default()
|
||||
hub := NewHub()
|
||||
|
||||
// Fetch registry (non-blocking for startup if it fails, but we'll try once)
|
||||
registry, err := utils.FetchRegistry()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to fetch initial model registry: %v\n", err)
|
||||
registry = &models.ModelRegistry{Providers: make(map[string]models.ProviderInfo)}
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
router: router,
|
||||
@@ -48,9 +41,19 @@ func NewServer(cfg *config.Config, database *db.DB) *Server {
|
||||
sessions: NewSessionManager(cfg.KeyBytes, 24*time.Hour),
|
||||
hub: hub,
|
||||
logger: NewRequestLogger(database, hub),
|
||||
registry: registry,
|
||||
registry: &models.ModelRegistry{Providers: make(map[string]models.ProviderInfo)},
|
||||
}
|
||||
|
||||
// Fetch registry in background
|
||||
go func() {
|
||||
registry, err := utils.FetchRegistry()
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Failed to fetch initial model registry: %v\n", err)
|
||||
} else {
|
||||
s.registry = registry
|
||||
}
|
||||
}()
|
||||
|
||||
// Initialize providers
|
||||
if cfg.Providers.OpenAI.Enabled {
|
||||
apiKey, _ := cfg.GetAPIKey("openai")
|
||||
|
||||
@@ -1134,6 +1134,53 @@ body {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
/* Connection Status Indicator */
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 0.875rem;
|
||||
background: var(--bg1);
|
||||
border: 1px solid var(--bg3);
|
||||
border-radius: 6px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
color: var(--fg3);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--fg4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: var(--green-light);
|
||||
box-shadow: 0 0 0 0 rgba(184, 187, 38, 0.4);
|
||||
animation: status-pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-dot.disconnected {
|
||||
background: var(--red-light);
|
||||
}
|
||||
|
||||
.status-dot.connecting {
|
||||
background: var(--yellow-light);
|
||||
}
|
||||
|
||||
.status-dot.error {
|
||||
background: var(--red);
|
||||
}
|
||||
|
||||
@keyframes status-pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(184, 187, 38, 0.4); }
|
||||
70% { box-shadow: 0 0 0 6px rgba(184, 187, 38, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(184, 187, 38, 0); }
|
||||
}
|
||||
|
||||
/* WebSocket Dot Pulse */
|
||||
@keyframes ws-pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(184, 187, 38, 0.4); }
|
||||
|
||||
@@ -248,21 +248,19 @@ class WebSocketManager {
|
||||
}
|
||||
|
||||
updateStatus(status) {
|
||||
const statusElement = document.getElementById('ws-status-nav');
|
||||
const statusElement = document.getElementById('connection-status');
|
||||
if (!statusElement) return;
|
||||
|
||||
const dot = statusElement.querySelector('.ws-dot');
|
||||
const text = statusElement.querySelector('.ws-text');
|
||||
const dot = statusElement.querySelector('.status-dot');
|
||||
const text = statusElement.querySelector('.status-text');
|
||||
|
||||
if (!dot || !text) return;
|
||||
|
||||
// Remove all status classes
|
||||
dot.classList.remove('connected', 'disconnected');
|
||||
statusElement.classList.remove('connected', 'disconnected');
|
||||
dot.classList.remove('connected', 'disconnected', 'error', 'connecting');
|
||||
|
||||
// Add new status class
|
||||
dot.classList.add(status);
|
||||
statusElement.classList.add(status);
|
||||
|
||||
// Update text
|
||||
const statusText = {
|
||||
|
||||
Reference in New Issue
Block a user