mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-29 22:19:31 +02:00
use fullDomain from resources in get traefik config
This commit is contained in:
parent
d7c6903a8e
commit
aa4806fb7f
5 changed files with 143 additions and 136 deletions
|
@ -47,9 +47,7 @@ app.prepare().then(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = `/api/v1`;
|
const prefix = `/api/v1`;
|
||||||
if (dev) {
|
externalServer.use(logIncomingMiddleware);
|
||||||
externalServer.use(logIncomingMiddleware);
|
|
||||||
}
|
|
||||||
externalServer.use(prefix, unauthenticated);
|
externalServer.use(prefix, unauthenticated);
|
||||||
externalServer.use(prefix, authenticated);
|
externalServer.use(prefix, authenticated);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import response from "@server/utils/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { sql, inArray } from "drizzle-orm";
|
import { sql, inArray } from "drizzle-orm";
|
||||||
import { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
const listOrgsSchema = z.object({
|
const listOrgsSchema = z.object({
|
||||||
|
@ -32,7 +31,7 @@ export type ListOrgsResponse = {
|
||||||
export async function listOrgs(
|
export async function listOrgs(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction,
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listOrgsSchema.safeParse(req.query);
|
const parsedQuery = listOrgsSchema.safeParse(req.query);
|
||||||
|
@ -40,13 +39,15 @@ export async function listOrgs(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedQuery.error.errors.map((e) => e.message).join(", "),
|
parsedQuery.error.errors.map((e) => e.message).join(", ")
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { limit, offset } = parsedQuery.data;
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
|
||||||
|
logger.debug("here0")
|
||||||
|
|
||||||
// Use the userOrgs passed from the middleware
|
// Use the userOrgs passed from the middleware
|
||||||
const userOrgIds = req.userOrgIds;
|
const userOrgIds = req.userOrgIds;
|
||||||
|
|
||||||
|
@ -65,6 +66,7 @@ export async function listOrgs(
|
||||||
message: "No organizations found for the user",
|
message: "No organizations found for the user",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.OK,
|
||||||
});
|
});
|
||||||
|
logger.debug("here1")
|
||||||
}
|
}
|
||||||
|
|
||||||
const organizations = await db
|
const organizations = await db
|
||||||
|
@ -80,6 +82,8 @@ export async function listOrgs(
|
||||||
.where(inArray(orgs.orgId, userOrgIds));
|
.where(inArray(orgs.orgId, userOrgIds));
|
||||||
const totalCount = totalCountResult[0].count;
|
const totalCount = totalCountResult[0].count;
|
||||||
|
|
||||||
|
logger.debug("here2")
|
||||||
|
|
||||||
return response<ListOrgsResponse>(res, {
|
return response<ListOrgsResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
orgs: organizations,
|
orgs: organizations,
|
||||||
|
@ -99,8 +103,8 @@ export async function listOrgs(
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
"An error occurred...",
|
"An error occurred..."
|
||||||
),
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,100 +2,97 @@ import { Request, Response } from "express";
|
||||||
import db from "@server/db";
|
import db from "@server/db";
|
||||||
import * as schema from "@server/db/schema";
|
import * as schema from "@server/db/schema";
|
||||||
import { DynamicTraefikConfig } from "./configSchema";
|
import { DynamicTraefikConfig } from "./configSchema";
|
||||||
import { and, like, eq, isNotNull } from "drizzle-orm";
|
import { and, eq, isNotNull } from "drizzle-orm";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import config from "@server/config";
|
import config from "@server/config";
|
||||||
|
|
||||||
export async function traefikConfigProvider(_: Request, res: Response) {
|
export async function traefikConfigProvider(
|
||||||
|
_: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const targets = await getAllTargets();
|
const all = await db
|
||||||
const traefikConfig = buildTraefikConfig(targets);
|
.select()
|
||||||
res.status(HttpCode.OK).json(traefikConfig);
|
.from(schema.targets)
|
||||||
|
.innerJoin(
|
||||||
|
schema.resources,
|
||||||
|
eq(schema.targets.resourceId, schema.resources.resourceId)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(schema.targets.enabled, true),
|
||||||
|
isNotNull(schema.resources.fullDomain)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!all.length) {
|
||||||
|
return { http: {} } as DynamicTraefikConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
const middlewareName = "badger";
|
||||||
|
|
||||||
|
const baseDomain = new URL(config.app.base_url).hostname;
|
||||||
|
|
||||||
|
const tls = {
|
||||||
|
certResolver: config.traefik.cert_resolver,
|
||||||
|
...(config.traefik.prefer_wildcard_cert
|
||||||
|
? { domains: [baseDomain, `*.${baseDomain}`] }
|
||||||
|
: {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const http: any = {
|
||||||
|
routers: {},
|
||||||
|
services: {},
|
||||||
|
middlewares: {
|
||||||
|
[middlewareName]: {
|
||||||
|
plugin: {
|
||||||
|
[middlewareName]: {
|
||||||
|
apiBaseUrl: new URL(
|
||||||
|
"/api/v1",
|
||||||
|
`http://${config.server.internal_hostname}:${config.server.internal_port}`
|
||||||
|
).href,
|
||||||
|
appBaseUrl: config.app.base_url,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
for (const item of all) {
|
||||||
|
const target = item.targets;
|
||||||
|
const resource = item.resources;
|
||||||
|
|
||||||
|
const routerName = `${target.targetId}-router`;
|
||||||
|
const serviceName = `${target.targetId}-service`;
|
||||||
|
|
||||||
|
http.routers![routerName] = {
|
||||||
|
entryPoints: [
|
||||||
|
target.ssl
|
||||||
|
? config.traefik.https_entrypoint
|
||||||
|
: config.traefik.http_entrypoint,
|
||||||
|
],
|
||||||
|
middlewares: [middlewareName],
|
||||||
|
service: serviceName,
|
||||||
|
rule: `Host(\`${resource.fullDomain}\`)`,
|
||||||
|
...(target.ssl ? { tls } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
http.services![serviceName] = {
|
||||||
|
loadBalancer: {
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
url: `${target.method}://${target.ip}:${target.port}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(HttpCode.OK).json({ http });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to build traefik config: ${e}`);
|
logger.error(`Failed to build traefik config: ${e}`);
|
||||||
res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
|
return res.status(HttpCode.INTERNAL_SERVER_ERROR).json({
|
||||||
error: "Failed to build traefik config",
|
error: "Failed to build traefik config",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildTraefikConfig(
|
|
||||||
targets: schema.Target[]
|
|
||||||
): DynamicTraefikConfig {
|
|
||||||
if (!targets.length) {
|
|
||||||
return { http: {} } as DynamicTraefikConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
const middlewareName = "badger";
|
|
||||||
|
|
||||||
const baseDomain = new URL(config.app.base_url).hostname;
|
|
||||||
|
|
||||||
const tls = {
|
|
||||||
certResolver: config.traefik.cert_resolver,
|
|
||||||
...(config.traefik.prefer_wildcard_cert
|
|
||||||
? { domains: [baseDomain, `*.${baseDomain}`] }
|
|
||||||
: {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const http: any = {
|
|
||||||
routers: {},
|
|
||||||
services: {},
|
|
||||||
middlewares: {
|
|
||||||
[middlewareName]: {
|
|
||||||
plugin: {
|
|
||||||
[middlewareName]: {
|
|
||||||
apiBaseUrl: new URL(
|
|
||||||
"/api/v1",
|
|
||||||
`http://${config.server.internal_hostname}:${config.server.internal_port}`
|
|
||||||
).href,
|
|
||||||
appBaseUrl: config.app.base_url,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
for (const target of targets) {
|
|
||||||
const routerName = `${target.targetId}-router`;
|
|
||||||
const serviceName = `${target.targetId}-service`;
|
|
||||||
|
|
||||||
http.routers![routerName] = {
|
|
||||||
entryPoints: [
|
|
||||||
target.ssl
|
|
||||||
? config.traefik.https_entrypoint
|
|
||||||
: config.traefik.http_entrypoint,
|
|
||||||
],
|
|
||||||
middlewares: [middlewareName],
|
|
||||||
service: serviceName,
|
|
||||||
rule: `Host(\`${target.resourceId}\`)`, // assuming resourceId is a valid full hostname
|
|
||||||
...(target.ssl ? { tls } : {}),
|
|
||||||
};
|
|
||||||
|
|
||||||
http.services![serviceName] = {
|
|
||||||
loadBalancer: {
|
|
||||||
servers: [
|
|
||||||
{ url: `${target.method}://${target.ip}:${target.port}` },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { http } as DynamicTraefikConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllTargets(): Promise<schema.Target[]> {
|
|
||||||
const all = await db
|
|
||||||
.select()
|
|
||||||
.from(schema.targets)
|
|
||||||
.innerJoin(
|
|
||||||
schema.resources,
|
|
||||||
eq(schema.targets.resourceId, schema.resources.resourceId)
|
|
||||||
)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(schema.targets.enabled, true),
|
|
||||||
isNotNull(schema.resources.fullDomain)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return all.map((row) => row.targets);
|
|
||||||
}
|
|
||||||
|
|
|
@ -24,24 +24,24 @@ export default async function RootLayout({
|
||||||
}>) {
|
}>) {
|
||||||
const user = await verifySession();
|
const user = await verifySession();
|
||||||
|
|
||||||
|
console.log("layout.tsx user", user);
|
||||||
|
|
||||||
let orgs: ListOrgsResponse["orgs"] = [];
|
let orgs: ListOrgsResponse["orgs"] = [];
|
||||||
if (user) {
|
// if (user) {
|
||||||
try {
|
// try {
|
||||||
const res = await internal.get<AxiosResponse<ListOrgsResponse>>(
|
// const res = await internal.get<AxiosResponse<ListOrgsResponse>>(
|
||||||
`/orgs`,
|
// `/orgs`,
|
||||||
await authCookieHeader(),
|
// await authCookieHeader()
|
||||||
);
|
// );
|
||||||
if (res && res.data.data.orgs) {
|
// if (res && res.data.data.orgs) {
|
||||||
orgs = res.data.data.orgs;
|
// orgs = res.data.data.orgs;
|
||||||
}
|
// }
|
||||||
|
// } catch {}
|
||||||
if (!orgs.length) {
|
|
||||||
redirect(`/setup`);
|
// if (!orgs.length) {
|
||||||
}
|
// redirect(`/setup`);
|
||||||
} catch (e) {
|
// }
|
||||||
console.error("Error fetching orgs", e);
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html suppressHydrationWarning>
|
<html suppressHydrationWarning>
|
||||||
|
|
|
@ -8,26 +8,30 @@ import { ArrowUpLeft, ArrowUpRight } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page(props: {
|
||||||
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
|
}) {
|
||||||
|
|
||||||
|
const params = await props.searchParams;
|
||||||
const user = await verifySession();
|
const user = await verifySession();
|
||||||
|
|
||||||
|
console.log("page.tsx user", user);
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
redirect("/auth/login");
|
// redirect("/auth/login");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let orgs: ListOrgsResponse["orgs"] = [];
|
// let orgs: ListOrgsResponse["orgs"] = [];
|
||||||
try {
|
// try {
|
||||||
const res = await internal.get<AxiosResponse<ListOrgsResponse>>(
|
// const res = await internal.get<AxiosResponse<ListOrgsResponse>>(
|
||||||
`/orgs`,
|
// `/orgs`,
|
||||||
await authCookieHeader(),
|
// await authCookieHeader()
|
||||||
);
|
// );
|
||||||
if (res && res.data.data.orgs) {
|
// if (res && res.data.data.orgs) {
|
||||||
orgs = res.data.data.orgs;
|
// orgs = res.data.data.orgs;
|
||||||
}
|
// }
|
||||||
} catch (e) {
|
// } catch (e) {}
|
||||||
console.error("Error fetching orgs", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -35,16 +39,20 @@ export default async function Page() {
|
||||||
<p>Logged in as {user.email}</p>
|
<p>Logged in as {user.email}</p>
|
||||||
</LandingProvider>
|
</LandingProvider>
|
||||||
|
|
||||||
<div className="mt-4">
|
{/* <div className="mt-4">
|
||||||
{orgs.map((org) => (
|
{orgs.map((org) => (
|
||||||
<Link key={org.orgId} href={`/${org.orgId}`} className="text-primary underline">
|
<Link
|
||||||
<div className="flex items-center">
|
key={org.orgId}
|
||||||
{org.name}
|
href={`/${org.orgId}`}
|
||||||
<ArrowUpRight className="w-4 h-4"/>
|
className="text-primary underline"
|
||||||
</div>
|
>
|
||||||
</Link>
|
<div className="flex items-center">
|
||||||
))}
|
{org.name}
|
||||||
</div>
|
<ArrowUpRight className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div> */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue