Use NextJS middleware for proxying
This commit is contained in:
parent
4e4603bd71
commit
df9b8abf15
13 changed files with 154 additions and 94 deletions
|
|
@ -68,7 +68,6 @@ export const Question: FC<QuestionProps> = ({ question, answer }) => {
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails sx={{ border: 0 }}>
|
<AccordionDetails sx={{ border: 0 }}>
|
||||||
<Box
|
<Box
|
||||||
component="p"
|
|
||||||
sx={{ ...p, p: 2, border: `1px solid ${lavender}`, borderRadius: 3 }}
|
sx={{ ...p, p: 2, border: `1px solid ${lavender}`, borderRadius: 3 }}
|
||||||
>
|
>
|
||||||
<ReactMarkdown>{answer}</ReactMarkdown>
|
<ReactMarkdown>{answer}</ReactMarkdown>
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,19 @@ module.exports = {
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
embedded: Boolean(process.env.LINK_EMBEDDED),
|
embedded: Boolean(process.env.LINK_EMBEDDED),
|
||||||
},
|
},
|
||||||
|
basePath: "/proxy/leafcutter",
|
||||||
|
assetPrefix: "/proxy/leafcutter",
|
||||||
i18n: {
|
i18n: {
|
||||||
locales: ["en", "fr"],
|
locales: ["en", "fr"],
|
||||||
defaultLocale: "en",
|
defaultLocale: "en",
|
||||||
},
|
},
|
||||||
|
|
||||||
rewrites: async () => ({
|
/* rewrites: async () => ({
|
||||||
fallback: [
|
fallback: [
|
||||||
{
|
{
|
||||||
source: "/:path*",
|
source: "/:path*",
|
||||||
destination: "/api/proxy/:path*",
|
destination: "/api/proxy/:path*",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}) */
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ const FAQ = () => {
|
||||||
question: t("whatCanYouDoWithLeafcutterQuestion"),
|
question: t("whatCanYouDoWithLeafcutterQuestion"),
|
||||||
answer: t("whatCanYouDoWithLeafcutterAnswer"),
|
answer: t("whatCanYouDoWithLeafcutterAnswer"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
question: t("whereIsTheDataComingFromQuestion"),
|
question: t("whereIsTheDataComingFromQuestion"),
|
||||||
answer: t("whereIsTheDataComingFromAnswer"),
|
answer: t("whereIsTheDataComingFromAnswer"),
|
||||||
|
|
@ -43,6 +44,7 @@ const FAQ = () => {
|
||||||
question: t("howDoWeKeepTheDataSafeQuestion"),
|
question: t("howDoWeKeepTheDataSafeQuestion"),
|
||||||
answer: t("howDoWeKeepTheDataSafeAnswer"),
|
answer: t("howDoWeKeepTheDataSafeAnswer"),
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
question: t("howLongDoYouKeepTheDataQuestion"),
|
question: t("howLongDoYouKeepTheDataQuestion"),
|
||||||
answer: t("howLongDoYouKeepTheDataAnswer"),
|
answer: t("howLongDoYouKeepTheDataAnswer"),
|
||||||
|
|
@ -94,7 +96,7 @@ const FAQ = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
{questions.map((q, index) => (
|
{questions.map((q: any, index: number) => (
|
||||||
<Question key={index} question={q.question} answer={q.answer} />
|
<Question key={index} question={q.question} answer={q.answer} />
|
||||||
))}
|
))}
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
|
import getConfig from "next/config";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import Iframe from "react-iframe";
|
import Iframe from "react-iframe";
|
||||||
|
|
||||||
|
|
@ -13,13 +14,16 @@ export const InternalZammadWrapper: FC<InternalZammadWrapperProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [display, setDisplay] = useState("none");
|
const [display, setDisplay] = useState("none");
|
||||||
const url = `${origin}/zammad${path}`;
|
const {
|
||||||
console.log({ origin, path, url });
|
publicRuntimeConfig: { linkURL },
|
||||||
|
} = getConfig();
|
||||||
|
const url = `${linkURL}/proxy/zammad${path}`;
|
||||||
|
console.log({ url });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
<Iframe
|
<Iframe
|
||||||
id="link"
|
id="zammad"
|
||||||
url={url}
|
url={url}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
|
@ -27,6 +31,8 @@ export const InternalZammadWrapper: FC<InternalZammadWrapperProps> = ({
|
||||||
styles={{ display }}
|
styles={{ display }}
|
||||||
onLoad={() => {
|
onLoad={() => {
|
||||||
const linkElement = document.querySelector("iframe");
|
const linkElement = document.querySelector("iframe");
|
||||||
|
// const baseElement = linkElement.contentDocument.createElement("base");
|
||||||
|
// baseElement.href = `${linkURL}/proxy/zammad`;
|
||||||
if (
|
if (
|
||||||
linkElement.contentDocument &&
|
linkElement.contentDocument &&
|
||||||
linkElement.contentDocument?.querySelector &&
|
linkElement.contentDocument?.querySelector &&
|
||||||
|
|
@ -50,15 +56,16 @@ export const InternalZammadWrapper: FC<InternalZammadWrapperProps> = ({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (linkElement.contentDocument.querySelector(".overview-header")) {
|
if (linkElement.contentDocument.querySelector(".overview-header")) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
linkElement.contentDocument.querySelector(".overview-header").style =
|
linkElement.contentDocument.querySelector(
|
||||||
"display: none";
|
".overview-header"
|
||||||
|
).style = "display: none";
|
||||||
}
|
}
|
||||||
|
|
||||||
setDisplay("inherit");
|
setDisplay("inherit");
|
||||||
|
|
||||||
if (linkElement.contentWindow) {
|
if (linkElement.contentWindow) {
|
||||||
linkElement.contentWindow.addEventListener('hashchange', () => {
|
linkElement.contentWindow.addEventListener("hashchange", () => {
|
||||||
const hash = linkElement.contentWindow?.location?.hash ?? ""
|
const hash = linkElement.contentWindow?.location?.hash ?? "";
|
||||||
if (hash.startsWith("#ticket/zoom/")) {
|
if (hash.startsWith("#ticket/zoom/")) {
|
||||||
setDisplay("none");
|
setDisplay("none");
|
||||||
const ticketID = hash.split("/").pop();
|
const ticketID = hash.split("/").pop();
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@ type LeafcutterWrapperProps = {
|
||||||
|
|
||||||
export const LeafcutterWrapper: FC<LeafcutterWrapperProps> = ({ path }) => {
|
export const LeafcutterWrapper: FC<LeafcutterWrapperProps> = ({ path }) => {
|
||||||
const {
|
const {
|
||||||
publicRuntimeConfig: { leafcutterURL },
|
publicRuntimeConfig: { linkURL },
|
||||||
} = getConfig();
|
} = getConfig();
|
||||||
const fullLeafcutterURL = `${leafcutterURL}/${path}`;
|
const fullLeafcutterURL = `${linkURL}/proxy/leafcutter/${path}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
@ -28,7 +28,7 @@ export const LeafcutterWrapper: FC<LeafcutterWrapperProps> = ({ path }) => {
|
||||||
>
|
>
|
||||||
<Grid item sx={{ height: "100vh", width: "100%" }}>
|
<Grid item sx={{ height: "100vh", width: "100%" }}>
|
||||||
<Iframe
|
<Iframe
|
||||||
id="link"
|
id="leafcutter"
|
||||||
url={fullLeafcutterURL}
|
url={fullLeafcutterURL}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
|
|
||||||
41
apps/link/components/MetamigoWrapper.tsx
Normal file
41
apps/link/components/MetamigoWrapper.tsx
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { FC } from "react";
|
||||||
|
import getConfig from "next/config";
|
||||||
|
import Head from "next/head";
|
||||||
|
import { Grid } from "@mui/material";
|
||||||
|
import { Layout } from "components/Layout";
|
||||||
|
import Iframe from "react-iframe";
|
||||||
|
|
||||||
|
type MetamigoWrapperProps = {
|
||||||
|
path: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MetamigoWrapper: FC<MetamigoWrapperProps> = ({ path }) => {
|
||||||
|
const {
|
||||||
|
publicRuntimeConfig: { linkURL },
|
||||||
|
} = getConfig();
|
||||||
|
const fullMetamigoURL = `${linkURL}/metamigo/${path}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<Head>
|
||||||
|
<title>Link Shell</title>
|
||||||
|
</Head>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={0}
|
||||||
|
sx={{ height: "100%", width: "100%" }}
|
||||||
|
direction="column"
|
||||||
|
>
|
||||||
|
<Grid item sx={{ height: "100vh", width: "100%" }}>
|
||||||
|
<Iframe
|
||||||
|
id="link"
|
||||||
|
url={fullMetamigoURL}
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
frameBorder={0}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -1,7 +1,46 @@
|
||||||
|
import { NextResponse } from 'next/server';
|
||||||
|
import type { NextRequest } from 'next/server';
|
||||||
import { withAuth } from "next-auth/middleware";
|
import { withAuth } from "next-auth/middleware";
|
||||||
|
import { getToken } from "next-auth/jwt";
|
||||||
|
|
||||||
|
const rewriteURL = (request: NextRequest, originBaseURL: string, destinationBaseURL: string, headers: any = {}) => {
|
||||||
|
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}`);
|
||||||
|
|
||||||
|
return NextResponse.rewrite(new URL(destinationURL), { ...request.headers, ...headers });
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkRewrites = async (request: NextRequest) => {
|
||||||
|
if (request.nextUrl.pathname.startsWith('/proxy/leafcutter')) {
|
||||||
|
return rewriteURL(request, process.env.LINK_URL, process.env.LEAFCUTTER_URL);
|
||||||
|
} else if (request.nextUrl.pathname.startsWith('/proxy/metamigo')) {
|
||||||
|
return rewriteURL(request, process.env.LINK_URL, process.env.METAMIGO_URL);
|
||||||
|
} else if (request.nextUrl.pathname.startsWith('/proxy/zammad')) {
|
||||||
|
const session = await getToken({
|
||||||
|
req: request,
|
||||||
|
secret: process.env.NEXTAUTH_SECRET,
|
||||||
|
});
|
||||||
|
const headers = {
|
||||||
|
'X-Forwarded-User': session.email.toLowerCase(),
|
||||||
|
host: 'zammad.example.com'
|
||||||
|
};
|
||||||
|
|
||||||
|
return rewriteURL(request, `${process.env.LINK_URL}/proxy/zammad`, process.env.ZAMMAD_URL, headers);
|
||||||
|
} else if (request.nextUrl.pathname.startsWith('/assets')) {
|
||||||
|
console.log('asset');
|
||||||
|
return rewriteURL(request, `${process.env.LINK_URL}`, process.env.ZAMMAD_URL);
|
||||||
|
} else if (request.nextUrl.pathname.startsWith('/proxy/assets') || request.nextUrl.pathname.startsWith('/proxy/api')) {
|
||||||
|
console.log('proxy asset');
|
||||||
|
return rewriteURL(request, `${process.env.LINK_URL}/proxy`, process.env.ZAMMAD_URL);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default withAuth(
|
export default withAuth(
|
||||||
() => { },
|
checkRewrites,
|
||||||
{
|
{
|
||||||
pages: {
|
pages: {
|
||||||
signIn: `/login`,
|
signIn: `/login`,
|
||||||
|
|
@ -32,3 +71,4 @@ export default withAuth(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,10 @@
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
|
linkURL: process.env.LINK_URL,
|
||||||
leafcutterURL: process.env.LEAFCUTTER_URL,
|
leafcutterURL: process.env.LEAFCUTTER_URL,
|
||||||
metamigoURL: process.env.METAMIGO_URL,
|
metamigoURL: process.env.METAMIGO_URL,
|
||||||
},
|
},
|
||||||
rewrites: async () => ({
|
|
||||||
fallback: [
|
|
||||||
{
|
|
||||||
source: "/:path*",
|
|
||||||
destination: "/api/proxy/:path*",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export default function LinkWeb(props: LinkWebProps) {
|
||||||
typeof window !== "undefined" && window.location.origin
|
typeof window !== "undefined" && window.location.origin
|
||||||
? window.location.origin
|
? window.location.origin
|
||||||
: null;
|
: null;
|
||||||
const client = new GraphQLClient(`${origin}/graphql`, {
|
const client = new GraphQLClient(`${origin}/proxy/zammad/graphql`, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
|
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import { NextApiRequest, NextApiResponse } from 'next';
|
|
||||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
|
||||||
import { getToken } from "next-auth/jwt";
|
|
||||||
|
|
||||||
const withAuthInfo =
|
|
||||||
(handler) => async (req: NextApiRequest, res: NextApiResponse) => {
|
|
||||||
const session = await getToken({
|
|
||||||
req,
|
|
||||||
secret: process.env.NEXTAUTH_SECRET,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return res.redirect("/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.headers) {
|
|
||||||
req.headers['X-Forwarded-User'] = session.email.toLowerCase();
|
|
||||||
req.headers['host'] = 'zammad.example.com';
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler(req, res);
|
|
||||||
};
|
|
||||||
|
|
||||||
const proxy = createProxyMiddleware({
|
|
||||||
target: process.env.ZAMMAD_URL,
|
|
||||||
changeOrigin: true,
|
|
||||||
xfwd: true,
|
|
||||||
ws: false,
|
|
||||||
pathRewrite: { "^/zammad": "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default withAuthInfo(proxy);
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
api: {
|
|
||||||
bodyParser: false,
|
|
||||||
externalResolver: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
@ -3,8 +3,18 @@
|
||||||
packages = Dir.glob('/opt/zammad/contrib/link/addons/*')
|
packages = Dir.glob('/opt/zammad/contrib/link/addons/*')
|
||||||
|
|
||||||
packages.each do |package|
|
packages.each do |package|
|
||||||
|
puts "Attempting to uninstall #{package} package..."
|
||||||
|
existing_package = Package.find_by(name: package.name)
|
||||||
|
if existing_package.blank?
|
||||||
|
puts "Package #{name} is not installed."
|
||||||
|
else
|
||||||
|
puts "Uninstalling #{package.name} #{package.version}..."
|
||||||
|
Package.uninstall(name: package.name, version: package.version)
|
||||||
|
puts 'Uninstalled'
|
||||||
|
end
|
||||||
puts "Installing #{package} package..."
|
puts "Installing #{package} package..."
|
||||||
Package.install(file: package)
|
Package.install(file: package)
|
||||||
|
puts 'Installed'
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
puts e.message
|
puts e.message
|
||||||
end
|
end
|
||||||
|
|
|
||||||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -11,7 +11,10 @@
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"prettier": "^2.8.8"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"apps/leafcutter": {
|
"apps/leafcutter": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
|
|
@ -17297,7 +17300,6 @@
|
||||||
"version": "2.8.8",
|
"version": "2.8.8",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||||
"dev": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin-prettier.js"
|
"prettier": "bin-prettier.js"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -27,5 +27,8 @@
|
||||||
"hapi-postgraphile": {
|
"hapi-postgraphile": {
|
||||||
"pg": "^8.11.0"
|
"pg": "^8.11.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^2.8.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue