Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(api): add grafana legacy #2797

Merged
merged 14 commits into from
Dec 23, 2024
484 changes: 256 additions & 228 deletions keep-ui/app/(keep)/providers/provider-form.tsx

Large diffs are not rendered by default.

87 changes: 87 additions & 0 deletions keep-ui/app/(keep)/providers/provider-logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from "react";
import { Card, Title, Badge, Text, Button } from "@tremor/react";
import { useProviderLogs, ProviderLog } from "@/utils/hooks/useProviderLogs";
import { EmptyStateCard } from "@/components/ui/EmptyStateCard";
import { ArrowPathIcon } from "@heroicons/react/24/outline";

interface ProviderLogsProps {
providerId: string;
}

const LOG_LEVEL_COLORS = {
INFO: "blue",
WARNING: "yellow",
ERROR: "red",
DEBUG: "gray",
CRITICAL: "rose",
} as const;

const ProviderLogs: React.FC<ProviderLogsProps> = ({ providerId }) => {
const { logs, isLoading, isError, refresh } = useProviderLogs({ providerId });

if (isLoading) {
return <Text>Loading logs...</Text>;
}

if (isError) {
return (
<div className="flex items-center">
<EmptyStateCard
title="Provider Logs Not Enabled"
description="Provider logs need to be enabled on the backend. Please check the documentation for instructions on how to enable provider logs."
buttonText="View Documentation"
onClick={() => window.open("https://docs.keephq.dev", "_blank")}
/>
</div>
);
}

return (
<div className="mt-4 space-y-4">
<div className="flex justify-between items-center">
<Text>Provider Logs</Text>
<Button
size="xs"
variant="secondary"
icon={ArrowPathIcon}
onClick={() => refresh()}
>
Refresh
</Button>
</div>

<Card className="p-4">
<div className="space-y-2 max-h-[500px] overflow-y-auto">
{logs.map((log) => (
<div key={log.id} className="flex items-start space-x-2">
<Badge
color={
LOG_LEVEL_COLORS[
log.log_level as keyof typeof LOG_LEVEL_COLORS
] || "gray"
}
>
{log.log_level}
</Badge>
<div className="flex-1">
<Text>{log.log_message}</Text>
{Object.keys(log.context).length > 0 && (
<pre className="mt-1 text-xs bg-gray-50 p-2 rounded">
{JSON.stringify(log.context, null, 2)}
</pre>
)}
</div>
<Text className="text-xs text-gray-500">
{new Date(log.timestamp).toLocaleString()}
</Text>
</div>
))}

{logs.length === 0 && <Text>No logs found</Text>}
</div>
</Card>
</div>
);
};

export default ProviderLogs;
64 changes: 64 additions & 0 deletions keep-ui/utils/hooks/useProviderLogs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { useApi } from "@/shared/lib/hooks/useApi";
import useSWR, { SWRConfiguration } from "swr";
import { KeepApiError } from "@/shared/api";
import { showErrorToast } from "@/shared/ui/utils/showErrorToast";

export interface ProviderLog {
id: string;
tenant_id: string;
provider_id: string;
timestamp: string;
log_message: string;
log_level: string;
context: Record<string, any>;
execution_id: string;
}

interface UseProviderLogsOptions {
providerId: string;
limit?: number;
startTime?: string;
endTime?: string;
options?: SWRConfiguration;
}

export function useProviderLogs({
providerId,
limit = 100,
startTime,
endTime,
options = { revalidateOnFocus: false },
}: UseProviderLogsOptions) {
const api = useApi();

const queryParams = new URLSearchParams();
if (limit) queryParams.append("limit", limit.toString());
if (startTime) queryParams.append("start_time", startTime);
if (endTime) queryParams.append("end_time", endTime);

const { data, error, isLoading, mutate } = useSWR<ProviderLog[], Error>(
// Only make the request if providerId exists and api is ready
providerId && api?.isReady()
shahargl marked this conversation as resolved.
Show resolved Hide resolved
? `/providers/${providerId}/logs?${queryParams.toString()}`
: null,
async (url: string) => {
try {
shahargl marked this conversation as resolved.
Show resolved Hide resolved
return await api.get(url);
} catch (err) {
console.error(err);
throw err;
}
},
{
...options,
shouldRetryOnError: false, // Prevent infinite retry on authentication errors
}
);

return {
logs: data || [],
isLoading,
isError: error instanceof Error ? error : null,
shahargl marked this conversation as resolved.
Show resolved Hide resolved
refresh: mutate,
};
}
Loading
Loading