mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-19 17:19:58 +02:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b1cd252ea6 | |||
| 0801c71cf2 | |||
| aea0a2779c | |||
| 89909f3b35 | |||
| e54df0c592 | |||
| 8c579b69e2 | |||
| 1ab28781e3 | |||
| 8c973e5455 | |||
| b1c26b700b | |||
| aada83e57f | |||
| 5fc03bc2e5 | |||
| a75cc9b723 | |||
| 6f1b3266c0 | |||
| 5681ec06cd | |||
| f5b2058112 | |||
| 7f9fe3c67b | |||
| b617670fd8 | |||
| 53d4433d87 | |||
| 36bbb652cc | |||
| 0394289aab | |||
| 52e9a8d43f | |||
| 72cfe81727 | |||
| ed1a7c2723 | |||
| 8a05885192 | |||
| fed9d4b4b5 | |||
| b0d338456f | |||
| 9442ad2214 | |||
| 3fbe8d0618 | |||
| 86a9eadcff | |||
| 6a7be975ed | |||
| ee3f9efa27 | |||
| c32ef54428 | |||
| bda09a1b74 | |||
| 178d1d9779 | |||
| d311eb6e8c | |||
| 829680e984 | |||
| be10dcd26e | |||
| cc23e8cf5d | |||
| fc77b5bb3e | |||
| 651acae326 | |||
| 75332a1d1f | |||
| 0f1631f592 | |||
| f1a303de14 | |||
| 191c062cec | |||
| 1839ea7ca3 | |||
| a3c2c98c5e | |||
| 43e42f90f8 | |||
| a954ba349b | |||
| f07aef4ace | |||
| 4afa01aba4 | |||
| 9b15e99cfb | |||
| a322d0479c | |||
| bb9761a171 | |||
| af4d80edec | |||
| da434f63b4 | |||
| 9718c3549b | |||
| d550eef6c2 | |||
| c76b591f9f | |||
| a1b7c69ba5 | |||
| 20e5d537b0 | |||
| 197cfdc339 | |||
| 418bb26ee6 | |||
| 4dfa81416b | |||
| 324f3e50ff | |||
| 64bb6d9f9c | |||
| e54d1aa73e | |||
| c6c12f1dcb | |||
| 4b30911f06 | |||
| c6c443f0e0 | |||
| 24a06dd8ca | |||
| c11bbe670f | |||
| 7ed646c02b | |||
| 9d5dd526bc | |||
| 9e25650f22 | |||
| 143b4cf757 | |||
| 25b151a021 | |||
| c62fb6cbe0 | |||
| e561c9f334 | |||
| 2b970ce6e7 | |||
| e8cb0297a1 | |||
| 4f7e993e70 | |||
| 8dc3a1378a | |||
| 3713b714e4 | |||
| 098157f671 | |||
| ea1b3e5270 | |||
| 899620a890 | |||
| c19cb706e8 | |||
| 938d7f145c |
@@ -266,7 +266,7 @@ jobs:
|
|||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.25
|
go-version: 1.26
|
||||||
|
|
||||||
- name: Update version in package.json
|
- name: Update version in package.json
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ jobs:
|
|||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
# Match the version the app ships on (Dockerfile /
|
||||||
|
# Dockerfile.dev both use 24.18.1) rather than floating
|
||||||
|
# to the latest 24.x.
|
||||||
|
node-version: '24.18.1'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ jobs:
|
|||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
# Pin to the version the app actually ships on (Dockerfile /
|
||||||
|
# Dockerfile.dev both use 24.18.1; .nvmrc pins the 24 major).
|
||||||
|
# A bare '24' floats to the latest 24.x; Node >= 24.19.0 added
|
||||||
|
# node::ObjectWrap cleanup hooks that crash better-sqlite3 on teardown
|
||||||
|
# ("Assertion failed: (env) != nullptr" in RemoveEnvironmentCleanupHook),
|
||||||
|
# which intermittently kills `npm run dev` in the step below.
|
||||||
|
node-version: '24.18.1'
|
||||||
|
|
||||||
- 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
|
||||||
@@ -43,19 +49,34 @@ jobs:
|
|||||||
run: npx tsc --noEmit
|
run: npx tsc --noEmit
|
||||||
|
|
||||||
- name: Start app in background
|
- name: Start app in background
|
||||||
run: nohup npm run dev &
|
run: |
|
||||||
|
nohup npm run dev > app.log 2>&1 &
|
||||||
|
echo $! > app.pid
|
||||||
|
|
||||||
- name: Wait for app availability
|
- name: Wait for app availability
|
||||||
run: |
|
run: |
|
||||||
|
print_log() {
|
||||||
|
if [ -f app.log ]; then cat app.log; else echo "(app.log not found)"; fi
|
||||||
|
}
|
||||||
for i in {1..5}; do
|
for i in {1..5}; do
|
||||||
if curl --silent --fail http://localhost:3002/auth/login; then
|
if curl --silent --fail http://localhost:3002/auth/login; then
|
||||||
echo "App is up"
|
echo "App is up"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
# Fail fast (and show why) if the process is gone. Read the PID
|
||||||
|
# safely: if it's missing/empty (e.g. nohup never started), treat
|
||||||
|
# that as "not running" instead of passing a bad arg to kill.
|
||||||
|
pid="$(cat app.pid 2>/dev/null || true)"
|
||||||
|
if [ -z "$pid" ] || ! kill -0 "$pid" 2>/dev/null; then
|
||||||
|
echo "App process exited before becoming available. Output:"
|
||||||
|
print_log
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
echo "Waiting for the app... attempt $i"
|
echo "Waiting for the app... attempt $i"
|
||||||
sleep 5
|
sleep 5
|
||||||
done
|
done
|
||||||
echo "App failed to start"
|
echo "App failed to start. Output:"
|
||||||
|
print_log
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
build-sqlite:
|
build-sqlite:
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# typescript-eslint@8.70.0 declares a peer range of typescript "<6.1.0" and
|
||||||
|
# hasn't caught up to typescript@7.x yet, even though it works fine against it
|
||||||
|
# in practice. Without this, `npm install`/`npm ci` fail with ERESOLVE.
|
||||||
|
legacy-peer-deps=true
|
||||||
+1
-1
@@ -5,7 +5,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json .npmrc ./
|
||||||
|
|
||||||
FROM base AS builder-dev
|
FROM base AS builder-dev
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN apk add --no-cache python3 make g++
|
RUN apk add --no-cache python3 make g++
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json .npmrc ./
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
## Example Docker Reference HA Deployment
|
## Example Docker Reference HA Deployment
|
||||||
|
|
||||||
This directory contains basic config for a highly available deployment of Pangolin with two nodes. For more information [refer to the docs](/self-host/clustering/understanding-clustering).
|
This directory contains a basic reference config for a highly available deployment of Pangolin with two nodes. For more information [refer to the docs](https://docs.pangolin.net/self-host/clustering/understanding-clustering).
|
||||||
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
module installer
|
module installer
|
||||||
|
|
||||||
go 1.25.0
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/huh v1.0.0
|
github.com/charmbracelet/huh v1.0.0
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
golang.org/x/term v0.45.0
|
golang.org/x/term v0.46.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +33,6 @@ require (
|
|||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
golang.org/x/sync v0.15.0 // indirect
|
golang.org/x/sync v0.15.0 // indirect
|
||||||
golang.org/x/sys v0.47.0 // indirect
|
golang.org/x/sys v0.48.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-4
@@ -69,10 +69,10 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
|||||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
golang.org/x/sys v0.48.0 h1:bbX/i/6MgT9BVLM9RT1thmxL04yeTAhbEz4SyadbXoo=
|
||||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.48.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
|
||||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE=
|
||||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Сигурни ли сте, че искате да рестартирате WireGuard тунела за <b>{name}</b>? Сайтът ще изгуби връзка за кратко.",
|
"siteRestartDialogMessage": "Сигурни ли сте, че искате да рестартирате WireGuard тунела за <b>{name}</b>? Сайтът ще изгуби връзка за кратко.",
|
||||||
"siteRestartWarning": "Сайтът ще се изключи за кратко, докато тунелът се рестартира.",
|
"siteRestartWarning": "Сайтът ще се изключи за кратко, докато тунелът се рестартира.",
|
||||||
"siteRestarted": "Сайтът е рестартиран",
|
"siteRestarted": "Сайтът е рестартиран",
|
||||||
"siteRestartedDescription": "WireGuard тунелът е рестартиран.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Неуспешно рестартиране на сайта",
|
"siteErrorRestart": "Неуспешно рестартиране на сайта",
|
||||||
"siteErrorRestartDescription": "Възникна грешка при рестартирането на сайта.",
|
"siteErrorRestartDescription": "Възникна грешка при рестартирането на сайта.",
|
||||||
"siteSettingDescription": "Конфигурирайте настройките на сайта",
|
"siteSettingDescription": "Конфигурирайте настройките на сайта",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Създайте и управлявайте ресурси, които са достъпни само чрез свързан клиент.",
|
"clientResourceDescription": "Създайте и управлявайте ресурси, които са достъпни само чрез свързан клиент.",
|
||||||
"privateResourcesBannerTitle": "Достъп до частни ресурси с нулево доверие.",
|
"privateResourcesBannerTitle": "Достъп до частни ресурси с нулево доверие.",
|
||||||
"privateResourcesBannerDescription": "Частните ресурси използват сигурност с нулево доверие, осигурявайки че потребителите и машините могат да имат само достъп до ресурси, които вие изрично предоставяте. Свържете потребителските устройства или машинните клиенти, за да получите достъп до тези ресурси чрез сигурна виртуална частна мрежа.",
|
"privateResourcesBannerDescription": "Частните ресурси използват сигурност с нулево доверие, осигурявайки че потребителите и машините могат да имат само достъп до ресурси, които вие изрично предоставяте. Свържете потребителските устройства или машинните клиенти, за да получите достъп до тези ресурси чрез сигурна виртуална частна мрежа.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Търсене на ресурси...",
|
"resourcesSearch": "Търсене на ресурси...",
|
||||||
"resourceAdd": "Добавете ресурс",
|
"resourceAdd": "Добавете ресурс",
|
||||||
"resourceErrorDelte": "Грешка при изтриване на ресурс",
|
"resourceErrorDelte": "Грешка при изтриване на ресурс",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Няма намерени сайтове.",
|
"sitesNotFound": "Няма намерени сайтове.",
|
||||||
"pangolinServerAdmin": "Администратор на сървър - Панголин",
|
"pangolinServerAdmin": "Администратор на сървър - Панголин",
|
||||||
"licenseTierProfessional": "Професионален лиценз",
|
"licenseTierProfessional": "Професионален лиценз",
|
||||||
"licenseTierEnterprise": "Предприятие лиценз",
|
"licenseTierEnterprise": "Предприятие",
|
||||||
"licenseTierPersonal": "Персонален лиценз",
|
"licenseTierPersonal": "Личен",
|
||||||
|
"licenseTierTier1": "Начален",
|
||||||
|
"licenseTierTier2": "Мащаб",
|
||||||
"licensed": "Лицензиран",
|
"licensed": "Лицензиран",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"no": "Не",
|
"no": "Не",
|
||||||
|
|||||||
+9
-4
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Opravdu chcete restartovat WireGuard tunel pro <b>{name}</b>? Lokalita krátce ztratí konektivitu.",
|
"siteRestartDialogMessage": "Opravdu chcete restartovat WireGuard tunel pro <b>{name}</b>? Lokalita krátce ztratí konektivitu.",
|
||||||
"siteRestartWarning": "Lokalita bude krátce odpojena, zatímco se tunel restartuje.",
|
"siteRestartWarning": "Lokalita bude krátce odpojena, zatímco se tunel restartuje.",
|
||||||
"siteRestarted": "Lokalita restartována",
|
"siteRestarted": "Lokalita restartována",
|
||||||
"siteRestartedDescription": "Tunel WireGuard byl restartován.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Nepodařilo se restartovat lokalitu",
|
"siteErrorRestart": "Nepodařilo se restartovat lokalitu",
|
||||||
"siteErrorRestartDescription": "Při restartování lokality došlo k chybě.",
|
"siteErrorRestartDescription": "Při restartování lokality došlo k chybě.",
|
||||||
"siteSettingDescription": "Konfigurace nastavení na webu",
|
"siteSettingDescription": "Konfigurace nastavení na webu",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Vytváření a správa zdrojů, které jsou přístupné pouze prostřednictvím připojeného klienta",
|
"clientResourceDescription": "Vytváření a správa zdrojů, které jsou přístupné pouze prostřednictvím připojeného klienta",
|
||||||
"privateResourcesBannerTitle": "Zero-Trust soukromý přístup",
|
"privateResourcesBannerTitle": "Zero-Trust soukromý přístup",
|
||||||
"privateResourcesBannerDescription": "Soukromé prostředky používají zero-trust zabezpečení, což zajišťuje, že uživatelé a zařízení mohou získat přístup pouze k prostředkům, k nimž máte explicitní práva. Připojte zařízení uživatele nebo klientské stroje, abyste získali přístup k těmto prostředkům přes zabezpečenou virtuální soukromou síť.",
|
"privateResourcesBannerDescription": "Soukromé prostředky používají zero-trust zabezpečení, což zajišťuje, že uživatelé a zařízení mohou získat přístup pouze k prostředkům, k nimž máte explicitní práva. Připojte zařízení uživatele nebo klientské stroje, abyste získali přístup k těmto prostředkům přes zabezpečenou virtuální soukromou síť.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Prohledat zdroje...",
|
"resourcesSearch": "Prohledat zdroje...",
|
||||||
"resourceAdd": "Přidat zdroj",
|
"resourceAdd": "Přidat zdroj",
|
||||||
"resourceErrorDelte": "Chyba při odstraňování zdroje",
|
"resourceErrorDelte": "Chyba při odstraňování zdroje",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Nebyly nalezeny žádné stránky.",
|
"sitesNotFound": "Nebyly nalezeny žádné stránky.",
|
||||||
"pangolinServerAdmin": "Správce serveru - Pangolin",
|
"pangolinServerAdmin": "Správce serveru - Pangolin",
|
||||||
"licenseTierProfessional": "Profesionální licence",
|
"licenseTierProfessional": "Profesionální licence",
|
||||||
"licenseTierEnterprise": "Podniková licence",
|
"licenseTierEnterprise": "Podniky",
|
||||||
"licenseTierPersonal": "Osobní licence",
|
"licenseTierPersonal": "Osobní",
|
||||||
|
"licenseTierTier1": "Počáteční",
|
||||||
|
"licenseTierTier2": "Měřítko",
|
||||||
"licensed": "Licencováno",
|
"licensed": "Licencováno",
|
||||||
"yes": "Ano",
|
"yes": "Ano",
|
||||||
"no": "Ne",
|
"no": "Ne",
|
||||||
@@ -3979,7 +3984,7 @@
|
|||||||
"tpmAvailable": "TPM k dispozici",
|
"tpmAvailable": "TPM k dispozici",
|
||||||
"windowsAntivirusEnabled": "Antivirus povolen",
|
"windowsAntivirusEnabled": "Antivirus povolen",
|
||||||
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Strážce",
|
||||||
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
||||||
"linuxAppArmorEnabled": "Pancíř aplikace",
|
"linuxAppArmorEnabled": "Pancíř aplikace",
|
||||||
"linuxSELinuxEnabled": "SELinux",
|
"linuxSELinuxEnabled": "SELinux",
|
||||||
|
|||||||
+9
-4
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Er du sikker på, at du vil genstarte WireGuard-tunnelen for <b>{name}</b>? Sitet vil kortvarigt miste forbindelsen.",
|
"siteRestartDialogMessage": "Er du sikker på, at du vil genstarte WireGuard-tunnelen for <b>{name}</b>? Sitet vil kortvarigt miste forbindelsen.",
|
||||||
"siteRestartWarning": "Sitet vil kortvarigt afbryde forbindelse, mens tunnelen genstarter.",
|
"siteRestartWarning": "Sitet vil kortvarigt afbryde forbindelse, mens tunnelen genstarter.",
|
||||||
"siteRestarted": "Site genstartet",
|
"siteRestarted": "Site genstartet",
|
||||||
"siteRestartedDescription": "WireGuard-tunnelen er blevet genstartet.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Kunne ikke genstarte site",
|
"siteErrorRestart": "Kunne ikke genstarte site",
|
||||||
"siteErrorRestartDescription": "En fejl opstod, mens sitet blev genstartet.",
|
"siteErrorRestartDescription": "En fejl opstod, mens sitet blev genstartet.",
|
||||||
"siteSettingDescription": "Konfigurer indstillingerne for sitet",
|
"siteSettingDescription": "Konfigurer indstillingerne for sitet",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Oprette og administrer ressourcer som kun er tilgængelige via en tilsluttet klient",
|
"clientResourceDescription": "Oprette og administrer ressourcer som kun er tilgængelige via en tilsluttet klient",
|
||||||
"privateResourcesBannerTitle": "Zero-Trust privat adgang",
|
"privateResourcesBannerTitle": "Zero-Trust privat adgang",
|
||||||
"privateResourcesBannerDescription": "Private ressourcer bruger Zero-Trust-sikkerhed og sikrer, at brugere og maskiner kun kan få adgang til ressourcer, du eksplicit giver tilladelse til. Tilslut brugerenheder eller maskinklienter for at få adgang til disse ressourcer via et sikkert virtuelt privat netværk.",
|
"privateResourcesBannerDescription": "Private ressourcer bruger Zero-Trust-sikkerhed og sikrer, at brugere og maskiner kun kan få adgang til ressourcer, du eksplicit giver tilladelse til. Tilslut brugerenheder eller maskinklienter for at få adgang til disse ressourcer via et sikkert virtuelt privat netværk.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Søg i ressourcer...",
|
"resourcesSearch": "Søg i ressourcer...",
|
||||||
"resourceAdd": "Tilføj ressource",
|
"resourceAdd": "Tilføj ressource",
|
||||||
"resourceErrorDelte": "Fejl ved sletning af ressource",
|
"resourceErrorDelte": "Fejl ved sletning af ressource",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Ingen sites fundet.",
|
"sitesNotFound": "Ingen sites fundet.",
|
||||||
"pangolinServerAdmin": "Serveradmin - Pangolin",
|
"pangolinServerAdmin": "Serveradmin - Pangolin",
|
||||||
"licenseTierProfessional": "Professionel licens",
|
"licenseTierProfessional": "Professionel licens",
|
||||||
"licenseTierEnterprise": "Enterprise-licens",
|
"licenseTierEnterprise": "Bedrift",
|
||||||
"licenseTierPersonal": "Personlig licens",
|
"licenseTierPersonal": "Personlig",
|
||||||
|
"licenseTierTier1": "Begynder",
|
||||||
|
"licenseTierTier2": "Skala",
|
||||||
"licensed": "Licenseret",
|
"licensed": "Licenseret",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nej",
|
"no": "Nej",
|
||||||
@@ -2068,7 +2073,7 @@
|
|||||||
"aiUsageFilterNotFound": "Ingen valg fundet",
|
"aiUsageFilterNotFound": "Ingen valg fundet",
|
||||||
"aiUsageResetFilters": "Nulstil Filtre",
|
"aiUsageResetFilters": "Nulstil Filtre",
|
||||||
"aiUsageRefresh": "Opdater",
|
"aiUsageRefresh": "Opdater",
|
||||||
"aiUsageTokenTypePrompt": "Prompt",
|
"aiUsageTokenTypePrompt": "Vis",
|
||||||
"aiUsageTokenTypeCacheRead": "Cache læs",
|
"aiUsageTokenTypeCacheRead": "Cache læs",
|
||||||
"aiUsageTokenTypeCacheWrite": "Cache skriv",
|
"aiUsageTokenTypeCacheWrite": "Cache skriv",
|
||||||
"aiUsageTokenTypeCompletion": "Komplettering",
|
"aiUsageTokenTypeCompletion": "Komplettering",
|
||||||
|
|||||||
+9
-4
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Sind Sie sicher, dass Sie den WireGuard-Tunnel für <b>{name}</b> neu starten möchten? Der Standort wird kurzzeitig die Konnektivität verlieren.",
|
"siteRestartDialogMessage": "Sind Sie sicher, dass Sie den WireGuard-Tunnel für <b>{name}</b> neu starten möchten? Der Standort wird kurzzeitig die Konnektivität verlieren.",
|
||||||
"siteRestartWarning": "Der Standort wird kurzzeitig getrennt, während der Tunnel neu gestartet wird.",
|
"siteRestartWarning": "Der Standort wird kurzzeitig getrennt, während der Tunnel neu gestartet wird.",
|
||||||
"siteRestarted": "Standort neu gestartet",
|
"siteRestarted": "Standort neu gestartet",
|
||||||
"siteRestartedDescription": "Der WireGuard-Tunnel wurde neu gestartet.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Fehler beim Neustart des Standorts",
|
"siteErrorRestart": "Fehler beim Neustart des Standorts",
|
||||||
"siteErrorRestartDescription": "Ein Fehler ist aufgetreten, während der Standort neu gestartet wurde.",
|
"siteErrorRestartDescription": "Ein Fehler ist aufgetreten, während der Standort neu gestartet wurde.",
|
||||||
"siteSettingDescription": "Standorteinstellungen konfigurieren",
|
"siteSettingDescription": "Standorteinstellungen konfigurieren",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Erstelle und verwalte Ressourcen, die nur über einen verbundenen Client zugänglich sind",
|
"clientResourceDescription": "Erstelle und verwalte Ressourcen, die nur über einen verbundenen Client zugänglich sind",
|
||||||
"privateResourcesBannerTitle": "Zero-Trust-Zugriff auf private Ressourcen",
|
"privateResourcesBannerTitle": "Zero-Trust-Zugriff auf private Ressourcen",
|
||||||
"privateResourcesBannerDescription": "Private Ressourcen nutzen Zero-Trust und stellen sicher, dass Benutzer und Maschinen nur auf Ressourcen zugreifen können, die Sie explizit gewähren. Verbinden Sie Benutzergeräte oder Maschinen-Clients, um auf diese Ressourcen über ein sicheres virtuelles privates Netzwerk zuzugreifen.",
|
"privateResourcesBannerDescription": "Private Ressourcen nutzen Zero-Trust und stellen sicher, dass Benutzer und Maschinen nur auf Ressourcen zugreifen können, die Sie explizit gewähren. Verbinden Sie Benutzergeräte oder Maschinen-Clients, um auf diese Ressourcen über ein sicheres virtuelles privates Netzwerk zuzugreifen.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Suche Ressourcen...",
|
"resourcesSearch": "Suche Ressourcen...",
|
||||||
"resourceAdd": "Ressource hinzufügen",
|
"resourceAdd": "Ressource hinzufügen",
|
||||||
"resourceErrorDelte": "Fehler beim Löschen der Ressource",
|
"resourceErrorDelte": "Fehler beim Löschen der Ressource",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Keine Standorte gefunden.",
|
"sitesNotFound": "Keine Standorte gefunden.",
|
||||||
"pangolinServerAdmin": "Server-Admin - Pangolin",
|
"pangolinServerAdmin": "Server-Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Professional Lizenz",
|
"licenseTierProfessional": "Professional Lizenz",
|
||||||
"licenseTierEnterprise": "Enterprise Lizenz",
|
"licenseTierEnterprise": "Firma",
|
||||||
"licenseTierPersonal": "Persönliche Lizenz",
|
"licenseTierPersonal": "Persönlich",
|
||||||
|
"licenseTierTier1": "Starter",
|
||||||
|
"licenseTierTier2": "Maßstab",
|
||||||
"licensed": "Lizenziert",
|
"licensed": "Lizenziert",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nein",
|
"no": "Nein",
|
||||||
@@ -3205,7 +3210,7 @@
|
|||||||
"idpAzureClientIdDescription2": "Azure App Registration Client ID",
|
"idpAzureClientIdDescription2": "Azure App Registration Client ID",
|
||||||
"idpAzureClientSecretDescription2": "Azure App Registration Client Geheimnis",
|
"idpAzureClientSecretDescription2": "Azure App Registration Client Geheimnis",
|
||||||
"idpGoogleDescription": "Google OAuth2/OIDC Provider",
|
"idpGoogleDescription": "Google OAuth2/OIDC Provider",
|
||||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC-Anbieter",
|
||||||
"subnet": "Subnetz",
|
"subnet": "Subnetz",
|
||||||
"utilitySubnet": "Nutzsubnetz",
|
"utilitySubnet": "Nutzsubnetz",
|
||||||
"subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.",
|
"subnetDescription": "Das Subnetz für die Netzwerkkonfiguration dieser Organisation.",
|
||||||
|
|||||||
+4
-2
@@ -1118,8 +1118,10 @@
|
|||||||
"sitesNotFound": "No sites found.",
|
"sitesNotFound": "No sites found.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Professional License",
|
"licenseTierProfessional": "Professional License",
|
||||||
"licenseTierEnterprise": "Enterprise License",
|
"licenseTierEnterprise": "Enterprise",
|
||||||
"licenseTierPersonal": "Personal License",
|
"licenseTierPersonal": "Personal",
|
||||||
|
"licenseTierTier1": "Starter",
|
||||||
|
"licenseTierTier2": "Scale",
|
||||||
"licensed": "Licensed",
|
"licensed": "Licensed",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "¿Estás seguro de que deseas reiniciar el túnel WireGuard para <b>{name}</b>? El sitio perderá conectividad brevemente.",
|
"siteRestartDialogMessage": "¿Estás seguro de que deseas reiniciar el túnel WireGuard para <b>{name}</b>? El sitio perderá conectividad brevemente.",
|
||||||
"siteRestartWarning": "El sitio se desconectará brevemente mientras se reinicia el túnel.",
|
"siteRestartWarning": "El sitio se desconectará brevemente mientras se reinicia el túnel.",
|
||||||
"siteRestarted": "Sitio reiniciado",
|
"siteRestarted": "Sitio reiniciado",
|
||||||
"siteRestartedDescription": "El túnel WireGuard ha sido reiniciado.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Error al reiniciar el sitio",
|
"siteErrorRestart": "Error al reiniciar el sitio",
|
||||||
"siteErrorRestartDescription": "Se ha producido un error al reiniciar el sitio.",
|
"siteErrorRestartDescription": "Se ha producido un error al reiniciar el sitio.",
|
||||||
"siteSettingDescription": "Configurar los ajustes en el sitio",
|
"siteSettingDescription": "Configurar los ajustes en el sitio",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado",
|
"clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado",
|
||||||
"privateResourcesBannerTitle": "Acceso privado de confianza cero",
|
"privateResourcesBannerTitle": "Acceso privado de confianza cero",
|
||||||
"privateResourcesBannerDescription": "Los recursos privados usan seguridad de confianza cero, asegurando que usuarios y máquinas solo puedan acceder a los recursos que usted conceda explícitamente. Conecte dispositivos de usuario o clientes de máquinas para acceder a estos recursos a través de una red privada virtual segura.",
|
"privateResourcesBannerDescription": "Los recursos privados usan seguridad de confianza cero, asegurando que usuarios y máquinas solo puedan acceder a los recursos que usted conceda explícitamente. Conecte dispositivos de usuario o clientes de máquinas para acceder a estos recursos a través de una red privada virtual segura.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Buscar recursos...",
|
"resourcesSearch": "Buscar recursos...",
|
||||||
"resourceAdd": "Añadir Recurso",
|
"resourceAdd": "Añadir Recurso",
|
||||||
"resourceErrorDelte": "Error al eliminar el recurso",
|
"resourceErrorDelte": "Error al eliminar el recurso",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Sitios no encontrados.",
|
"sitesNotFound": "Sitios no encontrados.",
|
||||||
"pangolinServerAdmin": "Admin Servidor - Pangolin",
|
"pangolinServerAdmin": "Admin Servidor - Pangolin",
|
||||||
"licenseTierProfessional": "Licencia profesional",
|
"licenseTierProfessional": "Licencia profesional",
|
||||||
"licenseTierEnterprise": "Licencia Enterprise",
|
"licenseTierEnterprise": "Empresa",
|
||||||
"licenseTierPersonal": "Licencia personal",
|
"licenseTierPersonal": "Uso personal",
|
||||||
|
"licenseTierTier1": "Iniciador",
|
||||||
|
"licenseTierTier2": "Escala",
|
||||||
"licensed": "Licenciado",
|
"licensed": "Licenciado",
|
||||||
"yes": "Sí",
|
"yes": "Sí",
|
||||||
"no": "Nu",
|
"no": "Nu",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Êtes-vous sûr de vouloir redémarrer le tunnel WireGuard pour <b>{name}</b>? Le site perdra brièvement sa connectivité.",
|
"siteRestartDialogMessage": "Êtes-vous sûr de vouloir redémarrer le tunnel WireGuard pour <b>{name}</b>? Le site perdra brièvement sa connectivité.",
|
||||||
"siteRestartWarning": "Le site sera brièvement déconnecté pendant le redémarrage du tunnel.",
|
"siteRestartWarning": "Le site sera brièvement déconnecté pendant le redémarrage du tunnel.",
|
||||||
"siteRestarted": "Site redémarré",
|
"siteRestarted": "Site redémarré",
|
||||||
"siteRestartedDescription": "Le tunnel WireGuard a été redémarré.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Échec du redémarrage du site",
|
"siteErrorRestart": "Échec du redémarrage du site",
|
||||||
"siteErrorRestartDescription": "Une erreur s'est produite lors du redémarrage du site.",
|
"siteErrorRestartDescription": "Une erreur s'est produite lors du redémarrage du site.",
|
||||||
"siteSettingDescription": "Configurer les paramètres du site",
|
"siteSettingDescription": "Configurer les paramètres du site",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Créer et gérer des ressources qui ne sont accessibles que via un client connecté",
|
"clientResourceDescription": "Créer et gérer des ressources qui ne sont accessibles que via un client connecté",
|
||||||
"privateResourcesBannerTitle": "Accès privé sans confiance",
|
"privateResourcesBannerTitle": "Accès privé sans confiance",
|
||||||
"privateResourcesBannerDescription": "Les ressources privées utilisent la sécurité sans confiance, garantissant que les utilisateurs et les machines ne peuvent accéder qu'aux ressources que vous accordez explicitement. Connectez les appareils utilisateur ou les clients machines à ces ressources via un réseau privé virtuel sécurisé.",
|
"privateResourcesBannerDescription": "Les ressources privées utilisent la sécurité sans confiance, garantissant que les utilisateurs et les machines ne peuvent accéder qu'aux ressources que vous accordez explicitement. Connectez les appareils utilisateur ou les clients machines à ces ressources via un réseau privé virtuel sécurisé.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Chercher des ressources...",
|
"resourcesSearch": "Chercher des ressources...",
|
||||||
"resourceAdd": "Ajouter une ressource",
|
"resourceAdd": "Ajouter une ressource",
|
||||||
"resourceErrorDelte": "Erreur lors de la de suppression de la ressource",
|
"resourceErrorDelte": "Erreur lors de la de suppression de la ressource",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Aucun site trouvé.",
|
"sitesNotFound": "Aucun site trouvé.",
|
||||||
"pangolinServerAdmin": "Admin Serveur - Pangolin",
|
"pangolinServerAdmin": "Admin Serveur - Pangolin",
|
||||||
"licenseTierProfessional": "Licence Professionnelle",
|
"licenseTierProfessional": "Licence Professionnelle",
|
||||||
"licenseTierEnterprise": "Licence Entreprise",
|
"licenseTierEnterprise": "Entreprise",
|
||||||
"licenseTierPersonal": "Licence personnelle",
|
"licenseTierPersonal": "Personnel",
|
||||||
|
"licenseTierTier1": "Démarrage",
|
||||||
|
"licenseTierTier2": "Échelle",
|
||||||
"licensed": "Sous licence",
|
"licensed": "Sous licence",
|
||||||
"yes": "Oui",
|
"yes": "Oui",
|
||||||
"no": "Non",
|
"no": "Non",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Sei sicuro di voler riavviare il tunnel WireGuard per <b>{name}</b>? Il sito perderà brevemente la connettività.",
|
"siteRestartDialogMessage": "Sei sicuro di voler riavviare il tunnel WireGuard per <b>{name}</b>? Il sito perderà brevemente la connettività.",
|
||||||
"siteRestartWarning": "Il sito si disconnette brevemente mentre il tunnel si riavvia.",
|
"siteRestartWarning": "Il sito si disconnette brevemente mentre il tunnel si riavvia.",
|
||||||
"siteRestarted": "Sito riavviato",
|
"siteRestarted": "Sito riavviato",
|
||||||
"siteRestartedDescription": "Il tunnel WireGuard è stato riavviato.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Impossibile riavviare il sito",
|
"siteErrorRestart": "Impossibile riavviare il sito",
|
||||||
"siteErrorRestartDescription": "Si è verificato un errore durante il riavvio del sito.",
|
"siteErrorRestartDescription": "Si è verificato un errore durante il riavvio del sito.",
|
||||||
"siteSettingDescription": "Configura le impostazioni del sito",
|
"siteSettingDescription": "Configura le impostazioni del sito",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso",
|
"clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso",
|
||||||
"privateResourcesBannerTitle": "Accesso Privato Zero-Trust",
|
"privateResourcesBannerTitle": "Accesso Privato Zero-Trust",
|
||||||
"privateResourcesBannerDescription": "Le risorse private utilizzano la sicurezza zero-trust, assicurandosi che gli utenti e le macchine possano accedere solo alle risorse a cui hai concesso esplicitamente l'accesso. Collega i dispositivi utente o i client macchina per accedere a queste risorse tramite una rete privata virtuale sicura.",
|
"privateResourcesBannerDescription": "Le risorse private utilizzano la sicurezza zero-trust, assicurandosi che gli utenti e le macchine possano accedere solo alle risorse a cui hai concesso esplicitamente l'accesso. Collega i dispositivi utente o i client macchina per accedere a queste risorse tramite una rete privata virtuale sicura.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Cerca risorse...",
|
"resourcesSearch": "Cerca risorse...",
|
||||||
"resourceAdd": "Aggiungi Risorsa",
|
"resourceAdd": "Aggiungi Risorsa",
|
||||||
"resourceErrorDelte": "Errore nell'eliminare la risorsa",
|
"resourceErrorDelte": "Errore nell'eliminare la risorsa",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Nessun sito trovato.",
|
"sitesNotFound": "Nessun sito trovato.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolina",
|
"pangolinServerAdmin": "Server Admin - Pangolina",
|
||||||
"licenseTierProfessional": "Licenza Professional",
|
"licenseTierProfessional": "Licenza Professional",
|
||||||
"licenseTierEnterprise": "Licenza Enterprise",
|
"licenseTierEnterprise": "Impresa",
|
||||||
"licenseTierPersonal": "Licenza Personale",
|
"licenseTierPersonal": "Personale",
|
||||||
|
"licenseTierTier1": "Avviatore",
|
||||||
|
"licenseTierTier2": "Scala",
|
||||||
"licensed": "Con Licenza",
|
"licensed": "Con Licenza",
|
||||||
"yes": "Sì",
|
"yes": "Sì",
|
||||||
"no": "No",
|
"no": "No",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "<b>{name}</b>의 WireGuard 터널을 재시작하시겠습니까? 이 작업으로 인해 사이트의 연결이 일시적으로 중단될 수 있습니다.",
|
"siteRestartDialogMessage": "<b>{name}</b>의 WireGuard 터널을 재시작하시겠습니까? 이 작업으로 인해 사이트의 연결이 일시적으로 중단될 수 있습니다.",
|
||||||
"siteRestartWarning": "터널을 재시작하는 동안 사이트가 일시적으로 연결이 끊깁니다.",
|
"siteRestartWarning": "터널을 재시작하는 동안 사이트가 일시적으로 연결이 끊깁니다.",
|
||||||
"siteRestarted": "사이트가 재시작되었습니다",
|
"siteRestarted": "사이트가 재시작되었습니다",
|
||||||
"siteRestartedDescription": "WireGuard 터널이 재시작되었습니다.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "사이트 재시작 실패",
|
"siteErrorRestart": "사이트 재시작 실패",
|
||||||
"siteErrorRestartDescription": "사이트를 재시작하는 중 오류가 발생했습니다.",
|
"siteErrorRestartDescription": "사이트를 재시작하는 중 오류가 발생했습니다.",
|
||||||
"siteSettingDescription": "사이트에서 설정을 구성하세요.",
|
"siteSettingDescription": "사이트에서 설정을 구성하세요.",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "연결된 클라이언트를 통해서만 접근할 수 있는 리소스를 생성하고 관리하세요.",
|
"clientResourceDescription": "연결된 클라이언트를 통해서만 접근할 수 있는 리소스를 생성하고 관리하세요.",
|
||||||
"privateResourcesBannerTitle": "제로 트러스트 개인 접근",
|
"privateResourcesBannerTitle": "제로 트러스트 개인 접근",
|
||||||
"privateResourcesBannerDescription": "개인 리소스는 제로 트러스트 보안을 사용하여, 사용자와 장치가 명시적으로 허용된 리소스에만 접근할 수 있도록 보장합니다. 이러한 리소스에 접근하려면 안전한 가상 사설 네트워크를 통해 사용자 장치 또는 머신 클라이언트를 연결하십시오.",
|
"privateResourcesBannerDescription": "개인 리소스는 제로 트러스트 보안을 사용하여, 사용자와 장치가 명시적으로 허용된 리소스에만 접근할 수 있도록 보장합니다. 이러한 리소스에 접근하려면 안전한 가상 사설 네트워크를 통해 사용자 장치 또는 머신 클라이언트를 연결하십시오.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "리소스 검색...",
|
"resourcesSearch": "리소스 검색...",
|
||||||
"resourceAdd": "리소스 추가",
|
"resourceAdd": "리소스 추가",
|
||||||
"resourceErrorDelte": "리소스 삭제 중 오류 발생",
|
"resourceErrorDelte": "리소스 삭제 중 오류 발생",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "사이트를 찾을 수 없습니다.",
|
"sitesNotFound": "사이트를 찾을 수 없습니다.",
|
||||||
"pangolinServerAdmin": "서버 관리자 - 판골린",
|
"pangolinServerAdmin": "서버 관리자 - 판골린",
|
||||||
"licenseTierProfessional": "전문 라이센스",
|
"licenseTierProfessional": "전문 라이센스",
|
||||||
"licenseTierEnterprise": "기업 라이선스",
|
"licenseTierEnterprise": "기업",
|
||||||
"licenseTierPersonal": "개인 라이선스",
|
"licenseTierPersonal": "개인",
|
||||||
|
"licenseTierTier1": "스타터",
|
||||||
|
"licenseTierTier2": "스케일",
|
||||||
"licensed": "라이센스",
|
"licensed": "라이센스",
|
||||||
"yes": "예",
|
"yes": "예",
|
||||||
"no": "아니요",
|
"no": "아니요",
|
||||||
|
|||||||
+24
-19
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Er du sikker på at du vil starte WireGuard-tunnelen for <b>{name}</b> på nytt? Området vil midlertidig miste tilkoblingen.",
|
"siteRestartDialogMessage": "Er du sikker på at du vil starte WireGuard-tunnelen for <b>{name}</b> på nytt? Området vil midlertidig miste tilkoblingen.",
|
||||||
"siteRestartWarning": "Området vil kobles kort fra mens tunnelen starter om.",
|
"siteRestartWarning": "Området vil kobles kort fra mens tunnelen starter om.",
|
||||||
"siteRestarted": "Område startet på nytt",
|
"siteRestarted": "Område startet på nytt",
|
||||||
"siteRestartedDescription": "WireGuard-tunnelen er startet på nytt.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Kan ikke starte område på nytt",
|
"siteErrorRestart": "Kan ikke starte område på nytt",
|
||||||
"siteErrorRestartDescription": "En feil oppstod ved omstart av området.",
|
"siteErrorRestartDescription": "En feil oppstod ved omstart av området.",
|
||||||
"siteSettingDescription": "Konfigurere innstillingene på nettstedet",
|
"siteSettingDescription": "Konfigurere innstillingene på nettstedet",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Opprette og administrere ressurser som bare er tilgjengelige via en tilkoblet klient",
|
"clientResourceDescription": "Opprette og administrere ressurser som bare er tilgjengelige via en tilkoblet klient",
|
||||||
"privateResourcesBannerTitle": "Zero-Trust privat tilgang",
|
"privateResourcesBannerTitle": "Zero-Trust privat tilgang",
|
||||||
"privateResourcesBannerDescription": "Private ressurser bruker Zero-Trust-sikkerhet, og sikrer at brukere og maskiner kun kan få tilgang til ressurser du eksplisitt gir tillatelse til. Koble bruker-enheter eller maskinklienter for å få tilgang til disse ressursene via et sikkert virtuelt privat nettverk.",
|
"privateResourcesBannerDescription": "Private ressurser bruker Zero-Trust-sikkerhet, og sikrer at brukere og maskiner kun kan få tilgang til ressurser du eksplisitt gir tillatelse til. Koble bruker-enheter eller maskinklienter for å få tilgang til disse ressursene via et sikkert virtuelt privat nettverk.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Søk i ressurser...",
|
"resourcesSearch": "Søk i ressurser...",
|
||||||
"resourceAdd": "Legg til ressurs",
|
"resourceAdd": "Legg til ressurs",
|
||||||
"resourceErrorDelte": "Feil ved sletting av ressurs",
|
"resourceErrorDelte": "Feil ved sletting av ressurs",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Ingen områder funnet.",
|
"sitesNotFound": "Ingen områder funnet.",
|
||||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||||
"licenseTierProfessional": "Profesjonell lisens",
|
"licenseTierProfessional": "Profesjonell lisens",
|
||||||
"licenseTierEnterprise": "Bedriftslisens",
|
"licenseTierEnterprise": "Bedrift",
|
||||||
"licenseTierPersonal": "Personlig lisens",
|
"licenseTierPersonal": "Personlig",
|
||||||
|
"licenseTierTier1": "Begynner",
|
||||||
|
"licenseTierTier2": "Skala",
|
||||||
"licensed": "Lisensiert",
|
"licensed": "Lisensiert",
|
||||||
"yes": "Ja",
|
"yes": "Ja",
|
||||||
"no": "Nei",
|
"no": "Nei",
|
||||||
@@ -2816,7 +2821,7 @@
|
|||||||
"roleTextImportPreview": "Forhåndsvisning",
|
"roleTextImportPreview": "Forhåndsvisning",
|
||||||
"roleTextImportItemCount": "{count, plural, =0 {Ingen elementer å importere} one {ett element å importere} other {# elementer å importere}}",
|
"roleTextImportItemCount": "{count, plural, =0 {Ingen elementer å importere} one {ett element å importere} other {# elementer å importere}}",
|
||||||
"roleTextImportTotalCount": "{existing} eksisterende + {imported} importert = {total} totalt",
|
"roleTextImportTotalCount": "{existing} eksisterende + {imported} importert = {total} totalt",
|
||||||
"roleTextImportConfirm": "Import",
|
"roleTextImportConfirm": "Importer",
|
||||||
"roleTextImportInvalidFile": "Ustøttet filtype",
|
"roleTextImportInvalidFile": "Ustøttet filtype",
|
||||||
"roleTextImportInvalidFileDescription": "Bare .txt og .csv filer er støttet.",
|
"roleTextImportInvalidFileDescription": "Bare .txt og .csv filer er støttet.",
|
||||||
"roleTextImportEmpty": "Ingen elementer funnet i filen",
|
"roleTextImportEmpty": "Ingen elementer funnet i filen",
|
||||||
@@ -3093,7 +3098,7 @@
|
|||||||
"regionAfrica": "Afrika",
|
"regionAfrica": "Afrika",
|
||||||
"regionNorthernAfrica": "[country name] Nord-Afrika",
|
"regionNorthernAfrica": "[country name] Nord-Afrika",
|
||||||
"regionEasternAfrica": "Øst-Afrika",
|
"regionEasternAfrica": "Øst-Afrika",
|
||||||
"regionMiddleAfrica": "Middle Africa",
|
"regionMiddleAfrica": "Midt-Afrika",
|
||||||
"regionSouthernAfrica": "Sør-Afrika",
|
"regionSouthernAfrica": "Sør-Afrika",
|
||||||
"regionWesternAfrica": "[country name] Vest-Afrika",
|
"regionWesternAfrica": "[country name] Vest-Afrika",
|
||||||
"regionAmericas": "Amerika",
|
"regionAmericas": "Amerika",
|
||||||
@@ -3112,10 +3117,10 @@
|
|||||||
"regionNorthernEurope": "Nord-Europa",
|
"regionNorthernEurope": "Nord-Europa",
|
||||||
"regionSouthernEurope": "Sørlige Europa",
|
"regionSouthernEurope": "Sørlige Europa",
|
||||||
"regionWesternEurope": "Vest-Europa",
|
"regionWesternEurope": "Vest-Europa",
|
||||||
"regionOceania": "Oceania",
|
"regionOceania": "Oseania",
|
||||||
"regionAustraliaAndNewZealand": "Australia og New Zealand",
|
"regionAustraliaAndNewZealand": "Australia og New Zealand",
|
||||||
"regionMelanesia": "Melanesia",
|
"regionMelanesia": "Melanesia",
|
||||||
"regionMicronesia": "Micronesia",
|
"regionMicronesia": "Mikronesia",
|
||||||
"regionPolynesia": "Polynesia",
|
"regionPolynesia": "Polynesia",
|
||||||
"managedSelfHosted": {
|
"managedSelfHosted": {
|
||||||
"title": "Administrert selv-hostet",
|
"title": "Administrert selv-hostet",
|
||||||
@@ -3205,7 +3210,7 @@
|
|||||||
"idpAzureClientIdDescription2": "Azure App registrerings klient-ID",
|
"idpAzureClientIdDescription2": "Azure App registrerings klient-ID",
|
||||||
"idpAzureClientSecretDescription2": "Azure App Registrering Klient Hemmelig",
|
"idpAzureClientSecretDescription2": "Azure App Registrering Klient Hemmelig",
|
||||||
"idpGoogleDescription": "Google OAuth2/OIDC leverandør",
|
"idpGoogleDescription": "Google OAuth2/OIDC leverandør",
|
||||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC-leverandør",
|
||||||
"subnet": "Subnett",
|
"subnet": "Subnett",
|
||||||
"utilitySubnet": "Nyttesubnett",
|
"utilitySubnet": "Nyttesubnett",
|
||||||
"subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.",
|
"subnetDescription": "Undernettverket for denne organisasjonens nettverkskonfigurasjon.",
|
||||||
@@ -3220,7 +3225,7 @@
|
|||||||
"authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside",
|
"authPageBrandingRemoveTitle": "Fjern markedsføring for autentiseringsside",
|
||||||
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
||||||
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
||||||
"brandingLogoURL": "Logo URL",
|
"brandingLogoURL": "Logo-URL",
|
||||||
"brandingLogoURLOrPath": "Logoen URL eller sti",
|
"brandingLogoURLOrPath": "Logoen URL eller sti",
|
||||||
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
|
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
|
||||||
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
|
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
|
||||||
@@ -3360,7 +3365,7 @@
|
|||||||
"resourceHeaderAuthSetupTitleDescription": "Angi grunnleggende auth legitimasjon (brukernavn og passord) for å beskytte denne ressursen med HTTP Header autentisering. Tilgang til det ved hjelp av formatet https://username:password@resource.example.com",
|
"resourceHeaderAuthSetupTitleDescription": "Angi grunnleggende auth legitimasjon (brukernavn og passord) for å beskytte denne ressursen med HTTP Header autentisering. Tilgang til det ved hjelp av formatet https://username:password@resource.example.com",
|
||||||
"resourceHeaderAuthSubmit": "Angi topptekst godkjenning",
|
"resourceHeaderAuthSubmit": "Angi topptekst godkjenning",
|
||||||
"actionSetResourceHeaderAuth": "Angi topptekst godkjenning",
|
"actionSetResourceHeaderAuth": "Angi topptekst godkjenning",
|
||||||
"enterpriseEdition": "Enterprise Edition",
|
"enterpriseEdition": "Enterprise-utgave",
|
||||||
"unlicensed": "Ikke lisensiert",
|
"unlicensed": "Ikke lisensiert",
|
||||||
"beta": "beta",
|
"beta": "beta",
|
||||||
"manageUserDevices": "Bruker Enheter",
|
"manageUserDevices": "Bruker Enheter",
|
||||||
@@ -3501,7 +3506,7 @@
|
|||||||
"priority": "Prioritet",
|
"priority": "Prioritet",
|
||||||
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
|
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
|
||||||
"instanceName": "Forekomst navn",
|
"instanceName": "Forekomst navn",
|
||||||
"clearInstanceName": "Reset Server Association",
|
"clearInstanceName": "Tilbakestill server-assosiasjon",
|
||||||
"pathMatchModalTitle": "Konfigurere matching av sti",
|
"pathMatchModalTitle": "Konfigurere matching av sti",
|
||||||
"pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.",
|
"pathMatchModalDescription": "Sett opp hvordan innkommende forespørsler skal matches basert på deres bane.",
|
||||||
"pathMatchType": "Trefftype",
|
"pathMatchType": "Trefftype",
|
||||||
@@ -3557,11 +3562,11 @@
|
|||||||
"allowedByRule": "Tillatt etter regel",
|
"allowedByRule": "Tillatt etter regel",
|
||||||
"allowedNoAuth": "Tillatt Ingen Auth",
|
"allowedNoAuth": "Tillatt Ingen Auth",
|
||||||
"validAccessToken": "Gyldig tilgangsnøkkel",
|
"validAccessToken": "Gyldig tilgangsnøkkel",
|
||||||
"validHeaderAuth": "Valid header auth",
|
"validHeaderAuth": "Gyldig header-autentisering",
|
||||||
"validPincode": "Gyldig PIN-kode",
|
"validPincode": "Gyldig PIN-kode",
|
||||||
"validPassword": "Gyldig passord",
|
"validPassword": "Gyldig passord",
|
||||||
"validEmail": "Valid email",
|
"validEmail": "Gyldig e-post",
|
||||||
"validSSO": "Valid SSO",
|
"validSSO": "Gyldig SSO",
|
||||||
"validVirtualAPIKey": "Gyldig Virtuell API-nøkkel",
|
"validVirtualAPIKey": "Gyldig Virtuell API-nøkkel",
|
||||||
"view": "Vis",
|
"view": "Vis",
|
||||||
"configManaged": "Konfigurasjon administrert",
|
"configManaged": "Konfigurasjon administrert",
|
||||||
@@ -3570,7 +3575,7 @@
|
|||||||
"droppedByRule": "Legg i regelen",
|
"droppedByRule": "Legg i regelen",
|
||||||
"noSessions": "Ingen økter",
|
"noSessions": "Ingen økter",
|
||||||
"temporaryRequestToken": "Midlertidig forespørsel Token",
|
"temporaryRequestToken": "Midlertidig forespørsel Token",
|
||||||
"noMoreAuthMethods": "No Valid Auth",
|
"noMoreAuthMethods": "Ingen gyldig autentisering",
|
||||||
"ip": "IP",
|
"ip": "IP",
|
||||||
"reason": "Grunn",
|
"reason": "Grunn",
|
||||||
"requestLogs": "HTTP-forespørselslogger",
|
"requestLogs": "HTTP-forespørselslogger",
|
||||||
@@ -3784,14 +3789,14 @@
|
|||||||
"niceIdUpdateErrorDescription": "Det oppstod en feil under oppdatering av Nice ID.",
|
"niceIdUpdateErrorDescription": "Det oppstod en feil under oppdatering av Nice ID.",
|
||||||
"niceIdCannotBeEmpty": "God ID kan ikke være tom",
|
"niceIdCannotBeEmpty": "God ID kan ikke være tom",
|
||||||
"enterIdentifier": "Angi identifikator",
|
"enterIdentifier": "Angi identifikator",
|
||||||
"identifier": "Identifier",
|
"identifier": "Identifikator",
|
||||||
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
|
||||||
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
|
||||||
"loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.",
|
"loginSelectAuthenticationMethod": "Velg en autentiseringsmetode for å fortsette.",
|
||||||
"noData": "Ingen data",
|
"noData": "Ingen data",
|
||||||
"machineClients": "Maskinklienter",
|
"machineClients": "Maskinklienter",
|
||||||
"install": "Installer",
|
"install": "Installer",
|
||||||
"downloadInstaller": "Download Installer",
|
"downloadInstaller": "Last ned installasjonsprogram",
|
||||||
"run": "Kjør",
|
"run": "Kjør",
|
||||||
"envFile": "Miljøfil",
|
"envFile": "Miljøfil",
|
||||||
"serviceFile": "Tjenestefil",
|
"serviceFile": "Tjenestefil",
|
||||||
@@ -3969,7 +3974,7 @@
|
|||||||
"kernelVersion": "Kjerne versjon",
|
"kernelVersion": "Kjerne versjon",
|
||||||
"deviceModel": "Enhets modell",
|
"deviceModel": "Enhets modell",
|
||||||
"serialNumber": "Serienummer",
|
"serialNumber": "Serienummer",
|
||||||
"hostname": "Hostname",
|
"hostname": "Vertsnavn",
|
||||||
"firstSeen": "Først sett",
|
"firstSeen": "Først sett",
|
||||||
"lastSeen": "Sist sett",
|
"lastSeen": "Sist sett",
|
||||||
"biometricsEnabled": "Biometri aktivert",
|
"biometricsEnabled": "Biometri aktivert",
|
||||||
@@ -3999,7 +4004,7 @@
|
|||||||
"disconnected": "Frakoblet",
|
"disconnected": "Frakoblet",
|
||||||
"approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert",
|
"approvalsEmptyStateTitle": "Enhetsgodkjenninger er ikke aktivert",
|
||||||
"approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.",
|
"approvalsEmptyStateDescription": "Aktivere godkjenninger av enheter for at roller må godkjennes av admin før brukere kan koble til nye enheter.",
|
||||||
"approvalsEmptyStateHowToTitle": "How to Enable",
|
"approvalsEmptyStateHowToTitle": "Hvordan aktivere",
|
||||||
"approvalsEmptyStateStep1Title": "Gå til roller",
|
"approvalsEmptyStateStep1Title": "Gå til roller",
|
||||||
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
|
"approvalsEmptyStateStep1Description": "Naviger til organisasjonens roller innstillinger for å konfigurere enhetsgodkjenninger.",
|
||||||
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
||||||
|
|||||||
+9
-4
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Weet u zeker dat u de WireGuard-tunnel voor <b>{name}</b> wilt herstarten? De site zal tijdelijk geen connectiviteit hebben.",
|
"siteRestartDialogMessage": "Weet u zeker dat u de WireGuard-tunnel voor <b>{name}</b> wilt herstarten? De site zal tijdelijk geen connectiviteit hebben.",
|
||||||
"siteRestartWarning": "De site zal kort worden losgekoppeld terwijl de tunnel opnieuw wordt gestart.",
|
"siteRestartWarning": "De site zal kort worden losgekoppeld terwijl de tunnel opnieuw wordt gestart.",
|
||||||
"siteRestarted": "Site herstart",
|
"siteRestarted": "Site herstart",
|
||||||
"siteRestartedDescription": "De WireGuard-tunnel is opnieuw gestart.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Site herstarten mislukt",
|
"siteErrorRestart": "Site herstarten mislukt",
|
||||||
"siteErrorRestartDescription": "Er is een fout opgetreden tijdens het herstarten van de site.",
|
"siteErrorRestartDescription": "Er is een fout opgetreden tijdens het herstarten van de site.",
|
||||||
"siteSettingDescription": "Configureer de instellingen van de site",
|
"siteSettingDescription": "Configureer de instellingen van de site",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Creëer en beheer bronnen die alleen toegankelijk zijn via een verbonden client",
|
"clientResourceDescription": "Creëer en beheer bronnen die alleen toegankelijk zijn via een verbonden client",
|
||||||
"privateResourcesBannerTitle": "Privébronnen via Zero Trust",
|
"privateResourcesBannerTitle": "Privébronnen via Zero Trust",
|
||||||
"privateResourcesBannerDescription": "Privébronnen maken gebruik van zero trust beveiliging, wat ervoor zorgt dat gebruikers en machines alleen toegang kunnen krijgen tot resources die je specifiek toestaat. Verbind gebruikersapparaten of machines om deze middelen te benaderen via een veilig, virtueel privénetwerk.",
|
"privateResourcesBannerDescription": "Privébronnen maken gebruik van zero trust beveiliging, wat ervoor zorgt dat gebruikers en machines alleen toegang kunnen krijgen tot resources die je specifiek toestaat. Verbind gebruikersapparaten of machines om deze middelen te benaderen via een veilig, virtueel privénetwerk.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Zoek bronnen...",
|
"resourcesSearch": "Zoek bronnen...",
|
||||||
"resourceAdd": "Bron toevoegen",
|
"resourceAdd": "Bron toevoegen",
|
||||||
"resourceErrorDelte": "Fout bij verwijderen document",
|
"resourceErrorDelte": "Fout bij verwijderen document",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Geen sites gevonden.",
|
"sitesNotFound": "Geen sites gevonden.",
|
||||||
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
||||||
"licenseTierProfessional": "Professionele licentie",
|
"licenseTierProfessional": "Professionele licentie",
|
||||||
"licenseTierEnterprise": "Enterprise Licentie",
|
"licenseTierEnterprise": "Onderneming",
|
||||||
"licenseTierPersonal": "Persoonlijke licentie",
|
"licenseTierPersonal": "Persoonlijk",
|
||||||
|
"licenseTierTier1": "Beginner",
|
||||||
|
"licenseTierTier2": "Schaal",
|
||||||
"licensed": "Gelicentieerd",
|
"licensed": "Gelicentieerd",
|
||||||
"yes": "ja",
|
"yes": "ja",
|
||||||
"no": "Neen",
|
"no": "Neen",
|
||||||
@@ -3205,7 +3210,7 @@
|
|||||||
"idpAzureClientIdDescription2": "Azure App registratie Client ID",
|
"idpAzureClientIdDescription2": "Azure App registratie Client ID",
|
||||||
"idpAzureClientSecretDescription2": "Azure App registratie client geheim",
|
"idpAzureClientSecretDescription2": "Azure App registratie client geheim",
|
||||||
"idpGoogleDescription": "Algemene OAuth2/OIDC provider",
|
"idpGoogleDescription": "Algemene OAuth2/OIDC provider",
|
||||||
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
|
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC-provider",
|
||||||
"subnet": "Subnet",
|
"subnet": "Subnet",
|
||||||
"utilitySubnet": "Hulpmiddel Subnet",
|
"utilitySubnet": "Hulpmiddel Subnet",
|
||||||
"subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.",
|
"subnetDescription": "Het subnet van de netwerkconfiguratie van deze organisatie.",
|
||||||
|
|||||||
+10
-5
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Czy na pewno chcesz uruchomić ponownie tunel WireGuard dla <b>{name}</b>? Strona tymczasowo straci łączność.",
|
"siteRestartDialogMessage": "Czy na pewno chcesz uruchomić ponownie tunel WireGuard dla <b>{name}</b>? Strona tymczasowo straci łączność.",
|
||||||
"siteRestartWarning": "Strona tymczasowo rozłączy się podczas ponownego uruchamiania tunelu.",
|
"siteRestartWarning": "Strona tymczasowo rozłączy się podczas ponownego uruchamiania tunelu.",
|
||||||
"siteRestarted": "Strona zrestartowana",
|
"siteRestarted": "Strona zrestartowana",
|
||||||
"siteRestartedDescription": "Tunel WireGuard został ponownie uruchomiony.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Nie udało się zrestartować strony",
|
"siteErrorRestart": "Nie udało się zrestartować strony",
|
||||||
"siteErrorRestartDescription": "Wystąpił błąd podczas ponownego uruchamiania strony.",
|
"siteErrorRestartDescription": "Wystąpił błąd podczas ponownego uruchamiania strony.",
|
||||||
"siteSettingDescription": "Skonfiguruj ustawienia na stronie",
|
"siteSettingDescription": "Skonfiguruj ustawienia na stronie",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Twórz i zarządzaj zasobami, które są dostępne tylko za pośrednictwem połączonego klienta",
|
"clientResourceDescription": "Twórz i zarządzaj zasobami, które są dostępne tylko za pośrednictwem połączonego klienta",
|
||||||
"privateResourcesBannerTitle": "Zero zaufania do prywatnego dostępu",
|
"privateResourcesBannerTitle": "Zero zaufania do prywatnego dostępu",
|
||||||
"privateResourcesBannerDescription": "Zasoby prywatne korzystają z zabezpieczeń zero-trust, zapewniając dostęp do zasobów użytkownikom i maszynom, którym wyraźnie udzielasz dostępu. Połącz urządzenia użytkowników lub klientów maszyn z tymi zasobami przez bezpieczną prywatną sieć wirtualną.",
|
"privateResourcesBannerDescription": "Zasoby prywatne korzystają z zabezpieczeń zero-trust, zapewniając dostęp do zasobów użytkownikom i maszynom, którym wyraźnie udzielasz dostępu. Połącz urządzenia użytkowników lub klientów maszyn z tymi zasobami przez bezpieczną prywatną sieć wirtualną.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Szukaj zasobów...",
|
"resourcesSearch": "Szukaj zasobów...",
|
||||||
"resourceAdd": "Dodaj zasób",
|
"resourceAdd": "Dodaj zasób",
|
||||||
"resourceErrorDelte": "Błąd podczas usuwania zasobu",
|
"resourceErrorDelte": "Błąd podczas usuwania zasobu",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Nie znaleziono witryn.",
|
"sitesNotFound": "Nie znaleziono witryn.",
|
||||||
"pangolinServerAdmin": "Administrator serwera - Pangolin",
|
"pangolinServerAdmin": "Administrator serwera - Pangolin",
|
||||||
"licenseTierProfessional": "Licencja Professional",
|
"licenseTierProfessional": "Licencja Professional",
|
||||||
"licenseTierEnterprise": "Licencja Enterprise",
|
"licenseTierEnterprise": "Przedsiębiorstwo",
|
||||||
"licenseTierPersonal": "Licencja osobista",
|
"licenseTierPersonal": "Osobiste",
|
||||||
|
"licenseTierTier1": "Startowy",
|
||||||
|
"licenseTierTier2": "Skala",
|
||||||
"licensed": "Licencjonowany",
|
"licensed": "Licencjonowany",
|
||||||
"yes": "Tak",
|
"yes": "Tak",
|
||||||
"no": "Nie",
|
"no": "Nie",
|
||||||
@@ -3791,7 +3796,7 @@
|
|||||||
"noData": "Brak danych",
|
"noData": "Brak danych",
|
||||||
"machineClients": "Klienci maszyn",
|
"machineClients": "Klienci maszyn",
|
||||||
"install": "Zainstaluj",
|
"install": "Zainstaluj",
|
||||||
"downloadInstaller": "Download Installer",
|
"downloadInstaller": "Pobierz instalator",
|
||||||
"run": "Uruchom",
|
"run": "Uruchom",
|
||||||
"envFile": "Plik środowiska",
|
"envFile": "Plik środowiska",
|
||||||
"serviceFile": "Plik serwisu",
|
"serviceFile": "Plik serwisu",
|
||||||
@@ -3999,7 +4004,7 @@
|
|||||||
"disconnected": "Rozłączony",
|
"disconnected": "Rozłączony",
|
||||||
"approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone",
|
"approvalsEmptyStateTitle": "Zatwierdzanie urządzenia nie włączone",
|
||||||
"approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.",
|
"approvalsEmptyStateDescription": "Włącz zatwierdzanie urządzeń dla ról aby wymagać zgody administratora, zanim użytkownicy będą mogli podłączyć nowe urządzenia.",
|
||||||
"approvalsEmptyStateHowToTitle": "How to Enable",
|
"approvalsEmptyStateHowToTitle": "Jak włączyć",
|
||||||
"approvalsEmptyStateStep1Title": "Przejdź do ról",
|
"approvalsEmptyStateStep1Title": "Przejdź do ról",
|
||||||
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
|
"approvalsEmptyStateStep1Description": "Przejdź do ustawień ról swojej organizacji, aby skonfigurować zatwierdzenia urządzenia.",
|
||||||
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Tem certeza de que deseja reiniciar o túnel WireGuard para <b>{name}</b>? O site perderá brevemente a conectividade.",
|
"siteRestartDialogMessage": "Tem certeza de que deseja reiniciar o túnel WireGuard para <b>{name}</b>? O site perderá brevemente a conectividade.",
|
||||||
"siteRestartWarning": "O site será desconectado brevemente enquanto o túnel reinicia.",
|
"siteRestartWarning": "O site será desconectado brevemente enquanto o túnel reinicia.",
|
||||||
"siteRestarted": "Site reiniciado",
|
"siteRestarted": "Site reiniciado",
|
||||||
"siteRestartedDescription": "O túnel WireGuard foi reiniciado.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Falha ao reiniciar o site",
|
"siteErrorRestart": "Falha ao reiniciar o site",
|
||||||
"siteErrorRestartDescription": "Ocorreu um erro ao reiniciar o site.",
|
"siteErrorRestartDescription": "Ocorreu um erro ao reiniciar o site.",
|
||||||
"siteSettingDescription": "Configurar as configurações no site",
|
"siteSettingDescription": "Configurar as configurações no site",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Criar e gerenciar recursos que só são acessíveis por meio de um cliente conectado",
|
"clientResourceDescription": "Criar e gerenciar recursos que só são acessíveis por meio de um cliente conectado",
|
||||||
"privateResourcesBannerTitle": "Acesso Privado com Confiança Zero",
|
"privateResourcesBannerTitle": "Acesso Privado com Confiança Zero",
|
||||||
"privateResourcesBannerDescription": "Os recursos privados usam segurança de zero confiança, garantindo que usuários e máquinas possam acessar apenas os recursos que você concede explicitamente. Conecte dispositivos de usuários ou clientes de máquinas para acessar esses recursos por meio de uma rede privada virtual segura.",
|
"privateResourcesBannerDescription": "Os recursos privados usam segurança de zero confiança, garantindo que usuários e máquinas possam acessar apenas os recursos que você concede explicitamente. Conecte dispositivos de usuários ou clientes de máquinas para acessar esses recursos por meio de uma rede privada virtual segura.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Procurar recursos...",
|
"resourcesSearch": "Procurar recursos...",
|
||||||
"resourceAdd": "Adicionar Recurso",
|
"resourceAdd": "Adicionar Recurso",
|
||||||
"resourceErrorDelte": "Erro ao apagar recurso",
|
"resourceErrorDelte": "Erro ao apagar recurso",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Nenhum site encontrado.",
|
"sitesNotFound": "Nenhum site encontrado.",
|
||||||
"pangolinServerAdmin": "Administrador do Servidor - Pangolin",
|
"pangolinServerAdmin": "Administrador do Servidor - Pangolin",
|
||||||
"licenseTierProfessional": "Licença Profissional",
|
"licenseTierProfessional": "Licença Profissional",
|
||||||
"licenseTierEnterprise": "Licença Empresarial",
|
"licenseTierEnterprise": "Empresa",
|
||||||
"licenseTierPersonal": "Licença Pessoal",
|
"licenseTierPersonal": "Pessoal",
|
||||||
|
"licenseTierTier1": "Iniciante",
|
||||||
|
"licenseTierTier2": "Escala",
|
||||||
"licensed": "Licenciado",
|
"licensed": "Licenciado",
|
||||||
"yes": "Sim",
|
"yes": "Sim",
|
||||||
"no": "Não",
|
"no": "Não",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "Вы уверены, что хотите перезапустить туннель WireGuard для <b>{name}</b>? Сайт кратковременно потеряет соединение.",
|
"siteRestartDialogMessage": "Вы уверены, что хотите перезапустить туннель WireGuard для <b>{name}</b>? Сайт кратковременно потеряет соединение.",
|
||||||
"siteRestartWarning": "Сайт кратковременно отключится во время перезапуска туннеля.",
|
"siteRestartWarning": "Сайт кратковременно отключится во время перезапуска туннеля.",
|
||||||
"siteRestarted": "Сайт перезапущен",
|
"siteRestarted": "Сайт перезапущен",
|
||||||
"siteRestartedDescription": "Туннель WireGuard был перезапущен.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Не удалось перезапустить сайт",
|
"siteErrorRestart": "Не удалось перезапустить сайт",
|
||||||
"siteErrorRestartDescription": "Произошла ошибка во время перезапуска сайта.",
|
"siteErrorRestartDescription": "Произошла ошибка во время перезапуска сайта.",
|
||||||
"siteSettingDescription": "Настройка параметров на сайте",
|
"siteSettingDescription": "Настройка параметров на сайте",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент",
|
"clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент",
|
||||||
"privateResourcesBannerTitle": "Частный доступ с нулевым доверием",
|
"privateResourcesBannerTitle": "Частный доступ с нулевым доверием",
|
||||||
"privateResourcesBannerDescription": "Частные ресурсы используют безопасность с нулевым доверием, обеспечивая доступ пользователей и устройств только к ресурсам, к которым вы явно предоставили доступ. Подключите пользовательские устройства или машинных клиентов, чтобы получить доступ к этим ресурсам через безопасную виртуальную частную сеть.",
|
"privateResourcesBannerDescription": "Частные ресурсы используют безопасность с нулевым доверием, обеспечивая доступ пользователей и устройств только к ресурсам, к которым вы явно предоставили доступ. Подключите пользовательские устройства или машинных клиентов, чтобы получить доступ к этим ресурсам через безопасную виртуальную частную сеть.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Поиск ресурсов...",
|
"resourcesSearch": "Поиск ресурсов...",
|
||||||
"resourceAdd": "Добавить ресурс",
|
"resourceAdd": "Добавить ресурс",
|
||||||
"resourceErrorDelte": "Ошибка при удалении ресурса",
|
"resourceErrorDelte": "Ошибка при удалении ресурса",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Сайты не найдены.",
|
"sitesNotFound": "Сайты не найдены.",
|
||||||
"pangolinServerAdmin": "Администратор сервера - Pangolin",
|
"pangolinServerAdmin": "Администратор сервера - Pangolin",
|
||||||
"licenseTierProfessional": "Профессиональная лицензия",
|
"licenseTierProfessional": "Профессиональная лицензия",
|
||||||
"licenseTierEnterprise": "Корпоративная лицензия",
|
"licenseTierEnterprise": "Предприятие",
|
||||||
"licenseTierPersonal": "Личная лицензия",
|
"licenseTierPersonal": "Личное",
|
||||||
|
"licenseTierTier1": "Старт",
|
||||||
|
"licenseTierTier2": "Масштаб",
|
||||||
"licensed": "Лицензировано",
|
"licensed": "Лицензировано",
|
||||||
"yes": "Да",
|
"yes": "Да",
|
||||||
"no": "Нет",
|
"no": "Нет",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "<b>{name}</b> için WireGuard tünelini yeniden başlatmak istediğinizden emin misiniz? Site kısa süreliğine bağlantıyı kaybedecektir.",
|
"siteRestartDialogMessage": "<b>{name}</b> için WireGuard tünelini yeniden başlatmak istediğinizden emin misiniz? Site kısa süreliğine bağlantıyı kaybedecektir.",
|
||||||
"siteRestartWarning": "Tünel yeniden başlatılırken site kısa süreliğine kesintiye uğrar.",
|
"siteRestartWarning": "Tünel yeniden başlatılırken site kısa süreliğine kesintiye uğrar.",
|
||||||
"siteRestarted": "Site yeniden başlatıldı",
|
"siteRestarted": "Site yeniden başlatıldı",
|
||||||
"siteRestartedDescription": "WireGuard tüneli yeniden başlatıldı.",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "Sitenin yeniden başlatılması başarısız oldu",
|
"siteErrorRestart": "Sitenin yeniden başlatılması başarısız oldu",
|
||||||
"siteErrorRestartDescription": "Site yeniden başlatılırken bir hata oluştu.",
|
"siteErrorRestartDescription": "Site yeniden başlatılırken bir hata oluştu.",
|
||||||
"siteSettingDescription": "Sitenizdeki ayarları yapılandırın",
|
"siteSettingDescription": "Sitenizdeki ayarları yapılandırın",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "Sadece bağlı bir istemci aracılığıyla erişilebilen kaynakları oluşturun ve yönetin",
|
"clientResourceDescription": "Sadece bağlı bir istemci aracılığıyla erişilebilen kaynakları oluşturun ve yönetin",
|
||||||
"privateResourcesBannerTitle": "Sıfır Güven Özel Erişim",
|
"privateResourcesBannerTitle": "Sıfır Güven Özel Erişim",
|
||||||
"privateResourcesBannerDescription": "Özel kaynaklar sıfır güven güvenliği kullanır, kullanıcılar ve makinelerin yalnızca açıkça izin verdiğiniz kaynaklara erişmesini sağlar. Bu kaynaklara güvenli bir sanal özel ağ üzerinden erişmek için kullanıcı cihazlarını veya makine müşterilerini bağlayın.",
|
"privateResourcesBannerDescription": "Özel kaynaklar sıfır güven güvenliği kullanır, kullanıcılar ve makinelerin yalnızca açıkça izin verdiğiniz kaynaklara erişmesini sağlar. Bu kaynaklara güvenli bir sanal özel ağ üzerinden erişmek için kullanıcı cihazlarını veya makine müşterilerini bağlayın.",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "Kaynakları ara...",
|
"resourcesSearch": "Kaynakları ara...",
|
||||||
"resourceAdd": "Kaynak Ekle",
|
"resourceAdd": "Kaynak Ekle",
|
||||||
"resourceErrorDelte": "Kaynak silinirken hata",
|
"resourceErrorDelte": "Kaynak silinirken hata",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "Site bulunamadı.",
|
"sitesNotFound": "Site bulunamadı.",
|
||||||
"pangolinServerAdmin": "Sunucu Yöneticisi - Pangolin",
|
"pangolinServerAdmin": "Sunucu Yöneticisi - Pangolin",
|
||||||
"licenseTierProfessional": "Profesyonel Lisans",
|
"licenseTierProfessional": "Profesyonel Lisans",
|
||||||
"licenseTierEnterprise": "Kurumsal Lisans",
|
"licenseTierEnterprise": "Kurumsal",
|
||||||
"licenseTierPersonal": "Kişisel Lisans",
|
"licenseTierPersonal": "Kişisel",
|
||||||
|
"licenseTierTier1": "Başlangıç",
|
||||||
|
"licenseTierTier2": "Ölçek",
|
||||||
"licensed": "Lisanslı",
|
"licensed": "Lisanslı",
|
||||||
"yes": "Evet",
|
"yes": "Evet",
|
||||||
"no": "Hayır",
|
"no": "Hayır",
|
||||||
|
|||||||
+8
-3
@@ -132,7 +132,7 @@
|
|||||||
"siteRestartDialogMessage": "确定要重启<b>{name}</b>的WireGuard隧道吗?站点将暂时断开连接。",
|
"siteRestartDialogMessage": "确定要重启<b>{name}</b>的WireGuard隧道吗?站点将暂时断开连接。",
|
||||||
"siteRestartWarning": "隧道重启时,站点将暂时断开连接。",
|
"siteRestartWarning": "隧道重启时,站点将暂时断开连接。",
|
||||||
"siteRestarted": "站点已重启",
|
"siteRestarted": "站点已重启",
|
||||||
"siteRestartedDescription": "WireGuard隧道已重启。",
|
"siteRestartedDescription": "The site has been restarted.",
|
||||||
"siteErrorRestart": "重启站点失败",
|
"siteErrorRestart": "重启站点失败",
|
||||||
"siteErrorRestartDescription": "重启站点时发生错误。",
|
"siteErrorRestartDescription": "重启站点时发生错误。",
|
||||||
"siteSettingDescription": "配置站点设置",
|
"siteSettingDescription": "配置站点设置",
|
||||||
@@ -234,6 +234,9 @@
|
|||||||
"clientResourceDescription": "创建和管理只能通过连接客户端访问的资源",
|
"clientResourceDescription": "创建和管理只能通过连接客户端访问的资源",
|
||||||
"privateResourcesBannerTitle": "零信任私有访问",
|
"privateResourcesBannerTitle": "零信任私有访问",
|
||||||
"privateResourcesBannerDescription": "私有资源采用零信任安全机制,确保只有获得明确授权的用户和机器才能访问。用户设备或机器客户端连接后,即可通过安全的虚拟专用网络访问这些资源。",
|
"privateResourcesBannerDescription": "私有资源采用零信任安全机制,确保只有获得明确授权的用户和机器才能访问。用户设备或机器客户端连接后,即可通过安全的虚拟专用网络访问这些资源。",
|
||||||
|
"licenseBillingBannerTitle": "Manage License Billing",
|
||||||
|
"licenseBillingBannerDescription": "To manage billing for your license keys, including payment methods and invoices, visit the billing page.",
|
||||||
|
"licenseBillingBannerButton": "Go to Billing",
|
||||||
"resourcesSearch": "搜索资源...",
|
"resourcesSearch": "搜索资源...",
|
||||||
"resourceAdd": "添加资源",
|
"resourceAdd": "添加资源",
|
||||||
"resourceErrorDelte": "删除资源时出错",
|
"resourceErrorDelte": "删除资源时出错",
|
||||||
@@ -1118,8 +1121,10 @@
|
|||||||
"sitesNotFound": "未找到站点。",
|
"sitesNotFound": "未找到站点。",
|
||||||
"pangolinServerAdmin": "服务器管理 - Pangolin",
|
"pangolinServerAdmin": "服务器管理 - Pangolin",
|
||||||
"licenseTierProfessional": "专业许可证",
|
"licenseTierProfessional": "专业许可证",
|
||||||
"licenseTierEnterprise": "企业许可证",
|
"licenseTierEnterprise": "企业",
|
||||||
"licenseTierPersonal": "个人许可证",
|
"licenseTierPersonal": "个人",
|
||||||
|
"licenseTierTier1": "启动器",
|
||||||
|
"licenseTierTier2": "扩展",
|
||||||
"licensed": "已授权",
|
"licensed": "已授权",
|
||||||
"yes": "是",
|
"yes": "是",
|
||||||
"no": "否",
|
"no": "否",
|
||||||
|
|||||||
Generated
+2660
-1913
File diff suppressed because it is too large
Load Diff
+29
-29
@@ -33,13 +33,13 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "9.1.0",
|
"@asteasolutions/zod-to-openapi": "9.1.0",
|
||||||
"@aws-sdk/client-s3": "3.1121.0",
|
"@aws-sdk/client-s3": "3.1131.0",
|
||||||
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
|
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
|
||||||
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
|
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.1.tgz",
|
||||||
"@headlessui/react": "2.2.10",
|
"@headlessui/react": "2.2.10",
|
||||||
"@hookform/resolvers": "5.9.1",
|
"@hookform/resolvers": "5.9.1",
|
||||||
"@monaco-editor/react": "4.7.0",
|
"@monaco-editor/react": "4.7.0",
|
||||||
"@node-rs/argon2": "2.2.0",
|
"@node-rs/argon2": "2.2.1",
|
||||||
"@novnc/novnc": "^1.7.0",
|
"@novnc/novnc": "^1.7.0",
|
||||||
"@oslojs/crypto": "1.0.1",
|
"@oslojs/crypto": "1.0.1",
|
||||||
"@oslojs/encoding": "1.1.0",
|
"@oslojs/encoding": "1.1.0",
|
||||||
@@ -65,8 +65,8 @@
|
|||||||
"@react-email/components": "1.0.12",
|
"@react-email/components": "1.0.12",
|
||||||
"@react-email/render": "2.1.0",
|
"@react-email/render": "2.1.0",
|
||||||
"@react-email/tailwind": "2.0.7",
|
"@react-email/tailwind": "2.0.7",
|
||||||
"@simplewebauthn/browser": "13.3.0",
|
"@simplewebauthn/browser": "14.0.0",
|
||||||
"@simplewebauthn/server": "13.3.3",
|
"@simplewebauthn/server": "14.0.1",
|
||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.102.8",
|
"@tanstack/react-query": "5.102.8",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
@@ -99,29 +99,29 @@
|
|||||||
"js-yaml": "5.4.1",
|
"js-yaml": "5.4.1",
|
||||||
"jsonwebtoken": "9.0.3",
|
"jsonwebtoken": "9.0.3",
|
||||||
"lru-cache": "11.5.2",
|
"lru-cache": "11.5.2",
|
||||||
"lucide-react": "1.38.0",
|
"lucide-react": "1.45.0",
|
||||||
"maxmind": "5.0.7",
|
"maxmind": "5.0.7",
|
||||||
"moment": "2.30.1",
|
"moment": "2.30.1",
|
||||||
"next": "16.3.3",
|
"next": "16.3.5",
|
||||||
"next-intl": "4.14.1",
|
"next-intl": "4.14.4",
|
||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"nextjs-toploader": "3.9.17",
|
"nextjs-toploader": "3.9.17",
|
||||||
"nodemailer": "9.1.0",
|
"nodemailer": "10.0.9",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "8.23.0",
|
"pg": "8.23.0",
|
||||||
"posthog-node": "5.51.4",
|
"posthog-node": "5.52.1",
|
||||||
"qrcode.react": "4.2.0",
|
"qrcode.react": "4.2.0",
|
||||||
"react": "19.2.8",
|
"react": "19.3.0",
|
||||||
"react-day-picker": "9.14.0",
|
"react-day-picker": "10.0.1",
|
||||||
"react-dom": "19.2.8",
|
"react-dom": "19.3.0",
|
||||||
"react-easy-sort": "1.8.0",
|
"react-easy-sort": "1.8.0",
|
||||||
"react-hook-form": "7.87.0",
|
"react-hook-form": "7.88.0",
|
||||||
"react-icons": "5.7.0",
|
"react-icons": "5.7.0",
|
||||||
"recharts": "3.10.1",
|
"recharts": "3.10.1",
|
||||||
"reodotdev": "1.1.0",
|
"reodotdev": "1.1.0",
|
||||||
"semver": "7.8.5",
|
"semver": "7.8.5",
|
||||||
"sshpk": "1.18.0",
|
"sshpk": "1.18.0",
|
||||||
"stripe": "22.6.0",
|
"stripe": "22.6.2",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
"tailwind-merge": "3.6.0",
|
"tailwind-merge": "3.6.0",
|
||||||
"topojson-client": "3.1.0",
|
"topojson-client": "3.1.0",
|
||||||
@@ -133,15 +133,15 @@
|
|||||||
"winston": "3.19.0",
|
"winston": "3.19.0",
|
||||||
"winston-daily-rotate-file": "5.0.0",
|
"winston-daily-rotate-file": "5.0.0",
|
||||||
"ws": "8.21.3",
|
"ws": "8.21.3",
|
||||||
"yaml": "2.9.0",
|
"yaml": "2.9.1",
|
||||||
"yargs": "18.1.0",
|
"yargs": "18.1.0",
|
||||||
"zod": "4.5.4",
|
"zod": "4.6.2",
|
||||||
"zod-validation-error": "5.0.0"
|
"zod-validation-error": "5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "2.23.0",
|
"@dotenvx/dotenvx": "2.24.1",
|
||||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||||
"@react-email/ui": "^6.9.3",
|
"@react-email/ui": "^6.9.5",
|
||||||
"@tailwindcss/postcss": "4.3.3",
|
"@tailwindcss/postcss": "4.3.3",
|
||||||
"@tanstack/react-query-devtools": "5.102.8",
|
"@tanstack/react-query-devtools": "5.102.8",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
@@ -155,12 +155,12 @@
|
|||||||
"@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.10",
|
"@types/jsonwebtoken": "9.0.10",
|
||||||
"@types/node": "26.4.0",
|
"@types/node": "26.5.1",
|
||||||
"@types/nodemailer": "8.0.1",
|
"@types/nodemailer": "8.0.1",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/pg": "8.23.1",
|
"@types/pg": "8.23.1",
|
||||||
"@types/react": "19.2.18",
|
"@types/react": "19.3.0",
|
||||||
"@types/react-dom": "19.2.5",
|
"@types/react-dom": "19.3.0",
|
||||||
"@types/semver": "7.8.0",
|
"@types/semver": "7.8.0",
|
||||||
"@types/sshpk": "1.17.5",
|
"@types/sshpk": "1.17.5",
|
||||||
"@types/swagger-ui-express": "4.1.8",
|
"@types/swagger-ui-express": "4.1.8",
|
||||||
@@ -171,20 +171,20 @@
|
|||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.28.2",
|
"esbuild": "0.28.2",
|
||||||
"esbuild-node-externals": "2.0.0",
|
"esbuild-node-externals": "2.0.0",
|
||||||
"eslint": "10.9.1",
|
"eslint": "10.10.0",
|
||||||
"eslint-config-next": "16.3.3",
|
"eslint-config-next": "16.3.5",
|
||||||
"postcss": "8.5.26",
|
"postcss": "8.5.28",
|
||||||
"prettier": "3.9.6",
|
"prettier": "3.9.6",
|
||||||
"react-email": "6.9.3",
|
"react-email": "6.9.5",
|
||||||
"tailwindcss": "4.3.3",
|
"tailwindcss": "4.3.3",
|
||||||
"tsc-alias": "1.9.2",
|
"tsc-alias": "1.9.5",
|
||||||
"tsx": "4.23.13",
|
"tsx": "4.23.13",
|
||||||
"typescript": "6.0.3",
|
"typescript": "7.0.2",
|
||||||
"typescript-eslint": "8.68.0"
|
"typescript-eslint": "8.70.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild": "0.28.2",
|
"esbuild": "0.28.2",
|
||||||
"dompurify": "3.4.0",
|
"dompurify": "3.4.0",
|
||||||
"postcss": "8.5.26"
|
"postcss": "8.5.28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-8
@@ -1,5 +1,6 @@
|
|||||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
import { withReplicas } from "drizzle-orm/pg-core";
|
import { withReplicas } from "drizzle-orm/pg-core";
|
||||||
import { createPool } from "./poolConfig";
|
import { createPool } from "./poolConfig";
|
||||||
|
|
||||||
@@ -7,17 +8,20 @@ function createDb() {
|
|||||||
const config = readConfigFile();
|
const config = readConfigFile();
|
||||||
|
|
||||||
// check the environment variables for postgres config first before the config file
|
// check the environment variables for postgres config first before the config file
|
||||||
if (process.env.POSTGRES_CONNECTION_STRING) {
|
const envConnectionString = readEnvOrFile("POSTGRES_CONNECTION_STRING");
|
||||||
|
if (envConnectionString) {
|
||||||
config.postgres = {
|
config.postgres = {
|
||||||
connection_string: process.env.POSTGRES_CONNECTION_STRING
|
connection_string: envConnectionString
|
||||||
};
|
};
|
||||||
if (process.env.POSTGRES_REPLICA_CONNECTION_STRINGS) {
|
const replicaConnectionStrings = readEnvOrFile(
|
||||||
const replicas =
|
"POSTGRES_REPLICA_CONNECTION_STRINGS"
|
||||||
process.env.POSTGRES_REPLICA_CONNECTION_STRINGS.split(",").map(
|
|
||||||
(conn) => ({
|
|
||||||
connection_string: conn.trim()
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
if (replicaConnectionStrings) {
|
||||||
|
const replicas = replicaConnectionStrings
|
||||||
|
.split(",")
|
||||||
|
.map((conn) => ({
|
||||||
|
connection_string: conn.trim()
|
||||||
|
}));
|
||||||
config.postgres.replicas = replicas;
|
config.postgres.replicas = replicas;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
|
||||||
import { readConfigFile } from "@server/lib/readConfigFile";
|
import { readConfigFile } from "@server/lib/readConfigFile";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
import { withReplicas } from "drizzle-orm/pg-core";
|
import { withReplicas } from "drizzle-orm/pg-core";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { db as mainDb } from "./driver";
|
import { db as mainDb } from "./driver";
|
||||||
@@ -17,7 +18,7 @@ function createLogsDb() {
|
|||||||
const logsConfig = config.postgres_logs;
|
const logsConfig = config.postgres_logs;
|
||||||
|
|
||||||
// Check environment variable first
|
// Check environment variable first
|
||||||
let connectionString = process.env.POSTGRES_LOGS_CONNECTION_STRING;
|
let connectionString = readEnvOrFile("POSTGRES_LOGS_CONNECTION_STRING");
|
||||||
let replicaConnections: Array<{ connection_string: string }> = [];
|
let replicaConnections: Array<{ connection_string: string }> = [];
|
||||||
|
|
||||||
if (!connectionString && logsConfig) {
|
if (!connectionString && logsConfig) {
|
||||||
@@ -26,13 +27,15 @@ function createLogsDb() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
|
||||||
if (process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS) {
|
const replicaConnectionStrings = readEnvOrFile(
|
||||||
replicaConnections =
|
"POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS"
|
||||||
process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS.split(",").map(
|
|
||||||
(conn) => ({
|
|
||||||
connection_string: conn.trim()
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
if (replicaConnectionStrings) {
|
||||||
|
replicaConnections = replicaConnectionStrings
|
||||||
|
.split(",")
|
||||||
|
.map((conn) => ({
|
||||||
|
connection_string: conn.trim()
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no logs database is configured, fall back to main database
|
// If no logs database is configured, fall back to main database
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import fs from "fs";
|
|||||||
import { APP_PATH } from "@server/lib/consts";
|
import { APP_PATH } from "@server/lib/consts";
|
||||||
import { existsSync, mkdirSync } from "fs";
|
import { existsSync, mkdirSync } from "fs";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import { readEnvOrFile } from "@server/lib/getEnvOrYaml";
|
||||||
|
|
||||||
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
export const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
export const exists = checkFileExists(location);
|
export const exists = checkFileExists(location);
|
||||||
@@ -19,7 +20,7 @@ function createDb() {
|
|||||||
: undefined;
|
: undefined;
|
||||||
const sqlite = new Database(location, { verbose });
|
const sqlite = new Database(location, { verbose });
|
||||||
|
|
||||||
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
|
if (readEnvOrFile("ENABLE_SQLITE_WAL_MODE") == "true") {
|
||||||
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
||||||
// contention across subsystems (verifySession, Traefik, audit, ping).
|
// contention across subsystems (verifySession, Traefik, audit, ping).
|
||||||
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
export enum LicenseId {
|
export enum LicenseId {
|
||||||
SMALL_LICENSE = "small_license",
|
TIER1 = "tier1",
|
||||||
BIG_LICENSE = "big_license"
|
TIER2 = "tier2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LicensePriceSet = {
|
export type LicensePriceSet = {
|
||||||
@@ -9,15 +9,15 @@ export type LicensePriceSet = {
|
|||||||
|
|
||||||
export const licensePriceSet: LicensePriceSet = {
|
export const licensePriceSet: LicensePriceSet = {
|
||||||
// Free license matches the freeLimitSet
|
// Free license matches the freeLimitSet
|
||||||
[LicenseId.SMALL_LICENSE]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
|
[LicenseId.TIER1]: "price_1TMJzmD3Ee2Ir7Wm05NlGImT",
|
||||||
[LicenseId.BIG_LICENSE]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
|
[LicenseId.TIER2]: "price_1TMJzzD3Ee2Ir7WmzJw9TerS"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const licensePriceSetSandbox: LicensePriceSet = {
|
export const licensePriceSetSandbox: LicensePriceSet = {
|
||||||
// Free license matches the freeLimitSet
|
// Free license matches the freeLimitSet
|
||||||
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
|
// when matching license the keys closer to 0 index are matched first so list the licenses in descending order of value
|
||||||
[LicenseId.SMALL_LICENSE]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
|
[LicenseId.TIER1]: "price_1SxDwuDCpkOb237Bz0yTiOgN",
|
||||||
[LicenseId.BIG_LICENSE]: "price_1SxDy0DCpkOb237BWJxrxYkl"
|
[LicenseId.TIER2]: "price_1SxDy0DCpkOb237BWJxrxYkl"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getLicensePriceSet(
|
export function getLicensePriceSet(
|
||||||
|
|||||||
@@ -1,3 +1,37 @@
|
|||||||
export const getEnvOrYaml = (envVar: string) => (valFromYaml: any) => {
|
import fs from "fs";
|
||||||
return process.env[envVar] ?? valFromYaml;
|
|
||||||
|
// Resolves an environment variable, also honoring a `<envVar>_FILE` variant
|
||||||
|
// that points to a file whose (trimmed) contents should be used as the
|
||||||
|
// value. This is the common convention for consuming Docker/Swarm secrets
|
||||||
|
// (e.g. mounted at /run/secrets/...) without putting the raw value in the
|
||||||
|
// container's environment.
|
||||||
|
export const readEnvOrFile = (envVar: string): string | undefined => {
|
||||||
|
const fileEnvVar = `${envVar}_FILE`;
|
||||||
|
const filePath = process.env[fileEnvVar];
|
||||||
|
|
||||||
|
if (filePath) {
|
||||||
|
if (process.env[envVar]) {
|
||||||
|
throw new Error(
|
||||||
|
`Both ${envVar} and ${fileEnvVar} are set. Please set only one.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, "utf8").trim();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to read ${fileEnvVar} (${filePath}): ${
|
||||||
|
error instanceof Error ? error.message : error
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return process.env[envVar];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getEnvOrYaml =
|
||||||
|
(envVar: string) =>
|
||||||
|
(valFromYaml: string | undefined): string | undefined => {
|
||||||
|
return readEnvOrFile(envVar) ?? valFromYaml;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as yaml from "js-yaml";
|
|||||||
import { configFilePath1, configFilePath2 } from "./consts";
|
import { configFilePath1, configFilePath2 } from "./consts";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import stoi from "./stoi";
|
import stoi from "./stoi";
|
||||||
import { getEnvOrYaml } from "./getEnvOrYaml";
|
import { getEnvOrYaml, readEnvOrFile } from "./getEnvOrYaml";
|
||||||
|
|
||||||
const portSchema = z.number().positive().gt(0).lte(65535);
|
const portSchema = z.number().positive().gt(0).lte(65535);
|
||||||
|
|
||||||
@@ -26,14 +26,12 @@ export const configSchema = z
|
|||||||
.object({
|
.object({
|
||||||
anonymous_usage: z.boolean().optional().default(true)
|
anonymous_usage: z.boolean().optional().default(true)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
notifications: z
|
notifications: z
|
||||||
.object({
|
.object({
|
||||||
product_updates: z.boolean().optional().default(true),
|
product_updates: z.boolean().optional().default(true),
|
||||||
new_releases: z.boolean().optional().default(true)
|
new_releases: z.boolean().optional().default(true)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
@@ -109,7 +107,6 @@ export const configSchema = z
|
|||||||
id: z.string().optional().default("P-Access-Token-Id"),
|
id: z.string().optional().default("P-Access-Token-Id"),
|
||||||
token: z.string().optional().default("P-Access-Token")
|
token: z.string().optional().default("P-Access-Token")
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
remote_headers: z
|
remote_headers: z
|
||||||
.object({
|
.object({
|
||||||
@@ -126,7 +123,6 @@ export const configSchema = z
|
|||||||
name: z.string().optional().default("Remote-Name"),
|
name: z.string().optional().default("Remote-Name"),
|
||||||
role: z.string().optional().default("Remote-Role")
|
role: z.string().optional().default("Remote-Role")
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
resource_session_request_param: z
|
resource_session_request_param: z
|
||||||
.string()
|
.string()
|
||||||
@@ -164,14 +160,17 @@ export const configSchema = z
|
|||||||
.boolean()
|
.boolean()
|
||||||
.optional()
|
.optional()
|
||||||
.default(false)
|
.default(false)
|
||||||
.transform((val) =>
|
.transform((val) => {
|
||||||
process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER !==
|
const envVal = readEnvOrFile(
|
||||||
undefined
|
"ENABLE_AI_GATEWAY_CLIENT_IP_HEADER"
|
||||||
? process.env.ENABLE_AI_GATEWAY_CLIENT_IP_HEADER ===
|
);
|
||||||
"true"
|
return envVal !== undefined ? envVal === "true" : val;
|
||||||
: val
|
}),
|
||||||
),
|
secret: z
|
||||||
secret: z.string().pipe(z.string().min(8)).optional(),
|
.string()
|
||||||
|
.pipe(z.string().min(8))
|
||||||
|
.optional()
|
||||||
|
.transform(getEnvOrYaml("SERVER_SECRET")),
|
||||||
maxmind_db_path: z.string().optional(),
|
maxmind_db_path: z.string().optional(),
|
||||||
maxmind_asn_path: z.string().optional()
|
maxmind_asn_path: z.string().optional()
|
||||||
})
|
})
|
||||||
@@ -202,7 +201,8 @@ export const configSchema = z
|
|||||||
dashboard_session_length_hours: 720,
|
dashboard_session_length_hours: 720,
|
||||||
resource_session_length_hours: 720,
|
resource_session_length_hours: 720,
|
||||||
trust_proxy: 1,
|
trust_proxy: 1,
|
||||||
enable_ai_gateway_client_ip_header: false
|
enable_ai_gateway_client_ip_header: false,
|
||||||
|
secret: undefined
|
||||||
}),
|
}),
|
||||||
postgres: z
|
postgres: z
|
||||||
.object({
|
.object({
|
||||||
@@ -238,7 +238,6 @@ export const configSchema = z
|
|||||||
.default(5000),
|
.default(5000),
|
||||||
jit_mode: z.boolean().default(true)
|
jit_mode: z.boolean().default(true)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -278,7 +277,6 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(5000)
|
.default(5000)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional(),
|
.optional(),
|
||||||
@@ -325,10 +323,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(50)
|
.default(50)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
gerbil: z
|
gerbil: z
|
||||||
.object({
|
.object({
|
||||||
@@ -357,7 +353,6 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(30)
|
.default(30)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
orgs: z
|
orgs: z
|
||||||
.object({
|
.object({
|
||||||
@@ -391,7 +386,6 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(500)
|
.default(500)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
auth: z
|
auth: z
|
||||||
.object({
|
.object({
|
||||||
@@ -408,10 +402,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(500)
|
.default(500)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
email: z
|
email: z
|
||||||
.object({
|
.object({
|
||||||
@@ -489,10 +481,8 @@ export const configSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default(12)
|
.default(12)
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({})
|
.prefault({})
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@@ -513,10 +503,7 @@ export const configSchema = z
|
|||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
// If hybrid is not defined, server secret must be defined. If its not defined already then pull it from env
|
// If hybrid is not defined, server secret must be defined
|
||||||
if (data.server?.secret === undefined) {
|
|
||||||
data.server.secret = process.env.SERVER_SECRET;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
data.server?.secret !== undefined &&
|
data.server?.secret !== undefined &&
|
||||||
data.server.secret.length > 0
|
data.server.secret.length > 0
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { getTraefikConfig } from "#dynamic/lib/traefik";
|
|||||||
import { getValidCertificatesForDomains } from "@server/lib/certificates";
|
import { getValidCertificatesForDomains } from "@server/lib/certificates";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import license from "#dynamic/license/license";
|
||||||
|
|
||||||
export class TraefikConfigManager {
|
export class TraefikConfigManager {
|
||||||
private intervalId: NodeJS.Timeout | null = null;
|
private intervalId: NodeJS.Timeout | null = null;
|
||||||
@@ -357,7 +358,11 @@ export class TraefikConfigManager {
|
|||||||
this.lastActiveDomains = new Set(domains);
|
this.lastActiveDomains = new Set(domains);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.CERT_MODE === "pangolin" && build != "oss") {
|
if (
|
||||||
|
process.env.CERT_MODE === "pangolin" &&
|
||||||
|
build != "oss" &&
|
||||||
|
(await license.hasTier(["personal", "tier2", "enterprise"]))
|
||||||
|
) {
|
||||||
// Scan current local certificate state
|
// Scan current local certificate state
|
||||||
this.lastLocalCertificateState =
|
this.lastLocalCertificateState =
|
||||||
await this.scanLocalCertificateState();
|
await this.scanLocalCertificateState();
|
||||||
@@ -717,10 +722,9 @@ export class TraefikConfigManager {
|
|||||||
}
|
}
|
||||||
if (shouldWrite) {
|
if (shouldWrite) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
traefikDynamicConfigPath,
|
traefikDynamicConfigPath,
|
||||||
yaml.dump(traefikConfig, { noRefs: true }),
|
yaml.dump(traefikConfig, { noRefs: true })
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
logger.info("Traefik dynamic config updated");
|
logger.info("Traefik dynamic config updated");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -822,7 +826,7 @@ export class TraefikConfigManager {
|
|||||||
// Only write the config if it has changed
|
// Only write the config if it has changed
|
||||||
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
||||||
if (newConfigYaml !== originalConfigYaml) {
|
if (newConfigYaml !== originalConfigYaml) {
|
||||||
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8");
|
this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
|
||||||
logger.info("Dynamic cert config updated from local certificates");
|
logger.info("Dynamic cert config updated from local certificates");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -900,26 +904,23 @@ export class TraefikConfigManager {
|
|||||||
`Processing certificate for domain: ${cert.domain}`
|
`Processing certificate for domain: ${cert.domain}`
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(certPath, cert.certFile, "utf8");
|
// Write atomically (temp file + rename) so Traefik's
|
||||||
fs.writeFileSync(keyPath, cert.keyFile, "utf8");
|
// file watcher never observes a partially written
|
||||||
|
// cert/key and fails with "failed to find any PEM data".
|
||||||
// Set appropriate permissions (readable by owner only for key file)
|
this.atomicWriteFileSync(certPath, cert.certFile, 0o644);
|
||||||
fs.chmodSync(certPath, 0o644);
|
this.atomicWriteFileSync(keyPath, cert.keyFile, 0o600);
|
||||||
fs.chmodSync(keyPath, 0o600);
|
|
||||||
|
|
||||||
// Write/update .last_update file with current timestamp
|
// Write/update .last_update file with current timestamp
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
lastUpdatePath,
|
lastUpdatePath,
|
||||||
new Date().toISOString(),
|
new Date().toISOString()
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if this is a wildcard certificate and store it
|
// Check if this is a wildcard certificate and store it
|
||||||
const wildcardPath = path.join(domainDir, ".wildcard");
|
const wildcardPath = path.join(domainDir, ".wildcard");
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
wildcardPath,
|
wildcardPath,
|
||||||
cert.wildcard ? "true" : "false",
|
cert.wildcard ? "true" : "false"
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
@@ -931,10 +932,9 @@ export class TraefikConfigManager {
|
|||||||
// even if the cert content didn't change
|
// even if the cert content didn't change
|
||||||
if (cert.expiresAt) {
|
if (cert.expiresAt) {
|
||||||
const expiresAtPath = path.join(domainDir, ".expires_at");
|
const expiresAtPath = path.join(domainDir, ".expires_at");
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
expiresAtPath,
|
expiresAtPath,
|
||||||
cert.expiresAt.toString(),
|
cert.expiresAt.toString()
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,7 +970,7 @@ export class TraefikConfigManager {
|
|||||||
// Only write the config if it has changed
|
// Only write the config if it has changed
|
||||||
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
const newConfigYaml = yaml.dump(dynamicConfig, { noRefs: true });
|
||||||
if (newConfigYaml !== originalConfigYaml) {
|
if (newConfigYaml !== originalConfigYaml) {
|
||||||
fs.writeFileSync(dynamicConfigPath, newConfigYaml, "utf8");
|
this.atomicWriteFileSync(dynamicConfigPath, newConfigYaml);
|
||||||
logger.info("Dynamic cert config updated");
|
logger.info("Dynamic cert config updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1141,10 +1141,9 @@ export class TraefikConfigManager {
|
|||||||
|
|
||||||
if (configChanged) {
|
if (configChanged) {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(
|
this.atomicWriteFileSync(
|
||||||
dynamicConfigPath,
|
dynamicConfigPath,
|
||||||
yaml.dump(dynamicConfig, { noRefs: true }),
|
yaml.dump(dynamicConfig, { noRefs: true })
|
||||||
"utf8"
|
|
||||||
);
|
);
|
||||||
logger.info("Dynamic config updated after cleanup");
|
logger.info("Dynamic config updated after cleanup");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -1171,6 +1170,36 @@ export class TraefikConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write a file atomically by writing to a temp file in the same
|
||||||
|
* directory and renaming it into place. This avoids Traefik (which
|
||||||
|
* watches these files/directories) picking up a partially written
|
||||||
|
* file and failing to parse it (e.g. "failed to find any PEM data").
|
||||||
|
*/
|
||||||
|
private atomicWriteFileSync(
|
||||||
|
filePath: string,
|
||||||
|
data: string,
|
||||||
|
mode?: number
|
||||||
|
): void {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
const tmpPath = path.join(
|
||||||
|
dir,
|
||||||
|
`.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(tmpPath, data, "utf8");
|
||||||
|
if (mode !== undefined) {
|
||||||
|
fs.chmodSync(tmpPath, mode);
|
||||||
|
}
|
||||||
|
fs.renameSync(tmpPath, filePath);
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
fs.rmSync(tmpPath, { force: true });
|
||||||
|
} catch {}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if file exists
|
* Check if file exists
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { setHostMeta } from "@server/lib/hostMeta";
|
|||||||
const keyTypes = ["host"] as const;
|
const keyTypes = ["host"] as const;
|
||||||
export type LicenseKeyType = (typeof keyTypes)[number];
|
export type LicenseKeyType = (typeof keyTypes)[number];
|
||||||
|
|
||||||
const keyTiers = ["personal", "enterprise"] as const;
|
const keyTiers = ["personal", "enterprise", "tier1", "tier2"] as const;
|
||||||
export type LicenseKeyTier = (typeof keyTiers)[number];
|
export type LicenseKeyTier = (typeof keyTiers)[number];
|
||||||
|
|
||||||
export type LicenseStatus = {
|
export type LicenseStatus = {
|
||||||
@@ -50,6 +50,10 @@ export class License {
|
|||||||
public async isUnlocked() {
|
public async isUnlocked() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasTier(tier: LicenseKeyTier[]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await setHostMeta();
|
await setHostMeta();
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ export class JobScheduler {
|
|||||||
label: string
|
label: string
|
||||||
): () => Promise<void> {
|
): () => Promise<void> {
|
||||||
return async () => {
|
return async () => {
|
||||||
if (!(await license.isUnlocked())) {
|
if (!(await license.hasTier(["personal", "tier2", "enterprise"]))) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Skipping ${label} tick - license is not subscribed`
|
`Skipping ${label} tick - requires a tier2 license`
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,10 +63,11 @@ export class AuthoritativeDNSServer {
|
|||||||
private allDomains: Set<string> = new Set();
|
private allDomains: Set<string> = new Set();
|
||||||
private domainRefreshInterval: NodeJS.Timeout | null = null;
|
private domainRefreshInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
// Cached license/subscription status. license.isUnlocked() does a DB
|
// Cached license/plan status - only a tier2 license unlocks the DNS
|
||||||
// round-trip on every call, so it can't be checked per-query on a UDP
|
// server. license.hasPlan() does a DB round-trip on every call, so it
|
||||||
// server that may see very high query volume - instead it's polled on
|
// can't be checked per-query on a UDP server that may see very high
|
||||||
// the same cadence as the domain set refresh and read from memory here.
|
// query volume - instead it's polled on the same cadence as the domain
|
||||||
|
// set refresh and read from memory here.
|
||||||
private isLicensed: boolean = false;
|
private isLicensed: boolean = false;
|
||||||
private licenseRefreshInterval: NodeJS.Timeout | null = null;
|
private licenseRefreshInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ export class AuthoritativeDNSServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!this.isLicensed) {
|
if (!this.isLicensed) {
|
||||||
logger.debug("Refusing DNS query - license is not subscribed");
|
logger.debug("Refusing DNS query - requires a tier2 license");
|
||||||
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
|
// REFUSED (rcode=5) indicates a policy refusal by this nameserver.
|
||||||
this.sendResponse(packet, [], rinfo, false, 5, []);
|
this.sendResponse(packet, [], rinfo, false, 5, []);
|
||||||
return;
|
return;
|
||||||
@@ -1042,7 +1043,11 @@ export class AuthoritativeDNSServer {
|
|||||||
|
|
||||||
private async refreshLicenseStatus(): Promise<void> {
|
private async refreshLicenseStatus(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
this.isLicensed = await license.isUnlocked();
|
this.isLicensed = await license.hasTier([
|
||||||
|
"personal",
|
||||||
|
"tier2",
|
||||||
|
"enterprise"
|
||||||
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to refresh license status:", error);
|
logger.error("Failed to refresh license status:", error);
|
||||||
this.isLicensed = false;
|
this.isLicensed = false;
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ export const privateConfigSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.transform(getEnvOrYaml("FOSSORIAL_API_KEY"))
|
.transform(getEnvOrYaml("FOSSORIAL_API_KEY"))
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
redis: z
|
redis: z
|
||||||
.object({
|
.object({
|
||||||
@@ -166,7 +165,6 @@ export const privateConfigSchema = z
|
|||||||
.optional()
|
.optional()
|
||||||
.default("http://gerbil:3004")
|
.default("http://gerbil:3004")
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
flags: z
|
flags: z
|
||||||
.object({
|
.object({
|
||||||
@@ -187,7 +185,6 @@ export const privateConfigSchema = z
|
|||||||
// (server/private/lib/config.ts).
|
// (server/private/lib/config.ts).
|
||||||
disable_private_http_placeholder: z.boolean().optional()
|
disable_private_http_placeholder: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
acme: z
|
acme: z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import {
|
|||||||
getValidCertificatesForDomains
|
getValidCertificatesForDomains
|
||||||
} from "@server/lib/certificates";
|
} from "@server/lib/certificates";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
import license from "#private/license/license";
|
||||||
import regionalCache from "#private/lib/cache";
|
import regionalCache from "#private/lib/cache";
|
||||||
import { TargetWithSite } from "@server/lib/traefik/types";
|
import { TargetWithSite } from "@server/lib/traefik/types";
|
||||||
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
|
import { buildWildcardTls } from "@server/lib/traefik/certResolver";
|
||||||
@@ -395,8 +396,15 @@ export async function getTraefikConfig(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
|
||||||
|
// license (self-hosted) or a saas build - otherwise fall back to
|
||||||
|
// Traefik's own cert resolvers (buildWildcardTls) throughout.
|
||||||
|
const pangolinCertModeEnabled =
|
||||||
|
privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin" &&
|
||||||
|
(await license.hasTier(["personal", "tier2", "enterprise"]));
|
||||||
|
|
||||||
let validCerts: CertificateResult[] = [];
|
let validCerts: CertificateResult[] = [];
|
||||||
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
|
if (pangolinCertModeEnabled) {
|
||||||
// create a list of all domains to get certs for
|
// create a list of all domains to get certs for
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
for (const resource of resourcesMap.values()) {
|
for (const resource of resourcesMap.values()) {
|
||||||
@@ -522,10 +530,7 @@ export async function getTraefikConfig(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let tls = {};
|
let tls = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
tls = buildWildcardTls({
|
tls = buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain: !!resource.subdomain,
|
hasSubdomain: !!resource.subdomain,
|
||||||
@@ -546,6 +551,30 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
config_output.http.services![serviceName] = {
|
||||||
|
loadBalancer: {
|
||||||
|
servers: buildHttpLoadBalancerServers(targets),
|
||||||
|
...(resource.stickySession
|
||||||
|
? buildStickySessionCookie(resource.ssl)
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (resource.tlsServerName) {
|
||||||
|
if (!config_output.http.serversTransports) {
|
||||||
|
config_output.http.serversTransports = {};
|
||||||
|
}
|
||||||
|
config_output.http.serversTransports![transportName] = {
|
||||||
|
serverName: resource.tlsServerName,
|
||||||
|
//unfortunately the following needs to be set. traefik doesn't merge the default serverTransport settings
|
||||||
|
// if defined in the static config and here. if not set, self-signed certs won't work
|
||||||
|
insecureSkipVerify: true
|
||||||
|
};
|
||||||
|
config_output.http.services![
|
||||||
|
serviceName
|
||||||
|
].loadBalancer.serversTransport = transportName;
|
||||||
|
}
|
||||||
|
|
||||||
if (resource.ssl) {
|
if (resource.ssl) {
|
||||||
config_output.http.routers![routerName + "-redirect"] = {
|
config_output.http.routers![routerName + "-redirect"] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
@@ -710,31 +739,6 @@ export async function getTraefikConfig(
|
|||||||
priority: priority,
|
priority: priority,
|
||||||
...(resource.ssl ? { tls } : {})
|
...(resource.ssl ? { tls } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
config_output.http.services![serviceName] = {
|
|
||||||
loadBalancer: {
|
|
||||||
servers: buildHttpLoadBalancerServers(targets),
|
|
||||||
...(resource.stickySession
|
|
||||||
? buildStickySessionCookie(resource.ssl)
|
|
||||||
: {})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add the serversTransport if TLS server name is provided
|
|
||||||
if (resource.tlsServerName) {
|
|
||||||
if (!config_output.http.serversTransports) {
|
|
||||||
config_output.http.serversTransports = {};
|
|
||||||
}
|
|
||||||
config_output.http.serversTransports![transportName] = {
|
|
||||||
serverName: resource.tlsServerName,
|
|
||||||
//unfortunately the following needs to be set. traefik doesn't merge the default serverTransport settings
|
|
||||||
// if defined in the static config and here. if not set, self-signed certs won't work
|
|
||||||
insecureSkipVerify: true
|
|
||||||
};
|
|
||||||
config_output.http.services![
|
|
||||||
serviceName
|
|
||||||
].loadBalancer.serversTransport = transportName;
|
|
||||||
}
|
|
||||||
} else if (resource.mode == "tcp" || resource.mode == "udp") {
|
} else if (resource.mode == "tcp" || resource.mode == "udp") {
|
||||||
// Non-HTTP (TCP/UDP) configuration
|
// Non-HTTP (TCP/UDP) configuration
|
||||||
if (!resource.enableProxy) {
|
if (!resource.enableProxy) {
|
||||||
@@ -791,10 +795,7 @@ export async function getTraefikConfig(
|
|||||||
domainCertResolver,
|
domainCertResolver,
|
||||||
preferWildcardCert
|
preferWildcardCert
|
||||||
}) => {
|
}) => {
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
return buildWildcardTls({
|
return buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain,
|
hasSubdomain,
|
||||||
@@ -835,10 +836,7 @@ export async function getTraefikConfig(
|
|||||||
maintenancePageUiUrl,
|
maintenancePageUiUrl,
|
||||||
redirectHttpsMiddlewareName,
|
redirectHttpsMiddlewareName,
|
||||||
resolveTls: (fullDomain) => {
|
resolveTls: (fullDomain) => {
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// siteResource aliases don't have a per-domain cert
|
// siteResource aliases don't have a per-domain cert
|
||||||
// resolver stored, so always fall back to the global
|
// resolver stored, so always fall back to the global
|
||||||
// defaults.
|
// defaults.
|
||||||
@@ -929,10 +927,7 @@ export async function getTraefikConfig(
|
|||||||
const rule = buildHostRule(fullDomain, ir.wildcard);
|
const rule = buildHostRule(fullDomain, ir.wildcard);
|
||||||
|
|
||||||
let tls: any = {};
|
let tls: any = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
tls = buildWildcardTls({
|
tls = buildWildcardTls({
|
||||||
fullDomain,
|
fullDomain,
|
||||||
hasSubdomain: !!ir.subdomain,
|
hasSubdomain: !!ir.subdomain,
|
||||||
@@ -1012,10 +1007,7 @@ export async function getTraefikConfig(
|
|||||||
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
|
const rule = `Host(\`${fullDomain}\`) && ClientIP(\`${exitNode.address}\`)`; // restrict to coming from the exit node ip range that the client is connected to
|
||||||
|
|
||||||
let tls: any = {};
|
let tls: any = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// siteResource aliases don't have a per-domain cert
|
// siteResource aliases don't have a per-domain cert
|
||||||
// resolver stored, so always fall back to the global
|
// resolver stored, so always fall back to the global
|
||||||
// defaults.
|
// defaults.
|
||||||
@@ -1089,7 +1081,7 @@ export async function getTraefikConfig(
|
|||||||
.where(eq(exitNodes.exitNodeId, exitNodeId));
|
.where(eq(exitNodes.exitNodeId, exitNodeId));
|
||||||
|
|
||||||
let validCertsLoginPages: CertificateResult[] = [];
|
let validCertsLoginPages: CertificateResult[] = [];
|
||||||
if (privateConfig.getRawPrivateConfig().acme?.cert_mode == "pangolin") {
|
if (pangolinCertModeEnabled) {
|
||||||
// create a list of all domains to get certs for
|
// create a list of all domains to get certs for
|
||||||
const domains = new Set<string>();
|
const domains = new Set<string>();
|
||||||
for (const lp of exitNodeLoginPages) {
|
for (const lp of exitNodeLoginPages) {
|
||||||
@@ -1134,10 +1126,7 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tls = {};
|
const tls = {};
|
||||||
if (
|
if (!pangolinCertModeEnabled) {
|
||||||
privateConfig.getRawPrivateConfig().acme?.cert_mode !=
|
|
||||||
"pangolin"
|
|
||||||
) {
|
|
||||||
// TODO: we need to add the wildcard logic here too
|
// TODO: we need to add the wildcard logic here too
|
||||||
} else {
|
} else {
|
||||||
// find a cert that matches the full domain, if not continue
|
// find a cert that matches the full domain, if not continue
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
LicenseStatus
|
LicenseStatus
|
||||||
} from "@server/license/license";
|
} from "@server/license/license";
|
||||||
import { setHostMeta } from "@server/lib/hostMeta";
|
import { setHostMeta } from "@server/lib/hostMeta";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
type ActivateLicenseKeyAPIResponse = {
|
type ActivateLicenseKeyAPIResponse = {
|
||||||
data: {
|
data: {
|
||||||
@@ -119,6 +120,9 @@ LQIDAQAB
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async isUnlocked(): Promise<boolean> {
|
public async isUnlocked(): Promise<boolean> {
|
||||||
|
if (build == "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const status = await this.check();
|
const status = await this.check();
|
||||||
if (status.isHostLicensed) {
|
if (status.isHostLicensed) {
|
||||||
if (status.isLicenseValid) {
|
if (status.isLicenseValid) {
|
||||||
@@ -128,6 +132,20 @@ LQIDAQAB
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async hasTier(tier: LicenseKeyTier[]): Promise<boolean> {
|
||||||
|
if (build == "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const status = await this.check();
|
||||||
|
if (status.isHostLicensed && status.isLicenseValid) {
|
||||||
|
return (
|
||||||
|
status.tier !== undefined &&
|
||||||
|
tier.includes(status.tier as LicenseKeyTier)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public async check(): Promise<LicenseStatus> {
|
public async check(): Promise<LicenseStatus> {
|
||||||
// If a check is already in progress, return the last known status
|
// If a check is already in progress, return the last known status
|
||||||
if (this.checkInProgress) {
|
if (this.checkInProgress) {
|
||||||
@@ -135,8 +153,7 @@ LQIDAQAB
|
|||||||
"License check already in progress, returning last known status"
|
"License check already in progress, returning last known status"
|
||||||
);
|
);
|
||||||
const lastStatus = this.statusCache.get(this.statusKey) as
|
const lastStatus = this.statusCache.get(this.statusKey) as
|
||||||
| LicenseStatus
|
LicenseStatus | undefined;
|
||||||
| undefined;
|
|
||||||
if (lastStatus) {
|
if (lastStatus) {
|
||||||
return lastStatus;
|
return lastStatus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ export const queryAccessAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export const queryActionAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ export const queryConnectionAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -222,15 +222,16 @@ export async function handleSubscriptionCreated(
|
|||||||
|
|
||||||
let numUsers: number;
|
let numUsers: number;
|
||||||
let numSites: number;
|
let numSites: number;
|
||||||
|
let tier = "enterprise";
|
||||||
|
|
||||||
if (subscriptionPriceId === priceSet[LicenseId.SMALL_LICENSE]) {
|
if (subscriptionPriceId === priceSet[LicenseId.TIER1]) {
|
||||||
numUsers = 25;
|
numUsers = 25;
|
||||||
numSites = 25;
|
numSites = 25;
|
||||||
} else if (
|
tier = "tier1";
|
||||||
subscriptionPriceId === priceSet[LicenseId.BIG_LICENSE]
|
} else if (subscriptionPriceId === priceSet[LicenseId.TIER2]) {
|
||||||
) {
|
|
||||||
numUsers = 50;
|
numUsers = 50;
|
||||||
numSites = 100;
|
numSites = 100;
|
||||||
|
tier = "tier2";
|
||||||
} else {
|
} else {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
|
`Unknown price ID ${subscriptionPriceId} for subscription ${subscription.id}`
|
||||||
@@ -256,7 +257,8 @@ export async function handleSubscriptionCreated(
|
|||||||
licenseId: parseInt(licenseId),
|
licenseId: parseInt(licenseId),
|
||||||
paidFor: true,
|
paidFor: true,
|
||||||
users: numUsers,
|
users: numUsers,
|
||||||
sites: numSites
|
sites: numSites,
|
||||||
|
tier: tier
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -64,14 +64,11 @@ export async function generateNewEnterpriseLicense(
|
|||||||
|
|
||||||
const licenseData = req.body;
|
const licenseData = req.body;
|
||||||
|
|
||||||
if (
|
if (licenseData.tier != "tier2" && licenseData.tier != "tier1") {
|
||||||
licenseData.tier != "big_license" &&
|
|
||||||
licenseData.tier != "small_license"
|
|
||||||
) {
|
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
"Invalid tier specified. Must be either 'big_license' or 'small_license'."
|
"Invalid tier specified. Must be either 'tier2' or 'tier1'."
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -118,9 +115,7 @@ export async function generateNewEnterpriseLicense(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tier =
|
const tier =
|
||||||
licenseData.tier === "big_license"
|
licenseData.tier === "tier2" ? LicenseId.TIER2 : LicenseId.TIER1;
|
||||||
? LicenseId.BIG_LICENSE
|
|
||||||
: LicenseId.SMALL_LICENSE;
|
|
||||||
const tierPrice = getLicensePriceSet()[tier];
|
const tierPrice = getLicensePriceSet()[tier];
|
||||||
|
|
||||||
const session = await stripe!.checkout.sessions.create({
|
const session = await stripe!.checkout.sessions.create({
|
||||||
|
|||||||
@@ -47,7 +47,6 @@ export const queryAiSessionLogsQuery = z.strictObject({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ const queryAccessAuditLogsQuery = z.object({
|
|||||||
error: "timeStart must be a valid ISO date string"
|
error: "timeStart must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => getSevenDaysAgo().toISOString())
|
.prefault(() => getSevenDaysAgo().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
@@ -34,7 +33,6 @@ const queryAccessAuditLogsQuery = z.object({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ export const queryAccessAuditLogsQuery = z.strictObject({
|
|||||||
error: "timeEnd must be a valid ISO date string"
|
error: "timeEnd must be a valid ISO date string"
|
||||||
})
|
})
|
||||||
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
.transform((val) => Math.floor(new Date(val).getTime() / 1000))
|
||||||
.optional()
|
|
||||||
.prefault(() => new Date().toISOString())
|
.prefault(() => new Date().toISOString())
|
||||||
.openapi({
|
.openapi({
|
||||||
type: "string",
|
type: "string",
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
import type {
|
import type {
|
||||||
GenerateRegistrationOptionsOpts,
|
GenerateRegistrationOptionsOpts,
|
||||||
GenerateAuthenticationOptionsOpts,
|
GenerateAuthenticationOptionsOpts,
|
||||||
AuthenticatorTransportFuture
|
AuthenticatorTransport
|
||||||
} from "@simplewebauthn/server";
|
} from "@simplewebauthn/server";
|
||||||
import { isoBase64URL } from "@simplewebauthn/server/helpers";
|
import { isoBase64URL } from "@simplewebauthn/server/helpers";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
@@ -221,7 +221,7 @@ export async function startRegistration(
|
|||||||
const excludeCredentials = existingSecurityKeys.map((key) => ({
|
const excludeCredentials = existingSecurityKeys.map((key) => ({
|
||||||
id: key.credentialId,
|
id: key.credentialId,
|
||||||
transports: key.transports
|
transports: key.transports
|
||||||
? (JSON.parse(key.transports) as AuthenticatorTransportFuture[])
|
? (JSON.parse(key.transports) as AuthenticatorTransport[])
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -571,7 +571,7 @@ export async function startAuthentication(
|
|||||||
transports: key.transports
|
transports: key.transports
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
key.transports
|
key.transports
|
||||||
) as AuthenticatorTransportFuture[])
|
) as AuthenticatorTransport[])
|
||||||
: undefined
|
: undefined
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -702,7 +702,7 @@ export async function verifyAuthentication(
|
|||||||
transports: securityKey.transports
|
transports: securityKey.transports
|
||||||
? (JSON.parse(
|
? (JSON.parse(
|
||||||
securityKey.transports
|
securityKey.transports
|
||||||
) as AuthenticatorTransportFuture[])
|
) as AuthenticatorTransport[])
|
||||||
: undefined
|
: undefined
|
||||||
},
|
},
|
||||||
requireUserVerification: false
|
requireUserVerification: false
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const handleHealthcheckStatusMessage: MessageHandler = async (
|
|||||||
const { message, client: c } = context;
|
const { message, client: c } = context;
|
||||||
const newt = c as Newt;
|
const newt = c as Newt;
|
||||||
|
|
||||||
logger.info("Handling healthcheck status message");
|
logger.debug("Handling healthcheck status message");
|
||||||
|
|
||||||
if (!newt) {
|
if (!newt) {
|
||||||
logger.warn("Newt not found");
|
logger.warn("Newt not found");
|
||||||
|
|||||||
@@ -252,9 +252,10 @@ export default function GenerateLicenseKeyForm({
|
|||||||
try {
|
try {
|
||||||
// Check if this is a business/enterprise license request
|
// Check if this is a business/enterprise license request
|
||||||
if (payload.useCaseType === "business") {
|
if (payload.useCaseType === "business") {
|
||||||
const response = await api.put<
|
const response = await api.put<AxiosResponse<string>>(
|
||||||
AxiosResponse<string>
|
`/org/${orgId}/license/enterprise`,
|
||||||
>(`/org/${orgId}/license/enterprise`, { ...payload, tier: "big_license" } );
|
{ ...payload, tier: "tier2" }
|
||||||
|
);
|
||||||
|
|
||||||
console.log("Checkout session response:", response.data);
|
console.log("Checkout session response:", response.data);
|
||||||
const checkoutUrl = response.data.data;
|
const checkoutUrl = response.data.data;
|
||||||
|
|||||||
@@ -201,9 +201,16 @@ export default function GenerateLicenseKeysTable({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tier = row.original.tier;
|
const tier = row.original.tier;
|
||||||
return tier === "enterprise"
|
switch (tier) {
|
||||||
? t("licenseTierEnterprise")
|
case "enterprise":
|
||||||
: t("licenseTierPersonal");
|
return t("licenseTierEnterprise");
|
||||||
|
case "tier1":
|
||||||
|
return t("licenseTierTier1");
|
||||||
|
case "tier2":
|
||||||
|
return t("licenseTierTier2");
|
||||||
|
default:
|
||||||
|
return t("licenseTierPersonal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -100,9 +100,16 @@ export function LicenseKeysDataTable({
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const tier = row.original.tier;
|
const tier = row.original.tier;
|
||||||
return tier === "enterprise"
|
switch (tier) {
|
||||||
? t("licenseTierEnterprise")
|
case "enterprise":
|
||||||
: t("licenseTierPersonal");
|
return t("licenseTierEnterprise");
|
||||||
|
case "tier1":
|
||||||
|
return t("licenseTierTier1");
|
||||||
|
case "tier2":
|
||||||
|
return t("licenseTierTier2");
|
||||||
|
default:
|
||||||
|
return t("licenseTierPersonal");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ import { InfoIcon } from "lucide-react";
|
|||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
|
|
||||||
const TIER_TO_LICENSE_ID = {
|
const TIER_TO_LICENSE_ID = {
|
||||||
starter: "small_license",
|
starter: "tier1",
|
||||||
scale: "big_license"
|
scale: "tier2"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
type FormProps = {
|
type FormProps = {
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ function Calendar({
|
|||||||
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
: "[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5",
|
||||||
defaultClassNames.caption_label
|
defaultClassNames.caption_label
|
||||||
),
|
),
|
||||||
table: "w-full border-collapse",
|
month_grid: "w-full border-collapse",
|
||||||
weekdays: cn("flex", defaultClassNames.weekdays),
|
weekdays: cn("flex", defaultClassNames.weekdays),
|
||||||
weekday: cn(
|
weekday: cn(
|
||||||
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
"text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal",
|
||||||
|
|||||||
Reference in New Issue
Block a user