mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-21 01:56:38 +00:00
Compare commits
81 Commits
private-si
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1729033cf | ||
|
|
7311766512 | ||
|
|
17105f3a51 | ||
|
|
edcfbd26e4 | ||
|
|
0c4d9ea164 | ||
|
|
a5a5224f5c | ||
|
|
8773f7c0a7 | ||
|
|
f385bc2d22 | ||
|
|
a8c9d2e7e6 | ||
|
|
db3f90318b | ||
|
|
2d4d0df5ca | ||
|
|
569ebc671d | ||
|
|
8c8e4e6233 | ||
|
|
c7901ef74b | ||
|
|
be3bd72c1b | ||
|
|
73d1f9288d | ||
|
|
fb7e9f6898 | ||
|
|
38e4b3077f | ||
|
|
312cdc563b | ||
|
|
48ff6dd705 | ||
|
|
695e831090 | ||
|
|
046b431bb8 | ||
|
|
ce2704fc1a | ||
|
|
7e89b36188 | ||
|
|
222dd6bba3 | ||
|
|
ca9ab65228 | ||
|
|
ee4e8f7029 | ||
|
|
f86a1eb32b | ||
|
|
ffd648ed74 | ||
|
|
b2b72169fd | ||
|
|
76746fb6e1 | ||
|
|
6258787c73 | ||
|
|
720080e487 | ||
|
|
46ad1317e4 | ||
|
|
cd28720e46 | ||
|
|
38af02ad3c | ||
|
|
5eed547f91 | ||
|
|
d363ee02ed | ||
|
|
594ee31f43 | ||
|
|
56e25d01ae | ||
|
|
d9766b0f99 | ||
|
|
eeaa1d56ad | ||
|
|
e7f5bc585c | ||
|
|
4f26fb7750 | ||
|
|
cdbc190bfc | ||
|
|
1b1f9ab4cf | ||
|
|
2efe6cfdb3 | ||
|
|
517c607ecf | ||
|
|
802e8f7a22 | ||
|
|
c7cfe2efcb | ||
|
|
ae1f36f39a | ||
|
|
a479ef28ac | ||
|
|
ce2cf50b5a | ||
|
|
f48d01acde | ||
|
|
991fed93ee | ||
|
|
26ab63d0e4 | ||
|
|
4843268537 | ||
|
|
f60ae13e4e | ||
|
|
e72697f8b8 | ||
|
|
0c3dc1ad14 | ||
|
|
840fe86f78 | ||
|
|
e079927a5b | ||
|
|
63379964fa | ||
|
|
0cfaf6ed7f | ||
|
|
043ee9e9d2 | ||
|
|
b63e3e5888 | ||
|
|
4f82470506 | ||
|
|
40e21b6f28 | ||
|
|
67fab1928d | ||
|
|
eb98374566 | ||
|
|
6c83e78256 | ||
|
|
0908f0f057 | ||
|
|
2785449c7a | ||
|
|
d2419ba572 | ||
|
|
aed86ce4ba | ||
|
|
10349932f4 | ||
|
|
2e2684c695 | ||
|
|
7e2fd8f49d | ||
|
|
a060c8029f | ||
|
|
aca9d1e070 | ||
|
|
5c4de03588 |
14
.github/workflows/cicd.yml
vendored
14
.github/workflows/cicd.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -204,7 +204,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -264,7 +264,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Install Go
|
- name: Install Go
|
||||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
|
||||||
with:
|
with:
|
||||||
go-version: 1.24
|
go-version: 1.24
|
||||||
|
|
||||||
@@ -299,7 +299,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Upload artifacts from /install/bin
|
- name: Upload artifacts from /install/bin
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: install-bin
|
name: install-bin
|
||||||
path: install/bin/
|
path: install/bin/
|
||||||
@@ -407,7 +407,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry (for cosign)
|
- name: Login to GitHub Container Registry (for cosign)
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -415,7 +415,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
# cosign is used to sign and verify container images (key and keyless)
|
# cosign is used to sign and verify container images (key and keyless)
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
|
||||||
|
|
||||||
- name: Dual-sign and verify (GHCR & Docker Hub)
|
- name: Dual-sign and verify (GHCR & Docker Hub)
|
||||||
# Sign each image by digest using keyless (OIDC) and key-based signing,
|
# Sign each image by digest using keyless (OIDC) and key-based signing,
|
||||||
|
|||||||
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/mirror.yaml
vendored
2
.github/workflows/mirror.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
skopeo --version
|
skopeo --version
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
uses: sigstore/cosign-installer@ba7bc0a3fef59531c69a25acd34668d6d3fe6f22 # v4.1.0
|
||||||
|
|
||||||
- name: Input check
|
- name: Input check
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
|
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -43,7 +43,7 @@
|
|||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<strong>
|
<strong>
|
||||||
Start testing Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
||||||
</strong>
|
</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ Pangolin is an open-source, identity-based remote access platform built on WireG
|
|||||||
|
|
||||||
| <img width=500 /> | Description |
|
| <img width=500 /> | Description |
|
||||||
|-----------------|--------------|
|
|-----------------|--------------|
|
||||||
|
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing — no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/understanding-nodes) and connect to our control plane. |
|
||||||
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
|
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
|
||||||
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |
|
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |
|
||||||
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing — no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/nodes) and connect to our control plane. |
|
|
||||||
|
|
||||||
## Key Features
|
## Key Features
|
||||||
|
|
||||||
@@ -85,17 +85,16 @@ Download the Pangolin client for your platform:
|
|||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
|
### Sign up now
|
||||||
|
|
||||||
|
Create an account at [app.pangolin.net](https://app.pangolin.net) to get started with Pangolin Cloud. A generous free tier is available.
|
||||||
|
|
||||||
### Check out the docs
|
### Check out the docs
|
||||||
|
|
||||||
We encourage everyone to read the full documentation first, which is
|
We encourage everyone to read the full documentation first, which is
|
||||||
available at [docs.pangolin.net](https://docs.pangolin.net). This README provides only a very brief subset of
|
available at [docs.pangolin.net](https://docs.pangolin.net). This README provides only a very brief subset of
|
||||||
the docs to illustrate some basic ideas.
|
the docs to illustrate some basic ideas.
|
||||||
|
|
||||||
### Sign up and try now
|
|
||||||
|
|
||||||
For Pangolin's managed service, you will first need to create an account at
|
|
||||||
[app.pangolin.net](https://app.pangolin.net). We have a generous free tier to get started.
|
|
||||||
|
|
||||||
## Licensing
|
## Licensing
|
||||||
|
|
||||||
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl.html). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
|
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl.html). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
module installer
|
module installer
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/huh v0.8.0
|
github.com/charmbracelet/huh v0.8.0
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
golang.org/x/term v0.40.0
|
golang.org/x/term v0.41.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.41.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
||||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
||||||
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=
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Прокси заявки чрез HTTPS, използвайки напълно квалифицирано име на домейн.",
|
"resourceHTTPDescription": "Прокси заявки чрез HTTPS, използвайки напълно квалифицирано име на домейн.",
|
||||||
"resourceRaw": "Суров TCP/UDP ресурс",
|
"resourceRaw": "Суров TCP/UDP ресурс",
|
||||||
"resourceRawDescription": "Прокси заявки чрез сурови TCP/UDP, използвайки порт номер.",
|
"resourceRawDescription": "Прокси заявки чрез сурови TCP/UDP, използвайки порт номер.",
|
||||||
"resourceRawDescriptionCloud": "Прокси заявките през суров TCP/UDP, използвайки номер на порт. ИЗИСКВА ИЗПОЛЗВАНЕ НА ОТДАЛЕЧЕН УЗЕЛ.",
|
"resourceRawDescriptionCloud": "Получавайте заявки чрез суров TCP/UDP с използване на портен номер. Изисква се сайтовете да се свързват към отдалечен възел.",
|
||||||
"resourceCreate": "Създайте ресурс",
|
"resourceCreate": "Създайте ресурс",
|
||||||
"resourceCreateDescription": "Следвайте стъпките по-долу, за да създадете нов ресурс",
|
"resourceCreateDescription": "Следвайте стъпките по-долу, за да създадете нов ресурс",
|
||||||
"resourceSeeAll": "Вижте всички ресурси",
|
"resourceSeeAll": "Вижте всички ресурси",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Име на пространство: {namespace}",
|
"domainPickerNamespace": "Име на пространство: {namespace}",
|
||||||
"domainPickerShowMore": "Покажи повече",
|
"domainPickerShowMore": "Покажи повече",
|
||||||
"regionSelectorTitle": "Избор на регион",
|
"regionSelectorTitle": "Избор на регион",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Предоставените домейни не се поддържат, когато сайтовете се свързват към отдалечени крайни възли. За да бъдат ресурсите налични на отдалечени възли, използвайте персонализиран домейн вместо това.",
|
||||||
"regionSelectorInfo": "Изборът на регион ни помага да предоставим по-добра производителност за вашето местоположение. Не е необходимо да сте в същия регион като сървъра.",
|
"regionSelectorInfo": "Изборът на регион ни помага да предоставим по-добра производителност за вашето местоположение. Не е необходимо да сте в същия регион като сървъра.",
|
||||||
"regionSelectorPlaceholder": "Изберете регион",
|
"regionSelectorPlaceholder": "Изберете регион",
|
||||||
"regionSelectorComingSoon": "Очаква се скоро",
|
"regionSelectorComingSoon": "Очаква се скоро",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Край на следващата година",
|
"logRetentionEndOfFollowingYear": "Край на следващата година",
|
||||||
"actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация",
|
"actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация",
|
||||||
"accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация",
|
"accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация",
|
||||||
"licenseRequiredToUse": "Изисква се лиценз за <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink>, за да използвате тази функция. Тази функция е също достъпна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Изисква се лиценз за <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> или <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> за използване на тази функция. <bookADemoLink>Резервирайте демонстрация или пробен POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "Необходимо е <enterpriseEditionLink>изданието Enterprise</enterpriseEditionLink>, за да използвате тази функция. Тази функция е също достъпна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> е необходим за използване на тази функция. Тази функция също е налична в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Резервирайте демонстрация или пробен POC</bookADemoLink>.",
|
||||||
"certResolver": "Решавач на сертификати",
|
"certResolver": "Решавач на сертификати",
|
||||||
"certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.",
|
"certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.",
|
||||||
"selectCertResolver": "Изберете решавач на сертификати",
|
"selectCertResolver": "Изберете решавач на сертификати",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Активирайте одобрения на устройства",
|
"approvalsEmptyStateStep2Title": "Активирайте одобрения на устройства",
|
||||||
"approvalsEmptyStateStep2Description": "Редактирайте ролята и активирайте опцията 'Изискване на одобрения за устройства'. Потребители с тази роля ще трябва администраторско одобрение за нови устройства.",
|
"approvalsEmptyStateStep2Description": "Редактирайте ролята и активирайте опцията 'Изискване на одобрения за устройства'. Потребители с тази роля ще трябва администраторско одобрение за нови устройства.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Преглед: Когато е активирано, чакащите заявки за устройства ще се появят тук за преглед",
|
"approvalsEmptyStatePreviewDescription": "Преглед: Когато е активирано, чакащите заявки за устройства ще се появят тук за преглед",
|
||||||
"approvalsEmptyStateButtonText": "Управлявайте роли"
|
"approvalsEmptyStateButtonText": "Управлявайте роли",
|
||||||
|
"domainErrorTitle": "Имаме проблем с проверката на вашия домейн"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy požadavky přes HTTPS pomocí plně kvalifikovaného názvu domény.",
|
"resourceHTTPDescription": "Proxy požadavky přes HTTPS pomocí plně kvalifikovaného názvu domény.",
|
||||||
"resourceRaw": "Surový TCP/UDP zdroj",
|
"resourceRaw": "Surový TCP/UDP zdroj",
|
||||||
"resourceRawDescription": "Proxy požadavky přes nezpracovaný TCP/UDP pomocí čísla portu.",
|
"resourceRawDescription": "Proxy požadavky přes nezpracovaný TCP/UDP pomocí čísla portu.",
|
||||||
"resourceRawDescriptionCloud": "Požadavky na proxy přes syrové TCP/UDP pomocí portového čísla. ŽÁDOSTI POUŽÍVAT POUŽITÍ Z REMOTE NODE.",
|
"resourceRawDescriptionCloud": "Proxy požadavky na syrové TCP/UDP pomocí čísla portu. Vyžaduje připojení stránek ke vzdálenému uzlu.",
|
||||||
"resourceCreate": "Vytvořit zdroj",
|
"resourceCreate": "Vytvořit zdroj",
|
||||||
"resourceCreateDescription": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili nový zdroj",
|
"resourceCreateDescription": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili nový zdroj",
|
||||||
"resourceSeeAll": "Zobrazit všechny zdroje",
|
"resourceSeeAll": "Zobrazit všechny zdroje",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Jmenný prostor: {namespace}",
|
"domainPickerNamespace": "Jmenný prostor: {namespace}",
|
||||||
"domainPickerShowMore": "Zobrazit více",
|
"domainPickerShowMore": "Zobrazit více",
|
||||||
"regionSelectorTitle": "Vybrat region",
|
"regionSelectorTitle": "Vybrat region",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Poskytnuté domény nejsou podporovány, když se stránky připojují k vzdáleným výstupním uzlům. Pro dostupné zdroje na vzdálených uzlech použijte vlastní doménu.",
|
||||||
"regionSelectorInfo": "Výběr regionu nám pomáhá poskytovat lepší výkon pro vaši polohu. Nemusíte být ve stejném regionu jako váš server.",
|
"regionSelectorInfo": "Výběr regionu nám pomáhá poskytovat lepší výkon pro vaši polohu. Nemusíte být ve stejném regionu jako váš server.",
|
||||||
"regionSelectorPlaceholder": "Vyberte region",
|
"regionSelectorPlaceholder": "Vyberte region",
|
||||||
"regionSelectorComingSoon": "Již brzy",
|
"regionSelectorComingSoon": "Již brzy",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Konec následujícího roku",
|
"logRetentionEndOfFollowingYear": "Konec následujícího roku",
|
||||||
"actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci",
|
"actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci",
|
||||||
"accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci",
|
"accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci",
|
||||||
"licenseRequiredToUse": "Pro použití této funkce je vyžadována licence <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Tato funkce je také dostupná v <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Pro použití této funkce je vyžadována licence <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> nebo <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> . <bookADemoLink>Zarezervujte si demo nebo POC zkušební verzi</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> je vyžadována pro použití této funkce. Tato funkce je také k dispozici v <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> je vyžadována pro použití této funkce. Tato funkce je také k dispozici v <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Rezervujte si demo nebo POC zkušební verzi</bookADemoLink>.",
|
||||||
"certResolver": "Oddělovač certifikátů",
|
"certResolver": "Oddělovač certifikátů",
|
||||||
"certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.",
|
"certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.",
|
||||||
"selectCertResolver": "Vyberte řešič certifikátů",
|
"selectCertResolver": "Vyberte řešič certifikátů",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Povolit schválení zařízení",
|
"approvalsEmptyStateStep2Title": "Povolit schválení zařízení",
|
||||||
"approvalsEmptyStateStep2Description": "Upravte roli a povolte možnost 'Vyžadovat schválení zařízení'. Uživatelé s touto rolí budou potřebovat schválení pro nová zařízení správce.",
|
"approvalsEmptyStateStep2Description": "Upravte roli a povolte možnost 'Vyžadovat schválení zařízení'. Uživatelé s touto rolí budou potřebovat schválení pro nová zařízení správce.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Náhled: Pokud je povoleno, čekající na zařízení se zde zobrazí žádosti o recenzi",
|
"approvalsEmptyStatePreviewDescription": "Náhled: Pokud je povoleno, čekající na zařízení se zde zobrazí žádosti o recenzi",
|
||||||
"approvalsEmptyStateButtonText": "Spravovat role"
|
"approvalsEmptyStateButtonText": "Spravovat role",
|
||||||
|
"domainErrorTitle": "Máme problém s ověřením tvé domény"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy-Anfragen über HTTPS mit einem voll qualifizierten Domain-Namen.",
|
"resourceHTTPDescription": "Proxy-Anfragen über HTTPS mit einem voll qualifizierten Domain-Namen.",
|
||||||
"resourceRaw": "Direkte TCP/UDP Ressource (raw)",
|
"resourceRaw": "Direkte TCP/UDP Ressource (raw)",
|
||||||
"resourceRawDescription": "Proxy-Anfragen über rohes TCP/UDP mit einer Portnummer.",
|
"resourceRawDescription": "Proxy-Anfragen über rohes TCP/UDP mit einer Portnummer.",
|
||||||
"resourceRawDescriptionCloud": "Proxy-Anfragen über rohe TCP/UDP mit einer Portnummer. Erfordert die NUTZUNG eines REMOTE Knotens.",
|
"resourceRawDescriptionCloud": "Proxy-Anfragen über rohe TCP/UDP mit Portnummer. Benötigt Sites, um sich mit einem entfernten Knoten zu verbinden.",
|
||||||
"resourceCreate": "Ressource erstellen",
|
"resourceCreate": "Ressource erstellen",
|
||||||
"resourceCreateDescription": "Folgen Sie den Schritten unten, um eine neue Ressource zu erstellen",
|
"resourceCreateDescription": "Folgen Sie den Schritten unten, um eine neue Ressource zu erstellen",
|
||||||
"resourceSeeAll": "Alle Ressourcen anzeigen",
|
"resourceSeeAll": "Alle Ressourcen anzeigen",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Namespace: {namespace}",
|
"domainPickerNamespace": "Namespace: {namespace}",
|
||||||
"domainPickerShowMore": "Mehr anzeigen",
|
"domainPickerShowMore": "Mehr anzeigen",
|
||||||
"regionSelectorTitle": "Region auswählen",
|
"regionSelectorTitle": "Region auswählen",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Angegebene Domains werden nicht unterstützt, wenn sich Websites mit externen Exit-Knoten verbinden. Damit Ressourcen auf entfernten Knoten verfügbar sind, verwenden Sie stattdessen eine eigene Domain.",
|
||||||
"regionSelectorInfo": "Das Auswählen einer Region hilft uns, eine bessere Leistung für Ihren Standort bereitzustellen. Sie müssen sich nicht in derselben Region wie Ihr Server befinden.",
|
"regionSelectorInfo": "Das Auswählen einer Region hilft uns, eine bessere Leistung für Ihren Standort bereitzustellen. Sie müssen sich nicht in derselben Region wie Ihr Server befinden.",
|
||||||
"regionSelectorPlaceholder": "Wähle eine Region",
|
"regionSelectorPlaceholder": "Wähle eine Region",
|
||||||
"regionSelectorComingSoon": "Kommt bald",
|
"regionSelectorComingSoon": "Kommt bald",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Ende des folgenden Jahres",
|
"logRetentionEndOfFollowingYear": "Ende des folgenden Jahres",
|
||||||
"actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen",
|
"actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen",
|
||||||
"accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen",
|
"accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen",
|
||||||
"licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> Lizenz erforderlich. Diese Funktion ist auch in der <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> verfügbar.",
|
"licenseRequiredToUse": "Eine <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> Lizenz oder <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> wird benötigt, um diese Funktion nutzen zu können. <bookADemoLink>Buchen Sie eine Demo oder POC Testversion</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "Um diese Funktion nutzen zu können, ist die <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> erforderlich. Diese Funktion ist auch in der <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> verfügbar.",
|
"ossEnterpriseEditionRequired": "Die <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> wird benötigt, um diese Funktion nutzen zu können. Diese Funktion ist auch in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>verfügbar. <bookADemoLink>Buchen Sie eine Demo oder POC Testversion</bookADemoLink>.",
|
||||||
"certResolver": "Zertifikatsauflöser",
|
"certResolver": "Zertifikatsauflöser",
|
||||||
"certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.",
|
"certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.",
|
||||||
"selectCertResolver": "Zertifikatsauflöser auswählen",
|
"selectCertResolver": "Zertifikatsauflöser auswählen",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Gerätegenehmigungen aktivieren",
|
"approvalsEmptyStateStep2Title": "Gerätegenehmigungen aktivieren",
|
||||||
"approvalsEmptyStateStep2Description": "Bearbeite eine Rolle und aktiviere die Option 'Gerätegenehmigung erforderlich'. Benutzer mit dieser Rolle benötigen Administrator-Genehmigung für neue Geräte.",
|
"approvalsEmptyStateStep2Description": "Bearbeite eine Rolle und aktiviere die Option 'Gerätegenehmigung erforderlich'. Benutzer mit dieser Rolle benötigen Administrator-Genehmigung für neue Geräte.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Vorschau: Wenn aktiviert, werden ausstehende Geräteanfragen hier zur Überprüfung angezeigt",
|
"approvalsEmptyStatePreviewDescription": "Vorschau: Wenn aktiviert, werden ausstehende Geräteanfragen hier zur Überprüfung angezeigt",
|
||||||
"approvalsEmptyStateButtonText": "Rollen verwalten"
|
"approvalsEmptyStateButtonText": "Rollen verwalten",
|
||||||
|
"domainErrorTitle": "Wir haben Probleme mit der Überprüfung deiner Domain"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy requests over HTTPS using a fully qualified domain name.",
|
"resourceHTTPDescription": "Proxy requests over HTTPS using a fully qualified domain name.",
|
||||||
"resourceRaw": "Raw TCP/UDP Resource",
|
"resourceRaw": "Raw TCP/UDP Resource",
|
||||||
"resourceRawDescription": "Proxy requests over raw TCP/UDP using a port number.",
|
"resourceRawDescription": "Proxy requests over raw TCP/UDP using a port number.",
|
||||||
"resourceRawDescriptionCloud": "Proxy requests over raw TCP/UDP using a port number. REQUIRES THE USE OF A REMOTE NODE.",
|
"resourceRawDescriptionCloud": "Proxy requests over raw TCP/UDP using a port number. Requires sites to connect to a remote node.",
|
||||||
"resourceCreate": "Create Resource",
|
"resourceCreate": "Create Resource",
|
||||||
"resourceCreateDescription": "Follow the steps below to create a new resource",
|
"resourceCreateDescription": "Follow the steps below to create a new resource",
|
||||||
"resourceSeeAll": "See All Resources",
|
"resourceSeeAll": "See All Resources",
|
||||||
@@ -1120,6 +1120,7 @@
|
|||||||
"setupTokenDescription": "Enter the setup token from the server console.",
|
"setupTokenDescription": "Enter the setup token from the server console.",
|
||||||
"setupTokenRequired": "Setup token is required",
|
"setupTokenRequired": "Setup token is required",
|
||||||
"actionUpdateSite": "Update Site",
|
"actionUpdateSite": "Update Site",
|
||||||
|
"actionResetSiteBandwidth": "Reset Organization Bandwidth",
|
||||||
"actionListSiteRoles": "List Allowed Site Roles",
|
"actionListSiteRoles": "List Allowed Site Roles",
|
||||||
"actionCreateResource": "Create Resource",
|
"actionCreateResource": "Create Resource",
|
||||||
"actionDeleteResource": "Delete Resource",
|
"actionDeleteResource": "Delete Resource",
|
||||||
@@ -1427,6 +1428,7 @@
|
|||||||
"domainPickerNamespace": "Namespace: {namespace}",
|
"domainPickerNamespace": "Namespace: {namespace}",
|
||||||
"domainPickerShowMore": "Show More",
|
"domainPickerShowMore": "Show More",
|
||||||
"regionSelectorTitle": "Select Region",
|
"regionSelectorTitle": "Select Region",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Provided domains are not supported when sites connect to remote exit nodes. For resources to be available on remote nodes, use a custom domain instead.",
|
||||||
"regionSelectorInfo": "Selecting a region helps us provide better performance for your location. You do not have to be in the same region as your server.",
|
"regionSelectorInfo": "Selecting a region helps us provide better performance for your location. You do not have to be in the same region as your server.",
|
||||||
"regionSelectorPlaceholder": "Choose a region",
|
"regionSelectorPlaceholder": "Choose a region",
|
||||||
"regionSelectorComingSoon": "Coming Soon",
|
"regionSelectorComingSoon": "Coming Soon",
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy proporciona solicitudes sobre HTTPS usando un nombre de dominio completamente calificado.",
|
"resourceHTTPDescription": "Proxy proporciona solicitudes sobre HTTPS usando un nombre de dominio completamente calificado.",
|
||||||
"resourceRaw": "Recurso TCP/UDP sin procesar",
|
"resourceRaw": "Recurso TCP/UDP sin procesar",
|
||||||
"resourceRawDescription": "Proxy proporciona solicitudes sobre TCP/UDP usando un número de puerto.",
|
"resourceRawDescription": "Proxy proporciona solicitudes sobre TCP/UDP usando un número de puerto.",
|
||||||
"resourceRawDescriptionCloud": "Las peticiones de proxy sobre TCP/UDP crudas usando un número de puerto. REQUIERE EL USO DE UN NODO REMOTE.",
|
"resourceRawDescriptionCloud": "Las peticiones de proxy sobre TCP/UDP crudas usando un número de puerto. Requiere que los sitios se conecten a un nodo remoto.",
|
||||||
"resourceCreate": "Crear Recurso",
|
"resourceCreate": "Crear Recurso",
|
||||||
"resourceCreateDescription": "Siga los siguientes pasos para crear un nuevo recurso",
|
"resourceCreateDescription": "Siga los siguientes pasos para crear un nuevo recurso",
|
||||||
"resourceSeeAll": "Ver todos los recursos",
|
"resourceSeeAll": "Ver todos los recursos",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Espacio de nombres: {namespace}",
|
"domainPickerNamespace": "Espacio de nombres: {namespace}",
|
||||||
"domainPickerShowMore": "Mostrar más",
|
"domainPickerShowMore": "Mostrar más",
|
||||||
"regionSelectorTitle": "Seleccionar Región",
|
"regionSelectorTitle": "Seleccionar Región",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Los dominios suministrados no son compatibles cuando los sitios se conectan a nodos de salida remotos. Para que los recursos estén disponibles en nodos remotos, utilice un dominio personalizado en su lugar.",
|
||||||
"regionSelectorInfo": "Seleccionar una región nos ayuda a brindar un mejor rendimiento para tu ubicación. No tienes que estar en la misma región que tu servidor.",
|
"regionSelectorInfo": "Seleccionar una región nos ayuda a brindar un mejor rendimiento para tu ubicación. No tienes que estar en la misma región que tu servidor.",
|
||||||
"regionSelectorPlaceholder": "Elige una región",
|
"regionSelectorPlaceholder": "Elige una región",
|
||||||
"regionSelectorComingSoon": "Próximamente",
|
"regionSelectorComingSoon": "Próximamente",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Fin del año siguiente",
|
"logRetentionEndOfFollowingYear": "Fin del año siguiente",
|
||||||
"actionLogsDescription": "Ver un historial de acciones realizadas en esta organización",
|
"actionLogsDescription": "Ver un historial de acciones realizadas en esta organización",
|
||||||
"accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización",
|
"accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización",
|
||||||
"licenseRequiredToUse": "Se requiere una licencia <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> para utilizar esta función. Esta característica también está disponible en <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Se requiere una licencia <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> o <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> para usar esta función. <bookADemoLink>Reserve una demostración o prueba POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "La <enterpriseEditionLink>versión Enterprise</enterpriseEditionLink> es necesaria para utilizar esta función. Esta función también está disponible en <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "La <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> es necesaria para utilizar esta función. Esta función también está disponible en <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Reserva una demostración o prueba POC</bookADemoLink>.",
|
||||||
"certResolver": "Resolver certificado",
|
"certResolver": "Resolver certificado",
|
||||||
"certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.",
|
"certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.",
|
||||||
"selectCertResolver": "Seleccionar Resolver Certificado",
|
"selectCertResolver": "Seleccionar Resolver Certificado",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Habilitar aprobaciones de dispositivo",
|
"approvalsEmptyStateStep2Title": "Habilitar aprobaciones de dispositivo",
|
||||||
"approvalsEmptyStateStep2Description": "Editar un rol y habilitar la opción 'Requerir aprobaciones de dispositivos'. Los usuarios con este rol necesitarán la aprobación del administrador para nuevos dispositivos.",
|
"approvalsEmptyStateStep2Description": "Editar un rol y habilitar la opción 'Requerir aprobaciones de dispositivos'. Los usuarios con este rol necesitarán la aprobación del administrador para nuevos dispositivos.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Vista previa: Cuando está habilitado, las solicitudes de dispositivo pendientes aparecerán aquí para su revisión",
|
"approvalsEmptyStatePreviewDescription": "Vista previa: Cuando está habilitado, las solicitudes de dispositivo pendientes aparecerán aquí para su revisión",
|
||||||
"approvalsEmptyStateButtonText": "Administrar roles"
|
"approvalsEmptyStateButtonText": "Administrar roles",
|
||||||
|
"domainErrorTitle": "Estamos teniendo problemas para verificar su dominio"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy les demandes sur HTTPS en utilisant un nom de domaine entièrement qualifié.",
|
"resourceHTTPDescription": "Proxy les demandes sur HTTPS en utilisant un nom de domaine entièrement qualifié.",
|
||||||
"resourceRaw": "Ressource TCP/UDP brute",
|
"resourceRaw": "Ressource TCP/UDP brute",
|
||||||
"resourceRawDescription": "Proxy les demandes sur TCP/UDP brut en utilisant un numéro de port.",
|
"resourceRawDescription": "Proxy les demandes sur TCP/UDP brut en utilisant un numéro de port.",
|
||||||
"resourceRawDescriptionCloud": "Requêtes de proxy sur TCP/UDP brute en utilisant un numéro de port. REQUISE L'UTILISATION D'UN Nœud DE REMOTE.",
|
"resourceRawDescriptionCloud": "Requêtes de proxy sur TCP/UDP brute en utilisant un numéro de port. Nécessite des sites pour se connecter à un noeud distant.",
|
||||||
"resourceCreate": "Créer une ressource",
|
"resourceCreate": "Créer une ressource",
|
||||||
"resourceCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle ressource",
|
"resourceCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle ressource",
|
||||||
"resourceSeeAll": "Voir toutes les ressources",
|
"resourceSeeAll": "Voir toutes les ressources",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Espace de noms : {namespace}",
|
"domainPickerNamespace": "Espace de noms : {namespace}",
|
||||||
"domainPickerShowMore": "Afficher plus",
|
"domainPickerShowMore": "Afficher plus",
|
||||||
"regionSelectorTitle": "Sélectionner Région",
|
"regionSelectorTitle": "Sélectionner Région",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Les domaines fournis ne sont pas pris en charge lorsque les sites se connectent à des nœuds de sortie distants. Pour que les ressources soient disponibles sur des nœuds distants, utilisez un domaine personnalisé à la place.",
|
||||||
"regionSelectorInfo": "Sélectionner une région nous aide à offrir de meilleures performances pour votre localisation. Vous n'avez pas besoin d'être dans la même région que votre serveur.",
|
"regionSelectorInfo": "Sélectionner une région nous aide à offrir de meilleures performances pour votre localisation. Vous n'avez pas besoin d'être dans la même région que votre serveur.",
|
||||||
"regionSelectorPlaceholder": "Choisissez une région",
|
"regionSelectorPlaceholder": "Choisissez une région",
|
||||||
"regionSelectorComingSoon": "Bientôt disponible",
|
"regionSelectorComingSoon": "Bientôt disponible",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Fin de l'année suivante",
|
"logRetentionEndOfFollowingYear": "Fin de l'année suivante",
|
||||||
"actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation",
|
"actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation",
|
||||||
"accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation",
|
"accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation",
|
||||||
"licenseRequiredToUse": "Une licence <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> est nécessaire pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Une <enterpriseLicenseLink>licence Enterprise Edition</enterpriseLicenseLink> ou <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> est requise pour utiliser cette fonctionnalité. <bookADemoLink>Réservez une démonstration ou une évaluation de POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "La version <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> est requise pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "La version <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> est requise pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Réservez une démo ou un essai POC</bookADemoLink>.",
|
||||||
"certResolver": "Résolveur de certificat",
|
"certResolver": "Résolveur de certificat",
|
||||||
"certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.",
|
"certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.",
|
||||||
"selectCertResolver": "Sélectionnez le résolveur de certificat",
|
"selectCertResolver": "Sélectionnez le résolveur de certificat",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Activer les autorisations de l'appareil",
|
"approvalsEmptyStateStep2Title": "Activer les autorisations de l'appareil",
|
||||||
"approvalsEmptyStateStep2Description": "Modifier un rôle et activer l'option 'Exiger les autorisations de l'appareil'. Les utilisateurs avec ce rôle auront besoin de l'approbation de l'administrateur pour les nouveaux appareils.",
|
"approvalsEmptyStateStep2Description": "Modifier un rôle et activer l'option 'Exiger les autorisations de l'appareil'. Les utilisateurs avec ce rôle auront besoin de l'approbation de l'administrateur pour les nouveaux appareils.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Aperçu: Lorsque cette option est activée, les demandes de périphérique en attente apparaîtront ici pour vérification",
|
"approvalsEmptyStatePreviewDescription": "Aperçu: Lorsque cette option est activée, les demandes de périphérique en attente apparaîtront ici pour vérification",
|
||||||
"approvalsEmptyStateButtonText": "Gérer les rôles"
|
"approvalsEmptyStateButtonText": "Gérer les rôles",
|
||||||
|
"domainErrorTitle": "Nous avons des difficultés à vérifier votre domaine"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Richieste proxy su HTTPS usando un nome di dominio completo.",
|
"resourceHTTPDescription": "Richieste proxy su HTTPS usando un nome di dominio completo.",
|
||||||
"resourceRaw": "Risorsa Raw TCP/UDP",
|
"resourceRaw": "Risorsa Raw TCP/UDP",
|
||||||
"resourceRawDescription": "Richieste proxy su TCP/UDP grezzo utilizzando un numero di porta.",
|
"resourceRawDescription": "Richieste proxy su TCP/UDP grezzo utilizzando un numero di porta.",
|
||||||
"resourceRawDescriptionCloud": "Richieste proxy su TCP/UDP grezzo utilizzando un numero di porta. RICHIEDE L'USO DI UN NODO REMOTO.",
|
"resourceRawDescriptionCloud": "Richiesta proxy su TCP/UDP grezzo utilizzando un numero di porta. Richiede siti per connettersi a un nodo remoto.",
|
||||||
"resourceCreate": "Crea Risorsa",
|
"resourceCreate": "Crea Risorsa",
|
||||||
"resourceCreateDescription": "Segui i passaggi seguenti per creare una nuova risorsa",
|
"resourceCreateDescription": "Segui i passaggi seguenti per creare una nuova risorsa",
|
||||||
"resourceSeeAll": "Vedi Tutte Le Risorse",
|
"resourceSeeAll": "Vedi Tutte Le Risorse",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Namespace: {namespace}",
|
"domainPickerNamespace": "Namespace: {namespace}",
|
||||||
"domainPickerShowMore": "Mostra Altro",
|
"domainPickerShowMore": "Mostra Altro",
|
||||||
"regionSelectorTitle": "Seleziona regione",
|
"regionSelectorTitle": "Seleziona regione",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "I domini forniti non sono supportati quando i siti si connettono a nodi di uscita remoti. Affinché le risorse siano disponibili su nodi remoti, utilizza invece un dominio personalizzato.",
|
||||||
"regionSelectorInfo": "Selezionare una regione ci aiuta a fornire migliori performance per la tua posizione. Non devi necessariamente essere nella stessa regione del tuo server.",
|
"regionSelectorInfo": "Selezionare una regione ci aiuta a fornire migliori performance per la tua posizione. Non devi necessariamente essere nella stessa regione del tuo server.",
|
||||||
"regionSelectorPlaceholder": "Scegli una regione",
|
"regionSelectorPlaceholder": "Scegli una regione",
|
||||||
"regionSelectorComingSoon": "Prossimamente",
|
"regionSelectorComingSoon": "Prossimamente",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Fine dell'anno successivo",
|
"logRetentionEndOfFollowingYear": "Fine dell'anno successivo",
|
||||||
"actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione",
|
"actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione",
|
||||||
"accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione",
|
"accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione",
|
||||||
"licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Questa funzionalità è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> o <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> . <bookADemoLink>Prenota una demo o una prova POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "L' <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> è necessaria per utilizzare questa funzione. Questa funzionalità è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "L' <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> è necessaria per utilizzare questa funzione. Questa funzione è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Prenota una demo o una prova POC</bookADemoLink>.",
|
||||||
"certResolver": "Risolutore Di Certificato",
|
"certResolver": "Risolutore Di Certificato",
|
||||||
"certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.",
|
"certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.",
|
||||||
"selectCertResolver": "Seleziona Risolutore Di Certificato",
|
"selectCertResolver": "Seleziona Risolutore Di Certificato",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Abilita Approvazioni Dispositivo",
|
"approvalsEmptyStateStep2Title": "Abilita Approvazioni Dispositivo",
|
||||||
"approvalsEmptyStateStep2Description": "Modifica un ruolo e abilita l'opzione 'Richiedi l'approvazione del dispositivo'. Gli utenti con questo ruolo avranno bisogno dell'approvazione dell'amministratore per i nuovi dispositivi.",
|
"approvalsEmptyStateStep2Description": "Modifica un ruolo e abilita l'opzione 'Richiedi l'approvazione del dispositivo'. Gli utenti con questo ruolo avranno bisogno dell'approvazione dell'amministratore per i nuovi dispositivi.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Anteprima: quando abilitato, le richieste di dispositivo in attesa appariranno qui per la revisione",
|
"approvalsEmptyStatePreviewDescription": "Anteprima: quando abilitato, le richieste di dispositivo in attesa appariranno qui per la revisione",
|
||||||
"approvalsEmptyStateButtonText": "Gestisci Ruoli"
|
"approvalsEmptyStateButtonText": "Gestisci Ruoli",
|
||||||
|
"domainErrorTitle": "Stiamo avendo problemi a verificare il tuo dominio"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "완전한 도메인 이름을 사용해 RAW 또는 HTTPS로 프록시 요청을 수행합니다.",
|
"resourceHTTPDescription": "완전한 도메인 이름을 사용해 RAW 또는 HTTPS로 프록시 요청을 수행합니다.",
|
||||||
"resourceRaw": "원시 TCP/UDP 리소스",
|
"resourceRaw": "원시 TCP/UDP 리소스",
|
||||||
"resourceRawDescription": "포트 번호를 사용하여 RAW TCP/UDP로 요청을 프록시합니다.",
|
"resourceRawDescription": "포트 번호를 사용하여 RAW TCP/UDP로 요청을 프록시합니다.",
|
||||||
"resourceRawDescriptionCloud": "원시 TCP/UDP를 포트 번호를 사용하여 프록시 요청합니다. 원격 노드 사용이 필요합니다.",
|
"resourceRawDescriptionCloud": "포트 번호를 사용하여 원격 노드에 연결해야 합니다. 원격 노드에서 리소스를 사용하려면 사용자 지정 도메인을 사용하십시오.",
|
||||||
"resourceCreate": "리소스 생성",
|
"resourceCreate": "리소스 생성",
|
||||||
"resourceCreateDescription": "아래 단계를 따라 새 리소스를 생성하세요.",
|
"resourceCreateDescription": "아래 단계를 따라 새 리소스를 생성하세요.",
|
||||||
"resourceSeeAll": "모든 리소스 보기",
|
"resourceSeeAll": "모든 리소스 보기",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "이름 공간: {namespace}",
|
"domainPickerNamespace": "이름 공간: {namespace}",
|
||||||
"domainPickerShowMore": "더보기",
|
"domainPickerShowMore": "더보기",
|
||||||
"regionSelectorTitle": "지역 선택",
|
"regionSelectorTitle": "지역 선택",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "제공된 도메인은 원격 종료 노드에 연결된 사이트에서 지원되지 않습니다. 원격 노드에서 리소스를 사용하려면 사용자 지정 도메인을 사용하십시오.",
|
||||||
"regionSelectorInfo": "지역을 선택하면 위치에 따라 더 나은 성능이 제공됩니다. 서버와 같은 지역에 있을 필요는 없습니다.",
|
"regionSelectorInfo": "지역을 선택하면 위치에 따라 더 나은 성능이 제공됩니다. 서버와 같은 지역에 있을 필요는 없습니다.",
|
||||||
"regionSelectorPlaceholder": "지역 선택",
|
"regionSelectorPlaceholder": "지역 선택",
|
||||||
"regionSelectorComingSoon": "곧 출시 예정",
|
"regionSelectorComingSoon": "곧 출시 예정",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "다음 연도 말",
|
"logRetentionEndOfFollowingYear": "다음 연도 말",
|
||||||
"actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다",
|
"actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다",
|
||||||
"accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다",
|
"accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다",
|
||||||
"licenseRequiredToUse": "이 기능을 사용하려면 <enterpriseLicenseLink>엔터프라이즈 에디션</enterpriseLicenseLink> 라이선스가 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다.",
|
"licenseRequiredToUse": "이 기능을 사용하려면 <enterpriseLicenseLink>엔터프라이즈 에디션</enterpriseLicenseLink> 라이선스가 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다. <bookADemoLink>데모 또는 POC 체험을 예약하세요</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "이 기능을 사용하려면 <enterpriseEditionLink>엔터프라이즈 에디션</enterpriseEditionLink>이 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다.",
|
"ossEnterpriseEditionRequired": "이 기능을 사용하려면 <enterpriseEditionLink>엔터프라이즈 에디션</enterpriseEditionLink>이(가) 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다. <bookADemoLink>데모 또는 POC 체험을 예약하세요</bookADemoLink>.",
|
||||||
"certResolver": "인증서 해결사",
|
"certResolver": "인증서 해결사",
|
||||||
"certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.",
|
"certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.",
|
||||||
"selectCertResolver": "인증서 해결사 선택",
|
"selectCertResolver": "인증서 해결사 선택",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "장치 승인 활성화",
|
"approvalsEmptyStateStep2Title": "장치 승인 활성화",
|
||||||
"approvalsEmptyStateStep2Description": "역할을 편집하고 '장치 승인 요구' 옵션을 활성화하세요. 이 역할을 가진 사용자는 새 장치에 대해 관리자의 승인이 필요합니다.",
|
"approvalsEmptyStateStep2Description": "역할을 편집하고 '장치 승인 요구' 옵션을 활성화하세요. 이 역할을 가진 사용자는 새 장치에 대해 관리자의 승인이 필요합니다.",
|
||||||
"approvalsEmptyStatePreviewDescription": "미리 보기: 활성화된 경우, 승인 대기 중인 장치 요청이 검토용으로 여기에 표시됩니다.",
|
"approvalsEmptyStatePreviewDescription": "미리 보기: 활성화된 경우, 승인 대기 중인 장치 요청이 검토용으로 여기에 표시됩니다.",
|
||||||
"approvalsEmptyStateButtonText": "역할 관리"
|
"approvalsEmptyStateButtonText": "역할 관리",
|
||||||
|
"domainErrorTitle": "도메인 확인에 문제가 발생했습니다."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy forespørsler over HTTPS ved å bruke et fullstendig kvalifisert domenenavn.",
|
"resourceHTTPDescription": "Proxy forespørsler over HTTPS ved å bruke et fullstendig kvalifisert domenenavn.",
|
||||||
"resourceRaw": "Rå TCP/UDP-ressurs",
|
"resourceRaw": "Rå TCP/UDP-ressurs",
|
||||||
"resourceRawDescription": "Proxy forespørsler over rå TCP/UDP ved å bruke et portnummer.",
|
"resourceRawDescription": "Proxy forespørsler over rå TCP/UDP ved å bruke et portnummer.",
|
||||||
"resourceRawDescriptionCloud": "Proxy ber om et portnummer. Om du vil bruke et sportsnummer.",
|
"resourceRawDescriptionCloud": "Proxy forespørsler om rå TCP/UDP ved hjelp av et portnummer. Krever sider for å koble til en ekstern node.",
|
||||||
"resourceCreate": "Opprett ressurs",
|
"resourceCreate": "Opprett ressurs",
|
||||||
"resourceCreateDescription": "Følg trinnene nedenfor for å opprette en ny ressurs",
|
"resourceCreateDescription": "Følg trinnene nedenfor for å opprette en ny ressurs",
|
||||||
"resourceSeeAll": "Se alle ressurser",
|
"resourceSeeAll": "Se alle ressurser",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Navnerom: {namespace}",
|
"domainPickerNamespace": "Navnerom: {namespace}",
|
||||||
"domainPickerShowMore": "Vis mer",
|
"domainPickerShowMore": "Vis mer",
|
||||||
"regionSelectorTitle": "Velg Region",
|
"regionSelectorTitle": "Velg Region",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Tilbudte domener støttes ikke når sider kobles til eksterne avkjøringsnoder. For ressurser som skal være tilgjengelige på eksterne noder, brukes et egendefinert domene i stedet.",
|
||||||
"regionSelectorInfo": "Å velge en region hjelper oss med å gi bedre ytelse for din lokasjon. Du trenger ikke være i samme region som serveren.",
|
"regionSelectorInfo": "Å velge en region hjelper oss med å gi bedre ytelse for din lokasjon. Du trenger ikke være i samme region som serveren.",
|
||||||
"regionSelectorPlaceholder": "Velg en region",
|
"regionSelectorPlaceholder": "Velg en region",
|
||||||
"regionSelectorComingSoon": "Kommer snart",
|
"regionSelectorComingSoon": "Kommer snart",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Slutt på neste år",
|
"logRetentionEndOfFollowingYear": "Slutt på neste år",
|
||||||
"actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen",
|
"actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen",
|
||||||
"accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen",
|
"accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen",
|
||||||
"licenseRequiredToUse": "En <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisens er påkrevd for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "En <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisens eller <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> er påkrevd for å bruke denne funksjonen. <bookADemoLink>Bestill en demo eller POC prøveversjon</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Bestill en demo eller POC studie</bookADemoLink>.",
|
||||||
"certResolver": "Sertifikat løser",
|
"certResolver": "Sertifikat løser",
|
||||||
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
|
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
|
||||||
"selectCertResolver": "Velg sertifikatløser",
|
"selectCertResolver": "Velg sertifikatløser",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
||||||
"approvalsEmptyStateStep2Description": "Rediger en rolle og aktiver alternativet 'Kreve enhetsgodkjenninger'. Brukere med denne rollen vil trenge administratorgodkjenning for nye enheter.",
|
"approvalsEmptyStateStep2Description": "Rediger en rolle og aktiver alternativet 'Kreve enhetsgodkjenninger'. Brukere med denne rollen vil trenge administratorgodkjenning for nye enheter.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Forhåndsvisning: Når aktivert, ventende enhets forespørsler vil vises her for vurdering",
|
"approvalsEmptyStatePreviewDescription": "Forhåndsvisning: Når aktivert, ventende enhets forespørsler vil vises her for vurdering",
|
||||||
"approvalsEmptyStateButtonText": "Administrer Roller"
|
"approvalsEmptyStateButtonText": "Administrer Roller",
|
||||||
|
"domainErrorTitle": "Vi har problemer med å verifisere domenet ditt"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxyverzoeken via HTTPS met een volledig gekwalificeerde domeinnaam.",
|
"resourceHTTPDescription": "Proxyverzoeken via HTTPS met een volledig gekwalificeerde domeinnaam.",
|
||||||
"resourceRaw": "TCP/UDP bron",
|
"resourceRaw": "TCP/UDP bron",
|
||||||
"resourceRawDescription": "Proxyverzoeken via ruwe TCP/UDP met een poortnummer.",
|
"resourceRawDescription": "Proxyverzoeken via ruwe TCP/UDP met een poortnummer.",
|
||||||
"resourceRawDescriptionCloud": "Proxy vraagt om onbewerkte TCP/UDP met behulp van een poortnummer. VEREIST HET GEBRUIK VAN EEN AFSTANDSBEDIENING NODE.",
|
"resourceRawDescriptionCloud": "Proxy verzoeken over rauwe TCP/UDP met behulp van een poortnummer. Vereist sites om verbinding te maken met een remote node.",
|
||||||
"resourceCreate": "Bron maken",
|
"resourceCreate": "Bron maken",
|
||||||
"resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken",
|
"resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken",
|
||||||
"resourceSeeAll": "Alle bronnen bekijken",
|
"resourceSeeAll": "Alle bronnen bekijken",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Naamruimte: {namespace}",
|
"domainPickerNamespace": "Naamruimte: {namespace}",
|
||||||
"domainPickerShowMore": "Meer weergeven",
|
"domainPickerShowMore": "Meer weergeven",
|
||||||
"regionSelectorTitle": "Selecteer Regio",
|
"regionSelectorTitle": "Selecteer Regio",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Opgegeven domeinen worden niet ondersteund wanneer websites verbinding maken met externe sluitnodes. Gebruik in plaats daarvan een aangepast domein. Om bronnen beschikbaar te maken op externe nodes.",
|
||||||
"regionSelectorInfo": "Het selecteren van een regio helpt ons om betere prestaties te leveren voor uw locatie. U hoeft niet in dezelfde regio als uw server te zijn.",
|
"regionSelectorInfo": "Het selecteren van een regio helpt ons om betere prestaties te leveren voor uw locatie. U hoeft niet in dezelfde regio als uw server te zijn.",
|
||||||
"regionSelectorPlaceholder": "Kies een regio",
|
"regionSelectorPlaceholder": "Kies een regio",
|
||||||
"regionSelectorComingSoon": "Komt binnenkort",
|
"regionSelectorComingSoon": "Komt binnenkort",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Einde van volgend jaar",
|
"logRetentionEndOfFollowingYear": "Einde van volgend jaar",
|
||||||
"actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie",
|
"actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie",
|
||||||
"accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken",
|
"accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken",
|
||||||
"licenseRequiredToUse": "Een <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> licentie is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Een <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> licentie of <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is vereist om deze functie te gebruiken. <bookADemoLink>Boek een demo of POC trial</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Boek een demo of POC trial</bookADemoLink>.",
|
||||||
"certResolver": "Certificaat Resolver",
|
"certResolver": "Certificaat Resolver",
|
||||||
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
|
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
|
||||||
"selectCertResolver": "Certificaat Resolver selecteren",
|
"selectCertResolver": "Certificaat Resolver selecteren",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
|
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
|
||||||
"approvalsEmptyStateStep2Description": "Bewerk een rol en schakel de optie 'Vereist Apparaat Goedkeuringen' in. Gebruikers met deze rol hebben admin goedkeuring nodig voor nieuwe apparaten.",
|
"approvalsEmptyStateStep2Description": "Bewerk een rol en schakel de optie 'Vereist Apparaat Goedkeuringen' in. Gebruikers met deze rol hebben admin goedkeuring nodig voor nieuwe apparaten.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Voorbeeld: Indien ingeschakeld, zullen in afwachting van apparaatverzoeken hier verschijnen om te beoordelen",
|
"approvalsEmptyStatePreviewDescription": "Voorbeeld: Indien ingeschakeld, zullen in afwachting van apparaatverzoeken hier verschijnen om te beoordelen",
|
||||||
"approvalsEmptyStateButtonText": "Rollen beheren"
|
"approvalsEmptyStateButtonText": "Rollen beheren",
|
||||||
|
"domainErrorTitle": "We ondervinden problemen bij het controleren van uw domein"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxy zapytań przez HTTPS przy użyciu w pełni kwalifikowanej nazwy domeny.",
|
"resourceHTTPDescription": "Proxy zapytań przez HTTPS przy użyciu w pełni kwalifikowanej nazwy domeny.",
|
||||||
"resourceRaw": "Surowy zasób TCP/UDP",
|
"resourceRaw": "Surowy zasób TCP/UDP",
|
||||||
"resourceRawDescription": "Proxy zapytań przez surowe TCP/UDP przy użyciu numeru portu.",
|
"resourceRawDescription": "Proxy zapytań przez surowe TCP/UDP przy użyciu numeru portu.",
|
||||||
"resourceRawDescriptionCloud": "Proxy żądania przesyłania danych nad surowym TCP/UDP przy użyciu numeru portu. Wymaga UŻYTKOWANIA PALIWA węzła.",
|
"resourceRawDescriptionCloud": "Żądania proxy nad surowym TCP/UDP przy użyciu numeru portu. Wymaga stron aby połączyć się ze zdalnym węzłem.",
|
||||||
"resourceCreate": "Utwórz zasób",
|
"resourceCreate": "Utwórz zasób",
|
||||||
"resourceCreateDescription": "Wykonaj poniższe kroki, aby utworzyć nowy zasób",
|
"resourceCreateDescription": "Wykonaj poniższe kroki, aby utworzyć nowy zasób",
|
||||||
"resourceSeeAll": "Zobacz wszystkie zasoby",
|
"resourceSeeAll": "Zobacz wszystkie zasoby",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Przestrzeń nazw: {namespace}",
|
"domainPickerNamespace": "Przestrzeń nazw: {namespace}",
|
||||||
"domainPickerShowMore": "Pokaż więcej",
|
"domainPickerShowMore": "Pokaż więcej",
|
||||||
"regionSelectorTitle": "Wybierz region",
|
"regionSelectorTitle": "Wybierz region",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Podane domeny nie są obsługiwane, gdy witryny łączą się ze zdalnymi węzłami wyjścia. Aby zasoby były dostępne w węzłach zdalnych, użyj domeny niestandardowej.",
|
||||||
"regionSelectorInfo": "Wybór regionu pomaga nam zapewnić lepszą wydajność dla Twojej lokalizacji. Nie musisz być w tym samym regionie co Twój serwer.",
|
"regionSelectorInfo": "Wybór regionu pomaga nam zapewnić lepszą wydajność dla Twojej lokalizacji. Nie musisz być w tym samym regionie co Twój serwer.",
|
||||||
"regionSelectorPlaceholder": "Wybierz region",
|
"regionSelectorPlaceholder": "Wybierz region",
|
||||||
"regionSelectorComingSoon": "Wkrótce dostępne",
|
"regionSelectorComingSoon": "Wkrótce dostępne",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Koniec następnego roku",
|
"logRetentionEndOfFollowingYear": "Koniec następnego roku",
|
||||||
"actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji",
|
"actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji",
|
||||||
"accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji",
|
"accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji",
|
||||||
"licenseRequiredToUse": "Do korzystania z tej funkcji wymagana jest licencja <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Ta funkcja jest również dostępna w <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Do korzystania z tej funkcji wymagana jest licencja <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lub <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> . <bookADemoLink>Zarezerwuj wersję demonstracyjną lub wersję próbną POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> jest wymagany do korzystania z tej funkcji. Ta funkcja jest również dostępna w <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> jest wymagany do korzystania z tej funkcji. Ta funkcja jest również dostępna w <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Zarezerwuj demo lub okres próbny POC</bookADemoLink>.",
|
||||||
"certResolver": "Rozwiązywanie certyfikatów",
|
"certResolver": "Rozwiązywanie certyfikatów",
|
||||||
"certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.",
|
"certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.",
|
||||||
"selectCertResolver": "Wybierz Resolver certyfikatów",
|
"selectCertResolver": "Wybierz Resolver certyfikatów",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
"approvalsEmptyStateStep2Title": "Włącz zatwierdzanie urządzenia",
|
||||||
"approvalsEmptyStateStep2Description": "Edytuj rolę i włącz opcję \"Wymagaj zatwierdzenia urządzenia\". Użytkownicy z tą rolą będą potrzebowali zatwierdzenia administratora dla nowych urządzeń.",
|
"approvalsEmptyStateStep2Description": "Edytuj rolę i włącz opcję \"Wymagaj zatwierdzenia urządzenia\". Użytkownicy z tą rolą będą potrzebowali zatwierdzenia administratora dla nowych urządzeń.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Podgląd: Gdy włączone, oczekujące prośby o sprawdzenie pojawią się tutaj",
|
"approvalsEmptyStatePreviewDescription": "Podgląd: Gdy włączone, oczekujące prośby o sprawdzenie pojawią się tutaj",
|
||||||
"approvalsEmptyStateButtonText": "Zarządzaj rolami"
|
"approvalsEmptyStateButtonText": "Zarządzaj rolami",
|
||||||
|
"domainErrorTitle": "Mamy problem z weryfikacją Twojej domeny"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Proxies requests sobre HTTPS usando um nome de domínio totalmente qualificado.",
|
"resourceHTTPDescription": "Proxies requests sobre HTTPS usando um nome de domínio totalmente qualificado.",
|
||||||
"resourceRaw": "Recurso TCP/UDP bruto",
|
"resourceRaw": "Recurso TCP/UDP bruto",
|
||||||
"resourceRawDescription": "Proxies solicitações sobre TCP/UDP bruto usando um número de porta.",
|
"resourceRawDescription": "Proxies solicitações sobre TCP/UDP bruto usando um número de porta.",
|
||||||
"resourceRawDescriptionCloud": "Proxy solicita sobre TCP/UDP bruto usando um número de porta. OBRIGATÓRIO O USO DE UMA NOTA REMOTA.",
|
"resourceRawDescriptionCloud": "Proxy solicita por TCP/UDP bruto usando um número de porta. Requer que sites se conectem a um nó remoto.",
|
||||||
"resourceCreate": "Criar Recurso",
|
"resourceCreate": "Criar Recurso",
|
||||||
"resourceCreateDescription": "Siga os passos abaixo para criar um novo recurso",
|
"resourceCreateDescription": "Siga os passos abaixo para criar um novo recurso",
|
||||||
"resourceSeeAll": "Ver todos os recursos",
|
"resourceSeeAll": "Ver todos os recursos",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Namespace: {namespace}",
|
"domainPickerNamespace": "Namespace: {namespace}",
|
||||||
"domainPickerShowMore": "Mostrar Mais",
|
"domainPickerShowMore": "Mostrar Mais",
|
||||||
"regionSelectorTitle": "Selecionar Região",
|
"regionSelectorTitle": "Selecionar Região",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Domínios fornecidos não são suportados quando os sites se conectam a nós de saída remota. Para recursos disponíveis em nós remotos, use um domínio personalizado.",
|
||||||
"regionSelectorInfo": "Selecionar uma região nos ajuda a fornecer melhor desempenho para sua localização. Você não precisa estar na mesma região que seu servidor.",
|
"regionSelectorInfo": "Selecionar uma região nos ajuda a fornecer melhor desempenho para sua localização. Você não precisa estar na mesma região que seu servidor.",
|
||||||
"regionSelectorPlaceholder": "Escolher uma região",
|
"regionSelectorPlaceholder": "Escolher uma região",
|
||||||
"regionSelectorComingSoon": "Em breve",
|
"regionSelectorComingSoon": "Em breve",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Fim do ano seguinte",
|
"logRetentionEndOfFollowingYear": "Fim do ano seguinte",
|
||||||
"actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização",
|
"actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização",
|
||||||
"accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização",
|
"accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização",
|
||||||
"licenseRequiredToUse": "Uma licença <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> é necessária para usar este recurso. Este recurso também está disponível no <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Uma licença <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> ou <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> é necessária para usar este recurso. <bookADemoLink>Reserve um teste de demonstração ou POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "O <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> é necessário para usar este recurso. Este recurso também está disponível no <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "O <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> é necessário para usar este recurso. Este recurso também está disponível no <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Reserve uma demonstração ou avaliação POC</bookADemoLink>.",
|
||||||
"certResolver": "Resolvedor de Certificado",
|
"certResolver": "Resolvedor de Certificado",
|
||||||
"certResolverDescription": "Selecione o resolvedor de certificados para este recurso.",
|
"certResolverDescription": "Selecione o resolvedor de certificados para este recurso.",
|
||||||
"selectCertResolver": "Selecionar solucionador de certificado",
|
"selectCertResolver": "Selecionar solucionador de certificado",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Habilitar Aprovações do Dispositivo",
|
"approvalsEmptyStateStep2Title": "Habilitar Aprovações do Dispositivo",
|
||||||
"approvalsEmptyStateStep2Description": "Editar uma função e habilitar a opção 'Exigir aprovação de dispositivos'. Usuários com essa função precisarão de aprovação de administrador para novos dispositivos.",
|
"approvalsEmptyStateStep2Description": "Editar uma função e habilitar a opção 'Exigir aprovação de dispositivos'. Usuários com essa função precisarão de aprovação de administrador para novos dispositivos.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Pré-visualização: Quando ativado, solicitações de dispositivo pendentes aparecerão aqui para revisão",
|
"approvalsEmptyStatePreviewDescription": "Pré-visualização: Quando ativado, solicitações de dispositivo pendentes aparecerão aqui para revisão",
|
||||||
"approvalsEmptyStateButtonText": "Gerir Funções"
|
"approvalsEmptyStateButtonText": "Gerir Funções",
|
||||||
|
"domainErrorTitle": "Estamos tendo problemas ao verificar seu domínio"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Проксировать запросы через HTTPS с использованием полного доменного имени.",
|
"resourceHTTPDescription": "Проксировать запросы через HTTPS с использованием полного доменного имени.",
|
||||||
"resourceRaw": "Сырой TCP/UDP-ресурс",
|
"resourceRaw": "Сырой TCP/UDP-ресурс",
|
||||||
"resourceRawDescription": "Проксировать запросы по сырому TCP/UDP с использованием номера порта.",
|
"resourceRawDescription": "Проксировать запросы по сырому TCP/UDP с использованием номера порта.",
|
||||||
"resourceRawDescriptionCloud": "Прокси-запросы через необработанный TCP/UDP с использованием номера порта. ТРЕБУЕТЕСЬ ИСПОЛЬЗОВАТЬ НЕОБХОДИМЫ.",
|
"resourceRawDescriptionCloud": "Прокси запросы через необработанный TCP/UDP с использованием номера порта. Требуется подключение сайтов к удаленному узлу.",
|
||||||
"resourceCreate": "Создание ресурса",
|
"resourceCreate": "Создание ресурса",
|
||||||
"resourceCreateDescription": "Следуйте инструкциям ниже для создания нового ресурса",
|
"resourceCreateDescription": "Следуйте инструкциям ниже для создания нового ресурса",
|
||||||
"resourceSeeAll": "Посмотреть все ресурсы",
|
"resourceSeeAll": "Посмотреть все ресурсы",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Пространство имен: {namespace}",
|
"domainPickerNamespace": "Пространство имен: {namespace}",
|
||||||
"domainPickerShowMore": "Показать еще",
|
"domainPickerShowMore": "Показать еще",
|
||||||
"regionSelectorTitle": "Выберите регион",
|
"regionSelectorTitle": "Выберите регион",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Предоставленные домены не поддерживаются при подключении сайтов к удаленным узлам. Для доступа к ресурсам на удаленных узлах используйте пользовательский домен.",
|
||||||
"regionSelectorInfo": "Выбор региона помогает нам обеспечить лучшее качество обслуживания для вашего расположения. Вам необязательно находиться в том же регионе, что и ваш сервер.",
|
"regionSelectorInfo": "Выбор региона помогает нам обеспечить лучшее качество обслуживания для вашего расположения. Вам необязательно находиться в том же регионе, что и ваш сервер.",
|
||||||
"regionSelectorPlaceholder": "Выбор региона",
|
"regionSelectorPlaceholder": "Выбор региона",
|
||||||
"regionSelectorComingSoon": "Скоро будет",
|
"regionSelectorComingSoon": "Скоро будет",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Конец следующего года",
|
"logRetentionEndOfFollowingYear": "Конец следующего года",
|
||||||
"actionLogsDescription": "Просмотр истории действий, выполненных в этой организации",
|
"actionLogsDescription": "Просмотр истории действий, выполненных в этой организации",
|
||||||
"accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации",
|
"accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации",
|
||||||
"licenseRequiredToUse": "Лицензия на <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> требуется для использования этой функции. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"licenseRequiredToUse": "Требуется лицензия на <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> или <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> для использования этой функции. <bookADemoLink>Забронируйте демонстрацию или пробный POC</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "Для использования этой функции требуется <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink>. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> требуется для использования этой функции. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Забронируйте демонстрацию или пробный POC</bookADemoLink>.",
|
||||||
"certResolver": "Резольвер сертификата",
|
"certResolver": "Резольвер сертификата",
|
||||||
"certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.",
|
"certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.",
|
||||||
"selectCertResolver": "Выберите резолвер сертификата",
|
"selectCertResolver": "Выберите резолвер сертификата",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Включить утверждения устройства",
|
"approvalsEmptyStateStep2Title": "Включить утверждения устройства",
|
||||||
"approvalsEmptyStateStep2Description": "Редактировать роль и включить опцию 'Требовать утверждения устройств'. Пользователям с этой ролью потребуется подтверждение администратора для новых устройств.",
|
"approvalsEmptyStateStep2Description": "Редактировать роль и включить опцию 'Требовать утверждения устройств'. Пользователям с этой ролью потребуется подтверждение администратора для новых устройств.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Предпросмотр: Если включено, ожидающие запросы на устройство появятся здесь для проверки",
|
"approvalsEmptyStatePreviewDescription": "Предпросмотр: Если включено, ожидающие запросы на устройство появятся здесь для проверки",
|
||||||
"approvalsEmptyStateButtonText": "Управление ролями"
|
"approvalsEmptyStateButtonText": "Управление ролями",
|
||||||
|
"domainErrorTitle": "У нас возникли проблемы с проверкой вашего домена"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "Tam nitelikli bir etki alanı adı kullanarak HTTPS üzerinden proxy isteklerini yönlendirin.",
|
"resourceHTTPDescription": "Tam nitelikli bir etki alanı adı kullanarak HTTPS üzerinden proxy isteklerini yönlendirin.",
|
||||||
"resourceRaw": "Ham TCP/UDP Kaynağı",
|
"resourceRaw": "Ham TCP/UDP Kaynağı",
|
||||||
"resourceRawDescription": "Port numarası kullanarak ham TCP/UDP üzerinden proxy isteklerini yönlendirin.",
|
"resourceRawDescription": "Port numarası kullanarak ham TCP/UDP üzerinden proxy isteklerini yönlendirin.",
|
||||||
"resourceRawDescriptionCloud": "Bir port numarası kullanarak ham TCP/UDP üzerinden istekleri proxy ile yönlendirin. UZAKTAN BİR DÜĞÜM KULLANIMINI GEREKTİRİR.",
|
"resourceRawDescriptionCloud": "Proxy isteklerini bir port numarası kullanarak ham TCP/UDP üzerinden yapın. Sitelerin uzak bir düğüme bağlanması gereklidir.",
|
||||||
"resourceCreate": "Kaynak Oluştur",
|
"resourceCreate": "Kaynak Oluştur",
|
||||||
"resourceCreateDescription": "Yeni bir kaynak oluşturmak için aşağıdaki adımları izleyin",
|
"resourceCreateDescription": "Yeni bir kaynak oluşturmak için aşağıdaki adımları izleyin",
|
||||||
"resourceSeeAll": "Tüm Kaynakları Gör",
|
"resourceSeeAll": "Tüm Kaynakları Gör",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "Ad Alanı: {namespace}",
|
"domainPickerNamespace": "Ad Alanı: {namespace}",
|
||||||
"domainPickerShowMore": "Daha Fazla Göster",
|
"domainPickerShowMore": "Daha Fazla Göster",
|
||||||
"regionSelectorTitle": "Bölge Seç",
|
"regionSelectorTitle": "Bölge Seç",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "Belirtilen alan adları, siteler uzak çıkış düğümlerine bağlandığında desteklenmez. Kaynakların uzak düğümlerde kullanılabilir olması için özel bir alan adı kullanın.",
|
||||||
"regionSelectorInfo": "Bir bölge seçmek, konumunuz için daha iyi performans sağlamamıza yardımcı olur. Sunucunuzla aynı bölgede olmanıza gerek yoktur.",
|
"regionSelectorInfo": "Bir bölge seçmek, konumunuz için daha iyi performans sağlamamıza yardımcı olur. Sunucunuzla aynı bölgede olmanıza gerek yoktur.",
|
||||||
"regionSelectorPlaceholder": "Bölge Seçin",
|
"regionSelectorPlaceholder": "Bölge Seçin",
|
||||||
"regionSelectorComingSoon": "Yakında Geliyor",
|
"regionSelectorComingSoon": "Yakında Geliyor",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu",
|
"logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu",
|
||||||
"actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin",
|
"actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin",
|
||||||
"accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin",
|
"accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin",
|
||||||
"licenseRequiredToUse": "Bu özelliği kullanmak için bir <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisansı gereklidir. Bu özellik ayrıca <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>'da da mevcuttur.",
|
"licenseRequiredToUse": "Bu özelliği kullanmak için bir <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisansı veya <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> gereklidir. <bookADemoLink>Tanıtım veya POC denemesi ayarlayın</bookADemoLink>.",
|
||||||
"ossEnterpriseEditionRequired": "Bu özelliği kullanmak için <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> gereklidir. Bu özellik ayrıca <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>'da da mevcuttur.",
|
"ossEnterpriseEditionRequired": "Bu özelliği kullanmak için <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> gereklidir. Bu özellik ayrıca <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>’da da mevcuttur. <bookADemoLink>Tanıtım veya POC denemesi ayarlayın</bookADemoLink>.",
|
||||||
"certResolver": "Sertifika Çözücü",
|
"certResolver": "Sertifika Çözücü",
|
||||||
"certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.",
|
"certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.",
|
||||||
"selectCertResolver": "Sertifika Çözücü Seçin",
|
"selectCertResolver": "Sertifika Çözücü Seçin",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "Cihaz Onaylarını Etkinleştir",
|
"approvalsEmptyStateStep2Title": "Cihaz Onaylarını Etkinleştir",
|
||||||
"approvalsEmptyStateStep2Description": "Bir rolü düzenleyin ve 'Cihaz Onaylarını Gerektir' seçeneğini etkinleştirin. Bu role sahip kullanıcıların yeni cihazlar için yönetici onayına ihtiyacı olacaktır.",
|
"approvalsEmptyStateStep2Description": "Bir rolü düzenleyin ve 'Cihaz Onaylarını Gerektir' seçeneğini etkinleştirin. Bu role sahip kullanıcıların yeni cihazlar için yönetici onayına ihtiyacı olacaktır.",
|
||||||
"approvalsEmptyStatePreviewDescription": "Önizleme: Etkinleştirildiğinde, bekleyen cihaz talepleri incelenmek üzere burada görünecektir.",
|
"approvalsEmptyStatePreviewDescription": "Önizleme: Etkinleştirildiğinde, bekleyen cihaz talepleri incelenmek üzere burada görünecektir.",
|
||||||
"approvalsEmptyStateButtonText": "Rolleri Yönet"
|
"approvalsEmptyStateButtonText": "Rolleri Yönet",
|
||||||
|
"domainErrorTitle": "Alan adınızı doğrulamada sorun yaşıyoruz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@
|
|||||||
"resourceHTTPDescription": "通过使用完全限定的域名的HTTPS代理请求。",
|
"resourceHTTPDescription": "通过使用完全限定的域名的HTTPS代理请求。",
|
||||||
"resourceRaw": "TCP/UDP 资源",
|
"resourceRaw": "TCP/UDP 资源",
|
||||||
"resourceRawDescription": "通过使用端口号的原始TCP/UDP代理请求。",
|
"resourceRawDescription": "通过使用端口号的原始TCP/UDP代理请求。",
|
||||||
"resourceRawDescriptionCloud": "正在使用端口号的 TCP/UDP 代理请求。请使用一个REMOTE",
|
"resourceRawDescriptionCloud": "正在使用端口号使用 TCP/UDP 代理请求。需要站点连接到远程节点。",
|
||||||
"resourceCreate": "创建资源",
|
"resourceCreate": "创建资源",
|
||||||
"resourceCreateDescription": "按照下面的步骤创建新资源",
|
"resourceCreateDescription": "按照下面的步骤创建新资源",
|
||||||
"resourceSeeAll": "查看所有资源",
|
"resourceSeeAll": "查看所有资源",
|
||||||
@@ -1426,6 +1426,7 @@
|
|||||||
"domainPickerNamespace": "命名空间:{namespace}",
|
"domainPickerNamespace": "命名空间:{namespace}",
|
||||||
"domainPickerShowMore": "显示更多",
|
"domainPickerShowMore": "显示更多",
|
||||||
"regionSelectorTitle": "选择区域",
|
"regionSelectorTitle": "选择区域",
|
||||||
|
"domainPickerRemoteExitNodeWarning": "当站点连接到远程退出节点时不支持所提供的域。为了资源可在远程节点上使用,请使用自定义域名。",
|
||||||
"regionSelectorInfo": "选择区域以帮助提升您所在地的性能。您不必与服务器在相同的区域。",
|
"regionSelectorInfo": "选择区域以帮助提升您所在地的性能。您不必与服务器在相同的区域。",
|
||||||
"regionSelectorPlaceholder": "选择一个区域",
|
"regionSelectorPlaceholder": "选择一个区域",
|
||||||
"regionSelectorComingSoon": "即将推出",
|
"regionSelectorComingSoon": "即将推出",
|
||||||
@@ -2342,8 +2343,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "下一年结束",
|
"logRetentionEndOfFollowingYear": "下一年结束",
|
||||||
"actionLogsDescription": "查看此机构执行的操作历史",
|
"actionLogsDescription": "查看此机构执行的操作历史",
|
||||||
"accessLogsDescription": "查看此机构资源的访问认证请求",
|
"accessLogsDescription": "查看此机构资源的访问认证请求",
|
||||||
"licenseRequiredToUse": "需要 <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> 许可才能使用此功能。此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> 中使用。",
|
"licenseRequiredToUse": "使用此功能需要<enterpriseLicenseLink>企业版</enterpriseLicenseLink>许可证或<pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>。<bookADemoLink>预约演示或POC试用</bookADemoLink>。",
|
||||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> 需要使用此功能。此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> 中使用。",
|
"ossEnterpriseEditionRequired": "需要 <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> 才能使用此功能。 此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>上获取。 <bookADemoLink>预订演示或POC 试用</bookADemoLink>。",
|
||||||
"certResolver": "证书解决器",
|
"certResolver": "证书解决器",
|
||||||
"certResolverDescription": "选择用于此资源的证书解析器。",
|
"certResolverDescription": "选择用于此资源的证书解析器。",
|
||||||
"selectCertResolver": "选择证书解析",
|
"selectCertResolver": "选择证书解析",
|
||||||
@@ -2680,5 +2681,6 @@
|
|||||||
"approvalsEmptyStateStep2Title": "启用设备批准",
|
"approvalsEmptyStateStep2Title": "启用设备批准",
|
||||||
"approvalsEmptyStateStep2Description": "编辑角色并启用“需要设备审批”选项。具有此角色的用户需要管理员批准新设备。",
|
"approvalsEmptyStateStep2Description": "编辑角色并启用“需要设备审批”选项。具有此角色的用户需要管理员批准新设备。",
|
||||||
"approvalsEmptyStatePreviewDescription": "预览:如果启用,待处理设备请求将出现在这里供审核",
|
"approvalsEmptyStatePreviewDescription": "预览:如果启用,待处理设备请求将出现在这里供审核",
|
||||||
"approvalsEmptyStateButtonText": "管理角色"
|
"approvalsEmptyStateButtonText": "管理角色",
|
||||||
|
"domainErrorTitle": "我们在验证您的域名时遇到了问题"
|
||||||
}
|
}
|
||||||
|
|||||||
2432
package-lock.json
generated
2432
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
18
package.json
18
package.json
@@ -33,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@asteasolutions/zod-to-openapi": "8.4.1",
|
"@asteasolutions/zod-to-openapi": "8.4.1",
|
||||||
"@aws-sdk/client-s3": "3.1004.0",
|
"@aws-sdk/client-s3": "3.1011.0",
|
||||||
"@faker-js/faker": "10.3.0",
|
"@faker-js/faker": "10.3.0",
|
||||||
"@headlessui/react": "2.2.9",
|
"@headlessui/react": "2.2.9",
|
||||||
"@hookform/resolvers": "5.2.2",
|
"@hookform/resolvers": "5.2.2",
|
||||||
@@ -62,8 +62,8 @@
|
|||||||
"@react-email/components": "1.0.8",
|
"@react-email/components": "1.0.8",
|
||||||
"@react-email/render": "2.0.4",
|
"@react-email/render": "2.0.4",
|
||||||
"@react-email/tailwind": "2.0.5",
|
"@react-email/tailwind": "2.0.5",
|
||||||
"@simplewebauthn/browser": "13.2.2",
|
"@simplewebauthn/browser": "13.3.0",
|
||||||
"@simplewebauthn/server": "13.2.3",
|
"@simplewebauthn/server": "13.3.0",
|
||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.90.21",
|
"@tanstack/react-query": "5.90.21",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "1.54.1",
|
"@dotenvx/dotenvx": "1.54.1",
|
||||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||||
"@react-email/preview-server": "5.2.8",
|
"@react-email/preview-server": "5.2.10",
|
||||||
"@tailwindcss/postcss": "4.2.1",
|
"@tailwindcss/postcss": "4.2.1",
|
||||||
"@tanstack/react-query-devtools": "5.91.3",
|
"@tanstack/react-query-devtools": "5.91.3",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
@@ -159,14 +159,14 @@
|
|||||||
"@types/ws": "8.18.1",
|
"@types/ws": "8.18.1",
|
||||||
"@types/yargs": "17.0.35",
|
"@types/yargs": "17.0.35",
|
||||||
"babel-plugin-react-compiler": "1.0.0",
|
"babel-plugin-react-compiler": "1.0.0",
|
||||||
"drizzle-kit": "0.31.9",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.27.3",
|
"esbuild": "0.27.3",
|
||||||
"esbuild-node-externals": "1.20.1",
|
"esbuild-node-externals": "1.20.1",
|
||||||
"eslint": "9.39.2",
|
"eslint": "10.0.3",
|
||||||
"eslint-config-next": "16.1.6",
|
"eslint-config-next": "16.1.7",
|
||||||
"postcss": "8.5.6",
|
"postcss": "8.5.8",
|
||||||
"prettier": "3.8.1",
|
"prettier": "3.8.1",
|
||||||
"react-email": "5.2.8",
|
"react-email": "5.2.10",
|
||||||
"tailwindcss": "4.2.1",
|
"tailwindcss": "4.2.1",
|
||||||
"tsc-alias": "1.8.16",
|
"tsc-alias": "1.8.16",
|
||||||
"tsx": "4.21.0",
|
"tsx": "4.21.0",
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export enum ActionsEnum {
|
|||||||
getSite = "getSite",
|
getSite = "getSite",
|
||||||
listSites = "listSites",
|
listSites = "listSites",
|
||||||
updateSite = "updateSite",
|
updateSite = "updateSite",
|
||||||
|
resetSiteBandwidth = "resetSiteBandwidth",
|
||||||
reGenerateSecret = "reGenerateSecret",
|
reGenerateSecret = "reGenerateSecret",
|
||||||
createResource = "createResource",
|
createResource = "createResource",
|
||||||
deleteResource = "deleteResource",
|
deleteResource = "deleteResource",
|
||||||
|
|||||||
@@ -216,18 +216,12 @@ export const exitNodes = pgTable("exitNodes", {
|
|||||||
export const siteResources = pgTable("siteResources", {
|
export const siteResources = pgTable("siteResources", {
|
||||||
// this is for the clients
|
// this is for the clients
|
||||||
siteResourceId: serial("siteResourceId").primaryKey(),
|
siteResourceId: serial("siteResourceId").primaryKey(),
|
||||||
|
siteId: integer("siteId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => sites.siteId, { onDelete: "cascade" }),
|
||||||
orgId: varchar("orgId")
|
orgId: varchar("orgId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
networkId: integer("networkId").references(() => networks.networkId, {
|
|
||||||
onDelete: "set null"
|
|
||||||
}),
|
|
||||||
defaultNetworkId: integer("defaultNetworkId").references(
|
|
||||||
() => networks.networkId,
|
|
||||||
{
|
|
||||||
onDelete: "restrict"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
niceId: varchar("niceId").notNull(),
|
niceId: varchar("niceId").notNull(),
|
||||||
name: varchar("name").notNull(),
|
name: varchar("name").notNull(),
|
||||||
mode: varchar("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port"
|
mode: varchar("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port"
|
||||||
@@ -247,32 +241,6 @@ export const siteResources = pgTable("siteResources", {
|
|||||||
.default("site")
|
.default("site")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const networks = pgTable("networks", {
|
|
||||||
networkId: serial("networkId").primaryKey(),
|
|
||||||
niceId: text("niceId"),
|
|
||||||
name: text("name"),
|
|
||||||
scope: varchar("scope")
|
|
||||||
.$type<"global" | "resource">()
|
|
||||||
.notNull()
|
|
||||||
.default("global"),
|
|
||||||
orgId: varchar("orgId")
|
|
||||||
.references(() => orgs.orgId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
})
|
|
||||||
.notNull()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const siteNetworks = pgTable("siteNetworks", {
|
|
||||||
siteId: integer("siteId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => sites.siteId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
networkId: integer("networkId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => networks.networkId, { onDelete: "cascade" })
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clientSiteResources = pgTable("clientSiteResources", {
|
export const clientSiteResources = pgTable("clientSiteResources", {
|
||||||
clientId: integer("clientId")
|
clientId: integer("clientId")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -1106,4 +1074,3 @@ export type RequestAuditLog = InferSelectModel<typeof requestAuditLog>;
|
|||||||
export type RoundTripMessageTracker = InferSelectModel<
|
export type RoundTripMessageTracker = InferSelectModel<
|
||||||
typeof roundTripMessageTracker
|
typeof roundTripMessageTracker
|
||||||
>;
|
>;
|
||||||
export type Network = InferSelectModel<typeof networks>;
|
|
||||||
|
|||||||
@@ -82,9 +82,6 @@ export const sites = sqliteTable("sites", {
|
|||||||
exitNodeId: integer("exitNode").references(() => exitNodes.exitNodeId, {
|
exitNodeId: integer("exitNode").references(() => exitNodes.exitNodeId, {
|
||||||
onDelete: "set null"
|
onDelete: "set null"
|
||||||
}),
|
}),
|
||||||
networkId: integer("networkId").references(() => networks.networkId, {
|
|
||||||
onDelete: "set null"
|
|
||||||
}),
|
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
pubKey: text("pubKey"),
|
pubKey: text("pubKey"),
|
||||||
subnet: text("subnet"),
|
subnet: text("subnet"),
|
||||||
@@ -242,16 +239,12 @@ export const siteResources = sqliteTable("siteResources", {
|
|||||||
siteResourceId: integer("siteResourceId").primaryKey({
|
siteResourceId: integer("siteResourceId").primaryKey({
|
||||||
autoIncrement: true
|
autoIncrement: true
|
||||||
}),
|
}),
|
||||||
|
siteId: integer("siteId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => sites.siteId, { onDelete: "cascade" }),
|
||||||
orgId: text("orgId")
|
orgId: text("orgId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
networkId: integer("networkId").references(() => networks.networkId, {
|
|
||||||
onDelete: "set null"
|
|
||||||
}),
|
|
||||||
defaultNetworkId: integer("defaultNetworkId").references(
|
|
||||||
() => networks.networkId,
|
|
||||||
{ onDelete: "restrict" }
|
|
||||||
),
|
|
||||||
niceId: text("niceId").notNull(),
|
niceId: text("niceId").notNull(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
mode: text("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port"
|
mode: text("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port"
|
||||||
@@ -273,30 +266,6 @@ export const siteResources = sqliteTable("siteResources", {
|
|||||||
.default("site")
|
.default("site")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const networks = sqliteTable("networks", {
|
|
||||||
networkId: integer("networkId").primaryKey({ autoIncrement: true }),
|
|
||||||
niceId: text("niceId"),
|
|
||||||
name: text("name"),
|
|
||||||
scope: text("scope")
|
|
||||||
.$type<"global" | "resource">()
|
|
||||||
.notNull()
|
|
||||||
.default("global"),
|
|
||||||
orgId: text("orgId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" })
|
|
||||||
});
|
|
||||||
|
|
||||||
export const siteNetworks = sqliteTable("siteNetworks", {
|
|
||||||
siteId: integer("siteId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => sites.siteId, {
|
|
||||||
onDelete: "cascade"
|
|
||||||
}),
|
|
||||||
networkId: integer("networkId")
|
|
||||||
.notNull()
|
|
||||||
.references(() => networks.networkId, { onDelete: "cascade" })
|
|
||||||
});
|
|
||||||
|
|
||||||
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
||||||
clientId: integer("clientId")
|
clientId: integer("clientId")
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -1189,7 +1158,6 @@ export type ApiKey = InferSelectModel<typeof apiKeys>;
|
|||||||
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
||||||
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
||||||
export type SiteResource = InferSelectModel<typeof siteResources>;
|
export type SiteResource = InferSelectModel<typeof siteResources>;
|
||||||
export type Network = InferSelectModel<typeof networks>;
|
|
||||||
export type OrgDomains = InferSelectModel<typeof orgDomains>;
|
export type OrgDomains = InferSelectModel<typeof orgDomains>;
|
||||||
export type SetupToken = InferSelectModel<typeof setupTokens>;
|
export type SetupToken = InferSelectModel<typeof setupTokens>;
|
||||||
export type HostMeta = InferSelectModel<typeof hostMeta>;
|
export type HostMeta = InferSelectModel<typeof hostMeta>;
|
||||||
|
|||||||
@@ -121,8 +121,8 @@ export async function applyBlueprint({
|
|||||||
for (const result of clientResourcesResults) {
|
for (const result of clientResourcesResults) {
|
||||||
if (
|
if (
|
||||||
result.oldSiteResource &&
|
result.oldSiteResource &&
|
||||||
JSON.stringify(result.newSites?.sort()) !==
|
result.oldSiteResource.siteId !=
|
||||||
JSON.stringify(result.oldSites?.sort())
|
result.newSiteResource.siteId
|
||||||
) {
|
) {
|
||||||
// query existing associations
|
// query existing associations
|
||||||
const existingRoleIds = await trx
|
const existingRoleIds = await trx
|
||||||
@@ -222,15 +222,13 @@ export async function applyBlueprint({
|
|||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let good = true;
|
const [newSite] = await trx
|
||||||
for (const newSite of result.newSites) {
|
|
||||||
const [site] = await trx
|
|
||||||
.select()
|
.select()
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.innerJoin(newts, eq(sites.siteId, newts.siteId))
|
.innerJoin(newts, eq(sites.siteId, newts.siteId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(sites.siteId, newSite.siteId),
|
eq(sites.siteId, result.newSiteResource.siteId),
|
||||||
eq(sites.orgId, orgId),
|
eq(sites.orgId, orgId),
|
||||||
eq(sites.type, "newt"),
|
eq(sites.type, "newt"),
|
||||||
isNotNull(sites.pubKey)
|
isNotNull(sites.pubKey)
|
||||||
@@ -238,30 +236,24 @@ export async function applyBlueprint({
|
|||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!site) {
|
if (!newSite) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`No newt sites found for client resource ${result.newSiteResource.siteResourceId}, skipping target update`
|
`No newt site found for client resource ${result.newSiteResource.siteResourceId}, skipping target update`
|
||||||
);
|
);
|
||||||
good = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug(
|
|
||||||
`Updating client resource ${result.newSiteResource.siteResourceId} on site ${newSite.siteId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!good) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`Updating client resource ${result.newSiteResource.siteResourceId} on site ${newSite.sites.siteId}`
|
||||||
|
);
|
||||||
|
|
||||||
await handleMessagingForUpdatedSiteResource(
|
await handleMessagingForUpdatedSiteResource(
|
||||||
result.oldSiteResource,
|
result.oldSiteResource,
|
||||||
result.newSiteResource,
|
result.newSiteResource,
|
||||||
result.newSites.map((site) => ({
|
{
|
||||||
siteId: site.siteId,
|
siteId: newSite.sites.siteId,
|
||||||
orgId: result.newSiteResource.orgId
|
orgId: newSite.sites.orgId
|
||||||
})),
|
},
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,12 @@ import {
|
|||||||
clientSiteResources,
|
clientSiteResources,
|
||||||
roles,
|
roles,
|
||||||
roleSiteResources,
|
roleSiteResources,
|
||||||
Site,
|
|
||||||
SiteResource,
|
SiteResource,
|
||||||
siteNetworks,
|
|
||||||
siteResources,
|
siteResources,
|
||||||
Transaction,
|
Transaction,
|
||||||
userOrgs,
|
userOrgs,
|
||||||
users,
|
users,
|
||||||
userSiteResources,
|
userSiteResources
|
||||||
networks
|
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { sites } from "@server/db";
|
import { sites } from "@server/db";
|
||||||
import { eq, and, ne, inArray, or } from "drizzle-orm";
|
import { eq, and, ne, inArray, or } from "drizzle-orm";
|
||||||
@@ -22,8 +19,6 @@ import { getNextAvailableAliasAddress } from "../ip";
|
|||||||
export type ClientResourcesResults = {
|
export type ClientResourcesResults = {
|
||||||
newSiteResource: SiteResource;
|
newSiteResource: SiteResource;
|
||||||
oldSiteResource?: SiteResource;
|
oldSiteResource?: SiteResource;
|
||||||
newSites: { siteId: number }[];
|
|
||||||
oldSites: { siteId: number }[];
|
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export async function updateClientResources(
|
export async function updateClientResources(
|
||||||
@@ -48,21 +43,12 @@ export async function updateClientResources(
|
|||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
const existingSiteIds = existingResource?.networkId
|
|
||||||
? await trx
|
|
||||||
.select({ siteId: sites.siteId })
|
|
||||||
.from(siteNetworks)
|
|
||||||
.where(eq(siteNetworks.networkId, existingResource.networkId))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
let allSites: { siteId: number }[] = [];
|
|
||||||
if (resourceData.site) {
|
|
||||||
let siteSingle;
|
|
||||||
const resourceSiteId = resourceData.site;
|
const resourceSiteId = resourceData.site;
|
||||||
|
let site;
|
||||||
|
|
||||||
if (resourceSiteId) {
|
if (resourceSiteId) {
|
||||||
// Look up site by niceId
|
// Look up site by niceId
|
||||||
[siteSingle] = await trx
|
[site] = await trx
|
||||||
.select({ siteId: sites.siteId })
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(
|
||||||
@@ -74,45 +60,20 @@ export async function updateClientResources(
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
} else if (siteId) {
|
} else if (siteId) {
|
||||||
// Use the provided siteId directly, but verify it belongs to the org
|
// Use the provided siteId directly, but verify it belongs to the org
|
||||||
[siteSingle] = await trx
|
[site] = await trx
|
||||||
.select({ siteId: sites.siteId })
|
.select({ siteId: sites.siteId })
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(
|
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
|
||||||
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
|
|
||||||
)
|
|
||||||
.limit(1);
|
.limit(1);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Target site is required`);
|
throw new Error(`Target site is required`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!siteSingle) {
|
if (!site) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Site not found: ${resourceSiteId} in org ${orgId}`
|
`Site not found: ${resourceSiteId} in org ${orgId}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
allSites.push(siteSingle);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resourceData.sites) {
|
|
||||||
for (const siteNiceId of resourceData.sites) {
|
|
||||||
const [site] = await trx
|
|
||||||
.select({ siteId: sites.siteId })
|
|
||||||
.from(sites)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(sites.niceId, siteNiceId),
|
|
||||||
eq(sites.orgId, orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
if (!site) {
|
|
||||||
throw new Error(
|
|
||||||
`Site not found: ${siteId} in org ${orgId}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
allSites.push(site);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingResource) {
|
if (existingResource) {
|
||||||
// Update existing resource
|
// Update existing resource
|
||||||
@@ -120,6 +81,7 @@ export async function updateClientResources(
|
|||||||
.update(siteResources)
|
.update(siteResources)
|
||||||
.set({
|
.set({
|
||||||
name: resourceData.name || resourceNiceId,
|
name: resourceData.name || resourceNiceId,
|
||||||
|
siteId: site.siteId,
|
||||||
mode: resourceData.mode,
|
mode: resourceData.mode,
|
||||||
destination: resourceData.destination,
|
destination: resourceData.destination,
|
||||||
enabled: true, // hardcoded for now
|
enabled: true, // hardcoded for now
|
||||||
@@ -140,21 +102,6 @@ export async function updateClientResources(
|
|||||||
const siteResourceId = existingResource.siteResourceId;
|
const siteResourceId = existingResource.siteResourceId;
|
||||||
const orgId = existingResource.orgId;
|
const orgId = existingResource.orgId;
|
||||||
|
|
||||||
if (updatedResource.networkId) {
|
|
||||||
await trx
|
|
||||||
.delete(siteNetworks)
|
|
||||||
.where(
|
|
||||||
eq(siteNetworks.networkId, updatedResource.networkId)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const site of allSites) {
|
|
||||||
await trx.insert(siteNetworks).values({
|
|
||||||
siteId: site.siteId,
|
|
||||||
networkId: updatedResource.networkId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await trx
|
await trx
|
||||||
.delete(clientSiteResources)
|
.delete(clientSiteResources)
|
||||||
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
||||||
@@ -257,9 +204,7 @@ export async function updateClientResources(
|
|||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
newSiteResource: updatedResource,
|
newSiteResource: updatedResource,
|
||||||
oldSiteResource: existingResource,
|
oldSiteResource: existingResource
|
||||||
newSites: allSites,
|
|
||||||
oldSites: existingSiteIds
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
let aliasAddress: string | null = null;
|
let aliasAddress: string | null = null;
|
||||||
@@ -268,22 +213,13 @@ export async function updateClientResources(
|
|||||||
aliasAddress = await getNextAvailableAliasAddress(orgId);
|
aliasAddress = await getNextAvailableAliasAddress(orgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [network] = await trx
|
|
||||||
.insert(networks)
|
|
||||||
.values({
|
|
||||||
scope: "resource",
|
|
||||||
orgId: orgId
|
|
||||||
})
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
// Create new resource
|
// Create new resource
|
||||||
const [newResource] = await trx
|
const [newResource] = await trx
|
||||||
.insert(siteResources)
|
.insert(siteResources)
|
||||||
.values({
|
.values({
|
||||||
orgId: orgId,
|
orgId: orgId,
|
||||||
|
siteId: site.siteId,
|
||||||
niceId: resourceNiceId,
|
niceId: resourceNiceId,
|
||||||
networkId: network.networkId,
|
|
||||||
defaultNetworkId: network.networkId,
|
|
||||||
name: resourceData.name || resourceNiceId,
|
name: resourceData.name || resourceNiceId,
|
||||||
mode: resourceData.mode,
|
mode: resourceData.mode,
|
||||||
destination: resourceData.destination,
|
destination: resourceData.destination,
|
||||||
@@ -299,13 +235,6 @@ export async function updateClientResources(
|
|||||||
|
|
||||||
const siteResourceId = newResource.siteResourceId;
|
const siteResourceId = newResource.siteResourceId;
|
||||||
|
|
||||||
for (const site of allSites) {
|
|
||||||
await trx.insert(siteNetworks).values({
|
|
||||||
siteId: site.siteId,
|
|
||||||
networkId: network.networkId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [adminRole] = await trx
|
const [adminRole] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(roles)
|
.from(roles)
|
||||||
@@ -395,11 +324,7 @@ export async function updateClientResources(
|
|||||||
`Created new client resource ${newResource.name} (${newResource.siteResourceId}) for org ${orgId}`
|
`Created new client resource ${newResource.name} (${newResource.siteResourceId}) for org ${orgId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
results.push({
|
results.push({ newSiteResource: newResource });
|
||||||
newSiteResource: newResource,
|
|
||||||
newSites: allSites,
|
|
||||||
oldSites: existingSiteIds
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -312,8 +312,7 @@ export const ClientResourceSchema = z
|
|||||||
.object({
|
.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
mode: z.enum(["host", "cidr"]),
|
mode: z.enum(["host", "cidr"]),
|
||||||
site: z.string(), // DEPRECATED IN FAVOR OF sites
|
site: z.string(),
|
||||||
sites: z.array(z.string()).optional().default([]),
|
|
||||||
// protocol: z.enum(["tcp", "udp"]).optional(),
|
// protocol: z.enum(["tcp", "udp"]).optional(),
|
||||||
// proxyPort: z.int().positive().optional(),
|
// proxyPort: z.int().positive().optional(),
|
||||||
// destinationPort: z.int().positive().optional(),
|
// destinationPort: z.int().positive().optional(),
|
||||||
|
|||||||
40
server/lib/sanitize.ts
Normal file
40
server/lib/sanitize.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Sanitize a string field before inserting into a database TEXT column.
|
||||||
|
*
|
||||||
|
* Two passes are applied:
|
||||||
|
*
|
||||||
|
* 1. Lone UTF-16 surrogates – JavaScript strings can hold unpaired surrogates
|
||||||
|
* (e.g. \uD800 without a following \uDC00-\uDFFF codepoint). These are
|
||||||
|
* valid in JS but cannot be encoded as UTF-8, triggering
|
||||||
|
* `report_invalid_encoding` in SQLite / Postgres. They are replaced with
|
||||||
|
* the Unicode replacement character U+FFFD so the data is preserved as a
|
||||||
|
* visible signal that something was malformed.
|
||||||
|
*
|
||||||
|
* 2. Null bytes and C0 control characters – SQLite stores TEXT as
|
||||||
|
* null-terminated C strings, so \x00 in a value causes
|
||||||
|
* `report_invalid_encoding`. Bots and scanners routinely inject null bytes
|
||||||
|
* into URLs (e.g. `/path\u0000.jpg`). All C0 control characters in the
|
||||||
|
* range \x00-\x1F are stripped except for the three that are legitimate in
|
||||||
|
* text payloads: HT (\x09), LF (\x0A), and CR (\x0D). DEL (\x7F) is also
|
||||||
|
* stripped.
|
||||||
|
*/
|
||||||
|
export function sanitizeString(value: string): string;
|
||||||
|
export function sanitizeString(
|
||||||
|
value: string | null | undefined
|
||||||
|
): string | undefined;
|
||||||
|
export function sanitizeString(
|
||||||
|
value: string | null | undefined
|
||||||
|
): string | undefined {
|
||||||
|
if (value == null) return undefined;
|
||||||
|
return (
|
||||||
|
value
|
||||||
|
// Replace lone high surrogates (not followed by a low surrogate)
|
||||||
|
// and lone low surrogates (not preceded by a high surrogate).
|
||||||
|
.replace(
|
||||||
|
/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g,
|
||||||
|
"\uFFFD"
|
||||||
|
)
|
||||||
|
// Strip null bytes, C0 control chars (except HT/LF/CR), and DEL.
|
||||||
|
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "")
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -81,6 +81,7 @@ import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToke
|
|||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
|
||||||
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
|
||||||
|
import { sanitizeString } from "@server/lib/sanitize";
|
||||||
|
|
||||||
// Zod schemas for request validation
|
// Zod schemas for request validation
|
||||||
const getResourceByDomainParamsSchema = z.strictObject({
|
const getResourceByDomainParamsSchema = z.strictObject({
|
||||||
@@ -1859,24 +1860,24 @@ hybridRouter.post(
|
|||||||
})
|
})
|
||||||
.map((logEntry) => ({
|
.map((logEntry) => ({
|
||||||
timestamp: logEntry.timestamp,
|
timestamp: logEntry.timestamp,
|
||||||
orgId: logEntry.orgId,
|
orgId: sanitizeString(logEntry.orgId),
|
||||||
actorType: logEntry.actorType,
|
actorType: sanitizeString(logEntry.actorType),
|
||||||
actor: logEntry.actor,
|
actor: sanitizeString(logEntry.actor),
|
||||||
actorId: logEntry.actorId,
|
actorId: sanitizeString(logEntry.actorId),
|
||||||
metadata: logEntry.metadata,
|
metadata: sanitizeString(logEntry.metadata),
|
||||||
action: logEntry.action,
|
action: logEntry.action,
|
||||||
resourceId: logEntry.resourceId,
|
resourceId: logEntry.resourceId,
|
||||||
reason: logEntry.reason,
|
reason: logEntry.reason,
|
||||||
location: logEntry.location,
|
location: sanitizeString(logEntry.location),
|
||||||
// userAgent: data.userAgent, // TODO: add this
|
// userAgent: data.userAgent, // TODO: add this
|
||||||
// headers: data.body.headers,
|
// headers: data.body.headers,
|
||||||
// query: data.body.query,
|
// query: data.body.query,
|
||||||
originalRequestURL: logEntry.originalRequestURL,
|
originalRequestURL: sanitizeString(logEntry.originalRequestURL) ?? "",
|
||||||
scheme: logEntry.scheme,
|
scheme: sanitizeString(logEntry.scheme) ?? "",
|
||||||
host: logEntry.host,
|
host: sanitizeString(logEntry.host) ?? "",
|
||||||
path: logEntry.path,
|
path: sanitizeString(logEntry.path) ?? "",
|
||||||
method: logEntry.method,
|
method: sanitizeString(logEntry.method) ?? "",
|
||||||
ip: logEntry.ip,
|
ip: sanitizeString(logEntry.ip),
|
||||||
tls: logEntry.tls
|
tls: logEntry.tls
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export const startRemoteExitNodeOfflineChecker = (): void => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Find clients that haven't pinged in the last 2 minutes and mark them as offline
|
// Find clients that haven't pinged in the last 2 minutes and mark them as offline
|
||||||
const newlyOfflineNodes = await db
|
const offlineNodes = await db
|
||||||
.update(exitNodes)
|
.update(exitNodes)
|
||||||
.set({ online: false })
|
.set({ online: false })
|
||||||
.where(
|
.where(
|
||||||
@@ -53,32 +53,15 @@ export const startRemoteExitNodeOfflineChecker = (): void => {
|
|||||||
)
|
)
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
// Update the sites to offline if they have not pinged either
|
if (offlineNodes.length > 0) {
|
||||||
const exitNodeIds = newlyOfflineNodes.map(
|
logger.info(
|
||||||
(node) => node.exitNodeId
|
`checkRemoteExitNodeOffline: Marked ${offlineNodes.length} remoteExitNode client(s) offline due to inactivity`
|
||||||
);
|
);
|
||||||
|
|
||||||
const sitesOnNode = await db
|
for (const offlineClient of offlineNodes) {
|
||||||
.select()
|
logger.debug(
|
||||||
.from(sites)
|
`checkRemoteExitNodeOffline: Client ${offlineClient.exitNodeId} marked offline (lastPing: ${offlineClient.lastPing})`
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(sites.online, true),
|
|
||||||
inArray(sites.exitNodeId, exitNodeIds)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// loop through the sites and process their lastBandwidthUpdate as an iso string and if its more than 1 minute old then mark the site offline
|
|
||||||
for (const site of sitesOnNode) {
|
|
||||||
if (!site.lastBandwidthUpdate) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const lastBandwidthUpdate = new Date(site.lastBandwidthUpdate);
|
|
||||||
if (Date.now() - lastBandwidthUpdate.getTime() > 60 * 1000) {
|
|
||||||
await db
|
|
||||||
.update(sites)
|
|
||||||
.set({ online: false })
|
|
||||||
.where(eq(sites.siteId, site.siteId));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import cache from "#dynamic/lib/cache";
|
|||||||
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
||||||
import { stripPortFromHost } from "@server/lib/ip";
|
import { stripPortFromHost } from "@server/lib/ip";
|
||||||
|
|
||||||
|
import { sanitizeString } from "@server/lib/sanitize";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
Reasons:
|
Reasons:
|
||||||
@@ -253,24 +255,23 @@ export async function logRequestAudit(
|
|||||||
// Add to buffer instead of writing directly to DB
|
// Add to buffer instead of writing directly to DB
|
||||||
auditLogBuffer.push({
|
auditLogBuffer.push({
|
||||||
timestamp,
|
timestamp,
|
||||||
orgId: data.orgId,
|
orgId: sanitizeString(data.orgId),
|
||||||
actorType,
|
actorType: sanitizeString(actorType),
|
||||||
actor,
|
actor: sanitizeString(actor),
|
||||||
actorId,
|
actorId: sanitizeString(actorId),
|
||||||
metadata,
|
metadata: sanitizeString(metadata),
|
||||||
action: data.action,
|
action: data.action,
|
||||||
resourceId: data.resourceId,
|
resourceId: data.resourceId,
|
||||||
reason: data.reason,
|
reason: data.reason,
|
||||||
location: data.location,
|
location: sanitizeString(data.location),
|
||||||
originalRequestURL: body.originalRequestURL,
|
originalRequestURL: sanitizeString(body.originalRequestURL) ?? "",
|
||||||
scheme: body.scheme,
|
scheme: sanitizeString(body.scheme) ?? "",
|
||||||
host: body.host,
|
host: sanitizeString(body.host) ?? "",
|
||||||
path: body.path,
|
path: sanitizeString(body.path) ?? "",
|
||||||
method: body.method,
|
method: sanitizeString(body.method) ?? "",
|
||||||
ip: clientIp,
|
ip: sanitizeString(clientIp),
|
||||||
tls: body.tls
|
tls: body.tls
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flush immediately if buffer is full, otherwise schedule a flush
|
// Flush immediately if buffer is full, otherwise schedule a flush
|
||||||
if (auditLogBuffer.length >= BATCH_SIZE) {
|
if (auditLogBuffer.length >= BATCH_SIZE) {
|
||||||
// Fire and forget - don't block the caller
|
// Fire and forget - don't block the caller
|
||||||
|
|||||||
@@ -135,6 +135,13 @@ authenticated.post(
|
|||||||
logActionAudit(ActionsEnum.updateSite),
|
logActionAudit(ActionsEnum.updateSite),
|
||||||
site.updateSite
|
site.updateSite
|
||||||
);
|
);
|
||||||
|
authenticated.post(
|
||||||
|
"/org/:orgId/reset-bandwidth",
|
||||||
|
verifyApiKeyOrgAccess,
|
||||||
|
verifyApiKeyHasAction(ActionsEnum.resetSiteBandwidth),
|
||||||
|
logActionAudit(ActionsEnum.resetSiteBandwidth),
|
||||||
|
org.resetOrgBandwidth
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
"/site/:siteId",
|
"/site/:siteId",
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import logger from "@server/logger";
|
|||||||
/**
|
/**
|
||||||
* Handles disconnecting messages from sites to show disconnected in the ui
|
* Handles disconnecting messages from sites to show disconnected in the ui
|
||||||
*/
|
*/
|
||||||
export const handleNewtDisconnectingMessage: MessageHandler = async (context) => {
|
export const handleNewtDisconnectingMessage: MessageHandler = async (
|
||||||
|
context
|
||||||
|
) => {
|
||||||
const { message, client: c, sendToClient } = context;
|
const { message, client: c, sendToClient } = context;
|
||||||
const newt = c as Newt;
|
const newt = c as Newt;
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ export const handleNewtDisconnectingMessage: MessageHandler = async (context) =>
|
|||||||
.set({
|
.set({
|
||||||
online: false
|
online: false
|
||||||
})
|
})
|
||||||
.where(eq(sites.siteId, sites.siteId));
|
.where(eq(sites.siteId, newt.siteId));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error handling disconnecting message", { error });
|
logger.error("Error handling disconnecting message", { error });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,3 +8,4 @@ export * from "./getOrgOverview";
|
|||||||
export * from "./listOrgs";
|
export * from "./listOrgs";
|
||||||
export * from "./pickOrgDefaults";
|
export * from "./pickOrgDefaults";
|
||||||
export * from "./checkOrgUserAccess";
|
export * from "./checkOrgUserAccess";
|
||||||
|
export * from "./resetOrgBandwidth";
|
||||||
|
|||||||
83
server/routers/org/resetOrgBandwidth.ts
Normal file
83
server/routers/org/resetOrgBandwidth.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { NextFunction, Request, Response } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { db, sites } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
|
const resetOrgBandwidthParamsSchema = z.strictObject({
|
||||||
|
orgId: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "post",
|
||||||
|
path: "/org/{orgId}/reset-bandwidth",
|
||||||
|
description: "Reset all sites in selected organization bandwidth counters.",
|
||||||
|
tags: [OpenAPITags.Org, OpenAPITags.Site],
|
||||||
|
request: {
|
||||||
|
params: resetOrgBandwidthParamsSchema
|
||||||
|
},
|
||||||
|
responses: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function resetOrgBandwidth(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = resetOrgBandwidthParamsSchema.safeParse(
|
||||||
|
req.params
|
||||||
|
);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
|
const [site] = await db
|
||||||
|
.select({ siteId: sites.siteId })
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.orgId, orgId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`No sites found in org ${orgId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(sites)
|
||||||
|
.set({
|
||||||
|
megabytesIn: 0,
|
||||||
|
megabytesOut: 0
|
||||||
|
})
|
||||||
|
.where(eq(sites.orgId, orgId));
|
||||||
|
|
||||||
|
return response(res, {
|
||||||
|
data: {},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Sites bandwidth reset successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, Site, siteNetworks, siteResources } from "@server/db";
|
import { db, Site, siteResources } from "@server/db";
|
||||||
import { newts, newtSessions, sites } from "@server/db";
|
import { newts, newtSessions, sites } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
@@ -71,24 +71,19 @@ export async function deleteSite(
|
|||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
}
|
}
|
||||||
} else if (site.type == "newt") {
|
} else if (site.type == "newt") {
|
||||||
const networks = await trx
|
// delete all of the site resources on this site
|
||||||
.select({ networkId: siteNetworks.networkId })
|
const siteResourcesOnSite = trx
|
||||||
.from(siteNetworks)
|
.delete(siteResources)
|
||||||
.where(eq(siteNetworks.siteId, siteId));
|
.where(eq(siteResources.siteId, siteId))
|
||||||
|
.returning();
|
||||||
|
|
||||||
// loop through them
|
// loop through them
|
||||||
for (const network of await networks) {
|
for (const removedSiteResource of await siteResourcesOnSite) {
|
||||||
const [siteResource] = await trx
|
|
||||||
.select()
|
|
||||||
.from(siteResources)
|
|
||||||
.where(eq(siteResources.networkId, network.networkId));
|
|
||||||
if (siteResource) {
|
|
||||||
await rebuildClientAssociationsFromSiteResource(
|
await rebuildClientAssociationsFromSiteResource(
|
||||||
siteResource,
|
removedSiteResource,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// get the newt on the site by querying the newt table for siteId
|
// get the newt on the site by querying the newt table for siteId
|
||||||
const [deletedNewt] = await trx
|
const [deletedNewt] = await trx
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import {
|
|||||||
orgs,
|
orgs,
|
||||||
roles,
|
roles,
|
||||||
roleSiteResources,
|
roleSiteResources,
|
||||||
siteNetworks,
|
|
||||||
networks,
|
|
||||||
SiteResource,
|
SiteResource,
|
||||||
siteResources,
|
siteResources,
|
||||||
sites,
|
sites,
|
||||||
@@ -25,7 +23,7 @@ import response from "@server/lib/response";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { and, eq, inArray } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -39,7 +37,7 @@ const createSiteResourceSchema = z
|
|||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
mode: z.enum(["host", "cidr", "port"]),
|
mode: z.enum(["host", "cidr", "port"]),
|
||||||
siteIds: z.array(z.int()),
|
siteId: z.int(),
|
||||||
// protocol: z.enum(["tcp", "udp"]).optional(),
|
// protocol: z.enum(["tcp", "udp"]).optional(),
|
||||||
// proxyPort: z.int().positive().optional(),
|
// proxyPort: z.int().positive().optional(),
|
||||||
// destinationPort: z.int().positive().optional(),
|
// destinationPort: z.int().positive().optional(),
|
||||||
@@ -161,7 +159,7 @@ export async function createSiteResource(
|
|||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
siteIds,
|
siteId,
|
||||||
mode,
|
mode,
|
||||||
// protocol,
|
// protocol,
|
||||||
// proxyPort,
|
// proxyPort,
|
||||||
@@ -180,16 +178,14 @@ export async function createSiteResource(
|
|||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
// Verify the site exists and belongs to the org
|
// Verify the site exists and belongs to the org
|
||||||
const sitesToAssign = await db
|
const [site] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.where(and(inArray(sites.siteId, siteIds), eq(sites.orgId, orgId)))
|
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (sitesToAssign.length !== siteIds.length) {
|
if (!site) {
|
||||||
return next(
|
return next(createHttpError(HttpCode.NOT_FOUND, "Site not found"));
|
||||||
createHttpError(HttpCode.NOT_FOUND, "Some site not found")
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [org] = await db
|
const [org] = await db
|
||||||
@@ -291,29 +287,12 @@ export async function createSiteResource(
|
|||||||
|
|
||||||
let newSiteResource: SiteResource | undefined;
|
let newSiteResource: SiteResource | undefined;
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
const [network] = await trx
|
|
||||||
.insert(networks)
|
|
||||||
.values({
|
|
||||||
scope: "resource",
|
|
||||||
orgId: orgId
|
|
||||||
})
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
if (!network) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
|
||||||
`Failed to create network`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the site resource
|
// Create the site resource
|
||||||
const insertValues: typeof siteResources.$inferInsert = {
|
const insertValues: typeof siteResources.$inferInsert = {
|
||||||
|
siteId,
|
||||||
niceId,
|
niceId,
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
networkId: network.networkId,
|
|
||||||
mode: mode as "host" | "cidr",
|
mode: mode as "host" | "cidr",
|
||||||
destination,
|
destination,
|
||||||
enabled,
|
enabled,
|
||||||
@@ -338,13 +317,6 @@ export async function createSiteResource(
|
|||||||
|
|
||||||
//////////////////// update the associations ////////////////////
|
//////////////////// update the associations ////////////////////
|
||||||
|
|
||||||
for (const siteId of siteIds) {
|
|
||||||
await trx.insert(siteNetworks).values({
|
|
||||||
siteId: siteId,
|
|
||||||
networkId: network.networkId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const [adminRole] = await trx
|
const [adminRole] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(roles)
|
.from(roles)
|
||||||
@@ -387,22 +359,17 @@ export async function createSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const siteToAssign of sitesToAssign) {
|
|
||||||
const [newt] = await trx
|
const [newt] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(newts)
|
.from(newts)
|
||||||
.where(eq(newts.siteId, siteToAssign.siteId))
|
.where(eq(newts.siteId, site.siteId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!newt) {
|
if (!newt) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Newt not found for site ${siteToAssign.siteId}`
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
await rebuildClientAssociationsFromSiteResource(
|
await rebuildClientAssociationsFromSiteResource(
|
||||||
newSiteResource,
|
newSiteResource,
|
||||||
@@ -420,7 +387,7 @@ export async function createSiteResource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Created site resource ${newSiteResource.siteResourceId} for org ${orgId}`
|
`Created site resource ${newSiteResource.siteResourceId} for site ${siteId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -70,18 +70,17 @@ export async function deleteSiteResource(
|
|||||||
.where(and(eq(siteResources.siteResourceId, siteResourceId)))
|
.where(and(eq(siteResources.siteResourceId, siteResourceId)))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
// not sure why this is here...
|
const [newt] = await trx
|
||||||
// const [newt] = await trx
|
.select()
|
||||||
// .select()
|
.from(newts)
|
||||||
// .from(newts)
|
.where(eq(newts.siteId, removedSiteResource.siteId))
|
||||||
// .where(eq(newts.siteId, removedSiteResource.siteId))
|
.limit(1);
|
||||||
// .limit(1);
|
|
||||||
|
|
||||||
// if (!newt) {
|
if (!newt) {
|
||||||
// return next(
|
return next(
|
||||||
// createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
||||||
// );
|
);
|
||||||
// }
|
}
|
||||||
|
|
||||||
await rebuildClientAssociationsFromSiteResource(
|
await rebuildClientAssociationsFromSiteResource(
|
||||||
removedSiteResource,
|
removedSiteResource,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { db, SiteResource, siteNetworks, siteResources, sites } from "@server/db";
|
import { db, SiteResource, siteResources, sites } from "@server/db";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
@@ -73,9 +73,9 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({
|
|||||||
|
|
||||||
export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{
|
export type ListAllSiteResourcesByOrgResponse = PaginatedResponse<{
|
||||||
siteResources: (SiteResource & {
|
siteResources: (SiteResource & {
|
||||||
siteNames: string[];
|
siteName: string;
|
||||||
siteNiceIds: string[];
|
siteNiceId: string;
|
||||||
siteAddresses: (string | null)[];
|
siteAddress: string | null;
|
||||||
})[];
|
})[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
@@ -83,6 +83,7 @@ function querySiteResourcesBase() {
|
|||||||
return db
|
return db
|
||||||
.select({
|
.select({
|
||||||
siteResourceId: siteResources.siteResourceId,
|
siteResourceId: siteResources.siteResourceId,
|
||||||
|
siteId: siteResources.siteId,
|
||||||
orgId: siteResources.orgId,
|
orgId: siteResources.orgId,
|
||||||
niceId: siteResources.niceId,
|
niceId: siteResources.niceId,
|
||||||
name: siteResources.name,
|
name: siteResources.name,
|
||||||
@@ -99,19 +100,14 @@ function querySiteResourcesBase() {
|
|||||||
disableIcmp: siteResources.disableIcmp,
|
disableIcmp: siteResources.disableIcmp,
|
||||||
authDaemonMode: siteResources.authDaemonMode,
|
authDaemonMode: siteResources.authDaemonMode,
|
||||||
authDaemonPort: siteResources.authDaemonPort,
|
authDaemonPort: siteResources.authDaemonPort,
|
||||||
networkId: siteResources.networkId,
|
siteName: sites.name,
|
||||||
defaultNetworkId: siteResources.defaultNetworkId,
|
siteNiceId: sites.niceId,
|
||||||
siteNames: sql<string[]>`array_agg(${sites.name})`,
|
siteAddress: sites.address
|
||||||
siteNiceIds: sql<string[]>`array_agg(${sites.niceId})`,
|
|
||||||
siteAddresses: sql<(string | null)[]>`array_agg(${sites.address})`
|
|
||||||
})
|
})
|
||||||
.from(siteResources)
|
.from(siteResources)
|
||||||
.innerJoin(siteNetworks, eq(siteResources.networkId, siteNetworks.networkId))
|
.innerJoin(sites, eq(siteResources.siteId, sites.siteId));
|
||||||
.innerJoin(sites, eq(siteNetworks.siteId, sites.siteId))
|
|
||||||
.groupBy(siteResources.siteResourceId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: "/org/{orgId}/site-resources",
|
path: "/org/{orgId}/site-resources",
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ import {
|
|||||||
orgs,
|
orgs,
|
||||||
roles,
|
roles,
|
||||||
roleSiteResources,
|
roleSiteResources,
|
||||||
siteNetworks,
|
|
||||||
sites,
|
sites,
|
||||||
networks,
|
|
||||||
Transaction,
|
Transaction,
|
||||||
userSiteResources
|
userSiteResources
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
@@ -18,7 +16,7 @@ import { siteResources, SiteResource } from "@server/db";
|
|||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { eq, and, ne, inArray } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
@@ -44,7 +42,7 @@ const updateSiteResourceParamsSchema = z.strictObject({
|
|||||||
const updateSiteResourceSchema = z
|
const updateSiteResourceSchema = z
|
||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
siteIds: z.array(z.int()),
|
siteId: z.int(),
|
||||||
// niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(),
|
// niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(),
|
||||||
// mode: z.enum(["host", "cidr", "port"]).optional(),
|
// mode: z.enum(["host", "cidr", "port"]).optional(),
|
||||||
mode: z.enum(["host", "cidr"]).optional(),
|
mode: z.enum(["host", "cidr"]).optional(),
|
||||||
@@ -168,7 +166,7 @@ export async function updateSiteResource(
|
|||||||
const { siteResourceId } = parsedParams.data;
|
const { siteResourceId } = parsedParams.data;
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
siteIds, // because it can change
|
siteId, // because it can change
|
||||||
mode,
|
mode,
|
||||||
destination,
|
destination,
|
||||||
alias,
|
alias,
|
||||||
@@ -183,6 +181,16 @@ export async function updateSiteResource(
|
|||||||
authDaemonMode
|
authDaemonMode
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
|
const [site] = await db
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.siteId, siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return next(createHttpError(HttpCode.NOT_FOUND, "Site not found"));
|
||||||
|
}
|
||||||
|
|
||||||
// Check if site resource exists
|
// Check if site resource exists
|
||||||
const [existingSiteResource] = await db
|
const [existingSiteResource] = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -222,24 +230,6 @@ export async function updateSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the site exists and belongs to the org
|
|
||||||
const sitesToAssign = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
inArray(sites.siteId, siteIds),
|
|
||||||
eq(sites.orgId, existingSiteResource.orgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (sitesToAssign.length !== siteIds.length) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.NOT_FOUND, "Some site not found")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only check if destination is an IP address
|
// Only check if destination is an IP address
|
||||||
const isIp = z
|
const isIp = z
|
||||||
.union([z.ipv4(), z.ipv6()])
|
.union([z.ipv4(), z.ipv6()])
|
||||||
@@ -257,24 +247,25 @@ export async function updateSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let sitesChanged = false;
|
let existingSite = site;
|
||||||
const existingSiteIds = existingSiteResource.networkId
|
let siteChanged = false;
|
||||||
? await db
|
if (existingSiteResource.siteId !== siteId) {
|
||||||
|
siteChanged = true;
|
||||||
|
// get the existing site
|
||||||
|
[existingSite] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(siteNetworks)
|
.from(sites)
|
||||||
.where(
|
.where(eq(sites.siteId, existingSiteResource.siteId))
|
||||||
eq(siteNetworks.networkId, existingSiteResource.networkId)
|
.limit(1);
|
||||||
|
|
||||||
|
if (!existingSite) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
"Existing site not found"
|
||||||
)
|
)
|
||||||
: [];
|
);
|
||||||
|
}
|
||||||
const existingSiteIdSet = new Set(existingSiteIds.map((s) => s.siteId));
|
|
||||||
const newSiteIdSet = new Set(siteIds);
|
|
||||||
|
|
||||||
if (
|
|
||||||
existingSiteIdSet.size !== newSiteIdSet.size ||
|
|
||||||
![...existingSiteIdSet].every((id) => newSiteIdSet.has(id))
|
|
||||||
) {
|
|
||||||
sitesChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the alias is unique within the org if provided
|
// make sure the alias is unique within the org if provided
|
||||||
@@ -304,7 +295,7 @@ export async function updateSiteResource(
|
|||||||
let updatedSiteResource: SiteResource | undefined;
|
let updatedSiteResource: SiteResource | undefined;
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
// if the site is changed we need to delete and recreate the resource to avoid complications with the rebuild function otherwise we can just update in place
|
// if the site is changed we need to delete and recreate the resource to avoid complications with the rebuild function otherwise we can just update in place
|
||||||
if (sitesChanged) {
|
if (siteChanged) {
|
||||||
// delete the existing site resource
|
// delete the existing site resource
|
||||||
await trx
|
await trx
|
||||||
.delete(siteResources)
|
.delete(siteResources)
|
||||||
@@ -330,8 +321,7 @@ export async function updateSiteResource(
|
|||||||
|
|
||||||
const sshPamSet =
|
const sshPamSet =
|
||||||
isLicensedSshPam &&
|
isLicensedSshPam &&
|
||||||
(authDaemonPort !== undefined ||
|
(authDaemonPort !== undefined || authDaemonMode !== undefined)
|
||||||
authDaemonMode !== undefined)
|
|
||||||
? {
|
? {
|
||||||
...(authDaemonPort !== undefined && {
|
...(authDaemonPort !== undefined && {
|
||||||
authDaemonPort
|
authDaemonPort
|
||||||
@@ -345,6 +335,7 @@ export async function updateSiteResource(
|
|||||||
.update(siteResources)
|
.update(siteResources)
|
||||||
.set({
|
.set({
|
||||||
name: name,
|
name: name,
|
||||||
|
siteId: siteId,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
@@ -432,8 +423,7 @@ export async function updateSiteResource(
|
|||||||
// Update the site resource
|
// Update the site resource
|
||||||
const sshPamSet =
|
const sshPamSet =
|
||||||
isLicensedSshPam &&
|
isLicensedSshPam &&
|
||||||
(authDaemonPort !== undefined ||
|
(authDaemonPort !== undefined || authDaemonMode !== undefined)
|
||||||
authDaemonMode !== undefined)
|
|
||||||
? {
|
? {
|
||||||
...(authDaemonPort !== undefined && {
|
...(authDaemonPort !== undefined && {
|
||||||
authDaemonPort
|
authDaemonPort
|
||||||
@@ -447,6 +437,7 @@ export async function updateSiteResource(
|
|||||||
.update(siteResources)
|
.update(siteResources)
|
||||||
.set({
|
.set({
|
||||||
name: name,
|
name: name,
|
||||||
|
siteId: siteId,
|
||||||
mode: mode,
|
mode: mode,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
@@ -463,20 +454,6 @@ export async function updateSiteResource(
|
|||||||
|
|
||||||
//////////////////// update the associations ////////////////////
|
//////////////////// update the associations ////////////////////
|
||||||
|
|
||||||
// delete the site - site resources associations
|
|
||||||
await trx
|
|
||||||
.delete(siteNetworks)
|
|
||||||
.where(
|
|
||||||
eq(siteNetworks.networkId, updatedSiteResource.networkId!)
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const siteId of siteIds) {
|
|
||||||
await trx.insert(siteNetworks).values({
|
|
||||||
siteId: siteId,
|
|
||||||
networkId: updatedSiteResource.networkId!
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await trx
|
await trx
|
||||||
.delete(clientSiteResources)
|
.delete(clientSiteResources)
|
||||||
.where(
|
.where(
|
||||||
@@ -547,16 +524,13 @@ export async function updateSiteResource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Updated site resource ${siteResourceId}`
|
`Updated site resource ${siteResourceId} for site ${siteId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
await handleMessagingForUpdatedSiteResource(
|
await handleMessagingForUpdatedSiteResource(
|
||||||
existingSiteResource,
|
existingSiteResource,
|
||||||
updatedSiteResource,
|
updatedSiteResource,
|
||||||
siteIds.map((siteId) => ({
|
{ siteId: site.siteId, orgId: site.orgId },
|
||||||
siteId,
|
|
||||||
orgId: existingSiteResource.orgId
|
|
||||||
})),
|
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -583,7 +557,7 @@ export async function updateSiteResource(
|
|||||||
export async function handleMessagingForUpdatedSiteResource(
|
export async function handleMessagingForUpdatedSiteResource(
|
||||||
existingSiteResource: SiteResource | undefined,
|
existingSiteResource: SiteResource | undefined,
|
||||||
updatedSiteResource: SiteResource,
|
updatedSiteResource: SiteResource,
|
||||||
sites: { siteId: number; orgId: string }[],
|
site: { siteId: number; orgId: string },
|
||||||
trx: Transaction
|
trx: Transaction
|
||||||
) {
|
) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
@@ -620,7 +594,6 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
// if the existingSiteResource is undefined (new resource) we don't need to do anything here, the rebuild above handled it all
|
// if the existingSiteResource is undefined (new resource) we don't need to do anything here, the rebuild above handled it all
|
||||||
|
|
||||||
if (destinationChanged || aliasChanged || portRangesChanged) {
|
if (destinationChanged || aliasChanged || portRangesChanged) {
|
||||||
for (const site of sites) {
|
|
||||||
const [newt] = await trx
|
const [newt] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(newts)
|
.from(newts)
|
||||||
@@ -644,14 +617,10 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
mergedAllClients
|
mergedAllClients
|
||||||
);
|
);
|
||||||
|
|
||||||
await updateTargets(
|
await updateTargets(newt.newtId, {
|
||||||
newt.newtId,
|
|
||||||
{
|
|
||||||
oldTargets: oldTargets,
|
oldTargets: oldTargets,
|
||||||
newTargets: newTargets
|
newTargets: newTargets
|
||||||
},
|
}, newt.version);
|
||||||
newt.version
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const olmJobs: Promise<void>[] = [];
|
const olmJobs: Promise<void>[] = [];
|
||||||
@@ -668,20 +637,13 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
siteResources.siteResourceId
|
siteResources.siteResourceId
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.innerJoin(
|
|
||||||
siteNetworks,
|
|
||||||
eq(
|
|
||||||
siteNetworks.networkId,
|
|
||||||
siteResources.networkId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(
|
eq(
|
||||||
clientSiteResourcesAssociationsCache.clientId,
|
clientSiteResourcesAssociationsCache.clientId,
|
||||||
client.clientId
|
client.clientId
|
||||||
),
|
),
|
||||||
eq(siteNetworks.siteId, site.siteId),
|
eq(siteResources.siteId, site.siteId),
|
||||||
eq(
|
eq(
|
||||||
siteResources.destination,
|
siteResources.destination,
|
||||||
existingSiteResource.destination
|
existingSiteResource.destination
|
||||||
@@ -693,7 +655,6 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
const oldDestinationStillInUseByASite =
|
const oldDestinationStillInUseByASite =
|
||||||
oldDestinationStillInUseSites.length > 0;
|
oldDestinationStillInUseSites.length > 0;
|
||||||
|
|
||||||
@@ -701,11 +662,10 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
olmJobs.push(
|
olmJobs.push(
|
||||||
updatePeerData(
|
updatePeerData(
|
||||||
client.clientId,
|
client.clientId,
|
||||||
site.siteId,
|
updatedSiteResource.siteId,
|
||||||
destinationChanged
|
destinationChanged
|
||||||
? {
|
? {
|
||||||
oldRemoteSubnets:
|
oldRemoteSubnets: !oldDestinationStillInUseByASite
|
||||||
!oldDestinationStillInUseByASite
|
|
||||||
? generateRemoteSubnets([
|
? generateRemoteSubnets([
|
||||||
existingSiteResource
|
existingSiteResource
|
||||||
])
|
])
|
||||||
@@ -731,5 +691,4 @@ export async function handleMessagingForUpdatedSiteResource(
|
|||||||
|
|
||||||
await Promise.all(olmJobs);
|
await Promise.all(olmJobs);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1109,6 +1109,9 @@ export default function Page() {
|
|||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<DomainPicker
|
<DomainPicker
|
||||||
orgId={orgId as string}
|
orgId={orgId as string}
|
||||||
|
warnOnProvidedDomain={
|
||||||
|
remoteExitNodes.length >= 1
|
||||||
|
}
|
||||||
onDomainChange={(res) => {
|
onDomainChange={(res) => {
|
||||||
if (!res) return;
|
if (!res) return;
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ interface DomainPickerProps {
|
|||||||
defaultFullDomain?: string | null;
|
defaultFullDomain?: string | null;
|
||||||
defaultSubdomain?: string | null;
|
defaultSubdomain?: string | null;
|
||||||
defaultDomainId?: string | null;
|
defaultDomainId?: string | null;
|
||||||
|
warnOnProvidedDomain?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DomainPicker({
|
export default function DomainPicker({
|
||||||
@@ -88,7 +89,8 @@ export default function DomainPicker({
|
|||||||
hideFreeDomain = false,
|
hideFreeDomain = false,
|
||||||
defaultSubdomain,
|
defaultSubdomain,
|
||||||
defaultFullDomain,
|
defaultFullDomain,
|
||||||
defaultDomainId
|
defaultDomainId,
|
||||||
|
warnOnProvidedDomain = false
|
||||||
}: DomainPickerProps) {
|
}: DomainPickerProps) {
|
||||||
const { env } = useEnvContext();
|
const { env } = useEnvContext();
|
||||||
const api = createApiClient({ env });
|
const api = createApiClient({ env });
|
||||||
@@ -689,6 +691,14 @@ export default function DomainPicker({
|
|||||||
|
|
||||||
{showProvidedDomainSearch && (
|
{showProvidedDomainSearch && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
{warnOnProvidedDomain && (
|
||||||
|
<Alert variant="warning">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
{t("domainPickerRemoteExitNodeWarning")}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
{isChecking && (
|
{isChecking && (
|
||||||
<div className="flex items-center justify-center p-8">
|
<div className="flex items-center justify-center p-8">
|
||||||
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionGetOrg")]: "getOrg",
|
[t("actionGetOrg")]: "getOrg",
|
||||||
[t("actionUpdateOrg")]: "updateOrg",
|
[t("actionUpdateOrg")]: "updateOrg",
|
||||||
[t("actionGetOrgUser")]: "getOrgUser",
|
[t("actionGetOrgUser")]: "getOrgUser",
|
||||||
|
[t("actionResetSiteBandwidth")]: "resetSiteBandwidth",
|
||||||
[t("actionInviteUser")]: "inviteUser",
|
[t("actionInviteUser")]: "inviteUser",
|
||||||
[t("actionRemoveInvitation")]: "removeInvitation",
|
[t("actionRemoveInvitation")]: "removeInvitation",
|
||||||
[t("actionListInvitations")]: "listInvitations",
|
[t("actionListInvitations")]: "listInvitations",
|
||||||
|
|||||||
Reference in New Issue
Block a user