This commit is contained in:
Darren Clarke 2024-03-20 17:51:21 +01:00
parent b8c6e893ff
commit b09cc82544
167 changed files with 2196 additions and 1302 deletions

View file

@ -9,7 +9,7 @@ import { useTranslate } from "react-polyglot";
import { LanguageSelect } from "app/_components/LanguageSelect"; import { LanguageSelect } from "app/_components/LanguageSelect";
import LeafcutterLogoLarge from "images/leafcutter-logo-large.png"; import LeafcutterLogoLarge from "images/leafcutter-logo-large.png";
import { signIn } from "next-auth/react"; import { signIn } from "next-auth/react";
import { useAppContext } from "app/_components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui";
type LoginProps = { type LoginProps = {
session: any; session: any;
@ -20,7 +20,7 @@ export const Login: FC<LoginProps> = ({ session }) => {
const { const {
colors: { leafcutterElectricBlue, lightGray }, colors: { leafcutterElectricBlue, lightGray },
typography: { h1, h4 }, typography: { h1, h4 },
} = useAppContext(); } = useLeafcutterContext();
const buttonStyles = { const buttonStyles = {
backgroundColor: lightGray, backgroundColor: lightGray,
borderRadius: 500, borderRadius: 500,

View file

@ -1,4 +1,4 @@
import { About } from "leafcutter-common"; import { About } from "leafcutter-ui";
export default function Page() { export default function Page() {
return <About />; return <About />;

View file

@ -1,5 +1,5 @@
import { getTemplates } from "app/_lib/opensearch"; import { getTemplates } from "app/_lib/opensearch";
import { Create } from "leafcutter-common"; import { Create } from "leafcutter-ui";
export default async function Page() { export default async function Page() {
const templates = await getTemplates(100); const templates = await getTemplates(100);

View file

@ -1,4 +1,4 @@
import { FAQ } from "leafcutter-common"; import { FAQ } from "leafcutter-ui";
export default function Page() { export default function Page() {
return <FAQ />; return <FAQ />;

View file

@ -1,7 +1,5 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import "app/_styles/global.css"; import "app/_styles/global.css";
// import getConfig from "next/config";
// import { LicenseInfo } from "@mui/x-data-grid-pro";
import { InternalLayout } from "../_components/InternalLayout"; import { InternalLayout } from "../_components/InternalLayout";
type LayoutProps = { type LayoutProps = {

View file

@ -1,7 +1,7 @@
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { authOptions } from "app/_lib/auth"; import { authOptions } from "app/_lib/auth";
import { getUserVisualizations } from "app/_lib/opensearch"; import { getUserVisualizations } from "app/_lib/opensearch";
import { Home } from "leafcutter-common"; import { Home } from "leafcutter-ui";
export default async function Page() { export default async function Page() {
const session = await getServerSession(authOptions); const session = await getServerSession(authOptions);

View file

@ -1,6 +1,6 @@
/* eslint-disable no-underscore-dangle */ /* eslint-disable no-underscore-dangle */
// import { Client } from "@opensearch-project/opensearch"; // import { Client } from "@opensearch-project/opensearch";
import { Preview } from "leafcutter-common"; import { Preview } from "leafcutter-ui";
// import { createVisualization } from "lib/opensearch"; // import { createVisualization } from "lib/opensearch";
export default function Page() { export default function Page() {

View file

@ -5,12 +5,12 @@ import { useLayoutEffect } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { Grid, CircularProgress } from "@mui/material"; import { Grid, CircularProgress } from "@mui/material";
import Iframe from "react-iframe"; import Iframe from "react-iframe";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
export const Setup: FC = () => { export const Setup: FC = () => {
const { const {
colors: { leafcutterElectricBlue }, colors: { leafcutterElectricBlue },
} = useAppContext(); } = useLeafcutterContext();
const router = useRouter(); const router = useRouter();
useLayoutEffect(() => { useLayoutEffect(() => {
setTimeout(() => router.push("/"), 4000); setTimeout(() => router.push("/"), 4000);

View file

@ -1,5 +1,5 @@
import { getTrends } from "app/_lib/opensearch"; import { getTrends } from "app/_lib/opensearch";
import { Trends } from "leafcutter-common"; import { Trends } from "leafcutter-ui";
export default async function Page() { export default async function Page() {
const visualizations = await getTrends(25); const visualizations = await getTrends(25);

View file

@ -1,6 +1,6 @@
/* eslint-disable no-underscore-dangle */ /* eslint-disable no-underscore-dangle */
import { Client } from "@opensearch-project/opensearch"; import { Client } from "@opensearch-project/opensearch";
import { VisualizationDetail } from "leafcutter-common"; import { VisualizationDetail } from "leafcutter-ui";
const getVisualization = async (visualizationID: string) => { const getVisualization = async (visualizationID: string) => {
const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`; const node = `https://${process.env.OPENSEARCH_USERNAME}:${process.env.OPENSEARCH_PASSWORD}@${process.env.OPENSEARCH_URL}`;

View file

@ -11,13 +11,13 @@ import {
bindTrigger, bindTrigger,
bindMenu, bindMenu,
} from "material-ui-popup-state/hooks"; } from "material-ui-popup-state/hooks";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
export const AccountButton: FC = () => { export const AccountButton: FC = () => {
const t = useTranslate(); const t = useTranslate();
const { const {
colors: { leafcutterElectricBlue }, colors: { leafcutterElectricBlue },
} = useAppContext(); } = useLeafcutterContext();
const popupState = usePopupState({ variant: "popover", popupId: "account" }); const popupState = usePopupState({ variant: "popover", popupId: "account" });
return ( return (

View file

@ -8,7 +8,7 @@ import {
useState, useState,
PropsWithChildren, PropsWithChildren,
} from "react"; } from "react";
import { colors, typography } from "leafcutter-common/styles/theme"; import { colors, typography } from "leafcutter-ui/styles/theme";
const basePath = process.env.GITLAB_CI const basePath = process.env.GITLAB_CI
? "/link/link-stack/apps/leafcutter" ? "/link/link-stack/apps/leafcutter"
@ -29,7 +29,7 @@ const AppContext = createContext({
setFoundCount: null as any, setFoundCount: null as any,
}); });
export const AppProvider: FC<PropsWithChildren> = ({ children }) => { export const LeafcutterProvider: FC<PropsWithChildren> = ({ children }) => {
const initialState = { const initialState = {
incidentType: { incidentType: {
display: "Incident Type", display: "Incident Type",
@ -156,6 +156,6 @@ export const AppProvider: FC<PropsWithChildren> = ({ children }) => {
); );
}; };
export function useAppContext() { export function useLeafcutterContext() {
return useContext(AppContext); return useContext(AppContext);
} }

View file

@ -4,7 +4,7 @@ import { FC, useState } from "react";
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import { Button } from "@mui/material"; import { Button } from "@mui/material";
import { QuestionMark as QuestionMarkIcon } from "@mui/icons-material"; import { QuestionMark as QuestionMarkIcon } from "@mui/icons-material";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
export const HelpButton: FC = () => { export const HelpButton: FC = () => {
const router = useRouter(); const router = useRouter();
@ -12,7 +12,7 @@ export const HelpButton: FC = () => {
const [helpActive, setHelpActive] = useState(false); const [helpActive, setHelpActive] = useState(false);
const { const {
colors: { leafcutterElectricBlue }, colors: { leafcutterElectricBlue },
} = useAppContext(); } = useLeafcutterContext();
const onClick = () => { const onClick = () => {
if (helpActive) { if (helpActive) {
router.push(pathname); router.push(pathname);

View file

@ -7,8 +7,8 @@ import CookieConsent from "react-cookie-consent";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import { TopNav } from "./TopNav"; import { TopNav } from "./TopNav";
import { Sidebar } from "./Sidebar"; import { Sidebar } from "./Sidebar";
import { GettingStartedDialog } from "leafcutter-common"; import { GettingStartedDialog } from "leafcutter-ui";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
// import { Footer } from "./Footer"; // import { Footer } from "./Footer";
type LayoutProps = PropsWithChildren<{ type LayoutProps = PropsWithChildren<{
@ -29,7 +29,7 @@ export const InternalLayout: FC<LayoutProps> = ({
cdrLinkOrange, cdrLinkOrange,
helpYellow, helpYellow,
}, },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<> <>

View file

@ -8,13 +8,13 @@ import {
bindTrigger, bindTrigger,
bindMenu, bindMenu,
} from "material-ui-popup-state/hooks"; } from "material-ui-popup-state/hooks";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
// import { Tooltip } from "./Tooltip"; // import { Tooltip } from "./Tooltip";
export const LanguageSelect = () => { export const LanguageSelect = () => {
const { const {
colors: { white, leafcutterElectricBlue }, colors: { white, leafcutterElectricBlue },
} = useAppContext(); } = useLeafcutterContext();
const router = useRouter(); const router = useRouter();
const locales: any = { en: "English", fr: "Français" }; const locales: any = { en: "English", fr: "Français" };
const locale = "en"; const locale = "en";

View file

@ -8,10 +8,10 @@ import { CookiesProvider } from "react-cookie";
import { I18n } from "react-polyglot"; import { I18n } from "react-polyglot";
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFnsV3"; import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFnsV3";
import { LocalizationProvider } from "@mui/x-date-pickers-pro"; import { LocalizationProvider } from "@mui/x-date-pickers-pro";
import { AppProvider } from "leafcutter-common/components/AppProvider"; import { LeafcutterProvider } from "leafcutter-ui/components/LeafcutterProvider";
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir"; import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
import en from "leafcutter-common/locales/en.json"; import en from "leafcutter-ui/locales/en.json";
import fr from "leafcutter-common/locales/fr.json"; import fr from "leafcutter-ui/locales/fr.json";
import { LicenseInfo } from "@mui/x-date-pickers-pro"; import { LicenseInfo } from "@mui/x-date-pickers-pro";
LicenseInfo.setLicenseKey( LicenseInfo.setLicenseKey(
@ -29,13 +29,13 @@ export const MultiProvider: FC<PropsWithChildren> = ({ children }: any) => {
<SessionProvider> <SessionProvider>
<CookiesProvider> <CookiesProvider>
<CssBaseline /> <CssBaseline />
<AppProvider> <LeafcutterProvider>
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDateFns}>
<I18n locale={locale} messages={messages[locale]}> <I18n locale={locale} messages={messages[locale]}>
{children} {children}
</I18n> </I18n>
</LocalizationProvider> </LocalizationProvider>
</AppProvider> </LeafcutterProvider>
</CookiesProvider> </CookiesProvider>
</SessionProvider> </SessionProvider>
</NextAppDirEmotionCacheProvider> </NextAppDirEmotionCacheProvider>

View file

@ -20,8 +20,8 @@ import {
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
import { Tooltip } from "leafcutter-common"; import { Tooltip } from "leafcutter-ui";
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material"; // import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
const MenuItem = ({ const MenuItem = ({
@ -43,7 +43,7 @@ const MenuItem = ({
}) => { }) => {
const { const {
colors: { leafcutterLightBlue, black }, colors: { leafcutterLightBlue, black },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Link href={href} passHref> <Link href={href} passHref>
@ -105,7 +105,7 @@ export const Sidebar: FC<SidebarProps> = ({ open }) => {
const section = pathname?.split("/")[1]; const section = pathname?.split("/")[1];
const { const {
colors: { white }, // leafcutterElectricBlue, leafcutterLightBlue, colors: { white }, // leafcutterElectricBlue, leafcutterLightBlue,
} = useAppContext(); } = useLeafcutterContext();
// const [recentUpdates, setRecentUpdates] = useState([]); // const [recentUpdates, setRecentUpdates] = useState([]);

View file

@ -8,8 +8,8 @@ import { useTranslate } from "react-polyglot";
import LeafcutterLogo from "images/leafcutter-logo.png"; import LeafcutterLogo from "images/leafcutter-logo.png";
import { AccountButton } from "./AccountButton"; import { AccountButton } from "./AccountButton";
import { HelpButton } from "./HelpButton"; import { HelpButton } from "./HelpButton";
import { Tooltip } from "leafcutter-common"; import { Tooltip } from "leafcutter-ui";
import { useAppContext } from "leafcutter-common/components/AppProvider"; import { useLeafcutterContext } from "leafcutter-ui/components/LeafcutterProvider";
// import { LanguageSelect } from "./LanguageSelect"; // import { LanguageSelect } from "./LanguageSelect";
export const TopNav: FC = () => { export const TopNav: FC = () => {
@ -17,7 +17,7 @@ export const TopNav: FC = () => {
const { const {
colors: { white, leafcutterElectricBlue, cdrLinkOrange }, colors: { white, leafcutterElectricBlue, cdrLinkOrange },
typography: { h5, h6 }, typography: { h5, h6 },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<AppBar <AppBar

View file

@ -1,3 +1,4 @@
import { withAuth } from "next-auth/middleware"; import { withAuth } from "next-auth/middleware";
export default withAuth( export default withAuth(
@ -6,26 +7,13 @@ export default withAuth(
signIn: `/login`, signIn: `/login`,
}, },
callbacks: { callbacks: {
authorized: ({ token, req }) => { authorized: ({ token }) => {
const { // check for existence in opensearch user list
url,
} = req;
const parsedURL = new URL(url);
console.log({ url });
console.log({ pathname: parsedURL.pathname });
console.log({ allowed: parsedURL.pathname.startsWith("/app") });
const allowed = parsedURL.pathname.startsWith('/login') || parsedURL.pathname.startsWith('/api' || parsedURL.pathname.startsWith("/app"));
if (allowed) {
return true;
}
if (token?.email) { if (token?.email) {
return true; return true;
} }
return false; return false;
}, },
} }
} }
@ -33,6 +21,6 @@ export default withAuth(
export const config = { export const config = {
matcher: [ matcher: [
'/((?!api|app|bootstrap|3961|ui|translations|internal|login|node_modules|_next/static|_next/image|favicon.ico).*)', '/((?!api|app|login|_next/static|_next/image|favicon.ico).*)',
], ],
}; };

View file

@ -7,7 +7,7 @@ const ContentSecurityPolicy = `
`; `;
module.exports = { module.exports = {
transpilePackages: ["leafcutter-common"], transpilePackages: ["leafcutter-ui", "opensearch-common"],
experimental: { experimental: {
missingSuspenseWithCSRBailout: false, missingSuspenseWithCSRBailout: false,
}, },

View file

@ -18,19 +18,20 @@
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5", "@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.168", "@mui/lab": "^5.0.0-alpha.169",
"@mui/material": "^5", "@mui/material": "^5",
"@mui/x-data-grid-pro": "^6.19.6", "@mui/x-data-grid-pro": "^6.19.6",
"@mui/x-date-pickers-pro": "^6.19.7", "@mui/x-date-pickers-pro": "^6.19.7",
"@opensearch-project/opensearch": "^2.6.0", "@opensearch-project/opensearch": "^2.6.0",
"cryptr": "^6.3.0", "cryptr": "^6.3.0",
"date-fns": "^3.5.0", "date-fns": "^3.6.0",
"http-proxy-middleware": "^2.0.6", "http-proxy-middleware": "^2.0.6",
"leafcutter-common": "*", "leafcutter-ui": "*",
"material-ui-popup-state": "^5.0.10", "material-ui-popup-state": "^5.1.0",
"next": "14.1.3", "next": "14.1.4",
"next-auth": "^4.24.7", "next-auth": "^4.24.7",
"next-http-proxy-middleware": "^1.2.6", "next-http-proxy-middleware": "^1.2.6",
"opensearch-common": "*",
"nodemailer": "^6.9.12", "nodemailer": "^6.9.12",
"react": "18.2.0", "react": "18.2.0",
"react-cookie": "^7.1.0", "react-cookie": "^7.1.0",
@ -45,14 +46,14 @@
"uuid": "^9.0.1" "uuid": "^9.0.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.0", "@babel/core": "^7.24.1",
"@types/node": "^20.11.28", "@types/node": "^20.11.30",
"@types/react": "18.2.66", "@types/react": "18.2.67",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^14.1.3", "eslint-config-next": "^14.1.4",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",

View file

@ -1,11 +1,9 @@
"use client"; "use client";
import { FC } from "react"; import { FC } from "react";
import { OpenSearchWrapper } from "leafcutter-common"; import { OpenSearchWrapper } from "leafcutter-ui";
export const Home: FC = () => ( export const Home: FC = () => (
<OpenSearchWrapper <OpenSearchWrapper url="/app/visualize#/edit/237b8f00-e6a0-11ee-94b3-d7b7409294e7?embed=true" marginTop="0"
url="/app/dashboards#/view/c39012d0-eb7a-11ed-8e00-17d7d50cd7b2?embed=true&tenant=global"
marginTop="0"
/> />
); );

View file

@ -35,6 +35,7 @@ import LinkLogo from "public/link-logo-small.png";
import { useSession, signOut } from "next-auth/react"; import { useSession, signOut } from "next-auth/react";
import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery"; import { getTicketOverviewCountsQuery } from "app/_graphql/getTicketOverviewCountsQuery";
import { SearchBox } from "./SearchBox"; import { SearchBox } from "./SearchBox";
import { fonts } from "app/_styles/theme";
const openWidth = 270; const openWidth = 270;
const closedWidth = 100; const closedWidth = 100;
@ -49,8 +50,10 @@ const MenuItem = ({
open = true, open = true,
badge, badge,
target = "_self", target = "_self",
}: any) => ( }: any) => {
<Link href={href} target={target}> const { roboto } = fonts;
return (<Link href={href} target={target}>
<ListItemButton <ListItemButton
sx={{ sx={{
p: 0, p: 0,
@ -123,7 +126,7 @@ const MenuItem = ({
variant="body1" variant="body1"
sx={{ sx={{
fontSize: 16, fontSize: 16,
fontFamily: "Roboto", fontFamily: roboto.style.fontFamily,
fontWeight: "bold", fontWeight: "bold",
border: 0, border: 0,
textAlign: "left", textAlign: "left",
@ -157,6 +160,7 @@ const MenuItem = ({
</ListItemButton> </ListItemButton>
</Link> </Link>
); );
}
interface SidebarProps { interface SidebarProps {
open: boolean; open: boolean;
@ -166,6 +170,7 @@ interface SidebarProps {
export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => { export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
const pathname = usePathname(); const pathname = usePathname();
const { data: session } = useSession(); const { data: session } = useSession();
const { poppins } = fonts;
const username = session?.user?.name || "User"; const username = session?.user?.name || "User";
// @ts-ignore // @ts-ignore
const roles = session?.user?.roles || []; const roles = session?.user?.roles || [];
@ -272,7 +277,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
fontWeight: 700, fontWeight: 700,
mt: 1, mt: 1,
ml: 0.5, ml: 0.5,
fontFamily: "Poppins", fontFamily: poppins.style.fontFamily,
}} }}
> >
CDR Link CDR Link

View file

@ -1,30 +0,0 @@
"use client";
import { FC, /* useEffect,*/ useState } from "react";
import { Home as HomeInternal } from "leafcutter-common";
// import { fetchLeafcutter } from "@/app/_lib/utils";
import ClientOnly from "@/app/(main)/_components/ClientOnly";
export const Home: FC = () => {
const [visualizations, setVisualizations] = useState([]);
/*
useEffect(() => {
const getVisualizations = async () => {
const visualizations = await fetchLeafcutter(
"/api/visualizations/list",
{},
);
if (visualizations) {
setVisualizations(visualizations);
}
};
getVisualizations();
}, []);
*/
return (
<ClientOnly>
<HomeInternal visualizations={visualizations} />
</ClientOnly>
);
};

View file

@ -1,10 +1,5 @@
import { Box } from "@mui/material"; import { About } from "leafcutter-ui";
import { About } from "leafcutter-common";
export default function Page() { export default function Page() {
return ( return <About />;
<Box sx={{ p: 3 }}>
<About />
</Box>
);
} }

View file

@ -1,13 +1,8 @@
// import { getTemplates } from "app/_lib/opensearch"; import { getTemplates } from "opensearch-common";
import { Create } from "leafcutter-common"; import { Create } from "leafcutter-ui";
import { Box } from "@mui/material";
export default async function Page() { export default async function Page() {
const templates = []; // await getTemplates(100); const templates = await getTemplates(100);
return ( return <Create templates={templates} />;
<Box sx={{ p: 3 }}>
<Create templates={templates} />
</Box>
);
} }

View file

@ -1,10 +1,5 @@
import { Box } from "@mui/material"; import { FAQ } from "leafcutter-ui";
import { FAQ } from "leafcutter-common";
export default function Page() { export default function Page() {
return ( return <FAQ />;
<Box sx={{ p: 3 }}>
<FAQ />
</Box>
);
} }

View file

@ -0,0 +1,10 @@
import { ReactNode } from "react";
import { LeafcutterWrapper } from "leafcutter-ui";
type LayoutProps = {
children: ReactNode;
};
export default function Layout({ children }: LayoutProps) {
return <LeafcutterWrapper>{children}</LeafcutterWrapper>;
}

View file

@ -1,10 +0,0 @@
import { Box } from "@mui/material";
import { FAQ } from "leafcutter-common";
export default function Page() {
return (
<Box sx={{ p: 3 }}>
<FAQ />
</Box>
);
}

View file

@ -1,5 +1,4 @@
import { Home } from "./_components/Home"; import { Home, LeafcutterWrapper } from "leafcutter-ui";
import { LeafcutterWrapper } from "./_components/LeafcutterWrapper";
export default async function Page() { export default async function Page() {
return ( return (

View file

@ -1,12 +1,7 @@
import { Box } from "@mui/material"; import { Trends } from "leafcutter-ui";
import { Trends } from "leafcutter-common";
export default function Page() {
return (
<Box sx={{ p: 3 }}>
<Trends visualizations={[]} />
</Box>
);
}
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default function Page() {
return <Trends visualizations={[]} />;
}

View file

@ -1,10 +1,23 @@
import { Metadata } from "next"; import { Metadata } from "next";
import { Home } from "./_components/Home"; import { getServerSession } from "app/_lib/authentication";
import { Home } from "leafcutter-ui";
import { getUserVisualizations } from "opensearch-common";
import { LeafcutterWrapper } from "leafcutter-ui";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Link", title: "Link",
}; };
export default function Page() { export default async function Page() {
return <Home />; const session = await getServerSession();
const {
user: { email },
}: any = session;
const visualizations = await getUserVisualizations(email ?? "none", 20);
return (
<LeafcutterWrapper>
<Home visualizations={visualizations} showWelcome={false} />
</LeafcutterWrapper>
);
} }

View file

@ -1,7 +1,6 @@
"use client"; "use client";
import { FC, PropsWithChildren, useState } from "react"; import { FC, PropsWithChildren, useState } from "react";
import { usePathname } from "next/navigation";
import { CssBaseline } from "@mui/material"; import { CssBaseline } from "@mui/material";
import { CookiesProvider } from "react-cookie"; import { CookiesProvider } from "react-cookie";
import { SessionProvider } from "next-auth/react"; import { SessionProvider } from "next-auth/react";
@ -12,7 +11,7 @@ import { I18n } from "react-polyglot";
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFnsV3"; import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFnsV3";
import { LocalizationProvider } from "@mui/x-date-pickers-pro"; import { LocalizationProvider } from "@mui/x-date-pickers-pro";
import { LicenseInfo } from "@mui/x-date-pickers-pro"; import { LicenseInfo } from "@mui/x-date-pickers-pro";
import { locales } from "leafcutter-common"; import { locales, LeafcutterProvider } from "leafcutter-ui";
LicenseInfo.setLicenseKey( LicenseInfo.setLicenseKey(
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=", "7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
@ -113,7 +112,9 @@ export const MultiProvider: FC<PropsWithChildren> = ({ children }) => {
<CookiesProvider> <CookiesProvider>
<LocalizationProvider dateAdapter={AdapterDateFns}> <LocalizationProvider dateAdapter={AdapterDateFns}>
<I18n locale={locale} messages={messages[locale]}> <I18n locale={locale} messages={messages[locale]}>
<LeafcutterProvider>
{children} {children}
</LeafcutterProvider>
</I18n> </I18n>
</LocalizationProvider> </LocalizationProvider>
</CookiesProvider> </CookiesProvider>

View file

@ -0,0 +1,140 @@
import type {
GetServerSidePropsContext,
NextApiRequest,
NextApiResponse,
} from "next";
import {
NextAuthOptions,
getServerSession as internalGetServerSession,
} from "next-auth";
import Google from "next-auth/providers/google";
import Credentials from "next-auth/providers/credentials";
import Apple from "next-auth/providers/apple";
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` };
const fetchRoles = async () => {
const url = `${process.env.ZAMMAD_URL}/api/v1/roles`;
const res = await fetch(url, { headers });
const roles = await res.json();
console.log({ roles });
const formattedRoles = roles.reduce((acc: any, role: any) => {
acc[role.id] = role.name;
return acc;
}, {});
return formattedRoles;
};
const fetchUser = async (email: string) => {
console.log({ email });
const url = `${process.env.ZAMMAD_URL}/api/v1/users/search?query=login:${email}&limit=1`;
console.log({ url });
const res = await fetch(url, { headers });
console.log({ res });
const users = await res.json();
console.log({ users });
const user = users?.[0];
return user;
};
const getUserRoles = async (email: string) => {
try {
const user = await fetchUser(email);
console.log({ user });
const allRoles = await fetchRoles();
console.log({ allRoles });
const roles = user.role_ids.map((roleID: number) => {
const role = allRoles[roleID];
return role ? role.toLowerCase().replace(" ", "_") : null;
});
return roles.filter((role: string) => role !== null);
} catch (e) {
console.log({ e });
return [];
}
};
const login = async (email: string, password: string) => {
const url = `${process.env.ZAMMAD_URL}/api/v1/users/me`;
console.log({ url });
const authorization =
"Basic " + Buffer.from(email + ":" + password).toString("base64");
const res = await fetch(url, {
headers: {
authorization,
},
});
const user = await res.json();
console.log({ user });
if (user && !user.error && user.id) {
return user;
} else {
return null;
}
};
export const authOptions: NextAuthOptions = {
pages: {
signIn: "/login",
error: "/login",
signOut: "/logout",
},
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,
}),
Credentials({
name: "Zammad",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const user = await login(credentials.email, credentials.password);
if (user) {
return user;
} else {
return null;
}
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
signIn: async ({ user, account, profile }) => {
const roles = (await getUserRoles(user.email)) ?? [];
return (
roles.includes("admin") ||
roles.includes("agent") ||
process.env.SETUP_MODE === "true"
);
},
session: async ({ session, user, token }) => {
// @ts-ignore
session.user.roles = token.roles ?? [];
// @ts-ignore
session.user.leafcutter = token.leafcutter; // remove
return session;
},
jwt: async ({ token, user, account, profile, trigger }) => {
if (user) {
token.roles = (await getUserRoles(user.email)) ?? [];
}
return token;
},
},
}
export const getServerSession = (
...args:
| [GetServerSidePropsContext["req"], GetServerSidePropsContext["res"]]
| [NextApiRequest, NextApiResponse]
| []
) => internalGetServerSession(...args, authOptions);

View file

@ -1,128 +1,6 @@
import NextAuth from "next-auth"; import NextAuth from "next-auth";
import Google from "next-auth/providers/google"; import { authOptions } from "app/_lib/authentication";
import Credentials from "next-auth/providers/credentials";
import Apple from "next-auth/providers/apple";
const headers = { Authorization: `Token ${process.env.ZAMMAD_API_TOKEN}` }; const handler = NextAuth(authOptions);
const fetchRoles = async () => {
const url = `${process.env.ZAMMAD_URL}/api/v1/roles`;
const res = await fetch(url, { headers });
const roles = await res.json();
console.log({ roles });
const formattedRoles = roles.reduce((acc: any, role: any) => {
acc[role.id] = role.name;
return acc;
}, {});
return formattedRoles;
};
const fetchUser = async (email: string) => {
console.log({ email });
const url = `${process.env.ZAMMAD_URL}/api/v1/users/search?query=login:${email}&limit=1`;
console.log({ url });
const res = await fetch(url, { headers });
console.log({ res });
const users = await res.json();
console.log({ users });
const user = users?.[0];
return user;
};
const getUserRoles = async (email: string) => {
try {
const user = await fetchUser(email);
console.log({ user });
const allRoles = await fetchRoles();
console.log({ allRoles });
const roles = user.role_ids.map((roleID: number) => {
const role = allRoles[roleID];
return role ? role.toLowerCase().replace(" ", "_") : null;
});
return roles.filter((role: string) => role !== null);
} catch (e) {
console.log({ e });
return [];
}
};
const login = async (email: string, password: string) => {
const url = `${process.env.ZAMMAD_URL}/api/v1/users/me`;
console.log({ url });
const authorization =
"Basic " + Buffer.from(email + ":" + password).toString("base64");
const res = await fetch(url, {
headers: {
authorization,
},
});
const user = await res.json();
console.log({ user });
if (user && !user.error && user.id) {
return user;
} else {
return null;
}
};
const handler = NextAuth({
pages: {
signIn: "/login",
error: "/login",
signOut: "/logout",
},
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,
}),
Credentials({
name: "Zammad",
credentials: {
email: { label: "Email", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials, req) {
const user = await login(credentials.email, credentials.password);
if (user) {
return user;
} else {
return null;
}
},
}),
],
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
signIn: async ({ user, account, profile }) => {
const roles = (await getUserRoles(user.email)) ?? [];
return (
roles.includes("admin") ||
roles.includes("agent") ||
process.env.SETUP_MODE === "true"
);
},
session: async ({ session, user, token }) => {
// @ts-ignore
session.user.roles = token.roles ?? [];
// @ts-ignore
session.user.leafcutter = token.leafcutter; // remove
return session;
},
jwt: async ({ token, user, account, profile, trigger }) => {
if (user) {
token.roles = (await getUserRoles(user.email)) ?? [];
}
return token;
},
},
});
export { handler as GET, handler as POST }; export { handler as GET, handler as POST };

View file

@ -1,94 +1,83 @@
import { NextResponse } from 'next/server'; import { NextResponse } from "next/server";
import { withAuth, NextRequestWithAuth } from "next-auth/middleware"; import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
const rewriteURL = (request: NextRequestWithAuth, originBaseURL: string, destinationBaseURL: string, headers: any = {}) => { const rewriteURL = (
if (request.nextUrl.pathname.startsWith('/api/v1/reports/sets')) { request: NextRequestWithAuth,
console.log(request.nextUrl.searchParams.get("sheet")); originBaseURL: string,
NextResponse.next(); destinationBaseURL: string,
} headers: any = {},
) => {
const destinationURL = request.url.replace(originBaseURL, destinationBaseURL); const destinationURL = request.url.replace(originBaseURL, destinationBaseURL);
console.log(`Rewriting ${request.url} to ${destinationURL}`); console.log(`Rewriting ${request.url} to ${destinationURL}`);
const requestHeaders = new Headers(request.headers); const requestHeaders = new Headers(request.headers);
for (const [key, value] of Object.entries(headers)) { for (const [key, value] of Object.entries(headers)) {
requestHeaders.set(key, value as string); requestHeaders.set(key, value as string);
} }
requestHeaders.delete("connection");
requestHeaders.delete('connection'); return NextResponse.rewrite(new URL(destinationURL), {
request: { headers: requestHeaders },
return NextResponse.rewrite(new URL(destinationURL), { request: { headers: requestHeaders } }); });
}; };
const checkRewrites = async (request: NextRequestWithAuth) => { const checkRewrites = async (request: NextRequestWithAuth) => {
const linkBaseURL = process.env.LINK_URL ?? "http://localhost:3000"; const linkBaseURL = process.env.LINK_URL ?? "http://localhost:3000";
const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080"; const zammadURL = process.env.ZAMMAD_URL ?? "http://zammad-nginx:8080";
const opensearchURL = process.env.OPENSEARCH_URL ?? "http://macmini:5601"; const opensearchDashboardsURL =
const metamigoURL = process.env.METAMIGO_URL ?? "http://metamigo-api:3000"; process.env.OPENSEARCH_DASHBOARDS_URL ?? "http://macmini:5601";
const labelStudioURL = process.env.LABEL_STUDIO_URL ?? "http://label-studio:8080"; const zammadPaths = ["/zammad", "/api/v1", "/auth/sso", "/assets", "/mobile"];
const { token } = request.nextauth; const { token } = request.nextauth;
const headers = { 'X-Forwarded-User': token?.email?.toLowerCase() }; const email = token?.email?.toLowerCase() ?? "unknown";
console.log({ pathname: request.nextUrl.pathname }); let headers = { "x-forwarded-user": email };
if (request.nextUrl.pathname.startsWith('/opensearch')) { if (request.nextUrl.pathname.startsWith("/dashboards")) {
const headers = { const roles: string[] = (token?.roles as string[]) ?? [];
'x-proxy-user': "admin", const leafcutterRole = roles.includes("admin")
'x-proxy-roles': "all_access", ? "leafcutter_admin"
// 'X-Forwarded-For': "link" : "leafcutter_user";
}; headers["x-forwarded-roles"] = "admin"; // leafcutterRole;
return rewriteURL(request, `${linkBaseURL}/opensearch`, opensearchURL, headers); // headers["secruitytenant"] = "global";
} else if (request.nextUrl.pathname.startsWith('/metamigo')) { // headers["x-forwarded-for"] = 'link';
return rewriteURL(request, `${linkBaseURL}/metamigo`, metamigoURL);
} else if (request.nextUrl.pathname.startsWith('/label-studio')) { return rewriteURL(
return rewriteURL(request, `${linkBaseURL}/label-studio`, labelStudioURL); request,
} else if (request.nextUrl.pathname.startsWith('/zammad')) { `${linkBaseURL}/dashboards`,
opensearchDashboardsURL,
headers,
);
} else if (request.nextUrl.pathname.startsWith("/zammad")) {
return rewriteURL(request, `${linkBaseURL}/zammad`, zammadURL, headers); return rewriteURL(request, `${linkBaseURL}/zammad`, zammadURL, headers);
} else if (request.nextUrl.pathname.startsWith('/auth/sso') || request.nextUrl.pathname.startsWith('/assets')) { } else if (zammadPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
return rewriteURL(request, linkBaseURL, zammadURL, headers);
} else if (request.nextUrl.pathname.startsWith('/proxy/api') || request.nextUrl.pathname.startsWith('/proxy/assets')) {
return rewriteURL(request, `${linkBaseURL}/proxy`, zammadURL);
} else if (request.nextUrl.pathname.startsWith('/api/v1') || request.nextUrl.pathname.startsWith('/auth/sso') || request.nextUrl.pathname.startsWith('/mobile')) {
return rewriteURL(request, linkBaseURL, zammadURL, headers); return rewriteURL(request, linkBaseURL, zammadURL, headers);
} }
return NextResponse.next(); return NextResponse.next();
}; };
export default withAuth( export default withAuth(checkRewrites, {
checkRewrites,
{
pages: { pages: {
signIn: `/login`, signIn: `/login`,
}, },
callbacks: { callbacks: {
authorized: ({ token, req }) => { authorized: ({ token, req }) => {
const { if (req.nextUrl.pathname === "/api/v1") {
url, return true;
} = req; }
const noAuthPaths = ["/login", "/api/v1"]; if (process.env.SETUP_MODE === "true") {
const parsedURL = new URL(url);
const path = parsedURL.pathname;
if (noAuthPaths.some((p: string) => path.startsWith(p))) {
console.log({ p: parsedURL.pathname, auth: "no" });
return true; return true;
} }
const roles: any = token?.roles ?? []; const roles: any = token?.roles ?? [];
if (roles.includes("admin") || roles.includes("agent") || process.env.SETUP_MODE === "true") { if (roles.includes("admin") || roles.includes("agent")) {
return true; return true;
} }
return false; return false;
}, },
} },
} });
);
export const config = { export const config = {
matcher: [ matcher: ["/((?!ws|wss|_next/static|_next/image|favicon.ico).*)"],
'/((?!ws|wss|_next/static|_next/image|favicon.ico).*)',
],
}; };

View file

@ -4,33 +4,13 @@ const nextConfig = {
experimental: { experimental: {
missingSuspenseWithCSRBailout: false, missingSuspenseWithCSRBailout: false,
}, },
modularizeImports: { transpilePackages: ["leafcutter-ui", "metamigo-ui", "opensearch-common", "ui"],
"@mui/material": {
transform: "@mui/material/{{member}}",
},
"@mui/icons-material": {
transform: "@mui/icons-material/{{member}}",
},
},
transpilePackages: ["leafcutter-common"],
publicRuntimeConfig: { publicRuntimeConfig: {
linkURL: process.env.LINK_URL ?? "http://localhost:3000", linkURL: process.env.LINK_URL ?? "http://localhost:3000",
leafcutterURL:
process.env.LEAFCUTTER_URL ?? "https://lc.digiresilience.org",
metamigoURL: process.env.METAMIGO_URL ?? "http://localhost:8002", metamigoURL: process.env.METAMIGO_URL ?? "http://localhost:8002",
labelStudioURL: process.env.LABEL_STUDIO_URL ?? "http://localhost:8006", labelStudioURL: process.env.LABEL_STUDIO_URL ?? "http://localhost:8006",
muiLicenseKey: process.env.MUI_LICENSE_KEY ?? "", muiLicenseKey: process.env.MUI_LICENSE_KEY ?? "",
}, },
async rewrites() {
return {
fallback: [
{
source: "/:path*",
destination: `/proxy/leafcutter/:path*`,
},
],
};
},
}; };
module.exports = nextConfig; module.exports = nextConfig;

View file

@ -16,26 +16,24 @@
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5", "@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.168", "@mui/lab": "^5.0.0-alpha.169",
"@mui/material": "^5", "@mui/material": "^5",
"@mui/x-data-grid-pro": "^6.19.6", "@mui/x-data-grid-pro": "^6.19.6",
"@mui/x-date-pickers-pro": "^6.19.7", "@mui/x-date-pickers-pro": "^6.19.7",
"cryptr": "^6.3.0", "date-fns": "^3.6.0",
"date-fns": "^3.5.0",
"graphql-request": "^6.1.0", "graphql-request": "^6.1.0",
"leafcutter-common": "*", "leafcutter-ui": "*",
"material-ui-popup-state": "^5.0.10", "material-ui-popup-state": "^5.1.0",
"metamigo-common": "*", "metamigo-ui": "*",
"mui-chips-input": "^2.1.4", "mui-chips-input": "^2.1.4",
"next": "14.1.3", "next": "14.1.4",
"next-auth": "^4.24.7", "next-auth": "^4.24.7",
"opensearch-common": "*",
"react": "18.2.0", "react": "18.2.0",
"react-cookie": "^7.1.0", "react-cookie": "^7.1.0",
"react-digit-input": "^2.1.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-iframe": "^1.8.5", "react-iframe": "^1.8.5",
"react-polyglot": "^0.7.2", "react-polyglot": "^0.7.2",
"react-timer-hook": "^3.0.7",
"sharp": "^0.33.2", "sharp": "^0.33.2",
"swr": "^2.2.5", "swr": "^2.2.5",
"tss-react": "^4.9.4", "tss-react": "^4.9.4",
@ -43,14 +41,14 @@
"ui": "*" "ui": "*"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.24.0", "@babel/core": "^7.24.1",
"@types/node": "^20.11.28", "@types/node": "^20.11.30",
"@types/react": "18.2.66", "@types/react": "18.2.67",
"@types/uuid": "^9.0.8", "@types/uuid": "^9.0.8",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-next": "^14.1.3", "eslint-config-next": "^14.1.4",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1", "eslint-plugin-import": "^2.29.1",
"eslint-plugin-jsx-a11y": "^6.8.0", "eslint-plugin-jsx-a11y": "^6.8.0",

View file

@ -4,6 +4,7 @@ import { KyselyAdapter } from "@auth/kysely-adapter"
import { db } from "./database"; import { db } from "./database";
export const authOptions = NextAuth({ export const authOptions = NextAuth({
// @ts-ignore
adapter: KyselyAdapter(db), adapter: KyselyAdapter(db),
providers: [ providers: [
GoogleProvider({ GoogleProvider({

View file

@ -6,7 +6,7 @@ import type { GeneratedAlways } from "kysely";
interface Database { interface Database {
User: { User: {
id: GeneratedAlways<string>; id: string;
name: string | null; name: string | null;
email: string; email: string;
emailVerified: Date | null; emailVerified: Date | null;
@ -16,16 +16,16 @@ interface Database {
Account: { Account: {
id: GeneratedAlways<string>; id: GeneratedAlways<string>;
userId: string; userId: string;
type: string; type: "oidc" | "oauth" | "email" | "webauthn";
provider: string; provider: string;
providerAccountId: string; providerAccountId: string;
refresh_token: string | null; refresh_token: string | undefined;
access_token: string | null; access_token: string | undefined;
expires_at: number | null; expires_at: number | undefined;
token_type: string | null; token_type: Lowercase<string> | undefined;
scope: string | null; scope: string | undefined;
id_token: string | null; id_token: string | undefined;
session_state: string | null; session_state: string | undefined;
}; };
Session: { Session: {

View file

@ -1,3 +1,4 @@
export default function Page() { export default function Page() {
return <h1>Home</h1>; return <h1>Home</h1>;
} }

View file

@ -14,16 +14,16 @@
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@mui/icons-material": "^5", "@mui/icons-material": "^5",
"@mui/lab": "^5.0.0-alpha.168", "@mui/lab": "^5.0.0-alpha.169",
"@mui/material": "^5", "@mui/material": "^5",
"@mui/material-nextjs": "^5.15.11", "@mui/material-nextjs": "^5.15.11",
"@mui/x-data-grid-pro": "^6.19.6", "@mui/x-data-grid-pro": "^6.19.6",
"@mui/x-date-pickers-pro": "^6.19.7", "@mui/x-date-pickers-pro": "^6.19.7",
"date-fns": "^3.5.0", "date-fns": "^3.6.0",
"kysely": "^0.26.1", "kysely": "^0.26.1",
"material-ui-popup-state": "^5.0.10", "material-ui-popup-state": "^5.1.0",
"mui-chips-input": "^2.1.4", "mui-chips-input": "^2.1.4",
"next": "14.1.3", "next": "14.1.4",
"next-auth": "^4.24.7", "next-auth": "^4.24.7",
"pg": "^8.11.3", "pg": "^8.11.3",
"react": "18.2.0", "react": "18.2.0",
@ -37,11 +37,11 @@
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/pg": "^8.11.2", "@types/pg": "^8.11.3",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.1.3", "eslint-config-next": "14.1.4",
"typescript": "^5", "typescript": "^5",
"ts-config": "*" "ts-config": "*"
} }

View file

@ -10,14 +10,14 @@
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"node-fetch": "^3", "node-fetch": "^3",
"pg-promise": "^11.5.4", "pg-promise": "^11.5.4",
"remeda": "^1.50.1", "remeda": "^1.55.0",
"twilio": "^5.0.1" "twilio": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"ts-config": "*", "ts-config": "*",
"@babel/core": "7.24.0", "@babel/core": "7.24.1",
"@babel/preset-env": "7.24.0", "@babel/preset-env": "7.24.1",
"@babel/preset-typescript": "7.23.3", "@babel/preset-typescript": "7.24.1",
"@types/fluent-ffmpeg": "^2.1.24", "@types/fluent-ffmpeg": "^2.1.24",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"eslint": "^8.57.0", "eslint": "^8.57.0",

View file

@ -7,17 +7,18 @@ opensearch.requestHeadersAllowlist:
"securitytenant", "securitytenant",
"Authorization", "Authorization",
"x-forwarded-for", "x-forwarded-for",
"x-proxy-user", "x-forwarded-user",
"x-proxy-roles", "x-forwarded-roles",
] ]
# opensearch_security.auth.type: "proxy" opensearch_security.auth.type: "proxy"
# opensearch_security.proxycache.user_header: "x-proxy-user" opensearch_security.proxycache.user_header: "x-forwarded-user"
# opensearch_security.proxycache.roles_header: "x-proxy-roles" opensearch_security.proxycache.roles_header: "x-forwarded-roles"
opensearch_security.multitenancy.enabled: true opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.enable_global: true
opensearch_security.multitenancy.tenants.enable_private: true
opensearch_security.multitenancy.tenants.preferred: [Private, Global] opensearch_security.multitenancy.tenants.preferred: [Private, Global]
opensearch_security.readonly_mode.roles: [kibana_read_only] # opensearch_security.readonly_mode.roles: [kibana_read_only]
opensearch_security.cookie.secure: false opensearch_security.cookie.secure: false
server.host: "0.0.0.0" server.host: "0.0.0.0"
server.basePath: "/opensearch" server.basePath: "/dashboards"
server.rewriteBasePath: false server.rewriteBasePath: false

View file

@ -30,7 +30,7 @@ config:
type: proxy type: proxy
challenge: false challenge: false
config: config:
user_header: "x-proxy-user" user_header: "x-forwarded-user"
roles_header: "x-proxy-roles" roles_header: "x-forwarded-roles"
authentication_backend: authentication_backend:
type: noop type: noop

1455
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,9 +9,9 @@
"fmt": "prettier \"profile/**/*.js\" --write" "fmt": "prettier \"profile/**/*.js\" --write"
}, },
"dependencies": { "dependencies": {
"@rushstack/eslint-patch": "^1.7.2", "@rushstack/eslint-patch": "^1.8.0",
"@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.2.0", "@typescript-eslint/parser": "^7.3.1",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-config-xo-space": "^0.35.0", "eslint-config-xo-space": "^0.35.0",
"eslint-plugin-cypress": "^2.15.1", "eslint-plugin-cypress": "^2.15.1",
@ -21,7 +21,7 @@
"eslint-plugin-no-use-extend-native": "^0.5.0", "eslint-plugin-no-use-extend-native": "^0.5.0",
"eslint-plugin-promise": "^6.1.1", "eslint-plugin-promise": "^6.1.1",
"eslint-plugin-unicorn": "51.0.1", "eslint-plugin-unicorn": "51.0.1",
"@babel/eslint-parser": "7.23.10" "@babel/eslint-parser": "7.24.1"
}, },
"peerDependencies": { "peerDependencies": {
"eslint": "^7.32.0", "eslint": "^7.32.0",

View file

@ -1,24 +0,0 @@
"use client";
import { FC, useEffect, useState } from "react";
import { useAppContext } from "./AppProvider";
import { RawDataViewer } from "./RawDataViewer";
export const LiveDataViewer: FC = () => {
const { query, setFoundCount } = useAppContext();
const [rows, setRows] = useState<any[]>([]);
const searchQuery = encodeURI(JSON.stringify(query));
useEffect(() => {
const fetchData = async () => {
const result = await fetch(
`/api/visualizations/query?searchQuery=${searchQuery}`,
);
const json = await result.json();
setRows(json);
setFoundCount(json?.length ?? 0);
};
fetchData();
}, [searchQuery, setFoundCount]);
return <RawDataViewer rows={rows} height={350} />;
};

View file

@ -1,17 +0,0 @@
import type { NextAuthOptions } from "next-auth";
import Google from "next-auth/providers/google";
import Apple from "next-auth/providers/apple";
export const authOptions: NextAuthOptions = {
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,
};

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,23 @@
"use server";
import { performLeafcutterQuery, performZammadQuery, createUserVisualization } from "opensearch-common";
export const createUserVisualizationAction = async ({visualizationID, title, description, query}: any) => {
const email = "darren@redaranj.com";
const id = await createUserVisualization({
email,
visualizationID,
title,
description,
query
});
return id;
}
export const searchVisualizationsAction = async (
kind: string,
searchQuery: string,
) =>
kind === "zammad"
? performZammadQuery(searchQuery, 1000)
: performLeafcutterQuery(searchQuery, 1000);

View file

@ -5,7 +5,7 @@ import Link from "next/link";
import Image from "next/image"; import Image from "next/image";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { Grid, Container, Box, Button } from "@mui/material"; import { Grid, Container, Box, Button } from "@mui/material";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
import { AboutBox } from "./AboutBox"; import { AboutBox } from "./AboutBox";
import { AboutFeature } from "./AboutFeature"; import { AboutFeature } from "./AboutFeature";
import { PageHeader } from "./PageHeader"; import { PageHeader } from "./PageHeader";
@ -21,7 +21,7 @@ export const About: FC = () => {
const { const {
colors: { white, leafcutterElectricBlue, cdrLinkOrange }, colors: { white, leafcutterElectricBlue, cdrLinkOrange },
typography: { h1, h4, p }, typography: { h1, h4, p },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<> <>

View file

@ -2,7 +2,7 @@
import { FC, PropsWithChildren } from "react"; import { FC, PropsWithChildren } from "react";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
type AboutBoxProps = PropsWithChildren<{ type AboutBoxProps = PropsWithChildren<{
backgroundColor: string; backgroundColor: string;
@ -14,7 +14,7 @@ export const AboutBox: FC<AboutBoxProps> = ({
}: any) => { }: any) => {
const { const {
colors: { white }, colors: { white },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Box <Box

View file

@ -4,7 +4,7 @@ import { FC } from "react";
import Image from "next/legacy/image"; import Image from "next/legacy/image";
import { Grid, Box, GridSize } from "@mui/material"; import { Grid, Box, GridSize } from "@mui/material";
import AboutDots from "../images/about-dots.png"; import AboutDots from "../images/about-dots.png";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface AboutFeatureProps { interface AboutFeatureProps {
title: string; title: string;
@ -25,7 +25,7 @@ export const AboutFeature: FC<AboutFeatureProps> = ({
}) => { }) => {
const { const {
typography: { h2, p }, typography: { h2, p },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Box <Box

View file

@ -3,7 +3,7 @@
import { FC } from "react"; import { FC } from "react";
import Link from "next/link"; import Link from "next/link";
import { Button as MUIButton } from "@mui/material"; import { Button as MUIButton } from "@mui/material";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface ButtonProps { interface ButtonProps {
text: string; text: string;
@ -14,7 +14,7 @@ interface ButtonProps {
export const Button: FC<ButtonProps> = ({ text, color, href }) => { export const Button: FC<ButtonProps> = ({ text, color, href }) => {
const { const {
colors: { white, almostBlack }, colors: { white, almostBlack },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Link href={href} passHref> <Link href={href} passHref>

View file

@ -5,7 +5,7 @@ import { useTranslate } from "react-polyglot";
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import { Box, Grid } from "@mui/material"; import { Box, Grid } from "@mui/material";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
import { PageHeader } from "./PageHeader"; import { PageHeader } from "./PageHeader";
import { VisualizationBuilder } from "./VisualizationBuilder"; import { VisualizationBuilder } from "./VisualizationBuilder";
@ -18,7 +18,7 @@ export const Create: FC<CreateProps> = ({ templates }) => {
const { const {
colors: { cdrLinkOrange }, colors: { cdrLinkOrange },
typography: { h1, h4 }, typography: { h1, h4 },
} = useAppContext(); } = useLeafcutterContext();
const router = useRouter(); const router = useRouter();
const pathname = usePathname() ?? ""; const pathname = usePathname() ?? "";
const cookieName = "searchIntroComplete"; const cookieName = "searchIntroComplete";

View file

@ -5,7 +5,7 @@ import { useTranslate } from "react-polyglot";
import { Box, Grid } from "@mui/material"; import { Box, Grid } from "@mui/material";
import { PageHeader } from "./PageHeader"; import { PageHeader } from "./PageHeader";
import { Question } from "./Question"; import { Question } from "./Question";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
import FaqHeader from "../images/faq-header.svg"; import FaqHeader from "../images/faq-header.svg";
export const FAQ: FC = () => { export const FAQ: FC = () => {
@ -13,7 +13,7 @@ export const FAQ: FC = () => {
const { const {
colors: { lavender }, colors: { lavender },
typography: { h1, h4, p }, typography: { h1, h4, p },
} = useAppContext(); } = useLeafcutterContext();
const questions = [ const questions = [
{ {

View file

@ -9,14 +9,14 @@ import leafcutterLogo from "../images/leafcutter-logo.png";
import footerLogo from "../images/footer-logo.png"; import footerLogo from "../images/footer-logo.png";
import twitterLogo from "../images/twitter-logo.png"; import twitterLogo from "../images/twitter-logo.png";
import gitlabLogo from "../images/gitlab-logo.png"; import gitlabLogo from "../images/gitlab-logo.png";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
export const Footer: FC = () => { export const Footer: FC = () => {
const t = useTranslate(); const t = useTranslate();
const { const {
colors: { white, leafcutterElectricBlue }, colors: { white, leafcutterElectricBlue },
typography: { bodySmall }, typography: { bodySmall },
} = useAppContext(); } = useLeafcutterContext();
const smallLinkStyles: any = { const smallLinkStyles: any = {
...bodySmall, ...bodySmall,
color: white, color: white,

View file

@ -5,7 +5,7 @@ import { Dialog, Box, Grid, Checkbox, IconButton } from "@mui/material";
import { Close as CloseIcon } from "@mui/icons-material"; import { Close as CloseIcon } from "@mui/icons-material";
import { useRouter, usePathname, useSearchParams } from "next/navigation"; import { useRouter, usePathname, useSearchParams } from "next/navigation";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
type CheckboxItemProps = { type CheckboxItemProps = {
title: string; title: string;
@ -22,7 +22,7 @@ const CheckboxItem: FC<CheckboxItemProps> = ({
}) => { }) => {
const { const {
typography: { p, small }, typography: { p, small },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Grid item container spacing={0}> <Grid item container spacing={0}>
@ -60,7 +60,7 @@ export const GettingStartedDialog: FC = () => {
const { const {
colors: { almostBlack }, colors: { almostBlack },
typography: { h4 }, typography: { h4 },
} = useAppContext(); } = useLeafcutterContext();
const t = useTranslate(); const t = useTranslate();
const router = useRouter(); const router = useRouter();
const [completedItems, setCompletedItems] = useState([] as any[]); const [completedItems, setCompletedItems] = useState([] as any[]);

View file

@ -10,13 +10,14 @@ import { useCookies } from "react-cookie";
import { Welcome } from "./Welcome"; import { Welcome } from "./Welcome";
import { WelcomeDialog } from "./WelcomeDialog"; import { WelcomeDialog } from "./WelcomeDialog";
import { VisualizationCard } from "./VisualizationCard"; import { VisualizationCard } from "./VisualizationCard";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
type HomeProps = { type HomeProps = {
visualizations: any; visualizations: any;
showWelcome?: boolean;
}; };
export const Home: FC<HomeProps> = ({ visualizations = [] }) => { export const Home: FC<HomeProps> = ({ visualizations = [], showWelcome = true }) => {
const router = useRouter(); const router = useRouter();
const pathname = usePathname() ?? ""; const pathname = usePathname() ?? "";
const cookieName = "homeIntroComplete"; const cookieName = "homeIntroComplete";
@ -25,7 +26,7 @@ export const Home: FC<HomeProps> = ({ visualizations = [] }) => {
const { const {
colors: { white, leafcutterElectricBlue }, colors: { white, leafcutterElectricBlue },
typography: { h4 }, typography: { h4 },
} = useAppContext(); } = useLeafcutterContext();
const homeIntroComplete = parseInt(cookies[cookieName], 10) || 0; const homeIntroComplete = parseInt(cookies[cookieName], 10) || 0;
useEffect(() => { useEffect(() => {
@ -37,14 +38,14 @@ export const Home: FC<HomeProps> = ({ visualizations = [] }) => {
return ( return (
<> <>
<Welcome /> {showWelcome && <Welcome />}
<Grid <Grid
container container
spacing={3} spacing={3}
sx={{ pt: "22px", pb: "22px" }} sx={{ pt: "22px", pb: "22px" }}
direction="row-reverse" direction="row-reverse"
> >
<Link href="/create" passHref> <Link href={`${process.env.LEAFCUTTER_BASE_PATH ?? ""}/create`} passHref>
<Button <Button
sx={{ sx={{
fontSize: 14, fontSize: 14,

View file

@ -8,7 +8,7 @@ import {
useState, useState,
PropsWithChildren, PropsWithChildren,
} from "react"; } from "react";
import { colors, typography } from "leafcutter-common/styles/theme"; import { colors, typography } from "../styles/theme";
const basePath = process.env.GITLAB_CI const basePath = process.env.GITLAB_CI
? "/link/link-stack/apps/leafcutter" ? "/link/link-stack/apps/leafcutter"
@ -16,10 +16,12 @@ const basePath = process.env.GITLAB_CI
const imageURL = (image: any) => const imageURL = (image: any) =>
typeof image === "string" ? `${basePath}${image}` : `${basePath}${image.src}`; typeof image === "string" ? `${basePath}${image}` : `${basePath}${image.src}`;
const AppContext = createContext({ const LeafcutterContext = createContext({
colors, colors,
typography, typography,
imageURL, imageURL,
datasource: "leafcutter",
setDatasource: null as any,
query: null as any, query: null as any,
updateQuery: null as any, updateQuery: null as any,
updateQueryType: null as any, updateQueryType: null as any,
@ -29,7 +31,7 @@ const AppContext = createContext({
setFoundCount: null as any, setFoundCount: null as any,
}); });
export const AppProvider: FC<PropsWithChildren> = ({ children }) => { export const LeafcutterProvider: FC<PropsWithChildren> = ({ children }) => {
const initialState = { const initialState = {
incidentType: { incidentType: {
display: "Incident Type", display: "Incident Type",
@ -134,14 +136,17 @@ export const AppProvider: FC<PropsWithChildren> = ({ children }) => {
const replaceQuery = (payload: any) => dispatch({ type: "REPLACE", payload }); const replaceQuery = (payload: any) => dispatch({ type: "REPLACE", payload });
const clearQuery = () => dispatch({ type: "CLEAR" }); const clearQuery = () => dispatch({ type: "CLEAR" });
const [foundCount, setFoundCount] = useState(0); const [foundCount, setFoundCount] = useState(0);
const [datasource, setDatasource] = useState("leafcutter");
return ( return (
<AppContext.Provider <LeafcutterContext.Provider
// eslint-disable-next-line react/jsx-no-constructed-context-values // eslint-disable-next-line react/jsx-no-constructed-context-values
value={{ value={{
colors, colors,
typography, typography,
imageURL, imageURL,
datasource,
setDatasource,
query, query,
updateQuery, updateQuery,
updateQueryType, updateQueryType,
@ -152,10 +157,10 @@ export const AppProvider: FC<PropsWithChildren> = ({ children }) => {
}} }}
> >
{children} {children}
</AppContext.Provider> </LeafcutterContext.Provider>
); );
}; };
export function useAppContext() { export function useLeafcutterContext() {
return useContext(AppContext); return useContext(LeafcutterContext);
} }

View file

@ -0,0 +1,21 @@
"use client";
import { FC, useEffect, useState } from "react";
import { useLeafcutterContext } from "./LeafcutterProvider";
import { RawDataViewer } from "./RawDataViewer";
import { searchVisualizationsAction } from "../actions/visualizations";
export const LiveDataViewer: FC = () => {
const { query, setFoundCount, datasource } = useLeafcutterContext();
const [rows, setRows] = useState<any[]>([]);
useEffect(() => {
const fetchData = async () => {
const result = await searchVisualizationsAction(datasource, query);
setRows(result);
setFoundCount(result?.length ?? 0);
};
fetchData();
}, [query, setFoundCount, datasource]);
return <RawDataViewer rows={rows} height={350} />;
};

View file

@ -11,7 +11,7 @@ import {
Public as PublicIcon, Public as PublicIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { VisualizationDetailDialog } from "./VisualizationDetailDialog"; import { VisualizationDetailDialog } from "./VisualizationDetailDialog";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface MetricSelectCardProps { interface MetricSelectCardProps {
visualizationID: string; visualizationID: string;
@ -35,7 +35,7 @@ export const MetricSelectCard: FC<MetricSelectCardProps> = ({
typography: { small }, typography: { small },
colors: { white, leafcutterElectricBlue, cdrLinkOrange }, colors: { white, leafcutterElectricBlue, cdrLinkOrange },
query, query,
} = useAppContext(); } = useLeafcutterContext();
/* const images = { /* const images = {
actor: PrivacyTipIcon, actor: PrivacyTipIcon,
incidenttype: PrivacyTipIcon, incidenttype: PrivacyTipIcon,

View file

@ -34,7 +34,7 @@ export const OpenSearchWrapper: FC<OpenSearchWrapperProps> = ({
> >
<Iframe <Iframe
id="opensearch" id="opensearch"
url={`/opensearch/${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`} url={`/dashboards/${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`}
width="100%" width="100%"
height="100%" height="100%"
frameBorder={0} frameBorder={0}

View file

@ -3,7 +3,7 @@
/* eslint-disable react/require-default-props */ /* eslint-disable react/require-default-props */
import { FC, PropsWithChildren } from "react"; import { FC, PropsWithChildren } from "react";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
type PageHeaderProps = PropsWithChildren<{ type PageHeaderProps = PropsWithChildren<{
backgroundColor: string; backgroundColor: string;
@ -17,7 +17,7 @@ export const PageHeader: FC<PageHeaderProps> = ({
}: any) => { }: any) => {
const { const {
colors: { white }, colors: { white },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Box <Box

View file

@ -21,7 +21,7 @@ import taxonomy from "../config/taxonomy.json";
import { QueryBuilderSection } from "./QueryBuilderSection"; import { QueryBuilderSection } from "./QueryBuilderSection";
import { QueryListSelector } from "./QueryListSelector"; import { QueryListSelector } from "./QueryListSelector";
import { QueryDateRangeSelector } from "./QueryDateRangeSelector"; import { QueryDateRangeSelector } from "./QueryDateRangeSelector";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
import { Tooltip } from "./Tooltip"; import { Tooltip } from "./Tooltip";
interface QueryBuilderProps {} interface QueryBuilderProps {}
@ -32,7 +32,7 @@ export const QueryBuilder: FC<QueryBuilderProps> = () => {
const { const {
typography: { p }, typography: { p },
colors: { leafcutterElectricBlue, mediumGray, almostBlack }, colors: { leafcutterElectricBlue, mediumGray, almostBlack },
} = useAppContext(); } = useLeafcutterContext();
const openAdvancedOptions = () => { const openAdvancedOptions = () => {
setDialogOpen(false); setDialogOpen(false);

View file

@ -17,7 +17,7 @@ import {
ExpandMore as ExpandMoreIcon, ExpandMore as ExpandMoreIcon,
Help as HelpIcon, Help as HelpIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface QueryBuilderSectionProps { interface QueryBuilderSectionProps {
name: string; name: string;
@ -42,7 +42,7 @@ const Tooltip: FC<TooltipProps> = ({ title, description, children, open }) => {
const { const {
colors: { white, leafcutterElectricBlue, almostBlack }, colors: { white, leafcutterElectricBlue, almostBlack },
typography: { h5, small }, typography: { h5, small },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<MUITooltip <MUITooltip
@ -108,7 +108,7 @@ export const QueryBuilderSection: FC<QueryBuilderSectionProps> = ({
colors: { white, leafcutterElectricBlue, warningPink, almostBlack }, colors: { white, leafcutterElectricBlue, warningPink, almostBlack },
typography: { h6, small }, typography: { h6, small },
updateQueryType, updateQueryType,
} = useAppContext(); } = useLeafcutterContext();
const updateType = (type: string) => { const updateType = (type: string) => {
setQueryType(type); setQueryType(type);
updateQueryType({ updateQueryType({

View file

@ -4,7 +4,7 @@ import { FC, useState, useEffect } from "react";
import { Box, Grid, TextField, Select, MenuItem } from "@mui/material"; import { Box, Grid, TextField, Select, MenuItem } from "@mui/material";
import { DatePicker } from "@mui/x-date-pickers-pro"; import { DatePicker } from "@mui/x-date-pickers-pro";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface QueryDateRangeSelectorProps {} interface QueryDateRangeSelectorProps {}
@ -13,7 +13,7 @@ export const QueryDateRangeSelector: FC<QueryDateRangeSelectorProps> = () => {
const [relativeDate, setRelativeDate] = useState(""); const [relativeDate, setRelativeDate] = useState("");
const [startDate, setStartDate] = useState(null); const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null); const [endDate, setEndDate] = useState(null);
const { updateQuery, query } = useAppContext(); const { updateQuery, query } = useLeafcutterContext();
useEffect(() => { useEffect(() => {
if (!query) return; if (!query) return;
setStartDate(query.startDate.values[0] ?? null); setStartDate(query.startDate.values[0] ?? null);

View file

@ -3,7 +3,7 @@
import { FC, useState, useEffect } from "react"; import { FC, useState, useEffect } from "react";
import { Box, Grid, Tooltip } from "@mui/material"; import { Box, Grid, Tooltip } from "@mui/material";
import { DataGridPro, GridColDef } from "@mui/x-data-grid-pro"; import { DataGridPro, GridColDef } from "@mui/x-data-grid-pro";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface QueryListSelectorProps { interface QueryListSelectorProps {
title: string; title: string;
@ -24,7 +24,7 @@ export const QueryListSelector: FC<QueryListSelectorProps> = ({
typography: { small }, typography: { small },
query, query,
updateQuery, updateQuery,
} = useAppContext(); } = useLeafcutterContext();
const isExclude = query?.[keyName]?.queryType === "exclude"; const isExclude = query?.[keyName]?.queryType === "exclude";
const columns: GridColDef[] = [ const columns: GridColDef[] = [
{ {

View file

@ -5,14 +5,14 @@ import { Box, Grid } from "@mui/material";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import taxonomy from "../config/taxonomy.json"; import taxonomy from "../config/taxonomy.json";
import { colors } from "../styles/theme"; import { colors } from "../styles/theme";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
export const QueryText: FC = () => { export const QueryText: FC = () => {
const t = useTranslate(); const t = useTranslate();
const { const {
typography: { h6 }, typography: { h6 },
query: q, query: q,
} = useAppContext(); } = useLeafcutterContext();
const displayNames: any = { const displayNames: any = {
incidentType: t("incidentType"), incidentType: t("incidentType"),

View file

@ -14,7 +14,7 @@ import {
ExpandMore as ExpandMoreIcon, ExpandMore as ExpandMoreIcon,
Circle as CircleIcon, Circle as CircleIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface QuestionProps { interface QuestionProps {
question: string; question: string;
@ -26,7 +26,7 @@ export const Question: FC<QuestionProps> = ({ question, answer }) => {
const { const {
colors: { lavender, darkLavender }, colors: { lavender, darkLavender },
typography: { h5, p }, typography: { h5, p },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Accordion <Accordion

View file

@ -1,6 +1,7 @@
"use client"; "use client";
import { FC } from "react"; import { FC } from "react";
import { useRouter } from "next/navigation";
import { Box, Grid } from "@mui/material"; import { Box, Grid } from "@mui/material";
import { DataGridPro } from "@mui/x-data-grid-pro"; import { DataGridPro } from "@mui/x-data-grid-pro";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
@ -12,14 +13,23 @@ interface RawDataViewerProps {
export const RawDataViewer: FC<RawDataViewerProps> = ({ rows, height }) => { export const RawDataViewer: FC<RawDataViewerProps> = ({ rows, height }) => {
const t = useTranslate(); const t = useTranslate();
const router = useRouter();
const columns = [ const columns = [
{ {
field: "date", field: "open_date",
headerName: t("date"), headerName: "Open Date", //t("date"),
editable: false, editable: false,
flex: 0.7, flex: 0.7,
valueFormatter: ({ value }: any) => new Date(value).toLocaleDateString(), valueFormatter: ({ value }: any) => new Date(value).toLocaleDateString(),
}, },
{
field: "close_date",
headerName: "Close Date", // t("date"),
editable: false,
flex: 0.7,
valueFormatter: ({ value }: any) => new Date(value).toLocaleDateString(),
},
{ {
field: "incident", field: "incident",
headerName: t("incident"), headerName: t("incident"),
@ -77,6 +87,7 @@ export const RawDataViewer: FC<RawDataViewerProps> = ({ rows, height }) => {
disableColumnMenu disableColumnMenu
scrollbarSize={10} scrollbarSize={10}
disableVirtualization disableVirtualization
onCellClick={(e) => router.push("/tickets/" + e.row.id)}
/> />
</Grid> </Grid>
</Grid> </Grid>

View file

@ -12,7 +12,7 @@ import {
} from "@mui/material"; } from "@mui/material";
import { Close as CloseIcon } from "@mui/icons-material"; import { Close as CloseIcon } from "@mui/icons-material";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface TooltipProps { interface TooltipProps {
title: string; title: string;
@ -38,7 +38,7 @@ export const Tooltip: FC<TooltipProps> = ({
const { const {
typography: { p, small }, typography: { p, small },
colors: { white, leafcutterElectricBlue, almostBlack }, colors: { white, leafcutterElectricBlue, almostBlack },
} = useAppContext(); } = useLeafcutterContext();
const router = useRouter(); const router = useRouter();
const pathname = usePathname() ?? ""; const pathname = usePathname() ?? "";
const searchParams = useSearchParams(); const searchParams = useSearchParams();

View file

@ -5,7 +5,7 @@ import { Grid, Box } from "@mui/material";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { PageHeader } from "./PageHeader"; import { PageHeader } from "./PageHeader";
import { VisualizationCard } from "./VisualizationCard"; import { VisualizationCard } from "./VisualizationCard";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
type TrendsProps = { type TrendsProps = {
visualizations: any; visualizations: any;
@ -16,7 +16,7 @@ export const Trends: FC<TrendsProps> = ({ visualizations }) => {
const { const {
colors: { cdrLinkOrange }, colors: { cdrLinkOrange },
typography: { h1, h4, p }, typography: { h1, h4, p },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<> <>

View file

@ -32,7 +32,7 @@ import { Tooltip } from "./Tooltip";
import visualizationMap from "../config/visualizationMap.json"; import visualizationMap from "../config/visualizationMap.json";
import { VisualizationSelectCard } from "./VisualizationSelectCard"; import { VisualizationSelectCard } from "./VisualizationSelectCard";
import { MetricSelectCard } from "./MetricSelectCard"; import { MetricSelectCard } from "./MetricSelectCard";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface VisualizationBuilderProps { interface VisualizationBuilderProps {
templates: any[]; templates: any[];
@ -49,7 +49,9 @@ export const VisualizationBuilder: FC<VisualizationBuilderProps> = ({
query, query,
replaceQuery, replaceQuery,
clearQuery, clearQuery,
} = useAppContext(); datasource,
setDatasource,
} = useLeafcutterContext();
const { visualizations } = visualizationMap; const { visualizations } = visualizationMap;
const [selectedVisualizationType, setSelectedVisualizationType] = useState( const [selectedVisualizationType, setSelectedVisualizationType] = useState(
null as any, null as any,
@ -194,6 +196,24 @@ export const VisualizationBuilder: FC<VisualizationBuilderProps> = ({
</Tooltip> </Tooltip>
</Grid> </Grid>
<Grid item> <Grid item>
<Button
variant="contained"
onClick={() =>
setDatasource(
datasource === "leafcutter" ? "zammad" : "leafcutter",
)
}
sx={{
backgroundColor: cdrLinkOrange,
textTransform: "none",
fontStyle: "italic",
fontWeight: "bold",
mr: 2,
}}
>
{datasource === "zammad" ? "Switch to Global" : "Switch to Local"}
</Button>
<Button <Button
aria-describedby={elementID} aria-describedby={elementID}
variant="contained" variant="contained"

View file

@ -3,7 +3,7 @@
import { FC, useState } from "react"; import { FC, useState } from "react";
import { Grid, Card, Box } from "@mui/material"; import { Grid, Card, Box } from "@mui/material";
import Iframe from "react-iframe"; import Iframe from "react-iframe";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
import { VisualizationDetailDialog } from "./VisualizationDetailDialog"; import { VisualizationDetailDialog } from "./VisualizationDetailDialog";
interface VisualizationCardProps { interface VisualizationCardProps {
@ -24,7 +24,7 @@ export const VisualizationCard: FC<VisualizationCardProps> = ({
const { const {
typography: { h4, p }, typography: { h4, p },
colors: { leafcutterLightBlue, leafcutterElectricBlue }, colors: { leafcutterLightBlue, leafcutterElectricBlue },
} = useAppContext(); } = useLeafcutterContext();
const finalURL = `${process.env.NEXT_PUBLIC_LEAFCUTTER_URL}${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`; const finalURL = `${process.env.NEXT_PUBLIC_LEAFCUTTER_URL}${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`;
return ( return (

View file

@ -3,7 +3,7 @@
import { FC } from "react"; import { FC } from "react";
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import Iframe from "react-iframe"; import Iframe from "react-iframe";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface VisualizationDetailProps { interface VisualizationDetailProps {
id: string; id: string;
@ -23,7 +23,7 @@ export const VisualizationDetail: FC<VisualizationDetailProps> = ({
const { const {
colors: { mediumGray }, colors: { mediumGray },
typography: { h4, p }, typography: { h4, p },
} = useAppContext(); } = useLeafcutterContext();
const finalURL = `${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`; const finalURL = `${url}&_g=(filters%3A!()%2CrefreshInterval%3A(pause%3A!t%2Cvalue%3A0)%2Ctime%3A(from%3Anow-3y%2Cto%3Anow))`;
console.log({ finalURL }); console.log({ finalURL });
return ( return (

View file

@ -11,7 +11,7 @@ import {
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
import { VisualizationDetail } from "./VisualizationDetail"; import { VisualizationDetail } from "./VisualizationDetail";
interface VisualizationDetailDialogProps { interface VisualizationDetailDialogProps {
@ -37,7 +37,7 @@ export const VisualizationDetailDialog: FC<VisualizationDetailDialogProps> = ({
const { const {
colors: { leafcutterElectricBlue, leafcutterLightBlue, white, almostBlack }, colors: { leafcutterElectricBlue, leafcutterLightBlue, white, almostBlack },
query, query,
} = useAppContext(); } = useLeafcutterContext();
const deleteAndClose = async () => { const deleteAndClose = async () => {
await fetch(`/api/visualizations/delete`, { await fetch(`/api/visualizations/delete`, {

View file

@ -13,7 +13,7 @@ import lineStacked from "../images/line-stacked.svg";
import dataTable from "../images/data-table.svg"; import dataTable from "../images/data-table.svg";
import metric from "../images/metric.svg"; import metric from "../images/metric.svg";
import tagCloud from "../images/tag-cloud.svg"; import tagCloud from "../images/tag-cloud.svg";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
interface VisualizationSelectCardProps { interface VisualizationSelectCardProps {
visualizationType: string; visualizationType: string;
@ -38,7 +38,7 @@ export const VisualizationSelectCard: FC<VisualizationSelectCardProps> = ({
leafcutterLightBlue, leafcutterLightBlue,
cdrLinkOrange, cdrLinkOrange,
}, },
} = useAppContext(); } = useLeafcutterContext();
const images: any = { const images: any = {
horizontalBar, horizontalBar,
horizontalBarStacked, horizontalBarStacked,

View file

@ -3,7 +3,7 @@
import { Box, Grid } from "@mui/material"; import { Box, Grid } from "@mui/material";
import { useSession } from "next-auth/react"; import { useSession } from "next-auth/react";
import { useTranslate } from "react-polyglot"; import { useTranslate } from "react-polyglot";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
export const Welcome = () => { export const Welcome = () => {
const t = useTranslate(); const t = useTranslate();
@ -17,7 +17,7 @@ export const Welcome = () => {
const { const {
colors: { white, leafcutterElectricBlue }, colors: { white, leafcutterElectricBlue },
typography: { h1, h4, p }, typography: { h1, h4, p },
} = useAppContext(); } = useLeafcutterContext();
return ( return (
<Box <Box

View file

@ -4,7 +4,7 @@ import { Box, Grid, Dialog, Button } from "@mui/material";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
// import { useSession } from "next-auth/react"; // import { useSession } from "next-auth/react";
// import { useTranslate } from "react-polyglot"; // import { useTranslate } from "react-polyglot";
import { useAppContext } from "./AppProvider"; import { useLeafcutterContext } from "./LeafcutterProvider";
export const WelcomeDialog = () => { export const WelcomeDialog = () => {
// const t = useTranslate(); // const t = useTranslate();
@ -15,7 +15,7 @@ export const WelcomeDialog = () => {
const { const {
colors: { white, leafcutterElectricBlue }, colors: { white, leafcutterElectricBlue },
typography: { h1, h6, p }, typography: { h1, h6, p },
} = useAppContext(); } = useLeafcutterContext();
const activeTooltip = searchParams?.get("tooltip")?.toString(); const activeTooltip = searchParams?.get("tooltip")?.toString();
const open = activeTooltip === "welcome"; const open = activeTooltip === "welcome";

Some files were not shown because too many files have changed in this diff Show more