-
+ {/* DateTime */}
+
-
-
+
+
-
+ transformOrigin: 'top left',
+ width: `${100/STATS_SCALE}%`
+ }}>
+
+
-
-
-
-
+
-
+ transformOrigin: 'top left',
+ width: `${100/ANALYTICS_SCALE}%`
+ }}>
+
+
diff --git a/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx b/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx
index 1398ee7..06687b3 100644
--- a/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx
+++ b/dashboard/src/components/dashboard/MiniRealtimeAnalytics.jsx
@@ -100,19 +100,9 @@ const MiniRealtimeAnalytics = () => {
}
return (
-
-
-
-
- Real-Time Analytics
-
-
- {format(new Date(basicData.lastUpdated), "h:mm a")}
-
-
-
+
-
+
{error && (
@@ -126,10 +116,10 @@ const MiniRealtimeAnalytics = () => {
"Active users",
basicData.last30MinUsers,
{
- colorClass: "text-purple-200",
- titleClass: "text-purple-100 font-bold text-md",
- descriptionClass: "text-purple-200 text-md font-semibold",
- background: "bg-gradient-to-br from-purple-800 to-purple-700"
+ colorClass: "text-sky-200",
+ titleClass: "text-sky-100 font-bold text-md",
+ descriptionClass: "text-sky-200 text-md font-semibold",
+ background: "bg-gradient-to-br from-sky-800 to-sky-700"
}
)}
{summaryCard(
@@ -137,15 +127,15 @@ const MiniRealtimeAnalytics = () => {
"Active users",
basicData.last5MinUsers,
{
- colorClass: "text-purple-200",
- titleClass: "text-purple-100 font-bold text-md",
- descriptionClass: "text-purple-200 text-md font-semibold",
- background: "bg-gradient-to-br from-purple-800 to-purple-700"
+ colorClass: "text-sky-200",
+ titleClass: "text-sky-100 font-bold text-md",
+ descriptionClass: "text-sky-200 text-md font-semibold",
+ background: "bg-gradient-to-br from-sky-800 to-sky-700"
}
)}
+
{
dataKey="minute"
tickFormatter={(value) => value + "m"}
className="text-xs"
- tick={{ fill: "#E9D5FF" }}
+ tick={{ fill: "#BAE6FD" }}
/>
{
@@ -168,16 +158,16 @@ const MiniRealtimeAnalytics = () => {
Date.now() + payload[0].payload.minute * 60000
);
return (
-
+
-
+
{format(timestamp, "h:mm a")}
-
+
Active Users:
-
+
{payload[0].value.toLocaleString()}
@@ -188,12 +178,11 @@ const MiniRealtimeAnalytics = () => {
return null;
}}
/>
-
+
-
);
};
diff --git a/dashboard/src/components/dashboard/RealtimeAnalytics.jsx b/dashboard/src/components/dashboard/RealtimeAnalytics.jsx
index e8a1dd2..334c28d 100644
--- a/dashboard/src/components/dashboard/RealtimeAnalytics.jsx
+++ b/dashboard/src/components/dashboard/RealtimeAnalytics.jsx
@@ -32,7 +32,7 @@ import {
import { format } from "date-fns";
import { Skeleton } from "@/components/ui/skeleton";
-const METRIC_COLORS = {
+export const METRIC_COLORS = {
activeUsers: {
color: "#8b5cf6",
className: "text-purple-600 dark:text-purple-400",
@@ -47,50 +47,154 @@ const METRIC_COLORS = {
},
};
-const formatNumber = (value, decimalPlaces = 0) => {
- return new Intl.NumberFormat("en-US", {
- minimumFractionDigits: decimalPlaces,
- maximumFractionDigits: decimalPlaces,
- }).format(value || 0);
-};
-
-const formatPercent = (value, decimalPlaces = 1) =>
- `${(value || 0).toFixed(decimalPlaces)}%`;
-
-const summaryCard = (label, sublabel, value, options = {}) => {
+export const summaryCard = (label, sublabel, value, options = {}) => {
const {
- isMonetary = false,
- isPercentage = false,
- decimalPlaces = 0,
- colorClass = METRIC_COLORS.activeUsers.className,
+ colorClass = "text-gray-900 dark:text-gray-100",
+ titleClass = "text-sm font-medium text-gray-600 dark:text-gray-300",
+ descriptionClass = "text-sm text-muted-foreground",
+ background = "bg-white dark:bg-gray-900/60",
} = options;
- let displayValue;
- if (isMonetary) {
- displayValue = formatCurrency(value, decimalPlaces);
- } else if (isPercentage) {
- displayValue = formatPercent(value, decimalPlaces);
- } else {
- displayValue = formatNumber(value, decimalPlaces);
- }
-
return (
-
+
- {label}
+ {label}
- {displayValue}
+ {value.toLocaleString()}
- {sublabel}
+ {sublabel}
);
};
-const QuotaInfo = ({ tokenQuota }) => {
- // Add early return if tokenQuota is null or undefined
+export const SkeletonSummaryCard = () => (
+
+
+
+
+
+
+
+
+
+);
+
+export const SkeletonBarChart = () => (
+
+
+ {/* Grid lines */}
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+ {/* Y-axis labels */}
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+ {/* X-axis labels */}
+
+ {[...Array(6)].map((_, i) => (
+
+ ))}
+
+ {/* Bars */}
+
+ {[...Array(30)].map((_, i) => (
+
+ ))}
+
+
+
+);
+
+export const SkeletonTable = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {[...Array(8)].map((_, i) => (
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+);
+
+export const processBasicData = (data) => {
+ const last30MinUsers = parseInt(
+ data.userResponse?.rows?.[0]?.metricValues?.[0]?.value || 0
+ );
+ const last5MinUsers = parseInt(
+ data.fiveMinResponse?.rows?.[0]?.metricValues?.[0]?.value || 0
+ );
+
+ const byMinute = Array.from({ length: 30 }, (_, i) => {
+ const matchingRow = data.timeSeriesResponse?.rows?.find(
+ (row) => parseInt(row.dimensionValues[0].value) === i
+ );
+ const users = matchingRow
+ ? parseInt(matchingRow.metricValues[0].value)
+ : 0;
+ const timestamp = new Date(Date.now() - i * 60000);
+ return {
+ minute: -i,
+ users,
+ timestamp: timestamp.toLocaleTimeString([], {
+ hour: "2-digit",
+ minute: "2-digit",
+ }),
+ };
+ }).reverse();
+
+ const tokenQuota = data.quotaInfo
+ ? {
+ projectHourly: data.quotaInfo.projectHourly || {},
+ daily: data.quotaInfo.daily || {},
+ serverErrors: data.quotaInfo.serverErrors || {},
+ thresholdedRequests: data.quotaInfo.thresholdedRequests || {},
+ }
+ : null;
+
+ return {
+ last30MinUsers,
+ last5MinUsers,
+ byMinute,
+ tokenQuota,
+ lastUpdated: new Date().toISOString(),
+ };
+};
+
+export const QuotaInfo = ({ tokenQuota }) => {
if (!tokenQuota || typeof tokenQuota !== "object") return null;
const {
@@ -100,7 +204,6 @@ const QuotaInfo = ({ tokenQuota }) => {
thresholdedRequests = {},
} = tokenQuota;
- // Add null checks and default values for all properties
const {
remaining: projectHourlyRemaining = 0,
consumed: projectHourlyConsumed = 0,
@@ -116,13 +219,11 @@ const QuotaInfo = ({ tokenQuota }) => {
consumed: thresholdConsumed = 0,
} = thresholdedRequests;
- // Calculate percentages with safe math
const hourlyPercentage = ((projectHourlyRemaining / 14000) * 100).toFixed(1);
const dailyPercentage = ((dailyRemaining / 200000) * 100).toFixed(1);
const errorPercentage = ((errorsRemaining / 10) * 100).toFixed(1);
const thresholdPercentage = ((thresholdRemaining / 120) * 100).toFixed(1);
- // Determine color based on remaining percentage
const getStatusColor = (percentage) => {
const numericPercentage = parseFloat(percentage);
if (isNaN(numericPercentage) || numericPercentage < 20)
@@ -180,86 +281,6 @@ const QuotaInfo = ({ tokenQuota }) => {
);
};
-const SkeletonSummaryCard = () => (
-
-
-
-
-
-
-
-
-
-);
-
-const SkeletonBarChart = () => (
-
-
- {/* Grid lines */}
- {[...Array(5)].map((_, i) => (
-
- ))}
- {/* Y-axis labels */}
-
- {[...Array(5)].map((_, i) => (
-
- ))}
-
- {/* X-axis labels */}
-
- {[...Array(6)].map((_, i) => (
-
- ))}
-
- {/* Bars */}
-
- {[...Array(30)].map((_, i) => (
-
- ))}
-
-
-
-);
-
-const SkeletonTable = () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
- {[...Array(8)].map((_, i) => (
-
-
-
-
-
-
-
-
- ))}
-
-
-
-);
-
export const RealtimeAnalytics = () => {
const [basicData, setBasicData] = useState({
last30MinUsers: 0,
@@ -279,50 +300,6 @@ export const RealtimeAnalytics = () => {
const [isPaused, setIsPaused] = useState(false);
const [error, setError] = useState(null);
- const processBasicData = (data) => {
- const last30MinUsers = parseInt(
- data.userResponse?.rows?.[0]?.metricValues?.[0]?.value || 0
- );
- const last5MinUsers = parseInt(
- data.fiveMinResponse?.rows?.[0]?.metricValues?.[0]?.value || 0
- );
-
- const byMinute = Array.from({ length: 30 }, (_, i) => {
- const matchingRow = data.timeSeriesResponse?.rows?.find(
- (row) => parseInt(row.dimensionValues[0].value) === i
- );
- const users = matchingRow
- ? parseInt(matchingRow.metricValues[0].value)
- : 0;
- const timestamp = new Date(Date.now() - i * 60000);
- return {
- minute: -i,
- users,
- timestamp: timestamp.toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- }),
- };
- }).reverse();
-
- const tokenQuota = data.quotaInfo
- ? {
- projectHourly: data.quotaInfo.projectHourly || {},
- daily: data.quotaInfo.daily || {},
- serverErrors: data.quotaInfo.serverErrors || {},
- thresholdedRequests: data.quotaInfo.thresholdedRequests || {},
- }
- : null;
-
- return {
- last30MinUsers,
- last5MinUsers,
- byMinute,
- tokenQuota,
- lastUpdated: new Date().toISOString(),
- };
- };
-
const processDetailedData = (data) => {
return {
currentPages:
@@ -636,14 +613,4 @@ export const RealtimeAnalytics = () => {
);
};
-export {
- summaryCard,
- SkeletonSummaryCard,
- SkeletonBarChart,
- SkeletonTable,
- processBasicData,
- METRIC_COLORS,
- QuotaInfo
-};
-
export default RealtimeAnalytics;