Skip to content

Commit

Permalink
Dashboard Done
Browse files Browse the repository at this point in the history
  • Loading branch information
Debajyoti14 committed Jul 24, 2024
1 parent e25f34e commit 6cc04e5
Show file tree
Hide file tree
Showing 11 changed files with 1,128 additions and 33 deletions.
13 changes: 6 additions & 7 deletions src/handlers/overview_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,20 @@ pub async fn get_all_overview_handler(
let active_session_count = all_sessions.iter().filter(|s| !s.is_revoked).count();
let revoked_session_count = all_sessions.iter().filter(|s| s.is_revoked).count();

// create a user-agent map from all_sessions
let user_agents = all_sessions
// create a user-agent map from all_sessions where is_revoked = false
let user_agents: Vec<String> = all_sessions
.iter()
.filter(|s| !s.is_revoked)
.map(|s| s.user_agent.clone())
.collect::<Vec<String>>();
.collect();

println!(">> user_agents: {:?}", user_agents);

let parser = Parser::new();

// find out os_types, device_types, browser_types from all_sessions using user-agent-parser
let results: Vec<Option<WootheeResult>> = all_sessions
.iter()
.map(|s| parser.parse(s.user_agent.as_str()))
.collect();
let results: Vec<Option<WootheeResult>> =
user_agents.iter().map(|ua| parser.parse(ua)).collect();

// get os_types as a string[] from results
let os_types: Vec<String> = results
Expand Down
19 changes: 19 additions & 0 deletions ui/app/api/overview/get-all/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export async function GET(req: Request) {
const endPoint: (string | undefined) = `${process.env.NEXT_PUBLIC_API_BASE_URL}/api/overview/get-all`

if (endPoint) {
try {
const res = await fetch(endPoint, {
headers: {
'Content-Type': 'application/json', // Set the appropriate content type for your request
'x-api-key': process.env.X_API_KEY!,
},
cache: 'no-cache',
});
const data = await res.json();
return Response.json({ data })
} catch (error) {
console.error('Error during request:', error);
}
}
}
18 changes: 18 additions & 0 deletions ui/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,24 @@
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
--radius: 0.5rem;

--chart-1-1: 221.2 83.2% 53.3%;
--chart-2-1: 212 95% 68%;
--chart-3-1: 216 92% 60%;
--chart-4-1: 210 98% 78%;
--chart-5-1: 212 97% 87%;

--chart-1-2: 359 2% 90%;
--chart-2-2: 240 1% 74%;
--chart-3-2: 240 1% 58%;
--chart-4-2: 240 1% 42%;
--chart-5-2: 240 2% 26%;

--chart-1-3: 139 65% 20%;
--chart-2-3: 140 74% 44%;
--chart-3-3: 142 88% 28%;
--chart-4-3: 137 55% 15%;
--chart-5-3: 141 40% 9%;
}
}

Expand Down
186 changes: 165 additions & 21 deletions ui/components/Overview/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,153 @@
"use client";
import React, { useEffect, useState } from 'react'
import { Loader } from '../custom/Loader';
import { IUser } from '@/interfaces/IUser';
import { MdOutlineConstruction } from "react-icons/md";
import { IOverview } from '@/interfaces/IOverview';
import { DonutChartStats } from '../custom/DonutChartForStats';
import { ChartConfig } from '../ui/chart';
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
import { FaUsersSlash } from 'react-icons/fa';
import { ChartPie } from '../custom/PieChart';

