Flatten
This commit is contained in:
parent
8f165d15d2
commit
c620e4bf25
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() {
|
||||
return <About />;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { getTemplates } from "app/_lib/opensearch";
|
||||
import { Create } from "./_components/Create";
|
||||
import { Create } from "leafcutter-common";
|
||||
|
||||
export default async function Page() {
|
||||
const templates = await getTemplates(100);
|
||||
|
||||
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() {
|
||||
return <FAQ />;
|
||||
|
|
|
|||
|
|
@ -7,16 +7,12 @@ import "@fontsource/roboto/700.css";
|
|||
import "@fontsource/playfair-display/900.css";
|
||||
// import getConfig from "next/config";
|
||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||
import { InternalLayout } from "app/_components/InternalLayout";
|
||||
import { headers } from 'next/headers'
|
||||
import { InternalLayout } from "../_components/InternalLayout";
|
||||
|
||||
type LayoutProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export default function Layout({ children }: LayoutProps) {
|
||||
const allHeaders = headers();
|
||||
const embedded = Boolean(allHeaders.get('x-leafcutter-embedded'));
|
||||
|
||||
return <InternalLayout embedded={embedded}>{children}</InternalLayout>;
|
||||
return <InternalLayout embedded={false}>{children}</InternalLayout>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "app/_lib/auth";
|
||||
import { getUserVisualizations } from "app/_lib/opensearch";
|
||||
import { Home } from "app/_components/Home";
|
||||
import { Home } from "leafcutter-common";
|
||||
|
||||
export default async function Page() {
|
||||
const session = await getServerSession(authOptions);
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
// import { Client } from "@opensearch-project/opensearch";
|
||||
import { Preview } from "./_components/Preview";
|
||||
import { Preview } from "leafcutter-common";
|
||||
// import { createVisualization } from "lib/opensearch";
|
||||
|
||||
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 { Grid, CircularProgress } from "@mui/material";
|
||||
import Iframe from "react-iframe";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import { useAppContext } from "../../../_components/AppProvider";
|
||||
|
||||
export const Setup: FC = () => {
|
||||
const {
|
||||
|
|
@ -20,6 +20,7 @@ export const Setup: FC = () => {
|
|||
<Grid
|
||||
sx={{ width: "100%", height: 700 }}
|
||||
direction="row"
|
||||
container
|
||||
justifyContent="space-around"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { Setup } from './_components/Setup';
|
||||
import { Setup } from "./_components/Setup";
|
||||
|
||||
export default function Page() {
|
||||
return <Setup />;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { getTrends } from "app/_lib/opensearch";
|
||||
import { Trends } from "./_components/Trends";
|
||||
import { Trends } from "../../../../../packages/leafcutter-common/components/Trends";
|
||||
|
||||
export default async function Page() {
|
||||
const visualizations = await getTrends(25);
|
||||
|
||||
return <Trends visualizations={visualizations} />;
|
||||
}
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable no-underscore-dangle */
|
||||
import { Client } from "@opensearch-project/opensearch";
|
||||
import { VisualizationDetail } from "app/_components/VisualizationDetail";
|
||||
import { VisualizationDetail } from "leafcutter-common";
|
||||
|
||||
const getVisualization = async (visualizationID: string) => {
|
||||
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 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 visualization = {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
useState,
|
||||
PropsWithChildren,
|
||||
} from "react";
|
||||
import { colors, typography } from "app/_styles/theme";
|
||||
import { colors, typography } from "leafcutter-common/styles/theme";
|
||||
|
||||
const basePath = process.env.GITLAB_CI
|
||||
? "/link/link-stack/apps/leafcutter"
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import CookieConsent from "react-cookie-consent";
|
|||
import { useCookies } from "react-cookie";
|
||||
import { TopNav } from "./TopNav";
|
||||
import { Sidebar } from "./Sidebar";
|
||||
import { GettingStartedDialog } from "./GettingStartedDialog";
|
||||
import { GettingStartedDialog } from "leafcutter-common";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
// import { Footer } from "./Footer";
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ import { CookiesProvider } from "react-cookie";
|
|||
import { I18n } from "react-polyglot";
|
||||
import { AdapterDateFns } from "@mui/x-date-pickers-pro/AdapterDateFns";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers-pro";
|
||||
import { AppProvider } from "app/_components/AppProvider";
|
||||
import { AppProvider } from "./AppProvider";
|
||||
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";
|
||||
import en from "app/_locales/en.json";
|
||||
import fr from "app/_locales/fr.json";
|
||||
import en from "leafcutter-common/locales/en.json";
|
||||
import fr from "leafcutter-common/locales/fr.json";
|
||||
import "@fontsource/poppins/400.css";
|
||||
import "@fontsource/poppins/700.css";
|
||||
import "@fontsource/roboto/400.css";
|
||||
|
|
@ -21,7 +21,7 @@ import "app/_styles/global.css";
|
|||
import { LicenseInfo } from "@mui/x-date-pickers-pro";
|
||||
|
||||
LicenseInfo.setLicenseKey(
|
||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI="
|
||||
"7c9bf25d9e240f76e77cbf7d2ba58a23Tz02NjU4OCxFPTE3MTU4NjIzMzQ2ODgsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||
);
|
||||
|
||||
const messages: any = { en, fr };
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import {
|
|||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import { useAppContext } from "app/_components/AppProvider";
|
||||
import { Tooltip } from "app/_components/Tooltip";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
import { Tooltip } from "leafcutter-common";
|
||||
// import { ArrowCircleRight as ArrowCircleRightIcon } from "@mui/icons-material";
|
||||
|
||||
const MenuItem = ({
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import Image from "next/legacy/image";
|
|||
import { AppBar, Grid, Box } from "@mui/material";
|
||||
import { useTranslate } from "react-polyglot";
|
||||
import LeafcutterLogo from "images/leafcutter-logo.png";
|
||||
import { AccountButton } from "app/_components/AccountButton";
|
||||
import { HelpButton } from "app/_components/HelpButton";
|
||||
import { Tooltip } from "app/_components/Tooltip";
|
||||
import { AccountButton } from "./AccountButton";
|
||||
import { HelpButton } from "./HelpButton";
|
||||
import { Tooltip } from "leafcutter-common";
|
||||
import { useAppContext } from "./AppProvider";
|
||||
// import { LanguageSelect } from "./LanguageSelect";
|
||||
|
||||
|
|
@ -43,50 +43,51 @@ export const TopNav: FC = () => {
|
|||
wrap="nowrap"
|
||||
spacing={4}
|
||||
>
|
||||
<Link href="/" passHref>
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
spacing={1}
|
||||
wrap="nowrap"
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<Grid item sx={{ pr: 1 }}>
|
||||
<Image src={LeafcutterLogo} alt="" width={56} height={52} />
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="flex-start"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
spacing={1}
|
||||
wrap="nowrap"
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<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 item container direction="column" alignContent="flex-start">
|
||||
<Grid item>
|
||||
<Box
|
||||
sx={{
|
||||
...h5,
|
||||
color: leafcutterElectricBlue,
|
||||
p: 0,
|
||||
m: 0,
|
||||
pt: 1,
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
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 item>
|
||||
<Box
|
||||
sx={{
|
||||
...h6,
|
||||
m: 0,
|
||||
p: 0,
|
||||
color: cdrLinkOrange,
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
A Project of Center for Digital Resilience
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Link>
|
||||
</Grid>
|
||||
|
||||
<Grid item>
|
||||
<HelpButton />
|
||||
</Grid>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import type { NextAuthOptions } from "next-auth";
|
||||
import Google from "next-auth/providers/google";
|
||||
import Apple from "next-auth/providers/apple";
|
||||
import Credentials from "next-auth/providers/credentials";
|
||||
import { checkAuth } from "./opensearch";
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
error: "/login",
|
||||
signOut: "/logout",
|
||||
},
|
||||
providers: [
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID ?? "",
|
||||
|
|
@ -12,6 +19,62 @@ export const authOptions: NextAuthOptions = {
|
|||
clientId: process.env.APPLE_CLIENT_ID ?? "",
|
||||
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,
|
||||
/*
|
||||
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 getEmbedURL = (tenant: string, visualizationID: string) =>
|
||||
|
|
|
|||
|
|
@ -3,3 +3,7 @@ body {
|
|||
overscroll-behavior-y: 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 () => {
|
||||
const results = await getTrends(5);
|
||||
console.log({ 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 getConfig from "next/config";
|
||||
// import { LicenseInfo } from "@mui/x-data-grid-pro";
|
||||
import { MultiProvider } from "app/_components/MultiProvider";
|
||||
import { MultiProvider } from "./_components/MultiProvider";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Leafcutter",
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ apiVersion: v2
|
|||
name: leafcutter
|
||||
description: A Helm chart for Kubernetes
|
||||
type: application
|
||||
version: 0.1.54
|
||||
appVersion: "0.1.54"
|
||||
version: 0.2.0
|
||||
appVersion: "0.2.0"
|
||||
|
|
|
|||
|
|
@ -1,57 +1,6 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { withAuth, NextRequestWithAuth } from "next-auth/middleware";
|
||||
import getConfig from "next/config";
|
||||
|
||||
const rewriteURL = (request: NextRequestWithAuth, originBaseURL: string, destinationBaseURL: string, headers: any = {}) => {
|
||||
if (request.nextUrl.protocol.startsWith('ws')) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
if (request.nextUrl.pathname.includes('/_next/static/development/')) {
|
||||
return NextResponse.next();
|
||||
}
|
||||
|
||||
const destinationURL = request.url.replace(originBaseURL, destinationBaseURL);
|
||||
console.log(`Rewriting ${request.url} to ${destinationURL}`);
|
||||
|
||||
const requestHeaders = new Headers(request.headers);
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
// @ts-ignore
|
||||
requestHeaders.set(key, value);
|
||||
}
|
||||
requestHeaders.delete('connection');
|
||||
|
||||
// console.log({ finalHeaders: requestHeaders });
|
||||
|
||||
return NextResponse.rewrite(new URL(destinationURL), { request: { headers: requestHeaders } });
|
||||
};
|
||||
|
||||
const checkRewrites = async (request: NextRequestWithAuth) => {
|
||||
console.log({ currentURL: request.nextUrl.href });
|
||||
|
||||
const leafcutterBaseURL = process.env.LEAFCUTTER_URL ?? "http://localhost:3000";
|
||||
const opensearchDashboardsURL = process.env.OPENSEARCH_URL ?? "http://localhost:5602";
|
||||
|
||||
if (request.nextUrl.pathname.startsWith('/proxy/opensearch')) {
|
||||
console.log('proxying to zammad');
|
||||
const { token } = request.nextauth;
|
||||
const auth = `${token?.email?.toLowerCase()}:${process.env.OPENSEARCH_USER_PASSWORD}`;
|
||||
const buff = Buffer.from(auth);
|
||||
const base64data = buff.toString("base64");
|
||||
const headers = {
|
||||
'X-Proxy-User': token?.email?.toLowerCase(),
|
||||
"X-Proxy-Roles": "leafcutter_user",
|
||||
"Authorization": `Basic ${base64data}`
|
||||
};
|
||||
|
||||
console.log({ headers });
|
||||
|
||||
return rewriteURL(request, `${leafcutterBaseURL}/proxy/opensearch`, opensearchDashboardsURL, headers);
|
||||
}
|
||||
};
|
||||
import { withAuth } from "next-auth/middleware";
|
||||
|
||||
export default withAuth(
|
||||
checkRewrites,
|
||||
{
|
||||
pages: {
|
||||
signIn: `/login`,
|
||||
|
|
@ -60,25 +9,30 @@ export default withAuth(
|
|||
authorized: ({ token, req }) => {
|
||||
const {
|
||||
url,
|
||||
headers,
|
||||
} = req;
|
||||
|
||||
// check login page
|
||||
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;
|
||||
}
|
||||
|
||||
// check session auth
|
||||
const authorizedDomains = ["redaranj.com", "digiresilience.org"];
|
||||
const userDomain = token?.email?.toLowerCase().split("@").pop() ?? "unauthorized.net";
|
||||
|
||||
if (authorizedDomains.includes(userDomain)) {
|
||||
if (token?.email) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 = {
|
||||
publicRuntimeConfig: {
|
||||
embedded: true
|
||||
},/*
|
||||
transpilePackages: ["leafcutter-common"],
|
||||
rewrites: async () => ({
|
||||
fallback: [
|
||||
{
|
||||
source: "/:path*",
|
||||
destination: "/api/proxy/:path*",
|
||||
},
|
||||
],
|
||||
}),
|
||||
/*
|
||||
basePath: "/proxy/leafcutter",
|
||||
assetPrefix: "/proxy/leafcutter",
|
||||
i18n: {
|
||||
|
|
@ -17,25 +24,19 @@ module.exports = {
|
|||
defaultLocale: "en",
|
||||
},
|
||||
*/
|
||||
/* rewrites: async () => ({
|
||||
fallback: [
|
||||
{
|
||||
source: "/:path*",
|
||||
destination: "/api/proxy/:path*",
|
||||
},
|
||||
],
|
||||
}) */
|
||||
|
||||
/*
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: [
|
||||
/*
|
||||
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: ContentSecurityPolicy.replace(/\s{2,}/g, ' ').trim()
|
||||
},
|
||||
*/
|
||||
|
||||
{
|
||||
key: 'Strict-Transport-Security',
|
||||
value: 'max-age=63072000; includeSubDomains; preload'
|
||||
|
|
@ -52,4 +53,5 @@ module.exports = {
|
|||
},
|
||||
]
|
||||
}
|
||||
*/
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
"login": "aws sso login --sso-session cdr",
|
||||
"kubeconfig": "aws eks update-kubeconfig --name cdr-leafcutter-dashboard-cluster --profile cdr-leafcutter-dashboard-production",
|
||||
"fwd:opensearch": "kubectl port-forward opensearch-cluster-master-0 9200:9200 --namespace leafcutter",
|
||||
"fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-vgmtf 5602:5601 --namespace leafcutter",
|
||||
"fwd:dashboards": "kubectl port-forward opensearch-dashboards-1-59854cdb9b-mx4qq 5602:5601 --namespace leafcutter",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "next export",
|
||||
|
|
@ -21,44 +21,46 @@
|
|||
"@fontsource/poppins": "^5.0.8",
|
||||
"@fontsource/roboto": "^5.0.8",
|
||||
"@mui/icons-material": "^5",
|
||||
"@mui/lab": "^5.0.0-alpha.138",
|
||||
"@mui/lab": "^5.0.0-alpha.140",
|
||||
"@mui/material": "^5",
|
||||
"@mui/x-data-grid-pro": "^6.11.0",
|
||||
"@mui/x-date-pickers-pro": "^6.11.0",
|
||||
"@mui/x-data-grid-pro": "^6.11.1",
|
||||
"@mui/x-date-pickers-pro": "^6.11.1",
|
||||
"@opensearch-project/opensearch": "^2.3.1",
|
||||
"cryptr": "^6.2.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"http-proxy-middleware": "^2.0.6",
|
||||
"leafcutter-common": "*",
|
||||
"material-ui-popup-state": "^5.0.9",
|
||||
"next": "13.4.13",
|
||||
"next-auth": "^4.22.4",
|
||||
"next": "13.4.7",
|
||||
"next-auth": "^4.23.1",
|
||||
"next-http-proxy-middleware": "^1.2.5",
|
||||
"nodemailer": "^6.9.4",
|
||||
"react": "18.2.0",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-cookie": "^5.0.0",
|
||||
"react-cookie-consent": "^8.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-iframe": "^1.8.5",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-polyglot": "^0.7.2",
|
||||
"sharp": "^0.32.4",
|
||||
"swr": "^2.2.0",
|
||||
"sharp": "^0.32.5",
|
||||
"swr": "^2.2.1",
|
||||
"tss-react": "^4.8.8",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.9",
|
||||
"@types/node": "^20.4.8",
|
||||
"@types/react": "18.2.18",
|
||||
"@babel/core": "^7.22.10",
|
||||
"@types/node": "^20.5.0",
|
||||
"@types/react": "18.2.20",
|
||||
"@types/uuid": "^9.0.2",
|
||||
"babel-loader": "^9.1.3",
|
||||
"eslint": "^8.46.0",
|
||||
"eslint": "^8.47.0",
|
||||
"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-plugin-import": "^2.28.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.33.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"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"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,24 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { Box, Grid, Container, IconButton } from "@mui/material";
|
||||
import { Apple as AppleIcon, Google as GoogleIcon } from "@mui/icons-material";
|
||||
import { FC, useState } from "react";
|
||||
import {
|
||||
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 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 = {
|
||||
session: any;
|
||||
|
|
@ -14,62 +29,198 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
|||
typeof window !== "undefined" && 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 = {
|
||||
borderRadius: 500,
|
||||
width: "100%",
|
||||
fontSize: "16px",
|
||||
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 (
|
||||
<>
|
||||
<Grid container direction="row-reverse" sx={{ p: 3 }}>
|
||||
<Grid item />
|
||||
</Grid>
|
||||
<Container maxWidth="md" sx={{ mt: 3, mb: 20 }}>
|
||||
<Box sx={{ backgroundColor: darkGray, height: "100vh" }}>
|
||||
<Container maxWidth="md" sx={{ p: 10 }}>
|
||||
<Grid container spacing={2} direction="column" alignItems="center">
|
||||
<Grid item>
|
||||
<Box sx={{ maxWidth: 200 }} />
|
||||
</Grid>
|
||||
<Grid item sx={{ textAlign: "center" }} />
|
||||
|
||||
<Grid item>
|
||||
{!session ? (
|
||||
<Grid
|
||||
container
|
||||
spacing={3}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
sx={{ width: 450, mt: 1 }}
|
||||
<Grid
|
||||
item
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Grid item>
|
||||
<Box
|
||||
sx={{
|
||||
width: "70px",
|
||||
height: "70px",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
>
|
||||
<Grid item sx={{ width: "100%" }}>
|
||||
<IconButton
|
||||
sx={buttonStyles}
|
||||
onClick={() =>
|
||||
signIn("google", {
|
||||
callbackUrl: `${origin}/setup`,
|
||||
})
|
||||
}
|
||||
>
|
||||
<GoogleIcon sx={{ mr: 1 }} />
|
||||
Google
|
||||
</IconButton>
|
||||
<Image
|
||||
src={LinkLogo}
|
||||
alt="Link logo"
|
||||
width={70}
|
||||
height={70}
|
||||
style={{
|
||||
objectFit: "cover",
|
||||
filter: "grayscale(100) brightness(100)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</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 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>
|
||||
</Container>
|
||||
) : null}
|
||||
{session ? (
|
||||
<Box component="h4">
|
||||
|
|
@ -79,6 +230,6 @@ export const Login: FC<LoginProps> = ({ session }) => {
|
|||
</Grid>
|
||||
</Grid>
|
||||
</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 {
|
||||
FeaturedPlayList as FeaturedPlayListIcon,
|
||||
Person as PersonIcon,
|
||||
Analytics as AnalyticsIcon,
|
||||
Insights as InsightsIcon,
|
||||
Logout as LogoutIcon,
|
||||
Cottage as CottageIcon,
|
||||
Settings as SettingsIcon,
|
||||
ExpandCircleDown as ExpandCircleDownIcon,
|
||||
Dvr as DvrIcon,
|
||||
Assessment as AssessmentIcon,
|
||||
LibraryBooks as LibraryBooksIcon,
|
||||
} from "@mui/icons-material";
|
||||
import { usePathname } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
|
|
@ -161,7 +163,10 @@ interface SidebarProps {
|
|||
export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
||||
const pathname = usePathname();
|
||||
const { data: session } = useSession();
|
||||
console.log({ session });
|
||||
const username = session?.user?.name || "User";
|
||||
// @ts-ignore
|
||||
const roles = session?.user?.roles || [];
|
||||
const { data: overviewData, error: overviewError }: any = useSWR(
|
||||
{
|
||||
document: getTicketOverviewCountsQuery,
|
||||
|
|
@ -419,17 +424,25 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
|||
<MenuItem
|
||||
name="Knowledge Base"
|
||||
href="/knowledge"
|
||||
Icon={CottageIcon}
|
||||
Icon={LibraryBooksIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/knowledge")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Reporting"
|
||||
href="/reporting"
|
||||
Icon={AssessmentIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/reporting")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Leafcutter"
|
||||
href="/leafcutter"
|
||||
Icon={AnalyticsIcon}
|
||||
Icon={InsightsIcon}
|
||||
iconSize={20}
|
||||
selected={pathname.endsWith("/leafcutter")}
|
||||
selected={false}
|
||||
open={open}
|
||||
/>
|
||||
<Collapse
|
||||
|
|
@ -471,7 +484,7 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
|||
<MenuItem
|
||||
name="About"
|
||||
href="/leafcutter/about"
|
||||
Icon={AnalyticsIcon}
|
||||
Icon={InsightsIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/leafcutter/about")}
|
||||
open={open}
|
||||
|
|
@ -486,49 +499,57 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
|||
selected={pathname.endsWith("/profile")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
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>
|
||||
{roles.includes("admin") && (
|
||||
<>
|
||||
<MenuItem
|
||||
name="Zammad Settings"
|
||||
name="Admin"
|
||||
href="/admin/zammad"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/zammad")}
|
||||
Icon={SettingsIcon}
|
||||
iconSize={20}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Metamigo"
|
||||
href="/admin/metamigo"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/metamigo")}
|
||||
open={open}
|
||||
/>
|
||||
<MenuItem
|
||||
name="Label Studio"
|
||||
href="/admin/label-studio"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/label-studio")}
|
||||
open={open}
|
||||
/>
|
||||
</List>
|
||||
</Collapse>
|
||||
<Collapse
|
||||
in={pathname.startsWith("/admin/")}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
onClick={undefined}
|
||||
>
|
||||
<List component="div" disablePadding>
|
||||
<MenuItem
|
||||
name="Zammad Settings"
|
||||
href="/admin/zammad"
|
||||
Icon={FeaturedPlayListIcon}
|
||||
iconSize={0}
|
||||
selected={pathname.endsWith("/admin/zammad")}
|
||||
open={open}
|
||||
/>
|
||||
{false && roles.includes("metamigo") && (
|
||||
<MenuItem
|
||||
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
|
||||
name="Zammad Interface"
|
||||
href="/proxy/zammad"
|
||||
href="/zammad"
|
||||
Icon={DvrIcon}
|
||||
iconSize={20}
|
||||
open={open}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
}) => {
|
||||
const router = useRouter();
|
||||
const [display, setDisplay] = useState("none");
|
||||
const url = `/proxy/zammad${path}`;
|
||||
console.log({ url });
|
||||
const url = `/zammad${path}`;
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
|
|
@ -33,8 +32,7 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
linkElement.contentDocument &&
|
||||
linkElement.contentDocument?.querySelector &&
|
||||
linkElement.contentDocument.querySelector("#navigation") &&
|
||||
linkElement.contentDocument.querySelector("body") &&
|
||||
linkElement.contentDocument.querySelector(".sidebar")
|
||||
linkElement.contentDocument.querySelector("body")
|
||||
) {
|
||||
// @ts-ignore
|
||||
linkElement.contentDocument.querySelector("#navigation").style =
|
||||
|
|
@ -43,7 +41,10 @@ export const ZammadWrapper: FC<ZammadWrapperProps> = ({
|
|||
linkElement.contentDocument.querySelector("body").style =
|
||||
"font-family: Arial";
|
||||
|
||||
if (hideSidebar) {
|
||||
if (
|
||||
hideSidebar &&
|
||||
linkElement.contentDocument.querySelector(".sidebar")
|
||||
) {
|
||||
// @ts-ignore
|
||||
linkElement.contentDocument.querySelector(".sidebar").style =
|
||||
"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 { useApolloClient } from "@apollo/client";
|
||||
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 { theme } from "./layout/themes";
|
||||
import { Layout } from "./layout";
|
||||
import englishMessages from "../_i18n/en";
|
||||
import users from "./users";
|
||||
import accounts from "./accounts";
|
||||
import whatsappBots from "./whatsapp/bots";
|
||||
import whatsappMessages from "./whatsapp/messages";
|
||||
import whatsappAttachments from "./whatsapp/attachments";
|
||||
|
|
@ -42,29 +40,25 @@ const MetamigoAdmin: FC = () => {
|
|||
}, [client]);
|
||||
return (
|
||||
dataProvider && (
|
||||
<ThemeProvider theme={muiTheme}>
|
||||
<Admin
|
||||
disableTelemetry
|
||||
dataProvider={dataProvider}
|
||||
layout={Layout}
|
||||
i18nProvider={i18nProvider}
|
||||
// @ts-ignore
|
||||
loginPage={AdminLogin}
|
||||
// @ts-ignore
|
||||
authProvider={authProvider}
|
||||
>
|
||||
<Resource name="webhooks" {...webhooks} />
|
||||
<Resource name="whatsappBots" {...whatsappBots} />
|
||||
<Resource name="whatsappMessages" {...whatsappMessages} />
|
||||
<Resource name="whatsappAttachments" {...whatsappAttachments} />
|
||||
<Resource name="signalBots" {...signalBots} />
|
||||
<Resource name="voiceProviders" {...voiceProviders} />
|
||||
<Resource name="voiceLines" {...voiceLines} />
|
||||
<Resource name="users" {...users} />
|
||||
<Resource name="accounts" {...accounts} />
|
||||
<Resource name="languages" />
|
||||
</Admin>
|
||||
</ThemeProvider>
|
||||
<Admin
|
||||
disableTelemetry
|
||||
dataProvider={dataProvider}
|
||||
layout={Layout}
|
||||
i18nProvider={i18nProvider}
|
||||
// @ts-ignore
|
||||
loginPage={AdminLogin}
|
||||
// @ts-ignore
|
||||
authProvider={authProvider}
|
||||
>
|
||||
<Resource name="webhooks" {...webhooks} />
|
||||
<Resource name="whatsappBots" {...whatsappBots} />
|
||||
<Resource name="whatsappMessages" {...whatsappMessages} />
|
||||
<Resource name="whatsappAttachments" {...whatsappAttachments} />
|
||||
<Resource name="signalBots" {...signalBots} />
|
||||
<Resource name="voiceProviders" {...voiceProviders} />
|
||||
<Resource name="voiceLines" {...voiceLines} />
|
||||
<Resource name="languages" />
|
||||
</Admin>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
|
@ -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";
|
||||
|
||||
import { FC } from "react";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
// import { makeStyles } from "@mui/styles";
|
||||
import {
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from "react-admin";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
/*
|
||||
const useStyles = makeStyles((_theme: any) => ({
|
||||
defaultToolbar: {
|
||||
flex: 1,
|
||||
|
|
@ -22,14 +23,14 @@ const useStyles = makeStyles((_theme: any) => ({
|
|||
justifyContent: "space-between",
|
||||
},
|
||||
}));
|
||||
|
||||
*/
|
||||
type AccountEditToolbarProps = {
|
||||
record?: any;
|
||||
};
|
||||
|
||||
const AccountEditToolbar: FC<AccountEditToolbarProps> = (props) => {
|
||||
const { data: session } = useSession();
|
||||
const classes = useStyles(props);
|
||||
const classes: any = {}; // useStyles(props);
|
||||
return (
|
||||
<Toolbar className={classes.defaultToolbar} {...props}>
|
||||
<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 Typography from "@mui/material/Typography";
|
||||
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({
|
||||
title: {
|
||||
flex: 1,
|
||||
|
|
@ -17,7 +18,7 @@ const useStyles = makeStyles({
|
|||
flex: 1,
|
||||
},
|
||||
});
|
||||
|
||||
*/
|
||||
// eslint-disable-next-line react/display-name
|
||||
const ConfigurationMenu = forwardRef<any, any>((props, ref) => {
|
||||
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 classes = useStyles();
|
||||
const classes: any = {}; // useStyles();
|
||||
const { darkLavender } = colors;
|
||||
return (
|
||||
<AppBar
|
||||
{...props}
|
||||
elevation={1}
|
||||
userMenu={<CustomUserMenu />}
|
||||
elevation={0}
|
||||
userMenu={<div />}
|
||||
position="sticky"
|
||||
sx={{ mt: -1 }}
|
||||
sx={{ mt: -1, backgroundColor: darkLavender }}
|
||||
>
|
||||
<Typography
|
||||
variant="h6"
|
||||
|
|
@ -2,15 +2,11 @@
|
|||
|
||||
/* eslint-disable camelcase */
|
||||
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 { Box } from "@mui/material";
|
||||
import { useTheme } from "@mui/styles";
|
||||
// import { useTheme } from "@mui/styles";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { useTranslate, MenuItemLink } from "react-admin";
|
||||
import users from "../users";
|
||||
import accounts from "../accounts";
|
||||
import webhooks from "../webhooks";
|
||||
import voiceLines from "../voice/voicelines";
|
||||
import voiceProviders from "../voice/providers";
|
||||
|
|
@ -26,11 +22,10 @@ export const Menu: FC = ({ onMenuClick, logout, dense = false }: any) => {
|
|||
menuSecurity: false,
|
||||
});
|
||||
const translate = useTranslate();
|
||||
const theme = useTheme();
|
||||
const theme: any = {}; // useTheme();
|
||||
// @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);
|
||||
// useSelector((state: any) => state.theme); // force rerender on theme change
|
||||
|
||||
const handleToggle = (menu: MenuName) => {
|
||||
setState((state: any) => ({ ...state, [menu]: !state[menu] }));
|
||||
|
|
@ -97,35 +92,6 @@ export const Menu: FC = ({ onMenuClick, logout, dense = false }: any) => {
|
|||
sidebarIsOpen={open}
|
||||
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}
|
||||
</Box>
|
||||
);
|
||||
|
|
@ -8,9 +8,10 @@ import ListItemIcon from "@mui/material/ListItemIcon";
|
|||
import Typography from "@mui/material/Typography";
|
||||
import Collapse from "@mui/material/Collapse";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
// import { makeStyles } from "@mui/styles";
|
||||
import { useTranslate } from "react-admin";
|
||||
|
||||
/*
|
||||
const useStyles = makeStyles((theme: any) => ({
|
||||
icon: { minWidth: theme.spacing(5) },
|
||||
sidebarIsOpen: {
|
||||
|
|
@ -26,7 +27,7 @@ const useStyles = makeStyles((theme: any) => ({
|
|||
},
|
||||
},
|
||||
}));
|
||||
|
||||
*/
|
||||
type SubMenuProps = PropsWithChildren<{
|
||||
dense: boolean;
|
||||
handleToggle: () => void;
|
||||
|
|
@ -46,7 +47,7 @@ export const SubMenu: FC<SubMenuProps> = ({
|
|||
dense,
|
||||
}: any) => {
|
||||
const translate = useTranslate();
|
||||
const classes = useStyles();
|
||||
const classes: any = {}; // = useStyles();
|
||||
|
||||
const header = (
|
||||
// @ts-ignore
|
||||
|
|
@ -45,8 +45,11 @@ export const theme = {
|
|||
},
|
||||
},
|
||||
typography: {
|
||||
h6: { fontSize: 16, fontWeight: 600, color: "#1bb1bb" },
|
||||
fontFamily: "Poppins, sans-serif",
|
||||
fontWeight: 900,
|
||||
h6: { fontSize: 16, fontWeight: 600, color: "red" },
|
||||
},
|
||||
/*
|
||||
overrides: {
|
||||
RaMenuItemLink: {
|
||||
root: {
|
||||
|
|
@ -96,4 +99,5 @@ export const theme = {
|
|||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
};
|
||||
|
|
@ -231,7 +231,7 @@ const VerificationCaptcha = ({
|
|||
.replace(/signalcaptcha:\/\//, "")
|
||||
.replace("“", "")
|
||||
.replace("”", "")
|
||||
.trim()
|
||||
.trim(),
|
||||
);
|
||||
else setCode(value);
|
||||
};
|
||||
|
|
@ -294,7 +294,7 @@ const VerificationCodeInput = ({
|
|||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
setSubmitting(false);
|
||||
const responseBody = await response.json();
|
||||
|
|
@ -302,12 +302,10 @@ const VerificationCodeInput = ({
|
|||
if (response.status === 200) {
|
||||
confirmVerification();
|
||||
} else if (responseBody.message)
|
||||
// @ts-expect-error
|
||||
setSubmissionError(`Error: ${responseBody.message}`);
|
||||
else {
|
||||
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";
|
||||
|
||||
import { makeStyles } from "@mui/styles";
|
||||
// import { makeStyles } from "@mui/styles";
|
||||
import {
|
||||
SimpleForm,
|
||||
TextInput,
|
||||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import { useSession } from "next-auth/react";
|
||||
import { UserRoleInput } from "./shared";
|
||||
|
||||
/*
|
||||
const useStyles = makeStyles((_theme: any) => ({
|
||||
defaultToolbar: {
|
||||
flex: 1,
|
||||
|
|
@ -23,14 +24,16 @@ const useStyles = makeStyles((_theme: any) => ({
|
|||
justifyContent: "space-between",
|
||||
},
|
||||
}));
|
||||
*/
|
||||
|
||||
const UserEditToolbar = (props: any) => {
|
||||
const classes = useStyles();
|
||||
const classes: any = {}; // = useStyles();
|
||||
const redirect = useRedirect();
|
||||
const record = useRecordContext();
|
||||
const {session} = props;
|
||||
const record = useRecordContext();
|
||||
const { session } = props;
|
||||
|
||||
const shouldDisableDelete = !session || !session.user || session.user.id === record.id;
|
||||
const shouldDisableDelete =
|
||||
!session || !session.user || session.user.id === record.id;
|
||||
|
||||
return (
|
||||
<Toolbar className={classes.defaultToolbar}>
|
||||
|
|
@ -6,7 +6,7 @@ import dynamic from "next/dynamic";
|
|||
import MicIcon from "@mui/icons-material/Mic";
|
||||
import StopIcon from "@mui/icons-material/Stop";
|
||||
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 { useStopwatch } from "react-timer-hook";
|
||||
import style from "./MicInput.module.css";
|
||||
|
|
@ -15,10 +15,10 @@ import style from "./MicInput.module.css";
|
|||
const ReactMic = dynamic<any>(
|
||||
() => {
|
||||
throw new Error(
|
||||
"MIC INPUT FEATURE IS DISABLED"
|
||||
"MIC INPUT FEATURE IS DISABLED",
|
||||
); /* return import("react-mic").then((mod) => mod.ReactMic); */
|
||||
},
|
||||
{ ssr: false }
|
||||
{ ssr: false },
|
||||
);
|
||||
|
||||
const blobToDataUri = (blob: Blob) => {
|
||||
|
|
@ -54,7 +54,7 @@ const blobToResult = async (blob: Blob) => {
|
|||
|
||||
const MicInput = (props: any) => {
|
||||
const { seconds, minutes, hours, start, reset, pause } = useStopwatch();
|
||||
const theme = useTheme();
|
||||
const theme: any = {}; // useTheme();
|
||||
const {
|
||||
field: { onChange }, // value
|
||||
} = useInput(props);
|
||||
|
|
@ -21,7 +21,7 @@ type TTSProvider = (voice: any, language: any, prompt: any) => Promise<void>;
|
|||
|
||||
const tts = async (providerId: any): Promise<TTSProvider> => {
|
||||
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 twilioClient = await import("twilio-client");
|
||||
|
|
@ -97,7 +97,7 @@ export const PromptInput = (form: any, ...rest: any[]) => (
|
|||
<TextInput
|
||||
source="promptText"
|
||||
multiline
|
||||
options={{ fullWidth: true }}
|
||||
// options={{ fullWidth: true }}
|
||||
InputProps={{ endAdornment: <TextToSpeechButton form={form} /> }}
|
||||
{...rest}
|
||||
/>
|
||||
|
|
@ -106,7 +106,6 @@ export const PromptInput = (form: any, ...rest: any[]) => (
|
|||
const validateVoice = (_args: any, values: any) => {
|
||||
if (!values.language) return "validation.language";
|
||||
if (!values.voice) return "validation.voice";
|
||||
// @ts-expect-error
|
||||
const availableVoices = TwilioLanguages.voices[values.language];
|
||||
const found =
|
||||
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[]) => {
|
||||
// @ts-expect-error
|
||||
const voice = TwilioLanguages.voices[form.formData.language] || [];
|
||||
return (
|
||||
// @ts-expect-error
|
||||
|
|
@ -140,7 +138,7 @@ const getAvailableNumbers = async (providerId: string) => {
|
|||
return availableNumbers;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Could not fetch available numbers for provider ${providerId} - ${error}`
|
||||
`Could not fetch available numbers for provider ${providerId} - ${error}`,
|
||||
);
|
||||
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 = (source: string) => () =>
|
||||
(
|
||||
<ReferenceInput
|
||||
label="Voice Line"
|
||||
source={source}
|
||||
reference="voiceLines"
|
||||
validate={[required()]}
|
||||
>
|
||||
<SelectInput optionText="number" />
|
||||
</ReferenceInput>
|
||||
);
|
||||
export const VoiceLineSelectInput = (source: string) => () => (
|
||||
<ReferenceInput
|
||||
label="Voice Line"
|
||||
source={source}
|
||||
reference="voiceLines"
|
||||
validate={[required()]}
|
||||
>
|
||||
<SelectInput optionText="number" />
|
||||
</ReferenceInput>
|
||||
);
|
||||
|
||||
export const VoiceLineField = (source: string) => () =>
|
||||
(
|
||||
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
|
||||
<TextField source="number" />
|
||||
</ReferenceField>
|
||||
);
|
||||
export const VoiceLineField = (source: string) => () => (
|
||||
<ReferenceField label="Voice Line" source={source} reference="voiceLines">
|
||||
<TextField source="number" />
|
||||
</ReferenceField>
|
||||
);
|
||||
|
|
@ -55,8 +55,7 @@ export const BackendTypeInput = (props: any) => (
|
|||
|
||||
export const BackendIdInput = (form: any, ...rest: any[]) => {
|
||||
const Component = form.formData.backendType
|
||||
? // @ts-expect-error
|
||||
backendInputComponents[form.formData.backendType]
|
||||
? backendInputComponents[form.formData.backendType]
|
||||
: false;
|
||||
return <>{Component && <Component form={form} {...rest} />}</>;
|
||||
};
|
||||
|
|
@ -65,8 +64,7 @@ export const BackendIdField = (form: any, ...rest: any[]) => {
|
|||
console.log(form);
|
||||
|
||||
const Component = form.record.backendType
|
||||
? // @ts-expect-error
|
||||
backendFieldComponents[form.record.backendType]
|
||||
? backendFieldComponents[form.record.backendType]
|
||||
: false;
|
||||
return <>{Component && <Component form={form} {...rest} />}</>;
|
||||
};
|
||||
|
|
@ -24,7 +24,7 @@ const errorLink = onError(
|
|||
);
|
||||
|
||||
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(),
|
||||
/*
|
||||
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