Skip to content
Open
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
39 changes: 39 additions & 0 deletions framework/configstore/rdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,45 @@ func (s *RDBConfigStore) UpdateProxyConfig(ctx context.Context, config *tables.G
}).Error
}

// GetRestartRequiredConfig retrieves the restart required configuration from the database.
func (s *RDBConfigStore) GetRestartRequiredConfig(ctx context.Context) (*tables.RestartRequiredConfig, error) {
var configEntry tables.TableGovernanceConfig
if err := s.db.WithContext(ctx).First(&configEntry, "key = ?", tables.ConfigRestartRequiredKey).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, err
}
if configEntry.Value == "" {
return nil, nil
}
var restartConfig tables.RestartRequiredConfig
if err := json.Unmarshal([]byte(configEntry.Value), &restartConfig); err != nil {
return nil, fmt.Errorf("failed to unmarshal restart required config: %w", err)
}
return &restartConfig, nil
}

// SetRestartRequiredConfig sets the restart required configuration in the database.
func (s *RDBConfigStore) SetRestartRequiredConfig(ctx context.Context, config *tables.RestartRequiredConfig) error {
configJSON, err := json.Marshal(config)
if err != nil {
return fmt.Errorf("failed to marshal restart required config: %w", err)
}
return s.db.WithContext(ctx).Save(&tables.TableGovernanceConfig{
Key: tables.ConfigRestartRequiredKey,
Value: string(configJSON),
}).Error
}

// ClearRestartRequiredConfig clears the restart required configuration in the database.
func (s *RDBConfigStore) ClearRestartRequiredConfig(ctx context.Context) error {
return s.db.WithContext(ctx).Save(&tables.TableGovernanceConfig{
Key: tables.ConfigRestartRequiredKey,
Value: `{"required":false,"reason":""}`,
}).Error
}

// GetSession retrieves a session from the database.
func (s *RDBConfigStore) GetSession(ctx context.Context, token string) (*tables.SessionsTable, error) {
var session tables.SessionsTable
Expand Down
5 changes: 5 additions & 0 deletions framework/configstore/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ type ConfigStore interface {
GetProxyConfig(ctx context.Context) (*tables.GlobalProxyConfig, error)
UpdateProxyConfig(ctx context.Context, config *tables.GlobalProxyConfig) error

// Restart required config CRUD
GetRestartRequiredConfig(ctx context.Context) (*tables.RestartRequiredConfig, error)
SetRestartRequiredConfig(ctx context.Context, config *tables.RestartRequiredConfig) error
ClearRestartRequiredConfig(ctx context.Context) error

// Session CRUD
GetSession(ctx context.Context, token string) (*tables.SessionsTable, error)
CreateSession(ctx context.Context, session *tables.SessionsTable) error
Expand Down
8 changes: 8 additions & 0 deletions framework/configstore/tables/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ const (
ConfigIsAuthEnabledKey = "is_auth_enabled"
ConfigDisableAuthOnInferenceKey = "disable_auth_on_inference"
ConfigProxyKey = "proxy_config"
ConfigRestartRequiredKey = "restart_required"
)

// RestartRequiredConfig represents the restart required configuration
// This is set when a config change requires a server restart to take effect
type RestartRequiredConfig struct {
Required bool `json:"required"`
Reason string `json:"reason,omitempty"`
}



// GlobalProxyConfig represents the global proxy configuration
Expand Down
119 changes: 97 additions & 22 deletions transports/bifrost-http/handlers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"net/http"
"slices"
"strings"
"time"

"github.com/fasthttp/router"
Expand Down Expand Up @@ -156,6 +157,13 @@ func (h *ConfigHandler) getConfig(ctx *fasthttp.RequestCtx) {
}
mapConfig["proxy_config"] = proxyConfig
}
// Fetching restart required config
restartConfig, err := h.store.ConfigStore.GetRestartRequiredConfig(ctx)
if err != nil {
logger.Warn(fmt.Sprintf("failed to get restart required config from store: %v", err))
} else if restartConfig != nil {
mapConfig["restart_required"] = restartConfig
}
}
SendJSON(ctx, mapConfig)
}
Expand Down Expand Up @@ -209,6 +217,7 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
updatedConfig := currentConfig