const Overview = () => {
const [users, setUsers] = useState([] as IUser[])
const [overview, setOverview] = useState<IOverview | null>(null)
const [loading, setLoading] = useState(true)

const getAllUsers = async () => {
const userChartData = [
{ name: "active", count: overview?.active_user_count, fill: "var(--color-active)" },
{ name: "inactive", count: overview?.inactive_user_count, fill: "var(--color-inactive)" },
]

const userChartConfig = {
user: {
label: "Users",
},
active: {
label: "Active",
color: "hsl(var(--chart-1-1))",
},
inactive: {
label: "Inactive",
color: "hsl(var(--chart-2-1))",
},
} satisfies ChartConfig

const sessionChartData = [
{ name: "active", count: overview?.active_session_count, fill: "var(--color-active)" },
{ name: "revoked", count: overview?.revoked_session_count, fill: "var(--color-revoked)" },
]

const sessionChartConfig = {
session: {
label: "Sessions",
},
active: {
label: "Active",
color: "hsl(var(--chart-1-1))",
},
revoked: {
label: "Revoked",
color: "hsl(var(--chart-2-2))",
},
} satisfies ChartConfig


// Define an interface for the device counts
interface CountObject {
[key: string]: number;
}

// Initialize the device counts object with the correct type
const deviceCounts: CountObject = (overview ?? {
device_types: [],
}).device_types.reduce((acc, device) => {
acc[device] = (acc[device] || 0) + 1;
return acc;
}, {} as CountObject);

// Count occurrences in the browsers array
const browserCounts: CountObject = (overview ?? {
browser_types: [],
}).browser_types.reduce((acc, browser) => {
acc[browser] = (acc[browser] || 0) + 1;
return acc;
}, {} as CountObject);

// Count occurrences in the OS types array
const osTypeCounts: CountObject = (overview ?? {
os_types: [],
}).os_types.reduce((acc, os) => {
acc[os] = (acc[os] || 0) + 1;
return acc;
}, {} as CountObject);

// Define a function to generate colors dynamically
const generateColor = (index: number, themeNo: number) => {
return `hsl(var(--chart-${index + 1}-${themeNo}))`;
};

// Generic function to generate chart configuration
const generateChartConfig = (counts: CountObject, themeNo: number) => {
const config: { [key: string]: { label: string; color: string } } = {};
const types = Object.keys(counts);


types.forEach((type, index) => {
config[type] = {
label: type,
color: generateColor(index, themeNo),
};
});

return config;
};

const sessionDeviceChartData = Object.keys(deviceCounts).map(device => ({
name: device,
count: deviceCounts[device] || 0,
fill: `var(--color-${device})`
}));

const sessionBrowserChartData = Object.keys(browserCounts).map(browser => ({
name: browser,
count: browserCounts[browser] || 0,
fill: `var(--color-${browser})`
}));

const sessionOsTypeChartData = Object.keys(osTypeCounts).map(os => ({
name: os,
count: osTypeCounts[os] || 0,
fill: `var(--color-${os})`
}));

const sessionDeviceChartConfig = generateChartConfig(deviceCounts, 2);

const sessionBrowserChartConfig = generateChartConfig(browserCounts, 1);

const sessionOSChartConfig = generateChartConfig(osTypeCounts, 2);


const getOverview = async () => {
try {
setLoading(true)
const res = await fetch(`${process.env.NEXT_PUBLIC_ENDPOINT}/api/user/get-all`, {
const res = await fetch(`${process.env.NEXT_PUBLIC_ENDPOINT}/api/overview/get-all`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
cache: 'no-cache',
});
const { data } = await res.json();
setUsers(data);
setOverview(data);
} catch (error) {
console.error('Error during POST request:', error);
}
setLoading(false)
}

useEffect(() => {
getAllUsers()
getOverview()
}, [])

return (
<div>
{
Expand All @@ -37,25 +156,50 @@ const Overview = () => {
<Loader />
</div>
:
<div className='flex justify-center items-center h-[calc(100vh-10rem)]'>
<div className='flex flex-col items-center gap-5'>
<MdOutlineConstruction className='text-primary' size={160} />
<h1 className='text-3xl'>Hold up! Work is in progress</h1>
</div>

{/* <div className='grid grid-cols-4 gap-5'>
<Card>
<div>
<div className='grid grid-cols-3 gap-5'>
<DonutChartStats
title='Total Users'
chartData={userChartData}
chartConfig={userChartConfig}
key='name'
/>
<Card className='flex flex-col justify-between'>
<CardHeader>
<CardTitle>Total Users</CardTitle>
<CardTitle>Blocked Users</CardTitle>
</CardHeader>
<CardContent className='flex justify-between items-end'>
<p className="text-6xl font-bold">
{users.length}
<CardContent className='flex flex-col gap-5 items-center'>
<p className="text-5xl font-bold">
{overview?.blocked_user_count}
</p>
<FaUsers size={80} className='text-primary' />
<FaUsersSlash size={80} className='text-gray-300' />
</CardContent>
</Card>
</div> */}
<DonutChartStats
title='Total Sessions'
chartData={sessionChartData}
chartConfig={sessionChartConfig}
key='name'
/>
<ChartPie
title='All Devices'
chartData={sessionDeviceChartData}
chartConfig={sessionDeviceChartConfig}
key='name'
/>
<ChartPie
title='All Browsers'
chartData={sessionBrowserChartData}
chartConfig={sessionBrowserChartConfig}
key='name'
/>
<ChartPie
title='Operating Systems'
chartData={sessionOsTypeChartData}
chartConfig={sessionOSChartConfig}
key='name'
/>
</div>
</div>
}
</div>
Expand Down
70 changes: 70 additions & 0 deletions ui/components/custom/BarChart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"use client"

import { Bar, BarChart, CartesianGrid, Rectangle, XAxis } from "recharts"

import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import {
ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart"

interface ChartPieProps {
title: string;
chartData: any;
key: string;
chartConfig: ChartConfig;
}

export function ChartBar({ title, chartData, key, chartConfig }: ChartPieProps) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>
<ChartContainer config={chartConfig}>
<BarChart accessibilityLayer data={chartData}>
<CartesianGrid vertical={false} />
<XAxis
dataKey={key}
tickLine={false}
tickMargin={10}
axisLine={false}
tickFormatter={(value) =>
String(chartData[value]?.name)
}
/>
<ChartTooltip
cursor={false}
content={<ChartTooltipContent hideLabel />}
/>
<Bar
dataKey="count"
strokeWidth={2}
radius={8}
activeIndex={2}
activeBar={({ ...props }) => {
return (
<Rectangle
{...props}
fillOpacity={0.8}
stroke={props.payload.fill}
strokeDasharray={4}
strokeDashoffset={4}
/>
)
}}
/>
</BarChart>
</ChartContainer>
</CardContent>
</Card>
)
}
Loading

0 comments on commit 6cc04e5

Please sign in to comment.