mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-07 03:06:40 +00:00
Merge branch 'dev' into feat/internal-user-passkey-support
This commit is contained in:
49
.github/workflows/test.yml
vendored
Normal file
49
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
name: Run Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Copy config file
|
||||||
|
run: cp config/config.example.yml config/config.yml
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Generate database migrations
|
||||||
|
run: npm run db:sqlite:generate
|
||||||
|
|
||||||
|
- name: Apply database migrations
|
||||||
|
run: npm run db:sqlite:push
|
||||||
|
|
||||||
|
- name: Start app in background
|
||||||
|
run: nohup npm run dev &
|
||||||
|
|
||||||
|
- name: Wait for app availability
|
||||||
|
run: |
|
||||||
|
for i in {1..5}; do
|
||||||
|
if curl --silent --fail http://localhost:3002/auth/login; then
|
||||||
|
echo "App is up"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Waiting for the app... attempt $i"
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
echo "App failed to start"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
run: make build
|
||||||
@@ -3,8 +3,8 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# COPY package.json package-lock.json ./
|
# COPY package.json package-lock.json ./
|
||||||
COPY package.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache curl
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
# COPY package.json package-lock.json ./
|
# COPY package.json package-lock.json ./
|
||||||
COPY package.json ./
|
COPY package*.json ./
|
||||||
RUN npm install --only=production && npm cache clean --force
|
RUN npm ci --omit=dev && npm cache clean --force
|
||||||
|
|
||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# COPY package.json package-lock.json ./
|
# COPY package.json package-lock.json ./
|
||||||
COPY package.json ./
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -23,8 +23,8 @@ WORKDIR /app
|
|||||||
RUN apk add --no-cache curl
|
RUN apk add --no-cache curl
|
||||||
|
|
||||||
# COPY package.json package-lock.json ./
|
# COPY package.json package-lock.json ./
|
||||||
COPY package.json ./
|
COPY package*.json ./
|
||||||
RUN npm install --only=production && npm cache clean --force
|
RUN npm ci --omit=dev && npm cache clean --force
|
||||||
|
|
||||||
COPY --from=builder /app/.next/standalone ./
|
COPY --from=builder /app/.next/standalone ./
|
||||||
COPY --from=builder /app/.next/static ./.next/static
|
COPY --from=builder /app/.next/static ./.next/static
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -1,6 +1,8 @@
|
|||||||
|
.PHONY: build build-release build-arm build-x86 test clean
|
||||||
|
|
||||||
build-release:
|
build-release:
|
||||||
@if [ -z "$(tag)" ]; then \
|
@if [ -z "$(tag)" ]; then \
|
||||||
echo "Error: tag is required. Usage: make build-all tag=<tag>"; \
|
echo "Error: tag is required. Usage: make build-release tag=<tag>"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi
|
fi
|
||||||
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:latest -f Dockerfile --push .
|
docker buildx build --platform linux/arm64,linux/amd64 -t fosrl/pangolin:latest -f Dockerfile --push .
|
||||||
|
|||||||
577
package-lock.json
generated
577
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
52
package.json
52
package.json
@@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
"@asteasolutions/zod-to-openapi": "^7.3.4",
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "3.9.1",
|
||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
"@oslojs/crypto": "1.0.1",
|
"@oslojs/crypto": "1.0.1",
|
||||||
"@oslojs/encoding": "1.1.0",
|
"@oslojs/encoding": "1.1.0",
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
"@radix-ui/react-switch": "1.2.5",
|
"@radix-ui/react-switch": "1.2.5",
|
||||||
"@radix-ui/react-tabs": "1.1.12",
|
"@radix-ui/react-tabs": "1.1.12",
|
||||||
"@radix-ui/react-toast": "1.2.14",
|
"@radix-ui/react-toast": "1.2.14",
|
||||||
"@react-email/components": "0.0.41",
|
"@react-email/components": "0.1.0",
|
||||||
"@react-email/render": "^1.1.2",
|
"@react-email/render": "^1.1.2",
|
||||||
"@react-email/tailwind": "1.0.5",
|
"@react-email/tailwind": "1.0.5",
|
||||||
"@simplewebauthn/browser": "^13.1.0",
|
"@simplewebauthn/browser": "^13.1.0",
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"arctic": "^3.7.0",
|
"arctic": "^3.7.0",
|
||||||
"axios": "1.9.0",
|
"axios": "1.10.0",
|
||||||
"better-sqlite3": "11.7.0",
|
"better-sqlite3": "11.7.0",
|
||||||
"canvas-confetti": "1.9.3",
|
"canvas-confetti": "1.9.3",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
@@ -68,12 +68,12 @@
|
|||||||
"cookies": "^0.9.1",
|
"cookies": "^0.9.1",
|
||||||
"cors": "2.8.5",
|
"cors": "2.8.5",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"drizzle-orm": "0.38.3",
|
"drizzle-orm": "0.44.2",
|
||||||
"eslint": "9.28.0",
|
"eslint": "9.29.0",
|
||||||
"eslint-config-next": "15.3.3",
|
"eslint-config-next": "15.3.4",
|
||||||
"express": "4.21.2",
|
"express": "4.21.2",
|
||||||
"express-rate-limit": "7.5.0",
|
"express-rate-limit": "7.5.1",
|
||||||
"glob": "11.0.2",
|
"glob": "11.0.3",
|
||||||
"helmet": "8.1.0",
|
"helmet": "8.1.0",
|
||||||
"http-errors": "2.0.0",
|
"http-errors": "2.0.0",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
@@ -81,41 +81,41 @@
|
|||||||
"jmespath": "^0.16.0",
|
"jmespath": "^0.16.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lucide-react": "^0.525.0",
|
"lucide-react": "0.522.0",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "15.3.3",
|
"next": "15.3.4",
|
||||||
"next-intl": "^4.1.0",
|
"next-intl": "^4.1.0",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"node-cache": "5.1.2",
|
"node-cache": "5.1.2",
|
||||||
"node-fetch": "3.3.2",
|
"node-fetch": "3.3.2",
|
||||||
"nodemailer": "6.9.16",
|
"nodemailer": "7.0.3",
|
||||||
"npm": "^11.4.1",
|
"npm": "^11.4.2",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "^8.16.0",
|
"pg": "^8.16.2",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.0",
|
||||||
"react-easy-sort": "^1.6.0",
|
"react-easy-sort": "^1.6.0",
|
||||||
"react-hook-form": "^7.60.0",
|
"react-hook-form": "7.58.1",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"rebuild": "0.1.2",
|
"rebuild": "0.1.2",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"tailwind-merge": "2.6.0",
|
"tailwind-merge": "3.3.1",
|
||||||
"tw-animate-css": "^1.3.3",
|
"tw-animate-css": "^1.3.3",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
"winston": "3.17.0",
|
"winston": "3.17.0",
|
||||||
"winston-daily-rotate-file": "5.0.0",
|
"winston-daily-rotate-file": "5.0.0",
|
||||||
"ws": "8.18.2",
|
"ws": "8.18.2",
|
||||||
"yargs": "18.0.0",
|
"zod": "3.25.67",
|
||||||
"zod": "^3.25.74",
|
"zod-validation-error": "3.5.2",
|
||||||
"zod-validation-error": "3.4.1"
|
"yargs": "18.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "1.44.1",
|
"@dotenvx/dotenvx": "1.45.1",
|
||||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||||
"@tailwindcss/postcss": "^4.1.8",
|
"@tailwindcss/postcss": "^4.1.10",
|
||||||
"@types/better-sqlite3": "7.6.12",
|
"@types/better-sqlite3": "7.6.12",
|
||||||
"@types/cookie-parser": "1.4.9",
|
"@types/cookie-parser": "1.4.9",
|
||||||
"@types/cors": "2.8.19",
|
"@types/cors": "2.8.19",
|
||||||
@@ -124,25 +124,25 @@
|
|||||||
"@types/express-session": "^1.18.2",
|
"@types/express-session": "^1.18.2",
|
||||||
"@types/jmespath": "^0.15.2",
|
"@types/jmespath": "^0.15.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsonwebtoken": "^9.0.9",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/node": "^22",
|
"@types/node": "^24",
|
||||||
"@types/nodemailer": "6.4.17",
|
"@types/nodemailer": "6.4.17",
|
||||||
"@types/react": "19.1.7",
|
"@types/react": "19.1.8",
|
||||||
"@types/react-dom": "19.1.6",
|
"@types/react-dom": "19.1.6",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/swagger-ui-express": "^4.1.8",
|
"@types/swagger-ui-express": "^4.1.8",
|
||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@types/yargs": "17.0.33",
|
"@types/yargs": "17.0.33",
|
||||||
"drizzle-kit": "0.31.1",
|
"drizzle-kit": "0.31.2",
|
||||||
"esbuild": "0.25.5",
|
"esbuild": "0.25.5",
|
||||||
"esbuild-node-externals": "1.18.0",
|
"esbuild-node-externals": "1.18.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"react-email": "4.0.16",
|
"react-email": "4.0.16",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsx": "4.19.4",
|
"tsx": "4.20.3",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
"typescript-eslint": "^8.34.0"
|
"typescript-eslint": "^8.35.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"emblor": {
|
"emblor": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import semver from "semver";
|
|||||||
import { versionMigrations } from "../db/pg";
|
import { versionMigrations } from "../db/pg";
|
||||||
import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
|
import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import m1 from "./scriptsSqlite/1.6.0";
|
import m1 from "./scriptsPg/1.6.0";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: t('proxyErrorInvalidHeader')
|
message: t("proxyErrorInvalidHeader")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -146,7 +146,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
message: t('proxyErrorTls')
|
message: t("proxyErrorTls")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@@ -203,10 +203,10 @@ export default function ReverseProxyTargets(props: {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('targetErrorFetch'),
|
title: t("targetErrorFetch"),
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
err,
|
err,
|
||||||
t('targetErrorFetchDescription')
|
t("targetErrorFetchDescription")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -228,10 +228,10 @@ export default function ReverseProxyTargets(props: {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('siteErrorFetch'),
|
title: t("siteErrorFetch"),
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
err,
|
err,
|
||||||
t('siteErrorFetchDescription')
|
t("siteErrorFetchDescription")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -251,8 +251,8 @@ export default function ReverseProxyTargets(props: {
|
|||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('targetErrorDuplicate'),
|
title: t("targetErrorDuplicate"),
|
||||||
description: t('targetErrorDuplicateDescription')
|
description: t("targetErrorDuplicateDescription")
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -261,11 +261,23 @@ export default function ReverseProxyTargets(props: {
|
|||||||
// make sure that the target IP is within the site subnet
|
// make sure that the target IP is within the site subnet
|
||||||
const targetIp = data.ip;
|
const targetIp = data.ip;
|
||||||
const subnet = site.subnet;
|
const subnet = site.subnet;
|
||||||
if (!isIPInSubnet(targetIp, subnet)) {
|
try {
|
||||||
|
if (!isIPInSubnet(targetIp, subnet)) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("targetWireGuardErrorInvalidIp"),
|
||||||
|
description: t(
|
||||||
|
"targetWireGuardErrorInvalidIpDescription"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('targetWireGuardErrorInvalidIp'),
|
title: t("targetWireGuardErrorInvalidIp"),
|
||||||
description: t('targetWireGuardErrorInvalidIpDescription')
|
description: t("targetWireGuardErrorInvalidIpDescription")
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -343,8 +355,8 @@ export default function ReverseProxyTargets(props: {
|
|||||||
updateResource({ stickySession: stickySessionData.stickySession });
|
updateResource({ stickySession: stickySessionData.stickySession });
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t('targetsUpdated'),
|
title: t("targetsUpdated"),
|
||||||
description: t('targetsUpdatedDescription')
|
description: t("targetsUpdatedDescription")
|
||||||
});
|
});
|
||||||
|
|
||||||
setTargetsToRemove([]);
|
setTargetsToRemove([]);
|
||||||
@@ -353,10 +365,10 @@ export default function ReverseProxyTargets(props: {
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('targetsErrorUpdate'),
|
title: t("targetsErrorUpdate"),
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
err,
|
err,
|
||||||
t('targetsErrorUpdateDescription')
|
t("targetsErrorUpdateDescription")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -377,17 +389,17 @@ export default function ReverseProxyTargets(props: {
|
|||||||
tlsServerName: data.tlsServerName || null
|
tlsServerName: data.tlsServerName || null
|
||||||
});
|
});
|
||||||
toast({
|
toast({
|
||||||
title: t('targetTlsUpdate'),
|
title: t("targetTlsUpdate"),
|
||||||
description: t('targetTlsUpdateDescription')
|
description: t("targetTlsUpdateDescription")
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('targetErrorTlsUpdate'),
|
title: t("targetErrorTlsUpdate"),
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
err,
|
err,
|
||||||
t('targetErrorTlsUpdateDescription')
|
t("targetErrorTlsUpdateDescription")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -406,17 +418,17 @@ export default function ReverseProxyTargets(props: {
|
|||||||
setHostHeader: data.setHostHeader || null
|
setHostHeader: data.setHostHeader || null
|
||||||
});
|
});
|
||||||
toast({
|
toast({
|
||||||
title: t('proxyUpdated'),
|
title: t("proxyUpdated"),
|
||||||
description: t('proxyUpdatedDescription')
|
description: t("proxyUpdatedDescription")
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t('proxyErrorUpdate'),
|
title: t("proxyErrorUpdate"),
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
err,
|
err,
|
||||||
t('proxyErrorUpdateDescription')
|
t("proxyErrorUpdateDescription")
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
@@ -427,7 +439,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
const columns: ColumnDef<LocalTarget>[] = [
|
const columns: ColumnDef<LocalTarget>[] = [
|
||||||
{
|
{
|
||||||
accessorKey: "ip",
|
accessorKey: "ip",
|
||||||
header: t('targetAddr'),
|
header: t("targetAddr"),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Input
|
<Input
|
||||||
defaultValue={row.original.ip}
|
defaultValue={row.original.ip}
|
||||||
@@ -442,7 +454,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "port",
|
accessorKey: "port",
|
||||||
header: t('targetPort'),
|
header: t("targetPort"),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -476,7 +488,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
header: t('enabled'),
|
header: t("enabled"),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Switch
|
<Switch
|
||||||
defaultChecked={row.original.enabled}
|
defaultChecked={row.original.enabled}
|
||||||
@@ -503,7 +515,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => removeTarget(row.original.targetId)}
|
onClick={() => removeTarget(row.original.targetId)}
|
||||||
>
|
>
|
||||||
{t('delete')}
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -514,7 +526,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
if (resource.http) {
|
if (resource.http) {
|
||||||
const methodCol: ColumnDef<LocalTarget> = {
|
const methodCol: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "method",
|
accessorKey: "method",
|
||||||
header: t('method'),
|
header: t("method"),
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<Select
|
<Select
|
||||||
defaultValue={row.original.method ?? ""}
|
defaultValue={row.original.method ?? ""}
|
||||||
@@ -561,11 +573,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>{t("targets")}</SettingsSectionTitle>
|
||||||
{t('targets')}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t('targetsDescription')}
|
{t("targetsDescription")}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
@@ -587,8 +597,12 @@ export default function ReverseProxyTargets(props: {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<SwitchInput
|
<SwitchInput
|
||||||
id="sticky-toggle"
|
id="sticky-toggle"
|
||||||
label={t('targetStickySessions')}
|
label={t(
|
||||||
description={t('targetStickySessionsDescription')}
|
"targetStickySessions"
|
||||||
|
)}
|
||||||
|
description={t(
|
||||||
|
"targetStickySessionsDescription"
|
||||||
|
)}
|
||||||
defaultChecked={
|
defaultChecked={
|
||||||
field.value
|
field.value
|
||||||
}
|
}
|
||||||
@@ -619,7 +633,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
name="method"
|
name="method"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('method')}</FormLabel>
|
<FormLabel>
|
||||||
|
{t("method")}
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
value={
|
value={
|
||||||
@@ -636,7 +652,11 @@ export default function ReverseProxyTargets(props: {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger id="method">
|
<SelectTrigger id="method">
|
||||||
<SelectValue placeholder={t('methodSelect')} />
|
<SelectValue
|
||||||
|
placeholder={t(
|
||||||
|
"methodSelect"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="http">
|
<SelectItem value="http">
|
||||||
@@ -662,7 +682,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
name="ip"
|
name="ip"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="relative">
|
<FormItem className="relative">
|
||||||
<FormLabel>{t('targetAddr')}</FormLabel>
|
<FormLabel>
|
||||||
|
{t("targetAddr")}
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input id="ip" {...field} />
|
<Input id="ip" {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@@ -695,7 +717,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
name="port"
|
name="port"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t('targetPort')}</FormLabel>
|
<FormLabel>
|
||||||
|
{t("targetPort")}
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
id="port"
|
id="port"
|
||||||
@@ -714,7 +738,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
className="mt-6"
|
className="mt-6"
|
||||||
disabled={!(watchedIp && watchedPort)}
|
disabled={!(watchedIp && watchedPort)}
|
||||||
>
|
>
|
||||||
{t('targetSubmit')}
|
{t("targetSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -758,13 +782,13 @@ export default function ReverseProxyTargets(props: {
|
|||||||
colSpan={columns.length}
|
colSpan={columns.length}
|
||||||
className="h-24 text-center"
|
className="h-24 text-center"
|
||||||
>
|
>
|
||||||
{t('targetNoOne')}
|
{t("targetNoOne")}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
)}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableCaption>
|
<TableCaption>
|
||||||
{t('targetNoOneDescription')}
|
{t("targetNoOneDescription")}
|
||||||
</TableCaption>
|
</TableCaption>
|
||||||
</Table>
|
</Table>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
@@ -775,7 +799,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
disabled={targetsLoading}
|
disabled={targetsLoading}
|
||||||
form="targets-settings-form"
|
form="targets-settings-form"
|
||||||
>
|
>
|
||||||
{t('targetsSubmit')}
|
{t("targetsSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
@@ -785,10 +809,10 @@ export default function ReverseProxyTargets(props: {
|
|||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
{t('targetTlsSettings')}
|
{t("targetTlsSettings")}
|
||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t('targetTlsSettingsDescription')}
|
{t("targetTlsSettingsDescription")}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
@@ -809,7 +833,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<SwitchInput
|
<SwitchInput
|
||||||
id="ssl-toggle"
|
id="ssl-toggle"
|
||||||
label={t('proxyEnableSSL')}
|
label={t(
|
||||||
|
"proxyEnableSSL"
|
||||||
|
)}
|
||||||
defaultChecked={
|
defaultChecked={
|
||||||
field.value
|
field.value
|
||||||
}
|
}
|
||||||
@@ -838,7 +864,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
className="p-0 flex items-center justify-start gap-2 w-full"
|
className="p-0 flex items-center justify-start gap-2 w-full"
|
||||||
>
|
>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t('targetTlsSettingsAdvanced')}
|
{t(
|
||||||
|
"targetTlsSettingsAdvanced"
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<ChevronsUpDown className="h-4 w-4" />
|
<ChevronsUpDown className="h-4 w-4" />
|
||||||
@@ -858,7 +886,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t('targetTlsSni')}
|
{t(
|
||||||
|
"targetTlsSni"
|
||||||
|
)}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -866,7 +896,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t('targetTlsSniDescription')}
|
{t(
|
||||||
|
"targetTlsSniDescription"
|
||||||
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -884,17 +916,17 @@ export default function ReverseProxyTargets(props: {
|
|||||||
loading={httpsTlsLoading}
|
loading={httpsTlsLoading}
|
||||||
form="tls-settings-form"
|
form="tls-settings-form"
|
||||||
>
|
>
|
||||||
{t('targetTlsSubmit')}
|
{t("targetTlsSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
{t('proxyAdditional')}
|
{t("proxyAdditional")}
|
||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t('proxyAdditionalDescription')}
|
{t("proxyAdditionalDescription")}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
@@ -913,13 +945,15 @@ export default function ReverseProxyTargets(props: {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t('proxyCustomHeader')}
|
{t("proxyCustomHeader")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input {...field} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t('proxyCustomHeaderDescription')}
|
{t(
|
||||||
|
"proxyCustomHeaderDescription"
|
||||||
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
@@ -935,7 +969,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
loading={proxySettingsLoading}
|
loading={proxySettingsLoading}
|
||||||
form="proxy-settings-form"
|
form="proxy-settings-form"
|
||||||
>
|
>
|
||||||
{t('targetTlsSubmit')}
|
{t("targetTlsSubmit")}
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
@@ -950,10 +984,8 @@ function isIPInSubnet(subnet: string, ip: string): boolean {
|
|||||||
const [subnetIP, maskBits] = subnet.split("/");
|
const [subnetIP, maskBits] = subnet.split("/");
|
||||||
const mask = parseInt(maskBits);
|
const mask = parseInt(maskBits);
|
||||||
|
|
||||||
const t = useTranslations();
|
|
||||||
|
|
||||||
if (mask < 0 || mask > 32) {
|
if (mask < 0 || mask > 32) {
|
||||||
throw new Error(t('subnetMaskErrorInvalid'));
|
throw new Error("subnetMaskErrorInvalid");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert IP addresses to binary numbers
|
// Convert IP addresses to binary numbers
|
||||||
@@ -970,17 +1002,16 @@ function isIPInSubnet(subnet: string, ip: string): boolean {
|
|||||||
function ipToNumber(ip: string): number {
|
function ipToNumber(ip: string): number {
|
||||||
// Validate IP address format
|
// Validate IP address format
|
||||||
const parts = ip.split(".");
|
const parts = ip.split(".");
|
||||||
const t = useTranslations();
|
|
||||||
|
|
||||||
if (parts.length !== 4) {
|
if (parts.length !== 4) {
|
||||||
throw new Error(t('ipAddressErrorInvalidFormat'));
|
throw new Error("ipAddressErrorInvalidFormat");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert IP octets to 32-bit number
|
// Convert IP octets to 32-bit number
|
||||||
return parts.reduce((num, octet) => {
|
return parts.reduce((num, octet) => {
|
||||||
const oct = parseInt(octet);
|
const oct = parseInt(octet);
|
||||||
if (isNaN(oct) || oct < 0 || oct > 255) {
|
if (isNaN(oct) || oct < 0 || oct > 255) {
|
||||||
throw new Error(t('ipAddressErrorInvalidOctet'));
|
throw new Error("ipAddressErrorInvalidOctet");
|
||||||
}
|
}
|
||||||
return (num << 8) + oct;
|
return (num << 8) + oct;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user