Skip to content

Commit 6445a81

Browse files
committed
minor ux uplifg, restart required status storage
1 parent f8d8fa0 commit 6445a81

File tree

14 files changed

+244
-69
lines changed

14 files changed

+244
-69
lines changed

framework/configstore/rdb.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,45 @@ func (s *RDBConfigStore) UpdateProxyConfig(ctx context.Context, config *tables.G
19491949
}).Error
19501950
}
19511951

1952+
// GetRestartRequiredConfig retrieves the restart required configuration from the database.
1953+
func (s *RDBConfigStore) GetRestartRequiredConfig(ctx context.Context) (*tables.RestartRequiredConfig, error) {
1954+
var configEntry tables.TableGovernanceConfig
1955+
if err := s.db.WithContext(ctx).First(&configEntry, "key = ?", tables.ConfigRestartRequiredKey).Error; err != nil {
1956+
if errors.Is(err, gorm.ErrRecordNotFound) {
1957+
return nil, nil
1958+
}
1959+
return nil, err
1960+
}
1961+
if configEntry.Value == "" {
1962+
return nil, nil
1963+
}
1964+
var restartConfig tables.RestartRequiredConfig
1965+
if err := json.Unmarshal([]byte(configEntry.Value), &restartConfig); err != nil {
1966+
return nil, fmt.Errorf("failed to unmarshal restart required config: %w", err)
1967+
}
1968+
return &restartConfig, nil
1969+
}
1970+
1971+
// SetRestartRequiredConfig sets the restart required configuration in the database.
1972+
func (s *RDBConfigStore) SetRestartRequiredConfig(ctx context.Context, config *tables.RestartRequiredConfig) error {
1973+
configJSON, err := json.Marshal(config)
1974+
if err != nil {
1975+
return fmt.Errorf("failed to marshal restart required config: %w", err)
1976+
}
1977+
return s.db.WithContext(ctx).Save(&tables.TableGovernanceConfig{
1978+
Key: tables.ConfigRestartRequiredKey,
1979+
Value: string(configJSON),
1980+
}).Error
1981+
}
1982+
1983+
// ClearRestartRequiredConfig clears the restart required configuration in the database.
1984+
func (s *RDBConfigStore) ClearRestartRequiredConfig(ctx context.Context) error {
1985+
return s.db.WithContext(ctx).Save(&tables.TableGovernanceConfig{
1986+
Key: tables.ConfigRestartRequiredKey,
1987+
Value: `{"required":false,"reason":""}`,
1988+
}).Error
1989+
}
1990+
19521991
// GetSession retrieves a session from the database.
19531992
func (s *RDBConfigStore) GetSession(ctx context.Context, token string) (*tables.SessionsTable, error) {
19541993
var session tables.SessionsTable

framework/configstore/store.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ type ConfigStore interface {
123123
GetProxyConfig(ctx context.Context) (*tables.GlobalProxyConfig, error)
124124
UpdateProxyConfig(ctx context.Context, config *tables.GlobalProxyConfig) error
125125

126+
// Restart required config CRUD
127+
GetRestartRequiredConfig(ctx context.Context) (*tables.RestartRequiredConfig, error)
128+
SetRestartRequiredConfig(ctx context.Context, config *tables.RestartRequiredConfig) error
129+
ClearRestartRequiredConfig(ctx context.Context) error
130+
126131
// Session CRUD
127132
GetSession(ctx context.Context, token string) (*tables.SessionsTable, error)
128133
CreateSession(ctx context.Context, session *tables.SessionsTable) error

framework/configstore/tables/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,16 @@ const (
88
ConfigIsAuthEnabledKey = "is_auth_enabled"
99
ConfigDisableAuthOnInferenceKey = "disable_auth_on_inference"
1010
ConfigProxyKey = "proxy_config"
11+
ConfigRestartRequiredKey = "restart_required"
1112
)
1213

14+
// RestartRequiredConfig represents the restart required configuration
15+
// This is set when a config change requires a server restart to take effect
16+
type RestartRequiredConfig struct {
17+
Required bool `json:"required"`
18+
Reason string `json:"reason,omitempty"`
19+
}
20+
1321

1422

1523
// GlobalProxyConfig represents the global proxy configuration

transports/bifrost-http/handlers/config.go

Lines changed: 97 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"net/http"
99
"slices"
10+
"strings"
1011
"time"
1112

1213
"github.com/fasthttp/router"
@@ -156,6 +157,13 @@ func (h *ConfigHandler) getConfig(ctx *fasthttp.RequestCtx) {
156157
}
157158
mapConfig["proxy_config"] = proxyConfig
158159
}
160+
// Fetching restart required config
161+
restartConfig, err := h.store.ConfigStore.GetRestartRequiredConfig(ctx)
162+
if err != nil {
163+
logger.Warn(fmt.Sprintf("failed to get restart required config from store: %v", err))
164+
} else if restartConfig != nil {
165+
mapConfig["restart_required"] = restartConfig
166+
}
159167
}
160168
SendJSON(ctx, mapConfig)
161169
}
@@ -209,6 +217,7 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
209217
updatedConfig := currentConfig
210218

211219
shouldReloadTelemetryPlugin := false
220+
var restartReasons []string
212221

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

223233
if !slices.Equal(payload.ClientConfig.AllowedOrigins, currentConfig.AllowedOrigins) {
224234
updatedConfig.AllowedOrigins = payload.ClientConfig.AllowedOrigins
235+
restartReasons = append(restartReasons, "Allowed origins")
225236
}
226237

238+
if payload.ClientConfig.InitialPoolSize != currentConfig.InitialPoolSize {
239+
restartReasons = append(restartReasons, "Initial pool size")
240+
}
227241
updatedConfig.InitialPoolSize = payload.ClientConfig.InitialPoolSize
242+
243+
if payload.ClientConfig.EnableLogging != currentConfig.EnableLogging {
244+
restartReasons = append(restartReasons, "Logging enabled")
245+
}
228246
updatedConfig.EnableLogging = payload.ClientConfig.EnableLogging
247+
248+
if payload.ClientConfig.DisableContentLogging != currentConfig.DisableContentLogging {
249+
restartReasons = append(restartReasons, "Content logging")
250+
}
229251
updatedConfig.DisableContentLogging = payload.ClientConfig.DisableContentLogging
252+
253+
if payload.ClientConfig.EnableGovernance != currentConfig.EnableGovernance {
254+
restartReasons = append(restartReasons, "Governance enabled")
255+
}
230256
updatedConfig.EnableGovernance = payload.ClientConfig.EnableGovernance
257+
231258
updatedConfig.EnforceGovernanceHeader = payload.ClientConfig.EnforceGovernanceHeader
232259
updatedConfig.AllowDirectKeys = payload.ClientConfig.AllowDirectKeys
260+
261+
if payload.ClientConfig.MaxRequestBodySizeMB != currentConfig.MaxRequestBodySizeMB {
262+
restartReasons = append(restartReasons, "Max request body size")
263+
}
233264
updatedConfig.MaxRequestBodySizeMB = payload.ClientConfig.MaxRequestBodySizeMB
265+
234266
updatedConfig.EnableLiteLLMFallbacks = payload.ClientConfig.EnableLiteLLMFallbacks
235267

236268
// Validate LogRetentionDays
@@ -337,7 +369,7 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
337369
// }
338370
}
339371
// Checking auth config and trying to update if required
340-
if payload.AuthConfig != nil && payload.AuthConfig.IsEnabled {
372+
if payload.AuthConfig != nil {
341373
// Getting current governance config
342374
authConfig, err := h.store.ConfigStore.GetAuthConfig(ctx)
343375
if err != nil {
@@ -347,29 +379,52 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
347379
return
348380
}
349381
}
350-
if authConfig == nil && payload.AuthConfig.IsEnabled && (payload.AuthConfig.AdminUserName == "" || payload.AuthConfig.AdminPassword == "") {
351-
SendError(ctx, fasthttp.StatusBadRequest, "auth username and password must be provided")
352-
return
382+
383+
// Check if auth config has changed (for restart required flag)
384+
authChanged := false
385+
if authConfig == nil {
386+
// No existing config, any enabled state is a change
387+
if payload.AuthConfig.IsEnabled {
388+
authChanged = true
389+
}
390+
} else {
391+
// Compare with existing config
392+
if payload.AuthConfig.IsEnabled != authConfig.IsEnabled ||
393+
payload.AuthConfig.AdminUserName != authConfig.AdminUserName ||
394+
payload.AuthConfig.DisableAuthOnInference != authConfig.DisableAuthOnInference ||
395+
(payload.AuthConfig.AdminPassword != "<redacted>" && payload.AuthConfig.AdminPassword != "") {
396+
authChanged = true
397+
}
353398
}
354-
// Fetching current Auth config
355-
if payload.AuthConfig.AdminUserName != "" {
356-
if payload.AuthConfig.AdminPassword == "<redacted>" {
357-
if authConfig == nil || authConfig.AdminPassword == "" {
358-
SendError(ctx, fasthttp.StatusBadRequest, "auth password must be provided")
359-
return
360-
}
361-
// Assuming that password hasn't been changed
362-
payload.AuthConfig.AdminPassword = authConfig.AdminPassword
363-
} else {
364-
// Password has been changed
365-
// We will hash the password
366-
hashedPassword, err := encrypt.Hash(payload.AuthConfig.AdminPassword)
367-
if err != nil {
368-
logger.Warn(fmt.Sprintf("failed to hash password: %v", err))
369-
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to hash password: %v", err))
370-
return
399+
if authChanged {
400+
restartReasons = append(restartReasons, "Authentication")
401+
}
402+
403+
if payload.AuthConfig.IsEnabled {
404+
if authConfig == nil && (payload.AuthConfig.AdminUserName == "" || payload.AuthConfig.AdminPassword == "") {
405+
SendError(ctx, fasthttp.StatusBadRequest, "auth username and password must be provided")
406+
return
407+
}
408+
// Fetching current Auth config
409+
if payload.AuthConfig.AdminUserName != "" {
410+
if payload.AuthConfig.AdminPassword == "<redacted>" {
411+
if authConfig == nil || authConfig.AdminPassword == "" {
412+
SendError(ctx, fasthttp.StatusBadRequest, "auth password must be provided")
413+
return
414+
}
415+
// Assuming that password hasn't been changed
416+
payload.AuthConfig.AdminPassword = authConfig.AdminPassword
417+
} else {
418+
// Password has been changed
419+
// We will hash the password
420+
hashedPassword, err := encrypt.Hash(payload.AuthConfig.AdminPassword)
421+
if err != nil {
422+
logger.Warn(fmt.Sprintf("failed to hash password: %v", err))
423+
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to hash password: %v", err))
424+
return
425+
}
426+
payload.AuthConfig.AdminPassword = string(hashedPassword)
371427
}
372-
payload.AuthConfig.AdminPassword = string(hashedPassword)
373428
}
374429
}
375430
err = h.configManager.UpdateAuthConfig(ctx, payload.AuthConfig)
@@ -379,6 +434,18 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
379434
return
380435
}
381436
}
437+
438+
// Set restart required flag if any restart-requiring configs changed
439+
if len(restartReasons) > 0 {
440+
reason := fmt.Sprintf("%s settings have been updated. A restart is required for changes to take full effect.", strings.Join(restartReasons, ", "))
441+
if err := h.store.ConfigStore.SetRestartRequiredConfig(ctx, &configstoreTables.RestartRequiredConfig{
442+
Required: true,
443+
Reason: reason,
444+
}); err != nil {
445+
logger.Warn(fmt.Sprintf("failed to set restart required config: %v", err))
446+
}
447+
}
448+
382449
ctx.SetStatusCode(fasthttp.StatusOK)
383450
SendJSON(ctx, map[string]any{
384451
"status": "success",
@@ -530,6 +597,14 @@ func (h *ConfigHandler) updateProxyConfig(ctx *fasthttp.RequestCtx) {
530597
return
531598
}
532599

600+
// Set restart required flag for proxy config changes
601+
if err := h.store.ConfigStore.SetRestartRequiredConfig(ctx, &configstoreTables.RestartRequiredConfig{
602+
Required: true,
603+
Reason: "Proxy configuration has been updated. A restart is required for all changes to take full effect.",
604+
}); err != nil {
605+
logger.Warn(fmt.Sprintf("failed to set restart required config: %v", err))
606+
}
607+
533608
ctx.SetStatusCode(fasthttp.StatusOK)
534609
SendJSON(ctx, map[string]any{
535610
"status": "success",

transports/bifrost-http/lib/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,10 @@ func initStoresFromFile(ctx context.Context, config *Config, configData *ConfigD
411411
return err
412412
}
413413
logger.Info("config store initialized")
414+
// Clear restart required flag on server startup
415+
if err = config.ConfigStore.ClearRestartRequiredConfig(ctx); err != nil {
416+
logger.Warn("failed to clear restart required config: %v", err)
417+
}
414418
}
415419

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

1529+
// Clear restart required flag on server startup
1530+
if err = config.ConfigStore.ClearRestartRequiredConfig(ctx); err != nil {
1531+
logger.Warn("failed to clear restart required config: %v", err)
1532+
}
1533+
15251534
// Load or create default client config
15261535
if err = loadDefaultClientConfig(ctx, config); err != nil {
15271536
return nil, err

ui/app/workspace/config/views/pricingConfigView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export default function PricingConfigView() {
8989
</div>
9090
<div className="flex items-center gap-2">
9191
<Button variant="outline" type="button" onClick={handleForceSync} disabled={isForceSyncing || !hasSettingsUpdateAccess}>
92-
{isForceSyncing ? "Forcing..." : "Force Sync Now"}
92+
{isForceSyncing ? "Syncing..." : "Force Sync Now"}
9393
</Button>
9494
<Button type="submit" disabled={!hasChanges || isLoading || !hasSettingsUpdateAccess}>
9595
{isLoading ? "Saving..." : "Save Changes"}

ui/app/workspace/config/views/securityView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ export default function SecurityView() {
238238
onChange={(e) => handleAuthFieldChange("admin_password", e.target.value)}
239239
/>
240240
</div>
241-
<div className="flex items-center justify-between rounded-lg border p-4">
241+
<div className="flex items-center justify-between">
242242
<div className="space-y-0.5">
243243
<Label htmlFor="disable-auth-inference" className="text-sm font-medium">
244244
Disable authentication on inference calls

ui/app/workspace/logs/views/columns.tsx

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
"use client"
1+
"use client";
22

3-
import { Badge } from "@/components/ui/badge"
4-
import { Button } from "@/components/ui/button"
5-
import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons"
6-
import { ProviderName, RequestTypeColors, RequestTypeLabels, Status, StatusColors } from "@/lib/constants/logs"
7-
import { LogEntry, ResponsesMessageContentBlock } from "@/lib/types/logs"
8-
import { ColumnDef } from "@tanstack/react-table"
9-
import { ArrowUpDown, Trash2 } from "lucide-react"
10-
import moment from "moment"
3+
import { Badge } from "@/components/ui/badge";
4+
import { Button } from "@/components/ui/button";
5+
import { ProviderIconType, RenderProviderIcon } from "@/lib/constants/icons";
6+
import { ProviderName, RequestTypeColors, RequestTypeLabels, Status, StatusBarColors } from "@/lib/constants/logs";
7+
import { LogEntry, ResponsesMessageContentBlock } from "@/lib/types/logs";
8+
import { ColumnDef } from "@tanstack/react-table";
9+
import { ArrowUpDown, Trash2 } from "lucide-react";
10+
import moment from "moment";
1111

1212
function getMessage(log?: LogEntry) {
1313
if (log?.input_history && log.input_history.length > 0) {
@@ -48,14 +48,12 @@ function getMessage(log?: LogEntry) {
4848
export const createColumns = (onDelete: (log: LogEntry) => void, hasDeleteAccess = true): ColumnDef<LogEntry>[] => [
4949
{
5050
accessorKey: "status",
51-
header: "Status",
51+
header: "",
52+
size: 8,
53+
maxSize: 8,
5254
cell: ({ row }) => {
5355
const status = row.original.status as Status;
54-
return (
55-
<Badge variant="secondary" className={`${StatusColors[status] ?? ""} font-mono text-xs uppercase`}>
56-
{status}
57-
</Badge>
58-
);
56+
return <div className={`h-full min-h-[24px] w-1 rounded-sm ${StatusBarColors[status]}`} />;
5957
},
6058
},
6159
{
@@ -68,7 +66,7 @@ export const createColumns = (onDelete: (log: LogEntry) => void, hasDeleteAccess
6866
),
6967
cell: ({ row }) => {
7068
const timestamp = row.original.timestamp;
71-
return <div className="font-mono text-xs">{moment(timestamp).format("YYYY-MM-DD hh:mm:ss A (Z)")}</div>;
69+
return <div className="text-xs">{moment(timestamp).format("YYYY-MM-DD hh:mm:ss A (Z)")}</div>;
7270
},
7371
},
7472
{
@@ -174,12 +172,12 @@ export const createColumns = (onDelete: (log: LogEntry) => void, hasDeleteAccess
174172
{
175173
id: "actions",
176174
cell: ({ row }) => {
177-
const log = row.original
175+
const log = row.original;
178176
return (
179177
<Button variant="outline" size="icon" onClick={() => onDelete(log)} disabled={!hasDeleteAccess}>
180178
<Trash2 />
181179
</Button>
182-
)
180+
);
183181
},
184182
},
185-
]
183+
];

0 commit comments

Comments
 (0)