Merge branch 'flatten' into 'develop'
Flatten See merge request digiresilience/link/link-stack!6
This commit is contained in:
commit
a9fb4317dc
264 changed files with 9983 additions and 2280 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { About } from './_components/About';
|
import { About } from "leafcutter-common";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <About />;
|
return <About />;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { getTemplates } from "app/_lib/opensearch";
|
import { getTemplates } from "app/_lib/opensearch";
|
||||||
import { Create } from "./_components/Create";
|
import { Create } from "leafcutter-common";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const templates = await getTemplates(100);
|
const templates = await getTemplates(100);
|
||||||
|
|
||||||
return <Create templates={templates} />;
|
return <Create templates={templates} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { FAQ } from "./_components/FAQ";
|
import { FAQ } from "leafcutter-common";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <FAQ />;
|
return <FAQ />;
|
||||||
|
|
|
||||||
|
|
@ -7,16 +7,12 @@ import "@fontsource/roboto/700.css";
|
||||||
import "@fontsource/playfair-display/900.css";
|
import "@fontsource/playfair-display/900.css";
|
||||||
// import getConfig from "next/config";
|
// import getConfig from "next/config";
|
||||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||||
import { InternalLayout } from "app/_components/InternalLayout";
|
import { InternalLayout } from "../_components/InternalLayout";
|
||||||
import { headers } from 'next/headers'
|
|
||||||
|
|
||||||
type LayoutProps = {
|
type LayoutProps = {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Layout({ children }: LayoutProps) {
|
export default function Layout({ children }: LayoutProps) {
|
||||||
const allHeaders = headers();
|
return <InternalLayout embedded={false}>{children}</InternalLayout>;
|
||||||
const embedded = Boolean(allHeaders.get('x-leafcutter-embedded'));
|
|
||||||
|
|
||||||
return <InternalLayout embedded={embedded}>{children}</InternalLayout>;
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 "app/_components/Home";
|
import { Home } from "leafcutter-common";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const session = await getServerSession(authOptions);
|
const session = await getServerSession(authOptions);
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
/* 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 "./_components/Preview";
|
import { Preview } from "leafcutter-common";
|
||||||
// import { createVisualization } from "lib/opensearch";
|
// import { createVisualization } from "lib/opensearch";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <Preview visualization={undefined} visualizationType={""} data={[]}/>;
|
return <Preview visualization={undefined} visualizationType={""} data={[]} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ 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 "app/_components/AppProvider";
|
import { useAppContext } from "../../../_components/AppProvider";
|
||||||
|
|
||||||
export const Setup: FC = () => {
|
export const Setup: FC = () => {
|
||||||
const {
|
const {
|
||||||
|
|
@ -20,6 +20,7 @@ export const Setup: FC = () => {
|
||||||
<Grid
|
<Grid
|
||||||
sx={{ width: "100%", height: 700 }}
|
sx={{ width: "100%", height: 700 }}
|
||||||
direction="row"
|
direction="row"
|
||||||
|
container
|
||||||
justifyContent="space-around"
|
justifyContent="space-around"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
alignContent="center"
|
alignContent="center"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { Setup } from './_components/Setup';
|
import { Setup } from "./_components/Setup";
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
return <Setup />;
|
return <Setup />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { getTrends } from "app/_lib/opensearch";
|
import { getTrends } from "app/_lib/opensearch";
|
||||||
import { Trends } from "./_components/Trends";
|
import { Trends } from "../../../../../packages/leafcutter-common/components/Trends";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
const visualizations = await getTrends(25);
|
const visualizations = await getTrends(25);
|
||||||
|
|
||||||
return <Trends visualizations={visualizations} />;
|
return <Trends visualizations={visualizations} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
|
||||||
|
|
@ -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 "app/_components/VisualizationDetail";
|
import { VisualizationDetail } from "leafcutter-common";
|
||||||
|
|
||||||
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}`;
|
||||||
|
|
@ -18,7 +18,7 @@ const getVisualization = async (visualizationID: string) => {
|
||||||
const response = rawResponse.body;
|
const response = rawResponse.body;
|
||||||
|
|
||||||
const hits = response.hits.hits.filter(
|
const hits = response.hits.hits.filter(
|
||||||
(hit: any) => hit._id.split(":")[1] === visualizationID[0]
|
(hit: any) => hit._id.split(":")[1] === visualizationID[0],
|
||||||
);
|
);
|
||||||
const hit = hits[0];
|
const hit = hits[0];
|
||||||
const visualization = {
|
const visualization = {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
useState,
|
useState,
|
||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { colors, typography } from "app/_styles/theme";
|
import { colors, typography } from "leafcutter-common/styles/theme";
|
||||||
|
|
||||||
const basePath = process.env.GITLAB_CI
|
const basePath = process.env.GITLAB_CI
|
||||||
? "/link/link-stack/apps/leafcutter"
|
? "/link/link-stack/apps/leafcutter"
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ 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 "./GettingStartedDialog";
|
import { GettingStartedDialog } from "leafcutter-common";
|
||||||
import { useAppContext } from "./AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
// import { Footer } from "./Footer";
|
// import { Footer } from "./Footer";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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/AdapterDateFns";
|
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
|
||||||
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
||||||
import { AppProvider } from "app/_components/AppProvider";
|
import { AppProvider } from "./AppProvider";
|
||||||
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
|
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
|
||||||
import en from "app/_locales/en.json";
|
import en from "leafcutter-common/locales/en.json";
|
||||||
import fr from "app/_locales/fr.json";
|
import fr from "leafcutter-common/locales/fr.json";
|
||||||
import "@fontsource/poppins/400.css";
|
import "@fontsource/poppins/400.css";
|
||||||
import "@fontsource/poppins/700.css";
|
import "@fontsource/poppins/700.css";
|
||||||
import "@fontsource/roboto/400.css";
|
import "@fontsource/roboto/400.css";
|
||||||
|
|
@ -21,7 +21,7 @@ import "app/_styles/global.css";
|
||||||
import { LicenseInfo } from "@mui/x-date-pickers-pro";
|
import { LicenseInfo } from "@mui/x-date-pickers-pro";
|
||||||
|
|
||||||
LicenseInfo.setLicenseKey(
|
LicenseInfo.setLicenseKey(
|
||||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI="
|
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||||
);
|
);
|
||||||
|
|
||||||
const messages: any = { en, fr };
|
const messages: any = { en, fr };
|
||||||
|
|
|
||||||
|
|
@ -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 "app/_components/AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
import { Tooltip } from "app/_components/Tooltip";
|
import { Tooltip } from "leafcutter-common";
|
||||||
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
|
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
|
||||||
|
|
||||||
const MenuItem = ({
|
const MenuItem = ({
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import Image from "next/legacy/image";
|
||||||
import { AppBar, Grid, Box } from "@mui/material";
|
import { AppBar, Grid, Box } from "@mui/material";
|
||||||
import { useTranslate } from "react-polyglot";
|
import { useTranslate } from "react-polyglot";
|
||||||
import LeafcutterLogo from "images/leafcutter-logo.png";
|
import LeafcutterLogo from "images/leafcutter-logo.png";
|
||||||
import { AccountButton } from "app/_components/AccountButton";
|
import { AccountButton } from "./AccountButton";
|
||||||
import { HelpButton } from "app/_components/HelpButton";
|
import { HelpButton } from "./HelpButton";
|
||||||
import { Tooltip } from "app/_components/Tooltip";
|
import { Tooltip } from "leafcutter-common";
|
||||||
import { useAppContext } from "./AppProvider";
|
import { useAppContext } from "./AppProvider";
|
||||||
// import { LanguageSelect } from "./LanguageSelect";
|
// import { LanguageSelect } from "./LanguageSelect";
|
||||||
|
|
||||||
|
|
@ -43,50 +43,51 @@ export const TopNav: FC = () => {
|
||||||
wrap="nowrap"
|
wrap="nowrap"
|
||||||
spacing={4}
|
spacing={4}
|
||||||
>
|
>
|
||||||
<Link href="/" passHref>
|
<Grid
|
||||||
<Grid
|
item
|
||||||
item
|
container
|
||||||
container
|
direction="row"
|
||||||
direction="row"
|
justifyContent="flex-start"
|
||||||
justifyContent="flex-start"
|
alignItems="center"
|
||||||
spacing={1}
|
alignContent="center"
|
||||||
wrap="nowrap"
|
spacing={1}
|
||||||
sx={{ cursor: "pointer" }}
|
wrap="nowrap"
|
||||||
>
|
sx={{ cursor: "pointer" }}
|
||||||
<Grid item sx={{ pr: 1 }}>
|
>
|
||||||
<Image src={LeafcutterLogo} alt="" width={56} height={52} />
|
<Grid item sx={{ pr: 1 }}>
|
||||||
|
<Image src={LeafcutterLogo} alt="" width={56} height={52} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item container direction="column" alignContent="flex-start">
|
||||||
|
<Grid item sx={{ mt: -1 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
...h5,
|
||||||
|
color: leafcutterElectricBlue,
|
||||||
|
p: 0,
|
||||||
|
m: 0,
|
||||||
|
pt: 1,
|
||||||
|
textAlign: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Leafcutter
|
||||||
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item container direction="column" alignContent="flex-start">
|
<Grid item>
|
||||||
<Grid item>
|
<Box
|
||||||
<Box
|
sx={{
|
||||||
sx={{
|
...h6,
|
||||||
...h5,
|
m: 0,
|
||||||
color: leafcutterElectricBlue,
|
p: 0,
|
||||||
p: 0,
|
color: cdrLinkOrange,
|
||||||
m: 0,
|
textAlign: "left",
|
||||||
pt: 1,
|
}}
|
||||||
textAlign: "left",
|
>
|
||||||
}}
|
A Project of Center for Digital Resilience
|
||||||
>
|
</Box>
|
||||||
Leafcutter
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
...h6,
|
|
||||||
m: 0,
|
|
||||||
p: 0,
|
|
||||||
color: cdrLinkOrange,
|
|
||||||
textAlign: "left",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
A Project of Center for Digital Resilience
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Link>
|
</Grid>
|
||||||
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<HelpButton />
|
<HelpButton />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import type { NextAuthOptions } from "next-auth";
|
import type { NextAuthOptions } from "next-auth";
|
||||||
import Google from "next-auth/providers/google";
|
import Google from "next-auth/providers/google";
|
||||||
import Apple from "next-auth/providers/apple";
|
import Apple from "next-auth/providers/apple";
|
||||||
|
import Credentials from "next-auth/providers/credentials";
|
||||||
|
import { checkAuth } from "./opensearch";
|
||||||
|
|
||||||
export const authOptions: NextAuthOptions = {
|
export const authOptions: NextAuthOptions = {
|
||||||
|
pages: {
|
||||||
|
signIn: "/login",
|
||||||
|
error: "/login",
|
||||||
|
signOut: "/logout",
|
||||||
|
},
|
||||||
providers: [
|
providers: [
|
||||||
Google({
|
Google({
|
||||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||||
|
|
@ -12,6 +19,62 @@ export const authOptions: NextAuthOptions = {
|
||||||
clientId: process.env.APPLE_CLIENT_ID ?? "",
|
clientId: process.env.APPLE_CLIENT_ID ?? "",
|
||||||
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
|
clientSecret: process.env.APPLE_CLIENT_SECRET ?? "",
|
||||||
}),
|
}),
|
||||||
|
Credentials({
|
||||||
|
name: "Link",
|
||||||
|
credentials: {
|
||||||
|
authToken: { label: "AuthToken", type: "text", },
|
||||||
|
},
|
||||||
|
async authorize(credentials, req) {
|
||||||
|
const { headers } = req;
|
||||||
|
console.log({ headers });
|
||||||
|
const leafcutterUser = headers?.["x-leafcutter-user"];
|
||||||
|
const authToken = credentials?.authToken;
|
||||||
|
|
||||||
|
if (!leafcutterUser || leafcutterUser.trim() === "") {
|
||||||
|
console.log("no leafcutter user");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ authToken });
|
||||||
|
return null;
|
||||||
|
/*
|
||||||
|
try {
|
||||||
|
// add role check
|
||||||
|
await checkAuth(username, password);
|
||||||
|
const user = {
|
||||||
|
id: leafcutterUser,
|
||||||
|
email: leafcutterUser
|
||||||
|
};
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch (e) {
|
||||||
|
console.log({ e });
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
],
|
],
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
/*
|
||||||
|
callbacks: {
|
||||||
|
signIn: async ({ user, account, profile }) => {
|
||||||
|
const roles: any = [];
|
||||||
|
return roles.includes("admin") || roles.includes("agent");
|
||||||
|
},
|
||||||
|
session: async ({ session, user, token }) => {
|
||||||
|
// @ts-ignore
|
||||||
|
session.user.roles = token.roles;
|
||||||
|
return session;
|
||||||
|
},
|
||||||
|
jwt: async ({ token, user, account, profile, trigger }) => {
|
||||||
|
if (user) {
|
||||||
|
token.roles = [];
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
},*/
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,24 @@ const createClient = () => new Client({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createUserClient = (username: string, password: string) => new Client({
|
||||||
|
node: baseURL,
|
||||||
|
auth: {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
},
|
||||||
|
ssl: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const checkAuth = async (username: string, password: string) => {
|
||||||
|
const client = createUserClient(username, password);
|
||||||
|
const res = await client.ping();
|
||||||
|
|
||||||
|
return res.statusCode === 200;
|
||||||
|
};
|
||||||
|
|
||||||
const getDocumentID = (doc: any) => doc._id.split(":")[1];
|
const getDocumentID = (doc: any) => doc._id.split(":")[1];
|
||||||
|
|
||||||
const getEmbedURL = (tenant: string, visualizationID: string) =>
|
const getEmbedURL = (tenant: string, visualizationID: string) =>
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,7 @@ body {
|
||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
text-size-adjust: none;
|
text-size-adjust: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
|
||||||
15
apps/leafcutter/app/api/link/auth/route.ts
Normal file
15
apps/leafcutter/app/api/link/auth/route.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export const GET = async (req: NextRequest) => {
|
||||||
|
const validDomains = "localhost";
|
||||||
|
console.log({ req });
|
||||||
|
|
||||||
|
return NextResponse.json({ response: "ok" });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const POST = async (req: NextRequest) => {
|
||||||
|
const validDomains = "localhost";
|
||||||
|
console.log({ req });
|
||||||
|
|
||||||
|
return NextResponse.json({ response: "ok" });
|
||||||
|
};
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
|
|
||||||
const withAuthInfo =
|
|
||||||
(handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session: any = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
req.headers["x-proxy-user"] = session.email.toLowerCase();
|
|
||||||
req.headers["x-proxy-roles"] = "leafcutter_user";
|
|
||||||
const auth = `${session.email.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
|
|
||||||
const buff = Buffer.from(auth);
|
|
||||||
const base64data = buff.toString("base64");
|
|
||||||
req.headers.Authorization = `Basic ${base64data}`;
|
|
||||||
return handler(req, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
const proxy = createProxyMiddleware({
|
|
||||||
target: process.env.OPENSEARCH_DASHBOARDS_URL,
|
|
||||||
changeOrigin: true,
|
|
||||||
xfwd: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withAuthInfo(proxy);
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
api: {
|
|
||||||
bodyParser: false,
|
|
||||||
externalResolver: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -3,7 +3,9 @@ import { getTrends } from "app/_lib/opensearch";
|
||||||
|
|
||||||
export const GET = async () => {
|
export const GET = async () => {
|
||||||
const results = await getTrends(5);
|
const results = await getTrends(5);
|
||||||
|
console.log({ results });
|
||||||
|
|
||||||
NextResponse.json(results);
|
NextResponse.json(results);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
13
apps/leafcutter/app/api/visualizations/list/route.ts
Normal file
13
apps/leafcutter/app/api/visualizations/list/route.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import { getServerSession } from "next-auth";
|
||||||
|
import { authOptions } from "app/_lib/auth";
|
||||||
|
import { getUserVisualizations } from "app/_lib/opensearch";
|
||||||
|
|
||||||
|
export const GET = async () => {
|
||||||
|
const session = await getServerSession(authOptions);
|
||||||
|
const { user: { email } }: any = session;
|
||||||
|
const visualizations = await getUserVisualizations(email, 20);
|
||||||
|
|
||||||
|
return NextResponse.json(visualizations);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
@ -8,7 +8,7 @@ import "@fontsource/roboto/700.css";
|
||||||
import "@fontsource/playfair-display/900.css";
|
import "@fontsource/playfair-display/900.css";
|
||||||
// import getConfig from "next/config";
|
// import getConfig from "next/config";
|
||||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||||
import { MultiProvider } from "app/_components/MultiProvider";
|
import { MultiProvider } from "./_components/MultiProvider";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Leafcutter",
|
title: "Leafcutter",
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@ apiVersion: v2
|
||||||
name: leafcutter
|
name: leafcutter
|
||||||
description: A Helm chart for Kubernetes
|
description: A Helm chart for Kubernetes
|
||||||
type: application
|
type: application
|
||||||
version: 0.1.54
|
version: 0.2.0
|
||||||
appVersion: "0.1.54"
|
appVersion: "0.2.0"
|
||||||
|
|
|
||||||
|
|
@ -1,57 +1,6 @@
|
||||||
import { NextResponse } from "next/server";
|
import { withAuth } from "next-auth/middleware";
|
||||||
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(
|
export default withAuth(
|
||||||
checkRewrites,
|
|
||||||
{
|
{
|
||||||
pages: {
|
pages: {
|
||||||
signIn: `/login`,
|
signIn: `/login`,
|
||||||
|
|
@ -60,25 +9,30 @@ export default withAuth(
|
||||||
authorized: ({ token, req }) => {
|
authorized: ({ token, req }) => {
|
||||||
const {
|
const {
|
||||||
url,
|
url,
|
||||||
headers,
|
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
// check login page
|
|
||||||
const parsedURL = new URL(url);
|
const parsedURL = new URL(url);
|
||||||
if (parsedURL.pathname.startsWith('/login')) {
|
|
||||||
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check session auth
|
if (token?.email) {
|
||||||
const authorizedDomains = ["redaranj.com", "digiresilience.org"];
|
|
||||||
const userDomain = token?.email?.toLowerCase().split("@").pop() ?? "unauthorized.net";
|
|
||||||
|
|
||||||
if (authorizedDomains.includes(userDomain)) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: [
|
||||||
|
'/((?!api|app|bootstrap|3961|ui|translations|internal|login|node_modules|_next/static|_next/image|favicon.ico).*)',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,16 @@ const ContentSecurityPolicy = `
|
||||||
`;
|
`;
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicRuntimeConfig: {
|
transpilePackages: ["leafcutter-common"],
|
||||||
embedded: true
|
rewrites: async () => ({
|
||||||
},/*
|
fallback: [
|
||||||
|
{
|
||||||
|
source: "/:path*",
|
||||||
|
destination: "/api/proxy/:path*",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
/*
|
||||||
basePath: "/proxy/leafcutter",
|
basePath: "/proxy/leafcutter",
|
||||||
assetPrefix: "/proxy/leafcutter",
|
assetPrefix: "/proxy/leafcutter",
|
||||||
i18n: {
|
i18n: {
|
||||||
|
|
@ -17,25 +24,19 @@ module.exports = {
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
/* rewrites: async () => ({
|
|
||||||
fallback: [
|
/*
|
||||||
{
|
|
||||||
source: "/:path*",
|
|
||||||
destination: "/api/proxy/:path*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}) */
|
|
||||||
async headers() {
|
async headers() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
source: '/:path*',
|
source: '/:path*',
|
||||||
headers: [
|
headers: [
|
||||||
/*
|
|
||||||
{
|
{
|
||||||
key: 'Content-Security-Policy',
|
key: 'Content-Security-Policy',
|
||||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
|
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
|
||||||
},
|
},
|
||||||
*/
|
|
||||||
{
|
{
|
||||||
key: 'Strict-Transport-Security',
|
key: 'Strict-Transport-Security',
|
||||||
value: 'max-age=63072000; includeSubDomains; preload'
|
value: 'max-age=63072000; includeSubDomains; preload'
|
||||||
|
|
@ -52,4 +53,5 @@ module.exports = {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
"login": "aws sso login --sso-session cdr",
|
"login": "aws sso login --sso-session cdr",
|
||||||
"kubeconfig": "aws eks update-kubeconfig --name cdr-leafcutter-dashboard-cluster --profile cdr-leafcutter-dashboard-production",
|
"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: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",
|
"fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-mx4qq 5602:5601 --namespace leafcutter",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"export": "next export",
|
"export": "next export",
|
||||||
|
|
@ -21,44 +21,46 @@
|
||||||
"@fontsource/poppins": "^5.0.8",
|
"@fontsource/poppins": "^5.0.8",
|
||||||
"@fontsource/roboto": "^5.0.8",
|
"@fontsource/roboto": "^5.0.8",
|
||||||
"@mui/icons-material": "^5",
|
"@mui/icons-material": "^5",
|
||||||
"@mui/lab": "^5.0.0-alpha.138",
|
"@mui/lab": "^5.0.0-alpha.140",
|
||||||
"@mui/material": "^5",
|
"@mui/material": "^5",
|
||||||
"@mui/x-data-grid-pro": "^6.11.0",
|
"@mui/x-data-grid-pro": "^6.11.1",
|
||||||
"@mui/x-date-pickers-pro": "^6.11.0",
|
"@mui/x-date-pickers-pro": "^6.11.1",
|
||||||
"@opensearch-project/opensearch": "^2.3.1",
|
"@opensearch-project/opensearch": "^2.3.1",
|
||||||
|
"cryptr": "^6.2.0",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"http-proxy-middleware": "^2.0.6",
|
"http-proxy-middleware": "^2.0.6",
|
||||||
|
"leafcutter-common": "*",
|
||||||
"material-ui-popup-state": "^5.0.9",
|
"material-ui-popup-state": "^5.0.9",
|
||||||
"next": "13.4.13",
|
"next": "13.4.7",
|
||||||
"next-auth": "^4.22.4",
|
"next-auth": "^4.23.1",
|
||||||
"next-http-proxy-middleware": "^1.2.5",
|
"next-http-proxy-middleware": "^1.2.5",
|
||||||
"nodemailer": "^6.9.4",
|
"nodemailer": "^6.9.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-cookie": "^4.1.1",
|
"react-cookie": "^5.0.0",
|
||||||
"react-cookie-consent": "^8.0.1",
|
"react-cookie-consent": "^8.0.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-iframe": "^1.8.5",
|
"react-iframe": "^1.8.5",
|
||||||
"react-markdown": "^8.0.7",
|
"react-markdown": "^8.0.7",
|
||||||
"react-polyglot": "^0.7.2",
|
"react-polyglot": "^0.7.2",
|
||||||
"sharp": "^0.32.4",
|
"sharp": "^0.32.5",
|
||||||
"swr": "^2.2.0",
|
"swr": "^2.2.1",
|
||||||
"tss-react": "^4.8.8",
|
"tss-react": "^4.8.8",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.22.9",
|
"@babel/core": "^7.22.10",
|
||||||
"@types/node": "^20.4.8",
|
"@types/node": "^20.5.0",
|
||||||
"@types/react": "18.2.18",
|
"@types/react": "18.2.20",
|
||||||
"@types/uuid": "^9.0.2",
|
"@types/uuid": "^9.0.2",
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-next": "^13.4.13",
|
"eslint-config-next": "^13.4.16",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"eslint-plugin-import": "^2.28.0",
|
"eslint-plugin-import": "^2.28.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-react": "^7.33.1",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
66
apps/leafcutter/pages/api/proxy/[[...path]].ts
Normal file
66
apps/leafcutter/pages/api/proxy/[[...path]].ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
||||||
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { getToken } from "next-auth/jwt";
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
if (validDomains.includes(domain)) {
|
||||||
|
res.headers.set("Access-Control-Allow-Origin", origin);
|
||||||
|
res.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||||
|
res.headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
const withAuthInfo =
|
||||||
|
(handler: any) => async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session: any = await getToken({
|
||||||
|
req,
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
});
|
||||||
|
let email = session?.email?.toLowerCase();
|
||||||
|
|
||||||
|
const requestSignature = req.query.signature;
|
||||||
|
const url = new URL(req.headers.referer as string);
|
||||||
|
const referrerSignature = url.searchParams.get("signature");
|
||||||
|
|
||||||
|
console.log({ requestSignature, referrerSignature });
|
||||||
|
const isAppPath = !!req.url?.startsWith("/app");
|
||||||
|
const isResourcePath = !!req.url?.match(/\/(api|app|bootstrap|3961|ui|translations|internal|login|node_modules)/);
|
||||||
|
|
||||||
|
if (requestSignature && isAppPath) {
|
||||||
|
console.log("Has Signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (referrerSignature && isResourcePath) {
|
||||||
|
console.log("Has Signature");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
return res.status(401).json({ error: "Not authorized" });
|
||||||
|
}
|
||||||
|
|
||||||
|
req.headers["x-proxy-user"] = email;
|
||||||
|
req.headers["x-proxy-roles"] = "leafcutter_user";
|
||||||
|
const auth = `${email}:${process.env.OPENSEARCH_USER_PASSWORD}`;
|
||||||
|
const buff = Buffer.from(auth);
|
||||||
|
const base64data = buff.toString("base64");
|
||||||
|
req.headers.Authorization = `Basic ${base64data}`;
|
||||||
|
return handler(req, res);
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxy = createProxyMiddleware({
|
||||||
|
target: process.env.OPENSEARCH_DASHBOARDS_URL,
|
||||||
|
changeOrigin: true,
|
||||||
|
xfwd: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default withAuthInfo(proxy);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
externalResolver: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -24,6 +24,17 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"../../packages/leafcutter-common/components/Create.tsx",
|
||||||
|
"../../packages/leafcutter-common/components/About.tsx",
|
||||||
|
"../../packages/leafcutter-common/components/FAQ.tsx",
|
||||||
|
"../../packages/leafcutter-common/components/Trends.tsx",
|
||||||
|
"../../packages/leafcutter-common/components/Preview.tsx",
|
||||||
|
"app/(main)/setup/_components/Setup.tsx"
|
||||||
|
, "app/api/proxy/[[...path]]" ],
|
||||||
"exclude": ["node_modules", "babel__core"]
|
"exclude": ["node_modules", "babel__core"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,24 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC, useState } from "react";
|
||||||
import { Box, Grid, Container, IconButton } from "@mui/material";
|
import {
|
||||||
import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
|
Box,
|
||||||
|
Grid,
|
||||||
|
Container,
|
||||||
|
IconButton,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
Apple as AppleIcon,
|
||||||
|
Google as GoogleIcon,
|
||||||
|
Key as KeyIcon,
|
||||||
|
} from "@mui/icons-material";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import LinkLogo from "public/link-logo-small.png";
|
||||||
|
import { colors } from "app/_styles/theme";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
type LoginProps = {
|
type LoginProps = {
|
||||||
session: any;
|
session: any;
|
||||||
|
|
@ -14,62 +29,198 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
typeof window !== "undefined" && window.location.origin
|
typeof window !== "undefined" && window.location.origin
|
||||||
? window.location.origin
|
? window.location.origin
|
||||||
: "";
|
: "";
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [password, setPassword] = useState("");
|
||||||
|
const params = useSearchParams();
|
||||||
|
const error = params.get("error");
|
||||||
|
const { darkGray, cdrLinkOrange, white } = colors;
|
||||||
const buttonStyles = {
|
const buttonStyles = {
|
||||||
borderRadius: 500,
|
borderRadius: 500,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
|
backgroundColor: white,
|
||||||
|
"&:hover": {
|
||||||
|
color: white,
|
||||||
|
backgroundColor: cdrLinkOrange,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const fieldStyles = {
|
||||||
|
"& label.Mui-focused": {
|
||||||
|
color: cdrLinkOrange,
|
||||||
|
},
|
||||||
|
"& .MuiInput-underline:after": {
|
||||||
|
borderBottomColor: cdrLinkOrange,
|
||||||
|
},
|
||||||
|
"& .MuiFilledInput-underline:after": {
|
||||||
|
borderBottomColor: cdrLinkOrange,
|
||||||
|
},
|
||||||
|
"& .MuiOutlinedInput-root": {
|
||||||
|
"&.Mui-focused fieldset": {
|
||||||
|
borderColor: cdrLinkOrange,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box sx={{ backgroundColor: darkGray, height: "100vh" }}>
|
||||||
<Grid container direction="row-reverse" sx={{ p: 3 }}>
|
<Container maxWidth="md" sx={{ p: 10 }}>
|
||||||
<Grid item />
|
|
||||||
</Grid>
|
|
||||||
<Container maxWidth="md" sx={{ mt: 3, mb: 20 }}>
|
|
||||||
<Grid container spacing={2} direction="column" alignItems="center">
|
<Grid container spacing={2} direction="column" alignItems="center">
|
||||||
<Grid item>
|
<Grid
|
||||||
<Box sx={{ maxWidth: 200 }} />
|
item
|
||||||
</Grid>
|
container
|
||||||
<Grid item sx={{ textAlign: "center" }} />
|
direction="row"
|
||||||
|
justifyContent="center"
|
||||||
<Grid item>
|
alignItems="center"
|
||||||
{!session ? (
|
>
|
||||||
<Grid
|
<Grid item>
|
||||||
container
|
<Box
|
||||||
spacing={3}
|
sx={{
|
||||||
direction="column"
|
width: "70px",
|
||||||
alignItems="center"
|
height: "70px",
|
||||||
sx={{ width: 450, mt: 1 }}
|
margin: "0 auto",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Grid item sx={{ width: "100%" }}>
|
<Image
|
||||||
<IconButton
|
src={LinkLogo}
|
||||||
sx={buttonStyles}
|
alt="Link logo"
|
||||||
onClick={() =>
|
width={70}
|
||||||
signIn("google", {
|
height={70}
|
||||||
callbackUrl: `${origin}/setup`,
|
style={{
|
||||||
})
|
objectFit: "cover",
|
||||||
}
|
filter: "grayscale(100) brightness(100)",
|
||||||
>
|
}}
|
||||||
<GoogleIcon sx={{ mr: 1 }} />
|
/>
|
||||||
Google
|
</Box>
|
||||||
</IconButton>
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
sx={{
|
||||||
|
fontSize: 36,
|
||||||
|
color: "white",
|
||||||
|
fontWeight: 700,
|
||||||
|
mt: 1,
|
||||||
|
ml: 0.5,
|
||||||
|
fontFamily: "Poppins",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
CDR Link
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
{!session ? (
|
||||||
|
<Container
|
||||||
|
maxWidth="xs"
|
||||||
|
sx={{
|
||||||
|
p: 3,
|
||||||
|
mt: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={3}
|
||||||
|
direction="column"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
{error ? (
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<Box sx={{ backgroundColor: "red", p: 3 }}>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{
|
||||||
|
fontSize: 18,
|
||||||
|
color: "white",
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{`${error} error`}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
) : null}
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<IconButton
|
||||||
|
sx={buttonStyles}
|
||||||
|
onClick={() =>
|
||||||
|
signIn("google", {
|
||||||
|
callbackUrl: `${origin}/setup`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<GoogleIcon sx={{ mr: 1 }} />
|
||||||
|
Sign in with Google
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<IconButton
|
||||||
|
aria-label="Sign in with Apple"
|
||||||
|
sx={buttonStyles}
|
||||||
|
onClick={() =>
|
||||||
|
signIn("apple", {
|
||||||
|
callbackUrl: `${window.location.origin}/setup`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<AppleIcon sx={{ mr: 1 }} />
|
||||||
|
Sign in with Apple
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
|
<Grid>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{
|
||||||
|
fontSize: 18,
|
||||||
|
color: white,
|
||||||
|
textAlign: "center",
|
||||||
|
mt: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
⸺ or ⸺
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<TextField
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
label="Email"
|
||||||
|
variant="filled"
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
sx={{ ...fieldStyles, backgroundColor: white }}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ ...fieldStyles, width: "100%" }}>
|
||||||
|
<TextField
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
label="Password"
|
||||||
|
variant="filled"
|
||||||
|
size="small"
|
||||||
|
fullWidth
|
||||||
|
sx={{ backgroundColor: white }}
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item sx={{ width: "100%" }}>
|
||||||
|
<IconButton
|
||||||
|
sx={buttonStyles}
|
||||||
|
onClick={() =>
|
||||||
|
signIn("credentials", {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
callbackUrl: `${origin}/setup`,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<KeyIcon sx={{ mr: 1 }} />
|
||||||
|
Sign in with Zammad credentials
|
||||||
|
</IconButton>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/*
|
</Container>
|
||||||
<Grid item sx={{ width: "100%" }}>
|
|
||||||
<IconButton
|
|
||||||
sx={buttonStyles}
|
|
||||||
onClick={() =>
|
|
||||||
signIn("apple", {
|
|
||||||
callbackUrl: `${window.location.origin}/setup`,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<AppleIcon sx={{ mr: 1 }} />
|
|
||||||
</IconButton>
|
|
||||||
</Grid>*/}
|
|
||||||
<Grid item sx={{ mt: 2 }} />
|
|
||||||
</Grid>
|
|
||||||
) : null}
|
) : null}
|
||||||
{session ? (
|
{session ? (
|
||||||
<Box component="h4">
|
<Box component="h4">
|
||||||
|
|
@ -79,6 +230,6 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
14
apps/link/app/(main)/_components/ClientOnly.tsx
Normal file
14
apps/link/app/(main)/_components/ClientOnly.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
type ClientOnlyProps = { children: JSX.Element };
|
||||||
|
const ClientOnly = (props: ClientOnlyProps) => {
|
||||||
|
const { children } = props;
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default dynamic(() => Promise.resolve(ClientOnly), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
@ -17,12 +17,14 @@ import {
|
||||||
import {
|
import {
|
||||||
FeaturedPlayList as FeaturedPlayListIcon,
|
FeaturedPlayList as FeaturedPlayListIcon,
|
||||||
Person as PersonIcon,
|
Person as PersonIcon,
|
||||||
Analytics as AnalyticsIcon,
|
Insights as InsightsIcon,
|
||||||
Logout as LogoutIcon,
|
Logout as LogoutIcon,
|
||||||
Cottage as CottageIcon,
|
Cottage as CottageIcon,
|
||||||
Settings as SettingsIcon,
|
Settings as SettingsIcon,
|
||||||
ExpandCircleDown as ExpandCircleDownIcon,
|
ExpandCircleDown as ExpandCircleDownIcon,
|
||||||
Dvr as DvrIcon,
|
Dvr as DvrIcon,
|
||||||
|
Assessment as AssessmentIcon,
|
||||||
|
LibraryBooks as LibraryBooksIcon,
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
@ -161,7 +163,10 @@ 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();
|
||||||
|
console.log({ session });
|
||||||
const username = session?.user?.name || "User";
|
const username = session?.user?.name || "User";
|
||||||
|
// @ts-ignore
|
||||||
|
const roles = session?.user?.roles || [];
|
||||||
const { data: overviewData, error: overviewError }: any = useSWR(
|
const { data: overviewData, error: overviewError }: any = useSWR(
|
||||||
{
|
{
|
||||||
document: getTicketOverviewCountsQuery,
|
document: getTicketOverviewCountsQuery,
|
||||||
|
|
@ -419,17 +424,25 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
||||||
<MenuItem
|
<MenuItem
|
||||||
name="Knowledge Base"
|
name="Knowledge Base"
|
||||||
href="/knowledge"
|
href="/knowledge"
|
||||||
Icon={CottageIcon}
|
Icon={LibraryBooksIcon}
|
||||||
iconSize={20}
|
iconSize={20}
|
||||||
selected={pathname.endsWith("/knowledge")}
|
selected={pathname.endsWith("/knowledge")}
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
|
<MenuItem
|
||||||
|
name="Reporting"
|
||||||
|
href="/reporting"
|
||||||
|
Icon={AssessmentIcon}
|
||||||
|
iconSize={20}
|
||||||
|
selected={pathname.endsWith("/reporting")}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
name="Leafcutter"
|
name="Leafcutter"
|
||||||
href="/leafcutter"
|
href="/leafcutter"
|
||||||
Icon={AnalyticsIcon}
|
Icon={InsightsIcon}
|
||||||
iconSize={20}
|
iconSize={20}
|
||||||
selected={pathname.endsWith("/leafcutter")}
|
selected={false}
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
<Collapse
|
<Collapse
|
||||||
|
|
@ -471,7 +484,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
||||||
<MenuItem
|
<MenuItem
|
||||||
name="About"
|
name="About"
|
||||||
href="/leafcutter/about"
|
href="/leafcutter/about"
|
||||||
Icon={AnalyticsIcon}
|
Icon={InsightsIcon}
|
||||||
iconSize={0}
|
iconSize={0}
|
||||||
selected={pathname.endsWith("/leafcutter/about")}
|
selected={pathname.endsWith("/leafcutter/about")}
|
||||||
open={open}
|
open={open}
|
||||||
|
|
@ -486,49 +499,57 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
||||||
selected={pathname.endsWith("/profile")}
|
selected={pathname.endsWith("/profile")}
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
{roles.includes("admin") && (
|
||||||
name="Admin"
|
<>
|
||||||
href="/admin/zammad"
|
|
||||||
Icon={SettingsIcon}
|
|
||||||
iconSize={20}
|
|
||||||
open={open}
|
|
||||||
/>
|
|
||||||
<Collapse
|
|
||||||
in={pathname.startsWith("/admin/")}
|
|
||||||
timeout="auto"
|
|
||||||
unmountOnExit
|
|
||||||
onClick={undefined}
|
|
||||||
>
|
|
||||||
<List component="div" disablePadding>
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
name="Zammad Settings"
|
name="Admin"
|
||||||
href="/admin/zammad"
|
href="/admin/zammad"
|
||||||
Icon={FeaturedPlayListIcon}
|
Icon={SettingsIcon}
|
||||||
iconSize={0}
|
iconSize={20}
|
||||||
selected={pathname.endsWith("/admin/zammad")}
|
|
||||||
open={open}
|
open={open}
|
||||||
/>
|
/>
|
||||||
<MenuItem
|
<Collapse
|
||||||
name="Metamigo"
|
in={pathname.startsWith("/admin/")}
|
||||||
href="/admin/metamigo"
|
timeout="auto"
|
||||||
Icon={FeaturedPlayListIcon}
|
unmountOnExit
|
||||||
iconSize={0}
|
onClick={undefined}
|
||||||
selected={pathname.endsWith("/admin/metamigo")}
|
>
|
||||||
open={open}
|
<List component="div" disablePadding>
|
||||||
/>
|
<MenuItem
|
||||||
<MenuItem
|
name="Zammad Settings"
|
||||||
name="Label Studio"
|
href="/admin/zammad"
|
||||||
href="/admin/label-studio"
|
Icon={FeaturedPlayListIcon}
|
||||||
Icon={FeaturedPlayListIcon}
|
iconSize={0}
|
||||||
iconSize={0}
|
selected={pathname.endsWith("/admin/zammad")}
|
||||||
selected={pathname.endsWith("/admin/label-studio")}
|
open={open}
|
||||||
open={open}
|
/>
|
||||||
/>
|
{false && roles.includes("metamigo") && (
|
||||||
</List>
|
<MenuItem
|
||||||
</Collapse>
|
name="Metamigo"
|
||||||
|
href="/admin/metamigo"
|
||||||
|
Icon={FeaturedPlayListIcon}
|
||||||
|
iconSize={0}
|
||||||
|
selected={pathname.endsWith("/admin/metamigo")}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{roles.includes("label_studio") && (
|
||||||
|
<MenuItem
|
||||||
|
name="Label Studio"
|
||||||
|
href="/admin/label-studio"
|
||||||
|
Icon={FeaturedPlayListIcon}
|
||||||
|
iconSize={0}
|
||||||
|
selected={pathname.endsWith("/admin/label-studio")}
|
||||||
|
open={open}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
</Collapse>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<MenuItem
|
<MenuItem
|
||||||
name="Zammad Interface"
|
name="Zammad Interface"
|
||||||
href="/proxy/zammad"
|
href="/zammad"
|
||||||
Icon={DvrIcon}
|
Icon={DvrIcon}
|
||||||
iconSize={20}
|
iconSize={20}
|
||||||
open={open}
|
open={open}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [display, setDisplay] = useState("none");
|
const [display, setDisplay] = useState("none");
|
||||||
const url = `/proxy/zammad${path}`;
|
const url = `/zammad${path}`;
|
||||||
console.log({ url });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -33,8 +32,7 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
linkElement.contentDocument &&
|
linkElement.contentDocument &&
|
||||||
linkElement.contentDocument?.querySelector &&
|
linkElement.contentDocument?.querySelector &&
|
||||||
linkElement.contentDocument.querySelector("#navigation") &&
|
linkElement.contentDocument.querySelector("#navigation") &&
|
||||||
linkElement.contentDocument.querySelector("body") &&
|
linkElement.contentDocument.querySelector("body")
|
||||||
linkElement.contentDocument.querySelector(".sidebar")
|
|
||||||
) {
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
linkElement.contentDocument.querySelector("#navigation").style =
|
linkElement.contentDocument.querySelector("#navigation").style =
|
||||||
|
|
@ -43,7 +41,10 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
||||||
linkElement.contentDocument.querySelector("body").style =
|
linkElement.contentDocument.querySelector("body").style =
|
||||||
"font-family: Arial";
|
"font-family: Arial";
|
||||||
|
|
||||||
if (hideSidebar) {
|
if (
|
||||||
|
hideSidebar &&
|
||||||
|
linkElement.contentDocument.querySelector(".sidebar")
|
||||||
|
) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
linkElement.contentDocument.querySelector(".sidebar").style =
|
linkElement.contentDocument.querySelector(".sidebar").style =
|
||||||
"display: none";
|
"display: none";
|
||||||
|
|
|
||||||
16
apps/link/app/(main)/admin/metamigo/_components/Admin.tsx
Normal file
16
apps/link/app/(main)/admin/metamigo/_components/Admin.tsx
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import { ApolloProvider } from "@apollo/client";
|
||||||
|
import { apolloClient } from "../_lib/apollo-client";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const MetamigoAdmin = dynamic(() => import("./MetamigoAdmin"), {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Admin: FC = () => (
|
||||||
|
<ApolloProvider client={apolloClient}>
|
||||||
|
<MetamigoAdmin />
|
||||||
|
</ApolloProvider>
|
||||||
|
);
|
||||||
|
|
@ -6,13 +6,11 @@ import { FC, useEffect, useState } from "react";
|
||||||
import { Admin, Resource } from "react-admin";
|
import { Admin, Resource } from "react-admin";
|
||||||
import { useApolloClient } from "@apollo/client";
|
import { useApolloClient } from "@apollo/client";
|
||||||
import polyglotI18nProvider from "ra-i18n-polyglot";
|
import polyglotI18nProvider from "ra-i18n-polyglot";
|
||||||
import { ThemeProvider, createTheme } from "@mui/material";
|
import { ThemeProvider, createTheme } from "@mui/material/styles";
|
||||||
import { metamigoDataProvider } from "../_lib/dataprovider";
|
import { metamigoDataProvider } from "../_lib/dataprovider";
|
||||||
import { theme } from "./layout/themes";
|
import { theme } from "./layout/themes";
|
||||||
import { Layout } from "./layout";
|
import { Layout } from "./layout";
|
||||||
import englishMessages from "../_i18n/en";
|
import englishMessages from "../_i18n/en";
|
||||||
import users from "./users";
|
|
||||||
import accounts from "./accounts";
|
|
||||||
import whatsappBots from "./whatsapp/bots";
|
import whatsappBots from "./whatsapp/bots";
|
||||||
import whatsappMessages from "./whatsapp/messages";
|
import whatsappMessages from "./whatsapp/messages";
|
||||||
import whatsappAttachments from "./whatsapp/attachments";
|
import whatsappAttachments from "./whatsapp/attachments";
|
||||||
|
|
@ -42,29 +40,25 @@ const MetamigoAdmin: FC = () => {
|
||||||
}, [client]);
|
}, [client]);
|
||||||
return (
|
return (
|
||||||
dataProvider && (
|
dataProvider && (
|
||||||
<ThemeProvider theme={muiTheme}>
|
<Admin
|
||||||
<Admin
|
disableTelemetry
|
||||||
disableTelemetry
|
dataProvider={dataProvider}
|
||||||
dataProvider={dataProvider}
|
layout={Layout}
|
||||||
layout={Layout}
|
i18nProvider={i18nProvider}
|
||||||
i18nProvider={i18nProvider}
|
// @ts-ignore
|
||||||
// @ts-ignore
|
loginPage={AdminLogin}
|
||||||
loginPage={AdminLogin}
|
// @ts-ignore
|
||||||
// @ts-ignore
|
authProvider={authProvider}
|
||||||
authProvider={authProvider}
|
>
|
||||||
>
|
<Resource name="webhooks" {...webhooks} />
|
||||||
<Resource name="webhooks" {...webhooks} />
|
<Resource name="whatsappBots" {...whatsappBots} />
|
||||||
<Resource name="whatsappBots" {...whatsappBots} />
|
<Resource name="whatsappMessages" {...whatsappMessages} />
|
||||||
<Resource name="whatsappMessages" {...whatsappMessages} />
|
<Resource name="whatsappAttachments" {...whatsappAttachments} />
|
||||||
<Resource name="whatsappAttachments" {...whatsappAttachments} />
|
<Resource name="signalBots" {...signalBots} />
|
||||||
<Resource name="signalBots" {...signalBots} />
|
<Resource name="voiceProviders" {...voiceProviders} />
|
||||||
<Resource name="voiceProviders" {...voiceProviders} />
|
<Resource name="voiceLines" {...voiceLines} />
|
||||||
<Resource name="voiceLines" {...voiceLines} />
|
<Resource name="languages" />
|
||||||
<Resource name="users" {...users} />
|
</Admin>
|
||||||
<Resource name="accounts" {...accounts} />
|
|
||||||
<Resource name="languages" />
|
|
||||||
</Admin>
|
|
||||||
</ThemeProvider>
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
"use client";
|
|
||||||
|
|
||||||
import { FC } from "react";
|
|
||||||
import { Grid } from "@mui/material";
|
|
||||||
import Iframe from "react-iframe";
|
|
||||||
|
|
||||||
type MetamigoWrapperProps = {
|
|
||||||
path: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const MetamigoWrapper: FC<MetamigoWrapperProps> = ({ path }) => {
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
spacing={0}
|
|
||||||
sx={{ height: "100%", width: "100%" }}
|
|
||||||
direction="column"
|
|
||||||
>
|
|
||||||
<Grid item sx={{ height: "100vh", width: "100%" }}>
|
|
||||||
<Iframe
|
|
||||||
id="metamigo"
|
|
||||||
url="/proxy/metamigo"
|
|
||||||
width="100%"
|
|
||||||
height="100%"
|
|
||||||
frameBorder={0}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { makeStyles } from "@mui/styles";
|
// import { makeStyles } from "@mui/styles";
|
||||||
import {
|
import {
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
|
||||||
|
/*
|
||||||
const useStyles = makeStyles((_theme: any) => ({
|
const useStyles = makeStyles((_theme: any) => ({
|
||||||
defaultToolbar: {
|
defaultToolbar: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -22,14 +23,14 @@ const useStyles = makeStyles((_theme: any) => ({
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
*/
|
||||||
type AccountEditToolbarProps = {
|
type AccountEditToolbarProps = {
|
||||||
record?: any;
|
record?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
const AccountEditToolbar: FC<AccountEditToolbarProps> = (props) => {
|
const AccountEditToolbar: FC<AccountEditToolbarProps> = (props) => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
const classes = useStyles(props);
|
const classes: any = {}; // useStyles(props);
|
||||||
return (
|
return (
|
||||||
<Toolbar className={classes.defaultToolbar} {...props}>
|
<Toolbar className={classes.defaultToolbar} {...props}>
|
||||||
<DeleteButton disabled={session?.user?.email === props.record?.userId} />
|
<DeleteButton disabled={session?.user?.email === props.record?.userId} />
|
||||||
|
|
@ -4,8 +4,9 @@ import { forwardRef } from "react";
|
||||||
import { AppBar, UserMenu, MenuItemLink, useTranslate } from "react-admin";
|
import { AppBar, UserMenu, MenuItemLink, useTranslate } from "react-admin";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { colors } from "app/_styles/theme";
|
||||||
|
// import { makeStyles } from "@mui/styles";
|
||||||
|
/*
|
||||||
const useStyles = makeStyles({
|
const useStyles = makeStyles({
|
||||||
title: {
|
title: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -17,7 +18,7 @@ const useStyles = makeStyles({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const ConfigurationMenu = forwardRef<any, any>((props, ref) => {
|
const ConfigurationMenu = forwardRef<any, any>((props, ref) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
|
|
@ -33,21 +34,16 @@ const ConfigurationMenu = forwardRef<any, any>((props, ref) => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const CustomUserMenu = (props: any) => (
|
|
||||||
<UserMenu {...props}>
|
|
||||||
<ConfigurationMenu />
|
|
||||||
</UserMenu>
|
|
||||||
);
|
|
||||||
|
|
||||||
const CustomAppBar = (props: any) => {
|
const CustomAppBar = (props: any) => {
|
||||||
const classes = useStyles();
|
const classes: any = {}; // useStyles();
|
||||||
|
const { darkLavender } = colors;
|
||||||
return (
|
return (
|
||||||
<AppBar
|
<AppBar
|
||||||
{...props}
|
{...props}
|
||||||
elevation={1}
|
elevation={0}
|
||||||
userMenu={<CustomUserMenu />}
|
userMenu={<div />}
|
||||||
position="sticky"
|
position="sticky"
|
||||||
sx={{ mt: -1 }}
|
sx={{ mt: -1, backgroundColor: darkLavender }}
|
||||||
>
|
>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
|
|
@ -2,15 +2,11 @@
|
||||||
|
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
// import { useSelector } from "react-redux";
|
|
||||||
import SecurityIcon from "@mui/icons-material/Security";
|
|
||||||
import VoiceIcon from "@mui/icons-material/PhoneInTalk";
|
import VoiceIcon from "@mui/icons-material/PhoneInTalk";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import { useTheme } from "@mui/styles";
|
// import { useTheme } from "@mui/styles";
|
||||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||||
import { useTranslate, MenuItemLink } from "react-admin";
|
import { useTranslate, MenuItemLink } from "react-admin";
|
||||||
import users from "../users";
|
|
||||||
import accounts from "../accounts";
|
|
||||||
import webhooks from "../webhooks";
|
import webhooks from "../webhooks";
|
||||||
import voiceLines from "../voice/voicelines";
|
import voiceLines from "../voice/voicelines";
|
||||||
import voiceProviders from "../voice/providers";
|
import voiceProviders from "../voice/providers";
|
||||||
|
|
@ -26,11 +22,10 @@ export const Menu: FC = ({ onMenuClick, logout, dense = false }: any) => {
|
||||||
menuSecurity: false,
|
menuSecurity: false,
|
||||||
});
|
});
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const theme = useTheme();
|
const theme: any = {}; // useTheme();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const isXSmall = useMediaQuery(theme.breakpoints.down("xs"));
|
const isXSmall = false; // useMediaQuery(theme?.breakpoints?.down("xs"));
|
||||||
const open = true; // useSelector((state: any) => state.admin.ui.sidebarOpen);
|
const open = true; // useSelector((state: any) => state.admin.ui.sidebarOpen);
|
||||||
// useSelector((state: any) => state.theme); // force rerender on theme change
|
|
||||||
|
|
||||||
const handleToggle = (menu: MenuName) => {
|
const handleToggle = (menu: MenuName) => {
|
||||||
setState((state: any) => ({ ...state, [menu]: !state[menu] }));
|
setState((state: any) => ({ ...state, [menu]: !state[menu] }));
|
||||||
|
|
@ -97,35 +92,6 @@ export const Menu: FC = ({ onMenuClick, logout, dense = false }: any) => {
|
||||||
sidebarIsOpen={open}
|
sidebarIsOpen={open}
|
||||||
dense={dense}
|
dense={dense}
|
||||||
/>
|
/>
|
||||||
<SubMenu
|
|
||||||
handleToggle={() => handleToggle("menuSecurity")}
|
|
||||||
isOpen={state.menuSecurity}
|
|
||||||
sidebarIsOpen={open}
|
|
||||||
name="pos.menu.security"
|
|
||||||
icon={<SecurityIcon />}
|
|
||||||
dense={dense}
|
|
||||||
>
|
|
||||||
<MenuItemLink
|
|
||||||
to={`/users`}
|
|
||||||
primaryText={translate(`resources.users.name`, {
|
|
||||||
smart_count: 2,
|
|
||||||
})}
|
|
||||||
leftIcon={<users.icon />}
|
|
||||||
onClick={onMenuClick}
|
|
||||||
sidebarIsOpen={open}
|
|
||||||
dense={dense}
|
|
||||||
/>
|
|
||||||
<MenuItemLink
|
|
||||||
to={`/accounts`}
|
|
||||||
primaryText={translate(`resources.accounts.name`, {
|
|
||||||
smart_count: 2,
|
|
||||||
})}
|
|
||||||
leftIcon={<accounts.icon />}
|
|
||||||
onClick={onMenuClick}
|
|
||||||
sidebarIsOpen={open}
|
|
||||||
dense={dense}
|
|
||||||
/>
|
|
||||||
</SubMenu>
|
|
||||||
{isXSmall && logout}
|
{isXSmall && logout}
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
@ -8,9 +8,10 @@ import ListItemIcon from "@mui/material/ListItemIcon";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Collapse from "@mui/material/Collapse";
|
import Collapse from "@mui/material/Collapse";
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { makeStyles } from "@mui/styles";
|
// import { makeStyles } from "@mui/styles";
|
||||||
import { useTranslate } from "react-admin";
|
import { useTranslate } from "react-admin";
|
||||||
|
|
||||||
|
/*
|
||||||
const useStyles = makeStyles((theme: any) => ({
|
const useStyles = makeStyles((theme: any) => ({
|
||||||
icon: { minWidth: theme.spacing(5) },
|
icon: { minWidth: theme.spacing(5) },
|
||||||
sidebarIsOpen: {
|
sidebarIsOpen: {
|
||||||
|
|
@ -26,7 +27,7 @@ const useStyles = makeStyles((theme: any) => ({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
*/
|
||||||
type SubMenuProps = PropsWithChildren<{
|
type SubMenuProps = PropsWithChildren<{
|
||||||
dense: boolean;
|
dense: boolean;
|
||||||
handleToggle: () => void;
|
handleToggle: () => void;
|
||||||
|
|
@ -46,7 +47,7 @@ export const SubMenu: FC<SubMenuProps> = ({
|
||||||
dense,
|
dense,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const translate = useTranslate();
|
const translate = useTranslate();
|
||||||
const classes = useStyles();
|
const classes: any = {}; // = useStyles();
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|
@ -45,8 +45,11 @@ export const theme = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
typography: {
|
typography: {
|
||||||
h6: { fontSize: 16, fontWeight: 600, color: "#1bb1bb" },
|
fontFamily: "Poppins, sans-serif",
|
||||||
|
fontWeight: 900,
|
||||||
|
h6: { fontSize: 16, fontWeight: 600, color: "red" },
|
||||||
},
|
},
|
||||||
|
/*
|
||||||
overrides: {
|
overrides: {
|
||||||
RaMenuItemLink: {
|
RaMenuItemLink: {
|
||||||
root: {
|
root: {
|
||||||
|
|
@ -96,4 +99,5 @@ export const theme = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
*/
|
||||||
};
|
};
|
||||||
|
|
@ -231,7 +231,7 @@ const VerificationCaptcha = ({
|
||||||
.replace(/signalcaptcha:\/\//, "")
|
.replace(/signalcaptcha:\/\//, "")
|
||||||
.replace("“", "")
|
.replace("“", "")
|
||||||
.replace("”", "")
|
.replace("”", "")
|
||||||
.trim()
|
.trim(),
|
||||||
);
|
);
|
||||||
else setCode(value);
|
else setCode(value);
|
||||||
};
|
};
|
||||||
|
|
@ -294,7 +294,7 @@ const VerificationCodeInput = ({
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
const responseBody = await response.json();
|
const responseBody = await response.json();
|
||||||
|
|
@ -302,12 +302,10 @@ const VerificationCodeInput = ({
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
confirmVerification();
|
confirmVerification();
|
||||||
} else if (responseBody.message)
|
} else if (responseBody.message)
|
||||||
// @ts-expect-error
|
|
||||||
setSubmissionError(`Error: ${responseBody.message}`);
|
setSubmissionError(`Error: ${responseBody.message}`);
|
||||||
else {
|
else {
|
||||||
setSubmissionError(
|
setSubmissionError(
|
||||||
// @ts-expect-error
|
"There was an error, sorry about that. Please try again later or contact support.",
|
||||||
"There was an error, sorry about that. Please try again later or contact support."
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { makeStyles } from "@mui/styles";
|
// import { makeStyles } from "@mui/styles";
|
||||||
import {
|
import {
|
||||||
SimpleForm,
|
SimpleForm,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
|
@ -16,6 +16,7 @@ import {
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { UserRoleInput } from "./shared";
|
import { UserRoleInput } from "./shared";
|
||||||
|
|
||||||
|
/*
|
||||||
const useStyles = makeStyles((_theme: any) => ({
|
const useStyles = makeStyles((_theme: any) => ({
|
||||||
defaultToolbar: {
|
defaultToolbar: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|
@ -23,14 +24,16 @@ const useStyles = makeStyles((_theme: any) => ({
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
*/
|
||||||
|
|
||||||
const UserEditToolbar = (props: any) => {
|
const UserEditToolbar = (props: any) => {
|
||||||
const classes = useStyles();
|
const classes: any = {}; // = useStyles();
|
||||||
const redirect = useRedirect();
|
const redirect = useRedirect();
|
||||||
const record = useRecordContext();
|
const record = useRecordContext();
|
||||||
const {session} = props;
|
const { session } = props;
|
||||||
|
|
||||||
const shouldDisableDelete = !session || !session.user || session.user.id === record.id;
|
const shouldDisableDelete =
|
||||||
|
!session || !session.user || session.user.id === record.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar className={classes.defaultToolbar}>
|
<Toolbar className={classes.defaultToolbar}>
|
||||||
|
|
@ -6,7 +6,7 @@ import dynamic from "next/dynamic";
|
||||||
import MicIcon from "@mui/icons-material/Mic";
|
import MicIcon from "@mui/icons-material/Mic";
|
||||||
import StopIcon from "@mui/icons-material/Stop";
|
import StopIcon from "@mui/icons-material/Stop";
|
||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import { useTheme } from "@mui/styles"; // makeStyles,
|
// import { useTheme } from "@mui/styles"; // makeStyles,
|
||||||
// import AudioPlayer from "material-ui-audio-player";
|
// import AudioPlayer from "material-ui-audio-player";
|
||||||
import { useStopwatch } from "react-timer-hook";
|
import { useStopwatch } from "react-timer-hook";
|
||||||
import style from "./MicInput.module.css";
|
import style from "./MicInput.module.css";
|
||||||
|
|
@ -15,10 +15,10 @@ import style from "./MicInput.module.css";
|
||||||
const ReactMic = dynamic<any>(
|
const ReactMic = dynamic<any>(
|
||||||
() => {
|
() => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"MIC INPUT FEATURE IS DISABLED"
|
"MIC INPUT FEATURE IS DISABLED",
|
||||||
); /* return import("react-mic").then((mod) => mod.ReactMic); */
|
); /* return import("react-mic").then((mod) => mod.ReactMic); */
|
||||||
},
|
},
|
||||||
{ ssr: false }
|
{ ssr: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
const blobToDataUri = (blob: Blob) => {
|
const blobToDataUri = (blob: Blob) => {
|
||||||
|
|
@ -54,7 +54,7 @@ const blobToResult = async (blob: Blob) => {
|
||||||
|
|
||||||
const MicInput = (props: any) => {
|
const MicInput = (props: any) => {
|
||||||
const { seconds, minutes, hours, start, reset, pause } = useStopwatch();
|
const { seconds, minutes, hours, start, reset, pause } = useStopwatch();
|
||||||
const theme = useTheme();
|
const theme: any = {}; // useTheme();
|
||||||
const {
|
const {
|
||||||
field: { onChange }, // value
|
field: { onChange }, // value
|
||||||
} = useInput(props);
|
} = useInput(props);
|
||||||
|
|
@ -21,7 +21,7 @@ type TTSProvider = (voice: any, language: any, prompt: any) => Promise<void>;
|
||||||
|
|
||||||
const tts = async (providerId: any): Promise<TTSProvider> => {
|
const tts = async (providerId: any): Promise<TTSProvider> => {
|
||||||
const r = await fetch(
|
const r = await fetch(
|
||||||
`/api/v1/voice/twilio/text-to-speech-token/${providerId}`
|
`/api/v1/voice/twilio/text-to-speech-token/${providerId}`,
|
||||||
);
|
);
|
||||||
const { token } = await r.json();
|
const { token } = await r.json();
|
||||||
const twilioClient = await import("twilio-client");
|
const twilioClient = await import("twilio-client");
|
||||||
|
|
@ -97,7 +97,7 @@ export const PromptInput = (form: any, ...rest: any[]) => (
|
||||||
<TextInput
|
<TextInput
|
||||||
source="promptText"
|
source="promptText"
|
||||||
multiline
|
multiline
|
||||||
options={{ fullWidth: true }}
|
// options={{ fullWidth: true }}
|
||||||
InputProps={{ endAdornment: <TextToSpeechButton form={form} /> }}
|
InputProps={{ endAdornment: <TextToSpeechButton form={form} /> }}
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
|
@ -106,7 +106,6 @@ export const PromptInput = (form: any, ...rest: any[]) => (
|
||||||
const validateVoice = (_args: any, values: any) => {
|
const validateVoice = (_args: any, values: any) => {
|
||||||
if (!values.language) return "validation.language";
|
if (!values.language) return "validation.language";
|
||||||
if (!values.voice) return "validation.voice";
|
if (!values.voice) return "validation.voice";
|
||||||
// @ts-expect-error
|
|
||||||
const availableVoices = TwilioLanguages.voices[values.language];
|
const availableVoices = TwilioLanguages.voices[values.language];
|
||||||
const found =
|
const found =
|
||||||
availableVoices.filter((v: any) => v.id === values.voice).length === 1;
|
availableVoices.filter((v: any) => v.id === values.voice).length === 1;
|
||||||
|
|
@ -116,7 +115,6 @@ const validateVoice = (_args: any, values: any) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VoiceInput = (form: any, ...rest: any[]) => {
|
export const VoiceInput = (form: any, ...rest: any[]) => {
|
||||||
// @ts-expect-error
|
|
||||||
const voice = TwilioLanguages.voices[form.formData.language] || [];
|
const voice = TwilioLanguages.voices[form.formData.language] || [];
|
||||||
return (
|
return (
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
|
@ -140,7 +138,7 @@ const getAvailableNumbers = async (providerId: string) => {
|
||||||
return availableNumbers;
|
return availableNumbers;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
console.error(
|
||||||
`Could not fetch available numbers for provider ${providerId} - ${error}`
|
`Could not fetch available numbers for provider ${providerId} - ${error}`,
|
||||||
);
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
@ -287,21 +285,19 @@ export const AsyncSelectInput = (choiceLoader: () => Promise<any[]>, label, sour
|
||||||
export const VoiceLineSelectInput = AsyncSelectInput(getVoiceLineChoices, "Voice Line", "backendId", "validation.noVoiceLines" )
|
export const VoiceLineSelectInput = AsyncSelectInput(getVoiceLineChoices, "Voice Line", "backendId", "validation.noVoiceLines" )
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const VoiceLineSelectInput = (source: string) => () =>
|
export const VoiceLineSelectInput = (source: string) => () => (
|
||||||
(
|
<ReferenceInput
|
||||||
<ReferenceInput
|
label="Voice Line"
|
||||||
label="Voice Line"
|
source={source}
|
||||||
source={source}
|
reference="voiceLines"
|
||||||
reference="voiceLines"
|
validate={[required()]}
|
||||||
validate={[required()]}
|
>
|
||||||
>
|
<SelectInput optionText="number" />
|
||||||
<SelectInput optionText="number" />
|
</ReferenceInput>
|
||||||
</ReferenceInput>
|
);
|
||||||
);
|
|
||||||
|
|
||||||
export const VoiceLineField = (source: string) => () =>
|
export const VoiceLineField = (source: string) => () => (
|
||||||
(
|
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
|
||||||
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
|
<TextField source="number" />
|
||||||
<TextField source="number" />
|
</ReferenceField>
|
||||||
</ReferenceField>
|
);
|
||||||
);
|
|
||||||
|
|
@ -55,8 +55,7 @@ export const BackendTypeInput = (props: any) => (
|
||||||
|
|
||||||
export const BackendIdInput = (form: any, ...rest: any[]) => {
|
export const BackendIdInput = (form: any, ...rest: any[]) => {
|
||||||
const Component = form.formData.backendType
|
const Component = form.formData.backendType
|
||||||
? // @ts-expect-error
|
? backendInputComponents[form.formData.backendType]
|
||||||
backendInputComponents[form.formData.backendType]
|
|
||||||
: false;
|
: false;
|
||||||
return <>{Component && <Component form={form} {...rest} />}</>;
|
return <>{Component && <Component form={form} {...rest} />}</>;
|
||||||
};
|
};
|
||||||
|
|
@ -65,8 +64,7 @@ export const BackendIdField = (form: any, ...rest: any[]) => {
|
||||||
console.log(form);
|
console.log(form);
|
||||||
|
|
||||||
const Component = form.record.backendType
|
const Component = form.record.backendType
|
||||||
? // @ts-expect-error
|
? backendFieldComponents[form.record.backendType]
|
||||||
backendFieldComponents[form.record.backendType]
|
|
||||||
: false;
|
: false;
|
||||||
return <>{Component && <Component form={form} {...rest} />}</>;
|
return <>{Component && <Component form={form} {...rest} />}</>;
|
||||||
};
|
};
|
||||||
|
|
@ -24,7 +24,7 @@ const errorLink = onError(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const apolloClient = new ApolloClient({
|
export const apolloClient = new ApolloClient({
|
||||||
link: ApolloLink.from([errorLink, new HttpLink({ uri: "/graphql" })]),
|
link: ApolloLink.from([errorLink, new HttpLink({ uri: "/proxy/metamigo/graphql" })]),
|
||||||
cache: new InMemoryCache(),
|
cache: new InMemoryCache(),
|
||||||
/*
|
/*
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
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