feat(ui): add notification system (#8394)

Co-authored-by: Pablo Lara <larabjj@gmail.com>
This commit is contained in:
sumit-tft
2025-08-05 12:36:15 +05:30
committed by GitHub
parent af8fbaf2cd
commit cf8402e013
9 changed files with 202 additions and 0 deletions
+2
View File
@@ -10,6 +10,8 @@ NEXT_PUBLIC_API_BASE_URL=${API_BASE_URL}
NEXT_PUBLIC_API_DOCS_URL=http://prowler-api:8080/api/v1/docs
AUTH_TRUST_HOST=true
UI_PORT=3000
# Temp URL for feeds need to use actual
RSS_FEED_URL=https://prowler.com/blog/rss
# openssl rand -base64 32
AUTH_SECRET="N/c6mnaS5+SWq81+819OrzQZlmx1Vxtp/orjttJSmw8="
# Google Tag Manager ID
+31
View File
@@ -0,0 +1,31 @@
"use server";
import Parser from "rss-parser";
const RSS_FEED_URL = process.env.RSS_FEED_URL || "";
export const fetchFeeds = async (): Promise<any | any[]> => {
if (RSS_FEED_URL?.trim()?.length > 0) {
const parser = new Parser();
try {
// TODO: Need to update return logic when actual URL is updated for RSS FEED
const feed = await parser.parseURL(RSS_FEED_URL);
return [
{
title: feed.title,
description: feed.description,
link: feed.link,
lastBuildDate: new Date(feed.lastBuildDate).toLocaleString(),
},
];
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error fetching RSS feed:", error);
return [];
}
} else {
// eslint-disable-next-line no-console
console.warn("RSS url not set");
return [];
}
};
+1
View File
@@ -0,0 +1 @@
export * from "./feeds";
+91
View File
@@ -0,0 +1,91 @@
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { fetchFeeds } from "@/actions/feeds";
import { BellIcon as Icon } from "@/components/icons";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu/dropdown-menu";
import { cn } from "@/lib/utils";
import { Button } from "../ui/button/button";
interface Feed {
title: string;
link: string;
description: string;
lastBuildDate: string;
}
export const FeedsDetail = () => {
// TODO: Need to update with actual interface when actual RSS data finialized
const [feed, setFeed] = useState<Feed[]>([]);
useEffect(() => {
const fetchFeedDetails = async () => {
const feeds = await fetchFeeds();
setFeed(feeds);
};
fetchFeedDetails();
}, []);
return (
<>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className={cn(
"relative",
"rounded-full",
"bg-transparent",
"p-2",
"h-8",
"w-8",
)}
>
<Icon size={18} />
{/* TODO: Update this condition once the RSS data response structure is finalized */}
{feed.length > 0 && (
<span className="absolute right-0 top-0 h-2 w-2 rounded-full bg-red-500 dark:bg-gray-400"></span>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" forceMount>
<h3 className="px-2 text-base font-medium">Feeds</h3>
<div className="max-h-48 w-80 overflow-y-auto">
{feed.length === 0 ? (
<p className="py-4 text-center text-gray-500">
No feeds available
</p>
) : (
feed.map((item, index) => (
<DropdownMenuItem key={index} className="hover:cursor-pointer">
<Link
href={item.link}
target="_blank"
className="flex flex-col"
>
<h3 className="text-small font-medium leading-none">
{item.title}
</h3>
<p className="text-sm text-gray-500">{item.description}</p>
<span className="text-muted-foreground mt-1 text-xs text-gray-400">
{item.lastBuildDate}
</span>
</Link>
</DropdownMenuItem>
))
)}
</div>
</DropdownMenuContent>
</DropdownMenu>
</>
);
};
+1
View File
@@ -0,0 +1 @@
export * from "./feeds-detail";
+25
View File
@@ -1163,3 +1163,28 @@ export const LighthouseIcon: React.FC<IconSvgProps> = ({
</svg>
);
};
export const BellIcon: React.FC<IconSvgProps> = ({
size = 24,
width,
height,
...props
}) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
width={size || width}
height={size || height}
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M10.268 21a2 2 0 0 0 3.464 0" />
<path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" />
</svg>
);
};
+2
View File
@@ -27,6 +27,8 @@ export function Navbar({ title, icon }: NavbarProps) {
/>
</div>
<div className="flex flex-1 items-center justify-end gap-3">
{/* TODO: Uncomment when this feature is enabled and ready for release */}
{/* {process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true" && <FeedsDetail />} */}
<ThemeSwitch />
<UserNav />
</div>
+48
View File
@@ -53,6 +53,7 @@
"react-hook-form": "^7.52.2",
"react-markdown": "^10.1.0",
"recharts": "^2.15.2",
"rss-parser": "^3.13.0",
"server-only": "^0.0.1",
"shadcn-ui": "^0.2.3",
"sharp": "^0.33.5",
@@ -8839,6 +8840,15 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/entities": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
"license": "BSD-2-Clause",
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/environment": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
@@ -14723,6 +14733,16 @@
"node": "*"
}
},
"node_modules/rss-parser": {
"version": "3.13.0",
"resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
"integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
"license": "MIT",
"dependencies": {
"entities": "^2.0.3",
"xml2js": "^0.5.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -14831,6 +14851,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
@@ -16802,6 +16828,28 @@
"dev": true,
"license": "ISC"
},
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/yaml": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz",
+1
View File
@@ -43,6 +43,7 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-hook-form": "^7.52.2",
"rss-parser": "^3.13.0",
"react-markdown": "^10.1.0",
"recharts": "^2.15.2",
"server-only": "^0.0.1",