App directory refactoring
This commit is contained in:
parent
a53a26f4c0
commit
b312a8c862
153 changed files with 1532 additions and 1447 deletions
|
|
@ -1,63 +0,0 @@
|
|||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { AppProps } from "next/app";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { useRouter } from "next/router";
|
||||
import Head from "next/head";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { CacheProvider, EmotionCache } from "@emotion/react";
|
||||
import { CookiesProvider } from "react-cookie";
|
||||
import { I18n } from "react-polyglot";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
||||
import { AppProvider } from "components/AppProvider";
|
||||
import createEmotionCache from "lib/createEmotionCache";
|
||||
import Favicon from "images/favicon.ico";
|
||||
import en from "locales/en.json";
|
||||
import fr from "locales/fr.json";
|
||||
import "@fontsource/poppins/400.css";
|
||||
import "@fontsource/poppins/700.css";
|
||||
import "@fontsource/roboto/400.css";
|
||||
import "@fontsource/roboto/700.css";
|
||||
import "@fontsource/playfair-display/900.css";
|
||||
import "styles/global.css";
|
||||
import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||
|
||||
LicenseInfo.setLicenseKey(process.env.MUI_LICENSE_KEY ?? "");
|
||||
|
||||
const clientSideEmotionCache: any = createEmotionCache();
|
||||
|
||||
const messages: any = { en, fr };
|
||||
|
||||
interface LeafcutterWebProps extends AppProps {
|
||||
// eslint-disable-next-line react/require-default-props
|
||||
emotionCache?: EmotionCache;
|
||||
}
|
||||
|
||||
const LeafcutterWeb = (props: LeafcutterWebProps) => {
|
||||
const { locale = "en" } = useRouter();
|
||||
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="icon" type="image/png" href={Favicon.src} />
|
||||
</Head>
|
||||
<SessionProvider session={(pageProps as any).session}>
|
||||
<CacheProvider value={emotionCache}>
|
||||
<CookiesProvider>
|
||||
<CssBaseline />
|
||||
<AppProvider>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<I18n locale={locale} messages={messages[locale]}>
|
||||
<Component {...pageProps} />
|
||||
</I18n>
|
||||
</LocalizationProvider>
|
||||
</AppProvider>
|
||||
</CookiesProvider>
|
||||
</CacheProvider>
|
||||
</SessionProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LeafcutterWeb;
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
// eslint-disable-next-line no-use-before-define
|
||||
import * as React from "react";
|
||||
import Document, { Html, Head, Main, NextScript } from "next/document";
|
||||
import createEmotionServer from "@emotion/server/create-instance";
|
||||
import createEmotionCache from "lib/createEmotionCache";
|
||||
|
||||
export default class LeafcutterDocument extends Document {
|
||||
render() {
|
||||
return (
|
||||
<Html lang="en">
|
||||
<Head />
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
LeafcutterDocument.getInitialProps = async (ctx): Promise<any> => {
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
const cache = createEmotionCache();
|
||||
const { extractCriticalToChunks } = createEmotionServer(cache as any);
|
||||
|
||||
ctx.renderPage = () =>
|
||||
originalRenderPage({
|
||||
enhanceApp: (App: any) => (props: any) =>
|
||||
<App emotionCache={cache} {...props} />,
|
||||
});
|
||||
|
||||
const initialProps = await Document.getInitialProps(ctx);
|
||||
const emotionStyles = extractCriticalToChunks(initialProps.html);
|
||||
const emotionStyleTags = emotionStyles.styles.map((style) => (
|
||||
<style
|
||||
data-emotion={`${style.key} ${style.ids.join(" ")}`}
|
||||
key={style.key}
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: style.css }}
|
||||
/>
|
||||
));
|
||||
|
||||
return {
|
||||
...initialProps,
|
||||
styles: [
|
||||
...React.Children.toArray(initialProps.styles),
|
||||
...emotionStyleTags,
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import Head from "next/head";
|
||||
import Image from "next/legacy/image";
|
||||
import Link from "next/link";
|
||||
import { Grid, Container, Box, Button } from "@mui/material";
|
||||
import { Layout } from "components/Layout";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { PageHeader } from "components/PageHeader";
|
||||
import { AboutFeature } from "components/AboutFeature";
|
||||
import { AboutBox } from "components/AboutBox";
|
||||
import AbstractDiagram from "images/abstract-diagram.png";
|
||||
import AboutHeader from "images/about-header.png";
|
||||
import Globe from "images/globe.png";
|
||||
import Controls from "images/controls.png";
|
||||
import CommunityBackground from "images/community-background.png";
|
||||
import Bicycle from "images/bicycle.png";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type AboutProps = {
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const About: NextPage<AboutProps> = ({ embedded }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { white, leafcutterElectricBlue, cdrLinkOrange },
|
||||
typography: { h1, h4, p },
|
||||
} = useAppContext();
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<PageHeader
|
||||
backgroundColor={leafcutterElectricBlue}
|
||||
sx={{
|
||||
backgroundImage: `url(${AboutHeader.src})`,
|
||||
backgroundSize: "200px",
|
||||
backgroundPosition: "bottom right",
|
||||
backgroundRepeat: "no-repeat",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item xs={9}>
|
||||
<Box component="h1" sx={h1}>
|
||||
{t("aboutLeafcutterTitle")}
|
||||
</Box>
|
||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||
{t("aboutLeafcutterDescription")}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageHeader>
|
||||
<Container maxWidth="lg">
|
||||
<AboutFeature
|
||||
title={t("whatIsLeafcutterTitle")}
|
||||
description={t("whatIsLeafcutterDescription")}
|
||||
direction="row"
|
||||
image={AbstractDiagram}
|
||||
showBackground={false}
|
||||
textColumns={8}
|
||||
/>
|
||||
<AboutFeature
|
||||
title={t("whatIsItForTitle")}
|
||||
description={t("whatIsItForDescription")}
|
||||
direction="row-reverse"
|
||||
image={Controls}
|
||||
showBackground
|
||||
textColumns={8}
|
||||
/>
|
||||
<AboutFeature
|
||||
title={t("whoCanUseItTitle")}
|
||||
description={t("whoCanUseItDescription")}
|
||||
direction="row"
|
||||
image={Globe}
|
||||
showBackground
|
||||
textColumns={6}
|
||||
/>
|
||||
</Container>
|
||||
<AboutBox backgroundColor={cdrLinkOrange}>
|
||||
<Box component="h4" sx={{ ...h4, mt: 0 }}>
|
||||
{t("whereDataComesFromTitle")}
|
||||
</Box>
|
||||
{t("whereDataComesFromDescription")
|
||||
.split("\n")
|
||||
.map((line: string, i: number) => (
|
||||
<Box component="p" key={i} sx={p}>
|
||||
{line}
|
||||
</Box>
|
||||
))}
|
||||
</AboutBox>
|
||||
<AboutBox backgroundColor={leafcutterElectricBlue}>
|
||||
<Box component="h4" sx={{ ...h4, mt: 0 }}>
|
||||
{t("projectSupportTitle")}
|
||||
</Box>
|
||||
{t("projectSupportDescription")
|
||||
.split("\n")
|
||||
.map((line: string, i: number) => (
|
||||
<Box component="p" key={i} sx={p}>
|
||||
{line}
|
||||
</Box>
|
||||
))}
|
||||
</AboutBox>
|
||||
<Box
|
||||
sx={{
|
||||
backgroundImage: `url(${CommunityBackground.src})`,
|
||||
backgroundSize: "90%",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "center",
|
||||
position: "relative",
|
||||
height: "700px",
|
||||
}}
|
||||
>
|
||||
<Box sx={{ position: "absolute", left: 0, bottom: -20, width: 300 }}>
|
||||
<Image src={Bicycle} alt="" />
|
||||
</Box>
|
||||
<Container
|
||||
maxWidth="md"
|
||||
sx={{ textAlign: "center", paddingTop: "280px" }}
|
||||
>
|
||||
<Box
|
||||
component="h4"
|
||||
sx={{ ...h4, maxWidth: 500, margin: "0 auto", mt: 3 }}
|
||||
>
|
||||
{t("interestedInLeafcutterTitle")}
|
||||
</Box>
|
||||
{t("interestedInLeafcutterDescription")
|
||||
.split("\n")
|
||||
.map((line: string, i: number) => (
|
||||
<Box
|
||||
component="p"
|
||||
key={i}
|
||||
sx={{ ...p, maxWidth: 500, margin: "0 auto" }}
|
||||
>
|
||||
{line}
|
||||
</Box>
|
||||
))}
|
||||
<Link href="mailto:info@digiresilience.org" passHref>
|
||||
<Button
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
borderRadius: 500,
|
||||
color: white,
|
||||
backgroundColor: cdrLinkOrange,
|
||||
fontWeight: "bold",
|
||||
textTransform: "uppercase",
|
||||
pl: 6,
|
||||
pr: 5,
|
||||
mt: 4,
|
||||
":hover": {
|
||||
backgroundColor: leafcutterElectricBlue,
|
||||
color: white,
|
||||
opacity: 0.8,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("contactUs")}
|
||||
</Button>
|
||||
</Link>
|
||||
</Container>
|
||||
</Box>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => ({ props: { embedded: getEmbedded(context) } });
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import NextAuth from "next-auth";
|
||||
import Google from "next-auth/providers/google";
|
||||
import Apple from "next-auth/providers/apple";
|
||||
|
||||
export default NextAuth({
|
||||
providers: [
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET ?? "",
|
||||
}),
|
||||
Apple({
|
||||
clientId: process.env.APPLE_CLIENT_ID ?? "",
|
||||
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
|
||||
}),
|
||||
],
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
|
||||
const withAuthInfo =
|
||||
(handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session: any = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
req.headers["x-proxy-user"] = session.email.toLowerCase();
|
||||
req.headers["x-proxy-roles"] = "leafcutter_user";
|
||||
const auth = `${session.email.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
|
||||
const buff = Buffer.from(auth);
|
||||
const base64data = buff.toString("base64");
|
||||
req.headers.Authorization = `Basic ${base64data}`;
|
||||
return handler(req, res);
|
||||
};
|
||||
|
||||
const proxy = createProxyMiddleware({
|
||||
target: process.env.OPENSEARCH_DASHBOARDS_URL,
|
||||
changeOrigin: true,
|
||||
xfwd: true,
|
||||
});
|
||||
|
||||
export default withAuthInfo(proxy);
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
externalResolver: true,
|
||||
},
|
||||
};
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { getUserMetadata, saveUserMetadata } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
||||
}
|
||||
|
||||
const { email }: any = session;
|
||||
const { name, query } = JSON.parse(req.body);
|
||||
const result = await getUserMetadata(email);
|
||||
const { savedSearches } = result;
|
||||
await saveUserMetadata(email, {
|
||||
savedSearches: [...savedSearches, { name, query }]
|
||||
});
|
||||
const { savedSearches: updatedSavedSearches } = await getUserMetadata(email);
|
||||
return res.json(updatedSavedSearches);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { getUserMetadata, saveUserMetadata } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
||||
}
|
||||
|
||||
const { email }: any = session;
|
||||
const { name } = JSON.parse(req.body);
|
||||
const { savedSearches } = await getUserMetadata(email);
|
||||
const updatedSavedSearches = savedSearches.filter((search: any) => search.name !== name);
|
||||
const result = await saveUserMetadata(email, { savedSearches: updatedSavedSearches });
|
||||
|
||||
return res.json({ result });
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { getUserMetadata } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
if (req.method !== "GET") {
|
||||
return res.status(500).json({ message: "Only GET requests are allowed" });
|
||||
}
|
||||
|
||||
const { email }: any = session;
|
||||
const { savedSearches } = await getUserMetadata(email);
|
||||
|
||||
return res.json(savedSearches);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { getTrends } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
const results = await getTrends(5);
|
||||
return res.json(results)
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { Client } from "@opensearch-project/opensearch";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import taxonomy from "config/taxonomy.json";
|
||||
import unRegions from "config/unRegions.json";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { headers: { authorization }, body: { tickets } } = req;
|
||||
const baseURL = `https://${process.env.OPENSEARCH_URL}`;
|
||||
const client = new Client({
|
||||
node: baseURL,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
headers: {
|
||||
authorization
|
||||
}
|
||||
});
|
||||
|
||||
const succeeded = [];
|
||||
const failed = [];
|
||||
|
||||
for await (const ticket of tickets) {
|
||||
const { id } = ticket;
|
||||
try {
|
||||
const country = ticket.country[0] ?? "none";
|
||||
// @ts-expect-error
|
||||
const translatedCountry = taxonomy.country[country]?.display ?? "none";
|
||||
const countryDetails: any = unRegions.find((c) => c.name === translatedCountry);
|
||||
const augmentedTicket = {
|
||||
...ticket,
|
||||
region: countryDetails['sub-region']?.toLowerCase().replace(" ", "-") ?? null,
|
||||
continent: countryDetails.region?.toLowerCase().replace(" ", "-") ?? null,
|
||||
};
|
||||
const out = await client.create({
|
||||
id: uuid(),
|
||||
index: "sample_tagged_tickets",
|
||||
refresh: true,
|
||||
body: augmentedTicket,
|
||||
});
|
||||
console.log(out);
|
||||
succeeded.push(id);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
failed.push(id);
|
||||
}
|
||||
}
|
||||
|
||||
const results = { succeeded, failed };
|
||||
return res.json(results);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { createUserVisualization } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
||||
}
|
||||
|
||||
const { visualizationID, title, description, query } = req.body;
|
||||
const id = await createUserVisualization({
|
||||
email: session.email as string,
|
||||
visualizationID,
|
||||
title,
|
||||
description,
|
||||
query
|
||||
});
|
||||
|
||||
return res.json({ id });
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { deleteUserVisualization } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
||||
}
|
||||
|
||||
const { id } = req.body;
|
||||
await deleteUserVisualization(session.email as string, id);
|
||||
|
||||
return res.json({ id });
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { performQuery } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
const { searchQuery } = req.query;
|
||||
const rawQuery = await JSON.parse(decodeURI(searchQuery as string));
|
||||
const results = await performQuery(rawQuery, 1000);
|
||||
|
||||
return res.json(results)
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
import { updateUserVisualization } from "lib/opensearch";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getToken({
|
||||
req,
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return res.redirect("/login");
|
||||
}
|
||||
|
||||
if (req.method !== "POST") {
|
||||
return res.status(500).json({ message: "Only POST requests are allowed" });
|
||||
}
|
||||
|
||||
const { id, title, description, query } = req.body;
|
||||
await updateUserVisualization({
|
||||
email: session.email as string,
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
query
|
||||
});
|
||||
|
||||
return res.json({ id });
|
||||
};
|
||||
|
||||
export default handler;
|
||||
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
import { FC, useEffect } from "react";
|
||||
import { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import Head from "next/head";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { useRouter } from "next/router";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { getTemplates } from "lib/opensearch";
|
||||
import { Layout } from "components/Layout";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { PageHeader } from "components/PageHeader";
|
||||
import { VisualizationBuilder } from "components/VisualizationBuilder";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type CreateProps = {
|
||||
templates: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const Create: FC<CreateProps> = ({ templates, embedded }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { cdrLinkOrange },
|
||||
typography: { h1, h4 },
|
||||
} = useAppContext();
|
||||
const router = useRouter();
|
||||
const cookieName = "searchIntroComplete";
|
||||
const [cookies, setCookie] = useCookies([cookieName]);
|
||||
const searchIntroComplete = parseInt(cookies[cookieName], 10) || 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (searchIntroComplete === 0) {
|
||||
setCookie(cookieName, `${1}`, { path: "/" });
|
||||
router.push(`${router.pathname}?group=search&tooltip=1&checklist=1`);
|
||||
}
|
||||
}, [searchIntroComplete, router, setCookie]);
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
|
||||
<PageHeader backgroundColor={cdrLinkOrange}>
|
||||
<Grid container direction="row" spacing={2} alignItems="center">
|
||||
{/* <Grid item xs={2} sx={{ textAlign: "center" }}>
|
||||
<Image src={SearchCreateHeader} width={100} height={100} alt="" />
|
||||
</Grid> */}
|
||||
<Grid container direction="column" item xs={10}>
|
||||
<Grid item>
|
||||
<Box component="h1" sx={{ ...h1 }}>
|
||||
{t("searchAndCreateTitle")}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||
{t("searchAndCreateSubtitle")}
|
||||
</Box>
|
||||
</Grid>
|
||||
{/* <Grid>
|
||||
<Box component="p" sx={{ ...p }}>
|
||||
{t("searchAndCreateDescription")}
|
||||
</Box>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageHeader>
|
||||
|
||||
<VisualizationBuilder templates={templates} />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Create;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const templates = await getTemplates(100);
|
||||
|
||||
return { props: { templates, embedded: getEmbedded(context) } };
|
||||
};
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
import Head from "next/head";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import { Layout } from "components/Layout";
|
||||
import { PageHeader } from "components/PageHeader";
|
||||
import { Question } from "components/Question";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import FaqHeader from "images/faq-header.svg";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type FAQProps = {
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const FAQ: NextPage<FAQProps> = ({ embedded }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { lavender },
|
||||
typography: { h1, h4, p },
|
||||
} = useAppContext();
|
||||
|
||||
const questions = [
|
||||
{
|
||||
question: t("whatIsLeafcutterQuestion"),
|
||||
answer: t("whatIsLeafcutterAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("whoBuiltLeafcutterQuestion"),
|
||||
answer: t("whoBuiltLeafcutterAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("whoCanUseLeafcutterQuestion"),
|
||||
answer: t("whoCanUseLeafcutterAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("whatCanYouDoWithLeafcutterQuestion"),
|
||||
answer: t("whatCanYouDoWithLeafcutterAnswer"),
|
||||
},
|
||||
|
||||
{
|
||||
question: t("whereIsTheDataComingFromQuestion"),
|
||||
answer: t("whereIsTheDataComingFromAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("whereIsTheDataStoredQuestion"),
|
||||
answer: t("whereIsTheDataStoredAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("howDoWeKeepTheDataSafeQuestion"),
|
||||
answer: t("howDoWeKeepTheDataSafeAnswer"),
|
||||
},
|
||||
|
||||
{
|
||||
question: t("howLongDoYouKeepTheDataQuestion"),
|
||||
answer: t("howLongDoYouKeepTheDataAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("whatOrganizationsAreParticipatingQuestion"),
|
||||
answer: t("whatOrganizationsAreParticipatingAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("howDidYouGetMyProfileInformationQuestion"),
|
||||
answer: t("howDidYouGetMyProfileInformationAnswer"),
|
||||
},
|
||||
{
|
||||
question: t("howCanILearnMoreAboutLeafcutterQuestion"),
|
||||
answer: t("howCanILearnMoreAboutLeafcutterAnswer"),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<PageHeader
|
||||
backgroundColor={lavender}
|
||||
sx={{
|
||||
backgroundImage: `url(${FaqHeader.src})`,
|
||||
backgroundSize: "150px",
|
||||
backgroundPosition: "bottom right",
|
||||
backgroundRepeat: "no-repeat",
|
||||
}}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Box component="h1" sx={{ ...h1 }}>
|
||||
{t("frequentlyAskedQuestionsTitle")}
|
||||
</Box>
|
||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||
{t("frequentlyAskedQuestionsSubtitle")}
|
||||
</Box>
|
||||
<Box component="p" sx={{ ...p }}>
|
||||
{t("frequentlyAskedQuestionsDescription")}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageHeader>
|
||||
{questions.map((q: any, index: number) => (
|
||||
<Question key={index} question={q.question} answer={q.answer} />
|
||||
))}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default FAQ;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => ({ props: { embedded: getEmbedded(context) } });
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
import { useEffect } from "react";
|
||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { getSession } from "next-auth/react";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Grid, Button } from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { Layout } from "components/Layout";
|
||||
import { getUserVisualizations } from "lib/opensearch";
|
||||
import { Welcome } from "components/Welcome";
|
||||
import { WelcomeDialog } from "components/WelcomeDialog";
|
||||
import { VisualizationCard } from "components/VisualizationCard";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type MyVisualizationsProps = {
|
||||
visualizations: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const MyVisualizations: NextPage<MyVisualizationsProps> = ({
|
||||
visualizations,
|
||||
embedded,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const cookieName = "homeIntroComplete";
|
||||
const [cookies, setCookie] = useCookies([cookieName]);
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { white, leafcutterElectricBlue },
|
||||
typography: { h4 },
|
||||
} = useAppContext();
|
||||
const homeIntroComplete = parseInt(cookies[cookieName], 10) || 0;
|
||||
|
||||
useEffect(() => {
|
||||
if (homeIntroComplete === 0) {
|
||||
setCookie(cookieName, `${1}`, { path: "/" });
|
||||
router.push(`${router.pathname}?tooltip=welcome`);
|
||||
}
|
||||
}, [homeIntroComplete, router, setCookie]);
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<Welcome />
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
sx={{ pt: "22px", pb: "22px" }}
|
||||
direction="row-reverse"
|
||||
>
|
||||
<Link href="/create" passHref>
|
||||
<Button
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
borderRadius: 500,
|
||||
color: leafcutterElectricBlue,
|
||||
border: `2px solid ${leafcutterElectricBlue}`,
|
||||
fontWeight: "bold",
|
||||
textTransform: "uppercase",
|
||||
pl: 6,
|
||||
pr: 5,
|
||||
":hover": {
|
||||
backgroundColor: leafcutterElectricBlue,
|
||||
color: white,
|
||||
opacity: 0.8,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{t("createVisualization")}
|
||||
</Button>
|
||||
</Link>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
wrap="wrap"
|
||||
spacing={3}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{visualizations.length === 0 ? (
|
||||
<Grid
|
||||
container
|
||||
sx={{ height: 300, width: "100%", pt: 10 }}
|
||||
justifyContent="center"
|
||||
>
|
||||
<Grid item sx={{ ...h4, width: 450, textAlign: "center" }}>
|
||||
<ReactMarkdown>{t("noSavedVisualizations")}</ReactMarkdown>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : null}
|
||||
{visualizations.map((visualization: any, index: number) => (
|
||||
<VisualizationCard
|
||||
id={visualization.id}
|
||||
key={index}
|
||||
title={visualization.title}
|
||||
description={visualization.description}
|
||||
url={visualization.url}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
<WelcomeDialog />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyVisualizations;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const session = (await getSession(context)) ?? null;
|
||||
const visualizations = await getUserVisualizations(
|
||||
session?.user?.email ?? "none",
|
||||
20
|
||||
);
|
||||
return { props: { visualizations, embedded: getEmbedded(context) } };
|
||||
};
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
import Head from "next/head";
|
||||
import { NextPage } from "next";
|
||||
import Link from "next/link";
|
||||
import Image from "next/legacy/image";
|
||||
import { Box, Grid, Container, IconButton } from "@mui/material";
|
||||
import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { LanguageSelect } from "components/LanguageSelect";
|
||||
import LeafcutterLogoLarge from "images/leafcutter-logo-large.png";
|
||||
import { signIn, getSession } from "next-auth/react";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
|
||||
type LoginProps = {
|
||||
session: any;
|
||||
};
|
||||
|
||||
const Login: NextPage<LoginProps> = ({ session }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { leafcutterElectricBlue, lightGray },
|
||||
typography: { h1, h4 },
|
||||
} = useAppContext();
|
||||
const buttonStyles = {
|
||||
backgroundColor: lightGray,
|
||||
borderRadius: 500,
|
||||
width: "100%",
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Leafcutter: Login</title>
|
||||
</Head>
|
||||
<Grid container direction="row-reverse" sx={{ p: 3 }}>
|
||||
<Grid item>
|
||||
<LanguageSelect />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Container maxWidth="md" sx={{ mt: 3, mb: 20 }}>
|
||||
<Grid container spacing={2} direction="column" alignItems="center">
|
||||
<Grid item>
|
||||
<Box sx={{ maxWidth: 200 }}>
|
||||
<Image src={LeafcutterLogoLarge} alt="" objectFit="fill" />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item sx={{ textAlign: "center" }}>
|
||||
<Box component="h1" sx={{ ...h1, color: leafcutterElectricBlue }}>
|
||||
{t("welcomeToLeafcutter")}
|
||||
</Box>
|
||||
<Box component="h4" sx={{ ...h4, mt: 1 }}>
|
||||
{t("welcomeToLeafcutterDescription")}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
{!session ? (
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
sx={{ width: 450, mt: 1 }}
|
||||
>
|
||||
<Grid item sx={{ width: "100%" }}>
|
||||
<IconButton
|
||||
sx={buttonStyles}
|
||||
onClick={() =>
|
||||
signIn("google", {
|
||||
callbackUrl: `${window.location.origin}/setup`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<GoogleIcon sx={{ mr: 1 }} />
|
||||
{`${t("signInWith")} Google`}
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item sx={{ width: "100%" }}>
|
||||
<IconButton
|
||||
sx={buttonStyles}
|
||||
onClick={() =>
|
||||
signIn("apple", {
|
||||
callbackUrl: `${window.location.origin}/setup`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<AppleIcon sx={{ mr: 1 }} />
|
||||
{`${t("signInWith")} Apple`}
|
||||
</IconButton>
|
||||
</Grid>
|
||||
<Grid item sx={{ mt: 2 }}>
|
||||
<Box>
|
||||
{t("dontHaveAccount")}{" "}
|
||||
<Link href="mailto:info@digiresilience.org">
|
||||
{t("requestAccessHere")}
|
||||
</Link>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : null}
|
||||
{session ? (
|
||||
<>
|
||||
<Box component="h4" sx={h4}>
|
||||
{`${t("welcome")}, ${
|
||||
session.user.name ?? session.user.email
|
||||
}.`}
|
||||
</Box>
|
||||
<Link href="/">{t("goHome")}</Link>
|
||||
</>
|
||||
) : null}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const session = (await getSession(context)) ?? null;
|
||||
|
||||
return {
|
||||
props: { session },
|
||||
};
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
import { FC } from "react";
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
// import { Client } from "@opensearch-project/opensearch";
|
||||
import { RawDataViewer } from "components/RawDataViewer";
|
||||
import { VisualizationDetail } from "components/VisualizationDetail";
|
||||
// import { createVisualization } from "lib/opensearch";
|
||||
|
||||
interface PreviewProps {
|
||||
visualization: any;
|
||||
visualizationType: string;
|
||||
data: any[];
|
||||
}
|
||||
|
||||
const Preview: FC<PreviewProps> = ({
|
||||
visualization,
|
||||
visualizationType,
|
||||
data,
|
||||
}) =>
|
||||
visualizationType === "rawData" ? (
|
||||
<RawDataViewer rows={data} height={750} />
|
||||
) : (
|
||||
<VisualizationDetail {...visualization} />
|
||||
);
|
||||
|
||||
export default Preview;
|
||||
|
||||
/*
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
|
||||
|
||||
const {
|
||||
visualizationID,
|
||||
searchQuery,
|
||||
visualizationType = "table",
|
||||
} = context.query;
|
||||
|
||||
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||
const client = new Client({
|
||||
node,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
res.props.visualizationType = visualizationType as string;
|
||||
|
||||
if (visualizationType !== "rawData") {
|
||||
await createVisualization({
|
||||
id: visualizationID as string,
|
||||
query: await JSON.parse(decodeURI(searchQuery as string)),
|
||||
kind: visualizationType as string,
|
||||
});
|
||||
const rawResponse = await client.search({
|
||||
index: ".kibana_1",
|
||||
size: 200,
|
||||
});
|
||||
const response = rawResponse.body;
|
||||
|
||||
const hits = response.hits.hits.filter(
|
||||
(hit) => hit._id.split(":")[1] === visualizationID[0]
|
||||
);
|
||||
const hit = hits[0];
|
||||
res.props.visualization = {
|
||||
id: hit._id.split(":")[1],
|
||||
title: hit._source.visualization.title,
|
||||
description: hit._source.visualization.description,
|
||||
url: `/app/visualize?security_tenant=global#/edit/${
|
||||
hit._id.split(":")[1]
|
||||
}?embed=true`,
|
||||
};
|
||||
}
|
||||
|
||||
const rawQuery = await JSON.parse(decodeURI(searchQuery as string));
|
||||
const query = {
|
||||
bool: {
|
||||
should: [],
|
||||
must_not: [],
|
||||
},
|
||||
};
|
||||
|
||||
if (rawQuery.impactedTechnology.values.length > 0) {
|
||||
rawQuery.impactedTechnology.values.forEach((value) => {
|
||||
query.bool.should.push({
|
||||
match: { technology: value },
|
||||
});
|
||||
});
|
||||
}
|
||||
console.log({ query });
|
||||
const dataResponse = await client.search({
|
||||
index: "demo_data",
|
||||
size: 200,
|
||||
body: { query },
|
||||
});
|
||||
console.log({ dataResponse });
|
||||
res.props.data = dataResponse.body.hits.hits.map((hit) => ({
|
||||
id: hit._id,
|
||||
...hit._source,
|
||||
}));
|
||||
console.log({ data: res.props.data });
|
||||
console.log(res.props.data[0]);
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
*/
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import { useLayoutEffect } from "react";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { Grid, CircularProgress } from "@mui/material";
|
||||
import Iframe from "react-iframe";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
|
||||
const Setup: NextPage = () => {
|
||||
const {
|
||||
colors: { leafcutterElectricBlue },
|
||||
} = useAppContext();
|
||||
const router = useRouter();
|
||||
useLayoutEffect(() => {
|
||||
setTimeout(() => router.push("/"), 4000);
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<Grid
|
||||
sx={{ width: "100%", height: 700 }}
|
||||
direction="row"
|
||||
justifyContent="space-around"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sx={{
|
||||
width: "200px",
|
||||
height: 700,
|
||||
textAlign: "center",
|
||||
margin: "0 auto",
|
||||
pt: 30,
|
||||
}}
|
||||
>
|
||||
<Iframe url="/app/home" height="1" width="1" frameBorder={0} />
|
||||
<CircularProgress
|
||||
size={80}
|
||||
thickness={5}
|
||||
sx={{ color: leafcutterElectricBlue }}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default Setup;
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import Head from "next/head";
|
||||
import { Grid, Box } from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { Layout } from "components/Layout";
|
||||
import { getTrends } from "lib/opensearch";
|
||||
import { PageHeader } from "components/PageHeader";
|
||||
import { VisualizationCard } from "components/VisualizationCard";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type TrendsProps = {
|
||||
visualizations: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const Trends: NextPage<TrendsProps> = ({ visualizations, embedded }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { cdrLinkOrange },
|
||||
typography: { h1, h4, p },
|
||||
} = useAppContext();
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<PageHeader backgroundColor={cdrLinkOrange}>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
spacing={2}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
{/* <Grid item xs={3} sx={{ textAlign: "center" }}>
|
||||
<Image src={SearchCreateHeader} width={200} height={200} alt="" />
|
||||
</Grid> */}
|
||||
<Grid item container direction="column" xs={12}>
|
||||
<Grid item>
|
||||
<Box component="h1" sx={{ ...h1 }}>
|
||||
{t("trendsTitle")}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Box component="h4" sx={{ ...h4, mt: 1, mb: 1 }}>
|
||||
{t("trendsSubtitle")}
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid>
|
||||
<Box component="p" sx={{ ...p }}>
|
||||
{t("trendsDescription")}
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageHeader>
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
wrap="wrap"
|
||||
spacing={3}
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{visualizations.map((visualization: any, index: number) => (
|
||||
<VisualizationCard
|
||||
key={index}
|
||||
id={visualization.id}
|
||||
title={visualization.title}
|
||||
description={visualization.description}
|
||||
url={visualization.url}
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default Trends;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const visualizations = await getTrends(25);
|
||||
|
||||
return { props: { visualizations, embedded: getEmbedded(context) } };
|
||||
};
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import { Client } from "@opensearch-project/opensearch";
|
||||
import Head from "next/head";
|
||||
import { Layout } from "components/Layout";
|
||||
import { VisualizationDetail } from "components/VisualizationDetail";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type VisualizationProps = {
|
||||
visualization: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const Visualization: NextPage<VisualizationProps> = ({
|
||||
visualization,
|
||||
embedded,
|
||||
}) => (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<VisualizationDetail {...visualization} />
|
||||
</Layout>
|
||||
);
|
||||
|
||||
export default Visualization;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const { visualizationID } = context.query;
|
||||
|
||||
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||
const client = new Client({
|
||||
node,
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
|
||||
const rawResponse = await client.search({
|
||||
index: ".kibana_1",
|
||||
size: 200,
|
||||
});
|
||||
const response = rawResponse.body;
|
||||
|
||||
const hits = response.hits.hits.filter(
|
||||
// @ts-expect-error
|
||||
(hit: any) => hit._id.split(":")[1] === visualizationID[0]
|
||||
);
|
||||
const hit = hits[0];
|
||||
const visualization = {
|
||||
id: hit._id.split(":")[1],
|
||||
title: hit._source.visualization.title,
|
||||
description: hit._source.visualization.description,
|
||||
url: `/app/visualize?security_tenant=global#/edit/${
|
||||
hit._id.split(":")[1]
|
||||
}?embed=true`,
|
||||
};
|
||||
|
||||
return { props: { visualization, embedded: getEmbedded(context) } };
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue