proxy,ui: add version endpoint and display version info in UI (#395)
- Add /api/version endpoint to ProxyManager that returns build date, commit hash, and version - Implement SetVersion method to configure version info in ProxyManager - Add version info fetching to APIProvider and display in ConnectionStatus component - Include version info in UI context and update dependencies - Add tests for version endpoint functionality
This commit is contained in:
@@ -95,7 +95,9 @@ func main() {
|
|||||||
|
|
||||||
fmt.Println("Configuration Changed")
|
fmt.Println("Configuration Changed")
|
||||||
currentPM.Shutdown()
|
currentPM.Shutdown()
|
||||||
srv.Handler = proxy.New(conf)
|
newPM := proxy.New(conf)
|
||||||
|
newPM.SetVersion(date, commit, version)
|
||||||
|
srv.Handler = newPM
|
||||||
fmt.Println("Configuration Reloaded")
|
fmt.Println("Configuration Reloaded")
|
||||||
|
|
||||||
// wait a few seconds and tell any UI to reload
|
// wait a few seconds and tell any UI to reload
|
||||||
@@ -110,7 +112,9 @@ func main() {
|
|||||||
fmt.Printf("Error, unable to load configuration: %v\n", err)
|
fmt.Printf("Error, unable to load configuration: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
srv.Handler = proxy.New(conf)
|
newPM := proxy.New(conf)
|
||||||
|
newPM.SetVersion(date, commit, version)
|
||||||
|
srv.Handler = newPM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ type ProxyManager struct {
|
|||||||
// shutdown signaling
|
// shutdown signaling
|
||||||
shutdownCtx context.Context
|
shutdownCtx context.Context
|
||||||
shutdownCancel context.CancelFunc
|
shutdownCancel context.CancelFunc
|
||||||
|
|
||||||
|
// version info
|
||||||
|
buildDate string
|
||||||
|
commit string
|
||||||
|
version string
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(config config.Config) *ProxyManager {
|
func New(config config.Config) *ProxyManager {
|
||||||
@@ -122,6 +127,10 @@ func New(config config.Config) *ProxyManager {
|
|||||||
|
|
||||||
shutdownCtx: shutdownCtx,
|
shutdownCtx: shutdownCtx,
|
||||||
shutdownCancel: shutdownCancel,
|
shutdownCancel: shutdownCancel,
|
||||||
|
|
||||||
|
buildDate: "unknown",
|
||||||
|
commit: "abcd1234",
|
||||||
|
version: "0",
|
||||||
}
|
}
|
||||||
|
|
||||||
// create the process groups
|
// create the process groups
|
||||||
@@ -770,3 +779,11 @@ func (pm *ProxyManager) findGroupByModelName(modelName string) *ProcessGroup {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) SetVersion(buildDate string, commit string, version string) {
|
||||||
|
pm.Lock()
|
||||||
|
defer pm.Unlock()
|
||||||
|
pm.buildDate = buildDate
|
||||||
|
pm.commit = commit
|
||||||
|
pm.version = version
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func addApiHandlers(pm *ProxyManager) {
|
|||||||
apiGroup.POST("/models/unload/*model", pm.apiUnloadSingleModelHandler)
|
apiGroup.POST("/models/unload/*model", pm.apiUnloadSingleModelHandler)
|
||||||
apiGroup.GET("/events", pm.apiSendEvents)
|
apiGroup.GET("/events", pm.apiSendEvents)
|
||||||
apiGroup.GET("/metrics", pm.apiGetMetrics)
|
apiGroup.GET("/metrics", pm.apiGetMetrics)
|
||||||
|
apiGroup.GET("/version", pm.apiGetVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,3 +228,11 @@ func (pm *ProxyManager) apiUnloadSingleModelHandler(c *gin.Context) {
|
|||||||
c.String(http.StatusOK, "OK")
|
c.String(http.StatusOK, "OK")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pm *ProxyManager) apiGetVersion(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusOK, map[string]string{
|
||||||
|
"version": pm.version,
|
||||||
|
"commit": pm.commit,
|
||||||
|
"build_date": pm.buildDate,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -1147,3 +1147,41 @@ func TestProxyManager_ProxiedStreamingEndpointReturnsNoBufferingHeader(t *testin
|
|||||||
assert.Equal(t, "no", rec.Header().Get("X-Accel-Buffering"))
|
assert.Equal(t, "no", rec.Header().Get("X-Accel-Buffering"))
|
||||||
assert.Contains(t, rec.Header().Get("Content-Type"), "text/event-stream")
|
assert.Contains(t, rec.Header().Get("Content-Type"), "text/event-stream")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestProxyManager_ApiGetVersion(t *testing.T) {
|
||||||
|
config := config.AddDefaultGroupToConfig(config.Config{
|
||||||
|
HealthCheckTimeout: 15,
|
||||||
|
Models: map[string]config.ModelConfig{
|
||||||
|
"model1": getTestSimpleResponderConfig("model1"),
|
||||||
|
},
|
||||||
|
LogLevel: "error",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Version test map
|
||||||
|
versionTest := map[string]string{
|
||||||
|
"build_date": "1970-01-01T00:00:00Z",
|
||||||
|
"commit": "cc915ddb6f04a42d9cd1f524e1d46ec6ed069fdc",
|
||||||
|
"version": "v001",
|
||||||
|
}
|
||||||
|
|
||||||
|
proxy := New(config)
|
||||||
|
proxy.SetVersion(versionTest["build_date"], versionTest["commit"], versionTest["version"])
|
||||||
|
defer proxy.StopProcesses(StopWaitForInflightRequest)
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/api/version", nil)
|
||||||
|
w := CreateTestResponseRecorder()
|
||||||
|
|
||||||
|
proxy.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
|
||||||
|
// Ensure json response
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
// Check for attributes
|
||||||
|
response := map[string]string{}
|
||||||
|
assert.NoError(t, json.Unmarshal(w.Body.Bytes(), &response))
|
||||||
|
for key, value := range versionTest {
|
||||||
|
assert.Equal(t, value, response[key], "%s value %s should match response %s", key, value, response[key])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useAPI } from "../contexts/APIProvider";
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
const ConnectionStatusIcon = () => {
|
const ConnectionStatusIcon = () => {
|
||||||
const { connectionStatus } = useAPI();
|
const { connectionStatus, versionInfo } = useAPI();
|
||||||
|
|
||||||
const eventStatusColor = useMemo(() => {
|
const eventStatusColor = useMemo(() => {
|
||||||
switch (connectionStatus) {
|
switch (connectionStatus) {
|
||||||
@@ -17,7 +17,7 @@ const ConnectionStatusIcon = () => {
|
|||||||
}, [connectionStatus]);
|
}, [connectionStatus]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center" title={`event stream: ${connectionStatus}`}>
|
<div className="flex items-center" title={`Event Stream: ${connectionStatus ?? 'unknown'}\nAPI Version: ${versionInfo?.version ?? 'unknown'}\nCommit Hash: ${versionInfo?.commit?.substring(0,7) ?? 'unknown'}\nBuild Date: ${versionInfo?.build_date ?? 'unknown'}`}>
|
||||||
<span className={`inline-block w-3 h-3 rounded-full ${eventStatusColor} mr-2`}></span>
|
<span className={`inline-block w-3 h-3 rounded-full ${eventStatusColor} mr-2`}></span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ interface APIProviderType {
|
|||||||
upstreamLogs: string;
|
upstreamLogs: string;
|
||||||
metrics: Metrics[];
|
metrics: Metrics[];
|
||||||
connectionStatus: ConnectionState;
|
connectionStatus: ConnectionState;
|
||||||
|
versionInfo: VersionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Metrics {
|
interface Metrics {
|
||||||
@@ -41,11 +42,18 @@ interface LogData {
|
|||||||
source: "upstream" | "proxy";
|
source: "upstream" | "proxy";
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface APIEventEnvelope {
|
interface APIEventEnvelope {
|
||||||
type: "modelStatus" | "logData" | "metrics";
|
type: "modelStatus" | "logData" | "metrics";
|
||||||
data: string;
|
data: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface VersionInfo {
|
||||||
|
build_date: string;
|
||||||
|
commit: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
const APIContext = createContext<APIProviderType | undefined>(undefined);
|
const APIContext = createContext<APIProviderType | undefined>(undefined);
|
||||||
type APIProviderProps = {
|
type APIProviderProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -59,6 +67,11 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
|
|||||||
const [upstreamLogs, setUpstreamLogs] = useState("");
|
const [upstreamLogs, setUpstreamLogs] = useState("");
|
||||||
const [metrics, setMetrics] = useState<Metrics[]>([]);
|
const [metrics, setMetrics] = useState<Metrics[]>([]);
|
||||||
const [connectionStatus, setConnectionState] = useState<ConnectionState>("disconnected");
|
const [connectionStatus, setConnectionState] = useState<ConnectionState>("disconnected");
|
||||||
|
const [versionInfo, setVersionInfo] = useState<VersionInfo>({
|
||||||
|
build_date: "unknown",
|
||||||
|
commit: "unknown",
|
||||||
|
version: "unknown"
|
||||||
|
});
|
||||||
//const apiEventSource = useRef<EventSource | null>(null);
|
//const apiEventSource = useRef<EventSource | null>(null);
|
||||||
|
|
||||||
const [models, setModels] = useState<Model[]>([]);
|
const [models, setModels] = useState<Model[]>([]);
|
||||||
@@ -152,6 +165,26 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
|
|||||||
connect();
|
connect();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// fetch version
|
||||||
|
const fetchVersion = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/version");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data: VersionInfo = await response.json();
|
||||||
|
setVersionInfo(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (connectionStatus === 'connected') {
|
||||||
|
fetchVersion();
|
||||||
|
}
|
||||||
|
}, [connectionStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (autoStartAPIEvents) {
|
if (autoStartAPIEvents) {
|
||||||
enableAPIEvents(true);
|
enableAPIEvents(true);
|
||||||
@@ -230,8 +263,9 @@ export function APIProvider({ children, autoStartAPIEvents = true }: APIProvider
|
|||||||
upstreamLogs,
|
upstreamLogs,
|
||||||
metrics,
|
metrics,
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
|
versionInfo,
|
||||||
}),
|
}),
|
||||||
[models, listModels, unloadAllModels, loadModel, enableAPIEvents, proxyLogs, upstreamLogs, metrics]
|
[models, listModels, unloadAllModels, unloadSingleModel, loadModel, enableAPIEvents, proxyLogs, upstreamLogs, metrics, connectionStatus, versionInfo]
|
||||||
);
|
);
|
||||||
|
|
||||||
return <APIContext.Provider value={value}>{children}</APIContext.Provider>;
|
return <APIContext.Provider value={value}>{children}</APIContext.Provider>;
|
||||||
|
|||||||
Reference in New Issue
Block a user