mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-22 18:29:19 +02:00
Merge branch 'dev' into clients-multi-pop
This commit is contained in:
commit
ae52fcc757
73 changed files with 1857 additions and 2589 deletions
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -15,7 +15,7 @@ jobs:
|
||||||
|
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '22'
|
||||||
|
|
||||||
- name: Copy config file
|
- name: Copy config file
|
||||||
run: cp config/config.example.yml config/config.yml
|
run: cp config/config.example.yml config/config.yml
|
||||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
||||||
20
|
22
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20-alpine
|
FROM node:22-alpine
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ RUN npx drizzle-kit generate --dialect postgresql --schema ./server/db/pg/schema
|
||||||
RUN npm run build:pg
|
RUN npm run build:pg
|
||||||
RUN npm run build:cli
|
RUN npm run build:cli
|
||||||
|
|
||||||
FROM node:20-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:20-alpine AS builder
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ RUN npx drizzle-kit generate --dialect sqlite --schema ./server/db/sqlite/schema
|
||||||
RUN npm run build:sqlite
|
RUN npm run build:sqlite
|
||||||
RUN npm run build:cli
|
RUN npm run build:cli
|
||||||
|
|
||||||
FROM node:20-alpine AS runner
|
FROM node:22-alpine AS runner
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ Pangolin is dual licensed under the AGPL-3 and the Fossorial Commercial license.
|
||||||
|
|
||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Looking for something to contribute? Take a look at issues marked with [help wanted](https://github.com/fosrl/pangolin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22).
|
Looking for something to contribute? Take a look at issues marked with [help wanted](https://github.com/fosrl/pangolin/issues?q=is%3Aissue%20state%3Aopen%20label%3A%22help%20wanted%22). Also take a look through the freature requests in Discussions - any are available and some are marked as a good first issue.
|
||||||
|
|
||||||
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
||||||
|
|
||||||
|
|
67
cli/commands/resetUserSecurityKeys.ts
Normal file
67
cli/commands/resetUserSecurityKeys.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import { CommandModule } from "yargs";
|
||||||
|
import { db, users, securityKeys } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
type ResetUserSecurityKeysArgs = {
|
||||||
|
email: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const resetUserSecurityKeys: CommandModule<{}, ResetUserSecurityKeysArgs> = {
|
||||||
|
command: "reset-user-security-keys",
|
||||||
|
describe: "Reset a user's security keys (passkeys) by deleting all their webauthn credentials",
|
||||||
|
builder: (yargs) => {
|
||||||
|
return yargs
|
||||||
|
.option("email", {
|
||||||
|
type: "string",
|
||||||
|
demandOption: true,
|
||||||
|
describe: "User email address"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handler: async (argv: { email: string }) => {
|
||||||
|
try {
|
||||||
|
const { email } = argv;
|
||||||
|
|
||||||
|
console.log(`Looking for user with email: ${email}`);
|
||||||
|
|
||||||
|
// Find the user by email
|
||||||
|
const [user] = await db
|
||||||
|
.select()
|
||||||
|
.from(users)
|
||||||
|
.where(eq(users.email, email))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
console.error(`User with email '${email}' not found`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found user: ${user.email} (ID: ${user.userId})`);
|
||||||
|
|
||||||
|
// Check if user has any security keys
|
||||||
|
const userSecurityKeys = await db
|
||||||
|
.select()
|
||||||
|
.from(securityKeys)
|
||||||
|
.where(eq(securityKeys.userId, user.userId));
|
||||||
|
|
||||||
|
if (userSecurityKeys.length === 0) {
|
||||||
|
console.log(`User '${email}' has no security keys to reset`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Found ${userSecurityKeys.length} security key(s) for user '${email}'`);
|
||||||
|
|
||||||
|
// Delete all security keys for the user
|
||||||
|
await db
|
||||||
|
.delete(securityKeys)
|
||||||
|
.where(eq(securityKeys.userId, user.userId));
|
||||||
|
|
||||||
|
console.log(`Successfully reset security keys for user '${email}'`);
|
||||||
|
console.log(`Deleted ${userSecurityKeys.length} security key(s)`);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -3,9 +3,11 @@
|
||||||
import yargs from "yargs";
|
import yargs from "yargs";
|
||||||
import { hideBin } from "yargs/helpers";
|
import { hideBin } from "yargs/helpers";
|
||||||
import { setAdminCredentials } from "@cli/commands/setAdminCredentials";
|
import { setAdminCredentials } from "@cli/commands/setAdminCredentials";
|
||||||
|
import { resetUserSecurityKeys } from "@cli/commands/resetUserSecurityKeys";
|
||||||
|
|
||||||
yargs(hideBin(process.argv))
|
yargs(hideBin(process.argv))
|
||||||
.scriptName("pangctl")
|
.scriptName("pangctl")
|
||||||
.command(setAdminCredentials)
|
.command(setAdminCredentials)
|
||||||
|
.command(resetUserSecurityKeys)
|
||||||
.demandCommand()
|
.demandCommand()
|
||||||
.help().argv;
|
.help().argv;
|
||||||
|
|
|
@ -64,7 +64,7 @@ esbuild
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
target: "node20",
|
target: "node22",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Build completed successfully");
|
console.log("Build completed successfully");
|
||||||
|
|
|
@ -60,4 +60,4 @@ networks:
|
||||||
default:
|
default:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
name: pangolin
|
name: pangolin
|
||||||
enable_ipv6: true
|
{{if .EnableIPv6}} enable_ipv6: true{{end}}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
docker
|
docker
|
||||||
example.com
|
example.com
|
||||||
pangolin.example.com
|
pangolin.example.com
|
||||||
|
yes
|
||||||
admin@example.com
|
admin@example.com
|
||||||
yes
|
yes
|
||||||
admin@example.com
|
admin@example.com
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
"net"
|
||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
@ -39,6 +40,7 @@ type Config struct {
|
||||||
BadgerVersion string
|
BadgerVersion string
|
||||||
BaseDomain string
|
BaseDomain string
|
||||||
DashboardDomain string
|
DashboardDomain string
|
||||||
|
EnableIPv6 bool
|
||||||
LetsEncryptEmail string
|
LetsEncryptEmail string
|
||||||
EnableEmail bool
|
EnableEmail bool
|
||||||
EmailSMTPHost string
|
EmailSMTPHost string
|
||||||
|
@ -75,6 +77,17 @@ func main() {
|
||||||
fmt.Println("Lets get started!")
|
fmt.Println("Lets get started!")
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
|
||||||
|
|
||||||
|
for _, p := range []int{80, 443} {
|
||||||
|
if err := checkPortsAvailable(p); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
|
||||||
|
fmt.Printf("Please close any services on ports 80/443 in order to run the installation smoothly")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
||||||
|
|
||||||
|
@ -303,6 +316,7 @@ func collectUserInput(reader *bufio.Reader) Config {
|
||||||
fmt.Println("\n=== Basic Configuration ===")
|
fmt.Println("\n=== Basic Configuration ===")
|
||||||
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
|
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
|
||||||
config.DashboardDomain = readString(reader, "Enter the domain for the Pangolin dashboard", "pangolin."+config.BaseDomain)
|
config.DashboardDomain = readString(reader, "Enter the domain for the Pangolin dashboard", "pangolin."+config.BaseDomain)
|
||||||
|
config.EnableIPv6 = readBool(reader, "Is your server IPv6 capable?", true)
|
||||||
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
|
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
|
||||||
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
|
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
|
||||||
|
|
||||||
|
@ -776,3 +790,21 @@ func run(name string, args ...string) error {
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkPortsAvailable(port int) error {
|
||||||
|
addr := fmt.Sprintf(":%d", port)
|
||||||
|
ln, err := net.Listen("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"ERROR: port %d is occupied or cannot be bound: %w\n\n",
|
||||||
|
port, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if closeErr := ln.Close(); closeErr != nil {
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"WARNING: failed to close test listener on port %d: %v\n",
|
||||||
|
port, closeErr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
||||||
"remoteSubnets": "Remote Subnets",
|
"remoteSubnets": "Remote Subnets",
|
||||||
"enterCidrRange": "Enter CIDR range",
|
"enterCidrRange": "Enter CIDR range",
|
||||||
"remoteSubnetsDescription": "Add CIDR ranges that can access this site remotely. Use format like 10.0.0.0/24 or 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Enable Public Proxy",
|
"resourceEnableProxy": "Enable Public Proxy",
|
||||||
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
||||||
"externalProxyEnabled": "External Proxy Enabled"
|
"externalProxyEnabled": "External Proxy Enabled"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
||||||
"remoteSubnets": "Remote Subnets",
|
"remoteSubnets": "Remote Subnets",
|
||||||
"enterCidrRange": "Enter CIDR range",
|
"enterCidrRange": "Enter CIDR range",
|
||||||
"remoteSubnetsDescription": "Add CIDR ranges that can access this site remotely. Use format like 10.0.0.0/24 or 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Enable Public Proxy",
|
"resourceEnableProxy": "Enable Public Proxy",
|
||||||
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
||||||
"externalProxyEnabled": "External Proxy Enabled"
|
"externalProxyEnabled": "External Proxy Enabled"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Beim Abrufen der neuesten Olm-Veröffentlichung ist ein Fehler aufgetreten.",
|
"olmErrorFetchLatest": "Beim Abrufen der neuesten Olm-Veröffentlichung ist ein Fehler aufgetreten.",
|
||||||
"remoteSubnets": "Remote-Subnetze",
|
"remoteSubnets": "Remote-Subnetze",
|
||||||
"enterCidrRange": "Geben Sie den CIDR-Bereich ein",
|
"enterCidrRange": "Geben Sie den CIDR-Bereich ein",
|
||||||
"remoteSubnetsDescription": "Fügen Sie CIDR-Bereiche hinzu, die aus der Ferne auf diesen Standort zugreifen können. Verwenden Sie das Format wie 10.0.0.0/24 oder 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Öffentlichen Proxy aktivieren",
|
"resourceEnableProxy": "Öffentlichen Proxy aktivieren",
|
||||||
"resourceEnableProxyDescription": "Ermöglichen Sie öffentliches Proxieren zu dieser Ressource. Dies ermöglicht den Zugriff auf die Ressource von außerhalb des Netzwerks durch die Cloud über einen offenen Port. Erfordert Traefik-Config.",
|
"resourceEnableProxyDescription": "Ermöglichen Sie öffentliches Proxieren zu dieser Ressource. Dies ermöglicht den Zugriff auf die Ressource von außerhalb des Netzwerks durch die Cloud über einen offenen Port. Erfordert Traefik-Config.",
|
||||||
"externalProxyEnabled": "Externer Proxy aktiviert"
|
"externalProxyEnabled": "Externer Proxy aktiviert"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
||||||
"remoteSubnets": "Remote Subnets",
|
"remoteSubnets": "Remote Subnets",
|
||||||
"enterCidrRange": "Enter CIDR range",
|
"enterCidrRange": "Enter CIDR range",
|
||||||
"remoteSubnetsDescription": "Add CIDR ranges that can access this site remotely. Use format like 10.0.0.0/24 or 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Enable Public Proxy",
|
"resourceEnableProxy": "Enable Public Proxy",
|
||||||
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
||||||
"externalProxyEnabled": "External Proxy Enabled"
|
"externalProxyEnabled": "External Proxy Enabled"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Se ha producido un error al recuperar la última versión de Olm.",
|
"olmErrorFetchLatest": "Se ha producido un error al recuperar la última versión de Olm.",
|
||||||
"remoteSubnets": "Subredes remotas",
|
"remoteSubnets": "Subredes remotas",
|
||||||
"enterCidrRange": "Ingresa el rango CIDR",
|
"enterCidrRange": "Ingresa el rango CIDR",
|
||||||
"remoteSubnetsDescription": "Agregue rangos CIDR que puedan acceder a este sitio de forma remota. Use un formato como 10.0.0.0/24 o 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Habilitar proxy público",
|
"resourceEnableProxy": "Habilitar proxy público",
|
||||||
"resourceEnableProxyDescription": "Habilite el proxy público para este recurso. Esto permite el acceso al recurso desde fuera de la red a través de la nube en un puerto abierto. Requiere configuración de Traefik.",
|
"resourceEnableProxyDescription": "Habilite el proxy público para este recurso. Esto permite el acceso al recurso desde fuera de la red a través de la nube en un puerto abierto. Requiere configuración de Traefik.",
|
||||||
"externalProxyEnabled": "Proxy externo habilitado"
|
"externalProxyEnabled": "Proxy externo habilitado"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Une erreur s'est produite lors de la récupération de la dernière version d'Olm.",
|
"olmErrorFetchLatest": "Une erreur s'est produite lors de la récupération de la dernière version d'Olm.",
|
||||||
"remoteSubnets": "Sous-réseaux distants",
|
"remoteSubnets": "Sous-réseaux distants",
|
||||||
"enterCidrRange": "Entrez la plage CIDR",
|
"enterCidrRange": "Entrez la plage CIDR",
|
||||||
"remoteSubnetsDescription": "Ajoutez des plages CIDR pouvant accéder à ce site à distance. Utilisez le format comme 10.0.0.0/24 ou 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Activer le proxy public",
|
"resourceEnableProxy": "Activer le proxy public",
|
||||||
"resourceEnableProxyDescription": "Activez le proxy public vers cette ressource. Cela permet d'accéder à la ressource depuis l'extérieur du réseau via le cloud sur un port ouvert. Nécessite la configuration de Traefik.",
|
"resourceEnableProxyDescription": "Activez le proxy public vers cette ressource. Cela permet d'accéder à la ressource depuis l'extérieur du réseau via le cloud sur un port ouvert. Nécessite la configuration de Traefik.",
|
||||||
"externalProxyEnabled": "Proxy externe activé"
|
"externalProxyEnabled": "Proxy externe activé"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Si è verificato un errore durante il recupero dell'ultima versione di Olm.",
|
"olmErrorFetchLatest": "Si è verificato un errore durante il recupero dell'ultima versione di Olm.",
|
||||||
"remoteSubnets": "Sottoreti Remote",
|
"remoteSubnets": "Sottoreti Remote",
|
||||||
"enterCidrRange": "Inserisci l'intervallo CIDR",
|
"enterCidrRange": "Inserisci l'intervallo CIDR",
|
||||||
"remoteSubnetsDescription": "Aggiungi intervalli CIDR che possono accedere a questo sito da remoto. Usa il formato come 10.0.0.0/24 o 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Abilita Proxy Pubblico",
|
"resourceEnableProxy": "Abilita Proxy Pubblico",
|
||||||
"resourceEnableProxyDescription": "Abilita il proxy pubblico a questa risorsa. Consente l'accesso alla risorsa dall'esterno della rete tramite il cloud su una porta aperta. Richiede la configurazione di Traefik.",
|
"resourceEnableProxyDescription": "Abilita il proxy pubblico a questa risorsa. Consente l'accesso alla risorsa dall'esterno della rete tramite il cloud su una porta aperta. Richiede la configurazione di Traefik.",
|
||||||
"externalProxyEnabled": "Proxy Esterno Abilitato"
|
"externalProxyEnabled": "Proxy Esterno Abilitato"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
||||||
"remoteSubnets": "Remote Subnets",
|
"remoteSubnets": "Remote Subnets",
|
||||||
"enterCidrRange": "Enter CIDR range",
|
"enterCidrRange": "Enter CIDR range",
|
||||||
"remoteSubnetsDescription": "Add CIDR ranges that can access this site remotely. Use format like 10.0.0.0/24 or 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Enable Public Proxy",
|
"resourceEnableProxy": "Enable Public Proxy",
|
||||||
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
||||||
"externalProxyEnabled": "External Proxy Enabled"
|
"externalProxyEnabled": "External Proxy Enabled"
|
||||||
|
|
1327
messages/nb-NO.json
Normal file
1327
messages/nb-NO.json
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Er is een fout opgetreden bij het ophalen van de nieuwste Olm release.",
|
"olmErrorFetchLatest": "Er is een fout opgetreden bij het ophalen van de nieuwste Olm release.",
|
||||||
"remoteSubnets": "Externe Subnets",
|
"remoteSubnets": "Externe Subnets",
|
||||||
"enterCidrRange": "Voer CIDR-bereik in",
|
"enterCidrRange": "Voer CIDR-bereik in",
|
||||||
"remoteSubnetsDescription": "Voeg CIDR-bereiken toe die deze site op afstand kunnen openen. Gebruik een format zoals 10.0.0.0/24 of 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Openbare proxy inschakelen",
|
"resourceEnableProxy": "Openbare proxy inschakelen",
|
||||||
"resourceEnableProxyDescription": "Schakel publieke proxy in voor deze resource. Dit maakt toegang tot de resource mogelijk vanuit het netwerk via de cloud met een open poort. Vereist Traefik-configuratie.",
|
"resourceEnableProxyDescription": "Schakel publieke proxy in voor deze resource. Dit maakt toegang tot de resource mogelijk vanuit het netwerk via de cloud met een open poort. Vereist Traefik-configuratie.",
|
||||||
"externalProxyEnabled": "Externe Proxy Ingeschakeld"
|
"externalProxyEnabled": "Externe Proxy Ingeschakeld"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Wystąpił błąd podczas pobierania najnowszego wydania Olm.",
|
"olmErrorFetchLatest": "Wystąpił błąd podczas pobierania najnowszego wydania Olm.",
|
||||||
"remoteSubnets": "Zdalne Podsieci",
|
"remoteSubnets": "Zdalne Podsieci",
|
||||||
"enterCidrRange": "Wprowadź zakres CIDR",
|
"enterCidrRange": "Wprowadź zakres CIDR",
|
||||||
"remoteSubnetsDescription": "Dodaj zakresy CIDR, które mogą uzyskać zdalny dostęp do tej witryny. Użyj formatu takiego jak 10.0.0.0/24 lub 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Włącz publiczny proxy",
|
"resourceEnableProxy": "Włącz publiczny proxy",
|
||||||
"resourceEnableProxyDescription": "Włącz publiczne proxy dla tego zasobu. To umożliwia dostęp do zasobu spoza sieci przez chmurę na otwartym porcie. Wymaga konfiguracji Traefik.",
|
"resourceEnableProxyDescription": "Włącz publiczne proxy dla tego zasobu. To umożliwia dostęp do zasobu spoza sieci przez chmurę na otwartym porcie. Wymaga konfiguracji Traefik.",
|
||||||
"externalProxyEnabled": "Zewnętrzny Proxy Włączony"
|
"externalProxyEnabled": "Zewnętrzny Proxy Włączony"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "Ocorreu um erro ao buscar o lançamento mais recente do Olm.",
|
"olmErrorFetchLatest": "Ocorreu um erro ao buscar o lançamento mais recente do Olm.",
|
||||||
"remoteSubnets": "Sub-redes Remotas",
|
"remoteSubnets": "Sub-redes Remotas",
|
||||||
"enterCidrRange": "Insira o intervalo CIDR",
|
"enterCidrRange": "Insira o intervalo CIDR",
|
||||||
"remoteSubnetsDescription": "Adicione intervalos CIDR que podem acessar este site remotamente. Use o formato como 10.0.0.0/24 ou 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Ativar Proxy Público",
|
"resourceEnableProxy": "Ativar Proxy Público",
|
||||||
"resourceEnableProxyDescription": "Permite proxy público para este recurso. Isso permite o acesso ao recurso de fora da rede através da nuvem em uma porta aberta. Requer configuração do Traefik.",
|
"resourceEnableProxyDescription": "Permite proxy público para este recurso. Isso permite o acesso ao recurso de fora da rede através da nuvem em uma porta aberta. Requer configuração do Traefik.",
|
||||||
"externalProxyEnabled": "Proxy Externo Habilitado"
|
"externalProxyEnabled": "Proxy Externo Habilitado"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
"olmErrorFetchLatest": "An error occurred while fetching the latest Olm release.",
|
||||||
"remoteSubnets": "Remote Subnets",
|
"remoteSubnets": "Remote Subnets",
|
||||||
"enterCidrRange": "Enter CIDR range",
|
"enterCidrRange": "Enter CIDR range",
|
||||||
"remoteSubnetsDescription": "Add CIDR ranges that can access this site remotely. Use format like 10.0.0.0/24 or 192.168.1.0/24.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Enable Public Proxy",
|
"resourceEnableProxy": "Enable Public Proxy",
|
||||||
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
"resourceEnableProxyDescription": "Enable public proxying to this resource. This allows access to the resource from outside the network through the cloud on an open port. Requires Traefik config.",
|
||||||
"externalProxyEnabled": "External Proxy Enabled"
|
"externalProxyEnabled": "External Proxy Enabled"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "En son Olm yayını alınırken bir hata oluştu.",
|
"olmErrorFetchLatest": "En son Olm yayını alınırken bir hata oluştu.",
|
||||||
"remoteSubnets": "Uzak Alt Ağlar",
|
"remoteSubnets": "Uzak Alt Ağlar",
|
||||||
"enterCidrRange": "CIDR aralığını girin",
|
"enterCidrRange": "CIDR aralığını girin",
|
||||||
"remoteSubnetsDescription": "Bu siteye uzaktan erişebilecek CIDR aralıklarını ekleyin. 10.0.0.0/24 veya 192.168.1.0/24 gibi formatlar kullanın.",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "Genel Proxy'i Etkinleştir",
|
"resourceEnableProxy": "Genel Proxy'i Etkinleştir",
|
||||||
"resourceEnableProxyDescription": "Bu kaynağa genel proxy erişimini etkinleştirin. Bu sayede ağ dışından açık bir port üzerinden kaynağa bulut aracılığıyla erişim sağlanır. Traefik yapılandırması gereklidir.",
|
"resourceEnableProxyDescription": "Bu kaynağa genel proxy erişimini etkinleştirin. Bu sayede ağ dışından açık bir port üzerinden kaynağa bulut aracılığıyla erişim sağlanır. Traefik yapılandırması gereklidir.",
|
||||||
"externalProxyEnabled": "Dış Proxy Etkinleştirildi"
|
"externalProxyEnabled": "Dış Proxy Etkinleştirildi"
|
||||||
|
|
|
@ -1320,7 +1320,7 @@
|
||||||
"olmErrorFetchLatest": "获取最新 Olm 发布版本时出错。",
|
"olmErrorFetchLatest": "获取最新 Olm 发布版本时出错。",
|
||||||
"remoteSubnets": "远程子网",
|
"remoteSubnets": "远程子网",
|
||||||
"enterCidrRange": "输入 CIDR 范围",
|
"enterCidrRange": "输入 CIDR 范围",
|
||||||
"remoteSubnetsDescription": "添加能远程访问此站点的 CIDR 范围。使用格式如 10.0.0.0/24 或 192.168.1.0/24。",
|
"remoteSubnetsDescription": "Add CIDR ranges that can be accessed from this site remotely using clients. Use format like 10.0.0.0/24. This ONLY applies to VPN client connectivity.",
|
||||||
"resourceEnableProxy": "启用公共代理",
|
"resourceEnableProxy": "启用公共代理",
|
||||||
"resourceEnableProxyDescription": "启用到此资源的公共代理。这允许外部网络通过开放端口访问资源。需要 Traefik 配置。",
|
"resourceEnableProxyDescription": "启用到此资源的公共代理。这允许外部网络通过开放端口访问资源。需要 Traefik 配置。",
|
||||||
"externalProxyEnabled": "外部代理已启用"
|
"externalProxyEnabled": "外部代理已启用"
|
||||||
|
|
2814
package-lock.json
generated
2814
package-lock.json
generated
File diff suppressed because it is too large
Load diff
38
package.json
38
package.json
|
@ -50,15 +50,15 @@
|
||||||
"@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",
|
||||||
"@radix-ui/react-tooltip": "^1.2.7",
|
"@radix-ui/react-tooltip": "^1.2.7",
|
||||||
"@react-email/components": "0.3.1",
|
"@react-email/components": "0.5.0",
|
||||||
"@react-email/render": "^1.1.2",
|
"@react-email/render": "^1.2.0",
|
||||||
"@simplewebauthn/browser": "^13.1.0",
|
"@simplewebauthn/browser": "^13.1.0",
|
||||||
"@simplewebauthn/server": "^9.0.3",
|
"@simplewebauthn/server": "^9.0.3",
|
||||||
"@react-email/tailwind": "1.2.1",
|
"@react-email/tailwind": "1.2.2",
|
||||||
"@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.10.0",
|
"axios": "1.11.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",
|
||||||
|
@ -69,9 +69,9 @@
|
||||||
"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.44.2",
|
"drizzle-orm": "0.44.4",
|
||||||
"eslint": "9.31.0",
|
"eslint": "9.32.0",
|
||||||
"eslint-config-next": "15.3.5",
|
"eslint-config-next": "15.4.6",
|
||||||
"express": "4.21.2",
|
"express": "4.21.2",
|
||||||
"express-rate-limit": "7.5.1",
|
"express-rate-limit": "7.5.1",
|
||||||
"glob": "11.0.3",
|
"glob": "11.0.3",
|
||||||
|
@ -82,28 +82,28 @@
|
||||||
"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.536.0",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "15.3.5",
|
"next": "15.4.6",
|
||||||
"next-intl": "^4.3.4",
|
"next-intl": "^4.3.4",
|
||||||
"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": "7.0.5",
|
"nodemailer": "7.0.5",
|
||||||
"npm": "^11.4.2",
|
"npm": "^11.5.2",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "^8.16.2",
|
"pg": "^8.16.2",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.1.0",
|
"react": "19.1.1",
|
||||||
"react-dom": "19.1.0",
|
"react-dom": "19.1.1",
|
||||||
"react-easy-sort": "^1.6.0",
|
"react-easy-sort": "^1.6.0",
|
||||||
"react-hook-form": "7.60.0",
|
"react-hook-form": "7.62.0",
|
||||||
"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": "3.3.1",
|
"tailwind-merge": "3.3.1",
|
||||||
"tw-animate-css": "^1.3.5",
|
"tw-animate-css": "^1.3.6",
|
||||||
"uuid": "^11.1.0",
|
"uuid": "^11.1.0",
|
||||||
"vaul": "1.1.2",
|
"vaul": "1.1.2",
|
||||||
"winston": "3.17.0",
|
"winston": "3.17.0",
|
||||||
|
@ -114,7 +114,7 @@
|
||||||
"yargs": "18.0.0"
|
"yargs": "18.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "1.47.6",
|
"@dotenvx/dotenvx": "1.48.4",
|
||||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||||
"@tailwindcss/postcss": "^4.1.10",
|
"@tailwindcss/postcss": "^4.1.10",
|
||||||
"@types/better-sqlite3": "7.6.12",
|
"@types/better-sqlite3": "7.6.12",
|
||||||
|
@ -129,8 +129,8 @@
|
||||||
"@types/node": "^24",
|
"@types/node": "^24",
|
||||||
"@types/nodemailer": "6.4.17",
|
"@types/nodemailer": "6.4.17",
|
||||||
"@types/pg": "8.15.4",
|
"@types/pg": "8.15.4",
|
||||||
"@types/react": "19.1.8",
|
"@types/react": "19.1.9",
|
||||||
"@types/react-dom": "19.1.6",
|
"@types/react-dom": "19.1.7",
|
||||||
"@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",
|
||||||
|
@ -139,12 +139,12 @@
|
||||||
"esbuild": "0.25.6",
|
"esbuild": "0.25.6",
|
||||||
"esbuild-node-externals": "1.18.0",
|
"esbuild-node-externals": "1.18.0",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"react-email": "4.1.0",
|
"react-email": "4.2.8",
|
||||||
"tailwindcss": "^4.1.4",
|
"tailwindcss": "^4.1.4",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsx": "4.20.3",
|
"tsx": "4.20.3",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
"typescript-eslint": "^8.36.0"
|
"typescript-eslint": "^8.39.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"emblor": {
|
"emblor": {
|
||||||
|
|
|
@ -271,7 +271,7 @@ export async function getNextAvailableClientSubnet(
|
||||||
)
|
)
|
||||||
].filter((address) => address !== null) as string[];
|
].filter((address) => address !== null) as string[];
|
||||||
|
|
||||||
let subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org
|
const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org
|
||||||
if (!subnet) {
|
if (!subnet) {
|
||||||
throw new Error("No available subnets remaining in space");
|
throw new Error("No available subnets remaining in space");
|
||||||
}
|
}
|
||||||
|
@ -289,7 +289,7 @@ export async function getNextAvailableOrgSubnet(): Promise<string> {
|
||||||
|
|
||||||
const addresses = existingAddresses.map((org) => org.subnet!);
|
const addresses = existingAddresses.map((org) => org.subnet!);
|
||||||
|
|
||||||
let subnet = findNextAvailableCidr(
|
const subnet = findNextAvailableCidr(
|
||||||
addresses,
|
addresses,
|
||||||
config.getRawConfig().orgs.block_size,
|
config.getRawConfig().orgs.block_size,
|
||||||
config.getRawConfig().orgs.subnet_group
|
config.getRawConfig().orgs.subnet_group
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { MemoryStore, Store } from "express-rate-limit";
|
import { MemoryStore, Store } from "express-rate-limit";
|
||||||
|
|
||||||
export function createStore(): Store {
|
export function createStore(): Store {
|
||||||
let rateLimitStore: Store = new MemoryStore();
|
const rateLimitStore: Store = new MemoryStore();
|
||||||
return rateLimitStore;
|
return rateLimitStore;
|
||||||
}
|
}
|
||||||
|
|
|
@ -222,7 +222,7 @@ export async function listAccessTokens(
|
||||||
(resource) => resource.resourceId
|
(resource) => resource.resourceId
|
||||||
);
|
);
|
||||||
|
|
||||||
let countQuery: any = db
|
const countQuery: any = db
|
||||||
.select({ count: count() })
|
.select({ count: count() })
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(inArray(resources.resourceId, accessibleResourceIds));
|
.where(inArray(resources.resourceId, accessibleResourceIds));
|
||||||
|
|
|
@ -48,7 +48,7 @@ export async function getAllRelays(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch exit node
|
// Fetch exit node
|
||||||
let [exitNode] = await db.select().from(exitNodes).where(eq(exitNodes.publicKey, publicKey));
|
const [exitNode] = await db.select().from(exitNodes).where(eq(exitNodes.publicKey, publicKey));
|
||||||
if (!exitNode) {
|
if (!exitNode) {
|
||||||
return next(createHttpError(HttpCode.NOT_FOUND, "Exit node not found"));
|
return next(createHttpError(HttpCode.NOT_FOUND, "Exit node not found"));
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ export async function getAllRelays(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize mappings object for multi-peer support
|
// Initialize mappings object for multi-peer support
|
||||||
let mappings: { [key: string]: ProxyMapping } = {};
|
const mappings: { [key: string]: ProxyMapping } = {};
|
||||||
|
|
||||||
// Process each site
|
// Process each site
|
||||||
for (const site of sitesRes) {
|
for (const site of sitesRes) {
|
||||||
|
|
|
@ -112,7 +112,7 @@ export async function getConfig(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
let peers = await Promise.all(
|
const peers = await Promise.all(
|
||||||
sitesRes.map(async (site) => {
|
sitesRes.map(async (site) => {
|
||||||
if (site.type === "wireguard") {
|
if (site.type === "wireguard") {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -68,7 +68,7 @@ export async function createOidcIdp(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
authUrl,
|
authUrl,
|
||||||
|
|
|
@ -85,7 +85,7 @@ export async function updateOidcIdp(
|
||||||
}
|
}
|
||||||
|
|
||||||
const { idpId } = parsedParams.data;
|
const { idpId } = parsedParams.data;
|
||||||
let {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
authUrl,
|
authUrl,
|
||||||
|
|
|
@ -238,7 +238,7 @@ export async function validateOidcCallback(
|
||||||
const defaultRoleMapping = existingIdp.idp.defaultRoleMapping;
|
const defaultRoleMapping = existingIdp.idp.defaultRoleMapping;
|
||||||
const defaultOrgMapping = existingIdp.idp.defaultOrgMapping;
|
const defaultOrgMapping = existingIdp.idp.defaultOrgMapping;
|
||||||
|
|
||||||
let userOrgInfo: { orgId: string; roleId: number }[] = [];
|
const userOrgInfo: { orgId: string; roleId: number }[] = [];
|
||||||
for (const org of allOrgs) {
|
for (const org of allOrgs) {
|
||||||
const [idpOrgRes] = await db
|
const [idpOrgRes] = await db
|
||||||
.select()
|
.select()
|
||||||
|
@ -314,7 +314,7 @@ export async function validateOidcCallback(
|
||||||
|
|
||||||
let existingUserId = existingUser?.userId;
|
let existingUserId = existingUser?.userId;
|
||||||
|
|
||||||
let orgUserCounts: { orgId: string; userCount: number }[] = [];
|
const orgUserCounts: { orgId: string; userCount: number }[] = [];
|
||||||
|
|
||||||
// sync the user with the orgs and roles
|
// sync the user with the orgs and roles
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const handleNewtPingRequestMessage: MessageHandler = async (context) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (currentConnections.count >= maxConnections) {
|
if (currentConnections.count >= maxConnections) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
weight =
|
weight =
|
||||||
|
|
|
@ -37,7 +37,7 @@ export const startOfflineChecker = (): void => {
|
||||||
}, OFFLINE_CHECK_INTERVAL);
|
}, OFFLINE_CHECK_INTERVAL);
|
||||||
|
|
||||||
logger.info("Started offline checker interval");
|
logger.info("Started offline checker interval");
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the background interval that checks for offline clients
|
* Stops the background interval that checks for offline clients
|
||||||
|
@ -48,7 +48,7 @@ export const stopOfflineChecker = (): void => {
|
||||||
offlineCheckerInterval = null;
|
offlineCheckerInterval = null;
|
||||||
logger.info("Stopped offline checker interval");
|
logger.info("Stopped offline checker interval");
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles ping messages from clients and responds with pong
|
* Handles ping messages from clients and responds with pong
|
||||||
|
|
|
@ -35,7 +35,7 @@ const listResourceRulesSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryResourceRules(resourceId: number) {
|
function queryResourceRules(resourceId: number) {
|
||||||
let baseQuery = db
|
const baseQuery = db
|
||||||
.select({
|
.select({
|
||||||
ruleId: resourceRules.ruleId,
|
ruleId: resourceRules.ruleId,
|
||||||
resourceId: resourceRules.resourceId,
|
resourceId: resourceRules.resourceId,
|
||||||
|
@ -117,7 +117,7 @@ export async function listResourceRules(
|
||||||
|
|
||||||
const baseQuery = queryResourceRules(resourceId);
|
const baseQuery = queryResourceRules(resourceId);
|
||||||
|
|
||||||
let countQuery = db
|
const countQuery = db
|
||||||
.select({ count: sql<number>`cast(count(*) as integer)` })
|
.select({ count: sql<number>`cast(count(*) as integer)` })
|
||||||
.from(resourceRules)
|
.from(resourceRules)
|
||||||
.where(eq(resourceRules.resourceId, resourceId));
|
.where(eq(resourceRules.resourceId, resourceId));
|
||||||
|
|
|
@ -231,7 +231,7 @@ export async function listResources(
|
||||||
(resource) => resource.resourceId
|
(resource) => resource.resourceId
|
||||||
);
|
);
|
||||||
|
|
||||||
let countQuery: any = db
|
const countQuery: any = db
|
||||||
.select({ count: count() })
|
.select({ count: count() })
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(inArray(resources.resourceId, accessibleResourceIds));
|
.where(inArray(resources.resourceId, accessibleResourceIds));
|
||||||
|
|
|
@ -100,7 +100,7 @@ export async function listRoles(
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
let countQuery: any = db
|
const countQuery: any = db
|
||||||
.select({ count: sql<number>`cast(count(*) as integer)` })
|
.select({ count: sql<number>`cast(count(*) as integer)` })
|
||||||
.from(roles)
|
.from(roles)
|
||||||
.where(eq(roles.orgId, orgId));
|
.where(eq(roles.orgId, orgId));
|
||||||
|
|
|
@ -176,7 +176,7 @@ export async function listSites(
|
||||||
const accessibleSiteIds = accessibleSites.map((site) => site.siteId);
|
const accessibleSiteIds = accessibleSites.map((site) => site.siteId);
|
||||||
const baseQuery = querySites(orgId, accessibleSiteIds);
|
const baseQuery = querySites(orgId, accessibleSiteIds);
|
||||||
|
|
||||||
let countQuery = db
|
const countQuery = db
|
||||||
.select({ count: count() })
|
.select({ count: count() })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
|
|
|
@ -86,7 +86,7 @@ export async function pickSiteDefaults(
|
||||||
.where(eq(sites.exitNodeId, exitNode.exitNodeId));
|
.where(eq(sites.exitNodeId, exitNode.exitNodeId));
|
||||||
|
|
||||||
// TODO: we need to lock this subnet for some time so someone else does not take it
|
// TODO: we need to lock this subnet for some time so someone else does not take it
|
||||||
let subnets = sitesQuery.map((site) => site.subnet).filter((subnet) => subnet !== null);
|
const subnets = sitesQuery.map((site) => site.subnet).filter((subnet) => subnet !== null);
|
||||||
// exclude the exit node address by replacing after the / with a site block size
|
// exclude the exit node address by replacing after the / with a site block size
|
||||||
subnets.push(
|
subnets.push(
|
||||||
exitNode.address.replace(
|
exitNode.address.replace(
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { db } from "@server/db";
|
||||||
import { resources, targets } from "@server/db";
|
import { resources, targets } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
let currentBannedPorts: number[] = [];
|
const currentBannedPorts: number[] = [];
|
||||||
|
|
||||||
export async function pickPort(siteId: number): Promise<{
|
export async function pickPort(siteId: number): Promise<{
|
||||||
internalPort: number;
|
internalPort: number;
|
||||||
|
@ -15,8 +15,8 @@ export async function pickPort(siteId: number): Promise<{
|
||||||
|
|
||||||
// TODO: is this all inefficient?
|
// TODO: is this all inefficient?
|
||||||
// Fetch targets for all resources of this site
|
// Fetch targets for all resources of this site
|
||||||
let targetIps: string[] = [];
|
const targetIps: string[] = [];
|
||||||
let targetInternalPorts: number[] = [];
|
const targetInternalPorts: number[] = [];
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
resourcesRes.map(async (resource) => {
|
resourcesRes.map(async (resource) => {
|
||||||
const targetsRes = await db
|
const targetsRes = await db
|
||||||
|
|
|
@ -35,7 +35,7 @@ const listTargetsSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryTargets(resourceId: number) {
|
function queryTargets(resourceId: number) {
|
||||||
let baseQuery = db
|
const baseQuery = db
|
||||||
.select({
|
.select({
|
||||||
targetId: targets.targetId,
|
targetId: targets.targetId,
|
||||||
ip: targets.ip,
|
ip: targets.ip,
|
||||||
|
@ -99,7 +99,7 @@ export async function listTargets(
|
||||||
|
|
||||||
const baseQuery = queryTargets(resourceId);
|
const baseQuery = queryTargets(resourceId);
|
||||||
|
|
||||||
let countQuery = db
|
const countQuery = db
|
||||||
.select({ count: sql<number>`cast(count(*) as integer)` })
|
.select({ count: sql<number>`cast(count(*) as integer)` })
|
||||||
.from(targets)
|
.from(targets)
|
||||||
.where(eq(targets.resourceId, resourceId));
|
.where(eq(targets.resourceId, resourceId));
|
||||||
|
|
|
@ -62,7 +62,7 @@ const wss: WebSocketServer = new WebSocketServer({ noServer: true });
|
||||||
const NODE_ID = uuidv4();
|
const NODE_ID = uuidv4();
|
||||||
|
|
||||||
// Client tracking map (local to this node)
|
// Client tracking map (local to this node)
|
||||||
let connectedClients: Map<string, AuthenticatedWebSocket[]> = new Map();
|
const connectedClients: Map<string, AuthenticatedWebSocket[]> = new Map();
|
||||||
// Helper to get map key
|
// Helper to get map key
|
||||||
const getClientMapKey = (clientId: string) => clientId;
|
const getClientMapKey = (clientId: string) => clientId;
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
if (rawConfig.server?.trust_proxy) {
|
if (rawConfig.server?.trust_proxy) {
|
||||||
|
|
|
@ -23,8 +23,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
delete rawConfig.server.secure_cookies;
|
delete rawConfig.server.secure_cookies;
|
||||||
|
|
|
@ -25,8 +25,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
if (!rawConfig.flags) {
|
if (!rawConfig.flags) {
|
||||||
|
|
|
@ -30,8 +30,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
const baseDomain = rawConfig.app.base_domain;
|
const baseDomain = rawConfig.app.base_domain;
|
||||||
|
|
|
@ -22,8 +22,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
// Validate the structure
|
// Validate the structure
|
||||||
|
|
|
@ -22,8 +22,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
// Validate the structure
|
// Validate the structure
|
||||||
|
|
|
@ -25,8 +25,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
// Validate the structure
|
// Validate the structure
|
||||||
|
|
|
@ -23,8 +23,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
// Validate the structure
|
// Validate the structure
|
||||||
|
|
|
@ -58,8 +58,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
rawConfig.server.resource_session_request_param =
|
rawConfig.server.resource_session_request_param =
|
||||||
|
@ -122,7 +122,7 @@ export default async function migration() {
|
||||||
const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
|
const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
|
||||||
const traefikConfig = yaml.load(traefikFileContents) as any;
|
const traefikConfig = yaml.load(traefikFileContents) as any;
|
||||||
|
|
||||||
let parsedConfig: any = schema.safeParse(traefikConfig);
|
const parsedConfig: any = schema.safeParse(traefikConfig);
|
||||||
|
|
||||||
if (parsedConfig.success) {
|
if (parsedConfig.success) {
|
||||||
// Ensure websecure entrypoint exists
|
// Ensure websecure entrypoint exists
|
||||||
|
@ -179,7 +179,7 @@ export default async function migration() {
|
||||||
const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
|
const traefikFileContents = fs.readFileSync(traefikPath, "utf8");
|
||||||
const traefikConfig = yaml.load(traefikFileContents) as any;
|
const traefikConfig = yaml.load(traefikFileContents) as any;
|
||||||
|
|
||||||
let parsedConfig: any = schema.safeParse(traefikConfig);
|
const parsedConfig: any = schema.safeParse(traefikConfig);
|
||||||
|
|
||||||
if (parsedConfig.success) {
|
if (parsedConfig.success) {
|
||||||
// delete permanent from redirect-to-https middleware
|
// delete permanent from redirect-to-https middleware
|
||||||
|
|
|
@ -43,8 +43,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
if (!rawConfig.flags) {
|
if (!rawConfig.flags) {
|
||||||
|
|
|
@ -177,7 +177,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
let rawConfig: any = yaml.load(fileContents);
|
let rawConfig: any;
|
||||||
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
if (!rawConfig.server.secret) {
|
if (!rawConfig.server.secret) {
|
||||||
rawConfig.server.secret = generateIdFromEntropySize(32);
|
rawConfig.server.secret = generateIdFromEntropySize(32);
|
||||||
|
|
|
@ -44,8 +44,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
if (rawConfig.cors?.headers) {
|
if (rawConfig.cors?.headers) {
|
||||||
|
|
|
@ -45,8 +45,8 @@ export default async function migration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read and parse the YAML file
|
// Read and parse the YAML file
|
||||||
let rawConfig: any;
|
|
||||||
const fileContents = fs.readFileSync(filePath, "utf8");
|
const fileContents = fs.readFileSync(filePath, "utf8");
|
||||||
|
let rawConfig: any;
|
||||||
rawConfig = yaml.load(fileContents);
|
rawConfig = yaml.load(fileContents);
|
||||||
|
|
||||||
if (rawConfig.server?.trust_proxy) {
|
if (rawConfig.server?.trust_proxy) {
|
||||||
|
|
|
@ -108,7 +108,7 @@ export default function Page() {
|
||||||
async function onSubmit(data: CreateFormValues) {
|
async function onSubmit(data: CreateFormValues) {
|
||||||
setCreateLoading(true);
|
setCreateLoading(true);
|
||||||
|
|
||||||
let payload: CreateOrgApiKeyBody = {
|
const payload: CreateOrgApiKeyBody = {
|
||||||
name: data.name
|
name: data.name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -290,7 +290,7 @@ export default function ClientsTable({ clients, orgId }: ClientTableProps) {
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={rows}
|
data={rows}
|
||||||
addClient={() => {
|
addClient={() => {
|
||||||
router.push(`/${orgId}/settings/clients/create`)
|
router.push(`/${orgId}/settings/clients/create`);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -280,7 +280,7 @@ export default function Page() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let payload: CreateClientBody = {
|
const payload: CreateClientBody = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
type: data.method as "olm",
|
type: data.method as "olm",
|
||||||
siteIds: data.siteIds.map((site) => parseInt(site.id)),
|
siteIds: data.siteIds.map((site) => parseInt(site.id)),
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||||
const { isEnabled, isAvailable } = useDockerSocket(site!);
|
const { isEnabled, isAvailable } = useDockerSocket(site!);
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
let fullUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
const fullUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert>
|
<Alert>
|
||||||
|
|
|
@ -327,7 +327,7 @@ export default function ReverseProxyTargets(props: {
|
||||||
setProxySettingsLoading(true);
|
setProxySettingsLoading(true);
|
||||||
|
|
||||||
// Save targets
|
// Save targets
|
||||||
for (let target of targets) {
|
for (const target of targets) {
|
||||||
const data = {
|
const data = {
|
||||||
ip: target.ip,
|
ip: target.ip,
|
||||||
port: target.port,
|
port: target.port,
|
||||||
|
|
|
@ -271,7 +271,7 @@ export default function ResourceRules(props: {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save rules
|
// Save rules
|
||||||
for (let rule of rules) {
|
for (const rule of rules) {
|
||||||
const data = {
|
const data = {
|
||||||
action: rule.action,
|
action: rule.action,
|
||||||
match: rule.match,
|
match: rule.match,
|
||||||
|
@ -348,7 +348,7 @@ export default function ResourceRules(props: {
|
||||||
|
|
||||||
setRules([
|
setRules([
|
||||||
...rules.map((r) => {
|
...rules.map((r) => {
|
||||||
let res = {
|
const res = {
|
||||||
...r,
|
...r,
|
||||||
new: false,
|
new: false,
|
||||||
updated: false
|
updated: false
|
||||||
|
|
|
@ -291,10 +291,10 @@ WantedBy=default.target`
|
||||||
},
|
},
|
||||||
nixos: {
|
nixos: {
|
||||||
x86_64: [
|
x86_64: [
|
||||||
`nix run 'nixpkgs#fosrl-newt' --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
`nix run 'nixpkgs#fosrl-newt' -- --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
||||||
],
|
],
|
||||||
aarch64: [
|
aarch64: [
|
||||||
`nix run 'nixpkgs#fosrl-newt' --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
`nix run 'nixpkgs#fosrl-newt' -- --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -106,7 +106,7 @@ export default function Page() {
|
||||||
async function onSubmit(data: CreateFormValues) {
|
async function onSubmit(data: CreateFormValues) {
|
||||||
setCreateLoading(true);
|
setCreateLoading(true);
|
||||||
|
|
||||||
let payload: CreateOrgApiKeyBody = {
|
const payload: CreateOrgApiKeyBody = {
|
||||||
name: data.name
|
name: data.name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default async function RootLayout({
|
||||||
const env = pullEnv();
|
const env = pullEnv();
|
||||||
const locale = await getLocale();
|
const locale = await getLocale();
|
||||||
|
|
||||||
let supporterData = {
|
const supporterData = {
|
||||||
visible: true
|
visible: true
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,10 @@ export default function LocaleSwitcher() {
|
||||||
{
|
{
|
||||||
value: "ko-KR",
|
value: "ko-KR",
|
||||||
label: "한국어"
|
label: "한국어"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "nb-NO",
|
||||||
|
label: "Norsk (Bokmål)"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export type Locale = (typeof locales)[number];
|
export type Locale = (typeof locales)[number];
|
||||||
|
|
||||||
export const locales = ['en-US', 'es-ES', 'fr-FR', 'de-DE', 'nl-NL', 'it-IT', 'pl-PL', 'pt-PT', 'tr-TR', 'zh-CN', 'ko-KR', 'bg-BG', 'cs-CZ', 'ru-RU'] as const;
|
export const locales = ['en-US', 'es-ES', 'fr-FR', 'de-DE', 'nl-NL', 'it-IT', 'pl-PL', 'pt-PT', 'tr-TR', 'zh-CN', 'ko-KR', 'bg-BG', 'cs-CZ', 'ru-RU', 'nb-NO'] as const;
|
||||||
export const defaultLocale: Locale = 'en-US';
|
export const defaultLocale: Locale = 'en-US';
|
|
@ -25,7 +25,7 @@
|
||||||
"name": "next"
|
"name": "next"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"target": "ES2017"
|
"target": "ES2022"
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue