Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
97 changes: 93 additions & 4 deletions transports/bifrost-http/handlers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
bifrost "github.com/maximhq/bifrost/core"
"github.com/maximhq/bifrost/core/schemas"
"github.com/maximhq/bifrost/framework/configstore"
"github.com/maximhq/bifrost/framework/logstore"
"github.com/maximhq/bifrost/framework/vectorstore"
"github.com/maximhq/bifrost/transports/bifrost-http/lib"
"github.com/valyala/fasthttp"
)
Expand Down Expand Up @@ -37,6 +39,14 @@ func (h *ConfigHandler) RegisterRoutes(r *router.Router) {
r.GET("/api/config", h.getConfig)
r.PUT("/api/config", h.updateConfig)
r.GET("/api/version", h.getVersion)

// Vector store configuration endpoints
r.GET("/api/config/vector-store", h.getVectorStoreConfig)
r.PUT("/api/config/vector-store", h.updateVectorStoreConfig)

// Log store configuration endpoints
r.GET("/api/config/log-store", h.getLogStoreConfig)
r.PUT("/api/config/log-store", h.updateLogStoreConfig)
}

// getVersion handles GET /api/version - Get the current version
Expand Down Expand Up @@ -124,8 +134,87 @@ func (h *ConfigHandler) updateConfig(ctx *fasthttp.RequestCtx) {
}

ctx.SetStatusCode(fasthttp.StatusOK)
SendJSON(ctx, map[string]any{
"status": "success",
"message": "configuration updated successfully",
}, h.logger)
SendJSON(ctx, map[string]string{"status": "success"}, h.logger)
}

// getVectorStoreConfig handles GET /api/config/vector-store - Get the current vector store configuration
func (h *ConfigHandler) getVectorStoreConfig(ctx *fasthttp.RequestCtx) {
if h.store.ConfigStore == nil {
SendError(ctx, fasthttp.StatusServiceUnavailable, "config store not available", h.logger)
return
}

config, err := h.store.ConfigStore.GetVectorStoreConfig()
if err != nil {
SendError(ctx, fasthttp.StatusInternalServerError,
fmt.Sprintf("failed to fetch vector store config: %v", err), h.logger)
return
}

SendJSON(ctx, config, h.logger)
}

// updateVectorStoreConfig handles PUT /api/config/vector-store - Update vector store configuration
func (h *ConfigHandler) updateVectorStoreConfig(ctx *fasthttp.RequestCtx) {
if h.store.ConfigStore == nil {
SendError(ctx, fasthttp.StatusInternalServerError, "Config store not initialized", h.logger)
return
}

var req vectorstore.Config

if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("Invalid request format: %v", err), h.logger)
return
}

if err := h.store.ConfigStore.UpdateVectorStoreConfig(&req); err != nil {
h.logger.Warn(fmt.Sprintf("failed to save vector store configuration: %v", err))
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to save vector store configuration: %v", err), h.logger)
return
}

ctx.SetStatusCode(fasthttp.StatusOK)
SendJSON(ctx, map[string]string{"status": "success"}, h.logger)
}

// getLogStoreConfig handles GET /api/config/log-store - Get the current log store configuration
func (h *ConfigHandler) getLogStoreConfig(ctx *fasthttp.RequestCtx) {
if h.store.ConfigStore == nil {
SendError(ctx, fasthttp.StatusServiceUnavailable, "config store not available", h.logger)
return
}

config, err := h.store.ConfigStore.GetLogsStoreConfig()
if err != nil {
SendError(ctx, fasthttp.StatusInternalServerError,
fmt.Sprintf("failed to fetch log store config: %v", err), h.logger)
return
}

SendJSON(ctx, config, h.logger)
}

// updateLogStoreConfig handles PUT /api/config/log-store - Update log store configuration
func (h *ConfigHandler) updateLogStoreConfig(ctx *fasthttp.RequestCtx) {
if h.store.ConfigStore == nil {
SendError(ctx, fasthttp.StatusInternalServerError, "Config store not initialized", h.logger)
return
}

var req logstore.Config

if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("Invalid request format: %v", err), h.logger)
return
}

if err := h.store.ConfigStore.UpdateLogsStoreConfig(&req); err != nil {
h.logger.Warn(fmt.Sprintf("failed to save log store configuration: %v", err))
SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("failed to save log store configuration: %v", err), h.logger)
return
}

ctx.SetStatusCode(fasthttp.StatusOK)
SendJSON(ctx, map[string]string{"status": "success"}, h.logger)
}
6 changes: 6 additions & 0 deletions ui/app/config/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use client";

