Merge branch 'develop' into 'main'
Develop See merge request digiresilience/link/link-stack!2
This commit is contained in:
commit
5c2187c7b7
302 changed files with 9897 additions and 10332 deletions
|
|
@ -1,4 +1,4 @@
|
|||
image: node:20-bullseye-slim
|
||||
image: node:20-bookworm-slim
|
||||
|
||||
stages:
|
||||
- build
|
||||
|
|
@ -11,6 +11,7 @@ build-all:
|
|||
TURBO_TOKEN: $TURBO_TOKEN
|
||||
TURBO_TEAM: $TURBO_TEAM
|
||||
script:
|
||||
- npm install npm@latest -g
|
||||
- npm install -g turbo
|
||||
- npm ci
|
||||
- turbo build
|
||||
|
|
|
|||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"prettier.prettierPath": ""
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ WORKDIR ${APP_DIR}
|
|||
COPY .gitignore .gitignore
|
||||
COPY --from=builder ${APP_DIR}/out/json/ .
|
||||
COPY --from=builder ${APP_DIR}/out/package-lock.json ./package-lock.json
|
||||
RUN npm ci --omit=dev
|
||||
RUN npm ci
|
||||
|
||||
COPY --from=builder ${APP_DIR}/out/full/ .
|
||||
ARG LINK_EMBEDDED=true
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
import Head from "next/head";
|
||||
import { NextPage } from "next";
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
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 { LanguageSelect } from "app/_components/LanguageSelect";
|
||||
import LeafcutterLogoLarge from "images/leafcutter-logo-large.png";
|
||||
import { signIn, getSession } from "next-auth/react";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { signIn } from "next-auth/react";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
|
||||
type LoginProps = {
|
||||
session: any;
|
||||
};
|
||||
|
||||
const Login: NextPage<LoginProps> = ({ session }) => {
|
||||
export const Login: FC<LoginProps> = ({ session }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { leafcutterElectricBlue, lightGray },
|
||||
|
|
@ -30,9 +31,6 @@ const Login: NextPage<LoginProps> = ({ session }) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Leafcutter: Login</title>
|
||||
</Head>
|
||||
<Grid container direction="row-reverse" sx={{ p: 3 }}>
|
||||
<Grid item>
|
||||
<LanguageSelect />
|
||||
|
|
@ -114,13 +112,3 @@ const Login: NextPage<LoginProps> = ({ session }) => {
|
|||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Login;
|
||||
|
||||
export async function getServerSideProps(context: any) {
|
||||
const session = (await getSession(context)) ?? null;
|
||||
|
||||
return {
|
||||
props: { session },
|
||||
};
|
||||
}
|
||||
16
apps/leafcutter/app/(login)/login/page.tsx
Normal file
16
apps/leafcutter/app/(login)/login/page.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Metadata } from "next";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { Login } from "./_components/Login";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Login",
|
||||
};
|
||||
|
||||
export default async function Page() {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
||||
return <Login session={session} />;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,27 +1,22 @@
|
|||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
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 { useAppContext } from "app/_components/AppProvider";
|
||||
import { PageHeader } from "app/_components/PageHeader";
|
||||
import { AboutFeature } from "./AboutFeature";
|
||||
import { AboutBox } from "./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 }) => {
|
||||
export const About: FC = () => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { white, leafcutterElectricBlue, cdrLinkOrange },
|
||||
|
|
@ -29,10 +24,7 @@ const About: NextPage<AboutProps> = ({ embedded }) => {
|
|||
} = useAppContext();
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<>
|
||||
<PageHeader
|
||||
backgroundColor={leafcutterElectricBlue}
|
||||
sx={{
|
||||
|
|
@ -166,12 +158,6 @@ const About: NextPage<AboutProps> = ({ embedded }) => {
|
|||
</Link>
|
||||
</Container>
|
||||
</Box>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default About;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => ({ props: { embedded: getEmbedded(context) } });
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
import { FC, PropsWithChildren } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
import { useAppContext } from "../../../_components/AppProvider";
|
||||
|
||||
type AboutBoxProps = PropsWithChildren<{
|
||||
backgroundColor: string;
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import { Grid, Box, GridSize } from "@mui/material";
|
||||
import AboutDots from "images/about-dots.png";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
|
||||
interface AboutFeatureProps {
|
||||
title: string;
|
||||
5
apps/leafcutter/app/(main)/about/page.tsx
Normal file
5
apps/leafcutter/app/(main)/about/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { About } from './_components/About';
|
||||
|
||||
export default function Page() {
|
||||
return <About />;
|
||||
}
|
||||
|
|
@ -1,29 +1,26 @@
|
|||
"use client";
|
||||
|
||||
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 { useRouter, usePathname } from "next/navigation";
|
||||
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";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import { PageHeader } from "app/_components/PageHeader";
|
||||
import { VisualizationBuilder } from "app/_components/VisualizationBuilder";
|
||||
|
||||
type CreateProps = {
|
||||
templates: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const Create: FC<CreateProps> = ({ templates, embedded }) => {
|
||||
export const Create: FC<CreateProps> = ({ templates }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { cdrLinkOrange },
|
||||
typography: { h1, h4 },
|
||||
} = useAppContext();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const cookieName = "searchIntroComplete";
|
||||
const [cookies, setCookie] = useCookies([cookieName]);
|
||||
const searchIntroComplete = parseInt(cookies[cookieName], 10) || 0;
|
||||
|
|
@ -31,16 +28,12 @@ const Create: FC<CreateProps> = ({ templates, embedded }) => {
|
|||
useEffect(() => {
|
||||
if (searchIntroComplete === 0) {
|
||||
setCookie(cookieName, `${1}`, { path: "/" });
|
||||
router.push(`${router.pathname}?group=search&tooltip=1&checklist=1`);
|
||||
router.push(`${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" }}>
|
||||
|
|
@ -65,18 +58,7 @@ const Create: FC<CreateProps> = ({ templates, embedded }) => {
|
|||
</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) } };
|
||||
};
|
||||
8
apps/leafcutter/app/(main)/create/page.tsx
Normal file
8
apps/leafcutter/app/(main)/create/page.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { getTemplates } from "app/_lib/opensearch";
|
||||
import { Create } from "./_components/Create";
|
||||
|
||||
export default async function Page() {
|
||||
const templates = await getTemplates(100);
|
||||
|
||||
return <Create templates={templates} />;
|
||||
}
|
||||
|
|
@ -1,19 +1,14 @@
|
|||
import Head from "next/head";
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
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 { PageHeader } from "app/_components/PageHeader";
|
||||
import { Question } from "app/_components/Question";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import FaqHeader from "images/faq-header.svg";
|
||||
import { getEmbedded } from "lib/utils";
|
||||
|
||||
type FAQProps = {
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const FAQ: NextPage<FAQProps> = ({ embedded }) => {
|
||||
export const FAQ: FC = () => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { lavender },
|
||||
|
|
@ -70,10 +65,7 @@ const FAQ: NextPage<FAQProps> = ({ embedded }) => {
|
|||
];
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<>
|
||||
<PageHeader
|
||||
backgroundColor={lavender}
|
||||
sx={{
|
||||
|
|
@ -105,12 +97,6 @@ const FAQ: NextPage<FAQProps> = ({ embedded }) => {
|
|||
{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) } });
|
||||
5
apps/leafcutter/app/(main)/faq/page.tsx
Normal file
5
apps/leafcutter/app/(main)/faq/page.tsx
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
import { FAQ } from "./_components/FAQ";
|
||||
|
||||
export default function Page() {
|
||||
return <FAQ />;
|
||||
}
|
||||
22
apps/leafcutter/app/(main)/layout.tsx
Normal file
22
apps/leafcutter/app/(main)/layout.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { ReactNode } from "react";
|
||||
import "app/_styles/global.css";
|
||||
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 getConfig from "next/config";
|
||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||
import { InternalLayout } from "app/_components/InternalLayout";
|
||||
import { headers } from 'next/headers'
|
||||
|
||||
type LayoutProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const allHeaders = headers();
|
||||
const embedded = Boolean(allHeaders.get('x-leafcutter-embedded'));
|
||||
|
||||
return <InternalLayout embedded={embedded}>{children}</InternalLayout>;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
import { RawDataViewer } from "app/_components/RawDataViewer";
|
||||
import { VisualizationDetail } from "app/_components/VisualizationDetail";
|
||||
|
||||
interface PreviewProps {
|
||||
visualization: any;
|
||||
visualizationType: string;
|
||||
data: any[];
|
||||
}
|
||||
|
||||
export const Preview: FC<PreviewProps> = ({
|
||||
visualization,
|
||||
visualizationType,
|
||||
data,
|
||||
}) =>
|
||||
visualizationType === "rawData" ? (
|
||||
<RawDataViewer rows={data} height={750} />
|
||||
) : (
|
||||
<VisualizationDetail {...visualization} />
|
||||
);
|
||||
|
|
@ -1,29 +1,12 @@
|
|||
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 { Preview } from "./_components/Preview";
|
||||
// import { createVisualization } from "lib/opensearch";
|
||||
|
||||
interface PreviewProps {
|
||||
visualization: any;
|
||||
visualizationType: string;
|
||||
data: any[];
|
||||
export default function Page() {
|
||||
return <Preview visualization={undefined} visualizationType={""} data={[]}/>;
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -1,11 +1,13 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { NextPage } from "next";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Grid, CircularProgress } from "@mui/material";
|
||||
import Iframe from "react-iframe";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
|
||||
const Setup: NextPage = () => {
|
||||
export const Setup: FC = () => {
|
||||
const {
|
||||
colors: { leafcutterElectricBlue },
|
||||
} = useAppContext();
|
||||
6
apps/leafcutter/app/(main)/setup/page.tsx
Normal file
6
apps/leafcutter/app/(main)/setup/page.tsx
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { Setup } from './_components/Setup';
|
||||
|
||||
export default function Page() {
|
||||
return <Setup />;
|
||||
}
|
||||
|
||||
|
|
@ -1,20 +1,17 @@
|
|||
import { NextPage, GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||
import Head from "next/head";
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
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";
|
||||
import { PageHeader } from "app/_components/PageHeader";
|
||||
import { VisualizationCard } from "app/_components/VisualizationCard";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
|
||||
type TrendsProps = {
|
||||
visualizations: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const Trends: NextPage<TrendsProps> = ({ visualizations, embedded }) => {
|
||||
export const Trends: FC<TrendsProps> = ({ visualizations }) => {
|
||||
const t = useTranslate();
|
||||
const {
|
||||
colors: { cdrLinkOrange },
|
||||
|
|
@ -22,10 +19,7 @@ const Trends: NextPage<TrendsProps> = ({ visualizations, embedded }) => {
|
|||
} = useAppContext();
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<>
|
||||
<PageHeader backgroundColor={cdrLinkOrange}>
|
||||
<Grid
|
||||
container
|
||||
|
|
@ -73,16 +67,6 @@ const Trends: NextPage<TrendsProps> = ({ visualizations, embedded }) => {
|
|||
/>
|
||||
))}
|
||||
</Grid>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Trends;
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (
|
||||
context: GetServerSidePropsContext
|
||||
) => {
|
||||
const visualizations = await getTrends(25);
|
||||
|
||||
return { props: { visualizations, embedded: getEmbedded(context) } };
|
||||
};
|
||||
8
apps/leafcutter/app/(main)/trends/page.tsx
Normal file
8
apps/leafcutter/app/(main)/trends/page.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { getTrends } from "app/_lib/opensearch";
|
||||
import { Trends } from "./_components/Trends";
|
||||
|
||||
export default async function Page() {
|
||||
const visualizations = await getTrends(25);
|
||||
|
||||
return <Trends visualizations={visualizations} />;
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
import { Client } from "@opensearch-project/opensearch";
|
||||
import { VisualizationDetail } from "app/_components/VisualizationDetail";
|
||||
|
||||
const getVisualization = async (visualizationID: string) => {
|
||||
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(
|
||||
(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 visualization;
|
||||
};
|
||||
|
||||
type PageProps = {
|
||||
params: {
|
||||
visualizationID: string;
|
||||
};
|
||||
};
|
||||
|
||||
export default async function Page({ params: { visualizationID } }: PageProps) {
|
||||
const visualization = await getVisualization(visualizationID);
|
||||
|
||||
return <VisualizationDetail {...visualization} editing={false} />;
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import { signOut } from "next-auth/react";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
FC,
|
||||
createContext,
|
||||
|
|
@ -6,7 +8,7 @@ import {
|
|||
useState,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import { colors, typography } from "styles/theme";
|
||||
import { colors, typography } from "app/_styles/theme";
|
||||
|
||||
const basePath = process.env.GITLAB_CI
|
||||
? "/link/link-stack/apps/leafcutter"
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Link from "next/link";
|
||||
import { Button as MUIButton } from "@mui/material";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Container, Grid, Box, Button } from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { Dialog, Box, Grid, Checkbox, IconButton } from "@mui/material";
|
||||
import { Close as CloseIcon } from "@mui/icons-material";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
|
||||
|
|
@ -60,9 +62,11 @@ export const GettingStartedDialog: FC = () => {
|
|||
typography: { h4 },
|
||||
} = useAppContext();
|
||||
const t = useTranslate();
|
||||
const [completedItems, setCompletedItems] = useState([] as any[]);
|
||||
const router = useRouter();
|
||||
const open = router.query.tooltip?.toString() === "checklist";
|
||||
const [completedItems, setCompletedItems] = useState([] as any[]);
|
||||
const searchParams = useSearchParams();
|
||||
const pathname = usePathname();
|
||||
const open = searchParams.get("tooltip")?.toString() === "checklist";
|
||||
const toggleCompletedItem = (item: any) => {
|
||||
if (completedItems.includes(item)) {
|
||||
setCompletedItems(completedItems.filter((i) => i !== item));
|
||||
|
|
@ -90,7 +94,7 @@ export const GettingStartedDialog: FC = () => {
|
|||
<Box sx={{ ...h4, mb: 3 }}>{t("getStartedChecklist")}</Box>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<IconButton onClick={() => router.push(router.pathname)}>
|
||||
<IconButton onClick={() => router.push(pathname)}>
|
||||
<CloseIcon sx={{ color: almostBlack, fontSize: "18px" }} />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { Button } from "@mui/material";
|
||||
import { QuestionMark as QuestionMarkIcon } from "@mui/icons-material";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
|
||||
export const HelpButton: FC = () => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [helpActive, setHelpActive] = useState(false);
|
||||
const {
|
||||
colors: { leafcutterElectricBlue },
|
||||
} = useAppContext();
|
||||
const onClick = () => {
|
||||
if (helpActive) {
|
||||
router.push(router.pathname);
|
||||
router.push(pathname);
|
||||
} else {
|
||||
router.push("/?tooltip=welcome");
|
||||
}
|
||||
|
|
@ -1,31 +1,24 @@
|
|||
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";
|
||||
"use client";
|
||||
|
||||
import { useEffect, FC } from "react";
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
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";
|
||||
import { Welcome } from "app/_components/Welcome";
|
||||
import { WelcomeDialog } from "app/_components/WelcomeDialog";
|
||||
import { VisualizationCard } from "app/_components/VisualizationCard";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
|
||||
type MyVisualizationsProps = {
|
||||
type HomeProps = {
|
||||
visualizations: any;
|
||||
embedded: boolean;
|
||||
};
|
||||
|
||||
const MyVisualizations: NextPage<MyVisualizationsProps> = ({
|
||||
visualizations,
|
||||
embedded,
|
||||
}) => {
|
||||
export const Home: FC<HomeProps> = ({ visualizations }) => {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const cookieName = "homeIntroComplete";
|
||||
const [cookies, setCookie] = useCookies([cookieName]);
|
||||
const t = useTranslate();
|
||||
|
|
@ -38,15 +31,12 @@ const MyVisualizations: NextPage<MyVisualizationsProps> = ({
|
|||
useEffect(() => {
|
||||
if (homeIntroComplete === 0) {
|
||||
setCookie(cookieName, `${1}`, { path: "/" });
|
||||
router.push(`${router.pathname}?tooltip=welcome`);
|
||||
router.push(`${pathname}?tooltip=welcome`);
|
||||
}
|
||||
}, [homeIntroComplete, router, setCookie]);
|
||||
|
||||
return (
|
||||
<Layout embedded={embedded}>
|
||||
<Head>
|
||||
<title>Digital Threat Dashboard – Leafcutter</title>
|
||||
</Head>
|
||||
<>
|
||||
<Welcome />
|
||||
<Grid
|
||||
container
|
||||
|
|
@ -105,19 +95,6 @@ const MyVisualizations: NextPage<MyVisualizationsProps> = ({
|
|||
))}
|
||||
</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,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, PropsWithChildren } from "react";
|
||||
import getConfig from "next/config";
|
||||
import { Grid, Container } from "@mui/material";
|
||||
|
|
@ -13,7 +15,7 @@ type LayoutProps = PropsWithChildren<{
|
|||
embedded?: boolean;
|
||||
}>;
|
||||
|
||||
export const Layout: FC<LayoutProps> = ({
|
||||
export const InternalLayout: FC<LayoutProps> = ({
|
||||
embedded = false,
|
||||
children,
|
||||
}: any) => {
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import { useRouter } from "next/router";
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { IconButton, Menu, MenuItem, Box } from "@mui/material";
|
||||
import { KeyboardArrowDown as KeyboardArrowDownIcon } from "@mui/icons-material";
|
||||
import {
|
||||
|
|
@ -15,6 +17,7 @@ export const LanguageSelect = () => {
|
|||
} = useAppContext();
|
||||
const router = useRouter();
|
||||
const locales: any = { en: "English", fr: "Français" };
|
||||
const locale = "en";
|
||||
const popupState = usePopupState({ variant: "popover", popupId: "language" });
|
||||
|
||||
return (
|
||||
|
|
@ -36,7 +39,7 @@ export const LanguageSelect = () => {
|
|||
},
|
||||
}}
|
||||
>
|
||||
{locales[router.locale as any] ?? locales.en}
|
||||
{locales[locale as any] ?? locales.en}
|
||||
<KeyboardArrowDownIcon />
|
||||
</IconButton>
|
||||
<Menu {...bindMenu(popupState)}>
|
||||
|
|
@ -44,7 +47,7 @@ export const LanguageSelect = () => {
|
|||
<MenuItem
|
||||
key={locale}
|
||||
onClick={() => {
|
||||
router.push(router.route, router.route, { locale });
|
||||
// router.push(router.route, router.route, { locale });
|
||||
popupState.close();
|
||||
}}
|
||||
>
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
import { RawDataViewer } from "./RawDataViewer";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { Card, Grid } from "@mui/material";
|
||||
import {
|
||||
49
apps/leafcutter/app/_components/MultiProvider.tsx
Normal file
49
apps/leafcutter/app/_components/MultiProvider.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
"use client";
|
||||
|
||||
/* eslint-disable react/jsx-props-no-spreading */
|
||||
import { FC, PropsWithChildren } from "react";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
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 "app/_components/AppProvider";
|
||||
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
|
||||
import en from "app/_locales/en.json";
|
||||
import fr from "app/_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 "app/_styles/global.css";
|
||||
import { LicenseInfo } from "@mui/x-date-pickers-pro";
|
||||
|
||||
LicenseInfo.setLicenseKey(
|
||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI="
|
||||
);
|
||||
|
||||
const messages: any = { en, fr };
|
||||
|
||||
export const MultiProvider: FC<PropsWithChildren> = ({ children }: any) => {
|
||||
// const { locale = "en" } = useRouter();
|
||||
const locale = "en";
|
||||
|
||||
return (
|
||||
<NextAppDirEmotionCacheProvider options={{ key: "css" }}>
|
||||
<SessionProvider>
|
||||
<CookiesProvider>
|
||||
<CssBaseline />
|
||||
<AppProvider>
|
||||
<LocalizationProvider dateAdapter={AdapterDateFns}>
|
||||
<I18n locale={locale} messages={messages[locale]}>
|
||||
{children}
|
||||
</I18n>
|
||||
</LocalizationProvider>
|
||||
</AppProvider>
|
||||
</CookiesProvider>
|
||||
</SessionProvider>
|
||||
</NextAppDirEmotionCacheProvider>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Iframe from "react-iframe";
|
||||
import { Box } from "@mui/material";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
/* eslint-disable react/require-default-props */
|
||||
import { FC, PropsWithChildren } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
|
|
@ -15,14 +17,14 @@ import {
|
|||
Group as GroupIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import taxonomy from "config/taxonomy.json";
|
||||
import taxonomy from "app/_config/taxonomy.json";
|
||||
import { QueryBuilderSection } from "./QueryBuilderSection";
|
||||
import { QueryListSelector } from "./QueryListSelector";
|
||||
import { QueryDateRangeSelector } from "./QueryDateRangeSelector";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
import { Tooltip } from "./Tooltip";
|
||||
|
||||
interface QueryBuilderProps { }
|
||||
interface QueryBuilderProps {}
|
||||
|
||||
export const QueryBuilder: FC<QueryBuilderProps> = () => {
|
||||
const t = useTranslate();
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, PropsWithChildren, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { Box, Grid, TextField, Select, MenuItem } from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers-pro";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { Box, Grid, Tooltip } from "@mui/material";
|
||||
import { DataGridPro, GridColDef } from "@mui/x-data-grid-pro";
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import taxonomy from "config/taxonomy.json";
|
||||
import { colors } from "styles/theme";
|
||||
import taxonomy from "app/_config/taxonomy.json";
|
||||
import { colors } from "app/_styles/theme";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
|
||||
export const QueryText: FC = () => {
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import {
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import { DataGridPro } from "@mui/x-data-grid-pro";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import DashboardMenuIcon from "images/dashboard-menu.png";
|
||||
import AboutMenuIcon from "images/about-menu.png";
|
||||
|
|
@ -16,10 +18,10 @@ import {
|
|||
Drawer,
|
||||
} from "@mui/material";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { Tooltip } from "components/Tooltip";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import { Tooltip } from "app/_components/Tooltip";
|
||||
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
|
||||
|
||||
const MenuItem = ({
|
||||
|
|
@ -99,8 +101,8 @@ interface SidebarProps {
|
|||
|
||||
export const Sidebar: FC<SidebarProps> = ({ open }) => {
|
||||
const t = useTranslate();
|
||||
const router = useRouter();
|
||||
const section = router.pathname.split("/")[1];
|
||||
const pathname = usePathname();
|
||||
const section = pathname.split("/")[1];
|
||||
const {
|
||||
colors: { white }, // leafcutterElectricBlue, leafcutterLightBlue,
|
||||
} = useAppContext();
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
"use client";
|
||||
|
||||
/* eslint-disable react/require-default-props */
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRouter, usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
Box,
|
||||
Grid,
|
||||
|
|
@ -38,7 +40,9 @@ export const Tooltip: FC<TooltipProps> = ({
|
|||
colors: { white, leafcutterElectricBlue, almostBlack },
|
||||
} = useAppContext();
|
||||
const router = useRouter();
|
||||
const activeTooltip = router.query.tooltip?.toString();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const activeTooltip = searchParams.get('tooltip')?.toString();
|
||||
const open = activeTooltip === tooltipID;
|
||||
const showNavigation = true;
|
||||
|
||||
|
|
@ -49,7 +53,7 @@ export const Tooltip: FC<TooltipProps> = ({
|
|||
<Grid container direction="column">
|
||||
<Grid item container direction="row-reverse">
|
||||
<Grid item>
|
||||
<IconButton onClick={() => router.push(router.pathname)}>
|
||||
<IconButton onClick={() => router.push(pathname)}>
|
||||
<CloseIcon
|
||||
sx={{
|
||||
color: leafcutterElectricBlue,
|
||||
|
|
@ -123,7 +127,7 @@ export const Tooltip: FC<TooltipProps> = ({
|
|||
color: leafcutterElectricBlue,
|
||||
textTransform: "none",
|
||||
}}
|
||||
onClick={() => router.push(router.pathname)}
|
||||
onClick={() => router.push(pathname)}
|
||||
>
|
||||
{t("done")}
|
||||
</Button>
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/legacy/image";
|
||||
import { AppBar, Grid, Box } from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import LeafcutterLogo from "images/leafcutter-logo.png";
|
||||
import { AccountButton } from "components/AccountButton";
|
||||
import { HelpButton } from "components/HelpButton";
|
||||
import { Tooltip } from "components/Tooltip";
|
||||
import { AccountButton } from "app/_components/AccountButton";
|
||||
import { HelpButton } from "app/_components/HelpButton";
|
||||
import { Tooltip } from "app/_components/Tooltip";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
// import { LanguageSelect } from "./LanguageSelect";
|
||||
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState, useEffect } from "react";
|
||||
import {
|
||||
Box,
|
||||
|
|
@ -23,11 +25,11 @@ import {
|
|||
RemoveCircle as RemoveCircleIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { QueryBuilder } from "components/QueryBuilder";
|
||||
import { QueryText } from "components/QueryText";
|
||||
import { LiveDataViewer } from "components/LiveDataViewer";
|
||||
import { Tooltip } from "components/Tooltip";
|
||||
import visualizationMap from "config/visualizationMap.json";
|
||||
import { QueryBuilder } from "app/_components/QueryBuilder";
|
||||
import { QueryText } from "app/_components/QueryText";
|
||||
import { LiveDataViewer } from "app/_components/LiveDataViewer";
|
||||
import { Tooltip } from "app/_components/Tooltip";
|
||||
import visualizationMap from "app/_config/visualizationMap.json";
|
||||
import { VisualizationSelectCard } from "./VisualizationSelectCard";
|
||||
import { MetricSelectCard } from "./MetricSelectCard";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
import { Grid, Card, Box } from "@mui/material";
|
||||
import Iframe from "react-iframe";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { VisualizationDetailDialog } from "components/VisualizationDetailDialog";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import { VisualizationDetailDialog } from "app/_components/VisualizationDetailDialog";
|
||||
|
||||
interface VisualizationCardProps {
|
||||
id: string;
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
// import Link from "next/link";
|
||||
import { Box } from "@mui/material";
|
||||
import Iframe from "react-iframe";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
|
||||
interface VisualizationDetailProps {
|
||||
id: string;
|
||||
|
|
@ -24,7 +26,7 @@ export const VisualizationDetail: FC<VisualizationDetailProps> = ({
|
|||
typography: { h4, p },
|
||||
} = useAppContext();
|
||||
const finalURL = `${process.env.NEXT_PUBLIC_NEXTAUTH_URL}${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`;
|
||||
|
||||
console.log({ finalURL });
|
||||
return (
|
||||
<Box key={id}>
|
||||
{!editing ? (
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC, useState } from "react";
|
||||
// import Link from "next/link";
|
||||
import {
|
||||
|
|
@ -9,7 +11,7 @@ import {
|
|||
TextField,
|
||||
} from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { useAppContext } from "components/AppProvider";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import { VisualizationDetail } from "./VisualizationDetail";
|
||||
|
||||
interface VisualizationDetailDialogProps {
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import { Card, Grid } from "@mui/material";
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
|
|
@ -6,9 +8,12 @@ import { useAppContext } from "./AppProvider";
|
|||
export const Welcome = () => {
|
||||
const t = useTranslate();
|
||||
const { data: session } = useSession();
|
||||
/*
|
||||
const {
|
||||
user: { name },
|
||||
} = session as any;
|
||||
*/
|
||||
const name = "Test User";
|
||||
const {
|
||||
colors: { white, leafcutterElectricBlue },
|
||||
typography: { h1, h4, p },
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { Box, Grid, Dialog, Button } from "@mui/material";
|
||||
import { useRouter } from "next/router";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
// import { useSession } from "next-auth/react";
|
||||
// import { useTranslate } from "react-polyglot";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
|
|
@ -7,13 +9,14 @@ import { useAppContext } from "./AppProvider";
|
|||
export const WelcomeDialog = () => {
|
||||
// const t = useTranslate();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
// const { data: session } = useSession();
|
||||
// const { user } = session;
|
||||
const {
|
||||
colors: { white, leafcutterElectricBlue },
|
||||
typography: { h1, h6, p },
|
||||
} = useAppContext();
|
||||
const activeTooltip = router.query.tooltip?.toString();
|
||||
const activeTooltip = searchParams.get('tooltip')?.toString();
|
||||
const open = activeTooltip === "welcome";
|
||||
|
||||
return (
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import NextAuth from "next-auth";
|
||||
import type { NextAuthOptions } from "next-auth";
|
||||
import Google from "next-auth/providers/google";
|
||||
import Apple from "next-auth/providers/apple";
|
||||
|
||||
export default NextAuth({
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||
|
|
@ -14,4 +14,4 @@ export default NextAuth({
|
|||
}),
|
||||
],
|
||||
secret: process.env.NEXTAUTH_SECRET,
|
||||
});
|
||||
};
|
||||
|
|
@ -8,10 +8,16 @@ const globalIndex = ".kibana_1";
|
|||
const dataIndexName = "sample_tagged_tickets";
|
||||
const userMetadataIndexName = "user_metadata";
|
||||
|
||||
const baseURL = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||
// const baseURL = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;
|
||||
|
||||
const baseURL = `https://localhost:9200`;
|
||||
|
||||
const createClient = () => new Client({
|
||||
node: baseURL,
|
||||
auth: {
|
||||
username: process.env.OPENSEARCH_USERNAME!,
|
||||
password: process.env.OPENSEARCH_PASSWORD!,
|
||||
},
|
||||
ssl: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
|
|
@ -532,6 +538,7 @@ export const getTrends = async (limit: number) => {
|
|||
|
||||
export const getTemplates = async (limit: number) => {
|
||||
const client = createClient();
|
||||
|
||||
const query = {
|
||||
query: {
|
||||
bool: {
|
||||
|
|
@ -546,11 +553,14 @@ export const getTemplates = async (limit: number) => {
|
|||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const rawResponse = await client.search({
|
||||
index: globalIndex,
|
||||
size: limit,
|
||||
body: query,
|
||||
});
|
||||
|
||||
const response = rawResponse.body;
|
||||
const {
|
||||
hits: { hits },
|
||||
6
apps/leafcutter/app/api/auth/[...nextauth]/route.ts
Normal file
6
apps/leafcutter/app/api/auth/[...nextauth]/route.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import NextAuth from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
22
apps/leafcutter/app/api/searches/create/route.ts
Normal file
22
apps/leafcutter/app/api/searches/create/route.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { getUserMetadata, saveUserMetadata } from "app/_lib/opensearch";
|
||||
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
const { user: { email } }: any = session;
|
||||
const { name, query } = await req.json();
|
||||
const result = await getUserMetadata(email);
|
||||
const { savedSearches } = result;
|
||||
await saveUserMetadata(email, {
|
||||
savedSearches: [...savedSearches, { name, query }]
|
||||
});
|
||||
const { savedSearches: updatedSavedSearches } = await getUserMetadata(email);
|
||||
|
||||
return NextResponse.json(updatedSavedSearches);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
19
apps/leafcutter/app/api/searches/delete/route.ts
Normal file
19
apps/leafcutter/app/api/searches/delete/route.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { getUserMetadata, saveUserMetadata } from "app/_lib/opensearch";
|
||||
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
const { user: { email } }: any = session;
|
||||
const { name } = await req.json();
|
||||
const { savedSearches } = await getUserMetadata(email);
|
||||
const updatedSavedSearches = savedSearches.filter((search: any) => search.name !== name);
|
||||
const result = await saveUserMetadata(email, { savedSearches: updatedSavedSearches });
|
||||
|
||||
return NextResponse.json({ result });
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
12
apps/leafcutter/app/api/searches/list/route.ts
Normal file
12
apps/leafcutter/app/api/searches/list/route.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { getUserMetadata } from "app/_lib/opensearch";
|
||||
|
||||
export const GET = async () => {
|
||||
const session = await getServerSession(authOptions);
|
||||
const { user: { email } }: any = session;
|
||||
const { savedSearches } = await getUserMetadata(email);
|
||||
|
||||
return NextResponse.json(savedSearches);
|
||||
};
|
||||
9
apps/leafcutter/app/api/trends/recent/_route.ts
Normal file
9
apps/leafcutter/app/api/trends/recent/_route.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { getTrends } from "app/_lib/opensearch";
|
||||
|
||||
export const GET = async () => {
|
||||
const results = await getTrends(5);
|
||||
|
||||
NextResponse.json(results);
|
||||
};
|
||||
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
/* eslint-disable no-restricted-syntax */
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { Client } from "@opensearch-project/opensearch";
|
||||
import { v4 as uuid } from "uuid";
|
||||
import taxonomy from "config/taxonomy.json";
|
||||
import unRegions from "config/unRegions.json";
|
||||
import taxonomy from "app/_config/taxonomy.json";
|
||||
import unRegions from "app/_config/unRegions.json";
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const { headers: { authorization }, body: { tickets } } = req;
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const { tickets } = await req.json();
|
||||
const authorization = req.headers.get("authorization");
|
||||
const baseURL = `https://${process.env.OPENSEARCH_URL}`;
|
||||
const client = new Client({
|
||||
node: baseURL,
|
||||
|
|
@ -48,7 +49,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||
}
|
||||
|
||||
const results = { succeeded, failed };
|
||||
return res.json(results);
|
||||
|
||||
return NextResponse.json(results);
|
||||
};
|
||||
|
||||
export default handler;
|
||||
20
apps/leafcutter/app/api/visualizations/create/route.ts
Normal file
20
apps/leafcutter/app/api/visualizations/create/route.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { createUserVisualization } from "app/_lib/opensearch";
|
||||
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
const { user: { email } }: any = session;
|
||||
const { visualizationID, title, description, query } = await req.json();
|
||||
const id = await createUserVisualization({
|
||||
email,
|
||||
visualizationID,
|
||||
title,
|
||||
description,
|
||||
query
|
||||
});
|
||||
|
||||
return NextResponse.json({ id });
|
||||
};
|
||||
|
||||
15
apps/leafcutter/app/api/visualizations/delete/route.ts
Normal file
15
apps/leafcutter/app/api/visualizations/delete/route.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { deleteUserVisualization } from "app/_lib/opensearch";
|
||||
|
||||
export const POST = async (req: NextRequest, res: NextResponse) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
const { user: { email } }: any = session;
|
||||
const { id } = await req.json();
|
||||
await deleteUserVisualization(email as string, id);
|
||||
|
||||
return NextResponse.json({ id });
|
||||
};
|
||||
|
||||
|
||||
12
apps/leafcutter/app/api/visualizations/query/route.ts
Normal file
12
apps/leafcutter/app/api/visualizations/query/route.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { performQuery } from "app/_lib/opensearch";
|
||||
|
||||
export const GET = async (req: NextRequest) => {
|
||||
const searchQuery = req.nextUrl.searchParams.get("searchQuery");
|
||||
const rawQuery = await JSON.parse(decodeURI(searchQuery as string));
|
||||
const results = await performQuery(rawQuery, 1000);
|
||||
|
||||
return NextResponse.json(results);
|
||||
};
|
||||
|
||||
|
||||
21
apps/leafcutter/app/api/visualizations/update/route.ts
Normal file
21
apps/leafcutter/app/api/visualizations/update/route.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { updateUserVisualization } from "app/_lib/opensearch";
|
||||
|
||||
export const POST = async (req: NextRequest) => {
|
||||
const session = await getServerSession(authOptions);
|
||||
const { user: { email } }: any = session;
|
||||
const { id, title, description, query } = await req.json();
|
||||
await updateUserVisualization({
|
||||
email,
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
query
|
||||
});
|
||||
|
||||
return NextResponse.json({ id });
|
||||
};
|
||||
|
||||
|
||||
32
apps/leafcutter/app/layout.tsx
Normal file
32
apps/leafcutter/app/layout.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { ReactNode } from "react";
|
||||
import { Metadata } from "next";
|
||||
import "app/_styles/global.css";
|
||||
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 getConfig from "next/config";
|
||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||
import { MultiProvider } from "app/_components/MultiProvider";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Leafcutter",
|
||||
};
|
||||
|
||||
type LayoutProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
// const { publicRuntimeConfig } = getConfig();
|
||||
// LicenseInfo.setLicenseKey(publicRuntimeConfig.muiLicenseKey);
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<MultiProvider>{children}</MultiProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
14
apps/leafcutter/app/page.tsx
Normal file
14
apps/leafcutter/app/page.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { getUserVisualizations } from "app/_lib/opensearch";
|
||||
import { Home } from "app/_components/Home";
|
||||
|
||||
export default async function Page() {
|
||||
const session = await getServerSession(authOptions);
|
||||
const {
|
||||
user: { email },
|
||||
}: any = session;
|
||||
const visualizations = await getUserVisualizations(email ?? "none", 20);
|
||||
|
||||
return <Home visualizations={visualizations} />;
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
import createCache from "@emotion/cache";
|
||||
|
||||
export default function createEmotionCache() {
|
||||
return createCache({ key: "css" });
|
||||
}
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
import { GetServerSidePropsContext } from "next";
|
||||
|
||||
export const getEmbedded = (context: GetServerSidePropsContext) =>
|
||||
context.req.headers["x-leafcutter-embedded"] === "true";
|
||||
|
|
@ -1,8 +1,57 @@
|
|||
import { withAuth } from "next-auth/middleware";
|
||||
import { NextResponse } from "next/server";
|
||||
import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
|
||||
import getConfig from "next/config";
|
||||
|
||||
const rewriteURL = (request: NextRequestWithAuth, originBaseURL: string, destinationBaseURL: string, headers: any = {}) => {
|
||||
if (request.nextUrl.protocol.startsWith('ws')) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
if (request.nextUrl.pathname.includes('/_next/static/development/')) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const destinationURL = request.url.replace(originBaseURL, destinationBaseURL);
|
||||
console.log(`Rewriting ${request.url} to ${destinationURL}`);
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
// @ts-ignore
|
||||
requestHeaders.set(key, value);
|
||||
}
|
||||
requestHeaders.delete('connection');
|
||||
|
||||
// console.log({ finalHeaders: requestHeaders });
|
||||
|
||||
return NextResponse.rewrite(new URL(destinationURL), { request: { headers: requestHeaders } });
|
||||
};
|
||||
|
||||
const checkRewrites = async (request: NextRequestWithAuth) => {
|
||||
console.log({ currentURL: request.nextUrl.href });
|
||||
|
||||
const leafcutterBaseURL = process.env.LEAFCUTTER_URL ?? "http://localhost:3000";
|
||||
const opensearchDashboardsURL = process.env.OPENSEARCH_URL ?? "http://localhost:5602";
|
||||
|
||||
if (request.nextUrl.pathname.startsWith('/proxy/opensearch')) {
|
||||
console.log('proxying to zammad');
|
||||
const { token } = request.nextauth;
|
||||
const auth = `${token?.email?.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
|
||||
const buff = Buffer.from(auth);
|
||||
const base64data = buff.toString("base64");
|
||||
const headers = {
|
||||
'X-Proxy-User': token?.email?.toLowerCase(),
|
||||
"X-Proxy-Roles": "leafcutter_user",
|
||||
"Authorization": `Basic ${base64data}`
|
||||
};
|
||||
|
||||
console.log({ headers });
|
||||
|
||||
return rewriteURL(request, `${leafcutterBaseURL}/proxy/opensearch`, opensearchDashboardsURL, headers);
|
||||
}
|
||||
};
|
||||
|
||||
export default withAuth(
|
||||
() => { },
|
||||
checkRewrites,
|
||||
{
|
||||
pages: {
|
||||
signIn: `/login`,
|
||||
|
|
@ -14,16 +63,6 @@ export default withAuth(
|
|||
headers,
|
||||
} = req;
|
||||
|
||||
return true;
|
||||
/*
|
||||
const {
|
||||
publicRuntimeConfig: { embedded },
|
||||
} = getConfig();
|
||||
|
||||
if (embedded) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// check login page
|
||||
const parsedURL = new URL(url);
|
||||
if (parsedURL.pathname.startsWith('/login')) {
|
||||
|
|
@ -39,7 +78,6 @@ export default withAuth(
|
|||
}
|
||||
|
||||
return false;
|
||||
*/
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
const ContentSecurityPolicy = `
|
||||
default-src 'self';
|
||||
script-src 'self';
|
||||
child-src example.com;
|
||||
style-src 'self' example.com;
|
||||
font-src 'self';
|
||||
`;
|
||||
|
||||
module.exports = {
|
||||
publicRuntimeConfig: {
|
||||
embedded: true
|
||||
},
|
||||
},/*
|
||||
basePath: "/proxy/leafcutter",
|
||||
assetPrefix: "/proxy/leafcutter",
|
||||
i18n: {
|
||||
locales: ["en", "fr"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
|
||||
*/
|
||||
/* rewrites: async () => ({
|
||||
fallback: [
|
||||
{
|
||||
|
|
@ -17,4 +25,31 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
}) */
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: [
|
||||
/*
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
|
||||
},
|
||||
*/
|
||||
{
|
||||
key: 'Strict-Transport-Security',
|
||||
value: 'max-age=63072000; includeSubDomains; preload'
|
||||
},
|
||||
{
|
||||
key: 'X-XSS-Protection',
|
||||
value: '1; mode=block'
|
||||
},
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN'
|
||||
}
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"version": "0.2.0",
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3001",
|
||||
"login": "aws sso login --profile cdr-leafcutter-dashboard-production",
|
||||
"login": "aws sso login --sso-session cdr",
|
||||
"kubeconfig": "aws eks update-kubeconfig --name cdr-leafcutter-dashboard-cluster --profile cdr-leafcutter-dashboard-production",
|
||||
"fwd:opensearch": "kubectl port-forward opensearch-cluster-master-0 9200:9200 --namespace leafcutter",
|
||||
"fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-vgmtf 5602:5601 --namespace leafcutter",
|
||||
|
|
@ -17,19 +17,19 @@
|
|||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@fontsource/playfair-display": "^5.0.3",
|
||||
"@fontsource/poppins": "^5.0.3",
|
||||
"@fontsource/roboto": "^5.0.3",
|
||||
"@fontsource/playfair-display": "^5.0.5",
|
||||
"@fontsource/poppins": "^5.0.5",
|
||||
"@fontsource/roboto": "^5.0.5",
|
||||
"@mui/icons-material": "^5",
|
||||
"@mui/lab": "^5.0.0-alpha.134",
|
||||
"@mui/lab": "^5.0.0-alpha.136",
|
||||
"@mui/material": "^5",
|
||||
"@mui/x-data-grid-pro": "^6.8.0",
|
||||
"@mui/x-date-pickers-pro": "^6.8.0",
|
||||
"@opensearch-project/opensearch": "^2.0.0",
|
||||
"@mui/x-data-grid-pro": "^6.10.0",
|
||||
"@mui/x-date-pickers-pro": "^6.10.0",
|
||||
"@opensearch-project/opensearch": "^2.3.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"material-ui-popup-state": "^5.0.9",
|
||||
"next": "13.4.6",
|
||||
"next": "13.4.10",
|
||||
"next-auth": "^4.22.1",
|
||||
"next-http-proxy-middleware": "^1.2.5",
|
||||
"nodemailer": "^6.9.3",
|
||||
|
|
@ -40,24 +40,25 @@
|
|||
"react-iframe": "^1.8.5",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-polyglot": "^0.7.2",
|
||||
"sharp": "^0.32.1",
|
||||
"swr": "^2.1.5",
|
||||
"sharp": "^0.32.3",
|
||||
"swr": "^2.2.0",
|
||||
"tss-react": "^4.8.8",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@types/react": "18.2.13",
|
||||
"@types/node": "^20.3.1",
|
||||
"@babel/core": "^7.22.9",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"babel-loader": "^9.1.2",
|
||||
"eslint": "^8.43.0",
|
||||
"babel-loader": "^9.1.3",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-next": "^13.4.6",
|
||||
"eslint-config-next": "^13.4.10",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"typescript": "5.1.3"
|
||||
"typescript": "5.1.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,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,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,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) } };
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue