mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-04 19:21:51 +00:00
feat(ui): add notification system (#8394)
Co-authored-by: Pablo Lara <larabjj@gmail.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./feeds";
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./feeds-detail";
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
Generated
+48
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user