shouldReloadTelemetryPlugin := false
var restartReasons []string

if payload.ClientConfig.DropExcessRequests != currentConfig.DropExcessRequests {
h.configManager.UpdateDropExcessRequests(ctx, payload.ClientConfig.DropExcessRequests)
Expand All @@ -218,19 +227,42 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
if !slices.Equal(payload.ClientConfig.PrometheusLabels, currentConfig.PrometheusLabels) {
updatedConfig.PrometheusLabels = payload.ClientConfig.PrometheusLabels
shouldReloadTelemetryPlugin = true
restartReasons = append(restartReasons, "Prometheus labels")
}

if !slices.Equal(payload.ClientConfig.AllowedOrigins, currentConfig.AllowedOrigins) {
updatedConfig.AllowedOrigins = payload.ClientConfig.AllowedOrigins
restartReasons = append(restartReasons, "Allowed origins")
}

if payload.ClientConfig.InitialPoolSize != currentConfig.InitialPoolSize {
restartReasons = append(restartReasons, "Initial pool size")
}
updatedConfig.InitialPoolSize = payload.ClientConfig.InitialPoolSize

if payload.ClientConfig.EnableLogging != currentConfig.EnableLogging {
restartReasons = append(restartReasons, "Logging enabled")
}
updatedConfig.EnableLogging = payload.ClientConfig.EnableLogging

if payload.ClientConfig.DisableContentLogging != currentConfig.DisableContentLogging {
restartReasons = append(restartReasons, "Content logging")
}
updatedConfig.DisableContentLogging = payload.ClientConfig.DisableContentLogging

if payload.ClientConfig.EnableGovernance != currentConfig.EnableGovernance {
restartReasons = append(restartReasons, "Governance enabled")
}
updatedConfig.EnableGovernance = payload.ClientConfig.EnableGovernance

updatedConfig.EnforceGovernanceHeader = payload.ClientConfig.EnforceGovernanceHeader
updatedConfig.AllowDirectKeys = payload.ClientConfig.AllowDirectKeys

if payload.ClientConfig.MaxRequestBodySizeMB != currentConfig.MaxRequestBodySizeMB {
restartReasons = append(restartReasons, "Max request body size")
}
updatedConfig.MaxRequestBodySizeMB = payload.ClientConfig.MaxRequestBodySizeMB

updatedConfig.EnableLiteLLMFallbacks = payload.ClientConfig.EnableLiteLLMFallbacks

// Validate LogRetentionDays
Expand Down Expand Up @@ -337,7 +369,7 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
// }
}
// Checking auth config and trying to update if required
if payload.AuthConfig != nil && payload.AuthConfig.IsEnabled {
if payload.AuthConfig != nil {
// Getting current governance config
authConfig, err := h.store.ConfigStore.GetAuthConfig(ctx)
if err != nil {
Expand All @@ -347,29 +379,52 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
return
}
}
if authConfig == nil && payload.AuthConfig.IsEnabled && (payload.AuthConfig.AdminUserName == "" || payload.AuthConfig.AdminPassword == "") {
SendError(ctx, fasthttp.StatusBadRequest, "auth username and password must be provided")
return

// Check if auth config has changed (for restart required flag)
authChanged := false
if authConfig == nil {
// No existing config, any enabled state is a change
if payload.AuthConfig.IsEnabled {
authChanged = true
}
} else {
// Compare with existing config
if payload.AuthConfig.IsEnabled != authConfig.IsEnabled ||
payload.AuthConfig.AdminUserName != authConfig.AdminUserName ||
payload.AuthConfig.DisableAuthOnInference != authConfig.DisableAuthOnInference ||
(payload.AuthConfig.AdminPassword != "<redacted>" && payload.AuthConfig.AdminPassword != "") {
authChanged = true
}
}
// Fetching current Auth config
if payload.AuthConfig.AdminUserName != "" {
if payload.AuthConfig.AdminPassword == "<redacted>" {
if authConfig == nil || authConfig.AdminPassword == "" {
SendError(ctx, fasthttp.StatusBadRequest, "auth password must be provided")
return
}
// Assuming that password hasn't been changed
payload.AuthConfig.AdminPassword = authConfig.AdminPassword
} else {
// Password has been changed
// We will hash the password
hashedPassword, err := encrypt.Hash(payload.AuthConfig.AdminPassword)
if err != nil {
logger.Warn(fmt.Sprintf("failed to hash password: %v", err))
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to hash password: %v", err))
return
if authChanged {
restartReasons = append(restartReasons, "Authentication")
}

if payload.AuthConfig.IsEnabled {
if authConfig == nil && (payload.AuthConfig.AdminUserName == "" || payload.AuthConfig.AdminPassword == "") {
SendError(ctx, fasthttp.StatusBadRequest, "auth username and password must be provided")
return
}
// Fetching current Auth config
if payload.AuthConfig.AdminUserName != "" {
if payload.AuthConfig.AdminPassword == "<redacted>" {
if authConfig == nil || authConfig.AdminPassword == "" {
SendError(ctx, fasthttp.StatusBadRequest, "auth password must be provided")
return
}
// Assuming that password hasn't been changed
payload.AuthConfig.AdminPassword = authConfig.AdminPassword
} else {
// Password has been changed
// We will hash the password
hashedPassword, err := encrypt.Hash(payload.AuthConfig.AdminPassword)
if err != nil {
logger.Warn(fmt.Sprintf("failed to hash password: %v", err))
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to hash password: %v", err))
return
}
payload.AuthConfig.AdminPassword = string(hashedPassword)
}
payload.AuthConfig.AdminPassword = string(hashedPassword)
}
}
err = h.configManager.UpdateAuthConfig(ctx, payload.AuthConfig)
Expand All @@ -379,6 +434,18 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
return
}
}

// Set restart required flag if any restart-requiring configs changed
if len(restartReasons) > 0 {
reason := fmt.Sprintf("%s settings have been updated. A restart is required for changes to take full effect.", strings.Join(restartReasons, ", "))
if err := h.store.ConfigStore.SetRestartRequiredConfig(ctx, &configstoreTables.RestartRequiredConfig{
Required: true,
Reason: reason,
}); err != nil {
logger.Warn(fmt.Sprintf("failed to set restart required config: %v", err))
}
}

ctx.SetStatusCode(fasthttp.StatusOK)
SendJSON(ctx, map[string]any{
"status": "success",
Expand Down Expand Up @@ -530,6 +597,14 @@ func (h *ConfigHandler) updateProxyConfig(ctx *fasthttp.RequestCtx) {
return
}

// Set restart required flag for proxy config changes
if err := h.store.ConfigStore.SetRestartRequiredConfig(ctx, &configstoreTables.RestartRequiredConfig{
Required: true,
Reason: "Proxy configuration has been updated. A restart is required for all changes to take full effect.",
}); err != nil {
logger.Warn(fmt.Sprintf("failed to set restart required config: %v", err))
}

ctx.SetStatusCode(fasthttp.StatusOK)
SendJSON(ctx, map[string]any{
"status": "success",
Expand Down
9 changes: 9 additions & 0 deletions transports/bifrost-http/lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ func initStoresFromFile(ctx context.Context, config *Config, configData *ConfigD
return err
}
logger.Info("config store initialized")
// Clear restart required flag on server startup
if err = config.ConfigStore.ClearRestartRequiredConfig(ctx); err != nil {
logger.Warn("failed to clear restart required config: %v", err)
}
}

// Initialize log store
Expand Down Expand Up @@ -1522,6 +1526,11 @@ func loadConfigFromDefaults(ctx context.Context, config *Config, configDBPath, l
return nil, err
}

// Clear restart required flag on server startup
if err = config.ConfigStore.ClearRestartRequiredConfig(ctx); err != nil {
logger.Warn("failed to clear restart required config: %v", err)
}

// Load or create default client config
if err = loadDefaultClientConfig(ctx, config); err != nil {
return nil, err
Expand Down
2 changes: 1 addition & 1 deletion ui/app/workspace/config/views/pricingConfigView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default function PricingConfigView() {
</div>
<div className="flex items-center gap-2">
<Button variant="outline" type="button" onClick={handleForceSync} disabled={isForceSyncing || !hasSettingsUpdateAccess}>
{isForceSyncing ? "Forcing..." : "Force Sync Now"}
{isForceSyncing ? "Syncing..." : "Force Sync Now"}
</Button>
<Button type="submit" disabled={!hasChanges || isLoading || !hasSettingsUpdateAccess}>
{isLoading ? "Saving..." : "Save Changes"}
Expand Down
2 changes: 1 addition & 1 deletion ui/app/workspace/config/views/securityView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export default function SecurityView() {
onChange={(e) => handleAuthFieldChange("admin_password", e.target.value)}
/>
</div>
<div className="flex items-center justify-between rounded-lg border p-4">
<div className="flex items-center justify-between">
<div className="space-y-0.5">
<Label htmlFor="disable-auth-inference" className="text-sm font-medium">
Disable authentication on inference calls
Expand Down
36 changes: 17 additions & 19 deletions ui/app/workspace/logs/views/columns.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client"
"use client";

import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"
import { ProviderName, RequestTypeColors, RequestTypeLabels, Status, StatusColors } from "@/lib/constants/logs"
import { LogEntry, ResponsesMessageContentBlock } from "@/lib/types/logs"
import { ColumnDef } from "@tanstack/react-table"
import { ArrowUpDown, Trash2 } from "lucide-react"
import moment from "moment"
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons";
import { ProviderName, RequestTypeColors, RequestTypeLabels, Status, StatusBarColors } from "@/lib/constants/logs";
import { LogEntry, ResponsesMessageContentBlock } from "@/lib/types/logs";
import { ColumnDef } from "@tanstack/react-table";
import { ArrowUpDown, Trash2 } from "lucide-react";
import moment from "moment";

function getMessage(log?: LogEntry) {
if (log?.input_history && log.input_history.length > 0) {
Expand Down Expand Up @@ -48,14 +48,12 @@ function getMessage(log?: LogEntry) {
export const createColumns = (onDelete: (log: LogEntry) => void, hasDeleteAccess = true): ColumnDef<LogEntry>[] => [
{
accessorKey: "status",
header: "Status",
header: "",
size: 8,
maxSize: 8,
cell: ({ row }) => {
const status = row.original.status as Status;
return (
<Badge variant="secondary" className={`${StatusColors[status] ?? ""} font-mono text-xs uppercase`}>
{status}
</Badge>
);
return <div className={`h-full min-h-[24px] w-1 rounded-sm ${StatusBarColors[status]}`} />;
},
},
{
Expand All @@ -68,7 +66,7 @@ export const createColumns = (onDelete: (log: LogEntry) => void, hasDeleteAccess
),
cell: ({ row }) => {
const timestamp = row.original.timestamp;
return <div className="font-mono text-xs">{moment(timestamp).format("YYYY-MM-DD hh:mm:ss A (Z)")}</div>;
return <div className="text-xs">{moment(timestamp).format("YYYY-MM-DD hh:mm:ss A (Z)")}</div>;
},
},
{
Expand Down Expand Up @@ -174,12 +172,12 @@ export const createColumns = (onDelete: (log: LogEntry) => void, hasDeleteAccess
{
id: "actions",
cell: ({ row }) => {
const log = row.original
const log = row.original;
return (
<Button variant="outline" size="icon" onClick={() => onDelete(log)} disabled={!hasDeleteAccess}>
<Trash2 />
</Button>
)
);
},
},
]
];
Loading