import PluginsForm from "@/app/config/views/pluginsForm";
import VectorStoreForm from "@/app/config/views/vectorStoreForm";
import LogStoreForm from "@/app/config/views/logStoreForm";
import FullPageLoader from "@/components/fullPageLoader";
import { Alert, AlertDescription } from "@/components/ui/alert";
import { CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
Expand Down Expand Up @@ -364,6 +366,10 @@ export default function ConfigPage() {

<PluginsForm isVectorStoreEnabled={bifrostConfig?.is_cache_connected ?? false} />

<VectorStoreForm />

<LogStoreForm />

<div>
<div className="space-y-2 rounded-lg border p-4">
<div className="space-y-0.5">
Expand Down
149 changes: 149 additions & 0 deletions ui/app/config/views/logStoreForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
"use client";

import { Alert, AlertDescription } from "@/components/ui/alert";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { useGetLogStoreConfigQuery, useUpdateLogStoreConfigMutation } from "@/lib/store";
import { LogStoreConfig, SQLiteConfig } from "@/lib/types/config";
import { getErrorMessage } from "@/lib/store";
import { AlertTriangle, Database, Save } from "lucide-react";
import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner";

const defaultSQLiteConfig: SQLiteConfig = {
path: "./bifrost.db",
};

export default function LogStoreForm() {
const { data: logStoreConfig, isLoading } = useGetLogStoreConfigQuery();
const [updateLogStoreConfig] = useUpdateLogStoreConfigMutation();

const [localConfig, setLocalConfig] = useState<LogStoreConfig>({
enabled: false,
type: "sqlite",
config: defaultSQLiteConfig,
});

const [needsRestart, setNeedsRestart] = useState(false);
const [hasChanges, setHasChanges] = useState(false);

// Update local config when data is loaded
useEffect(() => {
if (logStoreConfig) {
setLocalConfig(logStoreConfig);
setHasChanges(false);
}
}, [logStoreConfig]);

// Track changes
useEffect(() => {
if (logStoreConfig) {
const hasConfigChanges = JSON.stringify(localConfig) !== JSON.stringify(logStoreConfig);
setHasChanges(hasConfigChanges);
setNeedsRestart(hasConfigChanges);
Copy link
Collaborator

Choose a reason for hiding this comment

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

won't this logic not show the alert when we make some change and the just reload the page without restart bifrost? as we are directly making update in the DB via the handlers

}
}, [localConfig, logStoreConfig]);

const handleEnabledChange = useCallback((enabled: boolean) => {
setLocalConfig(prev => ({ ...prev, enabled }));
}, []);

const handleSQLiteConfigChange = useCallback((field: keyof SQLiteConfig, value: string) => {
setLocalConfig(prev => ({
...prev,
config: {
...(prev.config as SQLiteConfig),
[field]: value,
},
}));
}, []);

const handleSave = useCallback(async () => {
try {
await updateLogStoreConfig(localConfig).unwrap();
toast.success("Log store configuration updated successfully.");
setHasChanges(false);
setNeedsRestart(false);
} catch (error) {
toast.error(getErrorMessage(error));
}
}, [localConfig, updateLogStoreConfig]);

if (isLoading) {
return <div>Loading log store configuration...</div>;
}

return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Database className="h-5 w-5" />
Log Store Configuration
</CardTitle>
<CardDescription>
Configure log store for request and response logging to a SQLite database.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center justify-between space-x-2 rounded-lg border p-4">
<div className="space-y-0.5">
<Label htmlFor="log-store-enabled" className="text-sm font-medium">
Enable Log Store
</Label>
<p className="text-muted-foreground text-sm">
Enable logging of requests and responses to a SQLite database. This can add 40-60mb of overhead to the system memory.
</p>
</div>
<Switch
id="log-store-enabled"
size="md"
checked={localConfig.enabled}
onCheckedChange={handleEnabledChange}
/>
</div>

{localConfig.enabled && (
<>
<div className="space-y-4">
<h4 className="font-medium">SQLite Configuration</h4>
<div className="space-y-2">
<Label htmlFor="sqlite-path">Database Path</Label>
<Input
id="sqlite-path"
value={(localConfig.config as SQLiteConfig).path}
onChange={(e) => handleSQLiteConfigChange("path", e.target.value)}
placeholder="./bifrost.db"
/>
<p className="text-muted-foreground text-xs">
Path to the SQLite database file. Use relative path for current directory or absolute path.
</p>
</div>
</div>

{needsRestart && (
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
Log store configuration changes require a Bifrost service restart to take effect.
</AlertDescription>
</Alert>
)}

{hasChanges && (
<div className="flex justify-end">
<Button onClick={handleSave} className="flex items-center gap-2">
<Save className="h-4 w-4" />
Save Configuration
</Button>
</div>
)}
</>
)}
</CardContent>
</Card>
);
}

Loading