mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-20 20:06:39 +00:00
Compare commits
179 Commits
new-pricin
...
1.15.4-s.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9eacefb155 | ||
|
|
843b13ed57 | ||
|
|
be89e5ca55 | ||
|
|
333625f199 | ||
|
|
dbfd715381 | ||
|
|
f1d989964e | ||
|
|
b701629498 | ||
|
|
8250946325 | ||
|
|
71f63d8e6f | ||
|
|
dd5e834db0 | ||
|
|
970ecb52f0 | ||
|
|
62ea1b40e1 | ||
|
|
3b0fd5c592 | ||
|
|
b7616026dd | ||
|
|
16ad60b89a | ||
|
|
db7971d2f7 | ||
|
|
f3f8bd3125 | ||
|
|
516fd0ee8f | ||
|
|
8d6700d493 | ||
|
|
9d4ace9b3e | ||
|
|
2800655e33 | ||
|
|
91eecee11d | ||
|
|
899e5aa395 | ||
|
|
d5820c4902 | ||
|
|
a91c002274 | ||
|
|
4d142b93dd | ||
|
|
04dcf57ff3 | ||
|
|
975550c755 | ||
|
|
a964a80d85 | ||
|
|
22c3b8f116 | ||
|
|
c4b1831cfe | ||
|
|
cdb6813384 | ||
|
|
b14b68d83c | ||
|
|
3c2f930e6b | ||
|
|
ca9c7ce555 | ||
|
|
c2e95a0607 | ||
|
|
2767ee9e80 | ||
|
|
d998a8087f | ||
|
|
fdce016921 | ||
|
|
c73d70933b | ||
|
|
e9d0ad6e37 | ||
|
|
a35586f762 | ||
|
|
f527c30923 | ||
|
|
94e70219cf | ||
|
|
6496763aae | ||
|
|
a409ec269b | ||
|
|
bc7bc8da66 | ||
|
|
52484c774e | ||
|
|
69ecc22318 | ||
|
|
bff9d33ee6 | ||
|
|
edf506953b | ||
|
|
5e11746549 | ||
|
|
1ae315e303 | ||
|
|
758b03ab25 | ||
|
|
e756fad573 | ||
|
|
3547450b03 | ||
|
|
733f6692c6 | ||
|
|
2d83160b16 | ||
|
|
256fa880dd | ||
|
|
b08c5f5c67 | ||
|
|
d0862a2d26 | ||
|
|
e97340ed52 | ||
|
|
e27c81eea6 | ||
|
|
7f7f3d43b2 | ||
|
|
4b1b772098 | ||
|
|
f66b88490f | ||
|
|
18f9157169 | ||
|
|
6eb82a807b | ||
|
|
bf57a97833 | ||
|
|
e9e2093220 | ||
|
|
c3540da2e3 | ||
|
|
d228cf56dd | ||
|
|
8f4cecd963 | ||
|
|
66adff44bb | ||
|
|
be41c094dc | ||
|
|
273848ca18 | ||
|
|
1e9dbead3b | ||
|
|
aeaa8ba133 | ||
|
|
24654af635 | ||
|
|
e88a21d6db | ||
|
|
bcd01badaf | ||
|
|
8e063506e0 | ||
|
|
84f5d6137a | ||
|
|
0a8565f5e8 | ||
|
|
bd8da25a46 | ||
|
|
a841f588dd | ||
|
|
75a4362ce3 | ||
|
|
e763e001e5 | ||
|
|
69475a0ae7 | ||
|
|
53e14c2ad7 | ||
|
|
1edc33148a | ||
|
|
a4cbfc74e4 | ||
|
|
c0d25aeb02 | ||
|
|
40f49bf6da | ||
|
|
0bfce87dc6 | ||
|
|
2a0655e9de | ||
|
|
a86cfa5934 | ||
|
|
54b77523c5 | ||
|
|
ba06c8928d | ||
|
|
c8a4ac1ed4 | ||
|
|
143acbae48 | ||
|
|
937f6fdae8 | ||
|
|
ba7239ac08 | ||
|
|
2e748274c0 | ||
|
|
eab2750953 | ||
|
|
17b6cb0c73 | ||
|
|
98a4c453c1 | ||
|
|
6475dceab9 | ||
|
|
040a945774 | ||
|
|
47743a5fa8 | ||
|
|
d47d6de985 | ||
|
|
37818b8594 | ||
|
|
3b184acddd | ||
|
|
9c80404d17 | ||
|
|
aaa7082f9d | ||
|
|
a45b45b2ce | ||
|
|
e4bfbd267e | ||
|
|
65b4dcc672 | ||
|
|
36fc30b524 | ||
|
|
e724ed9137 | ||
|
|
7ca992af05 | ||
|
|
37f1c714ac | ||
|
|
397a43fb60 | ||
|
|
45e0a648c6 | ||
|
|
7336aa81d9 | ||
|
|
d727c10d98 | ||
|
|
321d77a317 | ||
|
|
19b8a6b737 | ||
|
|
f2e69dfb96 | ||
|
|
8207e49317 | ||
|
|
b75600b9ea | ||
|
|
7b01f1bef6 | ||
|
|
e7bd2c0001 | ||
|
|
a26076e9db | ||
|
|
9711a0fb8e | ||
|
|
accc670411 | ||
|
|
071c41a54f | ||
|
|
35ba6c19c3 | ||
|
|
14c8348166 | ||
|
|
7d6ee72025 | ||
|
|
ea0e770b57 | ||
|
|
193b7ff21e | ||
|
|
d814ad9f3e | ||
|
|
da8b620c75 | ||
|
|
911b5e6814 | ||
|
|
f991fd9c71 | ||
|
|
652e4c922d | ||
|
|
4364e3fbc1 | ||
|
|
a783fdecbc | ||
|
|
16f67455a2 | ||
|
|
0850a28d20 | ||
|
|
5ca598139e | ||
|
|
df1bf09163 | ||
|
|
50bc8d3e9c | ||
|
|
86d089024e | ||
|
|
d5c1cf594d | ||
|
|
a0b5731e69 | ||
|
|
ceb359d614 | ||
|
|
a49a9f8e3b | ||
|
|
766606b08d | ||
|
|
fed56c1959 | ||
|
|
ae6ed8ad97 | ||
|
|
c1ca0b8e2c | ||
|
|
569dc735ce | ||
|
|
dd11c2c871 | ||
|
|
8def4a2b68 | ||
|
|
13a5f24b07 | ||
|
|
0989d6353e | ||
|
|
4139a7b73f | ||
|
|
be60d66ce3 | ||
|
|
0a33043874 | ||
|
|
96d1d983e5 | ||
|
|
7ffb260d7c | ||
|
|
b4c01349d1 | ||
|
|
e4d4c62833 | ||
|
|
20ae903d7f | ||
|
|
b0566d3c6f | ||
|
|
5dda8c384f | ||
|
|
cb569ff14d |
@@ -31,4 +31,5 @@ dist
|
|||||||
migrations/
|
migrations/
|
||||||
config/
|
config/
|
||||||
build.ts
|
build.ts
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
|
migrations/
|
||||||
|
|||||||
158
.github/workflows/cicd.yml
vendored
158
.github/workflows/cicd.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: CI/CD Pipeline
|
name: Public CICD Pipeline
|
||||||
|
|
||||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
||||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
||||||
@@ -440,6 +440,10 @@ jobs:
|
|||||||
issuer="https://token.actions.githubusercontent.com"
|
issuer="https://token.actions.githubusercontent.com"
|
||||||
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
|
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
|
||||||
|
|
||||||
|
# Track failures
|
||||||
|
FAILED_TAGS=()
|
||||||
|
SUCCESSFUL_TAGS=()
|
||||||
|
|
||||||
# Determine if this is an RC release
|
# Determine if this is an RC release
|
||||||
IS_RC="false"
|
IS_RC="false"
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
if [[ "$TAG" == *"-rc."* ]]; then
|
||||||
@@ -471,94 +475,92 @@ jobs:
|
|||||||
for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
||||||
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
|
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
|
||||||
echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}"
|
echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||||
|
TAG_FAILED=false
|
||||||
|
|
||||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
|
# Wrap the entire tag processing in error handling
|
||||||
REF="${BASE_IMAGE}@${DIGEST}"
|
(
|
||||||
echo "Resolved digest: ${REF}"
|
set -e
|
||||||
|
DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
|
||||||
|
REF="${BASE_IMAGE}@${DIGEST}"
|
||||||
|
echo "Resolved digest: ${REF}"
|
||||||
|
|
||||||
echo "==> cosign sign (keyless) --recursive ${REF}"
|
echo "==> cosign sign (keyless) --recursive ${REF}"
|
||||||
cosign sign --recursive "${REF}"
|
cosign sign --recursive "${REF}"
|
||||||
|
|
||||||
echo "==> cosign sign (key) --recursive ${REF}"
|
echo "==> cosign sign (key) --recursive ${REF}"
|
||||||
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
||||||
|
|
||||||
# Retry wrapper for verification to handle registry propagation delays
|
# Retry wrapper for verification to handle registry propagation delays
|
||||||
retry_verify() {
|
retry_verify() {
|
||||||
local cmd="$1"
|
local cmd="$1"
|
||||||
local attempts=6
|
local attempts=6
|
||||||
local delay=5
|
local delay=5
|
||||||
local i=1
|
local i=1
|
||||||
until eval "$cmd"; do
|
until eval "$cmd"; do
|
||||||
if [ $i -ge $attempts ]; then
|
if [ $i -ge $attempts ]; then
|
||||||
echo "Verification failed after $attempts attempts"
|
echo "Verification failed after $attempts attempts"
|
||||||
return 1
|
return 1
|
||||||
fi
|
|
||||||
echo "Verification not yet available. Retry $i/$attempts after ${delay}s..."
|
|
||||||
sleep $delay
|
|
||||||
i=$((i+1))
|
|
||||||
delay=$((delay*2))
|
|
||||||
# Cap the delay to avoid very long waits
|
|
||||||
if [ $delay -gt 60 ]; then delay=60; fi
|
|
||||||
done
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "==> cosign verify (public key) ${REF}"
|
|
||||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then
|
|
||||||
VERIFIED_INDEX=true
|
|
||||||
else
|
|
||||||
VERIFIED_INDEX=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "==> cosign verify (keyless policy) ${REF}"
|
|
||||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then
|
|
||||||
VERIFIED_INDEX_KEYLESS=true
|
|
||||||
else
|
|
||||||
VERIFIED_INDEX_KEYLESS=false
|
|
||||||
fi
|
|
||||||
|
|
||||||
# If index verification fails, attempt to verify child platform manifests
|
|
||||||
if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then
|
|
||||||
echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}"
|
|
||||||
CHILD_VERIFIED=false
|
|
||||||
|
|
||||||
for ARCH in arm64 amd64; do
|
|
||||||
CHILD_TAG="${IMAGE_TAG}-${ARCH}"
|
|
||||||
echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}"
|
|
||||||
CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)"
|
|
||||||
if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then
|
|
||||||
CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}"
|
|
||||||
echo "==> cosign verify (public key) child ${CHILD_REF}"
|
|
||||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then
|
|
||||||
CHILD_VERIFIED=true
|
|
||||||
echo "Public key verification succeeded for child ${CHILD_REF}"
|
|
||||||
else
|
|
||||||
echo "Public key verification failed for child ${CHILD_REF}"
|
|
||||||
fi
|
fi
|
||||||
|
echo "Verification not yet available. Retry $i/$attempts after ${delay}s..."
|
||||||
|
sleep $delay
|
||||||
|
i=$((i+1))
|
||||||
|
delay=$((delay*2))
|
||||||
|
# Cap the delay to avoid very long waits
|
||||||
|
if [ $delay -gt 60 ]; then delay=60; fi
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
echo "==> cosign verify (keyless policy) child ${CHILD_REF}"
|
echo "==> cosign verify (public key) ${REF}"
|
||||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then
|
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then
|
||||||
CHILD_VERIFIED=true
|
VERIFIED_INDEX=true
|
||||||
echo "Keyless verification succeeded for child ${CHILD_REF}"
|
else
|
||||||
else
|
VERIFIED_INDEX=false
|
||||||
echo "Keyless verification failed for child ${CHILD_REF}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "${CHILD_VERIFIED}" != "true" ]; then
|
|
||||||
echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}"
|
|
||||||
exit 10
|
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
echo "==> cosign verify (keyless policy) ${REF}"
|
||||||
|
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then
|
||||||
|
VERIFIED_INDEX_KEYLESS=true
|
||||||
|
else
|
||||||
|
VERIFIED_INDEX_KEYLESS=false
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if verification succeeded
|
||||||
|
if [ "${VERIFIED_INDEX}" != "true" ] && [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then
|
||||||
|
echo "⚠️ WARNING: Verification not available for ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||||
|
echo "This may be due to registry propagation delays. Continuing anyway."
|
||||||
|
fi
|
||||||
|
) || TAG_FAILED=true
|
||||||
|
|
||||||
|
if [ "$TAG_FAILED" = "true" ]; then
|
||||||
|
echo "⚠️ WARNING: Failed to sign/verify ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||||
|
FAILED_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}")
|
||||||
|
else
|
||||||
|
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||||
|
SUCCESSFUL_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}")
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "All images signed and verified successfully!"
|
# Report summary
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Sign and Verify Summary"
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Successful: ${#SUCCESSFUL_TAGS[@]}"
|
||||||
|
echo "Failed: ${#FAILED_TAGS[@]}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ${#FAILED_TAGS[@]} -gt 0 ]; then
|
||||||
|
echo "Failed tags:"
|
||||||
|
for tag in "${FAILED_TAGS[@]}"; do
|
||||||
|
echo " - $tag"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ WARNING: Some tags failed to sign/verify, but continuing anyway"
|
||||||
|
else
|
||||||
|
echo "✓ All images signed and verified successfully!"
|
||||||
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
post-run:
|
post-run:
|
||||||
|
|||||||
426
.github/workflows/cicd.yml.backup
vendored
426
.github/workflows/cicd.yml.backup
vendored
@@ -1,426 +0,0 @@
|
|||||||
name: CI/CD Pipeline
|
|
||||||
|
|
||||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
|
||||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # for GHCR push
|
|
||||||
id-token: write # for Cosign Keyless (OIDC) Signing
|
|
||||||
|
|
||||||
# Required secrets:
|
|
||||||
# - DOCKER_HUB_USERNAME / DOCKER_HUB_ACCESS_TOKEN: push to Docker Hub
|
|
||||||
# - GITHUB_TOKEN: used for GHCR login and OIDC keyless signing
|
|
||||||
# - COSIGN_PRIVATE_KEY / COSIGN_PASSWORD / COSIGN_PUBLIC_KEY: for key-based signing
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+"
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pre-run:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions: write-all
|
|
||||||
steps:
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
|
||||||
with:
|
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
|
||||||
role-duration-seconds: 3600
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
|
|
||||||
- name: Verify AWS identity
|
|
||||||
run: aws sts get-caller-identity
|
|
||||||
|
|
||||||
- name: Start EC2 instances
|
|
||||||
run: |
|
|
||||||
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
|
|
||||||
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
|
|
||||||
echo "EC2 instances started"
|
|
||||||
|
|
||||||
|
|
||||||
release-arm:
|
|
||||||
name: Build and Release (ARM64)
|
|
||||||
runs-on: [self-hosted, linux, arm64, us-east-1]
|
|
||||||
needs: [pre-run]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.pre-run.result == 'success'
|
|
||||||
}}
|
|
||||||
# Job-level timeout to avoid runaway or stuck runs
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Target images
|
|
||||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Monitor storage space
|
|
||||||
run: |
|
|
||||||
THRESHOLD=75
|
|
||||||
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
|
|
||||||
echo "Used space: $USED_SPACE%"
|
|
||||||
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
|
|
||||||
echo "Used space is below the threshold of 75% free. Running Docker system prune."
|
|
||||||
echo y | docker system prune -a
|
|
||||||
else
|
|
||||||
echo "Storage space is above the threshold. No action needed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Update version in package.json
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
|
||||||
cat server/lib/consts.ts
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check if release candidate
|
|
||||||
id: check-rc
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
|
||||||
echo "IS_RC=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "IS_RC=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build and push Docker images (Docker Hub - ARM64)
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
|
||||||
make build-rc-arm tag=$TAG
|
|
||||||
else
|
|
||||||
make build-release-arm tag=$TAG
|
|
||||||
fi
|
|
||||||
echo "Built & pushed ARM64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
release-amd:
|
|
||||||
name: Build and Release (AMD64)
|
|
||||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
|
||||||
needs: [pre-run]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.pre-run.result == 'success'
|
|
||||||
}}
|
|
||||||
# Job-level timeout to avoid runaway or stuck runs
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Target images
|
|
||||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Monitor storage space
|
|
||||||
run: |
|
|
||||||
THRESHOLD=75
|
|
||||||
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
|
|
||||||
echo "Used space: $USED_SPACE%"
|
|
||||||
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
|
|
||||||
echo "Used space is below the threshold of 75% free. Running Docker system prune."
|
|
||||||
echo y | docker system prune -a
|
|
||||||
else
|
|
||||||
echo "Storage space is above the threshold. No action needed."
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Update version in package.json
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
|
||||||
cat server/lib/consts.ts
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check if release candidate
|
|
||||||
id: check-rc
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
|
||||||
echo "IS_RC=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "IS_RC=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build and push Docker images (Docker Hub - AMD64)
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
|
||||||
make build-rc-amd tag=$TAG
|
|
||||||
else
|
|
||||||
make build-release-amd tag=$TAG
|
|
||||||
fi
|
|
||||||
echo "Built & pushed AMD64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
create-manifest:
|
|
||||||
name: Create Multi-Arch Manifests
|
|
||||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
|
||||||
needs: [release-arm, release-amd]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.release-arm.result == 'success' &&
|
|
||||||
needs.release-amd.result == 'success'
|
|
||||||
}}
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: docker.io
|
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Check if release candidate
|
|
||||||
id: check-rc
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
|
||||||
echo "IS_RC=true" >> $GITHUB_ENV
|
|
||||||
else
|
|
||||||
echo "IS_RC=false" >> $GITHUB_ENV
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Create multi-arch manifests
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
|
||||||
make create-manifests-rc tag=$TAG
|
|
||||||
else
|
|
||||||
make create-manifests tag=$TAG
|
|
||||||
fi
|
|
||||||
echo "Created multi-arch manifests for tag: ${TAG}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
sign-and-package:
|
|
||||||
name: Sign and Package
|
|
||||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
|
||||||
needs: [release-arm, release-amd, create-manifest]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
needs.release-arm.result == 'success' &&
|
|
||||||
needs.release-amd.result == 'success' &&
|
|
||||||
needs.create-manifest.result == 'success'
|
|
||||||
}}
|
|
||||||
# Job-level timeout to avoid runaway or stuck runs
|
|
||||||
timeout-minutes: 120
|
|
||||||
env:
|
|
||||||
# Target images
|
|
||||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
|
||||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
|
||||||
|
|
||||||
- name: Extract tag name
|
|
||||||
id: get-tag
|
|
||||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install Go
|
|
||||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
|
||||||
with:
|
|
||||||
go-version: 1.24
|
|
||||||
|
|
||||||
- name: Update version in package.json
|
|
||||||
run: |
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
|
||||||
cat server/lib/consts.ts
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Pull latest Gerbil version
|
|
||||||
id: get-gerbil-tag
|
|
||||||
run: |
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name')
|
|
||||||
echo "LATEST_GERBIL_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Pull latest Badger version
|
|
||||||
id: get-badger-tag
|
|
||||||
run: |
|
|
||||||
LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name')
|
|
||||||
echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Update install/main.go
|
|
||||||
run: |
|
|
||||||
PANGOLIN_VERSION=${{ env.TAG }}
|
|
||||||
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }}
|
|
||||||
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
|
|
||||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go
|
|
||||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go
|
|
||||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go
|
|
||||||
echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION"
|
|
||||||
cat install/main.go
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build installer
|
|
||||||
working-directory: install
|
|
||||||
run: |
|
|
||||||
make go-build-release
|
|
||||||
|
|
||||||
- name: Upload artifacts from /install/bin
|
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
||||||
with:
|
|
||||||
name: install-bin
|
|
||||||
path: install/bin/
|
|
||||||
|
|
||||||
- name: Install skopeo + jq
|
|
||||||
# skopeo: copy/inspect images between registries
|
|
||||||
# jq: JSON parsing tool used to extract digest values
|
|
||||||
run: |
|
|
||||||
sudo apt-get update -y
|
|
||||||
sudo apt-get install -y skopeo jq
|
|
||||||
skopeo --version
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Login to GHCR
|
|
||||||
env:
|
|
||||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
|
||||||
run: |
|
|
||||||
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
|
|
||||||
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Copy tag from Docker Hub to GHCR
|
|
||||||
# Mirror the already-built image (all architectures) to GHCR so we can sign it
|
|
||||||
# Wait a bit for both architectures to be available in Docker Hub manifest
|
|
||||||
env:
|
|
||||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
TAG=${{ env.TAG }}
|
|
||||||
echo "Waiting for multi-arch manifest to be ready..."
|
|
||||||
sleep 30
|
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
|
|
||||||
skopeo copy --all --retry-times 3 \
|
|
||||||
docker://$DOCKERHUB_IMAGE:$TAG \
|
|
||||||
docker://$GHCR_IMAGE:$TAG
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry (for cosign)
|
|
||||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.actor }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Install cosign
|
|
||||||
# cosign is used to sign and verify container images (key and keyless)
|
|
||||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
|
||||||
|
|
||||||
- name: Dual-sign and verify (GHCR & Docker Hub)
|
|
||||||
# Sign each image by digest using keyless (OIDC) and key-based signing,
|
|
||||||
# then verify both the public key signature and the keyless OIDC signature.
|
|
||||||
env:
|
|
||||||
TAG: ${{ env.TAG }}
|
|
||||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
|
||||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
|
||||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
|
|
||||||
COSIGN_YES: "true"
|
|
||||||
run: |
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
issuer="https://token.actions.githubusercontent.com"
|
|
||||||
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
|
|
||||||
|
|
||||||
for IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
|
||||||
echo "Processing ${IMAGE}:${TAG}"
|
|
||||||
|
|
||||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${IMAGE}:${TAG} | jq -r '.Digest')"
|
|
||||||
REF="${IMAGE}@${DIGEST}"
|
|
||||||
echo "Resolved digest: ${REF}"
|
|
||||||
|
|
||||||
echo "==> cosign sign (keyless) --recursive ${REF}"
|
|
||||||
cosign sign --recursive "${REF}"
|
|
||||||
|
|
||||||
echo "==> cosign sign (key) --recursive ${REF}"
|
|
||||||
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
|
||||||
|
|
||||||
echo "==> cosign verify (public key) ${REF}"
|
|
||||||
cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text
|
|
||||||
|
|
||||||
echo "==> cosign verify (keyless policy) ${REF}"
|
|
||||||
cosign verify \
|
|
||||||
--certificate-oidc-issuer "${issuer}" \
|
|
||||||
--certificate-identity-regexp "${id_regex}" \
|
|
||||||
"${REF}" -o text
|
|
||||||
done
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
post-run:
|
|
||||||
needs: [pre-run, release-arm, release-amd, create-manifest, sign-and-package]
|
|
||||||
if: >-
|
|
||||||
${{
|
|
||||||
always() &&
|
|
||||||
needs.pre-run.result == 'success' &&
|
|
||||||
(needs.release-arm.result == 'success' || needs.release-arm.result == 'skipped' || needs.release-arm.result == 'failure') &&
|
|
||||||
(needs.release-amd.result == 'success' || needs.release-amd.result == 'skipped' || needs.release-amd.result == 'failure') &&
|
|
||||||
(needs.create-manifest.result == 'success' || needs.create-manifest.result == 'skipped' || needs.create-manifest.result == 'failure') &&
|
|
||||||
(needs.sign-and-package.result == 'success' || needs.sign-and-package.result == 'skipped' || needs.sign-and-package.result == 'failure')
|
|
||||||
}}
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions: write-all
|
|
||||||
steps:
|
|
||||||
- name: Configure AWS credentials
|
|
||||||
uses: aws-actions/configure-aws-credentials@v2
|
|
||||||
with:
|
|
||||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
|
||||||
role-duration-seconds: 3600
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
|
|
||||||
- name: Verify AWS identity
|
|
||||||
run: aws sts get-caller-identity
|
|
||||||
|
|
||||||
- name: Stop EC2 instances
|
|
||||||
run: |
|
|
||||||
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
|
|
||||||
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
|
|
||||||
echo "EC2 instances stopped"
|
|
||||||
2
.github/workflows/saas.yml
vendored
2
.github/workflows/saas.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
name: CI/CD Pipeline
|
name: SAAS Pipeline
|
||||||
|
|
||||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
||||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
run: npm run db:generate
|
run: npm run db:generate
|
||||||
|
|
||||||
- name: Apply database migrations
|
- name: Apply database migrations
|
||||||
run: npm run db:sqlite:push
|
run: npm run db:push
|
||||||
|
|
||||||
- name: Test with tsc
|
- name: Test with tsc
|
||||||
run: npx tsc --noEmit
|
run: npx tsc --noEmit
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -51,4 +51,5 @@ dynamic/
|
|||||||
scratch/
|
scratch/
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
hydrateSaas.ts
|
hydrateSaas.ts
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
|
drizzle.config.ts
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const schema = [path.join("server", "db", "pg", "schema")];
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
dialect: "postgresql",
|
|
||||||
schema: schema,
|
|
||||||
out: path.join("server", "migrations"),
|
|
||||||
verbose: true,
|
|
||||||
dbCredentials: {
|
|
||||||
url: process.env.DATABASE_URL as string
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Вие сте част от {count, plural, =0 {нула организации} one {една организация} other {# организации}}.",
|
"componentsMember": "Вие сте част от {count, plural, =0 {нула организации} one {една организация} other {# организации}}.",
|
||||||
"componentsInvalidKey": "Засечен е невалиден или изтекъл лиценз. Проверете лицензионните условия, за да се възползвате от всички функционалности.",
|
"componentsInvalidKey": "Засечен е невалиден или изтекъл лиценз. Проверете лицензионните условия, за да се възползвате от всички функционалности.",
|
||||||
"dismiss": "Отхвърляне",
|
"dismiss": "Отхвърляне",
|
||||||
|
"subscriptionViolationMessage": "Превишихте ограничението на текущия си план. Коригирайте проблема, като премахнете сайтове, потребители или други ресурси, за да оставате в рамките на плана си.",
|
||||||
|
"subscriptionViolationViewBilling": "Преглед на фактурирането",
|
||||||
"componentsLicenseViolation": "Нарушение на лиценза: Сървърът използва {usedSites} сайта, което надвишава лицензионния лимит от {maxSites} сайта. Проверете лицензионните условия, за да се възползвате от всички функционалности.",
|
"componentsLicenseViolation": "Нарушение на лиценза: Сървърът използва {usedSites} сайта, което надвишава лицензионния лимит от {maxSites} сайта. Проверете лицензионните условия, за да се възползвате от всички функционалности.",
|
||||||
"componentsSupporterMessage": "Благодарим ви, че подкрепяте Pangolin като {tier}!",
|
"componentsSupporterMessage": "Благодарим ви, че подкрепяте Pangolin като {tier}!",
|
||||||
"inviteErrorNotValid": "Съжаляваме, но изглежда, че поканата, до която се опитвате да получите достъп, не е приета или вече не е валидна.",
|
"inviteErrorNotValid": "Съжаляваме, но изглежда, че поканата, до която се опитвате да получите достъп, не е приета или вече не е валидна.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Увеличаване на броя на сайтовете",
|
"sitestCountIncrease": "Увеличаване на броя на сайтовете",
|
||||||
"idpManage": "Управление на доставчици на идентичност",
|
"idpManage": "Управление на доставчици на идентичност",
|
||||||
"idpManageDescription": "Прегледайте и управлявайте доставчици на идентичност в системата",
|
"idpManageDescription": "Прегледайте и управлявайте доставчици на идентичност в системата",
|
||||||
|
"idpGlobalModeBanner": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в <adminPanelLink>администраторския панел</adminPanelLink>. За да активирате IdPs за всяка организация, редактирайте конфигурацията на сървъра и задайте режима на IdP към org. <configDocsLink>Вижте документацията</configDocsLink>. Ако желаете да продължите да използвате глобалните IdPs и да премахнете това от настройките на организацията, изрично задайте режима на global в конфигурацията.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в <adminPanelLink>администраторския панел</adminPanelLink>. За да използвате доставчици на идентичност за всяка организация, трябва да надстроите до изданието Enterprise.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в <adminPanelLink>администраторския панел</adminPanelLink>. За да използвате доставчици на идентичност за всяка организация, е необходим лиценз за изданието Enterprise.",
|
||||||
"idpDeletedDescription": "Доставчик на идентичност успешно изтрит",
|
"idpDeletedDescription": "Доставчик на идентичност успешно изтрит",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Сигурни ли сте, че искате да изтриете доставчика за идентичност?",
|
"idpQuestionRemove": "Сигурни ли сте, че искате да изтриете доставчика за идентичност?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Преглед на лимитите за използване",
|
"billingUsageLimitsOverview": "Преглед на лимитите за използване",
|
||||||
"billingMonitorUsage": "Следете своята употреба спрямо конфигурираните лимити. Ако имате нужда от увеличаване на лимитите, моля свържете се с нас support@pangolin.net.",
|
"billingMonitorUsage": "Следете своята употреба спрямо конфигурираните лимити. Ако имате нужда от увеличаване на лимитите, моля свържете се с нас support@pangolin.net.",
|
||||||
"billingDataUsage": "Използване на данни",
|
"billingDataUsage": "Използване на данни",
|
||||||
"billingOnlineTime": "Време на работа на сайта",
|
"billingSites": "Сайтове",
|
||||||
"billingUsers": "Активни потребители",
|
"billingUsers": "Потребители",
|
||||||
"billingDomains": "Активни домейни",
|
"billingDomains": "Домейни",
|
||||||
"billingRemoteExitNodes": "Активни самостоятелно хоствани възли",
|
"billingRemoteExitNodes": "Дистанционни възли",
|
||||||
"billingNoLimitConfigured": "Няма конфигуриран лимит",
|
"billingNoLimitConfigured": "Няма конфигуриран лимит",
|
||||||
"billingEstimatedPeriod": "Очакван период на фактуриране",
|
"billingEstimatedPeriod": "Очакван период на фактуриране",
|
||||||
"billingIncludedUsage": "Включено използване",
|
"billingIncludedUsage": "Включено използване",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Неуспех при получаване на URL на портала",
|
"billingFailedToGetPortalUrl": "Неуспех при получаване на URL на портала",
|
||||||
"billingPortalError": "Грешка в портала",
|
"billingPortalError": "Грешка в портала",
|
||||||
"billingDataUsageInfo": "Таксува се за всички данни, прехвърляни през вашите защитени тунели, когато сте свързани към облака. Това включва както входящия, така и изходящия трафик за всички ваши сайтове. Когато достигнете лимита си, вашите сайтове ще бъдат прекъснати, докато не надстроите плана или не намалите използването. Данните не се таксуват при използване на възли.",
|
"billingDataUsageInfo": "Таксува се за всички данни, прехвърляни през вашите защитени тунели, когато сте свързани към облака. Това включва както входящия, така и изходящия трафик за всички ваши сайтове. Когато достигнете лимита си, вашите сайтове ще бъдат прекъснати, докато не надстроите плана или не намалите използването. Данните не се таксуват при използване на възли.",
|
||||||
"billingOnlineTimeInfo": "Таксува се на база колко време вашите сайтове остават свързани с облака. Пример: 44,640 минути се равняват на един сайт работещ 24/7 за цял месец. Когато достигнете лимита си, вашите сайтове ще бъдат прекъснати, докато не надстроите плана или не намалите използването. Времето не се таксува при използване на възли.",
|
"billingSInfo": "Колко сайта можете да използвате",
|
||||||
"billingUsersInfo": "Таксува се всеки потребител в организацията. Таксуването се изчислява ежедневно въз основа на броя на активните потребителски акаунти във вашата организация.",
|
"billingUsersInfo": "Колко потребители можете да използвате",
|
||||||
"billingDomainInfo": "Таксува се всеки домейн в организацията. Таксуването се изчислява ежедневно въз основа на броя на активните домейн акаунти във вашата организация.",
|
"billingDomainInfo": "Колко домейни можете да използвате",
|
||||||
"billingRemoteExitNodesInfo": "Таксува се всеки управляван възел в организацията. Таксуването се изчислява ежедневно въз основа на броя на активните управлявани възли във вашата организация.",
|
"billingRemoteExitNodesInfo": "Колко дистанционни възли можете да използвате",
|
||||||
|
"billingLicenseKeys": "Лицензионни ключове",
|
||||||
|
"billingLicenseKeysDescription": "Управлявайте вашите абонаменти за лицензионни ключове",
|
||||||
|
"billingLicenseSubscription": "Абонамент за лиценз",
|
||||||
|
"billingInactive": "Неактивен",
|
||||||
|
"billingLicenseItem": "Лицензионен елемент",
|
||||||
|
"billingQuantity": "Количество",
|
||||||
|
"billingTotal": "общо",
|
||||||
|
"billingModifyLicenses": "Промяна на абонамента за лиценз",
|
||||||
"domainNotFound": "Домейнът не е намерен",
|
"domainNotFound": "Домейнът не е намерен",
|
||||||
"domainNotFoundDescription": "Този ресурс е деактивиран, защото домейнът вече не съществува в нашата система. Моля, задайте нов домейн за този ресурс.",
|
"domainNotFoundDescription": "Този ресурс е деактивиран, защото домейнът вече не съществува в нашата система. Моля, задайте нов домейн за този ресурс.",
|
||||||
"failed": "Неуспешно",
|
"failed": "Неуспешно",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Номерът на порта е задължителен за не-HTTP ресурси",
|
"resourcePortRequired": "Номерът на порта е задължителен за не-HTTP ресурси",
|
||||||
"resourcePortNotAllowed": "Номерът на порта не трябва да бъде задаван за HTTP ресурси",
|
"resourcePortNotAllowed": "Номерът на порта не трябва да бъде задаван за HTTP ресурси",
|
||||||
"billingPricingCalculatorLink": "Калкулатор на цените",
|
"billingPricingCalculatorLink": "Калкулатор на цените",
|
||||||
|
"billingYourPlan": "Вашият план",
|
||||||
|
"billingViewOrModifyPlan": "Преглед или промяна на текущия ви план",
|
||||||
|
"billingViewPlanDetails": "Преглед на подробности за плана",
|
||||||
|
"billingUsageAndLimits": "Използване и граници",
|
||||||
|
"billingViewUsageAndLimits": "Преглед на ограниченията на плана и текущото използване",
|
||||||
|
"billingCurrentUsage": "Текущо използване",
|
||||||
|
"billingMaximumLimits": "Максимални граници",
|
||||||
|
"billingRemoteNodes": "Дистанционни възли",
|
||||||
|
"billingUnlimited": "Неограничено",
|
||||||
|
"billingPaidLicenseKeys": "Платени лицензионни ключове",
|
||||||
|
"billingManageLicenseSubscription": "Управлявайте абонамента си за платени самостоятелно хоствани лицензионни ключове",
|
||||||
|
"billingCurrentKeys": "Текущи ключове",
|
||||||
|
"billingModifyCurrentPlan": "Промяна на текущия план",
|
||||||
|
"billingConfirmUpgrade": "Потвърдете повишаването",
|
||||||
|
"billingConfirmDowngrade": "Потвърдете понижението",
|
||||||
|
"billingConfirmUpgradeDescription": "Предстои ви да повишите плана си. Прегледайте новите ограничения и цени по-долу.",
|
||||||
|
"billingConfirmDowngradeDescription": "Предстои ви да понижите плана си. Прегледайте новите ограничения и цени по-долу.",
|
||||||
|
"billingPlanIncludes": "Планът включва",
|
||||||
|
"billingProcessing": "Процесиране...",
|
||||||
|
"billingConfirmUpgradeButton": "Потвърдете повишаването",
|
||||||
|
"billingConfirmDowngradeButton": "Потвърдете понижението",
|
||||||
|
"billingLimitViolationWarning": "Използването надвишава новите планови ограничения",
|
||||||
|
"billingLimitViolationDescription": "Текущото ви използване надвишава ограниченията на този план. След понижаване, всички действия ще бъдат деактивирани, докато не намалите използването в рамките на новите ограничения. Моля, прегледайте по-долу функциите, които в момента са извън ограниченията. Ограничения в нарушение:",
|
||||||
|
"billingFeatureLossWarning": "Уведомление за наличност на функциите",
|
||||||
|
"billingFeatureLossDescription": "Чрез понижението на плана, функциите, недостъпни в новия план, ще бъдат автоматично деактивирани. Някои настройки и конфигурации може да бъдат загубени. Моля, прегледайте ценовата матрица, за да разберете кои функции вече няма да са на разположение.",
|
||||||
|
"billingUsageExceedsLimit": "Текущото използване ({current}) надвишава ограничението ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Съгласен съм с",
|
"IAgreeToThe": "Съгласен съм с",
|
||||||
"termsOfService": "условията за ползване",
|
"termsOfService": "условията за ползване",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Назад към стандартния вход.",
|
"orgAuthBackToSignIn": "Назад към стандартния вход.",
|
||||||
"orgAuthNoAccount": "Нямате профил?",
|
"orgAuthNoAccount": "Нямате профил?",
|
||||||
"subscriptionRequiredToUse": "Необходим е абонамент, за да използвате тази функция.",
|
"subscriptionRequiredToUse": "Необходим е абонамент, за да използвате тази функция.",
|
||||||
|
"mustUpgradeToUse": "Трябва да повишите своя абонамент, за да използвате тази функция.",
|
||||||
|
"subscriptionRequiredTierToUse": "Тази функция изисква <tierLink>{tier}</tierLink> или по-висок план.",
|
||||||
|
"upgradeToTierToUse": "Повишете до <tierLink>{tier}</tierLink> или по-висок план, за да използвате тази функция.",
|
||||||
|
"subscriptionTierTier1": "Домашен",
|
||||||
|
"subscriptionTierTier2": "Екип",
|
||||||
|
"subscriptionTierTier3": "Бизнес",
|
||||||
|
"subscriptionTierEnterprise": "Предприятие",
|
||||||
"idpDisabled": "Доставчиците на идентичност са деактивирани.",
|
"idpDisabled": "Доставчиците на идентичност са деактивирани.",
|
||||||
"orgAuthPageDisabled": "Страницата за удостоверяване на организацията е деактивирана.",
|
"orgAuthPageDisabled": "Страницата за удостоверяване на организацията е деактивирана.",
|
||||||
"domainRestartedDescription": "Проверка на домейна е рестартирана успешно",
|
"domainRestartedDescription": "Проверка на домейна е рестартирана успешно",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Получаване на лиценз",
|
||||||
|
"description": "Изберете план и ни кажете как планирате да използвате Pangolin.",
|
||||||
|
"chooseTier": "Изберете вашия план",
|
||||||
|
"viewPricingLink": "Вижте цените, функциите и ограниченията",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Стартов",
|
||||||
|
"description": "Предприятие, 25 потребители, 25 сайта и общностна поддръжка."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Скала",
|
||||||
|
"description": "Предприятие, 50 потребители, 50 сайта и приоритетна поддръжка."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Само за лична употреба (безплатен лиценз — без плащане)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Продължете към плащане"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Грешка при плащането",
|
||||||
|
"description": "Не можа да се започне плащането. Моля, опитайте отново."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Приоритет",
|
"priority": "Приоритет",
|
||||||
"priorityDescription": "По-високите приоритетни маршрути се оценяват първи. Приоритет = 100 означава автоматично подреждане (системата решава). Използвайте друго число, за да наложите ръчен приоритет.",
|
"priorityDescription": "По-високите приоритетни маршрути се оценяват първи. Приоритет = 100 означава автоматично подреждане (системата решава). Използвайте друго число, за да наложите ръчен приоритет.",
|
||||||
"instanceName": "Име на инстанция",
|
"instanceName": "Име на инстанция",
|
||||||
@@ -2211,7 +2283,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Край на следващата година",
|
"logRetentionEndOfFollowingYear": "Край на следващата година",
|
||||||
"actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация",
|
"actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация",
|
||||||
"accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация",
|
"accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация",
|
||||||
"licenseRequiredToUse": "Необходим е лиценз Enterprise, за да се използва тази функция.",
|
"licenseRequiredToUse": "Изисква се лиценз за <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink>, за да използвате тази функция. Тази функция е също достъпна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
|
"ossEnterpriseEditionRequired": "Необходимо е <enterpriseEditionLink>изданието Enterprise</enterpriseEditionLink>, за да използвате тази функция. Тази функция е също достъпна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"certResolver": "Решавач на сертификати",
|
"certResolver": "Решавач на сертификати",
|
||||||
"certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.",
|
"certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.",
|
||||||
"selectCertResolver": "Изберете решавач на сертификати",
|
"selectCertResolver": "Изберете решавач на сертификати",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Активирана защитна стена.",
|
"firewallEnabled": "Активирана защитна стена.",
|
||||||
"autoUpdatesEnabled": "Активирани автоматични актуализации.",
|
"autoUpdatesEnabled": "Активирани автоматични актуализации.",
|
||||||
"tpmAvailable": "TPM е на разположение.",
|
"tpmAvailable": "TPM е на разположение.",
|
||||||
|
"windowsAntivirusEnabled": "Активирана антивирусна програма",
|
||||||
"macosSipEnabled": "Protection на системната цялост (SIP).",
|
"macosSipEnabled": "Protection на системната цялост (SIP).",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper.",
|
"macosGatekeeperEnabled": "Gatekeeper.",
|
||||||
"macosFirewallStealthMode": "Скрит режим на защитната стена.",
|
"macosFirewallStealthMode": "Скрит режим на защитната стена.",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Jste členem {count, plural, =0 {0 organizací} one {1 organizace} other {# organizací}}.",
|
"componentsMember": "Jste členem {count, plural, =0 {0 organizací} one {1 organizace} other {# organizací}}.",
|
||||||
"componentsInvalidKey": "Byly nalezeny neplatné nebo propadlé licenční klíče. Pokud chcete nadále používat všechny funkce, postupujte podle licenčních podmínek.",
|
"componentsInvalidKey": "Byly nalezeny neplatné nebo propadlé licenční klíče. Pokud chcete nadále používat všechny funkce, postupujte podle licenčních podmínek.",
|
||||||
"dismiss": "Zavřít",
|
"dismiss": "Zavřít",
|
||||||
|
"subscriptionViolationMessage": "Jste za hranicemi vašeho aktuálního plánu. Opravte problém odstraněním webů, uživatelů nebo jiných zdrojů, abyste zůstali ve vašem tarifu.",
|
||||||
|
"subscriptionViolationViewBilling": "Zobrazit fakturaci",
|
||||||
"componentsLicenseViolation": "Porušení licenčních podmínek: Tento server používá {usedSites} stránek, což překračuje limit {maxSites} licencovaných stránek. Pokud chcete nadále používat všechny funkce, postupujte podle licenčních podmínek.",
|
"componentsLicenseViolation": "Porušení licenčních podmínek: Tento server používá {usedSites} stránek, což překračuje limit {maxSites} licencovaných stránek. Pokud chcete nadále používat všechny funkce, postupujte podle licenčních podmínek.",
|
||||||
"componentsSupporterMessage": "Děkujeme, že podporujete Pangolin jako {tier}!",
|
"componentsSupporterMessage": "Děkujeme, že podporujete Pangolin jako {tier}!",
|
||||||
"inviteErrorNotValid": "Je nám líto, ale vypadá to, že pozvánka, ke které se snažíte získat přístup, nebyla přijata nebo již není platná.",
|
"inviteErrorNotValid": "Je nám líto, ale vypadá to, že pozvánka, ke které se snažíte získat přístup, nebyla přijata nebo již není platná.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Zvýšit počet stránek",
|
"sitestCountIncrease": "Zvýšit počet stránek",
|
||||||
"idpManage": "Spravovat poskytovatele identity",
|
"idpManage": "Spravovat poskytovatele identity",
|
||||||
"idpManageDescription": "Zobrazit a spravovat poskytovatele identity v systému",
|
"idpManageDescription": "Zobrazit a spravovat poskytovatele identity v systému",
|
||||||
|
"idpGlobalModeBanner": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v <adminPanelLink>admin panelu</adminPanelLink>. Chcete-li povolit IdP pro každou organizaci, upravte konfiguraci serveru a nastavte IdP režim na org. <configDocsLink>Viz dokumentace</configDocsLink>. Pokud chcete pokračovat v používání globálních IdP a zmizet z nastavení organizace, explicitně nastavte režim na globální v konfiguraci.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Spravujte globální IdP v <adminPanelLink>admin panelu</adminPanelLink>. Chcete-li použít poskytovatele identity pro každou organizaci, musíte přejít na Enterprise vydání.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v <adminPanelLink>admin panelu</adminPanelLink>. Chcete-li použít poskytovatele identity pro každou organizaci, je vyžadována Enterprise licence.",
|
||||||
"idpDeletedDescription": "Poskytovatel identity byl úspěšně odstraněn",
|
"idpDeletedDescription": "Poskytovatel identity byl úspěšně odstraněn",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Jste si jisti, že chcete trvale odstranit poskytovatele identity?",
|
"idpQuestionRemove": "Jste si jisti, že chcete trvale odstranit poskytovatele identity?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Přehled omezení použití",
|
"billingUsageLimitsOverview": "Přehled omezení použití",
|
||||||
"billingMonitorUsage": "Sledujte vaše využití pomocí nastavených limitů. Pokud potřebujete zvýšit limity, kontaktujte nás prosím support@pangolin.net.",
|
"billingMonitorUsage": "Sledujte vaše využití pomocí nastavených limitů. Pokud potřebujete zvýšit limity, kontaktujte nás prosím support@pangolin.net.",
|
||||||
"billingDataUsage": "Využití dat",
|
"billingDataUsage": "Využití dat",
|
||||||
"billingOnlineTime": "Stránka online čas",
|
"billingSites": "Stránky",
|
||||||
"billingUsers": "Aktivní uživatelé",
|
"billingUsers": "Uživatelé",
|
||||||
"billingDomains": "Aktivní domény",
|
"billingDomains": "Domény",
|
||||||
"billingRemoteExitNodes": "Aktivní Samostatně hostované uzly",
|
"billingRemoteExitNodes": "Vzdálené uzly",
|
||||||
"billingNoLimitConfigured": "Žádný limit nenastaven",
|
"billingNoLimitConfigured": "Žádný limit nenastaven",
|
||||||
"billingEstimatedPeriod": "Odhadované období fakturace",
|
"billingEstimatedPeriod": "Odhadované období fakturace",
|
||||||
"billingIncludedUsage": "Zahrnuto využití",
|
"billingIncludedUsage": "Zahrnuto využití",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Nepodařilo se získat URL portálu",
|
"billingFailedToGetPortalUrl": "Nepodařilo se získat URL portálu",
|
||||||
"billingPortalError": "Chyba portálu",
|
"billingPortalError": "Chyba portálu",
|
||||||
"billingDataUsageInfo": "Pokud jste připojeni k cloudu, jsou vám účtována všechna data přenášená prostřednictvím zabezpečených tunelů. To zahrnuje příchozí i odchozí provoz na všech vašich stránkách. Jakmile dosáhnete svého limitu, vaše stránky se odpojí, dokud neaktualizujete svůj tarif nebo nezmenšíte jeho používání. Data nejsou nabírána při používání uzlů.",
|
"billingDataUsageInfo": "Pokud jste připojeni k cloudu, jsou vám účtována všechna data přenášená prostřednictvím zabezpečených tunelů. To zahrnuje příchozí i odchozí provoz na všech vašich stránkách. Jakmile dosáhnete svého limitu, vaše stránky se odpojí, dokud neaktualizujete svůj tarif nebo nezmenšíte jeho používání. Data nejsou nabírána při používání uzlů.",
|
||||||
"billingOnlineTimeInfo": "Platíte na základě toho, jak dlouho budou vaše stránky připojeny k cloudu. Například, 44,640 minut se rovná jedné stránce 24/7 po celý měsíc. Jakmile dosáhnete svého limitu, vaše stránky se odpojí, dokud neaktualizujete svůj tarif nebo nezkrátíte jeho používání. Čas není vybírán při používání uzlů.",
|
"billingSInfo": "Kolik stránek můžete použít",
|
||||||
"billingUsersInfo": "Každý uživatel v organizaci je účtován denně. Fakturace je počítána na základě počtu aktivních uživatelských účtů na Vašem org.",
|
"billingUsersInfo": "Kolik uživatelů můžete použít",
|
||||||
"billingDomainInfo": "Objednávka je účtována za každou doménu v organizaci. Fakturace je počítána denně na základě počtu aktivních doménových účtů na Vašem org.",
|
"billingDomainInfo": "Kolik domén můžete použít",
|
||||||
"billingRemoteExitNodesInfo": "Za každý spravovaný uzel v organizaci se vám účtuje denně. Fakturace je vypočítávána na základě počtu aktivních spravovaných uzlů ve vašem org.",
|
"billingRemoteExitNodesInfo": "Kolik vzdálených uzlů můžete použít",
|
||||||
|
"billingLicenseKeys": "Licenční klíče",
|
||||||
|
"billingLicenseKeysDescription": "Spravovat předplatné licenčního klíče",
|
||||||
|
"billingLicenseSubscription": "Předplatné licence",
|
||||||
|
"billingInactive": "Neaktivní",
|
||||||
|
"billingLicenseItem": "Položka licence",
|
||||||
|
"billingQuantity": "Množství",
|
||||||
|
"billingTotal": "celkem",
|
||||||
|
"billingModifyLicenses": "Upravit předplatné licence",
|
||||||
"domainNotFound": "Doména nenalezena",
|
"domainNotFound": "Doména nenalezena",
|
||||||
"domainNotFoundDescription": "Tento dokument je zakázán, protože doména již neexistuje náš systém. Nastavte prosím novou doménu pro tento dokument.",
|
"domainNotFoundDescription": "Tento dokument je zakázán, protože doména již neexistuje náš systém. Nastavte prosím novou doménu pro tento dokument.",
|
||||||
"failed": "Selhalo",
|
"failed": "Selhalo",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Pro neHTTP zdroje je vyžadováno číslo portu",
|
"resourcePortRequired": "Pro neHTTP zdroje je vyžadováno číslo portu",
|
||||||
"resourcePortNotAllowed": "Číslo portu by nemělo být nastaveno pro HTTP zdroje",
|
"resourcePortNotAllowed": "Číslo portu by nemělo být nastaveno pro HTTP zdroje",
|
||||||
"billingPricingCalculatorLink": "Cenová kalkulačka",
|
"billingPricingCalculatorLink": "Cenová kalkulačka",
|
||||||
|
"billingYourPlan": "Váš plán",
|
||||||
|
"billingViewOrModifyPlan": "Zobrazit nebo upravit aktuální tarif",
|
||||||
|
"billingViewPlanDetails": "Zobrazit detaily plánu",
|
||||||
|
"billingUsageAndLimits": "Limity a použití",
|
||||||
|
"billingViewUsageAndLimits": "Zobrazit limity vašeho plánu a aktuální využití",
|
||||||
|
"billingCurrentUsage": "Aktuální využití",
|
||||||
|
"billingMaximumLimits": "Maximální limity",
|
||||||
|
"billingRemoteNodes": "Vzdálené uzly",
|
||||||
|
"billingUnlimited": "Bez omezení",
|
||||||
|
"billingPaidLicenseKeys": "Placené licenční klíče",
|
||||||
|
"billingManageLicenseSubscription": "Spravujte své předplatné za placené samohostované licenční klíče",
|
||||||
|
"billingCurrentKeys": "Aktuální klíče",
|
||||||
|
"billingModifyCurrentPlan": "Upravit aktuální tarif",
|
||||||
|
"billingConfirmUpgrade": "Potvrdit aktualizaci",
|
||||||
|
"billingConfirmDowngrade": "Potvrdit downgrade",
|
||||||
|
"billingConfirmUpgradeDescription": "Chystáte se povýšit svůj tarif. Přečtěte si nové limity a ceny.",
|
||||||
|
"billingConfirmDowngradeDescription": "Chystáte se snížit svůj tarif. Přečtěte si nové limity a ceny níže.",
|
||||||
|
"billingPlanIncludes": "Plán zahrnuje",
|
||||||
|
"billingProcessing": "Zpracovávám...",
|
||||||
|
"billingConfirmUpgradeButton": "Potvrdit aktualizaci",
|
||||||
|
"billingConfirmDowngradeButton": "Potvrdit downgrade",
|
||||||
|
"billingLimitViolationWarning": "Využití překročilo limity nového plánu",
|
||||||
|
"billingLimitViolationDescription": "Vaše současné využití překračuje meze tohoto plánu. Po ponížení budou všechny akce zakázány, dokud nesnížíte využití v rámci nových limitů. Přečtěte si prosím níže uvedené funkce překračující limity. Limity při porušení:",
|
||||||
|
"billingFeatureLossWarning": "Upozornění na dostupnost funkce",
|
||||||
|
"billingFeatureLossDescription": "Po pomenutí budou funkce v novém plánu automaticky zakázány. Některá nastavení a konfigurace mohou být ztraceny. Zkontrolujte cenovou matrici, abyste pochopili, které funkce již nebudou k dispozici.",
|
||||||
|
"billingUsageExceedsLimit": "Aktuální využití ({current}) překračuje limit ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Souhlasím s",
|
"IAgreeToThe": "Souhlasím s",
|
||||||
"termsOfService": "podmínky služby",
|
"termsOfService": "podmínky služby",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Zpět ke standardnímu přihlášení",
|
"orgAuthBackToSignIn": "Zpět ke standardnímu přihlášení",
|
||||||
"orgAuthNoAccount": "Nemáte účet?",
|
"orgAuthNoAccount": "Nemáte účet?",
|
||||||
"subscriptionRequiredToUse": "Pro použití této funkce je vyžadováno předplatné.",
|
"subscriptionRequiredToUse": "Pro použití této funkce je vyžadováno předplatné.",
|
||||||
|
"mustUpgradeToUse": "Pro použití této funkce musíte aktualizovat své předplatné.",
|
||||||
|
"subscriptionRequiredTierToUse": "Tato funkce vyžaduje <tierLink>{tier}</tierLink> nebo vyšší.",
|
||||||
|
"upgradeToTierToUse": "Pro použití této funkce upgradujte na <tierLink>{tier}</tierLink> nebo vyšší.",
|
||||||
|
"subscriptionTierTier1": "Domů",
|
||||||
|
"subscriptionTierTier2": "Tým",
|
||||||
|
"subscriptionTierTier3": "Podniky",
|
||||||
|
"subscriptionTierEnterprise": "Podniky",
|
||||||
"idpDisabled": "Poskytovatelé identit jsou zakázáni.",
|
"idpDisabled": "Poskytovatelé identit jsou zakázáni.",
|
||||||
"orgAuthPageDisabled": "Ověřovací stránka organizace je zakázána.",
|
"orgAuthPageDisabled": "Ověřovací stránka organizace je zakázána.",
|
||||||
"domainRestartedDescription": "Ověření domény bylo úspěšně restartováno",
|
"domainRestartedDescription": "Ověření domény bylo úspěšně restartováno",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Získat licenci",
|
||||||
|
"description": "Vyberte si plán a řekněte nám, jak plánujete používat Pangolin.",
|
||||||
|
"chooseTier": "Vyberte si svůj plán",
|
||||||
|
"viewPricingLink": "Zobrazit ceny, funkce a limity",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Počáteční",
|
||||||
|
"description": "Firemní funkce, 25 uživatelů, 25 stránek a komunitní podpory."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Měřítko",
|
||||||
|
"description": "Podnikové funkce, 50 uživatelů, 50 míst a prioritní podpory."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Pouze osobní použití (bezplatná licence – bez platby)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Pokračovat do pokladny"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Chyba při objednávce",
|
||||||
|
"description": "Nelze spustit objednávku. Zkuste to prosím znovu."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Priorita",
|
"priority": "Priorita",
|
||||||
"priorityDescription": "Vyšší priorita je vyhodnocena jako první. Priorita = 100 znamená automatické řazení (rozhodnutí systému). Pro vynucení manuální priority použijte jiné číslo.",
|
"priorityDescription": "Vyšší priorita je vyhodnocena jako první. Priorita = 100 znamená automatické řazení (rozhodnutí systému). Pro vynucení manuální priority použijte jiné číslo.",
|
||||||
"instanceName": "Název instance",
|
"instanceName": "Název instance",
|
||||||
@@ -2211,7 +2283,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 pro podnikání.",
|
"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>.",
|
||||||
|
"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>.",
|
||||||
"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ů",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Firewall povolen",
|
"firewallEnabled": "Firewall povolen",
|
||||||
"autoUpdatesEnabled": "Automatické aktualizace povoleny",
|
"autoUpdatesEnabled": "Automatické aktualizace povoleny",
|
||||||
"tpmAvailable": "TPM k dispozici",
|
"tpmAvailable": "TPM k dispozici",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus povolen",
|
||||||
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
"macosSipEnabled": "Ochrana systémové integrity (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
"macosFirewallStealthMode": "Režim neviditelnosti firewallu",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Du bist Mitglied von {count, plural, =0 {keiner Organisation} one {einer Organisation} other {# Organisationen}}.",
|
"componentsMember": "Du bist Mitglied von {count, plural, =0 {keiner Organisation} one {einer Organisation} other {# Organisationen}}.",
|
||||||
"componentsInvalidKey": "Ungültige oder abgelaufene Lizenzschlüssel erkannt. Beachte die Lizenzbedingungen, um alle Funktionen weiterhin zu nutzen.",
|
"componentsInvalidKey": "Ungültige oder abgelaufene Lizenzschlüssel erkannt. Beachte die Lizenzbedingungen, um alle Funktionen weiterhin zu nutzen.",
|
||||||
"dismiss": "Verwerfen",
|
"dismiss": "Verwerfen",
|
||||||
|
"subscriptionViolationMessage": "Sie überschreiten Ihre Grenzen für Ihr aktuelles Paket. Korrigieren Sie das Problem, indem Sie Webseiten, Benutzer oder andere Ressourcen entfernen, um in Ihrem Paket zu bleiben.",
|
||||||
|
"subscriptionViolationViewBilling": "Rechnung anzeigen",
|
||||||
"componentsLicenseViolation": "Lizenzverstoß: Dieser Server benutzt {usedSites} Standorte, was das Lizenzlimit von {maxSites} Standorten überschreitet. Beachte die Lizenzbedingungen, um alle Funktionen weiterhin zu nutzen.",
|
"componentsLicenseViolation": "Lizenzverstoß: Dieser Server benutzt {usedSites} Standorte, was das Lizenzlimit von {maxSites} Standorten überschreitet. Beachte die Lizenzbedingungen, um alle Funktionen weiterhin zu nutzen.",
|
||||||
"componentsSupporterMessage": "Vielen Dank für die Unterstützung von Pangolin als {tier}!",
|
"componentsSupporterMessage": "Vielen Dank für die Unterstützung von Pangolin als {tier}!",
|
||||||
"inviteErrorNotValid": "Es tut uns leid, aber es sieht so aus, als wäre die Einladung, auf die du zugreifen möchtest, entweder nicht angenommen worden oder nicht mehr gültig.",
|
"inviteErrorNotValid": "Es tut uns leid, aber es sieht so aus, als wäre die Einladung, auf die du zugreifen möchtest, entweder nicht angenommen worden oder nicht mehr gültig.",
|
||||||
@@ -97,7 +99,7 @@
|
|||||||
"siteGeneralDescription": "Allgemeine Einstellungen für diesen Standort konfigurieren",
|
"siteGeneralDescription": "Allgemeine Einstellungen für diesen Standort konfigurieren",
|
||||||
"siteSettingDescription": "Standorteinstellungen konfigurieren",
|
"siteSettingDescription": "Standorteinstellungen konfigurieren",
|
||||||
"siteSetting": "{siteName} Einstellungen",
|
"siteSetting": "{siteName} Einstellungen",
|
||||||
"siteNewtTunnel": "Neuer Standort (empfohlen)",
|
"siteNewtTunnel": "Newt Standort (empfohlen)",
|
||||||
"siteNewtTunnelDescription": "Einfachster Weg, einen Einstiegspunkt in jedes Netzwerk zu erstellen. Keine zusätzliche Einrichtung.",
|
"siteNewtTunnelDescription": "Einfachster Weg, einen Einstiegspunkt in jedes Netzwerk zu erstellen. Keine zusätzliche Einrichtung.",
|
||||||
"siteWg": "Einfacher WireGuard Tunnel",
|
"siteWg": "Einfacher WireGuard Tunnel",
|
||||||
"siteWgDescription": "Verwende jeden WireGuard-Client, um einen Tunnel einzurichten. Manuelles NAT-Setup erforderlich.",
|
"siteWgDescription": "Verwende jeden WireGuard-Client, um einen Tunnel einzurichten. Manuelles NAT-Setup erforderlich.",
|
||||||
@@ -107,7 +109,7 @@
|
|||||||
"siteSeeAll": "Alle Standorte anzeigen",
|
"siteSeeAll": "Alle Standorte anzeigen",
|
||||||
"siteTunnelDescription": "Legen Sie fest, wie Sie sich mit dem Standort verbinden möchten",
|
"siteTunnelDescription": "Legen Sie fest, wie Sie sich mit dem Standort verbinden möchten",
|
||||||
"siteNewtCredentials": "Zugangsdaten",
|
"siteNewtCredentials": "Zugangsdaten",
|
||||||
"siteNewtCredentialsDescription": "So wird sich die Seite mit dem Server authentifizieren",
|
"siteNewtCredentialsDescription": "So wird sich der Standort mit dem Server authentifizieren",
|
||||||
"remoteNodeCredentialsDescription": "So wird sich der entfernte Node mit dem Server authentifizieren",
|
"remoteNodeCredentialsDescription": "So wird sich der entfernte Node mit dem Server authentifizieren",
|
||||||
"siteCredentialsSave": "Anmeldedaten speichern",
|
"siteCredentialsSave": "Anmeldedaten speichern",
|
||||||
"siteCredentialsSaveDescription": "Du kannst das nur einmal sehen. Stelle sicher, dass du es an einen sicheren Ort kopierst.",
|
"siteCredentialsSaveDescription": "Du kannst das nur einmal sehen. Stelle sicher, dass du es an einen sicheren Ort kopierst.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Anzahl der Standorte erhöhen",
|
"sitestCountIncrease": "Anzahl der Standorte erhöhen",
|
||||||
"idpManage": "Identitätsanbieter verwalten",
|
"idpManage": "Identitätsanbieter verwalten",
|
||||||
"idpManageDescription": "Identitätsanbieter im System anzeigen und verwalten",
|
"idpManageDescription": "Identitätsanbieter im System anzeigen und verwalten",
|
||||||
|
"idpGlobalModeBanner": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt über alle Organisationen). Verwalten Sie globale IdPs im <adminPanelLink>Admin-Panel</adminPanelLink>. Um IdPs pro Organisation zu aktivieren, bearbeiten Sie die Server-Konfiguration und setzen Sie den IdP-Modus auf org. <configDocsLink>Siehe Dokumentation</configDocsLink>. Wenn Sie weiterhin globale IdPs verwenden und diese in den Organisationseinstellungen verschwinden lassen wollen, setzen Sie den Modus explizit auf global in der Konfiguration.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im <adminPanelLink>Admin-Panel</adminPanelLink>verwalten. Um Identitätsanbieter pro Organisation nutzen zu können, müssen Sie zur Enterprise Edition upgraden.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im <adminPanelLink>Admin-Panel</adminPanelLink>verwalten. Um Identitätsanbieter pro Organisation zu verwenden, ist eine Enterprise-Lizenz erforderlich.",
|
||||||
"idpDeletedDescription": "Identitätsanbieter erfolgreich gelöscht",
|
"idpDeletedDescription": "Identitätsanbieter erfolgreich gelöscht",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Sind Sie sicher, dass Sie den Identitätsanbieter dauerhaft löschen möchten?",
|
"idpQuestionRemove": "Sind Sie sicher, dass Sie den Identitätsanbieter dauerhaft löschen möchten?",
|
||||||
@@ -1147,9 +1152,9 @@
|
|||||||
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
|
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
|
||||||
"actionCreateClient": "Client erstellen",
|
"actionCreateClient": "Client erstellen",
|
||||||
"actionDeleteClient": "Client löschen",
|
"actionDeleteClient": "Client löschen",
|
||||||
"actionArchiveClient": "Kunde archivieren",
|
"actionArchiveClient": "Client archivieren",
|
||||||
"actionUnarchiveClient": "Client dearchivieren",
|
"actionUnarchiveClient": "Client dearchivieren",
|
||||||
"actionBlockClient": "Klient sperren",
|
"actionBlockClient": "Client sperren",
|
||||||
"actionUnblockClient": "Client entsperren",
|
"actionUnblockClient": "Client entsperren",
|
||||||
"actionUpdateClient": "Client aktualisieren",
|
"actionUpdateClient": "Client aktualisieren",
|
||||||
"actionListClients": "Clients auflisten",
|
"actionListClients": "Clients auflisten",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Übersicht über Nutzungsgrenzen",
|
"billingUsageLimitsOverview": "Übersicht über Nutzungsgrenzen",
|
||||||
"billingMonitorUsage": "Überwachen Sie Ihren Verbrauch im Vergleich zu konfigurierten Grenzwerten. Wenn Sie eine Erhöhung der Limits benötigen, kontaktieren Sie uns bitte support@pangolin.net.",
|
"billingMonitorUsage": "Überwachen Sie Ihren Verbrauch im Vergleich zu konfigurierten Grenzwerten. Wenn Sie eine Erhöhung der Limits benötigen, kontaktieren Sie uns bitte support@pangolin.net.",
|
||||||
"billingDataUsage": "Datenverbrauch",
|
"billingDataUsage": "Datenverbrauch",
|
||||||
"billingOnlineTime": "Online-Zeit der Seite",
|
"billingSites": "Seiten",
|
||||||
"billingUsers": "Aktive Benutzer",
|
"billingUsers": "Benutzergeräte",
|
||||||
"billingDomains": "Aktive Domains",
|
"billingDomains": "Domänen",
|
||||||
"billingRemoteExitNodes": "Aktive selbstgehostete Nodes",
|
"billingRemoteExitNodes": "Entfernte Knoten",
|
||||||
"billingNoLimitConfigured": "Kein Limit konfiguriert",
|
"billingNoLimitConfigured": "Kein Limit konfiguriert",
|
||||||
"billingEstimatedPeriod": "Geschätzter Abrechnungszeitraum",
|
"billingEstimatedPeriod": "Geschätzter Abrechnungszeitraum",
|
||||||
"billingIncludedUsage": "Inklusive Nutzung",
|
"billingIncludedUsage": "Inklusive Nutzung",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Fehler beim Abrufen der Portal-URL",
|
"billingFailedToGetPortalUrl": "Fehler beim Abrufen der Portal-URL",
|
||||||
"billingPortalError": "Portalfehler",
|
"billingPortalError": "Portalfehler",
|
||||||
"billingDataUsageInfo": "Wenn Sie mit der Cloud verbunden sind, werden alle Daten über Ihre sicheren Tunnel belastet. Dies schließt eingehenden und ausgehenden Datenverkehr über alle Ihre Websites ein. Wenn Sie Ihr Limit erreichen, werden Ihre Seiten die Verbindung trennen, bis Sie Ihr Paket upgraden oder die Nutzung verringern. Daten werden nicht belastet, wenn Sie Knoten verwenden.",
|
"billingDataUsageInfo": "Wenn Sie mit der Cloud verbunden sind, werden alle Daten über Ihre sicheren Tunnel belastet. Dies schließt eingehenden und ausgehenden Datenverkehr über alle Ihre Websites ein. Wenn Sie Ihr Limit erreichen, werden Ihre Seiten die Verbindung trennen, bis Sie Ihr Paket upgraden oder die Nutzung verringern. Daten werden nicht belastet, wenn Sie Knoten verwenden.",
|
||||||
"billingOnlineTimeInfo": "Sie werden belastet, abhängig davon, wie lange Ihre Seiten mit der Cloud verbunden bleiben. Zum Beispiel 44.640 Minuten entspricht einer Site, die 24 Stunden am Tag des Monats läuft. Wenn Sie Ihr Limit erreichen, werden Ihre Seiten die Verbindung trennen, bis Sie Ihr Paket upgraden oder die Nutzung verringern. Die Zeit wird nicht belastet, wenn Sie Knoten verwenden.",
|
"billingSInfo": "Anzahl der Sites die Sie verwenden können",
|
||||||
"billingUsersInfo": "Sie werden für jeden Benutzer in der Organisation berechnet. Die Abrechnung wird täglich anhand der Anzahl der aktiven Benutzerkonten in Ihrer Org berechnet.",
|
"billingUsersInfo": "Wie viele Benutzer Sie verwenden können",
|
||||||
"billingDomainInfo": "Sie werden für jede Domain in der Organisation berechnet. Die Abrechnung wird täglich anhand der Anzahl der aktiven Domain-Konten in Ihrer Org berechnet.",
|
"billingDomainInfo": "Wie viele Domains Sie verwenden können",
|
||||||
"billingRemoteExitNodesInfo": "Sie werden für jeden verwalteten Knoten in der Organisation berechnet. Die Abrechnung wird täglich anhand der Anzahl der aktiven verwalteten Knoten in Ihrer Org berechnet.",
|
"billingRemoteExitNodesInfo": "Wie viele entfernte Knoten Sie verwenden können",
|
||||||
|
"billingLicenseKeys": "Lizenzschlüssel",
|
||||||
|
"billingLicenseKeysDescription": "Verwalten Sie Ihre Lizenzschlüssel Abonnements",
|
||||||
|
"billingLicenseSubscription": "Lizenzabonnement",
|
||||||
|
"billingInactive": "Inaktiv",
|
||||||
|
"billingLicenseItem": "Lizenz-Element",
|
||||||
|
"billingQuantity": "Menge",
|
||||||
|
"billingTotal": "gesamt",
|
||||||
|
"billingModifyLicenses": "Lizenzabonnement ändern",
|
||||||
"domainNotFound": "Domain nicht gefunden",
|
"domainNotFound": "Domain nicht gefunden",
|
||||||
"domainNotFoundDescription": "Diese Ressource ist deaktiviert, weil die Domain nicht mehr in unserem System existiert. Bitte setzen Sie eine neue Domain für diese Ressource.",
|
"domainNotFoundDescription": "Diese Ressource ist deaktiviert, weil die Domain nicht mehr in unserem System existiert. Bitte setzen Sie eine neue Domain für diese Ressource.",
|
||||||
"failed": "Fehlgeschlagen",
|
"failed": "Fehlgeschlagen",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Portnummer ist für nicht-HTTP-Ressourcen erforderlich",
|
"resourcePortRequired": "Portnummer ist für nicht-HTTP-Ressourcen erforderlich",
|
||||||
"resourcePortNotAllowed": "Portnummer sollte für HTTP-Ressourcen nicht gesetzt werden",
|
"resourcePortNotAllowed": "Portnummer sollte für HTTP-Ressourcen nicht gesetzt werden",
|
||||||
"billingPricingCalculatorLink": "Preisrechner",
|
"billingPricingCalculatorLink": "Preisrechner",
|
||||||
|
"billingYourPlan": "Ihr Plan",
|
||||||
|
"billingViewOrModifyPlan": "Zeige oder ändere dein aktuelles Paket",
|
||||||
|
"billingViewPlanDetails": "Plan Details anzeigen",
|
||||||
|
"billingUsageAndLimits": "Nutzung und Einschränkungen",
|
||||||
|
"billingViewUsageAndLimits": "Schau dir die Grenzen und die aktuelle Nutzung deines Plans an",
|
||||||
|
"billingCurrentUsage": "Aktuelle Nutzung",
|
||||||
|
"billingMaximumLimits": "Maximale Grenzen",
|
||||||
|
"billingRemoteNodes": "Entfernte Knoten",
|
||||||
|
"billingUnlimited": "Unbegrenzt",
|
||||||
|
"billingPaidLicenseKeys": "Bezahlte Lizenzschlüssel",
|
||||||
|
"billingManageLicenseSubscription": "Verwalten Sie Ihr Abonnement für kostenpflichtige selbstgehostete Lizenzschlüssel",
|
||||||
|
"billingCurrentKeys": "Aktuelle Tasten",
|
||||||
|
"billingModifyCurrentPlan": "Aktuelles Paket ändern",
|
||||||
|
"billingConfirmUpgrade": "Upgrade bestätigen",
|
||||||
|
"billingConfirmDowngrade": "Downgrade bestätigen",
|
||||||
|
"billingConfirmUpgradeDescription": "Sie sind dabei, Ihr Paket zu aktualisieren. Schauen Sie sich die neuen Limits und Preise unten an.",
|
||||||
|
"billingConfirmDowngradeDescription": "Sie sind dabei, Ihren Plan herunterzustufen. Überprüfen Sie die neuen Limits und Preise unten.",
|
||||||
|
"billingPlanIncludes": "Plan beinhaltet",
|
||||||
|
"billingProcessing": "Verarbeitung...",
|
||||||
|
"billingConfirmUpgradeButton": "Upgrade bestätigen",
|
||||||
|
"billingConfirmDowngradeButton": "Downgrade bestätigen",
|
||||||
|
"billingLimitViolationWarning": "Nutzung überschreitet neue Plan-Grenzen",
|
||||||
|
"billingLimitViolationDescription": "Ihre aktuelle Nutzung überschreitet die Grenzen dieses Plans. Nach dem Downgrade werden alle Aktionen deaktiviert, bis Sie die Nutzung innerhalb der neuen Grenzen reduzieren. Bitte überprüfen Sie die Funktionen unten, die derzeit über den Grenzen liegen. Grenzwerte verletzen:",
|
||||||
|
"billingFeatureLossWarning": "Verfügbarkeitshinweis",
|
||||||
|
"billingFeatureLossDescription": "Durch Herabstufung werden Funktionen, die im neuen Paket nicht verfügbar sind, automatisch deaktiviert. Einige Einstellungen und Konfigurationen können verloren gehen. Bitte überprüfen Sie die Preismatrix um zu verstehen, welche Funktionen nicht mehr verfügbar sein werden.",
|
||||||
|
"billingUsageExceedsLimit": "Aktuelle Nutzung ({current}) überschreitet das Limit ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Ich stimme den",
|
"IAgreeToThe": "Ich stimme den",
|
||||||
"termsOfService": "Nutzungsbedingungen zu",
|
"termsOfService": "Nutzungsbedingungen zu",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Zurück zum Standard Login",
|
"orgAuthBackToSignIn": "Zurück zum Standard Login",
|
||||||
"orgAuthNoAccount": "Sie haben noch kein Konto?",
|
"orgAuthNoAccount": "Sie haben noch kein Konto?",
|
||||||
"subscriptionRequiredToUse": "Um diese Funktion nutzen zu können, ist ein Abonnement erforderlich.",
|
"subscriptionRequiredToUse": "Um diese Funktion nutzen zu können, ist ein Abonnement erforderlich.",
|
||||||
|
"mustUpgradeToUse": "Sie müssen Ihr Abonnement aktualisieren, um diese Funktion nutzen zu können.",
|
||||||
|
"subscriptionRequiredTierToUse": "Diese Funktion erfordert <tierLink>{tier}</tierLink> oder höher.",
|
||||||
|
"upgradeToTierToUse": "Upgrade auf <tierLink>{tier}</tierLink> oder höher, um diese Funktion zu nutzen.",
|
||||||
|
"subscriptionTierTier1": "Zuhause",
|
||||||
|
"subscriptionTierTier2": "Team",
|
||||||
|
"subscriptionTierTier3": "Geschäftlich",
|
||||||
|
"subscriptionTierEnterprise": "Firma",
|
||||||
"idpDisabled": "Identitätsanbieter sind deaktiviert.",
|
"idpDisabled": "Identitätsanbieter sind deaktiviert.",
|
||||||
"orgAuthPageDisabled": "Organisations-Authentifizierungsseite ist deaktiviert.",
|
"orgAuthPageDisabled": "Organisations-Authentifizierungsseite ist deaktiviert.",
|
||||||
"domainRestartedDescription": "Domain-Verifizierung erfolgreich neu gestartet",
|
"domainRestartedDescription": "Domain-Verifizierung erfolgreich neu gestartet",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Lizenz erhalten",
|
||||||
|
"description": "Wählen Sie einen Plan und teilen Sie uns mit, wie Sie Pangolin verwenden möchten.",
|
||||||
|
"chooseTier": "Wählen Sie Ihren Plan",
|
||||||
|
"viewPricingLink": "Siehe Preise, Funktionen und Limits",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Starter",
|
||||||
|
"description": "Enterprise Features, 25 Benutzer, 25 Sites und Community-Unterstützung."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Maßstab",
|
||||||
|
"description": "Enterprise Features, 50 Benutzer, 50 Sites und Prioritätsunterstützung."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Nur persönliche Nutzung (kostenlose Lizenz — keine Kasse)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Weiter zur Kasse"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Checkout-Fehler",
|
||||||
|
"description": "Kasse konnte nicht gestartet werden. Bitte versuchen Sie es erneut."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Priorität",
|
"priority": "Priorität",
|
||||||
"priorityDescription": "Die Routen mit höherer Priorität werden zuerst ausgewertet. Priorität = 100 bedeutet automatische Bestellung (Systementscheidung). Verwenden Sie eine andere Nummer, um manuelle Priorität zu erzwingen.",
|
"priorityDescription": "Die Routen mit höherer Priorität werden zuerst ausgewertet. Priorität = 100 bedeutet automatische Bestellung (Systementscheidung). Verwenden Sie eine andere Nummer, um manuelle Priorität zu erzwingen.",
|
||||||
"instanceName": "Instanzname",
|
"instanceName": "Instanzname",
|
||||||
@@ -2211,7 +2283,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 Enterprise-Lizenz erforderlich.",
|
"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.",
|
||||||
|
"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.",
|
||||||
"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",
|
||||||
@@ -2455,14 +2528,14 @@
|
|||||||
"errorUnarchivingDevice": "Fehler beim Entarchivieren des Geräts",
|
"errorUnarchivingDevice": "Fehler beim Entarchivieren des Geräts",
|
||||||
"failedToUnarchiveDevice": "Fehler beim Entfernen des Geräts",
|
"failedToUnarchiveDevice": "Fehler beim Entfernen des Geräts",
|
||||||
"unarchive": "Archivieren",
|
"unarchive": "Archivieren",
|
||||||
"archiveClient": "Kunde archivieren",
|
"archiveClient": "Client archivieren",
|
||||||
"archiveClientQuestion": "Sind Sie sicher, dass Sie diesen Client archivieren möchten?",
|
"archiveClientQuestion": "Sind Sie sicher, dass Sie diesen Client archivieren möchten?",
|
||||||
"archiveClientMessage": "Der Client wird archiviert und aus der Liste Ihrer aktiven Clients entfernt.",
|
"archiveClientMessage": "Der Client wird archiviert und aus der Liste Ihrer aktiven Clients entfernt.",
|
||||||
"archiveClientConfirm": "Kunde archivieren",
|
"archiveClientConfirm": "Client archivieren",
|
||||||
"blockClient": "Klient sperren",
|
"blockClient": "Client sperren",
|
||||||
"blockClientQuestion": "Sind Sie sicher, dass Sie diesen Client blockieren möchten?",
|
"blockClientQuestion": "Sind Sie sicher, dass Sie diesen Client blockieren möchten?",
|
||||||
"blockClientMessage": "Das Gerät wird gezwungen, die Verbindung zu trennen, wenn es gerade verbunden ist. Sie können das Gerät später entsperren.",
|
"blockClientMessage": "Das Gerät wird gezwungen, die Verbindung zu trennen, wenn es gerade verbunden ist. Sie können das Gerät später entsperren.",
|
||||||
"blockClientConfirm": "Klient sperren",
|
"blockClientConfirm": "Client sperren",
|
||||||
"active": "Aktiv",
|
"active": "Aktiv",
|
||||||
"usernameOrEmail": "Benutzername oder E-Mail",
|
"usernameOrEmail": "Benutzername oder E-Mail",
|
||||||
"selectYourOrganization": "Wählen Sie Ihre Organisation",
|
"selectYourOrganization": "Wählen Sie Ihre Organisation",
|
||||||
@@ -2503,13 +2576,14 @@
|
|||||||
"deviceModel": "Gerätemodell",
|
"deviceModel": "Gerätemodell",
|
||||||
"serialNumber": "Seriennummer",
|
"serialNumber": "Seriennummer",
|
||||||
"hostname": "Hostname",
|
"hostname": "Hostname",
|
||||||
"firstSeen": "Erster Blick",
|
"firstSeen": "Zuerst gesehen",
|
||||||
"lastSeen": "Zuletzt gesehen",
|
"lastSeen": "Zuletzt gesehen",
|
||||||
"biometricsEnabled": "Biometrie aktiviert",
|
"biometricsEnabled": "Biometrie aktiviert",
|
||||||
"diskEncrypted": "Festplatte verschlüsselt",
|
"diskEncrypted": "Festplatte verschlüsselt",
|
||||||
"firewallEnabled": "Firewall aktiviert",
|
"firewallEnabled": "Firewall aktiviert",
|
||||||
"autoUpdatesEnabled": "Automatische Updates aktiviert",
|
"autoUpdatesEnabled": "Automatische Updates aktiviert",
|
||||||
"tpmAvailable": "TPM verfügbar",
|
"tpmAvailable": "TPM verfügbar",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus aktiviert",
|
||||||
"macosSipEnabled": "Schutz der Systemintegrität (SIP)",
|
"macosSipEnabled": "Schutz der Systemintegrität (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Firewall Stealth-Modus",
|
"macosFirewallStealthMode": "Firewall Stealth-Modus",
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
"protocolSelect": "Select a protocol",
|
"protocolSelect": "Select a protocol",
|
||||||
"resourcePortNumber": "Port Number",
|
"resourcePortNumber": "Port Number",
|
||||||
"resourcePortNumberDescription": "The external port number to proxy requests.",
|
"resourcePortNumberDescription": "The external port number to proxy requests.",
|
||||||
|
"back": "Back",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"resourceConfig": "Configuration Snippets",
|
"resourceConfig": "Configuration Snippets",
|
||||||
"resourceConfigDescription": "Copy and paste these configuration snippets to set up the TCP/UDP resource",
|
"resourceConfigDescription": "Copy and paste these configuration snippets to set up the TCP/UDP resource",
|
||||||
@@ -246,6 +247,17 @@
|
|||||||
"orgErrorDeleteMessage": "An error occurred while deleting the organization.",
|
"orgErrorDeleteMessage": "An error occurred while deleting the organization.",
|
||||||
"orgDeleted": "Organization deleted",
|
"orgDeleted": "Organization deleted",
|
||||||
"orgDeletedMessage": "The organization and its data has been deleted.",
|
"orgDeletedMessage": "The organization and its data has been deleted.",
|
||||||
|
"deleteAccount": "Delete Account",
|
||||||
|
"deleteAccountDescription": "Permanently delete your account, all organizations you own, and all data within those organizations. This cannot be undone.",
|
||||||
|
"deleteAccountButton": "Delete Account",
|
||||||
|
"deleteAccountConfirmTitle": "Delete Account",
|
||||||
|
"deleteAccountConfirmMessage": "This will permanently wipe your account, all organizations you own, and all data within those organizations. This cannot be undone.",
|
||||||
|
"deleteAccountConfirmString": "delete account",
|
||||||
|
"deleteAccountSuccess": "Account Deleted",
|
||||||
|
"deleteAccountSuccessMessage": "Your account has been deleted.",
|
||||||
|
"deleteAccountError": "Failed to delete account",
|
||||||
|
"deleteAccountPreviewAccount": "Your Account",
|
||||||
|
"deleteAccountPreviewOrgs": "Organizations you own (and all their data)",
|
||||||
"orgMissing": "Organization ID Missing",
|
"orgMissing": "Organization ID Missing",
|
||||||
"orgMissingMessage": "Unable to regenerate invitation without an organization ID.",
|
"orgMissingMessage": "Unable to regenerate invitation without an organization ID.",
|
||||||
"accessUsersManage": "Manage Users",
|
"accessUsersManage": "Manage Users",
|
||||||
@@ -791,6 +803,9 @@
|
|||||||
"sitestCountIncrease": "Increase site count",
|
"sitestCountIncrease": "Increase site count",
|
||||||
"idpManage": "Manage Identity Providers",
|
"idpManage": "Manage Identity Providers",
|
||||||
"idpManageDescription": "View and manage identity providers in the system",
|
"idpManageDescription": "View and manage identity providers in the system",
|
||||||
|
"idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the <adminPanelLink>admin panel</adminPanelLink>. To enable IdPs per organization, edit the server config and set IdP mode to org. <configDocsLink>See the docs</configDocsLink>. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the <adminPanelLink>admin panel</adminPanelLink>. To use identity providers per organization, you must upgrade to the Enterprise edition.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the <adminPanelLink>admin panel</adminPanelLink>. To use identity providers per organization, an Enterprise license is required.",
|
||||||
"idpDeletedDescription": "Identity provider deleted successfully",
|
"idpDeletedDescription": "Identity provider deleted successfully",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Are you sure you want to permanently delete the identity provider?",
|
"idpQuestionRemove": "Are you sure you want to permanently delete the identity provider?",
|
||||||
@@ -2057,7 +2072,7 @@
|
|||||||
"machineClientsBannerDescription": "Machine clients are for servers and automated systems that are not associated with a specific user. They authenticate with an ID and secret, and can run with Pangolin CLI, Olm CLI, or Olm as a container.",
|
"machineClientsBannerDescription": "Machine clients are for servers and automated systems that are not associated with a specific user. They authenticate with an ID and secret, and can run with Pangolin CLI, Olm CLI, or Olm as a container.",
|
||||||
"machineClientsBannerPangolinCLI": "Pangolin CLI",
|
"machineClientsBannerPangolinCLI": "Pangolin CLI",
|
||||||
"machineClientsBannerOlmCLI": "Olm CLI",
|
"machineClientsBannerOlmCLI": "Olm CLI",
|
||||||
"machineClientsBannerOlmContainer": "Olm Container",
|
"machineClientsBannerOlmContainer": "Container",
|
||||||
"clientsTableUserClients": "User",
|
"clientsTableUserClients": "User",
|
||||||
"clientsTableMachineClients": "Machine",
|
"clientsTableMachineClients": "Machine",
|
||||||
"licenseTableValidUntil": "Valid Until",
|
"licenseTableValidUntil": "Valid Until",
|
||||||
@@ -2280,8 +2295,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "End of following year",
|
"logRetentionEndOfFollowingYear": "End of following year",
|
||||||
"actionLogsDescription": "View a history of actions performed in this organization",
|
"actionLogsDescription": "View a history of actions performed in this organization",
|
||||||
"accessLogsDescription": "View access auth requests for resources in this organization",
|
"accessLogsDescription": "View access auth requests for resources in this organization",
|
||||||
"licenseRequiredToUse": "An Enterprise license is required to use this feature.",
|
"licenseRequiredToUse": "An <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> license is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature.",
|
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"certResolver": "Certificate Resolver",
|
"certResolver": "Certificate Resolver",
|
||||||
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
||||||
"selectCertResolver": "Select Certificate Resolver",
|
"selectCertResolver": "Select Certificate Resolver",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Eres un miembro de {count, plural, =0 {ninguna organización} one {una organización} other {# organizaciones}}.",
|
"componentsMember": "Eres un miembro de {count, plural, =0 {ninguna organización} one {una organización} other {# organizaciones}}.",
|
||||||
"componentsInvalidKey": "Se han detectado claves de licencia inválidas o caducadas. Siga los términos de licencia para seguir usando todas las características.",
|
"componentsInvalidKey": "Se han detectado claves de licencia inválidas o caducadas. Siga los términos de licencia para seguir usando todas las características.",
|
||||||
"dismiss": "Descartar",
|
"dismiss": "Descartar",
|
||||||
|
"subscriptionViolationMessage": "Estás más allá de tus límites para tu plan actual. Corrija el problema eliminando sitios, usuarios u otros recursos para permanecer dentro de tu plan.",
|
||||||
|
"subscriptionViolationViewBilling": "Ver facturación",
|
||||||
"componentsLicenseViolation": "Violación de la Licencia: Este servidor está usando sitios {usedSites} que exceden su límite de licencias de sitios {maxSites} . Siga los términos de licencia para seguir usando todas las características.",
|
"componentsLicenseViolation": "Violación de la Licencia: Este servidor está usando sitios {usedSites} que exceden su límite de licencias de sitios {maxSites} . Siga los términos de licencia para seguir usando todas las características.",
|
||||||
"componentsSupporterMessage": "¡Gracias por apoyar a Pangolin como {tier}!",
|
"componentsSupporterMessage": "¡Gracias por apoyar a Pangolin como {tier}!",
|
||||||
"inviteErrorNotValid": "Lo sentimos, pero parece que la invitación a la que intentas acceder no ha sido aceptada o ya no es válida.",
|
"inviteErrorNotValid": "Lo sentimos, pero parece que la invitación a la que intentas acceder no ha sido aceptada o ya no es válida.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Aumentar el número de sitios",
|
"sitestCountIncrease": "Aumentar el número de sitios",
|
||||||
"idpManage": "Administrar proveedores de identidad",
|
"idpManage": "Administrar proveedores de identidad",
|
||||||
"idpManageDescription": "Ver y administrar proveedores de identidad en el sistema",
|
"idpManageDescription": "Ver y administrar proveedores de identidad en el sistema",
|
||||||
|
"idpGlobalModeBanner": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el <adminPanelLink>panel de administración</adminPanelLink>. Para habilitar los IdPs por organización, edita la configuración del servidor y establece el modo de IdP en org. <configDocsLink>Consulta la documentación</configDocsLink>. Si deseas seguir utilizando IdPs globales y hacer que esto desaparezca de las configuraciones de la organización, establece explícitamente el modo en global en la configuración.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el <adminPanelLink>panel de administración</adminPanelLink>. Para usar proveedores de identidad por organización, debes actualizar a la edición Empresarial.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando identificadores globales (compartidos en todas las organizaciones). Gestionar identificaciones globales en el panel <adminPanelLink>de administración</adminPanelLink>. Para utilizar proveedores de identidad por organización, se requiere una licencia de empresa.",
|
||||||
"idpDeletedDescription": "Proveedor de identidad eliminado correctamente",
|
"idpDeletedDescription": "Proveedor de identidad eliminado correctamente",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?",
|
"idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Descripción general de los límites de uso",
|
"billingUsageLimitsOverview": "Descripción general de los límites de uso",
|
||||||
"billingMonitorUsage": "Monitorea tu uso comparado con los límites configurados. Si necesitas que aumenten los límites, contáctanos a soporte@pangolin.net.",
|
"billingMonitorUsage": "Monitorea tu uso comparado con los límites configurados. Si necesitas que aumenten los límites, contáctanos a soporte@pangolin.net.",
|
||||||
"billingDataUsage": "Uso de datos",
|
"billingDataUsage": "Uso de datos",
|
||||||
"billingOnlineTime": "Tiempo en línea del sitio",
|
"billingSites": "Sitios",
|
||||||
"billingUsers": "Usuarios activos",
|
"billingUsers": "Usuarios",
|
||||||
"billingDomains": "Dominios activos",
|
"billingDomains": "Dominios",
|
||||||
"billingRemoteExitNodes": "Nodos autogestionados activos",
|
"billingRemoteExitNodes": "Nodos remotos",
|
||||||
"billingNoLimitConfigured": "No se ha configurado ningún límite",
|
"billingNoLimitConfigured": "No se ha configurado ningún límite",
|
||||||
"billingEstimatedPeriod": "Período de facturación estimado",
|
"billingEstimatedPeriod": "Período de facturación estimado",
|
||||||
"billingIncludedUsage": "Uso incluido",
|
"billingIncludedUsage": "Uso incluido",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Error al obtener la URL del portal",
|
"billingFailedToGetPortalUrl": "Error al obtener la URL del portal",
|
||||||
"billingPortalError": "Error del portal",
|
"billingPortalError": "Error del portal",
|
||||||
"billingDataUsageInfo": "Se le cobran todos los datos transferidos a través de sus túneles seguros cuando se conectan a la nube. Esto incluye tanto tráfico entrante como saliente a través de todos sus sitios. Cuando alcance su límite, sus sitios se desconectarán hasta que actualice su plan o reduzca el uso. Los datos no se cargan cuando se usan nodos.",
|
"billingDataUsageInfo": "Se le cobran todos los datos transferidos a través de sus túneles seguros cuando se conectan a la nube. Esto incluye tanto tráfico entrante como saliente a través de todos sus sitios. Cuando alcance su límite, sus sitios se desconectarán hasta que actualice su plan o reduzca el uso. Los datos no se cargan cuando se usan nodos.",
|
||||||
"billingOnlineTimeInfo": "Se te cobrará en función del tiempo que tus sitios permanezcan conectados a la nube. Por ejemplo, 44.640 minutos equivale a un sitio que funciona 24/7 durante un mes completo. Cuando alcance su límite, sus sitios se desconectarán hasta que mejore su plan o reduzca el uso. No se cargará el tiempo al usar nodos.",
|
"billingSInfo": "Cuántos sitios puedes usar",
|
||||||
"billingUsersInfo": "Se le cobra por cada usuario en la organización. La facturación se calcula diariamente según el número de cuentas de usuario activas en su órgano.",
|
"billingUsersInfo": "Cuántos usuarios puedes usar",
|
||||||
"billingDomainInfo": "Se le cobra por cada dominio en la organización. La facturación se calcula diariamente en función del número de cuentas de dominio activas en su órgano.",
|
"billingDomainInfo": "Cuántos dominios puedes usar",
|
||||||
"billingRemoteExitNodesInfo": "Se le cobra por cada nodo administrado en la organización. La facturación se calcula diariamente en función del número de nodos activos gestionados en su órgano.",
|
"billingRemoteExitNodesInfo": "Cuántos nodos remotos puedes usar",
|
||||||
|
"billingLicenseKeys": "Claves de licencia",
|
||||||
|
"billingLicenseKeysDescription": "Administrar las suscripciones de su clave de licencia",
|
||||||
|
"billingLicenseSubscription": "Suscripción de licencia",
|
||||||
|
"billingInactive": "Inactivo",
|
||||||
|
"billingLicenseItem": "Licencia",
|
||||||
|
"billingQuantity": "Cantidad",
|
||||||
|
"billingTotal": "total",
|
||||||
|
"billingModifyLicenses": "Modificar suscripción de licencia",
|
||||||
"domainNotFound": "Dominio no encontrado",
|
"domainNotFound": "Dominio no encontrado",
|
||||||
"domainNotFoundDescription": "Este recurso está deshabilitado porque el dominio ya no existe en nuestro sistema. Por favor, establece un nuevo dominio para este recurso.",
|
"domainNotFoundDescription": "Este recurso está deshabilitado porque el dominio ya no existe en nuestro sistema. Por favor, establece un nuevo dominio para este recurso.",
|
||||||
"failed": "Fallido",
|
"failed": "Fallido",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Se requiere número de puerto para recursos no HTTP",
|
"resourcePortRequired": "Se requiere número de puerto para recursos no HTTP",
|
||||||
"resourcePortNotAllowed": "El número de puerto no debe establecerse para recursos HTTP",
|
"resourcePortNotAllowed": "El número de puerto no debe establecerse para recursos HTTP",
|
||||||
"billingPricingCalculatorLink": "Calculadora de Precios",
|
"billingPricingCalculatorLink": "Calculadora de Precios",
|
||||||
|
"billingYourPlan": "Su plan",
|
||||||
|
"billingViewOrModifyPlan": "Ver o modificar su plan actual",
|
||||||
|
"billingViewPlanDetails": "Ver detalles del plan",
|
||||||
|
"billingUsageAndLimits": "Uso y límites",
|
||||||
|
"billingViewUsageAndLimits": "Ver los límites de tu plan y el uso actual",
|
||||||
|
"billingCurrentUsage": "Uso actual",
|
||||||
|
"billingMaximumLimits": "Límites máximos",
|
||||||
|
"billingRemoteNodes": "Nodos remotos",
|
||||||
|
"billingUnlimited": "Ilimitado",
|
||||||
|
"billingPaidLicenseKeys": "Claves de licencia pagadas",
|
||||||
|
"billingManageLicenseSubscription": "Administra tu suscripción para las claves de licencia autoalojadas pagadas",
|
||||||
|
"billingCurrentKeys": "Claves actuales",
|
||||||
|
"billingModifyCurrentPlan": "Modificar plan actual",
|
||||||
|
"billingConfirmUpgrade": "Confirmar actualización",
|
||||||
|
"billingConfirmDowngrade": "Confirmar descenso",
|
||||||
|
"billingConfirmUpgradeDescription": "Estás a punto de actualizar tu plan. Revisa los nuevos límites y precios a continuación.",
|
||||||
|
"billingConfirmDowngradeDescription": "Está a punto de rebajar su plan. Revise los nuevos límites y los precios a continuación.",
|
||||||
|
"billingPlanIncludes": "Plan Incluye",
|
||||||
|
"billingProcessing": "Procesando...",
|
||||||
|
"billingConfirmUpgradeButton": "Confirmar actualización",
|
||||||
|
"billingConfirmDowngradeButton": "Confirmar descenso",
|
||||||
|
"billingLimitViolationWarning": "El uso excede los nuevos límites del plan",
|
||||||
|
"billingLimitViolationDescription": "Su uso actual excede los límites de este plan. Después de degradar, todas las acciones se desactivarán hasta que reduzca el uso dentro de los nuevos límites. Por favor, revisa las siguientes características que están actualmente por encima de los límites. Límites en violación:",
|
||||||
|
"billingFeatureLossWarning": "Aviso de disponibilidad de funcionalidad",
|
||||||
|
"billingFeatureLossDescription": "Al degradar, las características no disponibles en el nuevo plan se desactivarán automáticamente. Algunas configuraciones y configuraciones pueden perderse. Por favor, revise la matriz de precios para entender qué características ya no estarán disponibles.",
|
||||||
|
"billingUsageExceedsLimit": "El uso actual ({current}) supera el límite ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Estoy de acuerdo con los",
|
"IAgreeToThe": "Estoy de acuerdo con los",
|
||||||
"termsOfService": "términos del servicio",
|
"termsOfService": "términos del servicio",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Volver a iniciar sesión estándar",
|
"orgAuthBackToSignIn": "Volver a iniciar sesión estándar",
|
||||||
"orgAuthNoAccount": "¿No tienes una cuenta?",
|
"orgAuthNoAccount": "¿No tienes una cuenta?",
|
||||||
"subscriptionRequiredToUse": "Se requiere una suscripción para utilizar esta función.",
|
"subscriptionRequiredToUse": "Se requiere una suscripción para utilizar esta función.",
|
||||||
|
"mustUpgradeToUse": "Debes actualizar tu suscripción para usar esta función.",
|
||||||
|
"subscriptionRequiredTierToUse": "Esta función requiere <tierLink>{tier}</tierLink> o superior.",
|
||||||
|
"upgradeToTierToUse": "Actualiza a <tierLink>{tier}</tierLink> o superior para usar esta función.",
|
||||||
|
"subscriptionTierTier1": "Inicio",
|
||||||
|
"subscriptionTierTier2": "Equipo",
|
||||||
|
"subscriptionTierTier3": "Negocio",
|
||||||
|
"subscriptionTierEnterprise": "Empresa",
|
||||||
"idpDisabled": "Los proveedores de identidad están deshabilitados.",
|
"idpDisabled": "Los proveedores de identidad están deshabilitados.",
|
||||||
"orgAuthPageDisabled": "La página de autenticación de la organización está deshabilitada.",
|
"orgAuthPageDisabled": "La página de autenticación de la organización está deshabilitada.",
|
||||||
"domainRestartedDescription": "Verificación de dominio reiniciada con éxito",
|
"domainRestartedDescription": "Verificación de dominio reiniciada con éxito",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Obtener una licencia",
|
||||||
|
"description": "Elige un plan y dinos cómo planeas usar Pangolin.",
|
||||||
|
"chooseTier": "Elige tu plan",
|
||||||
|
"viewPricingLink": "Ver precios, características y límites",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Interruptor",
|
||||||
|
"description": "Características de la empresa, 25 usuarios, 25 sitios y soporte comunitario."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Escala",
|
||||||
|
"description": "Características de la empresa, 50 usuarios, 50 sitios y soporte prioritario."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Solo uso personal (licencia gratuita, sin pago)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Continuar con el pago"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Error de pago",
|
||||||
|
"description": "No se pudo iniciar el pago. Por favor, inténtelo de nuevo."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Prioridad",
|
"priority": "Prioridad",
|
||||||
"priorityDescription": "Las rutas de prioridad más alta son evaluadas primero. Prioridad = 100 significa orden automático (decisiones del sistema). Utilice otro número para hacer cumplir la prioridad manual.",
|
"priorityDescription": "Las rutas de prioridad más alta son evaluadas primero. Prioridad = 100 significa orden automático (decisiones del sistema). Utilice otro número para hacer cumplir la prioridad manual.",
|
||||||
"instanceName": "Nombre de instancia",
|
"instanceName": "Nombre de instancia",
|
||||||
@@ -2211,7 +2283,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 Enterprise para utilizar esta funció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>.",
|
||||||
|
"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>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Cortafuegos activado",
|
"firewallEnabled": "Cortafuegos activado",
|
||||||
"autoUpdatesEnabled": "Actualizaciones automáticas habilitadas",
|
"autoUpdatesEnabled": "Actualizaciones automáticas habilitadas",
|
||||||
"tpmAvailable": "TPM disponible",
|
"tpmAvailable": "TPM disponible",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus activado",
|
||||||
"macosSipEnabled": "Protección de integridad del sistema (SIP)",
|
"macosSipEnabled": "Protección de integridad del sistema (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Modo Sigilo Firewall",
|
"macosFirewallStealthMode": "Modo Sigilo Firewall",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Vous {count, plural, =0 {n'} other {} }êtes membre {count, plural, =0 {d'aucune organisation} one {d'une organisation} other {de # organisations}}.",
|
"componentsMember": "Vous {count, plural, =0 {n'} other {} }êtes membre {count, plural, =0 {d'aucune organisation} one {d'une organisation} other {de # organisations}}.",
|
||||||
"componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Veuillez respecter les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
|
"componentsInvalidKey": "Clés de licence invalides ou expirées détectées. Veuillez respecter les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
|
||||||
"dismiss": "Rejeter",
|
"dismiss": "Rejeter",
|
||||||
|
"subscriptionViolationMessage": "Vous dépassez vos limites pour votre forfait actuel. Corrigez le problème en supprimant des sites, des utilisateurs ou d'autres ressources pour rester dans votre forfait.",
|
||||||
|
"subscriptionViolationViewBilling": "Voir la facturation",
|
||||||
"componentsLicenseViolation": "Violation de licence : ce serveur utilise {usedSites} nœuds, ce qui dépasse la limite autorisée de {maxSites} nœuds. Respectez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
|
"componentsLicenseViolation": "Violation de licence : ce serveur utilise {usedSites} nœuds, ce qui dépasse la limite autorisée de {maxSites} nœuds. Respectez les conditions de licence pour continuer à utiliser toutes les fonctionnalités.",
|
||||||
"componentsSupporterMessage": "Merci de soutenir Pangolin en tant que {tier}!",
|
"componentsSupporterMessage": "Merci de soutenir Pangolin en tant que {tier}!",
|
||||||
"inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder n'ait pas été acceptée ou ne soit plus valide.",
|
"inviteErrorNotValid": "Nous sommes désolés, mais il semble que l'invitation à laquelle vous essayez d'accéder n'ait pas été acceptée ou ne soit plus valide.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Augmenter le nombre de sites",
|
"sitestCountIncrease": "Augmenter le nombre de sites",
|
||||||
"idpManage": "Gérer les fournisseurs d'identité",
|
"idpManage": "Gérer les fournisseurs d'identité",
|
||||||
"idpManageDescription": "Voir et gérer les fournisseurs d'identité dans le système",
|
"idpManageDescription": "Voir et gérer les fournisseurs d'identité dans le système",
|
||||||
|
"idpGlobalModeBanner": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérez les IdPs globaux dans le panneau d'administration <adminPanelLink></adminPanelLink>. Pour activer les IdPs par organisation, éditez la configuration du serveur et réglez le mode IdP sur org. <configDocsLink>Voir la documentation</configDocsLink>. Si vous voulez continuer à utiliser les IdPs globaux et faire disparaître cela des paramètres de l'organisation, définissez explicitement le mode à global dans la configuration.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration <adminPanelLink></adminPanelLink>. Pour utiliser les fournisseurs d'identité par organisation, vous devez passer à l'édition Entreprise.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration <adminPanelLink></adminPanelLink>. Pour utiliser les fournisseurs d'identité par organisation, une licence d'entreprise est requise.",
|
||||||
"idpDeletedDescription": "Fournisseur d'identité supprimé avec succès",
|
"idpDeletedDescription": "Fournisseur d'identité supprimé avec succès",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Êtes-vous sûr de vouloir supprimer définitivement le fournisseur d'identité?",
|
"idpQuestionRemove": "Êtes-vous sûr de vouloir supprimer définitivement le fournisseur d'identité?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Vue d'ensemble des limites d'utilisation",
|
"billingUsageLimitsOverview": "Vue d'ensemble des limites d'utilisation",
|
||||||
"billingMonitorUsage": "Surveillez votre consommation par rapport aux limites configurées. Si vous avez besoin d'une augmentation des limites, veuillez nous contacter à support@pangolin.net.",
|
"billingMonitorUsage": "Surveillez votre consommation par rapport aux limites configurées. Si vous avez besoin d'une augmentation des limites, veuillez nous contacter à support@pangolin.net.",
|
||||||
"billingDataUsage": "Utilisation des données",
|
"billingDataUsage": "Utilisation des données",
|
||||||
"billingOnlineTime": "Temps en ligne du site",
|
"billingSites": "Nœuds",
|
||||||
"billingUsers": "Utilisateurs actifs",
|
"billingUsers": "Utilisateurs",
|
||||||
"billingDomains": "Domaines actifs",
|
"billingDomains": "Domaines",
|
||||||
"billingRemoteExitNodes": "Nœuds auto-hébergés actifs",
|
"billingRemoteExitNodes": "Nœuds distants",
|
||||||
"billingNoLimitConfigured": "Aucune limite configurée",
|
"billingNoLimitConfigured": "Aucune limite configurée",
|
||||||
"billingEstimatedPeriod": "Période de facturation estimée",
|
"billingEstimatedPeriod": "Période de facturation estimée",
|
||||||
"billingIncludedUsage": "Utilisation incluse",
|
"billingIncludedUsage": "Utilisation incluse",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Échec pour obtenir l'URL du portail",
|
"billingFailedToGetPortalUrl": "Échec pour obtenir l'URL du portail",
|
||||||
"billingPortalError": "Erreur du portail",
|
"billingPortalError": "Erreur du portail",
|
||||||
"billingDataUsageInfo": "Vous êtes facturé pour toutes les données transférées via vos tunnels sécurisés lorsque vous êtes connecté au cloud. Cela inclut le trafic entrant et sortant sur tous vos sites. Lorsque vous atteignez votre limite, vos sites se déconnecteront jusqu'à ce que vous mettiez à niveau votre plan ou réduisiez l'utilisation. Les données ne sont pas facturées lors de l'utilisation de nœuds.",
|
"billingDataUsageInfo": "Vous êtes facturé pour toutes les données transférées via vos tunnels sécurisés lorsque vous êtes connecté au cloud. Cela inclut le trafic entrant et sortant sur tous vos sites. Lorsque vous atteignez votre limite, vos sites se déconnecteront jusqu'à ce que vous mettiez à niveau votre plan ou réduisiez l'utilisation. Les données ne sont pas facturées lors de l'utilisation de nœuds.",
|
||||||
"billingOnlineTimeInfo": "Vous êtes facturé en fonction de la durée de connexion de vos sites au cloud. Par exemple, 44 640 minutes équivaut à un site fonctionnant 24/7 pendant un mois complet. Lorsque vous atteignez votre limite, vos sites se déconnecteront jusqu'à ce que vous mettiez à niveau votre forfait ou réduisiez votre consommation. Le temps n'est pas facturé lors de l'utilisation de nœuds.",
|
"billingSInfo": "Combien de sites vous pouvez utiliser",
|
||||||
"billingUsersInfo": "Vous êtes facturé pour chaque utilisateur de l'organisation. La facturation est calculée quotidiennement en fonction du nombre de comptes d'utilisateurs actifs dans votre organisation.",
|
"billingUsersInfo": "Combien d'utilisateurs vous pouvez utiliser",
|
||||||
"billingDomainInfo": "Vous êtes facturé pour chaque domaine de l'organisation. La facturation est calculée quotidiennement en fonction du nombre de comptes de domaine actifs dans votre organisation.",
|
"billingDomainInfo": "Combien de domaines vous pouvez utiliser",
|
||||||
"billingRemoteExitNodesInfo": "Vous êtes facturé pour chaque noeud géré dans l'organisation. La facturation est calculée quotidiennement en fonction du nombre de nœuds gérés actifs dans votre organisation.",
|
"billingRemoteExitNodesInfo": "Combien de nœuds distants vous pouvez utiliser",
|
||||||
|
"billingLicenseKeys": "Clés de licence",
|
||||||
|
"billingLicenseKeysDescription": "Gérer vos abonnements à la clé de licence",
|
||||||
|
"billingLicenseSubscription": "Abonnement à la licence",
|
||||||
|
"billingInactive": "Inactif",
|
||||||
|
"billingLicenseItem": "Article de la licence",
|
||||||
|
"billingQuantity": "Quantité",
|
||||||
|
"billingTotal": "total",
|
||||||
|
"billingModifyLicenses": "Modifier l'abonnement à la licence",
|
||||||
"domainNotFound": "Domaine introuvable",
|
"domainNotFound": "Domaine introuvable",
|
||||||
"domainNotFoundDescription": "Cette ressource est désactivée car le domaine n'existe plus dans notre système. Veuillez définir un nouveau domaine pour cette ressource.",
|
"domainNotFoundDescription": "Cette ressource est désactivée car le domaine n'existe plus dans notre système. Veuillez définir un nouveau domaine pour cette ressource.",
|
||||||
"failed": "Échec",
|
"failed": "Échec",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Le numéro de port est requis pour les ressources non-HTTP",
|
"resourcePortRequired": "Le numéro de port est requis pour les ressources non-HTTP",
|
||||||
"resourcePortNotAllowed": "Le numéro de port ne doit pas être défini pour les ressources HTTP",
|
"resourcePortNotAllowed": "Le numéro de port ne doit pas être défini pour les ressources HTTP",
|
||||||
"billingPricingCalculatorLink": "Calculateur de prix",
|
"billingPricingCalculatorLink": "Calculateur de prix",
|
||||||
|
"billingYourPlan": "Votre plan",
|
||||||
|
"billingViewOrModifyPlan": "Voir ou modifier votre forfait actuel",
|
||||||
|
"billingViewPlanDetails": "Voir les détails du plan",
|
||||||
|
"billingUsageAndLimits": "Utilisation et limites",
|
||||||
|
"billingViewUsageAndLimits": "Voir les limites de votre plan et l'utilisation actuelle",
|
||||||
|
"billingCurrentUsage": "Utilisation actuelle",
|
||||||
|
"billingMaximumLimits": "Limites maximum",
|
||||||
|
"billingRemoteNodes": "Nœuds distants",
|
||||||
|
"billingUnlimited": "Illimité",
|
||||||
|
"billingPaidLicenseKeys": "Clés de licence payantes",
|
||||||
|
"billingManageLicenseSubscription": "Gérer votre abonnement pour les clés de licence auto-hébergées payantes",
|
||||||
|
"billingCurrentKeys": "Clés actuelles",
|
||||||
|
"billingModifyCurrentPlan": "Modifier le plan actuel",
|
||||||
|
"billingConfirmUpgrade": "Confirmer la mise à niveau",
|
||||||
|
"billingConfirmDowngrade": "Confirmer la rétrogradation",
|
||||||
|
"billingConfirmUpgradeDescription": "Vous êtes sur le point de mettre à niveau votre offre. Examinez les nouvelles limites et les nouveaux prix ci-dessous.",
|
||||||
|
"billingConfirmDowngradeDescription": "Vous êtes sur le point de rétrograder votre forfait. Examinez les nouvelles limites et les prix ci-dessous.",
|
||||||
|
"billingPlanIncludes": "Le forfait comprend",
|
||||||
|
"billingProcessing": "Traitement en cours...",
|
||||||
|
"billingConfirmUpgradeButton": "Confirmer la mise à niveau",
|
||||||
|
"billingConfirmDowngradeButton": "Confirmer la rétrogradation",
|
||||||
|
"billingLimitViolationWarning": "Utilisation dépassée les nouvelles limites de plan",
|
||||||
|
"billingLimitViolationDescription": "Votre utilisation actuelle dépasse les limites de ce plan. Après rétrogradation, toutes les actions seront désactivées jusqu'à ce que vous réduisiez l'utilisation dans les nouvelles limites. Veuillez consulter les fonctionnalités ci-dessous qui dépassent actuellement les limites. Limites en violation :",
|
||||||
|
"billingFeatureLossWarning": "Avis de disponibilité des fonctionnalités",
|
||||||
|
"billingFeatureLossDescription": "En rétrogradant, les fonctionnalités non disponibles dans le nouveau plan seront automatiquement désactivées. Certains paramètres et configurations peuvent être perdus. Veuillez consulter la matrice de prix pour comprendre quelles fonctionnalités ne seront plus disponibles.",
|
||||||
|
"billingUsageExceedsLimit": "L'utilisation actuelle ({current}) dépasse la limite ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Je suis d'accord avec",
|
"IAgreeToThe": "Je suis d'accord avec",
|
||||||
"termsOfService": "les conditions d'utilisation",
|
"termsOfService": "les conditions d'utilisation",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Retour à la connexion standard",
|
"orgAuthBackToSignIn": "Retour à la connexion standard",
|
||||||
"orgAuthNoAccount": "Vous n'avez pas de compte ?",
|
"orgAuthNoAccount": "Vous n'avez pas de compte ?",
|
||||||
"subscriptionRequiredToUse": "Un abonnement est requis pour utiliser cette fonctionnalité.",
|
"subscriptionRequiredToUse": "Un abonnement est requis pour utiliser cette fonctionnalité.",
|
||||||
|
"mustUpgradeToUse": "Vous devez mettre à jour votre abonnement pour utiliser cette fonctionnalité.",
|
||||||
|
"subscriptionRequiredTierToUse": "Cette fonctionnalité nécessite <tierLink>{tier}</tierLink> ou supérieur.",
|
||||||
|
"upgradeToTierToUse": "Passez à <tierLink>{tier}</tierLink> ou plus pour utiliser cette fonctionnalité.",
|
||||||
|
"subscriptionTierTier1": "Domicile",
|
||||||
|
"subscriptionTierTier2": "Equipe",
|
||||||
|
"subscriptionTierTier3": "Entreprise",
|
||||||
|
"subscriptionTierEnterprise": "Entreprise",
|
||||||
"idpDisabled": "Les fournisseurs d'identité sont désactivés.",
|
"idpDisabled": "Les fournisseurs d'identité sont désactivés.",
|
||||||
"orgAuthPageDisabled": "La page d'authentification de l'organisation est désactivée.",
|
"orgAuthPageDisabled": "La page d'authentification de l'organisation est désactivée.",
|
||||||
"domainRestartedDescription": "La vérification du domaine a été redémarrée avec succès",
|
"domainRestartedDescription": "La vérification du domaine a été redémarrée avec succès",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Obtenir une licence",
|
||||||
|
"description": "Choisissez un plan et dites-nous comment vous comptez utiliser Pangolin.",
|
||||||
|
"chooseTier": "Choisissez votre forfait",
|
||||||
|
"viewPricingLink": "Voir les prix, les fonctionnalités et les limites",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Démarrage",
|
||||||
|
"description": "Fonctionnalités d'entreprise, 25 utilisateurs, 25 sites et un support communautaire."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Échelle",
|
||||||
|
"description": "Fonctionnalités d'entreprise, 50 utilisateurs, 50 sites et une prise en charge prioritaire."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Utilisation personnelle uniquement (licence gratuite — sans checkout)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Continuer vers le paiement"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Erreur de paiement",
|
||||||
|
"description": "Impossible de commencer la commande. Veuillez réessayer."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Priorité",
|
"priority": "Priorité",
|
||||||
"priorityDescription": "Les routes de haute priorité sont évaluées en premier. La priorité = 100 signifie l'ordre automatique (décision du système). Utilisez un autre nombre pour imposer la priorité manuelle.",
|
"priorityDescription": "Les routes de haute priorité sont évaluées en premier. La priorité = 100 signifie l'ordre automatique (décision du système). Utilisez un autre nombre pour imposer la priorité manuelle.",
|
||||||
"instanceName": "Nom de l'instance",
|
"instanceName": "Nom de l'instance",
|
||||||
@@ -2211,7 +2283,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 Entreprise est nécessaire pour utiliser cette fonctionnalité.",
|
"licenseRequiredToUse": "Une licence <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> est nécessaire 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>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Pare-feu activé",
|
"firewallEnabled": "Pare-feu activé",
|
||||||
"autoUpdatesEnabled": "Mises à jour automatiques activées",
|
"autoUpdatesEnabled": "Mises à jour automatiques activées",
|
||||||
"tpmAvailable": "TPM disponible",
|
"tpmAvailable": "TPM disponible",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus activé",
|
||||||
"macosSipEnabled": "Protection contre l'intégrité du système (SIP)",
|
"macosSipEnabled": "Protection contre l'intégrité du système (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Mode furtif du pare-feu",
|
"macosFirewallStealthMode": "Mode furtif du pare-feu",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Sei un membro di {count, plural, =0 {nessuna organizzazione} one {un'organizzazione} other {# organizzazioni}}.",
|
"componentsMember": "Sei un membro di {count, plural, =0 {nessuna organizzazione} one {un'organizzazione} other {# organizzazioni}}.",
|
||||||
"componentsInvalidKey": "Rilevata chiave di licenza non valida o scaduta. Segui i termini di licenza per continuare a utilizzare tutte le funzionalità.",
|
"componentsInvalidKey": "Rilevata chiave di licenza non valida o scaduta. Segui i termini di licenza per continuare a utilizzare tutte le funzionalità.",
|
||||||
"dismiss": "Ignora",
|
"dismiss": "Ignora",
|
||||||
|
"subscriptionViolationMessage": "Hai superato i tuoi limiti per il tuo piano attuale. Correggi il problema rimuovendo siti, utenti o altre risorse per rimanere all'interno del tuo piano.",
|
||||||
|
"subscriptionViolationViewBilling": "Visualizza fatturazione",
|
||||||
"componentsLicenseViolation": "Violazione della licenza: Questo server sta usando i siti {usedSites} che superano il suo limite concesso in licenza per i siti {maxSites} . Segui i termini di licenza per continuare a usare tutte le funzionalità.",
|
"componentsLicenseViolation": "Violazione della licenza: Questo server sta usando i siti {usedSites} che superano il suo limite concesso in licenza per i siti {maxSites} . Segui i termini di licenza per continuare a usare tutte le funzionalità.",
|
||||||
"componentsSupporterMessage": "Grazie per aver supportato Pangolin come {tier}!",
|
"componentsSupporterMessage": "Grazie per aver supportato Pangolin come {tier}!",
|
||||||
"inviteErrorNotValid": "Siamo spiacenti, ma sembra che l'invito che stai cercando di accedere non sia stato accettato o non sia più valido.",
|
"inviteErrorNotValid": "Siamo spiacenti, ma sembra che l'invito che stai cercando di accedere non sia stato accettato o non sia più valido.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Aumenta conteggio siti",
|
"sitestCountIncrease": "Aumenta conteggio siti",
|
||||||
"idpManage": "Gestisci Provider di Identità",
|
"idpManage": "Gestisci Provider di Identità",
|
||||||
"idpManageDescription": "Visualizza e gestisci i provider di identità nel sistema",
|
"idpManageDescription": "Visualizza e gestisci i provider di identità nel sistema",
|
||||||
|
"idpGlobalModeBanner": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Sta utilizzando IdP globali (condivisi in tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione <adminPanelLink></adminPanelLink>. Per abilitare IdP per organizzazione, modificare la configurazione del server e impostare la modalità IdP su org. <configDocsLink>Vedere i documenti</configDocsLink>. Se si desidera continuare a utilizzare IdP globali e far sparire questo dalle impostazioni dell'organizzazione, impostare esplicitamente la modalità globale nella configurazione.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci gli IdP globali nel pannello di amministrazione <adminPanelLink></adminPanelLink>. Per utilizzare i provider di identità per organizzazione, è necessario aggiornare all'edizione Enterprise.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione <adminPanelLink></adminPanelLink>. Per utilizzare provider di identità per organizzazione, è richiesta una licenza Enterprise.",
|
||||||
"idpDeletedDescription": "Provider di identità eliminato con successo",
|
"idpDeletedDescription": "Provider di identità eliminato con successo",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Sei sicuro di voler eliminare definitivamente il provider di identità?",
|
"idpQuestionRemove": "Sei sicuro di voler eliminare definitivamente il provider di identità?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Panoramica dei Limiti di Utilizzo",
|
"billingUsageLimitsOverview": "Panoramica dei Limiti di Utilizzo",
|
||||||
"billingMonitorUsage": "Monitora il tuo utilizzo rispetto ai limiti configurati. Se hai bisogno di aumentare i limiti, contattaci all'indirizzo support@pangolin.net.",
|
"billingMonitorUsage": "Monitora il tuo utilizzo rispetto ai limiti configurati. Se hai bisogno di aumentare i limiti, contattaci all'indirizzo support@pangolin.net.",
|
||||||
"billingDataUsage": "Utilizzo dei Dati",
|
"billingDataUsage": "Utilizzo dei Dati",
|
||||||
"billingOnlineTime": "Tempo Online del Sito",
|
"billingSites": "Siti",
|
||||||
"billingUsers": "Utenti Attivi",
|
"billingUsers": "Utenti",
|
||||||
"billingDomains": "Domini Attivi",
|
"billingDomains": "Domini",
|
||||||
"billingRemoteExitNodes": "Nodi Self-hosted Attivi",
|
"billingRemoteExitNodes": "Nodi Remoti",
|
||||||
"billingNoLimitConfigured": "Nessun limite configurato",
|
"billingNoLimitConfigured": "Nessun limite configurato",
|
||||||
"billingEstimatedPeriod": "Periodo di Fatturazione Stimato",
|
"billingEstimatedPeriod": "Periodo di Fatturazione Stimato",
|
||||||
"billingIncludedUsage": "Utilizzo Incluso",
|
"billingIncludedUsage": "Utilizzo Incluso",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Errore durante l'ottenimento dell'URL del portale",
|
"billingFailedToGetPortalUrl": "Errore durante l'ottenimento dell'URL del portale",
|
||||||
"billingPortalError": "Errore del Portale",
|
"billingPortalError": "Errore del Portale",
|
||||||
"billingDataUsageInfo": "Hai addebitato tutti i dati trasferiti attraverso i tunnel sicuri quando sei connesso al cloud. Questo include sia il traffico in entrata e in uscita attraverso tutti i siti. Quando si raggiunge il limite, i siti si disconnetteranno fino a quando non si aggiorna il piano o si riduce l'utilizzo. I dati non vengono caricati quando si utilizzano nodi.",
|
"billingDataUsageInfo": "Hai addebitato tutti i dati trasferiti attraverso i tunnel sicuri quando sei connesso al cloud. Questo include sia il traffico in entrata e in uscita attraverso tutti i siti. Quando si raggiunge il limite, i siti si disconnetteranno fino a quando non si aggiorna il piano o si riduce l'utilizzo. I dati non vengono caricati quando si utilizzano nodi.",
|
||||||
"billingOnlineTimeInfo": "Ti viene addebitato in base al tempo in cui i tuoi siti rimangono connessi al cloud. Ad esempio, 44,640 minuti è uguale a un sito in esecuzione 24/7 per un mese intero. Quando raggiungi il tuo limite, i tuoi siti si disconnetteranno fino a quando non aggiorni il tuo piano o riduci l'utilizzo. Il tempo non viene caricato quando si usano i nodi.",
|
"billingSInfo": "Quanti siti puoi usare",
|
||||||
"billingUsersInfo": "Sei addebitato per ogni utente nell'organizzazione. La fatturazione viene calcolata quotidianamente in base al numero di account utente attivi nel tuo org.",
|
"billingUsersInfo": "Quanti utenti puoi usare",
|
||||||
"billingDomainInfo": "Sei addebitato per ogni dominio nell'organizzazione. La fatturazione viene calcolata quotidianamente in base al numero di account di dominio attivi nel tuo org.",
|
"billingDomainInfo": "Quanti domini puoi usare",
|
||||||
"billingRemoteExitNodesInfo": "Sei addebitato per ogni nodo gestito nell'organizzazione. La fatturazione viene calcolata quotidianamente in base al numero di nodi gestiti attivi nel tuo org.",
|
"billingRemoteExitNodesInfo": "Quanti nodi remoti puoi usare",
|
||||||
|
"billingLicenseKeys": "Chiavi di Licenza",
|
||||||
|
"billingLicenseKeysDescription": "Gestisci le sottoscrizioni alla chiave di licenza",
|
||||||
|
"billingLicenseSubscription": "Abbonamento Licenza",
|
||||||
|
"billingInactive": "Inattivo",
|
||||||
|
"billingLicenseItem": "Elemento Licenza",
|
||||||
|
"billingQuantity": "Quantità",
|
||||||
|
"billingTotal": "totale",
|
||||||
|
"billingModifyLicenses": "Modifica Abbonamento Licenza",
|
||||||
"domainNotFound": "Domini Non Trovati",
|
"domainNotFound": "Domini Non Trovati",
|
||||||
"domainNotFoundDescription": "Questa risorsa è disabilitata perché il dominio non esiste più nel nostro sistema. Si prega di impostare un nuovo dominio per questa risorsa.",
|
"domainNotFoundDescription": "Questa risorsa è disabilitata perché il dominio non esiste più nel nostro sistema. Si prega di impostare un nuovo dominio per questa risorsa.",
|
||||||
"failed": "Fallito",
|
"failed": "Fallito",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Numero di porta richiesto per risorse non-HTTP",
|
"resourcePortRequired": "Numero di porta richiesto per risorse non-HTTP",
|
||||||
"resourcePortNotAllowed": "Il numero di porta non deve essere impostato per risorse HTTP",
|
"resourcePortNotAllowed": "Il numero di porta non deve essere impostato per risorse HTTP",
|
||||||
"billingPricingCalculatorLink": "Calcolatore di Prezzi",
|
"billingPricingCalculatorLink": "Calcolatore di Prezzi",
|
||||||
|
"billingYourPlan": "Il Tuo Piano",
|
||||||
|
"billingViewOrModifyPlan": "Visualizza o modifica il tuo piano corrente",
|
||||||
|
"billingViewPlanDetails": "Visualizza Dettagli Piano",
|
||||||
|
"billingUsageAndLimits": "Utilizzo e limiti",
|
||||||
|
"billingViewUsageAndLimits": "Visualizza i limiti del tuo piano e l'utilizzo corrente",
|
||||||
|
"billingCurrentUsage": "Utilizzo Corrente",
|
||||||
|
"billingMaximumLimits": "Limiti Massimi",
|
||||||
|
"billingRemoteNodes": "Nodi Remoti",
|
||||||
|
"billingUnlimited": "Illimitato",
|
||||||
|
"billingPaidLicenseKeys": "Chiavi Di Licenza Pagate",
|
||||||
|
"billingManageLicenseSubscription": "Gestisci il tuo abbonamento per le chiavi di licenza self-hosted a pagamento",
|
||||||
|
"billingCurrentKeys": "Tasti Attuali",
|
||||||
|
"billingModifyCurrentPlan": "Modifica Il Piano Corrente",
|
||||||
|
"billingConfirmUpgrade": "Conferma Aggiornamento",
|
||||||
|
"billingConfirmDowngrade": "Conferma Downgrade",
|
||||||
|
"billingConfirmUpgradeDescription": "Stai per aggiornare il tuo piano. Controlla i nuovi limiti e prezzi qui sotto.",
|
||||||
|
"billingConfirmDowngradeDescription": "Stai per effettuare il downgrade del tuo piano. Controlla i nuovi limiti e i prezzi qui sotto.",
|
||||||
|
"billingPlanIncludes": "Piano Include",
|
||||||
|
"billingProcessing": "Elaborazione...",
|
||||||
|
"billingConfirmUpgradeButton": "Conferma Aggiornamento",
|
||||||
|
"billingConfirmDowngradeButton": "Conferma Downgrade",
|
||||||
|
"billingLimitViolationWarning": "Utilizzo Supera I Nuovi Limiti Del Piano",
|
||||||
|
"billingLimitViolationDescription": "Il tuo utilizzo attuale supera i limiti di questo piano. Dopo il downgrading, tutte le azioni saranno disabilitate fino a ridurre l'utilizzo entro i nuovi limiti. Si prega di rivedere le caratteristiche qui sotto che sono attualmente oltre i limiti. Limiti di violazione:",
|
||||||
|
"billingFeatureLossWarning": "Avviso Di Disponibilità Caratteristica",
|
||||||
|
"billingFeatureLossDescription": "Con il downgrading, le funzioni non disponibili nel nuovo piano saranno disattivate automaticamente. Alcune impostazioni e configurazioni potrebbero andare perse. Controlla la matrice dei prezzi per capire quali funzioni non saranno più disponibili.",
|
||||||
|
"billingUsageExceedsLimit": "L'utilizzo corrente ({current}) supera il limite ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Accetto i",
|
"IAgreeToThe": "Accetto i",
|
||||||
"termsOfService": "termini di servizio",
|
"termsOfService": "termini di servizio",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Torna alla modalità di accesso standard",
|
"orgAuthBackToSignIn": "Torna alla modalità di accesso standard",
|
||||||
"orgAuthNoAccount": "Non hai un account?",
|
"orgAuthNoAccount": "Non hai un account?",
|
||||||
"subscriptionRequiredToUse": "Per utilizzare questa funzionalità è necessario un abbonamento.",
|
"subscriptionRequiredToUse": "Per utilizzare questa funzionalità è necessario un abbonamento.",
|
||||||
|
"mustUpgradeToUse": "Devi aggiornare il tuo abbonamento per utilizzare questa funzionalità.",
|
||||||
|
"subscriptionRequiredTierToUse": "Questa funzione richiede <tierLink>{tier}</tierLink> o superiore.",
|
||||||
|
"upgradeToTierToUse": "Aggiorna ad <tierLink>{tier}</tierLink> o superiore per utilizzare questa funzionalità.",
|
||||||
|
"subscriptionTierTier1": "Home",
|
||||||
|
"subscriptionTierTier2": "Squadra",
|
||||||
|
"subscriptionTierTier3": "Business",
|
||||||
|
"subscriptionTierEnterprise": "Impresa",
|
||||||
"idpDisabled": "I provider di identità sono disabilitati.",
|
"idpDisabled": "I provider di identità sono disabilitati.",
|
||||||
"orgAuthPageDisabled": "La pagina di autenticazione dell'organizzazione è disabilitata.",
|
"orgAuthPageDisabled": "La pagina di autenticazione dell'organizzazione è disabilitata.",
|
||||||
"domainRestartedDescription": "Verifica del dominio riavviata con successo",
|
"domainRestartedDescription": "Verifica del dominio riavviata con successo",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Ottieni una licenza",
|
||||||
|
"description": "Scegli un piano e ci dica come intendi usare Pangolin.",
|
||||||
|
"chooseTier": "Scegli il tuo piano",
|
||||||
|
"viewPricingLink": "Vedi prezzi, funzionalità e limiti",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Avviatore",
|
||||||
|
"description": "Caratteristiche aziendali, 25 utenti, 25 siti e supporto alla comunità."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Scala",
|
||||||
|
"description": "Funzionalità aziendali, 50 utenti, 50 siti e supporto prioritario."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Solo uso personale (licenza gratuita — nessun checkout)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Continua al Checkout"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Errore di pagamento",
|
||||||
|
"description": "Impossibile avviare il checkout. Per favore riprova."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Priorità",
|
"priority": "Priorità",
|
||||||
"priorityDescription": "I percorsi prioritari più alti sono valutati prima. Priorità = 100 significa ordinamento automatico (decidi di sistema). Usa un altro numero per applicare la priorità manuale.",
|
"priorityDescription": "I percorsi prioritari più alti sono valutati prima. Priorità = 100 significa ordinamento automatico (decidi di sistema). Usa un altro numero per applicare la priorità manuale.",
|
||||||
"instanceName": "Nome Istanza",
|
"instanceName": "Nome Istanza",
|
||||||
@@ -2211,7 +2283,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 Enterprise.",
|
"licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Questa funzionalità è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
|
"ossEnterpriseEditionRequired": "L' <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> è necessaria per utilizzare questa funzione. Questa funzionalità è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Firewall Abilitato",
|
"firewallEnabled": "Firewall Abilitato",
|
||||||
"autoUpdatesEnabled": "Aggiornamenti Automatici Abilitati",
|
"autoUpdatesEnabled": "Aggiornamenti Automatici Abilitati",
|
||||||
"tpmAvailable": "TPM Disponibile",
|
"tpmAvailable": "TPM Disponibile",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus Abilitato",
|
||||||
"macosSipEnabled": "Protezione Dell'Integrità Del Sistema (Sip)",
|
"macosSipEnabled": "Protezione Dell'Integrità Del Sistema (Sip)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Modo Furtivo Del Firewall",
|
"macosFirewallStealthMode": "Modo Furtivo Del Firewall",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "당신은 {count, plural, =0 {조직이 없습니다} one {하나의 조직} other {# 개의 조직}}의 구성원입니다.",
|
"componentsMember": "당신은 {count, plural, =0 {조직이 없습니다} one {하나의 조직} other {# 개의 조직}}의 구성원입니다.",
|
||||||
"componentsInvalidKey": "유효하지 않거나 만료된 라이센스 키가 감지되었습니다. 모든 기능을 계속 사용하려면 라이센스 조건을 따르십시오.",
|
"componentsInvalidKey": "유효하지 않거나 만료된 라이센스 키가 감지되었습니다. 모든 기능을 계속 사용하려면 라이센스 조건을 따르십시오.",
|
||||||
"dismiss": "해제",
|
"dismiss": "해제",
|
||||||
|
"subscriptionViolationMessage": "현재 계획의 한계를 초과했습니다. 사이트, 사용자 또는 기타 리소스를 제거하여 계획 내에 머물도록 해결하세요.",
|
||||||
|
"subscriptionViolationViewBilling": "청구 보기",
|
||||||
"componentsLicenseViolation": "라이센스 위반: 이 서버는 {usedSites} 사이트를 사용하고 있으며, 이는 {maxSites} 사이트의 라이센스 한도를 초과합니다. 모든 기능을 계속 사용하려면 라이센스 조건을 따르십시오.",
|
"componentsLicenseViolation": "라이센스 위반: 이 서버는 {usedSites} 사이트를 사용하고 있으며, 이는 {maxSites} 사이트의 라이센스 한도를 초과합니다. 모든 기능을 계속 사용하려면 라이센스 조건을 따르십시오.",
|
||||||
"componentsSupporterMessage": "{tier}로 판골린을 지원해 주셔서 감사합니다!",
|
"componentsSupporterMessage": "{tier}로 판골린을 지원해 주셔서 감사합니다!",
|
||||||
"inviteErrorNotValid": "죄송하지만, 접근하려는 초대가 수락되지 않았거나 더 이상 유효하지 않은 것 같습니다.",
|
"inviteErrorNotValid": "죄송하지만, 접근하려는 초대가 수락되지 않았거나 더 이상 유효하지 않은 것 같습니다.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "사이트 수 증가",
|
"sitestCountIncrease": "사이트 수 증가",
|
||||||
"idpManage": "아이덴티티 공급자 관리",
|
"idpManage": "아이덴티티 공급자 관리",
|
||||||
"idpManageDescription": "시스템에서 ID 제공자를 보고 관리합니다",
|
"idpManageDescription": "시스템에서 ID 제공자를 보고 관리합니다",
|
||||||
|
"idpGlobalModeBanner": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. <adminPanelLink>관리자 패널</adminPanelLink>에서 글로벌 IdP를 관리하십시오. 조직별 IdP를 활성화하려면 서버 설정을 편집하고 IdP 모드를 조직으로 설정하십시오. <configDocsLink>문서 보기</configDocsLink>. 글로벌 IdP 사용을 계속하고 조직 설정에서 이 항목을 제거하려면 설정에서 모드를 글로벌로 명시적으로 설정하십시오.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. <adminPanelLink>관리자 패널</adminPanelLink>에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 Enterprise 에디션으로 업그레이드해야 합니다.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. <adminPanelLink>관리자 패널</adminPanelLink>에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 엔터프라이즈 라이선스가 필요합니다.",
|
||||||
"idpDeletedDescription": "신원 공급자가 성공적으로 삭제되었습니다",
|
"idpDeletedDescription": "신원 공급자가 성공적으로 삭제되었습니다",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "아이덴티티 공급자를 영구적으로 삭제하시겠습니까?",
|
"idpQuestionRemove": "아이덴티티 공급자를 영구적으로 삭제하시겠습니까?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "사용 한도 개요",
|
"billingUsageLimitsOverview": "사용 한도 개요",
|
||||||
"billingMonitorUsage": "설정된 한도에 대한 사용량을 모니터링합니다. 한도를 늘려야 하는 경우 support@pangolin.net로 연락하십시오.",
|
"billingMonitorUsage": "설정된 한도에 대한 사용량을 모니터링합니다. 한도를 늘려야 하는 경우 support@pangolin.net로 연락하십시오.",
|
||||||
"billingDataUsage": "데이터 사용량",
|
"billingDataUsage": "데이터 사용량",
|
||||||
"billingOnlineTime": "사이트 온라인 시간",
|
"billingSites": "사이트",
|
||||||
"billingUsers": "활성 사용자",
|
"billingUsers": "사용자",
|
||||||
"billingDomains": "활성 도메인",
|
"billingDomains": "도메인",
|
||||||
"billingRemoteExitNodes": "활성 자체 호스팅 노드",
|
"billingRemoteExitNodes": "원격 노드",
|
||||||
"billingNoLimitConfigured": "구성된 한도가 없습니다.",
|
"billingNoLimitConfigured": "구성된 한도가 없습니다.",
|
||||||
"billingEstimatedPeriod": "예상 청구 기간",
|
"billingEstimatedPeriod": "예상 청구 기간",
|
||||||
"billingIncludedUsage": "포함 사용량",
|
"billingIncludedUsage": "포함 사용량",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "포털 URL을 가져오는 데 실패했습니다.",
|
"billingFailedToGetPortalUrl": "포털 URL을 가져오는 데 실패했습니다.",
|
||||||
"billingPortalError": "포털 오류",
|
"billingPortalError": "포털 오류",
|
||||||
"billingDataUsageInfo": "클라우드에 연결할 때 보안 터널을 통해 전송된 모든 데이터에 대해 비용이 청구됩니다. 여기에는 모든 사이트의 들어오고 나가는 트래픽이 포함됩니다. 사용량 한도에 도달하면 플랜을 업그레이드하거나 사용량을 줄일 때까지 사이트가 연결 해제됩니다. 노드를 사용하는 경우 데이터는 요금이 청구되지 않습니다.",
|
"billingDataUsageInfo": "클라우드에 연결할 때 보안 터널을 통해 전송된 모든 데이터에 대해 비용이 청구됩니다. 여기에는 모든 사이트의 들어오고 나가는 트래픽이 포함됩니다. 사용량 한도에 도달하면 플랜을 업그레이드하거나 사용량을 줄일 때까지 사이트가 연결 해제됩니다. 노드를 사용하는 경우 데이터는 요금이 청구되지 않습니다.",
|
||||||
"billingOnlineTimeInfo": "사이트가 클라우드에 연결된 시간에 따라 요금이 청구됩니다. 예를 들어, 44,640분은 사이트가 한 달 내내 24시간 작동하는 것과 같습니다. 사용량 한도에 도달하면 플랜을 업그레이드하거나 사용량을 줄일 때까지 사이트가 연결 해제됩니다. 노드를 사용할 때 시간은 요금이 청구되지 않습니다.",
|
"billingSInfo": "사용할 수 있는 사이트 수",
|
||||||
"billingUsersInfo": "조직의 사용자마다 요금이 청구됩니다. 청구는 조직의 활성 사용자 계정 수에 따라 매일 계산됩니다.",
|
"billingUsersInfo": "사용할 수 있는 사용자 수",
|
||||||
"billingDomainInfo": "조직의 도메인마다 요금이 청구됩니다. 청구는 조직의 활성 도메인 계정 수에 따라 매일 계산됩니다.",
|
"billingDomainInfo": "사용할 수 있는 도메인 수",
|
||||||
"billingRemoteExitNodesInfo": "조직의 관리 노드마다 요금이 청구됩니다. 청구는 조직의 활성 관리 노드 수에 따라 매일 계산됩니다.",
|
"billingRemoteExitNodesInfo": "사용할 수 있는 원격 노드 수",
|
||||||
|
"billingLicenseKeys": "라이센스 키",
|
||||||
|
"billingLicenseKeysDescription": "라이센스 키 구독을 관리하세요",
|
||||||
|
"billingLicenseSubscription": "라이센스 구독",
|
||||||
|
"billingInactive": "비활성화됨",
|
||||||
|
"billingLicenseItem": "라이센스 항목",
|
||||||
|
"billingQuantity": "수량",
|
||||||
|
"billingTotal": "총계",
|
||||||
|
"billingModifyLicenses": "라이센스 구독 수정",
|
||||||
"domainNotFound": "도메인을 찾을 수 없습니다",
|
"domainNotFound": "도메인을 찾을 수 없습니다",
|
||||||
"domainNotFoundDescription": "이 리소스는 도메인이 더 이상 시스템에 존재하지 않아 비활성화되었습니다. 이 리소스에 대한 새 도메인을 설정하세요.",
|
"domainNotFoundDescription": "이 리소스는 도메인이 더 이상 시스템에 존재하지 않아 비활성화되었습니다. 이 리소스에 대한 새 도메인을 설정하세요.",
|
||||||
"failed": "실패",
|
"failed": "실패",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "HTTP 리소스가 아닌 경우 포트 번호가 필요합니다",
|
"resourcePortRequired": "HTTP 리소스가 아닌 경우 포트 번호가 필요합니다",
|
||||||
"resourcePortNotAllowed": "HTTP 리소스에 대해 포트 번호를 설정하지 마세요",
|
"resourcePortNotAllowed": "HTTP 리소스에 대해 포트 번호를 설정하지 마세요",
|
||||||
"billingPricingCalculatorLink": "가격 계산기",
|
"billingPricingCalculatorLink": "가격 계산기",
|
||||||
|
"billingYourPlan": "귀하의 계획",
|
||||||
|
"billingViewOrModifyPlan": "현재 계획 보기 또는 수정",
|
||||||
|
"billingViewPlanDetails": "계획 세부정보 보기",
|
||||||
|
"billingUsageAndLimits": "사용량 및 제한",
|
||||||
|
"billingViewUsageAndLimits": "계획의 제한 및 현재 사용량 보기",
|
||||||
|
"billingCurrentUsage": "현재 사용량",
|
||||||
|
"billingMaximumLimits": "최대 제한",
|
||||||
|
"billingRemoteNodes": "원격 노드",
|
||||||
|
"billingUnlimited": "무제한",
|
||||||
|
"billingPaidLicenseKeys": "유료 라이센스 키",
|
||||||
|
"billingManageLicenseSubscription": "유료 독립 호스트 라이센스 키를 위한 구독 관리",
|
||||||
|
"billingCurrentKeys": "현재 키",
|
||||||
|
"billingModifyCurrentPlan": "현재 계획 수정",
|
||||||
|
"billingConfirmUpgrade": "업그레이드 확인",
|
||||||
|
"billingConfirmDowngrade": "다운그레이드 확인",
|
||||||
|
"billingConfirmUpgradeDescription": "계획을 업그레이드하려고 합니다. 아래의 새로운 제한 및 가격을 검토하세요.",
|
||||||
|
"billingConfirmDowngradeDescription": "계획을 다운그레이드하려고 합니다. 아래의 새로운 제한 및 가격을 검토하세요.",
|
||||||
|
"billingPlanIncludes": "계획 포함",
|
||||||
|
"billingProcessing": "처리 중...",
|
||||||
|
"billingConfirmUpgradeButton": "업그레이드 확인",
|
||||||
|
"billingConfirmDowngradeButton": "다운그레이드 확인",
|
||||||
|
"billingLimitViolationWarning": "사용량이 새 계획의 제한을 초과합니다.",
|
||||||
|
"billingLimitViolationDescription": "현재 사용량이 이 계획의 제한을 초과합니다. 다운그레이드 후 모든 작업은 새로운 제한 내로 사용량을 줄일 때까지 비활성화됩니다. 현재 초과된 제한 특징들을 검토하세요. 위반된 제한:",
|
||||||
|
"billingFeatureLossWarning": "기능 가용성 알림",
|
||||||
|
"billingFeatureLossDescription": "다운그레이드함으로써 새 계획에서 사용할 수 없는 기능은 자동으로 비활성화됩니다. 일부 설정 및 구성은 손실될 수 있습니다. 어떤 기능들이 더 이상 사용 불가능한지 이해하기 위해 가격표를 검토하세요.",
|
||||||
|
"billingUsageExceedsLimit": "현재 사용량 ({current})이 제한 ({limit})을 초과합니다",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "동의합니다",
|
"IAgreeToThe": "동의합니다",
|
||||||
"termsOfService": "서비스 약관",
|
"termsOfService": "서비스 약관",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "표준 로그인을 통해 돌아가기",
|
"orgAuthBackToSignIn": "표준 로그인을 통해 돌아가기",
|
||||||
"orgAuthNoAccount": "계정이 없으신가요?",
|
"orgAuthNoAccount": "계정이 없으신가요?",
|
||||||
"subscriptionRequiredToUse": "이 기능을 사용하려면 구독이 필요합니다.",
|
"subscriptionRequiredToUse": "이 기능을 사용하려면 구독이 필요합니다.",
|
||||||
|
"mustUpgradeToUse": "이 기능을 사용하려면 구독을 업그레이드해야 합니다.",
|
||||||
|
"subscriptionRequiredTierToUse": "이 기능을 사용하려면 <tierLink>{tier}</tierLink> 이상의 등급이 필요합니다.",
|
||||||
|
"upgradeToTierToUse": "이 기능을 사용하려면 <tierLink>{tier}</tierLink> 이상으로 업그레이드하세요.",
|
||||||
|
"subscriptionTierTier1": "홈",
|
||||||
|
"subscriptionTierTier2": "팀",
|
||||||
|
"subscriptionTierTier3": "비즈니스",
|
||||||
|
"subscriptionTierEnterprise": "기업",
|
||||||
"idpDisabled": "신원 공급자가 비활성화되었습니다.",
|
"idpDisabled": "신원 공급자가 비활성화되었습니다.",
|
||||||
"orgAuthPageDisabled": "조직 인증 페이지가 비활성화되었습니다.",
|
"orgAuthPageDisabled": "조직 인증 페이지가 비활성화되었습니다.",
|
||||||
"domainRestartedDescription": "도메인 인증이 성공적으로 재시작되었습니다.",
|
"domainRestartedDescription": "도메인 인증이 성공적으로 재시작되었습니다.",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "라이센스 가져오기",
|
||||||
|
"description": "계획을 선택하고 Pangolin을 어떻게 사용할지 알려주세요.",
|
||||||
|
"chooseTier": "계획 선택",
|
||||||
|
"viewPricingLink": "가격, 기능 및 제한 보기",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "스타터",
|
||||||
|
"description": "기업 기능, 25명의 사용자, 25개의 사이트, 커뮤니티 지원."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "스케일",
|
||||||
|
"description": "기업 기능, 50명의 사용자, 50개의 사이트, 우선 지원."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "개인 사용 전용 (무료 라이센스 — 체크아웃 없음)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "결제로 진행"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "체크아웃 오류",
|
||||||
|
"description": "체크아웃을 시작할 수 없습니다. 다시 시도하세요."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "우선순위",
|
"priority": "우선순위",
|
||||||
"priorityDescription": "우선 순위가 높은 경로가 먼저 평가됩니다. 우선 순위 = 100은 자동 정렬(시스템 결정)이 의미합니다. 수동 우선 순위를 적용하려면 다른 숫자를 사용하세요.",
|
"priorityDescription": "우선 순위가 높은 경로가 먼저 평가됩니다. 우선 순위 = 100은 자동 정렬(시스템 결정)이 의미합니다. 수동 우선 순위를 적용하려면 다른 숫자를 사용하세요.",
|
||||||
"instanceName": "인스턴스 이름",
|
"instanceName": "인스턴스 이름",
|
||||||
@@ -2211,7 +2283,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "다음 연도 말",
|
"logRetentionEndOfFollowingYear": "다음 연도 말",
|
||||||
"actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다",
|
"actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다",
|
||||||
"accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다",
|
"accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다",
|
||||||
"licenseRequiredToUse": "이 기능을 사용하려면 Enterprise 라이선스가 필요합니다.",
|
"licenseRequiredToUse": "이 기능을 사용하려면 <enterpriseLicenseLink>엔터프라이즈 에디션</enterpriseLicenseLink> 라이선스가 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다.",
|
||||||
|
"ossEnterpriseEditionRequired": "이 기능을 사용하려면 <enterpriseEditionLink>엔터프라이즈 에디션</enterpriseEditionLink>이 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다.",
|
||||||
"certResolver": "인증서 해결사",
|
"certResolver": "인증서 해결사",
|
||||||
"certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.",
|
"certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.",
|
||||||
"selectCertResolver": "인증서 해결사 선택",
|
"selectCertResolver": "인증서 해결사 선택",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "방화벽 활성화",
|
"firewallEnabled": "방화벽 활성화",
|
||||||
"autoUpdatesEnabled": "자동 업데이트 활성화",
|
"autoUpdatesEnabled": "자동 업데이트 활성화",
|
||||||
"tpmAvailable": "TPM 사용 가능",
|
"tpmAvailable": "TPM 사용 가능",
|
||||||
|
"windowsAntivirusEnabled": "안티바이러스 활성화됨",
|
||||||
"macosSipEnabled": "시스템 무결성 보호 (SIP)",
|
"macosSipEnabled": "시스템 무결성 보호 (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "방화벽 스텔스 모드",
|
"macosFirewallStealthMode": "방화벽 스텔스 모드",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Du er {count, plural, =0 {ikke medlem av noen organisasjoner} one {medlem av en organisasjon} other {medlem av # organisasjoner}}.",
|
"componentsMember": "Du er {count, plural, =0 {ikke medlem av noen organisasjoner} one {medlem av en organisasjon} other {medlem av # organisasjoner}}.",
|
||||||
"componentsInvalidKey": "Ugyldig eller utgått lisensnøkkel oppdaget. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
"componentsInvalidKey": "Ugyldig eller utgått lisensnøkkel oppdaget. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
||||||
"dismiss": "Avvis",
|
"dismiss": "Avvis",
|
||||||
|
"subscriptionViolationMessage": "Du er utenfor grensen for gjeldende plan. Rett problemet ved å fjerne nettsteder, brukere eller andre ressurser for å bli innenfor planen din.",
|
||||||
|
"subscriptionViolationViewBilling": "Vis fakturering",
|
||||||
"componentsLicenseViolation": "Lisens Brudd: Denne serveren bruker {usedSites} områder som overskrider den lisensierte grenser av {maxSites} områder. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
"componentsLicenseViolation": "Lisens Brudd: Denne serveren bruker {usedSites} områder som overskrider den lisensierte grenser av {maxSites} områder. Følg lisensvilkårene for å fortsette å kunne bruke alle funksjonene.",
|
||||||
"componentsSupporterMessage": "Takk for at du støtter Pangolin som en {tier}!",
|
"componentsSupporterMessage": "Takk for at du støtter Pangolin som en {tier}!",
|
||||||
"inviteErrorNotValid": "Beklager, men det ser ut som invitasjonen du prøver å bruke ikke har blitt akseptert eller ikke er gyldig lenger.",
|
"inviteErrorNotValid": "Beklager, men det ser ut som invitasjonen du prøver å bruke ikke har blitt akseptert eller ikke er gyldig lenger.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Øk antall områder",
|
"sitestCountIncrease": "Øk antall områder",
|
||||||
"idpManage": "Administrer Identitetsleverandører",
|
"idpManage": "Administrer Identitetsleverandører",
|
||||||
"idpManageDescription": "Vis og administrer identitetsleverandører i systemet",
|
"idpManageDescription": "Vis og administrer identitetsleverandører i systemet",
|
||||||
|
"idpGlobalModeBanner": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdP (delt over alle organisasjoner). Administrer globale IdP'er i <adminPanelLink>admin-panelet</adminPanelLink>. For å aktivere IdP per organisasjon, rediger serverkonfigurasjonen og sett IdP-modus til org. <configDocsLink>Se dokumentasjonen</configDocsLink>. Hvis du vil fortsette å bruke globale IdPs og få denne til å forsvinne fra organisasjonens innstillinger, satt eksplisitt modusen til global i konfigurasjonen.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i <adminPanelLink>administrasjons-panelet</adminPanelLink>. For å bruke identitetsleverandører per organisasjon, må du oppgradere til Enterprise-utgaven.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Identitetsleverandører (IdPs) per organisasjon er deaktivert på denne serveren. Den bruker globale IdPs (delt på tvers av alle organisasjoner). Administrer globale IdPs i <adminPanelLink>administrasjons-panelet</adminPanelLink>. For å bruke identitetsleverandører per organisasjon, kreves en Enterprise-lisens.",
|
||||||
"idpDeletedDescription": "Identitetsleverandør slettet vellykket",
|
"idpDeletedDescription": "Identitetsleverandør slettet vellykket",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?",
|
"idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Oversikt over bruksgrenser",
|
"billingUsageLimitsOverview": "Oversikt over bruksgrenser",
|
||||||
"billingMonitorUsage": "Overvåk bruken din i forhold til konfigurerte grenser. Hvis du trenger økte grenser, vennligst kontakt support@pangolin.net.",
|
"billingMonitorUsage": "Overvåk bruken din i forhold til konfigurerte grenser. Hvis du trenger økte grenser, vennligst kontakt support@pangolin.net.",
|
||||||
"billingDataUsage": "Databruk",
|
"billingDataUsage": "Databruk",
|
||||||
"billingOnlineTime": "Online tid for nettsteder",
|
"billingSites": "Områder",
|
||||||
"billingUsers": "Aktive brukere",
|
"billingUsers": "Brukere",
|
||||||
"billingDomains": "Aktive domener",
|
"billingDomains": "Domener",
|
||||||
"billingRemoteExitNodes": "Aktive selvstyrte noder",
|
"billingRemoteExitNodes": "Eksterne Noder",
|
||||||
"billingNoLimitConfigured": "Ingen grense konfigurert",
|
"billingNoLimitConfigured": "Ingen grense konfigurert",
|
||||||
"billingEstimatedPeriod": "Estimert faktureringsperiode",
|
"billingEstimatedPeriod": "Estimert faktureringsperiode",
|
||||||
"billingIncludedUsage": "Inkludert Bruk",
|
"billingIncludedUsage": "Inkludert Bruk",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Mislyktes å hente portal URL",
|
"billingFailedToGetPortalUrl": "Mislyktes å hente portal URL",
|
||||||
"billingPortalError": "Portalfeil",
|
"billingPortalError": "Portalfeil",
|
||||||
"billingDataUsageInfo": "Du er ladet for all data som overføres gjennom dine sikre tunneler når du er koblet til skyen. Dette inkluderer både innkommende og utgående trafikk på alle dine nettsteder. Når du når grensen din, vil sidene koble fra til du oppgraderer planen eller reduserer bruken. Data belastes ikke ved bruk av EK-grupper.",
|
"billingDataUsageInfo": "Du er ladet for all data som overføres gjennom dine sikre tunneler når du er koblet til skyen. Dette inkluderer både innkommende og utgående trafikk på alle dine nettsteder. Når du når grensen din, vil sidene koble fra til du oppgraderer planen eller reduserer bruken. Data belastes ikke ved bruk av EK-grupper.",
|
||||||
"billingOnlineTimeInfo": "Du er ladet på hvor lenge sidene dine forblir koblet til skyen. For eksempel tilsvarer 44,640 minutter ett nettsted som går 24/7 i en hel måned. Når du når grensen din, vil sidene koble fra til du oppgraderer planen eller reduserer bruken. Tid belastes ikke når du bruker noder.",
|
"billingSInfo": "Hvor mange nettsteder du kan bruke",
|
||||||
"billingUsersInfo": "Du lades for hver bruker i organisasjonen. Fakturering beregnes daglig basert på antall aktive brukerkontoer i dine org.",
|
"billingUsersInfo": "Hvor mange brukere du kan bruke",
|
||||||
"billingDomainInfo": "Du lades for hvert domene i organisasjonen. Fakturering beregnes daglig basert på antallet aktive domenekontoer i din org.",
|
"billingDomainInfo": "Hvor mange domener du kan bruke",
|
||||||
"billingRemoteExitNodesInfo": "Du lades for hver håndterte node i organisasjonen. Fakturering beregnes daglig basert på antallet aktive håndterte noder i dine org.",
|
"billingRemoteExitNodesInfo": "Hvor mange fjernnoder du kan bruke",
|
||||||
|
"billingLicenseKeys": "Lisensnøkler",
|
||||||
|
"billingLicenseKeysDescription": "Administrer dine lisensnøkkelabonnementer",
|
||||||
|
"billingLicenseSubscription": "Lisens abonnement",
|
||||||
|
"billingInactive": "Inaktiv",
|
||||||
|
"billingLicenseItem": "Lisens artikkel",
|
||||||
|
"billingQuantity": "Antall",
|
||||||
|
"billingTotal": "totalt",
|
||||||
|
"billingModifyLicenses": "Endre lisensabonnement",
|
||||||
"domainNotFound": "Domene ikke funnet",
|
"domainNotFound": "Domene ikke funnet",
|
||||||
"domainNotFoundDescription": "Denne ressursen er deaktivert fordi domenet ikke lenger eksisterer i systemet vårt. Vennligst angi et nytt domene for denne ressursen.",
|
"domainNotFoundDescription": "Denne ressursen er deaktivert fordi domenet ikke lenger eksisterer i systemet vårt. Vennligst angi et nytt domene for denne ressursen.",
|
||||||
"failed": "Mislyktes",
|
"failed": "Mislyktes",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Portnummer er påkrevd for ikke-HTTP-ressurser",
|
"resourcePortRequired": "Portnummer er påkrevd for ikke-HTTP-ressurser",
|
||||||
"resourcePortNotAllowed": "Portnummer skal ikke angis for HTTP-ressurser",
|
"resourcePortNotAllowed": "Portnummer skal ikke angis for HTTP-ressurser",
|
||||||
"billingPricingCalculatorLink": "Pris Kalkulator",
|
"billingPricingCalculatorLink": "Pris Kalkulator",
|
||||||
|
"billingYourPlan": "Din funksjonsplan",
|
||||||
|
"billingViewOrModifyPlan": "Vis eller endre gjeldende abonnement",
|
||||||
|
"billingViewPlanDetails": "Se Planleggings detaljer",
|
||||||
|
"billingUsageAndLimits": "Bruk og grenser",
|
||||||
|
"billingViewUsageAndLimits": "Se planets grenser og gjeldende bruk",
|
||||||
|
"billingCurrentUsage": "Gjeldende bruk",
|
||||||
|
"billingMaximumLimits": "Maks antall grenser",
|
||||||
|
"billingRemoteNodes": "Eksterne Noder",
|
||||||
|
"billingUnlimited": "Ubegrenset",
|
||||||
|
"billingPaidLicenseKeys": "Betalt lisensnøkler",
|
||||||
|
"billingManageLicenseSubscription": "Administrer abonnementet for betalte lisensnøkler selv hostet",
|
||||||
|
"billingCurrentKeys": "Nåværende nøkler",
|
||||||
|
"billingModifyCurrentPlan": "Endre gjeldende plan",
|
||||||
|
"billingConfirmUpgrade": "Bekreft oppgradering",
|
||||||
|
"billingConfirmDowngrade": "Bekreft nedgradering",
|
||||||
|
"billingConfirmUpgradeDescription": "Du er i ferd med å oppgradere abonnementet ditt. Gå gjennom de nye grensene og pris nedenfor.",
|
||||||
|
"billingConfirmDowngradeDescription": "Du er i ferd med å nedgradere planen din. Gå gjennom de nye grensene og pris nedenfor.",
|
||||||
|
"billingPlanIncludes": "Plan Inkluderer",
|
||||||
|
"billingProcessing": "Behandler...",
|
||||||
|
"billingConfirmUpgradeButton": "Bekreft oppgradering",
|
||||||
|
"billingConfirmDowngradeButton": "Bekreft nedgradering",
|
||||||
|
"billingLimitViolationWarning": "Bruk overbelastede grenser for ny plan",
|
||||||
|
"billingLimitViolationDescription": "Gjeldende bruk overskrider grensene for denne planen. Etter nedgradering vil alle handlinger deaktiveres inntil du reduserer bruken innenfor de nye grensene. Vennligst se igjennom funksjonene under som er i øyeblikket over grensene. Begrensninger i vold:",
|
||||||
|
"billingFeatureLossWarning": "Fremhev tilgjengelig varsel",
|
||||||
|
"billingFeatureLossDescription": "Ved å nedgradere vil funksjoner som ikke er tilgjengelige i den nye planen automatisk bli deaktivert. Noen innstillinger og konfigurasjoner kan gå tapt. Vennligst gjennomgå prismatrisen for å forstå hvilke funksjoner som ikke lenger vil være tilgjengelige.",
|
||||||
|
"billingUsageExceedsLimit": "Gjeldende bruk ({current}) overskrider grensen ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Jeg godtar",
|
"IAgreeToThe": "Jeg godtar",
|
||||||
"termsOfService": "brukervilkårene",
|
"termsOfService": "brukervilkårene",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Tilbake til standard innlogging",
|
"orgAuthBackToSignIn": "Tilbake til standard innlogging",
|
||||||
"orgAuthNoAccount": "Har du ikke konto?",
|
"orgAuthNoAccount": "Har du ikke konto?",
|
||||||
"subscriptionRequiredToUse": "Et abonnement er påkrevd for å bruke denne funksjonen.",
|
"subscriptionRequiredToUse": "Et abonnement er påkrevd for å bruke denne funksjonen.",
|
||||||
|
"mustUpgradeToUse": "Du må oppgradere ditt abonnement for å bruke denne funksjonen.",
|
||||||
|
"subscriptionRequiredTierToUse": "Denne funksjonen krever <tierLink>{tier}</tierLink> eller høyere.",
|
||||||
|
"upgradeToTierToUse": "Oppgrader til <tierLink>{tier}</tierLink> eller høyere for å bruke denne funksjonen.",
|
||||||
|
"subscriptionTierTier1": "Hjem",
|
||||||
|
"subscriptionTierTier2": "Lag",
|
||||||
|
"subscriptionTierTier3": "Forretninger",
|
||||||
|
"subscriptionTierEnterprise": "Bedrift",
|
||||||
"idpDisabled": "Identitetsleverandører er deaktivert.",
|
"idpDisabled": "Identitetsleverandører er deaktivert.",
|
||||||
"orgAuthPageDisabled": "Informasjons-siden for organisasjon er deaktivert.",
|
"orgAuthPageDisabled": "Informasjons-siden for organisasjon er deaktivert.",
|
||||||
"domainRestartedDescription": "Domene-verifiseringen ble startet på nytt",
|
"domainRestartedDescription": "Domene-verifiseringen ble startet på nytt",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Få en lisens",
|
||||||
|
"description": "Velg en plan og fortell oss hvordan du planlegger å bruke Pangolin.",
|
||||||
|
"chooseTier": "Velg din funksjonsplan",
|
||||||
|
"viewPricingLink": "Se prising, egenskaper og grenser",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Begynner",
|
||||||
|
"description": "Enterprise features, 25 brukere, 25 sitater og støtte fra fellesskapet."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Skala",
|
||||||
|
"description": "Enterprise features, 50 brukere, 50 nettsteder og prioritetsstøtte."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Kun personlig bruk (gratis lisens - ingen utsjekking)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Fortsett til kassen"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Feil ved utsjekk",
|
||||||
|
"description": "Kan ikke starte kassen. Prøv på nytt."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Prioritet",
|
"priority": "Prioritet",
|
||||||
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
|
"priorityDescription": "Høyere prioriterte ruter evalueres først. Prioritet = 100 betyr automatisk bestilling (systembeslutninger). Bruk et annet nummer til å håndheve manuell prioritet.",
|
||||||
"instanceName": "Forekomst navn",
|
"instanceName": "Forekomst navn",
|
||||||
@@ -2211,7 +2283,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 Enterprise lisens er påkrevd for å bruke denne funksjonen.",
|
"licenseRequiredToUse": "En <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisens er påkrevd 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>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Brannmur aktivert",
|
"firewallEnabled": "Brannmur aktivert",
|
||||||
"autoUpdatesEnabled": "Automatiske oppdateringer aktivert",
|
"autoUpdatesEnabled": "Automatiske oppdateringer aktivert",
|
||||||
"tpmAvailable": "TPM tilgjengelig",
|
"tpmAvailable": "TPM tilgjengelig",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus aktivert",
|
||||||
"macosSipEnabled": "System Integritetsbeskyttelse (SIP)",
|
"macosSipEnabled": "System Integritetsbeskyttelse (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Brannmur Usynlig Modus",
|
"macosFirewallStealthMode": "Brannmur Usynlig Modus",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Je bent lid van {count, plural, =0 {geen organisatie} one {één organisatie} other {# organisaties}}.",
|
"componentsMember": "Je bent lid van {count, plural, =0 {geen organisatie} one {één organisatie} other {# organisaties}}.",
|
||||||
"componentsInvalidKey": "Ongeldige of verlopen licentiesleutels gedetecteerd. Volg de licentievoorwaarden om alle functies te blijven gebruiken.",
|
"componentsInvalidKey": "Ongeldige of verlopen licentiesleutels gedetecteerd. Volg de licentievoorwaarden om alle functies te blijven gebruiken.",
|
||||||
"dismiss": "Uitschakelen",
|
"dismiss": "Uitschakelen",
|
||||||
|
"subscriptionViolationMessage": "U overschrijdt uw huidige abonnement. Corrigeer het probleem door sites, gebruikers of andere bronnen te verwijderen om binnen uw plan te blijven.",
|
||||||
|
"subscriptionViolationViewBilling": "Facturering bekijken",
|
||||||
"componentsLicenseViolation": "Licentie overtreding: Deze server gebruikt {usedSites} sites die de gelicentieerde limiet van {maxSites} sites overschrijden. Volg de licentievoorwaarden om door te gaan met het gebruik van alle functies.",
|
"componentsLicenseViolation": "Licentie overtreding: Deze server gebruikt {usedSites} sites die de gelicentieerde limiet van {maxSites} sites overschrijden. Volg de licentievoorwaarden om door te gaan met het gebruik van alle functies.",
|
||||||
"componentsSupporterMessage": "Bedankt voor het ondersteunen van Pangolin als {tier}!",
|
"componentsSupporterMessage": "Bedankt voor het ondersteunen van Pangolin als {tier}!",
|
||||||
"inviteErrorNotValid": "Het spijt ons, maar de uitnodiging die je probeert te bezoeken is niet geaccepteerd of is niet meer geldig.",
|
"inviteErrorNotValid": "Het spijt ons, maar de uitnodiging die je probeert te bezoeken is niet geaccepteerd of is niet meer geldig.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Toename van site vergroten",
|
"sitestCountIncrease": "Toename van site vergroten",
|
||||||
"idpManage": "Identiteitsaanbieders beheren",
|
"idpManage": "Identiteitsaanbieders beheren",
|
||||||
"idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren",
|
"idpManageDescription": "Identiteitsaanbieders in het systeem bekijken en beheren",
|
||||||
|
"idpGlobalModeBanner": "Identiteitsaanbieders (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld tussen alle organisaties). Beheer globale IdPs in het <adminPanelLink>beheerderspaneel</adminPanelLink>. Om IdPs per organisatie in te schakelen, bewerk de server configuratie en zet IdP modus op org. <configDocsLink>Zie de documenten</configDocsLink>. Als je globale IdPs wilt blijven gebruiken en dit uit de organisatie-instellingen wilt laten verdwijnen, zet dan expliciet de modus naar globaal in de config.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het <adminPanelLink>beheerderspaneel</adminPanelLink>. Om identiteitsproviders per organisatie te gebruiken, moet u upgraden naar de Enterprise editie.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organisatie zijn uitgeschakeld op deze server. Het gebruikt globale IdPs (gedeeld in alle organisaties) Beheer globale IdPs in het <adminPanelLink>beheerderspaneel</adminPanelLink>. Om identiteitsaanbieders per organisatie te gebruiken, is een Enterprise-licentie vereist.",
|
||||||
"idpDeletedDescription": "Identity provider succesvol verwijderd",
|
"idpDeletedDescription": "Identity provider succesvol verwijderd",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?",
|
"idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Overzicht gebruikslimieten",
|
"billingUsageLimitsOverview": "Overzicht gebruikslimieten",
|
||||||
"billingMonitorUsage": "Houd uw gebruik in de gaten ten opzichte van de ingestelde limieten. Als u verhoogde limieten nodig heeft, neem dan contact met ons op support@pangolin.net.",
|
"billingMonitorUsage": "Houd uw gebruik in de gaten ten opzichte van de ingestelde limieten. Als u verhoogde limieten nodig heeft, neem dan contact met ons op support@pangolin.net.",
|
||||||
"billingDataUsage": "Gegevensgebruik",
|
"billingDataUsage": "Gegevensgebruik",
|
||||||
"billingOnlineTime": "Site Online Tijd",
|
"billingSites": "Sites",
|
||||||
"billingUsers": "Actieve Gebruikers",
|
"billingUsers": "Gebruikers",
|
||||||
"billingDomains": "Actieve Domeinen",
|
"billingDomains": "Domeinen",
|
||||||
"billingRemoteExitNodes": "Actieve Zelfgehoste Nodes",
|
"billingRemoteExitNodes": "Externe knooppunten",
|
||||||
"billingNoLimitConfigured": "Geen limiet ingesteld",
|
"billingNoLimitConfigured": "Geen limiet ingesteld",
|
||||||
"billingEstimatedPeriod": "Geschatte Facturatie Periode",
|
"billingEstimatedPeriod": "Geschatte Facturatie Periode",
|
||||||
"billingIncludedUsage": "Opgenomen Gebruik",
|
"billingIncludedUsage": "Opgenomen Gebruik",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Niet gelukt om portal URL te krijgen",
|
"billingFailedToGetPortalUrl": "Niet gelukt om portal URL te krijgen",
|
||||||
"billingPortalError": "Portal Fout",
|
"billingPortalError": "Portal Fout",
|
||||||
"billingDataUsageInfo": "U bent in rekening gebracht voor alle gegevens die via uw beveiligde tunnels via de cloud worden verzonden. Dit omvat zowel inkomende als uitgaande verkeer over al uw sites. Wanneer u uw limiet bereikt zullen uw sites de verbinding verbreken totdat u uw abonnement upgradet of het gebruik vermindert. Gegevens worden niet in rekening gebracht bij het gebruik van knooppunten.",
|
"billingDataUsageInfo": "U bent in rekening gebracht voor alle gegevens die via uw beveiligde tunnels via de cloud worden verzonden. Dit omvat zowel inkomende als uitgaande verkeer over al uw sites. Wanneer u uw limiet bereikt zullen uw sites de verbinding verbreken totdat u uw abonnement upgradet of het gebruik vermindert. Gegevens worden niet in rekening gebracht bij het gebruik van knooppunten.",
|
||||||
"billingOnlineTimeInfo": "U wordt in rekening gebracht op basis van hoe lang uw sites verbonden blijven met de cloud. Bijvoorbeeld 44,640 minuten is gelijk aan één site met 24/7 voor een volledige maand. Wanneer u uw limiet bereikt, zal de verbinding tussen uw sites worden verbroken totdat u een upgrade van uw abonnement uitvoert of het gebruik vermindert. Tijd wordt niet belast bij het gebruik van knooppunten.",
|
"billingSInfo": "Hoeveel sites u kunt gebruiken",
|
||||||
"billingUsersInfo": "U bent in rekening gebracht voor elke gebruiker in de organisatie. Facturering wordt dagelijks berekend op basis van het aantal actieve gebruikersaccounts in uw org.",
|
"billingUsersInfo": "Hoeveel gebruikers je kan gebruiken",
|
||||||
"billingDomainInfo": "U wordt voor elk domein in de organisatie in rekening gebracht. Facturering wordt dagelijks berekend op basis van het aantal actieve domeinaccounts in uw org.",
|
"billingDomainInfo": "Hoeveel domeinen je kunt gebruiken",
|
||||||
"billingRemoteExitNodesInfo": "U bent belast voor elke beheerde node in de organisatie. Facturering wordt dagelijks berekend op basis van het aantal actieve beheerde knooppunten in uw org.",
|
"billingRemoteExitNodesInfo": "Hoeveel externe nodes je kunt gebruiken",
|
||||||
|
"billingLicenseKeys": "Licentie Sleutels",
|
||||||
|
"billingLicenseKeysDescription": "Beheer uw licentiesleutelabonnementen",
|
||||||
|
"billingLicenseSubscription": "Licentie abonnement",
|
||||||
|
"billingInactive": "Inactief",
|
||||||
|
"billingLicenseItem": "Licentie artikel",
|
||||||
|
"billingQuantity": "Aantal",
|
||||||
|
"billingTotal": "totaal",
|
||||||
|
"billingModifyLicenses": "Licentieabonnement wijzigen",
|
||||||
"domainNotFound": "Domein niet gevonden",
|
"domainNotFound": "Domein niet gevonden",
|
||||||
"domainNotFoundDescription": "Deze bron is uitgeschakeld omdat het domein niet langer in ons systeem bestaat. Stel een nieuw domein in voor deze bron.",
|
"domainNotFoundDescription": "Deze bron is uitgeschakeld omdat het domein niet langer in ons systeem bestaat. Stel een nieuw domein in voor deze bron.",
|
||||||
"failed": "Mislukt",
|
"failed": "Mislukt",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Poortnummer is vereist voor niet-HTTP-bronnen",
|
"resourcePortRequired": "Poortnummer is vereist voor niet-HTTP-bronnen",
|
||||||
"resourcePortNotAllowed": "Poortnummer mag niet worden ingesteld voor HTTP-bronnen",
|
"resourcePortNotAllowed": "Poortnummer mag niet worden ingesteld voor HTTP-bronnen",
|
||||||
"billingPricingCalculatorLink": "Prijs Calculator",
|
"billingPricingCalculatorLink": "Prijs Calculator",
|
||||||
|
"billingYourPlan": "Uw abonnement",
|
||||||
|
"billingViewOrModifyPlan": "Bekijk of wijzig uw huidige abonnement",
|
||||||
|
"billingViewPlanDetails": "Abonnementsdetails bekijken",
|
||||||
|
"billingUsageAndLimits": "Gebruik en limieten",
|
||||||
|
"billingViewUsageAndLimits": "Limiet van je abonnement en huidig gebruik bekijken",
|
||||||
|
"billingCurrentUsage": "Huidig gebruik",
|
||||||
|
"billingMaximumLimits": "Maximaal aantal limieten",
|
||||||
|
"billingRemoteNodes": "Externe knooppunten",
|
||||||
|
"billingUnlimited": "Onbeperkt",
|
||||||
|
"billingPaidLicenseKeys": "Betaalde licentiesleutels",
|
||||||
|
"billingManageLicenseSubscription": "Beheer je abonnement voor betaalde zelf gehoste licentiesleutels",
|
||||||
|
"billingCurrentKeys": "Huidige toetsen",
|
||||||
|
"billingModifyCurrentPlan": "Huidig plan wijzigen",
|
||||||
|
"billingConfirmUpgrade": "Bevestig Upgrade",
|
||||||
|
"billingConfirmDowngrade": "Downgraden bevestigen",
|
||||||
|
"billingConfirmUpgradeDescription": "U staat op het punt uw abonnement te upgraden. Controleer de nieuwe limieten en prijzen hieronder.",
|
||||||
|
"billingConfirmDowngradeDescription": "U staat op het punt om uw abonnement te downgraden. Controleer de nieuwe limieten en prijzen hieronder.",
|
||||||
|
"billingPlanIncludes": "Abonnement bevat",
|
||||||
|
"billingProcessing": "Verwerken...",
|
||||||
|
"billingConfirmUpgradeButton": "Bevestig Upgrade",
|
||||||
|
"billingConfirmDowngradeButton": "Downgraden bevestigen",
|
||||||
|
"billingLimitViolationWarning": "Gebruik Overschrijdt nieuwe Plan Limieten",
|
||||||
|
"billingLimitViolationDescription": "Uw huidige verbruik overschrijdt de limieten van dit plan. Na het downgraden worden alle acties uitgeschakeld totdat u het verbruik vermindert binnen de nieuwe grenzen. Controleer de onderstaande functies die de limieten overschrijden. Beperkingen in overtreding:",
|
||||||
|
"billingFeatureLossWarning": "Kennisgeving beschikbaarheid",
|
||||||
|
"billingFeatureLossDescription": "Door downgraden worden functies die niet beschikbaar zijn in het nieuwe abonnement automatisch uitgeschakeld. Sommige instellingen en configuraties kunnen verloren gaan. Raadpleeg de prijsmatrix om te begrijpen welke functies niet langer beschikbaar zijn.",
|
||||||
|
"billingUsageExceedsLimit": "Huidig gebruik ({current}) overschrijdt limiet ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Ik ga akkoord met de",
|
"IAgreeToThe": "Ik ga akkoord met de",
|
||||||
"termsOfService": "servicevoorwaarden",
|
"termsOfService": "servicevoorwaarden",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Terug naar standaard aanmelden",
|
"orgAuthBackToSignIn": "Terug naar standaard aanmelden",
|
||||||
"orgAuthNoAccount": "Nog geen account?",
|
"orgAuthNoAccount": "Nog geen account?",
|
||||||
"subscriptionRequiredToUse": "Een abonnement is vereist om deze functie te gebruiken.",
|
"subscriptionRequiredToUse": "Een abonnement is vereist om deze functie te gebruiken.",
|
||||||
|
"mustUpgradeToUse": "U moet uw abonnement upgraden om deze functie te gebruiken.",
|
||||||
|
"subscriptionRequiredTierToUse": "Deze functie vereist <tierLink>{tier}</tierLink> of hoger.",
|
||||||
|
"upgradeToTierToUse": "Upgrade naar <tierLink>{tier}</tierLink> of hoger om deze functie te gebruiken.",
|
||||||
|
"subscriptionTierTier1": "Startpagina",
|
||||||
|
"subscriptionTierTier2": "Team",
|
||||||
|
"subscriptionTierTier3": "Bedrijfsleven",
|
||||||
|
"subscriptionTierEnterprise": "Onderneming",
|
||||||
"idpDisabled": "Identiteitsaanbieders zijn uitgeschakeld.",
|
"idpDisabled": "Identiteitsaanbieders zijn uitgeschakeld.",
|
||||||
"orgAuthPageDisabled": "Pagina voor organisatie-authenticatie is uitgeschakeld.",
|
"orgAuthPageDisabled": "Pagina voor organisatie-authenticatie is uitgeschakeld.",
|
||||||
"domainRestartedDescription": "Domeinverificatie met succes opnieuw gestart",
|
"domainRestartedDescription": "Domeinverificatie met succes opnieuw gestart",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Krijg een licentie",
|
||||||
|
"description": "Kies een plan en vertel ons hoe u Pangolin wilt gebruiken.",
|
||||||
|
"chooseTier": "Kies uw abonnement",
|
||||||
|
"viewPricingLink": "Zie prijzen, functies en limieten",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Beginner",
|
||||||
|
"description": "Enterprise functies, 25 gebruikers, 25 sites en community ondersteuning."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Schaal",
|
||||||
|
"description": "Enterprise functies, 50 gebruikers, 50 sites en prioriteit ondersteuning."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Alleen persoonlijk gebruik (gratis licentie - geen afrekenen)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Doorgaan naar afrekenen"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Fout bij afrekenen",
|
||||||
|
"description": "Kan de afhandeling niet starten. Probeer het opnieuw."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Prioriteit",
|
"priority": "Prioriteit",
|
||||||
"priorityDescription": "routes met hogere prioriteit worden eerst geëvalueerd. Prioriteit = 100 betekent automatisch bestellen (systeem beslist de). Gebruik een ander nummer om handmatige prioriteit af te dwingen.",
|
"priorityDescription": "routes met hogere prioriteit worden eerst geëvalueerd. Prioriteit = 100 betekent automatisch bestellen (systeem beslist de). Gebruik een ander nummer om handmatige prioriteit af te dwingen.",
|
||||||
"instanceName": "Naam instantie",
|
"instanceName": "Naam instantie",
|
||||||
@@ -2211,7 +2283,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 Enterprise-licentie is vereist om deze functie te gebruiken.",
|
"licenseRequiredToUse": "Een <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> licentie 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>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Firewall ingeschakeld",
|
"firewallEnabled": "Firewall ingeschakeld",
|
||||||
"autoUpdatesEnabled": "Auto Updates Ingeschakeld",
|
"autoUpdatesEnabled": "Auto Updates Ingeschakeld",
|
||||||
"tpmAvailable": "TPM beschikbaar",
|
"tpmAvailable": "TPM beschikbaar",
|
||||||
|
"windowsAntivirusEnabled": "Antivirus ingeschakeld",
|
||||||
"macosSipEnabled": "Systeemintegriteitsbescherming (SIP)",
|
"macosSipEnabled": "Systeemintegriteitsbescherming (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Firewall Verberg Modus",
|
"macosFirewallStealthMode": "Firewall Verberg Modus",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Jesteś członkiem {count, plural, =0 {żadna organizacja} one {jedna organizacja} few {# organizacje} many {# organizacji} other {# organizacji}}.",
|
"componentsMember": "Jesteś członkiem {count, plural, =0 {żadna organizacja} one {jedna organizacja} few {# organizacje} many {# organizacji} other {# organizacji}}.",
|
||||||
"componentsInvalidKey": "Wykryto nieprawidłowe lub wygasłe klucze licencyjne. Postępuj zgodnie z warunkami licencji, aby kontynuować korzystanie ze wszystkich funkcji.",
|
"componentsInvalidKey": "Wykryto nieprawidłowe lub wygasłe klucze licencyjne. Postępuj zgodnie z warunkami licencji, aby kontynuować korzystanie ze wszystkich funkcji.",
|
||||||
"dismiss": "Odrzuć",
|
"dismiss": "Odrzuć",
|
||||||
|
"subscriptionViolationMessage": "Nie masz ograniczeń dla aktualnego planu. Popraw problem poprzez usunięcie stron, użytkowników lub innych zasobów, aby pozostać w swoim planie.",
|
||||||
|
"subscriptionViolationViewBilling": "Zobacz rozliczenie",
|
||||||
"componentsLicenseViolation": "Naruszenie licencji: Ten serwer używa stron {usedSites} , które przekraczają limit licencyjny stron {maxSites} . Postępuj zgodnie z warunkami licencji, aby kontynuować korzystanie ze wszystkich funkcji.",
|
"componentsLicenseViolation": "Naruszenie licencji: Ten serwer używa stron {usedSites} , które przekraczają limit licencyjny stron {maxSites} . Postępuj zgodnie z warunkami licencji, aby kontynuować korzystanie ze wszystkich funkcji.",
|
||||||
"componentsSupporterMessage": "Dziękujemy za wsparcie Pangolina jako {tier}!",
|
"componentsSupporterMessage": "Dziękujemy za wsparcie Pangolina jako {tier}!",
|
||||||
"inviteErrorNotValid": "Przykro nam, ale wygląda na to, że zaproszenie, do którego próbujesz uzyskać dostęp, nie zostało zaakceptowane lub jest już nieważne.",
|
"inviteErrorNotValid": "Przykro nam, ale wygląda na to, że zaproszenie, do którego próbujesz uzyskać dostęp, nie zostało zaakceptowane lub jest już nieważne.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Zwiększ liczbę witryn",
|
"sitestCountIncrease": "Zwiększ liczbę witryn",
|
||||||
"idpManage": "Zarządzaj dostawcami tożsamości",
|
"idpManage": "Zarządzaj dostawcami tożsamości",
|
||||||
"idpManageDescription": "Wyświetl i zarządzaj dostawcami tożsamości w systemie",
|
"idpManageDescription": "Wyświetl i zarządzaj dostawcami tożsamości w systemie",
|
||||||
|
"idpGlobalModeBanner": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używa globalnych IdP (współdzielonych ze wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym <adminPanelLink></adminPanelLink>. Aby włączyć IdP na organizację, edytuj konfigurację serwera i ustaw tryb IdP na org. <configDocsLink>Zobacz dokumentację</configDocsLink>. Jeśli chcesz nadal używać globalnych IdP i sprawić, że zniknie to z ustawień organizacji, wyraźnie ustaw tryb globalny w konfiguracji.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym <adminPanelLink></adminPanelLink>. Aby korzystać z dostawców tożsamości na organizację, musisz zaktualizować do edycji Enterprise.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym <adminPanelLink></adminPanelLink>. Aby korzystać z dostawców tożsamości na organizację, wymagana jest licencja Enterprise.",
|
||||||
"idpDeletedDescription": "Dostawca tożsamości został pomyślnie usunięty",
|
"idpDeletedDescription": "Dostawca tożsamości został pomyślnie usunięty",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Czy na pewno chcesz trwale usunąć dostawcę tożsamości?",
|
"idpQuestionRemove": "Czy na pewno chcesz trwale usunąć dostawcę tożsamości?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Przegląd Limitów Użytkowania",
|
"billingUsageLimitsOverview": "Przegląd Limitów Użytkowania",
|
||||||
"billingMonitorUsage": "Monitoruj swoje wykorzystanie w porównaniu do skonfigurowanych limitów. Jeśli potrzebujesz zwiększenia limitów, skontaktuj się z nami pod adresem support@pangolin.net.",
|
"billingMonitorUsage": "Monitoruj swoje wykorzystanie w porównaniu do skonfigurowanych limitów. Jeśli potrzebujesz zwiększenia limitów, skontaktuj się z nami pod adresem support@pangolin.net.",
|
||||||
"billingDataUsage": "Użycie danych",
|
"billingDataUsage": "Użycie danych",
|
||||||
"billingOnlineTime": "Czas Online Strony",
|
"billingSites": "Witryny",
|
||||||
"billingUsers": "Aktywni użytkownicy",
|
"billingUsers": "Użytkownicy",
|
||||||
"billingDomains": "Aktywne domeny",
|
"billingDomains": "Domeny",
|
||||||
"billingRemoteExitNodes": "Aktywne samodzielnie-hostowane węzły",
|
"billingRemoteExitNodes": "Zdalne węzły",
|
||||||
"billingNoLimitConfigured": "Nie skonfigurowano limitu",
|
"billingNoLimitConfigured": "Nie skonfigurowano limitu",
|
||||||
"billingEstimatedPeriod": "Szacowany Okres Rozliczeniowy",
|
"billingEstimatedPeriod": "Szacowany Okres Rozliczeniowy",
|
||||||
"billingIncludedUsage": "Zawarte użycie",
|
"billingIncludedUsage": "Zawarte użycie",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Nie udało się uzyskać adresu URL portalu",
|
"billingFailedToGetPortalUrl": "Nie udało się uzyskać adresu URL portalu",
|
||||||
"billingPortalError": "Błąd Portalu",
|
"billingPortalError": "Błąd Portalu",
|
||||||
"billingDataUsageInfo": "Jesteś obciążony za wszystkie dane przesyłane przez bezpieczne tunele, gdy jesteś podłączony do chmury. Obejmuje to zarówno ruch przychodzący, jak i wychodzący we wszystkich Twoich witrynach. Gdy osiągniesz swój limit, twoje strony zostaną rozłączone, dopóki nie zaktualizujesz planu lub nie ograniczysz użycia. Dane nie będą naliczane przy użyciu węzłów.",
|
"billingDataUsageInfo": "Jesteś obciążony za wszystkie dane przesyłane przez bezpieczne tunele, gdy jesteś podłączony do chmury. Obejmuje to zarówno ruch przychodzący, jak i wychodzący we wszystkich Twoich witrynach. Gdy osiągniesz swój limit, twoje strony zostaną rozłączone, dopóki nie zaktualizujesz planu lub nie ograniczysz użycia. Dane nie będą naliczane przy użyciu węzłów.",
|
||||||
"billingOnlineTimeInfo": "Opłata zależy od tego, jak długo twoje strony pozostają połączone z chmurą. Na przykład 44,640 minut oznacza jedną stronę działającą 24/7 przez cały miesiąc. Kiedy osiągniesz swój limit, twoje strony zostaną rozłączone, dopóki nie zaktualizujesz planu lub nie zmniejsz jego wykorzystania. Czas nie będzie naliczany przy użyciu węzłów.",
|
"billingSInfo": "Ile stron możesz użyć",
|
||||||
"billingUsersInfo": "Opłata za każdego użytkownika w organizacji. Płatność jest obliczana codziennie na podstawie liczby aktywnych kont użytkowników w Twojej organizacji.",
|
"billingUsersInfo": "Ile użytkowników możesz użyć",
|
||||||
"billingDomainInfo": "Opłata za każdą domenę w organizacji. Płatność jest obliczana codziennie na podstawie liczby aktywnych kont domen w Twojej organizacji.",
|
"billingDomainInfo": "Ile domen możesz użyć",
|
||||||
"billingRemoteExitNodesInfo": "Opłata za każdy zarządzany węzeł w organizacji. Płatność jest obliczana codziennie na podstawie liczby aktywnych zarządzanych węzłów w Twojej organizacji.",
|
"billingRemoteExitNodesInfo": "Ile zdalnych węzłów możesz użyć",
|
||||||
|
"billingLicenseKeys": "Klucze licencyjne",
|
||||||
|
"billingLicenseKeysDescription": "Zarządzaj subskrypcjami kluczy licencyjnych",
|
||||||
|
"billingLicenseSubscription": "Subskrypcja licencji",
|
||||||
|
"billingInactive": "Nieaktywny",
|
||||||
|
"billingLicenseItem": "Element licencji",
|
||||||
|
"billingQuantity": "Ilość",
|
||||||
|
"billingTotal": "łącznie",
|
||||||
|
"billingModifyLicenses": "Modyfikuj subskrypcję licencji",
|
||||||
"domainNotFound": "Nie znaleziono domeny",
|
"domainNotFound": "Nie znaleziono domeny",
|
||||||
"domainNotFoundDescription": "Zasób jest wyłączony, ponieważ domena nie istnieje już w naszym systemie. Proszę ustawić nową domenę dla tego zasobu.",
|
"domainNotFoundDescription": "Zasób jest wyłączony, ponieważ domena nie istnieje już w naszym systemie. Proszę ustawić nową domenę dla tego zasobu.",
|
||||||
"failed": "Niepowodzenie",
|
"failed": "Niepowodzenie",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Numer portu jest wymagany dla zasobów non-HTTP",
|
"resourcePortRequired": "Numer portu jest wymagany dla zasobów non-HTTP",
|
||||||
"resourcePortNotAllowed": "Numer portu nie powinien być ustawiony dla zasobów HTTP",
|
"resourcePortNotAllowed": "Numer portu nie powinien być ustawiony dla zasobów HTTP",
|
||||||
"billingPricingCalculatorLink": "Kalkulator Cen",
|
"billingPricingCalculatorLink": "Kalkulator Cen",
|
||||||
|
"billingYourPlan": "Twój plan",
|
||||||
|
"billingViewOrModifyPlan": "Wyświetl lub zmodyfikuj swój aktualny plan",
|
||||||
|
"billingViewPlanDetails": "Zobacz szczegóły planu",
|
||||||
|
"billingUsageAndLimits": "Stosowanie i ograniczenia",
|
||||||
|
"billingViewUsageAndLimits": "Zobacz limity swojego planu i bieżące użycie",
|
||||||
|
"billingCurrentUsage": "Bieżące użycie",
|
||||||
|
"billingMaximumLimits": "Maksymalne limity",
|
||||||
|
"billingRemoteNodes": "Zdalne węzły",
|
||||||
|
"billingUnlimited": "Nieograniczona",
|
||||||
|
"billingPaidLicenseKeys": "Płatne klucze licencyjne",
|
||||||
|
"billingManageLicenseSubscription": "Zarządzaj subskrypcją płatnych własnych kluczy licencyjnych",
|
||||||
|
"billingCurrentKeys": "Bieżące klucze",
|
||||||
|
"billingModifyCurrentPlan": "Modyfikuj bieżący plan",
|
||||||
|
"billingConfirmUpgrade": "Potwierdź aktualizację",
|
||||||
|
"billingConfirmDowngrade": "Potwierdź obniżenie",
|
||||||
|
"billingConfirmUpgradeDescription": "Zamierzasz ulepszyć swój plan. Przejrzyj nowe limity i ceny poniżej.",
|
||||||
|
"billingConfirmDowngradeDescription": "Zamierzasz obniżyć swój plan. Przejrzyj nowe limity i ceny poniżej.",
|
||||||
|
"billingPlanIncludes": "Plan zawiera",
|
||||||
|
"billingProcessing": "Przetwarzanie...",
|
||||||
|
"billingConfirmUpgradeButton": "Potwierdź aktualizację",
|
||||||
|
"billingConfirmDowngradeButton": "Potwierdź obniżenie",
|
||||||
|
"billingLimitViolationWarning": "Użycie przekracza nowe limity planu",
|
||||||
|
"billingLimitViolationDescription": "Bieżące użycie przekracza limity tego planu. Po obniżeniu, wszystkie działania zostaną wyłączone, dopóki nie zmniejsz zużycia w ramach nowych limitów. Zapoznaj się z poniższymi funkcjami, które obecnie przekraczają limity. Limity naruszenia:",
|
||||||
|
"billingFeatureLossWarning": "Powiadomienie o dostępności funkcji",
|
||||||
|
"billingFeatureLossDescription": "Po obniżeniu wartości funkcje niedostępne w nowym planie zostaną automatycznie wyłączone. Niektóre ustawienia i konfiguracje mogą zostać utracone. Zapoznaj się z matrycą cenową, aby zrozumieć, które funkcje nie będą już dostępne.",
|
||||||
|
"billingUsageExceedsLimit": "Bieżące użycie ({current}) przekracza limit ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Zgadzam się z",
|
"IAgreeToThe": "Zgadzam się z",
|
||||||
"termsOfService": "warunkami usługi",
|
"termsOfService": "warunkami usługi",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Powrót do standardowego logowania",
|
"orgAuthBackToSignIn": "Powrót do standardowego logowania",
|
||||||
"orgAuthNoAccount": "Nie masz konta?",
|
"orgAuthNoAccount": "Nie masz konta?",
|
||||||
"subscriptionRequiredToUse": "Do korzystania z tej funkcji wymagana jest subskrypcja.",
|
"subscriptionRequiredToUse": "Do korzystania z tej funkcji wymagana jest subskrypcja.",
|
||||||
|
"mustUpgradeToUse": "Musisz uaktualnić subskrypcję, aby korzystać z tej funkcji.",
|
||||||
|
"subscriptionRequiredTierToUse": "Ta funkcja wymaga funkcji <tierLink>{tier}</tierLink> lub wyższej.",
|
||||||
|
"upgradeToTierToUse": "Aby skorzystać z tej funkcji, przejdź na <tierLink>{tier}</tierLink> lub wyższy pakiet.",
|
||||||
|
"subscriptionTierTier1": "Strona główna",
|
||||||
|
"subscriptionTierTier2": "Drużyna",
|
||||||
|
"subscriptionTierTier3": "Biznes",
|
||||||
|
"subscriptionTierEnterprise": "Przedsiębiorstwo",
|
||||||
"idpDisabled": "Dostawcy tożsamości są wyłączeni",
|
"idpDisabled": "Dostawcy tożsamości są wyłączeni",
|
||||||
"orgAuthPageDisabled": "Strona autoryzacji organizacji jest wyłączona.",
|
"orgAuthPageDisabled": "Strona autoryzacji organizacji jest wyłączona.",
|
||||||
"domainRestartedDescription": "Weryfikacja domeny zrestartowana pomyślnie",
|
"domainRestartedDescription": "Weryfikacja domeny zrestartowana pomyślnie",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Uzyskaj licencję",
|
||||||
|
"description": "Wybierz plan i powiedz nam, jak planujesz korzystać z Pangolin.",
|
||||||
|
"chooseTier": "Wybierz swój plan",
|
||||||
|
"viewPricingLink": "Zobacz cenniki, funkcje i limity",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Rozpocznij",
|
||||||
|
"description": "Środki te przeznaczone są na pokrycie wydatków na personel i wydatków administracyjnych Agencji (tytuły 1 i 2) oraz jej wydatków operacyjnych (tytuł 3)."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Skala",
|
||||||
|
"description": "Cechy przedsiębiorstw, 50 użytkowników, 50 obiektów i wsparcie priorytetowe."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Wyłącznie do użytku osobistego (bezpłatna licencja – brak zamówień)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Przejdź do zamówienia"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Błąd zamówienia",
|
||||||
|
"description": "Nie można uruchomić zamówienia. Spróbuj ponownie."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Priorytet",
|
"priority": "Priorytet",
|
||||||
"priorityDescription": "Najpierw oceniane są trasy priorytetowe. Priorytet = 100 oznacza automatyczne zamawianie (decyzje systemowe). Użyj innego numeru, aby wyegzekwować ręczny priorytet.",
|
"priorityDescription": "Najpierw oceniane są trasy priorytetowe. Priorytet = 100 oznacza automatyczne zamawianie (decyzje systemowe). Użyj innego numeru, aby wyegzekwować ręczny priorytet.",
|
||||||
"instanceName": "Nazwa instancji",
|
"instanceName": "Nazwa instancji",
|
||||||
@@ -2211,7 +2283,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": "Licencja Enterprise jest wymagana do korzystania z tej funkcji.",
|
"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>.",
|
||||||
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> jest wymagany do korzystania z tej funkcji. Ta funkcja jest również dostępna w <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Zapora włączona",
|
"firewallEnabled": "Zapora włączona",
|
||||||
"autoUpdatesEnabled": "Automatyczne aktualizacje włączone",
|
"autoUpdatesEnabled": "Automatyczne aktualizacje włączone",
|
||||||
"tpmAvailable": "TPM dostępne",
|
"tpmAvailable": "TPM dostępne",
|
||||||
|
"windowsAntivirusEnabled": "Antywirus włączony",
|
||||||
"macosSipEnabled": "Ochrona integralności systemu (SIP)",
|
"macosSipEnabled": "Ochrona integralności systemu (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Tryb Stealth zapory",
|
"macosFirewallStealthMode": "Tryb Stealth zapory",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "É membro de {count, plural, =0 {nenhuma organização} one {uma organização} other {# organizações}}.",
|
"componentsMember": "É membro de {count, plural, =0 {nenhuma organização} one {uma organização} other {# organizações}}.",
|
||||||
"componentsInvalidKey": "Chaves de licença inválidas ou expiradas detectadas. Siga os termos da licença para continuar usando todos os recursos.",
|
"componentsInvalidKey": "Chaves de licença inválidas ou expiradas detectadas. Siga os termos da licença para continuar usando todos os recursos.",
|
||||||
"dismiss": "Rejeitar",
|
"dismiss": "Rejeitar",
|
||||||
|
"subscriptionViolationMessage": "Você está além dos seus limites para o seu plano atual. Corrija o problema removendo sites, usuários, ou outros recursos para ficar em seu plano.",
|
||||||
|
"subscriptionViolationViewBilling": "Ver faturamento",
|
||||||
"componentsLicenseViolation": "Violação de Licença: Este servidor está usando sites {usedSites} que excedem o limite licenciado de sites {maxSites} . Siga os termos da licença para continuar usando todos os recursos.",
|
"componentsLicenseViolation": "Violação de Licença: Este servidor está usando sites {usedSites} que excedem o limite licenciado de sites {maxSites} . Siga os termos da licença para continuar usando todos os recursos.",
|
||||||
"componentsSupporterMessage": "Obrigado por apoiar o Pangolin como um {tier}!",
|
"componentsSupporterMessage": "Obrigado por apoiar o Pangolin como um {tier}!",
|
||||||
"inviteErrorNotValid": "Desculpe, mas parece que o convite que está a tentar aceder não foi aceito ou não é mais válido.",
|
"inviteErrorNotValid": "Desculpe, mas parece que o convite que está a tentar aceder não foi aceito ou não é mais válido.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Aumentar contagem de sites",
|
"sitestCountIncrease": "Aumentar contagem de sites",
|
||||||
"idpManage": "Gerir Provedores de Identidade",
|
"idpManage": "Gerir Provedores de Identidade",
|
||||||
"idpManageDescription": "Visualizar e gerir provedores de identidade no sistema",
|
"idpManageDescription": "Visualizar e gerir provedores de identidade no sistema",
|
||||||
|
"idpGlobalModeBanner": "Provedores de identidade (Pds) por organização estão desabilitados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie IdPs no painel <adminPanelLink>admin</adminPanelLink>. Para habilitar IdPs por organização, edite a configuração do servidor e defina o modo IdP como org. <configDocsLink>Veja a documentação</configDocsLink>. Se quiser continuar usando IdPs globais e fazer isso desaparecer das configurações da organização, defina explicitamente o modo como global na configuração.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no <adminPanelLink>painel administrativo</adminPanelLink>. Para usar provedores de identidade por organização, você deve atualizar para a edição Enterprise.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no <adminPanelLink>painel administrativo</adminPanelLink>. Para usar provedores de identidade por organização, é necessário uma licença Enterprise.",
|
||||||
"idpDeletedDescription": "Provedor de identidade eliminado com sucesso",
|
"idpDeletedDescription": "Provedor de identidade eliminado com sucesso",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Tem certeza que deseja eliminar permanentemente o provedor de identidade?",
|
"idpQuestionRemove": "Tem certeza que deseja eliminar permanentemente o provedor de identidade?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Visão Geral dos Limites de Uso",
|
"billingUsageLimitsOverview": "Visão Geral dos Limites de Uso",
|
||||||
"billingMonitorUsage": "Monitore seu uso em relação aos limites configurados. Se precisar aumentar esses limites, entre em contato conosco support@pangolin.net.",
|
"billingMonitorUsage": "Monitore seu uso em relação aos limites configurados. Se precisar aumentar esses limites, entre em contato conosco support@pangolin.net.",
|
||||||
"billingDataUsage": "Uso de Dados",
|
"billingDataUsage": "Uso de Dados",
|
||||||
"billingOnlineTime": "Tempo Online do Site",
|
"billingSites": "sites",
|
||||||
"billingUsers": "Usuários Ativos",
|
"billingUsers": "Utilizadores",
|
||||||
"billingDomains": "Domínios Ativos",
|
"billingDomains": "Domínios",
|
||||||
"billingRemoteExitNodes": "Nodos Auto-Hospedados Ativos",
|
"billingRemoteExitNodes": "Nós remotos",
|
||||||
"billingNoLimitConfigured": "Nenhum limite configurado",
|
"billingNoLimitConfigured": "Nenhum limite configurado",
|
||||||
"billingEstimatedPeriod": "Período Estimado de Cobrança",
|
"billingEstimatedPeriod": "Período Estimado de Cobrança",
|
||||||
"billingIncludedUsage": "Uso Incluído",
|
"billingIncludedUsage": "Uso Incluído",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Falha ao obter URL do portal",
|
"billingFailedToGetPortalUrl": "Falha ao obter URL do portal",
|
||||||
"billingPortalError": "Erro do Portal",
|
"billingPortalError": "Erro do Portal",
|
||||||
"billingDataUsageInfo": "Você é cobrado por todos os dados transferidos através de seus túneis seguros quando conectado à nuvem. Isso inclui o tráfego de entrada e saída em todos os seus sites. Quando você atingir o seu limite, seus sites desconectarão até que você atualize seu plano ou reduza o uso. Os dados não serão cobrados ao usar os nós.",
|
"billingDataUsageInfo": "Você é cobrado por todos os dados transferidos através de seus túneis seguros quando conectado à nuvem. Isso inclui o tráfego de entrada e saída em todos os seus sites. Quando você atingir o seu limite, seus sites desconectarão até que você atualize seu plano ou reduza o uso. Os dados não serão cobrados ao usar os nós.",
|
||||||
"billingOnlineTimeInfo": "Cobrança de acordo com o tempo em que seus sites permanecem conectados à nuvem. Por exemplo, 44,640 minutos é igual a um site que roda 24/7 para um mês inteiro. Quando você atinge o seu limite, seus sites desconectarão até que você faça o upgrade do seu plano ou reduza o uso. O tempo não é cobrado ao usar nós.",
|
"billingSInfo": "Quantos sites você pode usar",
|
||||||
"billingUsersInfo": "A cobrança é feita por cada usuário na organização. A cobrança é feita diariamente com base no número de contas de usuário ativas na sua organização.",
|
"billingUsersInfo": "Quantos usuários você pode usar",
|
||||||
"billingDomainInfo": "A cobrança é feita por cada domínio da organização. A cobrança é feita diariamente com base no número de contas de domínio ativas na sua organização.",
|
"billingDomainInfo": "Quantos domínios você pode usar",
|
||||||
"billingRemoteExitNodesInfo": "Você é cobrado por cada nó gerenciado na organização. A cobrança é calculada diariamente com base no número de nós gerenciados ativos em sua organização.",
|
"billingRemoteExitNodesInfo": "Quantos nós remotos você pode usar",
|
||||||
|
"billingLicenseKeys": "Chaves de Licença",
|
||||||
|
"billingLicenseKeysDescription": "Gerenciar suas subscrições de chave de licença",
|
||||||
|
"billingLicenseSubscription": "Assinatura de Licença",
|
||||||
|
"billingInactive": "Inativo",
|
||||||
|
"billingLicenseItem": "Item de Licença",
|
||||||
|
"billingQuantity": "Quantidade",
|
||||||
|
"billingTotal": "total:",
|
||||||
|
"billingModifyLicenses": "Modificar assinatura de licença",
|
||||||
"domainNotFound": "Domínio Não Encontrado",
|
"domainNotFound": "Domínio Não Encontrado",
|
||||||
"domainNotFoundDescription": "Este recurso está desativado porque o domínio não existe mais em nosso sistema. Defina um novo domínio para este recurso.",
|
"domainNotFoundDescription": "Este recurso está desativado porque o domínio não existe mais em nosso sistema. Defina um novo domínio para este recurso.",
|
||||||
"failed": "Falhou",
|
"failed": "Falhou",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Número da porta é obrigatório para recursos não-HTTP",
|
"resourcePortRequired": "Número da porta é obrigatório para recursos não-HTTP",
|
||||||
"resourcePortNotAllowed": "Número da porta não deve ser definido para recursos HTTP",
|
"resourcePortNotAllowed": "Número da porta não deve ser definido para recursos HTTP",
|
||||||
"billingPricingCalculatorLink": "Calculadora de Preços",
|
"billingPricingCalculatorLink": "Calculadora de Preços",
|
||||||
|
"billingYourPlan": "Seu plano",
|
||||||
|
"billingViewOrModifyPlan": "Ver ou modificar seu plano atual",
|
||||||
|
"billingViewPlanDetails": "Ver detalhes do plano",
|
||||||
|
"billingUsageAndLimits": "Uso e Limites",
|
||||||
|
"billingViewUsageAndLimits": "Ver os limites do seu plano e o uso atual",
|
||||||
|
"billingCurrentUsage": "Uso atual",
|
||||||
|
"billingMaximumLimits": "Limite Máximo",
|
||||||
|
"billingRemoteNodes": "Nós remotos",
|
||||||
|
"billingUnlimited": "Ilimitado",
|
||||||
|
"billingPaidLicenseKeys": "Chaves de licença paga",
|
||||||
|
"billingManageLicenseSubscription": "Gerencie sua assinatura para as chaves de licenças auto-hospedadas pagas",
|
||||||
|
"billingCurrentKeys": "Chaves atuais",
|
||||||
|
"billingModifyCurrentPlan": "Modificar o Plano Atual",
|
||||||
|
"billingConfirmUpgrade": "Confirmar a atualização",
|
||||||
|
"billingConfirmDowngrade": "Confirmar downgrade",
|
||||||
|
"billingConfirmUpgradeDescription": "Você está prestes a atualizar seu plano. Revise os novos limites e preços abaixo.",
|
||||||
|
"billingConfirmDowngradeDescription": "Você está prestes a fazer o downgrade do seu plano. Revise os novos limites e preços abaixo.",
|
||||||
|
"billingPlanIncludes": "Plano Inclui",
|
||||||
|
"billingProcessing": "Processandochar@@0",
|
||||||
|
"billingConfirmUpgradeButton": "Confirmar a atualização",
|
||||||
|
"billingConfirmDowngradeButton": "Confirmar downgrade",
|
||||||
|
"billingLimitViolationWarning": "Uso excede novos limites de plano",
|
||||||
|
"billingLimitViolationDescription": "Seu uso atual excede os limites deste plano. Após desclassificação, todas as ações serão desabilitadas até que você reduza o uso dentro dos novos limites. Por favor, reveja os recursos abaixo que atualmente estão acima dos limites. Limites de violação:",
|
||||||
|
"billingFeatureLossWarning": "Aviso de disponibilidade de recursos",
|
||||||
|
"billingFeatureLossDescription": "Ao fazer o downgrading, recursos não disponíveis no novo plano serão desativados automaticamente. Algumas configurações e configurações podem ser perdidas. Por favor, revise a matriz de preços para entender quais características não estarão mais disponíveis.",
|
||||||
|
"billingUsageExceedsLimit": "Uso atual ({current}) excede o limite ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Concordo com",
|
"IAgreeToThe": "Concordo com",
|
||||||
"termsOfService": "os termos de serviço",
|
"termsOfService": "os termos de serviço",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Voltar para entrada padrão",
|
"orgAuthBackToSignIn": "Voltar para entrada padrão",
|
||||||
"orgAuthNoAccount": "Não possui uma conta?",
|
"orgAuthNoAccount": "Não possui uma conta?",
|
||||||
"subscriptionRequiredToUse": "Uma assinatura é necessária para usar esse recurso.",
|
"subscriptionRequiredToUse": "Uma assinatura é necessária para usar esse recurso.",
|
||||||
|
"mustUpgradeToUse": "Você deve atualizar sua assinatura para usar este recurso.",
|
||||||
|
"subscriptionRequiredTierToUse": "Esta função requer <tierLink>{tier}</tierLink> ou superior.",
|
||||||
|
"upgradeToTierToUse": "Atualize para <tierLink>{tier}</tierLink> ou superior para usar este recurso.",
|
||||||
|
"subscriptionTierTier1": "Residencial",
|
||||||
|
"subscriptionTierTier2": "Equipe",
|
||||||
|
"subscriptionTierTier3": "Empresas",
|
||||||
|
"subscriptionTierEnterprise": "Empresa",
|
||||||
"idpDisabled": "Provedores de identidade estão desabilitados.",
|
"idpDisabled": "Provedores de identidade estão desabilitados.",
|
||||||
"orgAuthPageDisabled": "A página de autenticação da organização está desativada.",
|
"orgAuthPageDisabled": "A página de autenticação da organização está desativada.",
|
||||||
"domainRestartedDescription": "Verificação de domínio reiniciado com sucesso",
|
"domainRestartedDescription": "Verificação de domínio reiniciado com sucesso",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Obtenha uma licença",
|
||||||
|
"description": "Escolha um plano e nos diga como você planeja usar o Pangolin.",
|
||||||
|
"chooseTier": "Escolha seu plano",
|
||||||
|
"viewPricingLink": "Veja os preços, recursos e limites",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Iniciante",
|
||||||
|
"description": "Recursos de empresa, 25 usuários, 25 sites e apoio da comunidade."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Escala",
|
||||||
|
"description": "Recursos de empresa, 50 usuários, 50 sites e apoio prioritário."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Apenas uso pessoal (licença gratuita — sem check-out)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Continuar com checkout"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Erro no check-out",
|
||||||
|
"description": "Não foi possível iniciar o checkout. Por favor, tente novamente."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Prioridade",
|
"priority": "Prioridade",
|
||||||
"priorityDescription": "Rotas de alta prioridade são avaliadas primeiro. Prioridade = 100 significa ordem automática (decisões do sistema). Use outro número para aplicar prioridade manual.",
|
"priorityDescription": "Rotas de alta prioridade são avaliadas primeiro. Prioridade = 100 significa ordem automática (decisões do sistema). Use outro número para aplicar prioridade manual.",
|
||||||
"instanceName": "Nome da Instância",
|
"instanceName": "Nome da Instância",
|
||||||
@@ -2211,7 +2283,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": "É necessária uma licença empresarial para usar esse recurso.",
|
"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>.",
|
||||||
|
"ossEnterpriseEditionRequired": "O <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> é necessário para usar este recurso. Este recurso também está disponível no <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Firewall habilitado",
|
"firewallEnabled": "Firewall habilitado",
|
||||||
"autoUpdatesEnabled": "Atualizações Automáticas Habilitadas",
|
"autoUpdatesEnabled": "Atualizações Automáticas Habilitadas",
|
||||||
"tpmAvailable": "TPM disponível",
|
"tpmAvailable": "TPM disponível",
|
||||||
|
"windowsAntivirusEnabled": "Antivírus habilitado",
|
||||||
"macosSipEnabled": "Proteção da Integridade do Sistema (SIP)",
|
"macosSipEnabled": "Proteção da Integridade do Sistema (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Modo Furtivo do Firewall",
|
"macosFirewallStealthMode": "Modo Furtivo do Firewall",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "Вы состоите в {count, plural, =0 {0 организациях} one {# организации} few {# организациях} many {# организациях} other {# организациях}}.",
|
"componentsMember": "Вы состоите в {count, plural, =0 {0 организациях} one {# организации} few {# организациях} many {# организациях} other {# организациях}}.",
|
||||||
"componentsInvalidKey": "Обнаружены недействительные или просроченные лицензионные ключи. Соблюдайте условия лицензии для использования всех функций.",
|
"componentsInvalidKey": "Обнаружены недействительные или просроченные лицензионные ключи. Соблюдайте условия лицензии для использования всех функций.",
|
||||||
"dismiss": "Отменить",
|
"dismiss": "Отменить",
|
||||||
|
"subscriptionViolationMessage": "Вы превысили лимиты для вашего текущего плана. Исправьте проблему, удалив сайты, пользователей или другие ресурсы, чтобы остаться в пределах вашего плана.",
|
||||||
|
"subscriptionViolationViewBilling": "Просмотр биллинга",
|
||||||
"componentsLicenseViolation": "Нарушение лицензии: Сервер использует {usedSites} сайтов, что превышает лицензионный лимит в {maxSites} сайтов. Соблюдайте условия лицензии для использования всех функций.",
|
"componentsLicenseViolation": "Нарушение лицензии: Сервер использует {usedSites} сайтов, что превышает лицензионный лимит в {maxSites} сайтов. Соблюдайте условия лицензии для использования всех функций.",
|
||||||
"componentsSupporterMessage": "Спасибо за поддержку Pangolin в качестве {tier}!",
|
"componentsSupporterMessage": "Спасибо за поддержку Pangolin в качестве {tier}!",
|
||||||
"inviteErrorNotValid": "Извините, но это приглашение не было принято или срок его действия истёк.",
|
"inviteErrorNotValid": "Извините, но это приглашение не было принято или срок его действия истёк.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Увеличить количество сайтов",
|
"sitestCountIncrease": "Увеличить количество сайтов",
|
||||||
"idpManage": "Управление поставщиками удостоверений",
|
"idpManage": "Управление поставщиками удостоверений",
|
||||||
"idpManageDescription": "Просмотр и управление поставщиками удостоверений в системе",
|
"idpManageDescription": "Просмотр и управление поставщиками удостоверений в системе",
|
||||||
|
"idpGlobalModeBanner": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в <adminPanelLink>админ-панели</adminPanelLink>. Чтобы включить IdP для каждой организации, отредактируйте конфигурацию сервера и установите режим IdP в org. <configDocsLink>См. документацию</configDocsLink>. Если вы хотите продолжать использовать глобальные IdP и скрыть это из настроек организации, явно установите режим в глобальном конфиге.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в <adminPanelLink>админ-панели</adminPanelLink>. Чтобы использовать поставщиков удостоверений для каждой организации, необходимо обновить систему до версии Enterprise.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в <adminPanelLink>админ-панели</adminPanelLink>. Для использования поставщиков удостоверений на организацию требуется лицензия Enterprise.",
|
||||||
"idpDeletedDescription": "Поставщик удостоверений успешно удалён",
|
"idpDeletedDescription": "Поставщик удостоверений успешно удалён",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Вы уверены, что хотите навсегда удалить поставщика удостоверений?",
|
"idpQuestionRemove": "Вы уверены, что хотите навсегда удалить поставщика удостоверений?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Обзор лимитов использования",
|
"billingUsageLimitsOverview": "Обзор лимитов использования",
|
||||||
"billingMonitorUsage": "Контролируйте использование в соответствии с установленными лимитами. Если вам требуется увеличение лимитов, пожалуйста, свяжитесь с нами support@pangolin.net.",
|
"billingMonitorUsage": "Контролируйте использование в соответствии с установленными лимитами. Если вам требуется увеличение лимитов, пожалуйста, свяжитесь с нами support@pangolin.net.",
|
||||||
"billingDataUsage": "Использование данных",
|
"billingDataUsage": "Использование данных",
|
||||||
"billingOnlineTime": "Время работы сайта",
|
"billingSites": "Сайты",
|
||||||
"billingUsers": "Активные пользователи",
|
"billingUsers": "Пользователи",
|
||||||
"billingDomains": "Активные домены",
|
"billingDomains": "Домены",
|
||||||
"billingRemoteExitNodes": "Активные самоуправляемые узлы",
|
"billingRemoteExitNodes": "Удаленные узлы",
|
||||||
"billingNoLimitConfigured": "Лимит не установлен",
|
"billingNoLimitConfigured": "Лимит не установлен",
|
||||||
"billingEstimatedPeriod": "Предполагаемый период выставления счетов",
|
"billingEstimatedPeriod": "Предполагаемый период выставления счетов",
|
||||||
"billingIncludedUsage": "Включенное использование",
|
"billingIncludedUsage": "Включенное использование",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Не удалось получить URL-адрес портала",
|
"billingFailedToGetPortalUrl": "Не удалось получить URL-адрес портала",
|
||||||
"billingPortalError": "Ошибка портала",
|
"billingPortalError": "Ошибка портала",
|
||||||
"billingDataUsageInfo": "Вы несете ответственность за все данные, переданные через безопасные туннели при подключении к облаку. Это включает как входящий, так и исходящий трафик на всех ваших сайтах. При достижении лимита ваши сайты будут отключаться до тех пор, пока вы не обновите план или не уменьшите его использование. При использовании узлов не взимается плата.",
|
"billingDataUsageInfo": "Вы несете ответственность за все данные, переданные через безопасные туннели при подключении к облаку. Это включает как входящий, так и исходящий трафик на всех ваших сайтах. При достижении лимита ваши сайты будут отключаться до тех пор, пока вы не обновите план или не уменьшите его использование. При использовании узлов не взимается плата.",
|
||||||
"billingOnlineTimeInfo": "Вы тарифицируете на то, как долго ваши сайты будут подключены к облаку. Например, 44 640 минут равны одному сайту, работающему круглосуточно за весь месяц. Когда вы достигните лимита, ваши сайты будут отключаться до тех пор, пока вы не обновите тарифный план или не сократите нагрузку. При использовании узлов не тарифицируется.",
|
"billingSInfo": "Сколько сайтов вы можете использовать",
|
||||||
"billingUsersInfo": "Вы оплачиваете за каждого пользователя в организации. Платеж рассчитывается ежедневно в зависимости от количества активных учетных записей в вашем органе.",
|
"billingUsersInfo": "Сколько пользователей вы можете использовать",
|
||||||
"billingDomainInfo": "Вы платите за каждый домен в организации. Платеж рассчитывается ежедневно в зависимости от количества активных доменных аккаунтов в вашем органе.",
|
"billingDomainInfo": "Сколько доменов вы можете использовать",
|
||||||
"billingRemoteExitNodesInfo": "Вы платите за каждый управляемый узел организации. Платёж рассчитывается ежедневно на основе количества активных управляемых узлов в вашем органе.",
|
"billingRemoteExitNodesInfo": "Сколько удаленных узлов вы можете использовать",
|
||||||
|
"billingLicenseKeys": "Лицензионные ключи",
|
||||||
|
"billingLicenseKeysDescription": "Управление подписками на лицензионные ключи",
|
||||||
|
"billingLicenseSubscription": "Лицензионное соглашение",
|
||||||
|
"billingInactive": "Неактивный",
|
||||||
|
"billingLicenseItem": "Элемент лицензии",
|
||||||
|
"billingQuantity": "Количество",
|
||||||
|
"billingTotal": "итого",
|
||||||
|
"billingModifyLicenses": "Изменить лицензию подписки",
|
||||||
"domainNotFound": "Домен не найден",
|
"domainNotFound": "Домен не найден",
|
||||||
"domainNotFoundDescription": "Этот ресурс отключен, так как домен больше не существует в нашей системе. Пожалуйста, установите новый домен для этого ресурса.",
|
"domainNotFoundDescription": "Этот ресурс отключен, так как домен больше не существует в нашей системе. Пожалуйста, установите новый домен для этого ресурса.",
|
||||||
"failed": "Ошибка",
|
"failed": "Ошибка",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "Номер порта необходим для не-HTTP ресурсов",
|
"resourcePortRequired": "Номер порта необходим для не-HTTP ресурсов",
|
||||||
"resourcePortNotAllowed": "Номер порта не должен быть установлен для HTTP ресурсов",
|
"resourcePortNotAllowed": "Номер порта не должен быть установлен для HTTP ресурсов",
|
||||||
"billingPricingCalculatorLink": "Калькулятор расценок",
|
"billingPricingCalculatorLink": "Калькулятор расценок",
|
||||||
|
"billingYourPlan": "Ваш план",
|
||||||
|
"billingViewOrModifyPlan": "Просмотреть или изменить ваш текущий тариф",
|
||||||
|
"billingViewPlanDetails": "Подробности плана",
|
||||||
|
"billingUsageAndLimits": "Использование и ограничения",
|
||||||
|
"billingViewUsageAndLimits": "Просмотр лимитов и текущего использования вашего плана",
|
||||||
|
"billingCurrentUsage": "Текущее использование",
|
||||||
|
"billingMaximumLimits": "Максимальные ограничения",
|
||||||
|
"billingRemoteNodes": "Удаленные узлы",
|
||||||
|
"billingUnlimited": "Неограниченный",
|
||||||
|
"billingPaidLicenseKeys": "Платные лицензионные ключи",
|
||||||
|
"billingManageLicenseSubscription": "Управление подпиской на платные лицензионные ключи собственного хостинга",
|
||||||
|
"billingCurrentKeys": "Текущие ключи",
|
||||||
|
"billingModifyCurrentPlan": "Изменить текущий план",
|
||||||
|
"billingConfirmUpgrade": "Подтвердить обновление",
|
||||||
|
"billingConfirmDowngrade": "Подтверждение понижения",
|
||||||
|
"billingConfirmUpgradeDescription": "Вы собираетесь обновить тарифный план. Проверьте новые лимиты и цены ниже.",
|
||||||
|
"billingConfirmDowngradeDescription": "Вы собираетесь понизить тарифный план. Проверьте новые ограничения и цены ниже.",
|
||||||
|
"billingPlanIncludes": "Включает план",
|
||||||
|
"billingProcessing": "Обработка...",
|
||||||
|
"billingConfirmUpgradeButton": "Подтвердить обновление",
|
||||||
|
"billingConfirmDowngradeButton": "Подтверждение понижения",
|
||||||
|
"billingLimitViolationWarning": "Превышено количество новых лимитов плана",
|
||||||
|
"billingLimitViolationDescription": "Ваше текущее использование превышает лимиты этого плана. После понижения значения все действия будут отключены до уменьшения использования в пределах новых лимитов. Пожалуйста, ознакомьтесь с функциями, которые в настоящее время превышают лимиты. Ограничения:",
|
||||||
|
"billingFeatureLossWarning": "Уведомление о доступности функций",
|
||||||
|
"billingFeatureLossDescription": "При переходе на другой тарифный план функции не будут автоматически отключены. Некоторые настройки и конфигурации могут быть потеряны. Пожалуйста, ознакомьтесь с матрицей ценообразования, чтобы понять, какие функции больше не будут доступны.",
|
||||||
|
"billingUsageExceedsLimit": "Текущее использование ({current}) превышает предел ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Я согласен с",
|
"IAgreeToThe": "Я согласен с",
|
||||||
"termsOfService": "условия использования",
|
"termsOfService": "условия использования",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Вернуться к стандартному входу",
|
"orgAuthBackToSignIn": "Вернуться к стандартному входу",
|
||||||
"orgAuthNoAccount": "Нет учётной записи?",
|
"orgAuthNoAccount": "Нет учётной записи?",
|
||||||
"subscriptionRequiredToUse": "Для использования этой функции требуется подписка.",
|
"subscriptionRequiredToUse": "Для использования этой функции требуется подписка.",
|
||||||
|
"mustUpgradeToUse": "Вы должны обновить подписку, чтобы использовать эту функцию.",
|
||||||
|
"subscriptionRequiredTierToUse": "Эта функция требует <tierLink>{tier}</tierLink> или выше.",
|
||||||
|
"upgradeToTierToUse": "Обновитесь до <tierLink>{tier}</tierLink> или выше, чтобы использовать эту функцию.",
|
||||||
|
"subscriptionTierTier1": "Главная",
|
||||||
|
"subscriptionTierTier2": "Команда",
|
||||||
|
"subscriptionTierTier3": "Бизнес",
|
||||||
|
"subscriptionTierEnterprise": "Предприятие",
|
||||||
"idpDisabled": "Провайдеры идентификации отключены.",
|
"idpDisabled": "Провайдеры идентификации отключены.",
|
||||||
"orgAuthPageDisabled": "Страница авторизации организации отключена.",
|
"orgAuthPageDisabled": "Страница авторизации организации отключена.",
|
||||||
"domainRestartedDescription": "Проверка домена успешно перезапущена",
|
"domainRestartedDescription": "Проверка домена успешно перезапущена",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Получить лицензию",
|
||||||
|
"description": "Выберите план и расскажите нам, как вы планируете использовать Панголин.",
|
||||||
|
"chooseTier": "Выберите ваш план",
|
||||||
|
"viewPricingLink": "Смотрите цены, возможности и ограничения",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Старт",
|
||||||
|
"description": "Функции предприятия, 25 пользователей, 25 сайтов, и поддержка сообщества."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Масштаб",
|
||||||
|
"description": "Функции предприятия, 50 пользователей, 50 сайтов, а также приоритетная поддержка."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Только для личного пользования (бесплатная лицензия — без оформления)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Продолжить оформление заказа"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Ошибка оформления заказа",
|
||||||
|
"description": "Не удалось начать оформление заказа. Пожалуйста, попробуйте еще раз."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Приоритет",
|
"priority": "Приоритет",
|
||||||
"priorityDescription": "Маршруты с более высоким приоритетом оцениваются первым. Приоритет = 100 означает автоматическое упорядочение (решение системы). Используйте другой номер для обеспечения ручного приоритета.",
|
"priorityDescription": "Маршруты с более высоким приоритетом оцениваются первым. Приоритет = 100 означает автоматическое упорядочение (решение системы). Используйте другой номер для обеспечения ручного приоритета.",
|
||||||
"instanceName": "Имя экземпляра",
|
"instanceName": "Имя экземпляра",
|
||||||
@@ -2211,7 +2283,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "Конец следующего года",
|
"logRetentionEndOfFollowingYear": "Конец следующего года",
|
||||||
"actionLogsDescription": "Просмотр истории действий, выполненных в этой организации",
|
"actionLogsDescription": "Просмотр истории действий, выполненных в этой организации",
|
||||||
"accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации",
|
"accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации",
|
||||||
"licenseRequiredToUse": "Для использования этой функции требуется лицензия предприятия.",
|
"licenseRequiredToUse": "Лицензия на <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> требуется для использования этой функции. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
|
"ossEnterpriseEditionRequired": "Для использования этой функции требуется <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink>. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||||
"certResolver": "Резольвер сертификата",
|
"certResolver": "Резольвер сертификата",
|
||||||
"certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.",
|
"certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.",
|
||||||
"selectCertResolver": "Выберите резолвер сертификата",
|
"selectCertResolver": "Выберите резолвер сертификата",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Брандмауэр включен",
|
"firewallEnabled": "Брандмауэр включен",
|
||||||
"autoUpdatesEnabled": "Автоматические обновления включены",
|
"autoUpdatesEnabled": "Автоматические обновления включены",
|
||||||
"tpmAvailable": "Доступно TPM",
|
"tpmAvailable": "Доступно TPM",
|
||||||
|
"windowsAntivirusEnabled": "Антивирус включен",
|
||||||
"macosSipEnabled": "Защита целостности системы (SIP)",
|
"macosSipEnabled": "Защита целостности системы (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Стилс-режим брандмауэра",
|
"macosFirewallStealthMode": "Стилс-режим брандмауэра",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "{count, plural, =0 {hiçbir organizasyon} one {bir organizasyon} other {# organizasyon}} üyesisiniz.",
|
"componentsMember": "{count, plural, =0 {hiçbir organizasyon} one {bir organizasyon} other {# organizasyon}} üyesisiniz.",
|
||||||
"componentsInvalidKey": "Geçersiz veya süresi dolmuş lisans anahtarları tespit edildi. Tüm özellikleri kullanmaya devam etmek için lisans koşullarına uyun.",
|
"componentsInvalidKey": "Geçersiz veya süresi dolmuş lisans anahtarları tespit edildi. Tüm özellikleri kullanmaya devam etmek için lisans koşullarına uyun.",
|
||||||
"dismiss": "Kapat",
|
"dismiss": "Kapat",
|
||||||
|
"subscriptionViolationMessage": "Geçerli planınız için limitlerinizi aştınız. Planınız dahilinde kalmak için siteleri, kullanıcıları veya diğer kaynakları kaldırarak sorunu düzeltin.",
|
||||||
|
"subscriptionViolationViewBilling": "Faturalamayı görüntüle",
|
||||||
"componentsLicenseViolation": "Lisans İhlali: Bu sunucu, lisanslı sınırı olan {maxSites} sitesini aşarak {usedSites} site kullanmaktadır. Tüm özellikleri kullanmaya devam etmek için lisans koşullarına uyun.",
|
"componentsLicenseViolation": "Lisans İhlali: Bu sunucu, lisanslı sınırı olan {maxSites} sitesini aşarak {usedSites} site kullanmaktadır. Tüm özellikleri kullanmaya devam etmek için lisans koşullarına uyun.",
|
||||||
"componentsSupporterMessage": "Pangolin'e {tier} olarak destek olduğunuz için teşekkür ederiz!",
|
"componentsSupporterMessage": "Pangolin'e {tier} olarak destek olduğunuz için teşekkür ederiz!",
|
||||||
"inviteErrorNotValid": "Üzgünüz, ancak erişmeye çalıştığınız davet kabul edilmemiş veya artık geçerli değil gibi görünüyor.",
|
"inviteErrorNotValid": "Üzgünüz, ancak erişmeye çalıştığınız davet kabul edilmemiş veya artık geçerli değil gibi görünüyor.",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "Site sayısını artır",
|
"sitestCountIncrease": "Site sayısını artır",
|
||||||
"idpManage": "Kimlik Sağlayıcılarını Yönet",
|
"idpManage": "Kimlik Sağlayıcılarını Yönet",
|
||||||
"idpManageDescription": "Sistem içindeki kimlik sağlayıcıları görün ve yönetin",
|
"idpManageDescription": "Sistem içindeki kimlik sağlayıcıları görün ve yönetin",
|
||||||
|
"idpGlobalModeBanner": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri <adminPanelLink> yönetici panelinde </adminPanelLink>yönetin. Örgüt başına IdP'leri etkinleştirmek için, sunucu yapılandırmasını düzenleyin ve IdP modunu 'org' olarak ayarlayın. <configDocsLink> Belgeleri inceleyin </configDocsLink>. Küresel IdP'leri kullanmaya devam etmek istiyorsanız ve bunun örgüt ayarlarından kaybolmasını istiyorsanız, yapılandırmada modu otomatik olarak 'global' olarak ayarlayın.",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri <adminPanelLink> yönetici panelinde </adminPanelLink>yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için, Enterprise sürümüne yükseltmeniz gerekmektedir.",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri <adminPanelLink> yönetici panelinde </adminPanelLink>yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için Enterprise lisansı gereklidir.",
|
||||||
"idpDeletedDescription": "Kimlik sağlayıcı başarıyla silindi",
|
"idpDeletedDescription": "Kimlik sağlayıcı başarıyla silindi",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "Kimlik sağlayıcısını kalıcı olarak silmek istediğinizden emin misiniz?",
|
"idpQuestionRemove": "Kimlik sağlayıcısını kalıcı olarak silmek istediğinizden emin misiniz?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "Kullanım Limitleri Genel Görünümü",
|
"billingUsageLimitsOverview": "Kullanım Limitleri Genel Görünümü",
|
||||||
"billingMonitorUsage": "Kullanımınızı yapılandırılmış limitlerle karşılaştırın. Limitlerin artırılmasına ihtiyacınız varsa, lütfen support@pangolin.net adresinden bizimle iletişime geçin.",
|
"billingMonitorUsage": "Kullanımınızı yapılandırılmış limitlerle karşılaştırın. Limitlerin artırılmasına ihtiyacınız varsa, lütfen support@pangolin.net adresinden bizimle iletişime geçin.",
|
||||||
"billingDataUsage": "Veri Kullanımı",
|
"billingDataUsage": "Veri Kullanımı",
|
||||||
"billingOnlineTime": "Site Çevrimiçi Süresi",
|
"billingSites": "Siteler",
|
||||||
"billingUsers": "Aktif Kullanıcılar",
|
"billingUsers": "Kullanıcılar",
|
||||||
"billingDomains": "Aktif Alanlar",
|
"billingDomains": "Alan Adları",
|
||||||
"billingRemoteExitNodes": "Aktif Öz-Host Düğümleri",
|
"billingRemoteExitNodes": "Uzak Düğümler",
|
||||||
"billingNoLimitConfigured": "Hiçbir limit yapılandırılmadı",
|
"billingNoLimitConfigured": "Hiçbir limit yapılandırılmadı",
|
||||||
"billingEstimatedPeriod": "Tahmini Fatura Dönemi",
|
"billingEstimatedPeriod": "Tahmini Fatura Dönemi",
|
||||||
"billingIncludedUsage": "Dahil Kullanım",
|
"billingIncludedUsage": "Dahil Kullanım",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "Portal URL'si alınamadı",
|
"billingFailedToGetPortalUrl": "Portal URL'si alınamadı",
|
||||||
"billingPortalError": "Portal Hatası",
|
"billingPortalError": "Portal Hatası",
|
||||||
"billingDataUsageInfo": "Buluta bağlandığınızda, güvenli tünellerinizden aktarılan tüm verilerden ücret alınırsınız. Bu, tüm sitelerinizdeki gelen ve giden trafiği içerir. Limitinize ulaştığınızda, planınızı yükseltmeli veya kullanımı azaltmalısınız, aksi takdirde siteleriniz bağlantıyı keser. Düğümler kullanırken verilerden ücret alınmaz.",
|
"billingDataUsageInfo": "Buluta bağlandığınızda, güvenli tünellerinizden aktarılan tüm verilerden ücret alınırsınız. Bu, tüm sitelerinizdeki gelen ve giden trafiği içerir. Limitinize ulaştığınızda, planınızı yükseltmeli veya kullanımı azaltmalısınız, aksi takdirde siteleriniz bağlantıyı keser. Düğümler kullanırken verilerden ücret alınmaz.",
|
||||||
"billingOnlineTimeInfo": "Sitelerinizin buluta ne kadar süre bağlı kaldığına göre ücretlendirilirsiniz. Örneğin, 44,640 dakika, bir sitenin 24/7 boyunca tam bir ay boyunca çalışması anlamına gelir. Limitinize ulaştığınızda, planınızı yükseltmeyip kullanımı azaltmazsanız siteleriniz bağlantıyı keser. Düğümler kullanırken zamandan ücret alınmaz.",
|
"billingSInfo": "Kaç tane site kullanabileceğiniz",
|
||||||
"billingUsersInfo": "Kuruluşunuzdaki her kullanıcı için ücretlendirilirsiniz. Faturalandırma, organizasyonunuza kayıtlı aktif kullanıcı hesaplarının sayısına göre günlük olarak hesaplanır.",
|
"billingUsersInfo": "Kaç tane kullanıcı kullanabileceğiniz",
|
||||||
"billingDomainInfo": "Kuruluşunuzdaki her alan adı için ücretlendirilirsiniz. Faturalandırma, organizasyonunuza kayıtlı aktif alan adları hesaplarının sayısına göre günlük olarak hesaplanır.",
|
"billingDomainInfo": "Kaç tane alan adı kullanabileceğiniz",
|
||||||
"billingRemoteExitNodesInfo": "Kuruluşunuzdaki her yönetilen Düğüm için ücretlendirilirsiniz. Faturalandırma, organizasyonunuza kayıtlı aktif yönetilen Düğümler sayısına göre günlük olarak hesaplanır.",
|
"billingRemoteExitNodesInfo": "Kaç tane uzaktan düğüm kullanabileceğiniz",
|
||||||
|
"billingLicenseKeys": "Lisans Anahtarları",
|
||||||
|
"billingLicenseKeysDescription": "Lisans anahtarı aboneliklerinizi yönetin",
|
||||||
|
"billingLicenseSubscription": "Lisans Aboneliği",
|
||||||
|
"billingInactive": "Pasif",
|
||||||
|
"billingLicenseItem": "Lisans Öğesi",
|
||||||
|
"billingQuantity": "Miktar",
|
||||||
|
"billingTotal": "toplam",
|
||||||
|
"billingModifyLicenses": "Lisans Aboneliğini Düzenle",
|
||||||
"domainNotFound": "Alan Adı Bulunamadı",
|
"domainNotFound": "Alan Adı Bulunamadı",
|
||||||
"domainNotFoundDescription": "Bu kaynak devre dışıdır çünkü alan adı sistemimizde artık mevcut değil. Bu kaynak için yeni bir alan adı belirleyin.",
|
"domainNotFoundDescription": "Bu kaynak devre dışıdır çünkü alan adı sistemimizde artık mevcut değil. Bu kaynak için yeni bir alan adı belirleyin.",
|
||||||
"failed": "Başarısız",
|
"failed": "Başarısız",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "HTTP dışı kaynaklar için bağlantı noktası numarası gereklidir",
|
"resourcePortRequired": "HTTP dışı kaynaklar için bağlantı noktası numarası gereklidir",
|
||||||
"resourcePortNotAllowed": "HTTP kaynakları için bağlantı noktası numarası ayarlanmamalı",
|
"resourcePortNotAllowed": "HTTP kaynakları için bağlantı noktası numarası ayarlanmamalı",
|
||||||
"billingPricingCalculatorLink": "Fiyat Hesaplayıcı",
|
"billingPricingCalculatorLink": "Fiyat Hesaplayıcı",
|
||||||
|
"billingYourPlan": "Planınız",
|
||||||
|
"billingViewOrModifyPlan": "Mevcut planınızı görüntüleyin veya düzenleyin",
|
||||||
|
"billingViewPlanDetails": "Plan Detaylarını Görüntüle",
|
||||||
|
"billingUsageAndLimits": "Kullanım ve Sınırlar",
|
||||||
|
"billingViewUsageAndLimits": "Planınızın limitlerini ve mevcut kullanım durumunu görüntüleyin",
|
||||||
|
"billingCurrentUsage": "Mevcut Kullanım",
|
||||||
|
"billingMaximumLimits": "Maksimum Sınırlar",
|
||||||
|
"billingRemoteNodes": "Uzak Düğümler",
|
||||||
|
"billingUnlimited": "Sınırsız",
|
||||||
|
"billingPaidLicenseKeys": "Ücretli Lisans Anahtarları",
|
||||||
|
"billingManageLicenseSubscription": "Kendi barındırdığınız ücretli lisans anahtarları için aboneliğinizi yönetin",
|
||||||
|
"billingCurrentKeys": "Mevcut Anahtarlar",
|
||||||
|
"billingModifyCurrentPlan": "Mevcut Planı Düzenle",
|
||||||
|
"billingConfirmUpgrade": "Yükseltmeyi Onayla",
|
||||||
|
"billingConfirmDowngrade": "Düşürmeyi Onayla",
|
||||||
|
"billingConfirmUpgradeDescription": "Planınızı yükseltmek üzeresiniz. Yeni limitleri ve fiyatları aşağıda inceleyin.",
|
||||||
|
"billingConfirmDowngradeDescription": "Planınızı düşürmek üzeresiniz. Yeni limitleri ve fiyatları aşağıda inceleyin.",
|
||||||
|
"billingPlanIncludes": "Plan İçerikleri",
|
||||||
|
"billingProcessing": "İşleniyor...",
|
||||||
|
"billingConfirmUpgradeButton": "Yükseltmeyi Onayla",
|
||||||
|
"billingConfirmDowngradeButton": "Düşürmeyi Onayla",
|
||||||
|
"billingLimitViolationWarning": "Kullanım Yeni Plan Sınırlarını Aşıyor",
|
||||||
|
"billingLimitViolationDescription": "Mevcut kullanımınız bu planın sınırlarını aşıyor. Düzeltmelerden sonra, yeni sınırlar içinde kalana kadar tüm işlemler devre dışı bırakılacak. Lütfen şu anda limitlerin üzerinde olan özellikleri inceleyin. İhlal edilen sınırlar:",
|
||||||
|
"billingFeatureLossWarning": "Özellik Kullanılabilirlik Bildirimi",
|
||||||
|
"billingFeatureLossDescription": "Plan düşürüldüğünde, yeni planda mevcut olmayan özellikler otomatik olarak devre dışı bırakılacaktır. Bazı ayarlar ve yapılar kaybolabilir. Hangi özelliklerin artık mevcut olmayacağını anlamak için fiyat tablosunu inceleyiniz.",
|
||||||
|
"billingUsageExceedsLimit": "Mevcut kullanım ({current}) limitleri ({limit}) aşıyor",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "Kabul ediyorum",
|
"IAgreeToThe": "Kabul ediyorum",
|
||||||
"termsOfService": "hizmet şartları",
|
"termsOfService": "hizmet şartları",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "Standart girişe geri dön",
|
"orgAuthBackToSignIn": "Standart girişe geri dön",
|
||||||
"orgAuthNoAccount": "Hesabınız yok mu?",
|
"orgAuthNoAccount": "Hesabınız yok mu?",
|
||||||
"subscriptionRequiredToUse": "Bu özelliği kullanmak için abonelik gerekmektedir.",
|
"subscriptionRequiredToUse": "Bu özelliği kullanmak için abonelik gerekmektedir.",
|
||||||
|
"mustUpgradeToUse": "Bu özelliği kullanmak için aboneliğinizi yükseltmelisiniz.",
|
||||||
|
"subscriptionRequiredTierToUse": "Bu özellik <tierLink>{tier}</tierLink> veya daha üstünü gerektirir.",
|
||||||
|
"upgradeToTierToUse": "Bu özelliği kullanmak için <tierLink>{tier}</tierLink> veya daha üst bir seviyeye yükseltin.",
|
||||||
|
"subscriptionTierTier1": "Ana Sayfa",
|
||||||
|
"subscriptionTierTier2": "Takım",
|
||||||
|
"subscriptionTierTier3": "İşletme",
|
||||||
|
"subscriptionTierEnterprise": "Kurumsal",
|
||||||
"idpDisabled": "Kimlik sağlayıcılar devre dışı bırakılmıştır.",
|
"idpDisabled": "Kimlik sağlayıcılar devre dışı bırakılmıştır.",
|
||||||
"orgAuthPageDisabled": "Kuruluş kimlik doğrulama sayfası devre dışı bırakılmıştır.",
|
"orgAuthPageDisabled": "Kuruluş kimlik doğrulama sayfası devre dışı bırakılmıştır.",
|
||||||
"domainRestartedDescription": "Alan doğrulaması başarıyla yeniden başlatıldı",
|
"domainRestartedDescription": "Alan doğrulaması başarıyla yeniden başlatıldı",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "Bir lisans alın",
|
||||||
|
"description": "Bir plan seçin ve Pangolin'i nasıl kullanmayı planladığınızı anlatın.",
|
||||||
|
"chooseTier": "Planınızı seçin",
|
||||||
|
"viewPricingLink": "Fiyatları, özellikleri ve limitleri görüntüleyin",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "Başlangıç",
|
||||||
|
"description": "Kurumsal özellikler, 25 kullanıcı, 25 site ve topluluk desteği."
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "Ölçek",
|
||||||
|
"description": "Kurumsal özellikler, 50 kullanıcı, 50 site ve öncelikli destek."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "Yalnızca kişisel kullanım (ücretsiz lisans — ödeme yapılmaz)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "Ödemeye Devam Et"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "Ödeme Hatası",
|
||||||
|
"description": "Ödeme işlemi başlatılamadı. Lütfen tekrar deneyin."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "Öncelik",
|
"priority": "Öncelik",
|
||||||
"priorityDescription": "Daha yüksek öncelikli rotalar önce değerlendirilir. Öncelik = 100, otomatik sıralama anlamına gelir (sistem karar verir). Manuel öncelik uygulamak için başka bir numara kullanın.",
|
"priorityDescription": "Daha yüksek öncelikli rotalar önce değerlendirilir. Öncelik = 100, otomatik sıralama anlamına gelir (sistem karar verir). Manuel öncelik uygulamak için başka bir numara kullanın.",
|
||||||
"instanceName": "Örnek İsmi",
|
"instanceName": "Örnek İsmi",
|
||||||
@@ -2211,7 +2283,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 kurumsal lisans gereklidir.",
|
"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.",
|
||||||
|
"ossEnterpriseEditionRequired": "Bu özelliği kullanmak için <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> gereklidir. Bu özellik ayrıca <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>'da da mevcuttur.",
|
||||||
"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",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "Güvenlik Duvarı Etkin",
|
"firewallEnabled": "Güvenlik Duvarı Etkin",
|
||||||
"autoUpdatesEnabled": "Otomatik Güncellemeler Etkin",
|
"autoUpdatesEnabled": "Otomatik Güncellemeler Etkin",
|
||||||
"tpmAvailable": "TPM Mevcut",
|
"tpmAvailable": "TPM Mevcut",
|
||||||
|
"windowsAntivirusEnabled": "Antivirüs Etkinleştirildi",
|
||||||
"macosSipEnabled": "Sistem Bütünlüğü Koruması (SIP)",
|
"macosSipEnabled": "Sistem Bütünlüğü Koruması (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "Güvenlik Duvarı Gizlilik Modu",
|
"macosFirewallStealthMode": "Güvenlik Duvarı Gizlilik Modu",
|
||||||
|
|||||||
@@ -18,6 +18,8 @@
|
|||||||
"componentsMember": "您属于{count, plural, =0 {没有组织} one {一个组织} other {# 个组织}}。",
|
"componentsMember": "您属于{count, plural, =0 {没有组织} one {一个组织} other {# 个组织}}。",
|
||||||
"componentsInvalidKey": "检测到无效或过期的许可证密钥。按照许可证条款操作以继续使用所有功能。",
|
"componentsInvalidKey": "检测到无效或过期的许可证密钥。按照许可证条款操作以继续使用所有功能。",
|
||||||
"dismiss": "忽略",
|
"dismiss": "忽略",
|
||||||
|
"subscriptionViolationMessage": "您的当前计划超出了您的限制。通过移除站点、用户或其他资源以保持在您的计划范围内来纠正问题。",
|
||||||
|
"subscriptionViolationViewBilling": "查看计费",
|
||||||
"componentsLicenseViolation": "许可证超限:该服务器使用了 {usedSites} 个站点,已超过授权的 {maxSites} 个。请遵守许可证条款以继续使用全部功能。",
|
"componentsLicenseViolation": "许可证超限:该服务器使用了 {usedSites} 个站点,已超过授权的 {maxSites} 个。请遵守许可证条款以继续使用全部功能。",
|
||||||
"componentsSupporterMessage": "感谢您的支持!您现在是 Pangolin 的 {tier} 用户。",
|
"componentsSupporterMessage": "感谢您的支持!您现在是 Pangolin 的 {tier} 用户。",
|
||||||
"inviteErrorNotValid": "很抱歉,但看起来你试图访问的邀请尚未被接受或不再有效。",
|
"inviteErrorNotValid": "很抱歉,但看起来你试图访问的邀请尚未被接受或不再有效。",
|
||||||
@@ -789,6 +791,9 @@
|
|||||||
"sitestCountIncrease": "增加站点数量",
|
"sitestCountIncrease": "增加站点数量",
|
||||||
"idpManage": "管理身份提供商",
|
"idpManage": "管理身份提供商",
|
||||||
"idpManageDescription": "查看和管理系统中的身份提供商",
|
"idpManageDescription": "查看和管理系统中的身份提供商",
|
||||||
|
"idpGlobalModeBanner": "此服务器上禁用了每个组织的身份提供商(Idps)。 它正在使用全局IdP(所有组织共享)。在 <adminPanelLink>管理面板</adminPanelLink>中管理全局IdP。 要启用每个组织的 IdP,请编辑服务器配置并将 IdP 模式设置为 org。 <configDocsLink>请参阅文档</configDocsLink>。 如果您想要继续使用全局IdP并使其从组织设置中消失,请在配置中将模式设置为全局模式。",
|
||||||
|
"idpGlobalModeBannerUpgradeRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 <adminPanelLink>管理面板</adminPanelLink>管理全局身份。要使用每个组织的身份提供者,您必须升级到企业版本。",
|
||||||
|
"idpGlobalModeBannerLicenseRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 <adminPanelLink>管理面板</adminPanelLink>管理全局身份。要使用每个组织的身份提供者,需要企业许可证。",
|
||||||
"idpDeletedDescription": "身份提供商删除成功",
|
"idpDeletedDescription": "身份提供商删除成功",
|
||||||
"idpOidc": "OAuth2/OIDC",
|
"idpOidc": "OAuth2/OIDC",
|
||||||
"idpQuestionRemove": "您确定要永久删除身份提供者吗?",
|
"idpQuestionRemove": "您确定要永久删除身份提供者吗?",
|
||||||
@@ -1404,10 +1409,10 @@
|
|||||||
"billingUsageLimitsOverview": "使用限制概览",
|
"billingUsageLimitsOverview": "使用限制概览",
|
||||||
"billingMonitorUsage": "监控您的使用情况以对比已配置的限制。如需提高限制请联系我们 support@pangolin.net。",
|
"billingMonitorUsage": "监控您的使用情况以对比已配置的限制。如需提高限制请联系我们 support@pangolin.net。",
|
||||||
"billingDataUsage": "数据使用情况",
|
"billingDataUsage": "数据使用情况",
|
||||||
"billingOnlineTime": "站点在线时间",
|
"billingSites": "站点",
|
||||||
"billingUsers": "活跃用户",
|
"billingUsers": "用户",
|
||||||
"billingDomains": "活跃域",
|
"billingDomains": "域",
|
||||||
"billingRemoteExitNodes": "活跃自托管节点",
|
"billingRemoteExitNodes": "远程节点",
|
||||||
"billingNoLimitConfigured": "未配置限制",
|
"billingNoLimitConfigured": "未配置限制",
|
||||||
"billingEstimatedPeriod": "估计结算周期",
|
"billingEstimatedPeriod": "估计结算周期",
|
||||||
"billingIncludedUsage": "包含的使用量",
|
"billingIncludedUsage": "包含的使用量",
|
||||||
@@ -1432,10 +1437,18 @@
|
|||||||
"billingFailedToGetPortalUrl": "无法获取门户网址",
|
"billingFailedToGetPortalUrl": "无法获取门户网址",
|
||||||
"billingPortalError": "门户错误",
|
"billingPortalError": "门户错误",
|
||||||
"billingDataUsageInfo": "当连接到云端时,您将为通过安全隧道传输的所有数据收取费用。 这包括您所有站点的进出流量。 当您达到上限时,您的站点将断开连接,直到您升级计划或减少使用。使用节点时不收取数据。",
|
"billingDataUsageInfo": "当连接到云端时,您将为通过安全隧道传输的所有数据收取费用。 这包括您所有站点的进出流量。 当您达到上限时,您的站点将断开连接,直到您升级计划或减少使用。使用节点时不收取数据。",
|
||||||
"billingOnlineTimeInfo": "您要根据您的网站连接到云端的时间长短收取费用。 例如,44,640分钟等于一个24/7全月运行的网站。 当您达到上限时,您的站点将断开连接,直到您升级计划或减少使用。使用节点时不收取费用。",
|
"billingSInfo": "您可以使用多少站点",
|
||||||
"billingUsersInfo": "您为组织中的每个用户收取费用。每日计费是根据您组织中活跃用户帐户的数量计算的。",
|
"billingUsersInfo": "您可以使用多少用户",
|
||||||
"billingDomainInfo": "您在组织中的每个域都要收取费用。每日计费是根据您组织中的活动域帐户数计算的。",
|
"billingDomainInfo": "您可以使用多少域",
|
||||||
"billingRemoteExitNodesInfo": "您为组织中的每个管理节点收取费用。计费是每日根据您组织中活跃的管理节点数计算的。",
|
"billingRemoteExitNodesInfo": "您可以使用多少远程节点",
|
||||||
|
"billingLicenseKeys": "许可证密钥",
|
||||||
|
"billingLicenseKeysDescription": "管理您的许可证密钥订阅",
|
||||||
|
"billingLicenseSubscription": "许可订阅",
|
||||||
|
"billingInactive": "未激活",
|
||||||
|
"billingLicenseItem": "许可证项目",
|
||||||
|
"billingQuantity": "数量",
|
||||||
|
"billingTotal": "总计",
|
||||||
|
"billingModifyLicenses": "修改许可订阅",
|
||||||
"domainNotFound": "域未找到",
|
"domainNotFound": "域未找到",
|
||||||
"domainNotFoundDescription": "此资源已禁用,因为该域不再在我们的系统中存在。请为此资源设置一个新域。",
|
"domainNotFoundDescription": "此资源已禁用,因为该域不再在我们的系统中存在。请为此资源设置一个新域。",
|
||||||
"failed": "失败",
|
"failed": "失败",
|
||||||
@@ -1512,6 +1525,32 @@
|
|||||||
"resourcePortRequired": "非 HTTP 资源必须输入端口号",
|
"resourcePortRequired": "非 HTTP 资源必须输入端口号",
|
||||||
"resourcePortNotAllowed": "HTTP 资源不应设置端口号",
|
"resourcePortNotAllowed": "HTTP 资源不应设置端口号",
|
||||||
"billingPricingCalculatorLink": "价格计算器",
|
"billingPricingCalculatorLink": "价格计算器",
|
||||||
|
"billingYourPlan": "您的计划",
|
||||||
|
"billingViewOrModifyPlan": "查看或修改您当前的计划",
|
||||||
|
"billingViewPlanDetails": "查看计划详细信息",
|
||||||
|
"billingUsageAndLimits": "用法和限制",
|
||||||
|
"billingViewUsageAndLimits": "查看您的计划限制和当前使用情况",
|
||||||
|
"billingCurrentUsage": "当前使用情况",
|
||||||
|
"billingMaximumLimits": "最大限制",
|
||||||
|
"billingRemoteNodes": "远程节点",
|
||||||
|
"billingUnlimited": "无限制",
|
||||||
|
"billingPaidLicenseKeys": "付费许可证密钥",
|
||||||
|
"billingManageLicenseSubscription": "管理您对付费的自托管许可证密钥的订阅",
|
||||||
|
"billingCurrentKeys": "当前密钥",
|
||||||
|
"billingModifyCurrentPlan": "修改当前计划",
|
||||||
|
"billingConfirmUpgrade": "确认升级",
|
||||||
|
"billingConfirmDowngrade": "确认降级",
|
||||||
|
"billingConfirmUpgradeDescription": "您即将升级您的计划。请检查下面的新限额和定价。",
|
||||||
|
"billingConfirmDowngradeDescription": "您即将降级计划。请检查下面的新限额和定价。",
|
||||||
|
"billingPlanIncludes": "计划包含",
|
||||||
|
"billingProcessing": "正在处理...",
|
||||||
|
"billingConfirmUpgradeButton": "确认升级",
|
||||||
|
"billingConfirmDowngradeButton": "确认降级",
|
||||||
|
"billingLimitViolationWarning": "超出新计划限制",
|
||||||
|
"billingLimitViolationDescription": "您当前的使用量超过了此计划的限制。降级后,所有操作都将被禁用,直到您在新的限制范围内减少使用量。 请查看以下当前超出限制的特性:",
|
||||||
|
"billingFeatureLossWarning": "功能可用通知",
|
||||||
|
"billingFeatureLossDescription": "如果降级,新计划中不可用的功能将被自动禁用。一些设置和配置可能会丢失。 请查看定价矩阵以了解哪些功能将不再可用。",
|
||||||
|
"billingUsageExceedsLimit": "当前使用量 ({current}) 超出限制 ({limit})",
|
||||||
"signUpTerms": {
|
"signUpTerms": {
|
||||||
"IAgreeToThe": "我同意",
|
"IAgreeToThe": "我同意",
|
||||||
"termsOfService": "服务条款",
|
"termsOfService": "服务条款",
|
||||||
@@ -1926,6 +1965,13 @@
|
|||||||
"orgAuthBackToSignIn": "返回标准登录",
|
"orgAuthBackToSignIn": "返回标准登录",
|
||||||
"orgAuthNoAccount": "没有账户?",
|
"orgAuthNoAccount": "没有账户?",
|
||||||
"subscriptionRequiredToUse": "需要订阅才能使用此功能。",
|
"subscriptionRequiredToUse": "需要订阅才能使用此功能。",
|
||||||
|
"mustUpgradeToUse": "您必须升级您的订阅才能使用此功能。",
|
||||||
|
"subscriptionRequiredTierToUse": "此功能需要 <tierLink>{tier}</tierLink> 或更高级别。",
|
||||||
|
"upgradeToTierToUse": "升级到 <tierLink>{tier}</tierLink> 或更高级别以使用此功能。",
|
||||||
|
"subscriptionTierTier1": "首页",
|
||||||
|
"subscriptionTierTier2": "团队",
|
||||||
|
"subscriptionTierTier3": "业务",
|
||||||
|
"subscriptionTierEnterprise": "企业",
|
||||||
"idpDisabled": "身份提供者已禁用。",
|
"idpDisabled": "身份提供者已禁用。",
|
||||||
"orgAuthPageDisabled": "组织认证页面已禁用。",
|
"orgAuthPageDisabled": "组织认证页面已禁用。",
|
||||||
"domainRestartedDescription": "域验证重新启动成功",
|
"domainRestartedDescription": "域验证重新启动成功",
|
||||||
@@ -2113,6 +2159,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"newPricingLicenseForm": {
|
||||||
|
"title": "获取许可证",
|
||||||
|
"description": "选择一个计划,告诉我们你计划如何使用 Pangolin。",
|
||||||
|
"chooseTier": "选择您的计划",
|
||||||
|
"viewPricingLink": "查看价格、特征和限制",
|
||||||
|
"tiers": {
|
||||||
|
"starter": {
|
||||||
|
"title": "启动器",
|
||||||
|
"description": "企业特征,25个用户,25个站点和社区支持。"
|
||||||
|
},
|
||||||
|
"scale": {
|
||||||
|
"title": "缩放比例",
|
||||||
|
"description": "企业特征、50个用户、50个站点和优先支持。"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personalUseOnly": "仅供个人使用 (免费许可证-无签出)",
|
||||||
|
"buttons": {
|
||||||
|
"continueToCheckout": "继续签出"
|
||||||
|
},
|
||||||
|
"toasts": {
|
||||||
|
"checkoutError": {
|
||||||
|
"title": "签出错误",
|
||||||
|
"description": "无法启动结帐。请重试。"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"priority": "优先权",
|
"priority": "优先权",
|
||||||
"priorityDescription": "先评估更高优先级线路。优先级 = 100意味着自动排序(系统决定). 使用另一个数字强制执行手动优先级。",
|
"priorityDescription": "先评估更高优先级线路。优先级 = 100意味着自动排序(系统决定). 使用另一个数字强制执行手动优先级。",
|
||||||
"instanceName": "实例名称",
|
"instanceName": "实例名称",
|
||||||
@@ -2211,7 +2283,8 @@
|
|||||||
"logRetentionEndOfFollowingYear": "下一年结束",
|
"logRetentionEndOfFollowingYear": "下一年结束",
|
||||||
"actionLogsDescription": "查看此机构执行的操作历史",
|
"actionLogsDescription": "查看此机构执行的操作历史",
|
||||||
"accessLogsDescription": "查看此机构资源的访问认证请求",
|
"accessLogsDescription": "查看此机构资源的访问认证请求",
|
||||||
"licenseRequiredToUse": "需要企业许可证才能使用此功能。",
|
"licenseRequiredToUse": "需要 <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> 许可才能使用此功能。此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> 中使用。",
|
||||||
|
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> 需要使用此功能。此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> 中使用。",
|
||||||
"certResolver": "证书解决器",
|
"certResolver": "证书解决器",
|
||||||
"certResolverDescription": "选择用于此资源的证书解析器。",
|
"certResolverDescription": "选择用于此资源的证书解析器。",
|
||||||
"selectCertResolver": "选择证书解析",
|
"selectCertResolver": "选择证书解析",
|
||||||
@@ -2510,6 +2583,7 @@
|
|||||||
"firewallEnabled": "防火墙已启用",
|
"firewallEnabled": "防火墙已启用",
|
||||||
"autoUpdatesEnabled": "启用自动更新",
|
"autoUpdatesEnabled": "启用自动更新",
|
||||||
"tpmAvailable": "TPM 可用",
|
"tpmAvailable": "TPM 可用",
|
||||||
|
"windowsAntivirusEnabled": "抗病毒已启用",
|
||||||
"macosSipEnabled": "系统完整性保护 (SIP)",
|
"macosSipEnabled": "系统完整性保护 (SIP)",
|
||||||
"macosGatekeeperEnabled": "Gatekeeper",
|
"macosGatekeeperEnabled": "Gatekeeper",
|
||||||
"macosFirewallStealthMode": "防火墙隐形模式",
|
"macosFirewallStealthMode": "防火墙隐形模式",
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -13,13 +13,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
|
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
|
||||||
"dev:check": "npx tsc --noEmit && npm run format:check",
|
"dev:check": "npx tsc --noEmit && npm run format:check",
|
||||||
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:generate && npm run db:sqlite:push",
|
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push",
|
||||||
"db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts",
|
"db:generate": "drizzle-kit generate --config=./drizzle.config.ts",
|
||||||
"db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts",
|
"db:push": "npx tsx server/db/migrate.ts",
|
||||||
"db:pg:push": "npx tsx server/db/pg/migrate.ts",
|
"db:studio": "drizzle-kit studio --config=./drizzle.config.ts",
|
||||||
"db:sqlite:push": "npx tsx server/db/sqlite/migrate.ts",
|
|
||||||
"db:pg:studio": "drizzle-kit studio --config=./drizzle.pg.config.ts",
|
|
||||||
"db:sqlite:studio": "drizzle-kit studio --config=./drizzle.sqlite.config.ts",
|
|
||||||
"db:clear-migrations": "rm -rf server/migrations",
|
"db:clear-migrations": "rm -rf server/migrations",
|
||||||
"set:oss": "echo 'export const build = \"oss\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.oss.json tsconfig.json",
|
"set:oss": "echo 'export const build = \"oss\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.oss.json tsconfig.json",
|
||||||
"set:saas": "echo 'export const build = \"saas\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.saas.json tsconfig.json",
|
"set:saas": "echo 'export const build = \"saas\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.saas.json tsconfig.json",
|
||||||
|
|||||||
@@ -56,15 +56,15 @@ Ensure drizzle-kit is installed.
|
|||||||
You must have a connection string in your config file, as shown above.
|
You must have a connection string in your config file, as shown above.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run db:pg:generate
|
npm run db:generate
|
||||||
npm run db:pg:push
|
npm run db:push
|
||||||
```
|
```
|
||||||
|
|
||||||
### SQLite
|
### SQLite
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run db:sqlite:generate
|
npm run db:generate
|
||||||
npm run db:sqlite:push
|
npm run db:push
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build Time
|
## Build Time
|
||||||
|
|||||||
3
server/db/migrate.ts
Normal file
3
server/db/migrate.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { runMigrations } from "./";
|
||||||
|
|
||||||
|
await runMigrations();
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from "./driver";
|
export * from "./driver";
|
||||||
export * from "./schema/schema";
|
export * from "./schema/schema";
|
||||||
export * from "./schema/privateSchema";
|
export * from "./schema/privateSchema";
|
||||||
|
export * from "./migrate";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from "path";
|
|||||||
|
|
||||||
const migrationsFolder = path.join("server/migrations");
|
const migrationsFolder = path.join("server/migrations");
|
||||||
|
|
||||||
const runMigrations = async () => {
|
export const runMigrations = async () => {
|
||||||
console.log("Running migrations...");
|
console.log("Running migrations...");
|
||||||
try {
|
try {
|
||||||
await migrate(db as any, {
|
await migrate(db as any, {
|
||||||
@@ -17,5 +17,3 @@ const runMigrations = async () => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
runMigrations();
|
|
||||||
|
|||||||
@@ -142,7 +142,8 @@ export const resources = pgTable("resources", {
|
|||||||
}).default("forced"), // "forced" = always show, "automatic" = only when down
|
}).default("forced"), // "forced" = always show, "automatic" = only when down
|
||||||
maintenanceTitle: text("maintenanceTitle"),
|
maintenanceTitle: text("maintenanceTitle"),
|
||||||
maintenanceMessage: text("maintenanceMessage"),
|
maintenanceMessage: text("maintenanceMessage"),
|
||||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
|
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
||||||
|
postAuthPath: text("postAuthPath")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const targets = pgTable("targets", {
|
export const targets = pgTable("targets", {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from "./driver";
|
export * from "./driver";
|
||||||
export * from "./schema/schema";
|
export * from "./schema/schema";
|
||||||
export * from "./schema/privateSchema";
|
export * from "./schema/privateSchema";
|
||||||
|
export * from "./migrate";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import path from "path";
|
|||||||
|
|
||||||
const migrationsFolder = path.join("server/migrations");
|
const migrationsFolder = path.join("server/migrations");
|
||||||
|
|
||||||
const runMigrations = async () => {
|
export const runMigrations = async () => {
|
||||||
console.log("Running migrations...");
|
console.log("Running migrations...");
|
||||||
try {
|
try {
|
||||||
migrate(db as any, {
|
migrate(db as any, {
|
||||||
@@ -16,5 +16,3 @@ const runMigrations = async () => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
runMigrations();
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", {
|
|||||||
subscriptionItemId: integer("subscriptionItemId").primaryKey({
|
subscriptionItemId: integer("subscriptionItemId").primaryKey({
|
||||||
autoIncrement: true
|
autoIncrement: true
|
||||||
}),
|
}),
|
||||||
|
stripeSubscriptionItemId: text("stripeSubscriptionItemId"),
|
||||||
subscriptionId: text("subscriptionId")
|
subscriptionId: text("subscriptionId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => subscriptions.subscriptionId, {
|
.references(() => subscriptions.subscriptionId, {
|
||||||
|
|||||||
@@ -162,7 +162,8 @@ export const resources = sqliteTable("resources", {
|
|||||||
}).default("forced"), // "forced" = always show, "automatic" = only when down
|
}).default("forced"), // "forced" = always show, "automatic" = only when down
|
||||||
maintenanceTitle: text("maintenanceTitle"),
|
maintenanceTitle: text("maintenanceTitle"),
|
||||||
maintenanceMessage: text("maintenanceMessage"),
|
maintenanceMessage: text("maintenanceMessage"),
|
||||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
|
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
||||||
|
postAuthPath: text("postAuthPath")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const targets = sqliteTable("targets", {
|
export const targets = sqliteTable("targets", {
|
||||||
|
|||||||
@@ -56,22 +56,22 @@ export function getFeatureIdByMetricId(
|
|||||||
|
|
||||||
export type FeaturePriceSet = Partial<Record<FeatureId, string>>;
|
export type FeaturePriceSet = Partial<Record<FeatureId, string>>;
|
||||||
|
|
||||||
export const homeLabFeaturePriceSet: FeaturePriceSet = {
|
export const tier1FeaturePriceSet: FeaturePriceSet = {
|
||||||
[FeatureId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
|
[FeatureId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
|
||||||
};
|
};
|
||||||
|
|
||||||
export const homeLabFeaturePriceSetSandbox: FeaturePriceSet = {
|
export const tier1FeaturePriceSetSandbox: FeaturePriceSet = {
|
||||||
[FeatureId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
|
[FeatureId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getHomeLabFeaturePriceSet(): FeaturePriceSet {
|
export function getTier1FeaturePriceSet(): FeaturePriceSet {
|
||||||
if (
|
if (
|
||||||
process.env.ENVIRONMENT == "prod" &&
|
process.env.ENVIRONMENT == "prod" &&
|
||||||
process.env.SANDBOX_MODE !== "true"
|
process.env.SANDBOX_MODE !== "true"
|
||||||
) {
|
) {
|
||||||
return homeLabFeaturePriceSet;
|
return tier1FeaturePriceSet;
|
||||||
} else {
|
} else {
|
||||||
return homeLabFeaturePriceSetSandbox;
|
return tier1FeaturePriceSetSandbox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ export const tier2FeaturePriceSetSandbox: FeaturePriceSet = {
|
|||||||
[FeatureId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
|
[FeatureId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getStarterFeaturePriceSet(): FeaturePriceSet {
|
export function getTier2FeaturePriceSet(): FeaturePriceSet {
|
||||||
if (
|
if (
|
||||||
process.env.ENVIRONMENT == "prod" &&
|
process.env.ENVIRONMENT == "prod" &&
|
||||||
process.env.SANDBOX_MODE !== "true"
|
process.env.SANDBOX_MODE !== "true"
|
||||||
@@ -102,7 +102,7 @@ export const tier3FeaturePriceSetSandbox: FeaturePriceSet = {
|
|||||||
[FeatureId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
|
[FeatureId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getScaleFeaturePriceSet(): FeaturePriceSet {
|
export function getTier3FeaturePriceSet(): FeaturePriceSet {
|
||||||
if (
|
if (
|
||||||
process.env.ENVIRONMENT == "prod" &&
|
process.env.ENVIRONMENT == "prod" &&
|
||||||
process.env.SANDBOX_MODE !== "true"
|
process.env.SANDBOX_MODE !== "true"
|
||||||
@@ -116,9 +116,9 @@ export function getScaleFeaturePriceSet(): FeaturePriceSet {
|
|||||||
export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
|
export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
|
||||||
// Check all feature price sets
|
// Check all feature price sets
|
||||||
const allPriceSets = [
|
const allPriceSets = [
|
||||||
getHomeLabFeaturePriceSet(),
|
getTier1FeaturePriceSet(),
|
||||||
getStarterFeaturePriceSet(),
|
getTier2FeaturePriceSet(),
|
||||||
getScaleFeaturePriceSet()
|
getTier3FeaturePriceSet()
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const priceSet of allPriceSets) {
|
for (const priceSet of allPriceSets) {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ export const sandboxLimitSet: LimitSet = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const freeLimitSet: LimitSet = {
|
export const freeLimitSet: LimitSet = {
|
||||||
[FeatureId.USERS]: { value: 5, description: "Starter limit" },
|
[FeatureId.SITES]: { value: 5, description: "Basic limit" },
|
||||||
[FeatureId.SITES]: { value: 5, description: "Starter limit" },
|
[FeatureId.USERS]: { value: 5, description: "Basic limit" },
|
||||||
[FeatureId.DOMAINS]: { value: 5, description: "Starter limit" },
|
[FeatureId.DOMAINS]: { value: 5, description: "Basic limit" },
|
||||||
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Starter limit" },
|
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const tier1LimitSet: LimitSet = {
|
export const tier1LimitSet: LimitSet = {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import path from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
// This is a placeholder value replaced by the build process
|
// This is a placeholder value replaced by the build process
|
||||||
export const APP_VERSION = "1.15.0";
|
export const APP_VERSION = "1.15.4";
|
||||||
|
|
||||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||||
export const __DIRNAME = path.dirname(__FILENAME);
|
export const __DIRNAME = path.dirname(__FILENAME);
|
||||||
|
|||||||
169
server/lib/deleteOrg.ts
Normal file
169
server/lib/deleteOrg.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import {
|
||||||
|
clients,
|
||||||
|
clientSiteResourcesAssociationsCache,
|
||||||
|
clientSitesAssociationsCache,
|
||||||
|
db,
|
||||||
|
domains,
|
||||||
|
olms,
|
||||||
|
orgDomains,
|
||||||
|
orgs,
|
||||||
|
resources,
|
||||||
|
sites
|
||||||
|
} from "@server/db";
|
||||||
|
import { newts, newtSessions } from "@server/db";
|
||||||
|
import { eq, and, inArray, sql } from "drizzle-orm";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
|
import { deletePeer } from "@server/routers/gerbil/peers";
|
||||||
|
import { OlmErrorCodes } from "@server/routers/olm/error";
|
||||||
|
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||||
|
|
||||||
|
export type DeleteOrgByIdResult = {
|
||||||
|
deletedNewtIds: string[];
|
||||||
|
olmsToTerminate: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes one organization and its related data. Returns ids for termination
|
||||||
|
* messages; caller should call sendTerminationMessages with the result.
|
||||||
|
* Throws if org not found.
|
||||||
|
*/
|
||||||
|
export async function deleteOrgById(
|
||||||
|
orgId: string
|
||||||
|
): Promise<DeleteOrgByIdResult> {
|
||||||
|
const [org] = await db
|
||||||
|
.select()
|
||||||
|
.from(orgs)
|
||||||
|
.where(eq(orgs.orgId, orgId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
throw createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Organization with ID ${orgId} not found`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgSites = await db
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.orgId, orgId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
const orgClients = await db
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(eq(clients.orgId, orgId));
|
||||||
|
|
||||||
|
const deletedNewtIds: string[] = [];
|
||||||
|
const olmsToTerminate: string[] = [];
|
||||||
|
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
for (const site of orgSites) {
|
||||||
|
if (site.pubKey) {
|
||||||
|
if (site.type == "wireguard") {
|
||||||
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
|
} else if (site.type == "newt") {
|
||||||
|
const [deletedNewt] = await trx
|
||||||
|
.delete(newts)
|
||||||
|
.where(eq(newts.siteId, site.siteId))
|
||||||
|
.returning();
|
||||||
|
if (deletedNewt) {
|
||||||
|
deletedNewtIds.push(deletedNewt.newtId);
|
||||||
|
await trx
|
||||||
|
.delete(newtSessions)
|
||||||
|
.where(
|
||||||
|
eq(newtSessions.newtId, deletedNewt.newtId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger.info(`Deleting site ${site.siteId}`);
|
||||||
|
await trx.delete(sites).where(eq(sites.siteId, site.siteId));
|
||||||
|
}
|
||||||
|
for (const client of orgClients) {
|
||||||
|
const [olm] = await trx
|
||||||
|
.select()
|
||||||
|
.from(olms)
|
||||||
|
.where(eq(olms.clientId, client.clientId))
|
||||||
|
.limit(1);
|
||||||
|
if (olm) {
|
||||||
|
olmsToTerminate.push(olm.olmId);
|
||||||
|
}
|
||||||
|
logger.info(`Deleting client ${client.clientId}`);
|
||||||
|
await trx
|
||||||
|
.delete(clients)
|
||||||
|
.where(eq(clients.clientId, client.clientId));
|
||||||
|
await trx
|
||||||
|
.delete(clientSiteResourcesAssociationsCache)
|
||||||
|
.where(
|
||||||
|
eq(
|
||||||
|
clientSiteResourcesAssociationsCache.clientId,
|
||||||
|
client.clientId
|
||||||
|
)
|
||||||
|
);
|
||||||
|
await trx
|
||||||
|
.delete(clientSitesAssociationsCache)
|
||||||
|
.where(
|
||||||
|
eq(clientSitesAssociationsCache.clientId, client.clientId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const allOrgDomains = await trx
|
||||||
|
.select()
|
||||||
|
.from(orgDomains)
|
||||||
|
.innerJoin(domains, eq(domains.domainId, orgDomains.domainId))
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(orgDomains.orgId, orgId),
|
||||||
|
eq(domains.configManaged, false)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const domainIdsToDelete: string[] = [];
|
||||||
|
for (const orgDomain of allOrgDomains) {
|
||||||
|
const domainId = orgDomain.domains.domainId;
|
||||||
|
const orgCount = await trx
|
||||||
|
.select({ count: sql<number>`count(*)` })
|
||||||
|
.from(orgDomains)
|
||||||
|
.where(eq(orgDomains.domainId, domainId));
|
||||||
|
if (orgCount[0].count === 1) {
|
||||||
|
domainIdsToDelete.push(domainId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (domainIdsToDelete.length > 0) {
|
||||||
|
await trx
|
||||||
|
.delete(domains)
|
||||||
|
.where(inArray(domains.domainId, domainIdsToDelete));
|
||||||
|
}
|
||||||
|
await trx.delete(resources).where(eq(resources.orgId, orgId));
|
||||||
|
await trx.delete(orgs).where(eq(orgs.orgId, orgId));
|
||||||
|
});
|
||||||
|
|
||||||
|
return { deletedNewtIds, olmsToTerminate };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendTerminationMessages(result: DeleteOrgByIdResult): void {
|
||||||
|
for (const newtId of result.deletedNewtIds) {
|
||||||
|
sendToClient(newtId, { type: `newt/wg/terminate`, data: {} }).catch(
|
||||||
|
(error) => {
|
||||||
|
logger.error(
|
||||||
|
"Failed to send termination message to newt:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for (const olmId of result.olmsToTerminate) {
|
||||||
|
sendTerminateClient(
|
||||||
|
0,
|
||||||
|
OlmErrorCodes.TERMINATED_REKEYED,
|
||||||
|
olmId
|
||||||
|
).catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
"Failed to send termination message to olm:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
18
server/lib/normalizePostAuthPath.ts
Normal file
18
server/lib/normalizePostAuthPath.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Normalizes a post-authentication path for safe use when building redirect URLs.
|
||||||
|
* Returns a path that starts with / and does not allow open redirects (no //, no :).
|
||||||
|
*/
|
||||||
|
export function normalizePostAuthPath(path: string | null | undefined): string | null {
|
||||||
|
if (path == null || typeof path !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trimmed = path.trim();
|
||||||
|
if (trimmed === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Reject protocol-relative (//) or scheme (:) to avoid open redirect
|
||||||
|
if (trimmed.includes("//") || trimmed.includes(":")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
|
||||||
|
}
|
||||||
@@ -65,6 +65,11 @@ export class PrivateConfig {
|
|||||||
this.rawPrivateConfig.branding?.logo?.dark_path || undefined;
|
this.rawPrivateConfig.branding?.logo?.dark_path || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.rawPrivateConfig.app.identity_provider_mode) {
|
||||||
|
process.env.IDENTITY_PROVIDER_MODE =
|
||||||
|
this.rawPrivateConfig.app.identity_provider_mode;
|
||||||
|
}
|
||||||
|
|
||||||
process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding
|
process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding
|
||||||
?.logo?.auth_page?.width
|
?.logo?.auth_page?.width
|
||||||
? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString()
|
? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString()
|
||||||
@@ -129,10 +134,6 @@ export class PrivateConfig {
|
|||||||
process.env.USE_PANGOLIN_DNS =
|
process.env.USE_PANGOLIN_DNS =
|
||||||
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
|
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
|
||||||
}
|
}
|
||||||
if (this.rawPrivateConfig.flags.use_org_only_idp) {
|
|
||||||
process.env.USE_ORG_ONLY_IDP =
|
|
||||||
this.rawPrivateConfig.flags.use_org_only_idp.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRawPrivateConfig() {
|
public getRawPrivateConfig() {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export const privateConfigSchema = z.object({
|
|||||||
app: z
|
app: z
|
||||||
.object({
|
.object({
|
||||||
region: z.string().optional().default("default"),
|
region: z.string().optional().default("default"),
|
||||||
base_domain: z.string().optional()
|
base_domain: z.string().optional(),
|
||||||
|
identity_provider_mode: z.enum(["global", "org"]).optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.default({
|
.default({
|
||||||
@@ -95,7 +96,7 @@ export const privateConfigSchema = z.object({
|
|||||||
.object({
|
.object({
|
||||||
enable_redis: z.boolean().optional().default(false),
|
enable_redis: z.boolean().optional().default(false),
|
||||||
use_pangolin_dns: z.boolean().optional().default(false),
|
use_pangolin_dns: z.boolean().optional().default(false),
|
||||||
use_org_only_idp: z.boolean().optional().default(false),
|
use_org_only_idp: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
.prefault({}),
|
.prefault({}),
|
||||||
@@ -181,7 +182,29 @@ export const privateConfigSchema = z.object({
|
|||||||
// localFilePath: z.string().optional()
|
// localFilePath: z.string().optional()
|
||||||
})
|
})
|
||||||
.optional()
|
.optional()
|
||||||
});
|
})
|
||||||
|
.transform((data) => {
|
||||||
|
// this to maintain backwards compatibility with the old config file
|
||||||
|
const identityProviderMode = data.app?.identity_provider_mode;
|
||||||
|
const useOrgOnlyIdp = data.flags?.use_org_only_idp;
|
||||||
|
|
||||||
|
if (identityProviderMode !== undefined) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
if (useOrgOnlyIdp === true) {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
app: { ...data.app, identity_provider_mode: "org" as const }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (useOrgOnlyIdp === false) {
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
app: { ...data.app, identity_provider_mode: "global" as const }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
|
||||||
export function readPrivateConfigFile() {
|
export function readPrivateConfigFile() {
|
||||||
if (build == "oss") {
|
if (build == "oss") {
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import stripe from "#private/lib/stripe";
|
import stripe from "#private/lib/stripe";
|
||||||
import {
|
import {
|
||||||
getHomeLabFeaturePriceSet,
|
getTier1FeaturePriceSet,
|
||||||
getScaleFeaturePriceSet,
|
getTier3FeaturePriceSet,
|
||||||
getStarterFeaturePriceSet,
|
getTier2FeaturePriceSet,
|
||||||
FeatureId,
|
FeatureId,
|
||||||
type FeaturePriceSet
|
type FeaturePriceSet
|
||||||
} from "@server/lib/billing";
|
} from "@server/lib/billing";
|
||||||
@@ -113,11 +113,11 @@ export async function changeTier(
|
|||||||
// Get the target tier's price set
|
// Get the target tier's price set
|
||||||
let targetPriceSet: FeaturePriceSet;
|
let targetPriceSet: FeaturePriceSet;
|
||||||
if (tier === "tier1") {
|
if (tier === "tier1") {
|
||||||
targetPriceSet = getHomeLabFeaturePriceSet();
|
targetPriceSet = getTier1FeaturePriceSet();
|
||||||
} else if (tier === "tier2") {
|
} else if (tier === "tier2") {
|
||||||
targetPriceSet = getStarterFeaturePriceSet();
|
targetPriceSet = getTier2FeaturePriceSet();
|
||||||
} else if (tier === "tier3") {
|
} else if (tier === "tier3") {
|
||||||
targetPriceSet = getScaleFeaturePriceSet();
|
targetPriceSet = getTier3FeaturePriceSet();
|
||||||
} else {
|
} else {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid tier"));
|
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid tier"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,9 +23,9 @@ import config from "@server/lib/config";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import stripe from "#private/lib/stripe";
|
import stripe from "#private/lib/stripe";
|
||||||
import {
|
import {
|
||||||
getHomeLabFeaturePriceSet,
|
getTier1FeaturePriceSet,
|
||||||
getScaleFeaturePriceSet,
|
getTier3FeaturePriceSet,
|
||||||
getStarterFeaturePriceSet
|
getTier2FeaturePriceSet
|
||||||
} from "@server/lib/billing";
|
} from "@server/lib/billing";
|
||||||
import { getLineItems } from "@server/lib/billing/getLineItems";
|
import { getLineItems } from "@server/lib/billing/getLineItems";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
@@ -88,11 +88,11 @@ export async function createCheckoutSession(
|
|||||||
|
|
||||||
let lineItems: Stripe.Checkout.SessionCreateParams.LineItem[];
|
let lineItems: Stripe.Checkout.SessionCreateParams.LineItem[];
|
||||||
if (tier === "tier1") {
|
if (tier === "tier1") {
|
||||||
lineItems = await getLineItems(getHomeLabFeaturePriceSet(), orgId);
|
lineItems = await getLineItems(getTier1FeaturePriceSet(), orgId);
|
||||||
} else if (tier === "tier2") {
|
} else if (tier === "tier2") {
|
||||||
lineItems = await getLineItems(getStarterFeaturePriceSet(), orgId);
|
lineItems = await getLineItems(getTier2FeaturePriceSet(), orgId);
|
||||||
} else if (tier === "tier3") {
|
} else if (tier === "tier3") {
|
||||||
lineItems = await getLineItems(getScaleFeaturePriceSet(), orgId);
|
lineItems = await getLineItems(getTier3FeaturePriceSet(), orgId);
|
||||||
} else {
|
} else {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid plan"));
|
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid plan"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,113 @@ import logger from "@server/logger";
|
|||||||
import { db, idp, idpOrg, loginPage, loginPageBranding, loginPageBrandingOrg, loginPageOrg, orgs, resources, roles } from "@server/db";
|
import { db, idp, idpOrg, loginPage, loginPageBranding, loginPageBrandingOrg, loginPageOrg, orgs, resources, roles } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the maximum allowed retention days for a given tier
|
||||||
|
* Returns null for enterprise tier (unlimited)
|
||||||
|
*/
|
||||||
|
function getMaxRetentionDaysForTier(tier: Tier | null): number | null {
|
||||||
|
if (!tier) {
|
||||||
|
return 3; // Free tier
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tier) {
|
||||||
|
case "tier1":
|
||||||
|
return 7;
|
||||||
|
case "tier2":
|
||||||
|
return 30;
|
||||||
|
case "tier3":
|
||||||
|
return 90;
|
||||||
|
case "enterprise":
|
||||||
|
return null; // No limit
|
||||||
|
default:
|
||||||
|
return 3; // Default to free tier limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cap retention days to the maximum allowed for the given tier
|
||||||
|
*/
|
||||||
|
async function capRetentionDays(
|
||||||
|
orgId: string,
|
||||||
|
tier: Tier | null
|
||||||
|
): Promise<void> {
|
||||||
|
const maxRetentionDays = getMaxRetentionDaysForTier(tier);
|
||||||
|
|
||||||
|
// If there's no limit (enterprise tier), no capping needed
|
||||||
|
if (maxRetentionDays === null) {
|
||||||
|
logger.debug(
|
||||||
|
`No retention day limit for org ${orgId} on tier ${tier || "free"}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current org settings
|
||||||
|
const [org] = await db
|
||||||
|
.select()
|
||||||
|
.from(orgs)
|
||||||
|
.where(eq(orgs.orgId, orgId));
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
logger.warn(`Org ${orgId} not found when capping retention days`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updates: Partial<typeof orgs.$inferInsert> = {};
|
||||||
|
let needsUpdate = false;
|
||||||
|
|
||||||
|
// Cap request log retention if it exceeds the limit
|
||||||
|
if (
|
||||||
|
org.settingsLogRetentionDaysRequest !== null &&
|
||||||
|
org.settingsLogRetentionDaysRequest > maxRetentionDays
|
||||||
|
) {
|
||||||
|
updates.settingsLogRetentionDaysRequest = maxRetentionDays;
|
||||||
|
needsUpdate = true;
|
||||||
|
logger.info(
|
||||||
|
`Capping request log retention from ${org.settingsLogRetentionDaysRequest} to ${maxRetentionDays} days for org ${orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap access log retention if it exceeds the limit
|
||||||
|
if (
|
||||||
|
org.settingsLogRetentionDaysAccess !== null &&
|
||||||
|
org.settingsLogRetentionDaysAccess > maxRetentionDays
|
||||||
|
) {
|
||||||
|
updates.settingsLogRetentionDaysAccess = maxRetentionDays;
|
||||||
|
needsUpdate = true;
|
||||||
|
logger.info(
|
||||||
|
`Capping access log retention from ${org.settingsLogRetentionDaysAccess} to ${maxRetentionDays} days for org ${orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap action log retention if it exceeds the limit
|
||||||
|
if (
|
||||||
|
org.settingsLogRetentionDaysAction !== null &&
|
||||||
|
org.settingsLogRetentionDaysAction > maxRetentionDays
|
||||||
|
) {
|
||||||
|
updates.settingsLogRetentionDaysAction = maxRetentionDays;
|
||||||
|
needsUpdate = true;
|
||||||
|
logger.info(
|
||||||
|
`Capping action log retention from ${org.settingsLogRetentionDaysAction} to ${maxRetentionDays} days for org ${orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply updates if needed
|
||||||
|
if (needsUpdate) {
|
||||||
|
await db
|
||||||
|
.update(orgs)
|
||||||
|
.set(updates)
|
||||||
|
.where(eq(orgs.orgId, orgId));
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Successfully capped retention days for org ${orgId} to max ${maxRetentionDays} days`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
`No retention day capping needed for org ${orgId}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function handleTierChange(
|
export async function handleTierChange(
|
||||||
orgId: string,
|
orgId: string,
|
||||||
newTier: SubscriptionType | null,
|
newTier: SubscriptionType | null,
|
||||||
@@ -40,6 +147,9 @@ export async function handleTierChange(
|
|||||||
logger.info(
|
logger.info(
|
||||||
`Org ${orgId} is reverting to free tier, disabling all paid features`
|
`Org ${orgId} is reverting to free tier, disabling all paid features`
|
||||||
);
|
);
|
||||||
|
// Cap retention days to free tier limits
|
||||||
|
await capRetentionDays(orgId, null);
|
||||||
|
|
||||||
// Disable all features in the tier matrix
|
// Disable all features in the tier matrix
|
||||||
for (const [featureKey] of Object.entries(tierMatrix)) {
|
for (const [featureKey] of Object.entries(tierMatrix)) {
|
||||||
const feature = featureKey as TierFeature;
|
const feature = featureKey as TierFeature;
|
||||||
@@ -57,6 +167,9 @@ export async function handleTierChange(
|
|||||||
// Get the tier (cast as Tier since we've ruled out "license" and null)
|
// Get the tier (cast as Tier since we've ruled out "license" and null)
|
||||||
const tier = newTier as Tier;
|
const tier = newTier as Tier;
|
||||||
|
|
||||||
|
// Cap retention days to the new tier's limits
|
||||||
|
await capRetentionDays(orgId, tier);
|
||||||
|
|
||||||
// Check each feature in the tier matrix
|
// Check each feature in the tier matrix
|
||||||
for (const [featureKey, allowedTiers] of Object.entries(tierMatrix)) {
|
for (const [featureKey, allowedTiers] of Object.entries(tierMatrix)) {
|
||||||
const feature = featureKey as TierFeature;
|
const feature = featureKey as TierFeature;
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import {
|
|||||||
getLicensePriceSet,
|
getLicensePriceSet,
|
||||||
} from "@server/lib/billing/licenses";
|
} from "@server/lib/billing/licenses";
|
||||||
import {
|
import {
|
||||||
getHomeLabFeaturePriceSet,
|
getTier1FeaturePriceSet,
|
||||||
getStarterFeaturePriceSet,
|
getTier2FeaturePriceSet,
|
||||||
getScaleFeaturePriceSet,
|
getTier3FeaturePriceSet,
|
||||||
} from "@server/lib/billing/features";
|
} from "@server/lib/billing/features";
|
||||||
import Stripe from "stripe";
|
import Stripe from "stripe";
|
||||||
import { Tier } from "@server/types/Tiers";
|
import { Tier } from "@server/types/Tiers";
|
||||||
@@ -40,19 +40,19 @@ export function getSubType(fullSubscription: Stripe.Response<Stripe.Subscription
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if price ID matches home lab tier
|
// Check if price ID matches home lab tier
|
||||||
const homeLabPrices = Object.values(getHomeLabFeaturePriceSet());
|
const homeLabPrices = Object.values(getTier1FeaturePriceSet());
|
||||||
if (homeLabPrices.includes(priceId)) {
|
if (homeLabPrices.includes(priceId)) {
|
||||||
return "tier1";
|
return "tier1";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if price ID matches tier2 tier
|
// Check if price ID matches tier2 tier
|
||||||
const tier2Prices = Object.values(getStarterFeaturePriceSet());
|
const tier2Prices = Object.values(getTier2FeaturePriceSet());
|
||||||
if (tier2Prices.includes(priceId)) {
|
if (tier2Prices.includes(priceId)) {
|
||||||
return "tier2";
|
return "tier2";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if price ID matches tier3 tier
|
// Check if price ID matches tier3 tier
|
||||||
const tier3Prices = Object.values(getScaleFeaturePriceSet());
|
const tier3Prices = Object.values(getTier3FeaturePriceSet());
|
||||||
if (tier3Prices.includes(priceId)) {
|
if (tier3Prices.includes(priceId)) {
|
||||||
return "tier3";
|
return "tier3";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export async function generateNewEnterpriseLicense(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const tier = licenseData.tier === "big_license" ? LicenseId.BIG_LICENSE : LicenseId.SMALL_LICENSE;
|
const tier = licenseData.tier === "big_license" ? LicenseId.BIG_LICENSE : LicenseId.SMALL_LICENSE;
|
||||||
const tierPrice = getLicensePriceSet()[tier]
|
const tierPrice = getLicensePriceSet()[tier];
|
||||||
|
|
||||||
const session = await stripe!.checkout.sessions.create({
|
const session = await stripe!.checkout.sessions.create({
|
||||||
client_reference_id: keyId.toString(),
|
client_reference_id: keyId.toString(),
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ import { generateOidcRedirectUrl } from "@server/lib/idp/generateRedirectUrl";
|
|||||||
import { encrypt } from "@server/lib/crypto";
|
import { encrypt } from "@server/lib/crypto";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||||
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
import privateConfig from "#private/lib/config";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
||||||
|
|
||||||
@@ -92,6 +94,18 @@ export async function createOrgOidcIdp(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||||
|
"org"
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
clientSecret,
|
clientSecret,
|
||||||
@@ -109,12 +123,14 @@ export async function createOrgOidcIdp(
|
|||||||
|
|
||||||
let { autoProvision } = parsedBody.data;
|
let { autoProvision } = parsedBody.data;
|
||||||
|
|
||||||
const subscribed = await isSubscribed(
|
if (build == "saas") { // this is not paywalled with a ee license because this whole endpoint is restricted
|
||||||
orgId,
|
const subscribed = await isSubscribed(
|
||||||
tierMatrix.deviceApprovals
|
orgId,
|
||||||
);
|
tierMatrix.deviceApprovals
|
||||||
if (!subscribed) {
|
);
|
||||||
autoProvision = false;
|
if (!subscribed) {
|
||||||
|
autoProvision = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = config.getRawConfig().server.secret!;
|
const key = config.getRawConfig().server.secret!;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { idp, idpOidcConfig, idpOrg } from "@server/db";
|
import { idp, idpOidcConfig, idpOrg } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import privateConfig from "#private/lib/config";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -59,6 +60,18 @@ export async function deleteOrgIdp(
|
|||||||
|
|
||||||
const { idpId } = parsedParams.data;
|
const { idpId } = parsedParams.data;
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||||
|
"org"
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Check if IDP exists
|
// Check if IDP exists
|
||||||
const [existingIdp] = await db
|
const [existingIdp] = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -24,8 +24,10 @@ import { idp, idpOidcConfig } from "@server/db";
|
|||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { encrypt } from "@server/lib/crypto";
|
import { encrypt } from "@server/lib/crypto";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
import privateConfig from "#private/lib/config";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -97,6 +99,18 @@ export async function updateOrgOidcIdp(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||||
|
"org"
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { idpId, orgId } = parsedParams.data;
|
const { idpId, orgId } = parsedParams.data;
|
||||||
const {
|
const {
|
||||||
clientId,
|
clientId,
|
||||||
@@ -114,12 +128,15 @@ export async function updateOrgOidcIdp(
|
|||||||
|
|
||||||
let { autoProvision } = parsedBody.data;
|
let { autoProvision } = parsedBody.data;
|
||||||
|
|
||||||
const subscribed = await isSubscribed(
|
if (build == "saas") {
|
||||||
orgId,
|
// this is not paywalled with a ee license because this whole endpoint is restricted
|
||||||
tierMatrix.deviceApprovals
|
const subscribed = await isSubscribed(
|
||||||
);
|
orgId,
|
||||||
if (!subscribed) {
|
tierMatrix.deviceApprovals
|
||||||
autoProvision = false;
|
);
|
||||||
|
if (!subscribed) {
|
||||||
|
autoProvision = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if IDP exists and is of type OIDC
|
// Check if IDP exists and is of type OIDC
|
||||||
|
|||||||
228
server/routers/auth/deleteMyAccount.ts
Normal file
228
server/routers/auth/deleteMyAccount.ts
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { db, orgs, userOrgs, users } from "@server/db";
|
||||||
|
import { eq, and, inArray } 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 { verifySession } from "@server/auth/sessions/verifySession";
|
||||||
|
import {
|
||||||
|
invalidateSession,
|
||||||
|
createBlankSessionTokenCookie
|
||||||
|
} from "@server/auth/sessions/app";
|
||||||
|
import { verifyPassword } from "@server/auth/password";
|
||||||
|
import { verifyTotpCode } from "@server/auth/totp";
|
||||||
|
import { calculateUserClientsForOrgs } from "@server/lib/calculateUserClientsForOrgs";
|
||||||
|
import {
|
||||||
|
deleteOrgById,
|
||||||
|
sendTerminationMessages
|
||||||
|
} from "@server/lib/deleteOrg";
|
||||||
|
import { UserType } from "@server/types/UserTypes";
|
||||||
|
|
||||||
|
const deleteMyAccountBody = z.strictObject({
|
||||||
|
password: z.string().optional(),
|
||||||
|
code: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DeleteMyAccountPreviewResponse = {
|
||||||
|
preview: true;
|
||||||
|
orgs: { orgId: string; name: string }[];
|
||||||
|
twoFactorEnabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteMyAccountCodeRequestedResponse = {
|
||||||
|
codeRequested: true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DeleteMyAccountSuccessResponse = {
|
||||||
|
success: true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Self-service account deletion (saas only). Returns preview when no password;
|
||||||
|
* requires password and optional 2FA code to perform deletion. Uses shared
|
||||||
|
* deleteOrgById for each owned org (delete-my-account may delete multiple orgs).
|
||||||
|
*/
|
||||||
|
export async function deleteMyAccount(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const { user, session } = await verifySession(req);
|
||||||
|
if (!user || !session) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.UNAUTHORIZED, "Not authenticated")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.serverAdmin) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Server admins cannot delete their account this way"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.type !== UserType.Internal) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Account deletion with password is only supported for internal users"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = deleteMyAccountBody.safeParse(req.body ?? {});
|
||||||
|
if (!parsed.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsed.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { password, code } = parsed.data;
|
||||||
|
|
||||||
|
const userId = user.userId;
|
||||||
|
|
||||||
|
const ownedOrgsRows = await db
|
||||||
|
.select({
|
||||||
|
orgId: userOrgs.orgId
|
||||||
|
})
|
||||||
|
.from(userOrgs)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userOrgs.userId, userId),
|
||||||
|
eq(userOrgs.isOwner, true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const orgIds = ownedOrgsRows.map((r) => r.orgId);
|
||||||
|
|
||||||
|
if (!password) {
|
||||||
|
const orgsWithNames =
|
||||||
|
orgIds.length > 0
|
||||||
|
? await db
|
||||||
|
.select({
|
||||||
|
orgId: orgs.orgId,
|
||||||
|
name: orgs.name
|
||||||
|
})
|
||||||
|
.from(orgs)
|
||||||
|
.where(inArray(orgs.orgId, orgIds))
|
||||||
|
: [];
|
||||||
|
return response<DeleteMyAccountPreviewResponse>(res, {
|
||||||
|
data: {
|
||||||
|
preview: true,
|
||||||
|
orgs: orgsWithNames.map((o) => ({
|
||||||
|
orgId: o.orgId,
|
||||||
|
name: o.name ?? ""
|
||||||
|
})),
|
||||||
|
twoFactorEnabled: user.twoFactorEnabled ?? false
|
||||||
|
},
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Preview",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const validPassword = await verifyPassword(
|
||||||
|
password,
|
||||||
|
user.passwordHash!
|
||||||
|
);
|
||||||
|
if (!validPassword) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.UNAUTHORIZED, "Invalid password")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.twoFactorEnabled) {
|
||||||
|
if (!code) {
|
||||||
|
return response<DeleteMyAccountCodeRequestedResponse>(res, {
|
||||||
|
data: { codeRequested: true },
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Two-factor code required",
|
||||||
|
status: HttpCode.ACCEPTED
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const validOTP = await verifyTotpCode(
|
||||||
|
code,
|
||||||
|
user.twoFactorSecret!,
|
||||||
|
user.userId
|
||||||
|
);
|
||||||
|
if (!validOTP) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"The two-factor code you entered is incorrect"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allDeletedNewtIds: string[] = [];
|
||||||
|
const allOlmsToTerminate: string[] = [];
|
||||||
|
|
||||||
|
for (const row of ownedOrgsRows) {
|
||||||
|
try {
|
||||||
|
const result = await deleteOrgById(row.orgId);
|
||||||
|
allDeletedNewtIds.push(...result.deletedNewtIds);
|
||||||
|
allOlmsToTerminate.push(...result.olmsToTerminate);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to delete org ${row.orgId} during account deletion`,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to delete organization"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTerminationMessages({
|
||||||
|
deletedNewtIds: allDeletedNewtIds,
|
||||||
|
olmsToTerminate: allOlmsToTerminate
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
await trx.delete(users).where(eq(users.userId, userId));
|
||||||
|
await calculateUserClientsForOrgs(userId, trx);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await invalidateSession(session.sessionId);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
"Failed to invalidate session after account deletion",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSecure = req.protocol === "https";
|
||||||
|
res.setHeader("Set-Cookie", createBlankSessionTokenCookie(isSecure));
|
||||||
|
|
||||||
|
return response<DeleteMyAccountSuccessResponse>(res, {
|
||||||
|
data: { success: true },
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Account deleted successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"An error occurred"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,4 +17,5 @@ export * from "./securityKey";
|
|||||||
export * from "./startDeviceWebAuth";
|
export * from "./startDeviceWebAuth";
|
||||||
export * from "./verifyDeviceWebAuth";
|
export * from "./verifyDeviceWebAuth";
|
||||||
export * from "./pollDeviceWebAuth";
|
export * from "./pollDeviceWebAuth";
|
||||||
export * from "./lookupUser";
|
export * from "./lookupUser";
|
||||||
|
export * from "./deleteMyAccount";
|
||||||
@@ -39,7 +39,7 @@ import {
|
|||||||
import { logRequestAudit } from "./logRequestAudit";
|
import { logRequestAudit } from "./logRequestAudit";
|
||||||
import cache from "@server/lib/cache";
|
import cache from "@server/lib/cache";
|
||||||
import { APP_VERSION } from "@server/lib/consts";
|
import { APP_VERSION } from "@server/lib/consts";
|
||||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
import { isSubscribed } from "#dynamic/lib/isSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
|
||||||
const verifyResourceSessionSchema = z.object({
|
const verifyResourceSessionSchema = z.object({
|
||||||
@@ -797,7 +797,7 @@ async function notAllowed(
|
|||||||
) {
|
) {
|
||||||
let loginPage: LoginPage | null = null;
|
let loginPage: LoginPage | null = null;
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
const subscribed = await isSubscribed(
|
const subscribed = await isSubscribed( // this is fine because the org login page is only a saas feature
|
||||||
orgId,
|
orgId,
|
||||||
tierMatrix.loginPageDomain
|
tierMatrix.loginPageDomain
|
||||||
);
|
);
|
||||||
@@ -854,7 +854,7 @@ async function headerAuthChallenged(
|
|||||||
) {
|
) {
|
||||||
let loginPage: LoginPage | null = null;
|
let loginPage: LoginPage | null = null;
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
const subscribed = await isSubscribed(orgId, tierMatrix.loginPageDomain);
|
const subscribed = await isSubscribed(orgId, tierMatrix.loginPageDomain); // this is fine because the org login page is only a saas feature
|
||||||
if (subscribed) {
|
if (subscribed) {
|
||||||
loginPage = await getOrgLoginPage(orgId);
|
loginPage = await getOrgLoginPage(orgId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import { generateId } from "@server/auth/sessions/app";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
import { getUniqueClientName } from "@server/db/names";
|
import { getUniqueClientName } from "@server/db/names";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
const createClientParamsSchema = z.strictObject({
|
const createClientParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -195,6 +196,12 @@ export async function createClient(
|
|||||||
const randomExitNode =
|
const randomExitNode =
|
||||||
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
|
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
|
||||||
|
|
||||||
|
if (!randomExitNode) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.NOT_FOUND, `No exit nodes available. ${build == "saas" ? "Please contact support." : "You need to install gerbil to use the clients."}`)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const [adminRole] = await trx
|
const [adminRole] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(roles)
|
.from(roles)
|
||||||
|
|||||||
@@ -1164,6 +1164,7 @@ authRouter.post(
|
|||||||
auth.login
|
auth.login
|
||||||
);
|
);
|
||||||
authRouter.post("/logout", auth.logout);
|
authRouter.post("/logout", auth.logout);
|
||||||
|
authRouter.post("/delete-my-account", auth.deleteMyAccount);
|
||||||
authRouter.post(
|
authRouter.post(
|
||||||
"/lookup-user",
|
"/lookup-user",
|
||||||
rateLimit({
|
rateLimit({
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export async function validateOidcCallback(
|
|||||||
allOrgs[0].orgId,
|
allOrgs[0].orgId,
|
||||||
tierMatrix.autoProvisioning
|
tierMatrix.autoProvisioning
|
||||||
);
|
);
|
||||||
if (subscribed) {
|
if (!subscribed) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
|
|||||||
@@ -1,28 +1,12 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
|
||||||
clients,
|
|
||||||
clientSiteResourcesAssociationsCache,
|
|
||||||
clientSitesAssociationsCache,
|
|
||||||
db,
|
|
||||||
domains,
|
|
||||||
olms,
|
|
||||||
orgDomains,
|
|
||||||
resources
|
|
||||||
} from "@server/db";
|
|
||||||
import { newts, newtSessions, orgs, sites, userActions } from "@server/db";
|
|
||||||
import { eq, and, inArray, sql } from "drizzle-orm";
|
|
||||||
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 { ActionsEnum, checkUserActionPermission } from "@server/auth/actions";
|
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { sendToClient } from "#dynamic/routers/ws";
|
|
||||||
import { deletePeer } from "../gerbil/peers";
|
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { OlmErrorCodes } from "../olm/error";
|
import { deleteOrgById, sendTerminationMessages } from "@server/lib/deleteOrg";
|
||||||
import { sendTerminateClient } from "../client/terminate";
|
|
||||||
|
|
||||||
const deleteOrgSchema = z.strictObject({
|
const deleteOrgSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -56,170 +40,9 @@ export async function deleteOrg(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
const result = await deleteOrgById(orgId);
|
||||||
const [org] = await db
|
sendTerminationMessages(result);
|
||||||
.select()
|
|
||||||
.from(orgs)
|
|
||||||
.where(eq(orgs.orgId, orgId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!org) {
|
|
||||||
return next(
|
|
||||||
createHttpError(
|
|
||||||
HttpCode.NOT_FOUND,
|
|
||||||
`Organization with ID ${orgId} not found`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// we need to handle deleting each site
|
|
||||||
const orgSites = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(eq(sites.orgId, orgId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
const orgClients = await db
|
|
||||||
.select()
|
|
||||||
.from(clients)
|
|
||||||
.where(eq(clients.orgId, orgId));
|
|
||||||
|
|
||||||
const deletedNewtIds: string[] = [];
|
|
||||||
const olmsToTerminate: string[] = [];
|
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
|
||||||
for (const site of orgSites) {
|
|
||||||
if (site.pubKey) {
|
|
||||||
if (site.type == "wireguard") {
|
|
||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
|
||||||
} else if (site.type == "newt") {
|
|
||||||
// get the newt on the site by querying the newt table for siteId
|
|
||||||
const [deletedNewt] = await trx
|
|
||||||
.delete(newts)
|
|
||||||
.where(eq(newts.siteId, site.siteId))
|
|
||||||
.returning();
|
|
||||||
if (deletedNewt) {
|
|
||||||
deletedNewtIds.push(deletedNewt.newtId);
|
|
||||||
|
|
||||||
// delete all of the sessions for the newt
|
|
||||||
await trx
|
|
||||||
.delete(newtSessions)
|
|
||||||
.where(
|
|
||||||
eq(newtSessions.newtId, deletedNewt.newtId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Deleting site ${site.siteId}`);
|
|
||||||
await trx.delete(sites).where(eq(sites.siteId, site.siteId));
|
|
||||||
}
|
|
||||||
for (const client of orgClients) {
|
|
||||||
const [olm] = await trx
|
|
||||||
.select()
|
|
||||||
.from(olms)
|
|
||||||
.where(eq(olms.clientId, client.clientId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (olm) {
|
|
||||||
olmsToTerminate.push(olm.olmId);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info(`Deleting client ${client.clientId}`);
|
|
||||||
await trx
|
|
||||||
.delete(clients)
|
|
||||||
.where(eq(clients.clientId, client.clientId));
|
|
||||||
|
|
||||||
// also delete the associations
|
|
||||||
await trx
|
|
||||||
.delete(clientSiteResourcesAssociationsCache)
|
|
||||||
.where(
|
|
||||||
eq(
|
|
||||||
clientSiteResourcesAssociationsCache.clientId,
|
|
||||||
client.clientId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await trx
|
|
||||||
.delete(clientSitesAssociationsCache)
|
|
||||||
.where(
|
|
||||||
eq(
|
|
||||||
clientSitesAssociationsCache.clientId,
|
|
||||||
client.clientId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const allOrgDomains = await trx
|
|
||||||
.select()
|
|
||||||
.from(orgDomains)
|
|
||||||
.innerJoin(domains, eq(domains.domainId, orgDomains.domainId))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(orgDomains.orgId, orgId),
|
|
||||||
eq(domains.configManaged, false)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// For each domain, check if it belongs to multiple organizations
|
|
||||||
const domainIdsToDelete: string[] = [];
|
|
||||||
for (const orgDomain of allOrgDomains) {
|
|
||||||
const domainId = orgDomain.domains.domainId;
|
|
||||||
|
|
||||||
// Count how many organizations this domain belongs to
|
|
||||||
const orgCount = await trx
|
|
||||||
.select({ count: sql<number>`count(*)` })
|
|
||||||
.from(orgDomains)
|
|
||||||
.where(eq(orgDomains.domainId, domainId));
|
|
||||||
|
|
||||||
// Only delete the domain if it belongs to exactly 1 organization (the one being deleted)
|
|
||||||
if (orgCount[0].count === 1) {
|
|
||||||
domainIdsToDelete.push(domainId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete domains that belong exclusively to this organization
|
|
||||||
if (domainIdsToDelete.length > 0) {
|
|
||||||
await trx
|
|
||||||
.delete(domains)
|
|
||||||
.where(inArray(domains.domainId, domainIdsToDelete));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete resources
|
|
||||||
await trx.delete(resources).where(eq(resources.orgId, orgId));
|
|
||||||
|
|
||||||
await trx.delete(orgs).where(eq(orgs.orgId, orgId));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Send termination messages outside of transaction to prevent blocking
|
|
||||||
for (const newtId of deletedNewtIds) {
|
|
||||||
const payload = {
|
|
||||||
type: `newt/wg/terminate`,
|
|
||||||
data: {}
|
|
||||||
};
|
|
||||||
// Don't await this to prevent blocking the response
|
|
||||||
sendToClient(newtId, payload).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
"Failed to send termination message to newt:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const olmId of olmsToTerminate) {
|
|
||||||
sendTerminateClient(
|
|
||||||
0, // clientId not needed since we're passing olmId
|
|
||||||
OlmErrorCodes.TERMINATED_REKEYED,
|
|
||||||
olmId
|
|
||||||
).catch((error) => {
|
|
||||||
logger.error(
|
|
||||||
"Failed to send termination message to olm:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: null,
|
data: null,
|
||||||
success: true,
|
success: true,
|
||||||
@@ -228,6 +51,9 @@ export async function deleteOrg(
|
|||||||
status: HttpCode.OK
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (createHttpError.isHttpError(error)) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToke
|
|||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import stoi from "@server/lib/stoi";
|
import stoi from "@server/lib/stoi";
|
||||||
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
|
import { logAccessAudit } from "#dynamic/lib/logAccessAudit";
|
||||||
|
import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath";
|
||||||
|
|
||||||
const authWithAccessTokenBodySchema = z.strictObject({
|
const authWithAccessTokenBodySchema = z.strictObject({
|
||||||
accessToken: z.string(),
|
accessToken: z.string(),
|
||||||
@@ -164,10 +165,16 @@ export async function authWithAccessToken(
|
|||||||
requestIp: req.ip
|
requestIp: req.ip
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let redirectUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
||||||
|
const postAuthPath = normalizePostAuthPath(resource.postAuthPath);
|
||||||
|
if (postAuthPath) {
|
||||||
|
redirectUrl = redirectUrl + postAuthPath;
|
||||||
|
}
|
||||||
|
|
||||||
return response<AuthWithAccessTokenResponse>(res, {
|
return response<AuthWithAccessTokenResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
session: token,
|
session: token,
|
||||||
redirectUrl: `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`
|
redirectUrl
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ const createHttpResourceSchema = z
|
|||||||
http: z.boolean(),
|
http: z.boolean(),
|
||||||
protocol: z.enum(["tcp", "udp"]),
|
protocol: z.enum(["tcp", "udp"]),
|
||||||
domainId: z.string(),
|
domainId: z.string(),
|
||||||
stickySession: z.boolean().optional()
|
stickySession: z.boolean().optional(),
|
||||||
|
postAuthPath: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
@@ -188,7 +189,7 @@ async function createHttpResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, domainId } = parsedBody.data;
|
const { name, domainId, postAuthPath } = parsedBody.data;
|
||||||
const subdomain = parsedBody.data.subdomain;
|
const subdomain = parsedBody.data.subdomain;
|
||||||
const stickySession = parsedBody.data.stickySession;
|
const stickySession = parsedBody.data.stickySession;
|
||||||
|
|
||||||
@@ -255,7 +256,8 @@ async function createHttpResource(
|
|||||||
http: true,
|
http: true,
|
||||||
protocol: "tcp",
|
protocol: "tcp",
|
||||||
ssl: true,
|
ssl: true,
|
||||||
stickySession: stickySession
|
stickySession: stickySession,
|
||||||
|
postAuthPath: postAuthPath
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ export type GetResourceAuthInfoResponse = {
|
|||||||
whitelist: boolean;
|
whitelist: boolean;
|
||||||
skipToIdpId: number | null;
|
skipToIdpId: number | null;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
postAuthPath: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function getResourceAuthInfo(
|
export async function getResourceAuthInfo(
|
||||||
@@ -147,7 +148,8 @@ export async function getResourceAuthInfo(
|
|||||||
url,
|
url,
|
||||||
whitelist: resource.emailWhitelistEnabled,
|
whitelist: resource.emailWhitelistEnabled,
|
||||||
skipToIdpId: resource.skipToIdpId,
|
skipToIdpId: resource.skipToIdpId,
|
||||||
orgId: resource.orgId
|
orgId: resource.orgId,
|
||||||
|
postAuthPath: resource.postAuthPath ?? null
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
|
|||||||
@@ -55,7 +55,8 @@ const updateHttpResourceBodySchema = z
|
|||||||
maintenanceModeType: z.enum(["forced", "automatic"]).optional(),
|
maintenanceModeType: z.enum(["forced", "automatic"]).optional(),
|
||||||
maintenanceTitle: z.string().max(255).nullable().optional(),
|
maintenanceTitle: z.string().max(255).nullable().optional(),
|
||||||
maintenanceMessage: z.string().max(2000).nullable().optional(),
|
maintenanceMessage: z.string().max(2000).nullable().optional(),
|
||||||
maintenanceEstimatedTime: z.string().max(100).nullable().optional()
|
maintenanceEstimatedTime: z.string().max(100).nullable().optional(),
|
||||||
|
postAuthPath: z.string().nullable().optional()
|
||||||
})
|
})
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
error: "At least one field must be provided for update"
|
error: "At least one field must be provided for update"
|
||||||
|
|||||||
@@ -290,7 +290,19 @@ export async function createSite(
|
|||||||
let newSite: Site | undefined;
|
let newSite: Site | undefined;
|
||||||
let numSites: Site[] | undefined;
|
let numSites: Site[] | undefined;
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
if (type == "wireguard" || type == "newt") {
|
if (type == "newt") {
|
||||||
|
[newSite] = await trx
|
||||||
|
.insert(sites)
|
||||||
|
.values({
|
||||||
|
orgId,
|
||||||
|
name,
|
||||||
|
niceId,
|
||||||
|
address: updatedAddress || null,
|
||||||
|
type,
|
||||||
|
dockerSocketEnabled: true
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
} else if (type == "wireguard") {
|
||||||
// we are creating a site with an exit node (tunneled)
|
// we are creating a site with an exit node (tunneled)
|
||||||
if (!subnet) {
|
if (!subnet) {
|
||||||
return next(
|
return next(
|
||||||
@@ -342,11 +354,9 @@ export async function createSite(
|
|||||||
exitNodeId,
|
exitNodeId,
|
||||||
name,
|
name,
|
||||||
niceId,
|
niceId,
|
||||||
address: updatedAddress || null,
|
|
||||||
subnet,
|
subnet,
|
||||||
type,
|
type,
|
||||||
dockerSocketEnabled: type == "newt",
|
pubKey: pubKey || null
|
||||||
...(pubKey && type == "wireguard" && { pubKey })
|
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
} else if (type == "local") {
|
} else if (type == "local") {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export async function createOrgUser(
|
|||||||
orgId,
|
orgId,
|
||||||
tierMatrix.orgOidc
|
tierMatrix.orgOidc
|
||||||
);
|
);
|
||||||
if (subscribed) {
|
if (!subscribed) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
|
|||||||
1
server/setup/.gitignore
vendored
Normal file
1
server/setup/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
migrations.ts
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
#! /usr/bin/env node
|
|
||||||
import { migrate } from "drizzle-orm/node-postgres/migrator";
|
|
||||||
import { db } from "../db/pg";
|
|
||||||
import semver from "semver";
|
|
||||||
import { versionMigrations } from "../db/pg";
|
|
||||||
import { __DIRNAME, APP_VERSION } from "@server/lib/consts";
|
|
||||||
import path from "path";
|
|
||||||
import m1 from "./scriptsPg/1.6.0";
|
|
||||||
import m2 from "./scriptsPg/1.7.0";
|
|
||||||
import m3 from "./scriptsPg/1.8.0";
|
|
||||||
import m4 from "./scriptsPg/1.9.0";
|
|
||||||
import m5 from "./scriptsPg/1.10.0";
|
|
||||||
import m6 from "./scriptsPg/1.10.2";
|
|
||||||
import m7 from "./scriptsPg/1.11.0";
|
|
||||||
import m8 from "./scriptsPg/1.11.1";
|
|
||||||
import m9 from "./scriptsPg/1.12.0";
|
|
||||||
import m10 from "./scriptsPg/1.13.0";
|
|
||||||
import m11 from "./scriptsPg/1.14.0";
|
|
||||||
import m12 from "./scriptsPg/1.15.0";
|
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
|
||||||
|
|
||||||
// Define the migration list with versions and their corresponding functions
|
|
||||||
const migrations = [
|
|
||||||
{ version: "1.6.0", run: m1 },
|
|
||||||
{ version: "1.7.0", run: m2 },
|
|
||||||
{ version: "1.8.0", run: m3 },
|
|
||||||
{ version: "1.9.0", run: m4 },
|
|
||||||
{ version: "1.10.0", run: m5 },
|
|
||||||
{ version: "1.10.2", run: m6 },
|
|
||||||
{ version: "1.11.0", run: m7 },
|
|
||||||
{ version: "1.11.1", run: m8 },
|
|
||||||
{ version: "1.12.0", run: m9 },
|
|
||||||
{ version: "1.13.0", run: m10 },
|
|
||||||
{ version: "1.14.0", run: m11 },
|
|
||||||
{ version: "1.15.0", run: m12 }
|
|
||||||
// Add new migrations here as they are created
|
|
||||||
] as {
|
|
||||||
version: string;
|
|
||||||
run: () => Promise<void>;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
async function run() {
|
|
||||||
// run the migrations
|
|
||||||
await runMigrations();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function runMigrations() {
|
|
||||||
if (process.env.DISABLE_MIGRATIONS) {
|
|
||||||
console.log("Migrations are disabled. Skipping...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const appVersion = APP_VERSION;
|
|
||||||
|
|
||||||
// determine if the migrations table exists
|
|
||||||
const exists = await db
|
|
||||||
.select()
|
|
||||||
.from(versionMigrations)
|
|
||||||
.limit(1)
|
|
||||||
.execute()
|
|
||||||
.then((res) => res.length > 0)
|
|
||||||
.catch(() => false);
|
|
||||||
|
|
||||||
if (exists) {
|
|
||||||
console.log("Migrations table exists, running scripts...");
|
|
||||||
await executeScripts();
|
|
||||||
} else {
|
|
||||||
console.log("Migrations table does not exist, creating it...");
|
|
||||||
console.log("Running migrations...");
|
|
||||||
try {
|
|
||||||
await migrate(db, {
|
|
||||||
migrationsFolder: path.join(__DIRNAME, "init") // put here during the docker build
|
|
||||||
});
|
|
||||||
console.log("Migrations completed successfully.");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error running migrations:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.insert(versionMigrations)
|
|
||||||
.values({
|
|
||||||
version: appVersion,
|
|
||||||
executedAt: Date.now()
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error running migrations:", e);
|
|
||||||
await new Promise((resolve) =>
|
|
||||||
setTimeout(resolve, 1000 * 60 * 60 * 24 * 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function executeScripts() {
|
|
||||||
try {
|
|
||||||
// Get the last executed version from the database
|
|
||||||
const lastExecuted = await db.select().from(versionMigrations);
|
|
||||||
|
|
||||||
// Filter and sort migrations
|
|
||||||
const pendingMigrations = lastExecuted
|
|
||||||
.map((m) => m)
|
|
||||||
.sort((a, b) => semver.compare(b.version, a.version));
|
|
||||||
const startVersion = pendingMigrations[0]?.version ?? "0.0.0";
|
|
||||||
console.log(`Starting migrations from version ${startVersion}`);
|
|
||||||
|
|
||||||
const migrationsToRun = migrations.filter((migration) =>
|
|
||||||
semver.gt(migration.version, startVersion)
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Migrations to run:",
|
|
||||||
migrationsToRun.map((m) => m.version).join(", ")
|
|
||||||
);
|
|
||||||
|
|
||||||
// Run migrations in order
|
|
||||||
for (const migration of migrationsToRun) {
|
|
||||||
console.log(`Running migration ${migration.version}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await migration.run();
|
|
||||||
|
|
||||||
// Update version in database
|
|
||||||
await db
|
|
||||||
.insert(versionMigrations)
|
|
||||||
.values({
|
|
||||||
version: migration.version,
|
|
||||||
executedAt: Date.now()
|
|
||||||
})
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Successfully completed migration ${migration.version}`
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
if (
|
|
||||||
e instanceof Error &&
|
|
||||||
typeof (e as any).code === "string" &&
|
|
||||||
(e as any).code === "23505"
|
|
||||||
) {
|
|
||||||
console.error("Migration has already run! Skipping...");
|
|
||||||
continue; // or return, depending on context
|
|
||||||
}
|
|
||||||
|
|
||||||
console.error(
|
|
||||||
`Failed to run migration ${migration.version}:`,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("All migrations completed successfully");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Migration process failed:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,6 +17,8 @@ import m9 from "./scriptsPg/1.12.0";
|
|||||||
import m10 from "./scriptsPg/1.13.0";
|
import m10 from "./scriptsPg/1.13.0";
|
||||||
import m11 from "./scriptsPg/1.14.0";
|
import m11 from "./scriptsPg/1.14.0";
|
||||||
import m12 from "./scriptsPg/1.15.0";
|
import m12 from "./scriptsPg/1.15.0";
|
||||||
|
import m13 from "./scriptsPg/1.15.3";
|
||||||
|
import m14 from "./scriptsPg/1.15.4";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
@@ -34,7 +36,9 @@ const migrations = [
|
|||||||
{ version: "1.12.0", run: m9 },
|
{ version: "1.12.0", run: m9 },
|
||||||
{ version: "1.13.0", run: m10 },
|
{ version: "1.13.0", run: m10 },
|
||||||
{ version: "1.14.0", run: m11 },
|
{ version: "1.14.0", run: m11 },
|
||||||
{ version: "1.15.0", run: m12 }
|
{ version: "1.15.0", run: m12 },
|
||||||
|
{ version: "1.15.3", run: m13 },
|
||||||
|
{ version: "1.15.4", run: m14 }
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as {
|
] as {
|
||||||
version: string;
|
version: string;
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import m30 from "./scriptsSqlite/1.12.0";
|
|||||||
import m31 from "./scriptsSqlite/1.13.0";
|
import m31 from "./scriptsSqlite/1.13.0";
|
||||||
import m32 from "./scriptsSqlite/1.14.0";
|
import m32 from "./scriptsSqlite/1.14.0";
|
||||||
import m33 from "./scriptsSqlite/1.15.0";
|
import m33 from "./scriptsSqlite/1.15.0";
|
||||||
|
import m34 from "./scriptsSqlite/1.15.3";
|
||||||
|
import m35 from "./scriptsSqlite/1.15.4";
|
||||||
|
|
||||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||||
@@ -68,7 +70,9 @@ const migrations = [
|
|||||||
{ version: "1.12.0", run: m30 },
|
{ version: "1.12.0", run: m30 },
|
||||||
{ version: "1.13.0", run: m31 },
|
{ version: "1.13.0", run: m31 },
|
||||||
{ version: "1.14.0", run: m32 },
|
{ version: "1.14.0", run: m32 },
|
||||||
{ version: "1.15.0", run: m33 }
|
{ version: "1.15.0", run: m33 },
|
||||||
|
{ version: "1.15.3", run: m34 },
|
||||||
|
{ version: "1.15.4", run: m35 }
|
||||||
// Add new migrations here as they are created
|
// Add new migrations here as they are created
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
|
|||||||
39
server/setup/scriptsPg/1.15.3.ts
Normal file
39
server/setup/scriptsPg/1.15.3.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { db } from "@server/db/pg/driver";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { __DIRNAME } from "@server/lib/consts";
|
||||||
|
|
||||||
|
const version = "1.15.3";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.execute(sql`BEGIN`);
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "limits" ADD COLUMN "override" boolean DEFAULT false;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "subscriptionItems" ADD COLUMN "stripeSubscriptionItemId" varchar(255);`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "subscriptionItems" ADD COLUMN "featureId" varchar(255);`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "subscriptions" ADD COLUMN "version" integer;`
|
||||||
|
);
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "subscriptions" ADD COLUMN "type" varchar(50);`
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.execute(sql`COMMIT`);
|
||||||
|
console.log("Migrated database");
|
||||||
|
} catch (e) {
|
||||||
|
await db.execute(sql`ROLLBACK`);
|
||||||
|
console.log("Unable to migrate database");
|
||||||
|
console.log(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${version} migration complete`);
|
||||||
|
}
|
||||||
27
server/setup/scriptsPg/1.15.4.ts
Normal file
27
server/setup/scriptsPg/1.15.4.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { db } from "@server/db/pg/driver";
|
||||||
|
import { sql } from "drizzle-orm";
|
||||||
|
import { __DIRNAME } from "@server/lib/consts";
|
||||||
|
|
||||||
|
const version = "1.15.4";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.execute(sql`BEGIN`);
|
||||||
|
|
||||||
|
await db.execute(
|
||||||
|
sql`ALTER TABLE "resources" ADD COLUMN "postAuthPath" text;`
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.execute(sql`COMMIT`);
|
||||||
|
console.log("Migrated database");
|
||||||
|
} catch (e) {
|
||||||
|
await db.execute(sql`ROLLBACK`);
|
||||||
|
console.log("Unable to migrate database");
|
||||||
|
console.log(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${version} migration complete`);
|
||||||
|
}
|
||||||
29
server/setup/scriptsSqlite/1.15.3.ts
Normal file
29
server/setup/scriptsSqlite/1.15.3.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { __DIRNAME, APP_PATH } from "@server/lib/consts";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const version = "1.15.3";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
|
const db = new Database(location);
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(() => {
|
||||||
|
db.prepare(`ALTER TABLE 'limits' ADD 'override' integer DEFAULT false;`).run();
|
||||||
|
db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'featureId' text;`).run();
|
||||||
|
db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'stripeSubscriptionItemId' text;`).run();
|
||||||
|
db.prepare(`ALTER TABLE 'subscriptions' ADD 'version' integer;`).run();
|
||||||
|
db.prepare(`ALTER TABLE 'subscriptions' ADD 'type' text;`).run();
|
||||||
|
})();
|
||||||
|
|
||||||
|
console.log(`Migrated database`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to migrate db:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${version} migration complete`);
|
||||||
|
}
|
||||||
27
server/setup/scriptsSqlite/1.15.4.ts
Normal file
27
server/setup/scriptsSqlite/1.15.4.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import { __DIRNAME, APP_PATH } from "@server/lib/consts";
|
||||||
|
import Database from "better-sqlite3";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const version = "1.15.4";
|
||||||
|
|
||||||
|
export default async function migration() {
|
||||||
|
console.log(`Running setup script ${version}...`);
|
||||||
|
|
||||||
|
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||||
|
const db = new Database(location);
|
||||||
|
|
||||||
|
try {
|
||||||
|
db.transaction(() => {
|
||||||
|
db.prepare(
|
||||||
|
`ALTER TABLE 'resources' ADD 'postAuthPath' text;`
|
||||||
|
).run();
|
||||||
|
})();
|
||||||
|
|
||||||
|
console.log(`Migrated database`);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to migrate db:", e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${version} migration complete`);
|
||||||
|
}
|
||||||
@@ -61,7 +61,7 @@ import {
|
|||||||
import { FeatureId } from "@server/lib/billing/features";
|
import { FeatureId } from "@server/lib/billing/features";
|
||||||
|
|
||||||
// Plan tier definitions matching the mockup
|
// Plan tier definitions matching the mockup
|
||||||
type PlanId = "starter" | "home" | "team" | "business" | "enterprise";
|
type PlanId = "basic" | "home" | "team" | "business" | "enterprise";
|
||||||
|
|
||||||
type PlanOption = {
|
type PlanOption = {
|
||||||
id: PlanId;
|
id: PlanId;
|
||||||
@@ -73,8 +73,8 @@ type PlanOption = {
|
|||||||
|
|
||||||
const planOptions: PlanOption[] = [
|
const planOptions: PlanOption[] = [
|
||||||
{
|
{
|
||||||
id: "starter",
|
id: "basic",
|
||||||
name: "Starter",
|
name: "Basic",
|
||||||
price: "Free",
|
price: "Free",
|
||||||
tierType: null
|
tierType: null
|
||||||
},
|
},
|
||||||
@@ -109,10 +109,10 @@ const planOptions: PlanOption[] = [
|
|||||||
|
|
||||||
// Tier limits mapping derived from limit sets
|
// Tier limits mapping derived from limit sets
|
||||||
const tierLimits: Record<
|
const tierLimits: Record<
|
||||||
Tier | "starter",
|
Tier | "basic",
|
||||||
{ users: number; sites: number; domains: number; remoteNodes: number }
|
{ users: number; sites: number; domains: number; remoteNodes: number }
|
||||||
> = {
|
> = {
|
||||||
starter: {
|
basic: {
|
||||||
users: freeLimitSet[FeatureId.USERS]?.value ?? 0,
|
users: freeLimitSet[FeatureId.USERS]?.value ?? 0,
|
||||||
sites: freeLimitSet[FeatureId.SITES]?.value ?? 0,
|
sites: freeLimitSet[FeatureId.SITES]?.value ?? 0,
|
||||||
domains: freeLimitSet[FeatureId.DOMAINS]?.value ?? 0,
|
domains: freeLimitSet[FeatureId.DOMAINS]?.value ?? 0,
|
||||||
@@ -183,7 +183,7 @@ export default function BillingPage() {
|
|||||||
// Confirmation dialog state
|
// Confirmation dialog state
|
||||||
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
||||||
const [pendingTier, setPendingTier] = useState<{
|
const [pendingTier, setPendingTier] = useState<{
|
||||||
tier: Tier | "starter";
|
tier: Tier | "basic";
|
||||||
action: "upgrade" | "downgrade";
|
action: "upgrade" | "downgrade";
|
||||||
planName: string;
|
planName: string;
|
||||||
price: string;
|
price: string;
|
||||||
@@ -402,8 +402,8 @@ export default function BillingPage() {
|
|||||||
pendingTier.action === "upgrade" ||
|
pendingTier.action === "upgrade" ||
|
||||||
pendingTier.action === "downgrade"
|
pendingTier.action === "downgrade"
|
||||||
) {
|
) {
|
||||||
// If downgrading to starter (free tier), go to Stripe portal
|
// If downgrading to basic (free tier), go to Stripe portal
|
||||||
if (pendingTier.tier === "starter") {
|
if (pendingTier.tier === "basic") {
|
||||||
handleModifySubscription();
|
handleModifySubscription();
|
||||||
} else if (hasSubscription) {
|
} else if (hasSubscription) {
|
||||||
handleChangeTier(pendingTier.tier);
|
handleChangeTier(pendingTier.tier);
|
||||||
@@ -417,7 +417,7 @@ export default function BillingPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showTierConfirmation = (
|
const showTierConfirmation = (
|
||||||
tier: Tier | "starter",
|
tier: Tier | "basic",
|
||||||
action: "upgrade" | "downgrade",
|
action: "upgrade" | "downgrade",
|
||||||
planName: string,
|
planName: string,
|
||||||
price: string
|
price: string
|
||||||
@@ -432,9 +432,9 @@ export default function BillingPage() {
|
|||||||
|
|
||||||
// Get current plan ID from tier
|
// Get current plan ID from tier
|
||||||
const getCurrentPlanId = (): PlanId => {
|
const getCurrentPlanId = (): PlanId => {
|
||||||
if (!hasSubscription || !currentTier) return "starter";
|
if (!hasSubscription || !currentTier) return "basic";
|
||||||
const plan = planOptions.find((p) => p.tierType === currentTier);
|
const plan = planOptions.find((p) => p.tierType === currentTier);
|
||||||
return plan?.id || "starter";
|
return plan?.id || "basic";
|
||||||
};
|
};
|
||||||
|
|
||||||
const currentPlanId = getCurrentPlanId();
|
const currentPlanId = getCurrentPlanId();
|
||||||
@@ -451,8 +451,8 @@ export default function BillingPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (plan.id === currentPlanId) {
|
if (plan.id === currentPlanId) {
|
||||||
// If it's the starter plan (starter with no subscription), show as current but disabled
|
// If it's the basic plan (basic with no subscription), show as current but disabled
|
||||||
if (plan.id === "starter" && !hasSubscription) {
|
if (plan.id === "basic" && !hasSubscription) {
|
||||||
return {
|
return {
|
||||||
label: "Current Plan",
|
label: "Current Plan",
|
||||||
action: () => {},
|
action: () => {},
|
||||||
@@ -484,10 +484,10 @@ export default function BillingPage() {
|
|||||||
plan.name,
|
plan.name,
|
||||||
plan.price + (" " + plan.priceDetail || "")
|
plan.price + (" " + plan.priceDetail || "")
|
||||||
);
|
);
|
||||||
} else if (plan.id === "starter") {
|
} else if (plan.id === "basic") {
|
||||||
// Show confirmation for downgrading to starter (free tier)
|
// Show confirmation for downgrading to basic (free tier)
|
||||||
showTierConfirmation(
|
showTierConfirmation(
|
||||||
"starter",
|
"basic",
|
||||||
"downgrade",
|
"downgrade",
|
||||||
plan.name,
|
plan.name,
|
||||||
plan.price
|
plan.price
|
||||||
@@ -566,7 +566,7 @@ export default function BillingPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Check if downgrading to a tier would violate current usage limits
|
// Check if downgrading to a tier would violate current usage limits
|
||||||
const checkLimitViolations = (targetTier: Tier | "starter"): Array<{
|
const checkLimitViolations = (targetTier: Tier | "basic"): Array<{
|
||||||
feature: string;
|
feature: string;
|
||||||
currentUsage: number;
|
currentUsage: number;
|
||||||
newLimit: number;
|
newLimit: number;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
|||||||
import IdpTable, { IdpRow } from "@app/components/OrgIdpTable";
|
import IdpTable, { IdpRow } from "@app/components/OrgIdpTable";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
|
import { IdpGlobalModeBanner } from "@app/components/IdpGlobalModeBanner";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
|
||||||
type OrgIdpPageProps = {
|
type OrgIdpPageProps = {
|
||||||
@@ -36,6 +37,8 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
|
|||||||
description={t("idpManageDescription")}
|
description={t("idpManageDescription")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<IdpGlobalModeBanner />
|
||||||
|
|
||||||
<PaidFeaturesAlert tiers={tierMatrix.orgOidc} />
|
<PaidFeaturesAlert tiers={tierMatrix.orgOidc} />
|
||||||
|
|
||||||
<IdpTable idps={idps} orgId={params.orgId} />
|
<IdpTable idps={idps} orgId={params.orgId} />
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
|||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
import type { OrgContextType } from "@app/contexts/orgContext";
|
import type { OrgContextType } from "@app/contexts/orgContext";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
import { isAppPageRouteDefinition } from "next/dist/server/route-definitions/app-page-route-definition";
|
|
||||||
|
|
||||||
// Session length options in hours
|
// Session length options in hours
|
||||||
const SESSION_LENGTH_OPTIONS = [
|
const SESSION_LENGTH_OPTIONS = [
|
||||||
@@ -219,6 +218,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{LOG_RETENTION_OPTIONS.filter(
|
{LOG_RETENTION_OPTIONS.filter(
|
||||||
(option) => {
|
(option) => {
|
||||||
|
if (build != "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let maxDays: number;
|
let maxDays: number;
|
||||||
|
|
||||||
if (!subscriptionTier) {
|
if (!subscriptionTier) {
|
||||||
@@ -314,6 +317,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{LOG_RETENTION_OPTIONS.filter(
|
{LOG_RETENTION_OPTIONS.filter(
|
||||||
(option) => {
|
(option) => {
|
||||||
|
if (build != "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let maxDays: number;
|
let maxDays: number;
|
||||||
|
|
||||||
if (!subscriptionTier) {
|
if (!subscriptionTier) {
|
||||||
@@ -411,6 +418,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{LOG_RETENTION_OPTIONS.filter(
|
{LOG_RETENTION_OPTIONS.filter(
|
||||||
(option) => {
|
(option) => {
|
||||||
|
if (build != "saas") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
let maxDays: number;
|
let maxDays: number;
|
||||||
|
|
||||||
if (!subscriptionTier) {
|
if (!subscriptionTier) {
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export default function ResourceAuthenticationPage() {
|
|||||||
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
||||||
orgQueries.identityProviders({
|
orgQueries.identityProviders({
|
||||||
orgId: org.org.orgId,
|
orgId: org.org.orgId,
|
||||||
useOrgOnlyIdp: env.flags.useOrgOnlyIdp
|
useOrgOnlyIdp: env.app.identityProviderMode === "org"
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default async function UsersPage(props: PageProps) {
|
|||||||
title={t("userTitle")}
|
title={t("userTitle")}
|
||||||
description={t("userDescription")}
|
description={t("userDescription")}
|
||||||
/>
|
/>
|
||||||
<Alert variant="neutral" className="mb-6">
|
<Alert className="mb-6">
|
||||||
<InfoIcon className="h-4 w-4" />
|
<InfoIcon className="h-4 w-4" />
|
||||||
<AlertTitle className="font-semibold">
|
<AlertTitle className="font-semibold">
|
||||||
{t("userAbount")}
|
{t("userAbount")}
|
||||||
|
|||||||
74
src/app/auth/delete-account/DeleteAccountClient.tsx
Normal file
74
src/app/auth/delete-account/DeleteAccountClient.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import DeleteAccountConfirmDialog from "@app/components/DeleteAccountConfirmDialog";
|
||||||
|
import UserProfileCard from "@app/components/UserProfileCard";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import { createApiClient } from "@app/lib/api";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
|
|
||||||
|
type DeleteAccountClientProps = {
|
||||||
|
displayName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DeleteAccountClient({
|
||||||
|
displayName
|
||||||
|
}: DeleteAccountClientProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const t = useTranslations();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
|
|
||||||
|
function handleUseDifferentAccount() {
|
||||||
|
api.post("/auth/logout")
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(t("logoutError"), e);
|
||||||
|
toast({
|
||||||
|
title: t("logoutError"),
|
||||||
|
description: formatAxiosError(e, t("logoutError"))
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
router.push(
|
||||||
|
"/auth/login?internal_redirect=/auth/delete-account"
|
||||||
|
);
|
||||||
|
router.refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<UserProfileCard
|
||||||
|
identifier={displayName}
|
||||||
|
description={t("signingAs")}
|
||||||
|
onUseDifferentAccount={handleUseDifferentAccount}
|
||||||
|
useDifferentAccountText={t("deviceLoginUseDifferentAccount")}
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("deleteAccountDescription")}
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button variant="outline" onClick={() => router.back()}>
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
{t("back")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => setIsDialogOpen(true)}
|
||||||
|
>
|
||||||
|
{t("deleteAccountButton")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<DeleteAccountConfirmDialog
|
||||||
|
open={isDialogOpen}
|
||||||
|
setOpen={setIsDialogOpen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
src/app/auth/delete-account/page.tsx
Normal file
28
src/app/auth/delete-account/page.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { verifySession } from "@app/lib/auth/verifySession";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
import { cache } from "react";
|
||||||
|
import DeleteAccountClient from "./DeleteAccountClient";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||||
|
|
||||||
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
|
export default async function DeleteAccountPage() {
|
||||||
|
const getUser = cache(verifySession);
|
||||||
|
const user = await getUser({ skipCheckVerifyEmail: true });
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
redirect("/auth/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = await getTranslations();
|
||||||
|
const displayName = getUserDisplayName({ user });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h1 className="text-xl font-semibold">{t("deleteAccount")}</h1>
|
||||||
|
<DeleteAccountClient displayName={displayName} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -76,12 +76,13 @@ export default async function Page(props: {
|
|||||||
|
|
||||||
// Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled)
|
// Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled)
|
||||||
const useSmartLogin =
|
const useSmartLogin =
|
||||||
build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp);
|
build === "saas" ||
|
||||||
|
(build === "enterprise" && env.app.identityProviderMode === "org");
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (!useSmartLogin) {
|
if (!useSmartLogin) {
|
||||||
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
|
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
|
||||||
if (build === "oss" || !env.flags.useOrgOnlyIdp) {
|
if (build === "oss" || env.app.identityProviderMode !== "org") {
|
||||||
const idpsRes = await cache(
|
const idpsRes = await cache(
|
||||||
async () =>
|
async () =>
|
||||||
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
||||||
@@ -165,7 +166,8 @@ export default async function Page(props: {
|
|||||||
forceLogin={forceLogin}
|
forceLogin={forceLogin}
|
||||||
showOrgLogin={
|
showOrgLogin={
|
||||||
!isInvite &&
|
!isInvite &&
|
||||||
(build === "saas" || env.flags.useOrgOnlyIdp)
|
(build === "saas" ||
|
||||||
|
env.app.identityProviderMode === "org")
|
||||||
}
|
}
|
||||||
searchParams={searchParams}
|
searchParams={searchParams}
|
||||||
defaultUser={defaultUser}
|
defaultUser={defaultUser}
|
||||||
@@ -188,7 +190,8 @@ export default async function Page(props: {
|
|||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
|
{!isInvite &&
|
||||||
|
(build === "saas" || env.app.identityProviderMode === "org") ? (
|
||||||
<OrgSignInLink
|
<OrgSignInLink
|
||||||
href={`/auth/org${buildQueryString(searchParams)}`}
|
href={`/auth/org${buildQueryString(searchParams)}`}
|
||||||
linkText={t("orgAuthSignInToOrg")}
|
linkText={t("orgAuthSignInToOrg")}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default async function OrgAuthPage(props: {
|
|||||||
|
|
||||||
const env = pullEnv();
|
const env = pullEnv();
|
||||||
|
|
||||||
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
if (build !== "saas" && env.app.identityProviderMode !== "org") {
|
||||||
const queryString = new URLSearchParams(searchParams as any).toString();
|
const queryString = new URLSearchParams(searchParams as any).toString();
|
||||||
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default async function OrgAuthPage(props: {
|
|||||||
|
|
||||||
const env = pullEnv();
|
const env = pullEnv();
|
||||||
|
|
||||||
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
if (build !== "saas" && env.app.identityProviderMode !== "org") {
|
||||||
redirect("/");
|
redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import type {
|
|||||||
import { CheckOrgUserAccessResponse } from "@server/routers/org";
|
import { CheckOrgUserAccessResponse } from "@server/routers/org";
|
||||||
import OrgPolicyRequired from "@app/components/OrgPolicyRequired";
|
import OrgPolicyRequired from "@app/components/OrgPolicyRequired";
|
||||||
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
|
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
|
||||||
|
import { normalizePostAuthPath } from "@server/lib/normalizePostAuthPath";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -108,6 +109,11 @@ export default async function ResourceAuthPage(props: {
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const normalizedPostAuthPath = normalizePostAuthPath(authInfo.postAuthPath);
|
||||||
|
if (normalizedPostAuthPath) {
|
||||||
|
redirectUrl = new URL(authInfo.url).origin + normalizedPostAuthPath;
|
||||||
|
}
|
||||||
|
|
||||||
const hasAuth =
|
const hasAuth =
|
||||||
authInfo.password ||
|
authInfo.password ||
|
||||||
authInfo.pincode ||
|
authInfo.pincode ||
|
||||||
@@ -202,7 +208,7 @@ export default async function ResourceAuthPage(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loginIdps: LoginFormIDP[] = [];
|
let loginIdps: LoginFormIDP[] = [];
|
||||||
if (build === "saas" || env.flags.useOrgOnlyIdp) {
|
if (build === "saas" || env.app.identityProviderMode === "org") {
|
||||||
if (subscribed) {
|
if (subscribed) {
|
||||||
const idpsRes = await cache(
|
const idpsRes = await cache(
|
||||||
async () =>
|
async () =>
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
// PaidFeaturesAlert
|
// PaidFeaturesAlert
|
||||||
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||||
build === "saas" ||
|
build === "saas" ||
|
||||||
env?.flags.useOrgOnlyIdp
|
env?.app.identityProviderMode === "org" ||
|
||||||
|
(env?.app.identityProviderMode === undefined && build !== "oss")
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: "sidebarIdentityProviders",
|
title: "sidebarIdentityProviders",
|
||||||
@@ -251,7 +252,9 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
|||||||
href: "/admin/api-keys",
|
href: "/admin/api-keys",
|
||||||
icon: <KeyRound className="size-4 flex-none" />
|
icon: <KeyRound className="size-4 flex-none" />
|
||||||
},
|
},
|
||||||
...(build === "oss" || !env?.flags.useOrgOnlyIdp
|
...(build === "oss" ||
|
||||||
|
env?.app.identityProviderMode === "global" ||
|
||||||
|
env?.app.identityProviderMode === undefined
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: "sidebarIdentityProviders",
|
title: "sidebarIdentityProviders",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { consumeInternalRedirectPath } from "@app/lib/internalRedirect";
|
import { getInternalRedirectTarget } from "@app/lib/internalRedirect";
|
||||||
|
|
||||||
type ApplyInternalRedirectProps = {
|
type ApplyInternalRedirectProps = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
@@ -14,9 +14,9 @@ export default function ApplyInternalRedirect({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const path = consumeInternalRedirectPath();
|
const target = getInternalRedirectTarget(orgId);
|
||||||
if (path) {
|
if (target) {
|
||||||
router.replace(`/${orgId}${path}`);
|
router.replace(target);
|
||||||
}
|
}
|
||||||
}, [orgId, router]);
|
}, [orgId, router]);
|
||||||
|
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ export default function AuthPageBrandingForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{build === "saas" ||
|
{build === "saas" ||
|
||||||
env.env.flags.useOrgOnlyIdp ? (
|
env.env.app.identityProviderMode === "org" ? (
|
||||||
<>
|
<>
|
||||||
<div className="mt-3 mb-6">
|
<div className="mt-3 mb-6">
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
const [udpCustomPorts, setUdpCustomPorts] = useState<string>("");
|
const [udpCustomPorts, setUdpCustomPorts] = useState<string>("");
|
||||||
|
|
||||||
const availableSites = sites.filter(
|
const availableSites = sites.filter(
|
||||||
(site) => site.type === "newt" && site.subnet
|
(site) => site.type === "newt"
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<FormData>({
|
const form = useForm<FormData>({
|
||||||
|
|||||||
414
src/components/DeleteAccountConfirmDialog.tsx
Normal file
414
src/components/DeleteAccountConfirmDialog.tsx
Normal file
@@ -0,0 +1,414 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState, useEffect, useMemo } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { createApiClient } from "@app/lib/api";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import {
|
||||||
|
Credenza,
|
||||||
|
CredenzaBody,
|
||||||
|
CredenzaClose,
|
||||||
|
CredenzaContent,
|
||||||
|
CredenzaFooter,
|
||||||
|
CredenzaHeader,
|
||||||
|
CredenzaTitle
|
||||||
|
} from "@app/components/Credenza";
|
||||||
|
import {
|
||||||
|
Form,
|
||||||
|
FormControl,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage
|
||||||
|
} from "@app/components/ui/form";
|
||||||
|
import { Input } from "@app/components/ui/input";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { z } from "zod";
|
||||||
|
import {
|
||||||
|
InputOTP,
|
||||||
|
InputOTPGroup,
|
||||||
|
InputOTPSlot
|
||||||
|
} from "@app/components/ui/input-otp";
|
||||||
|
import { REGEXP_ONLY_DIGITS_AND_CHARS } from "input-otp";
|
||||||
|
import type {
|
||||||
|
DeleteMyAccountPreviewResponse,
|
||||||
|
DeleteMyAccountCodeRequestedResponse,
|
||||||
|
DeleteMyAccountSuccessResponse
|
||||||
|
} from "@server/routers/auth/deleteMyAccount";
|
||||||
|
import { AxiosResponse } from "axios";
|
||||||
|
|
||||||
|
type DeleteAccountConfirmDialogProps = {
|
||||||
|
open: boolean;
|
||||||
|
setOpen: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DeleteAccountConfirmDialog({
|
||||||
|
open,
|
||||||
|
setOpen
|
||||||
|
}: DeleteAccountConfirmDialogProps) {
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
const router = useRouter();
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
|
const passwordSchema = useMemo(
|
||||||
|
() =>
|
||||||
|
z.object({
|
||||||
|
password: z.string().min(1, { message: t("passwordRequired") })
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const codeSchema = useMemo(
|
||||||
|
() =>
|
||||||
|
z.object({
|
||||||
|
code: z.string().length(6, { message: t("pincodeInvalid") })
|
||||||
|
}),
|
||||||
|
[t]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [step, setStep] = useState<0 | 1 | 2>(0);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [loadingPreview, setLoadingPreview] = useState(false);
|
||||||
|
const [preview, setPreview] =
|
||||||
|
useState<DeleteMyAccountPreviewResponse | null>(null);
|
||||||
|
const [passwordValue, setPasswordValue] = useState("");
|
||||||
|
|
||||||
|
const passwordForm = useForm<z.infer<typeof passwordSchema>>({
|
||||||
|
resolver: zodResolver(passwordSchema),
|
||||||
|
defaultValues: { password: "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
const codeForm = useForm<z.infer<typeof codeSchema>>({
|
||||||
|
resolver: zodResolver(codeSchema),
|
||||||
|
defaultValues: { code: "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open && step === 0 && !preview) {
|
||||||
|
setLoadingPreview(true);
|
||||||
|
api.post<AxiosResponse<DeleteMyAccountPreviewResponse>>(
|
||||||
|
"/auth/delete-my-account",
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
if (res.data?.data?.preview) {
|
||||||
|
setPreview(res.data.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("deleteAccountError"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
err,
|
||||||
|
t("deleteAccountError")
|
||||||
|
)
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
})
|
||||||
|
.finally(() => setLoadingPreview(false));
|
||||||
|
}
|
||||||
|
}, [open, step, preview, api, setOpen, t]);
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
setStep(0);
|
||||||
|
setPreview(null);
|
||||||
|
setPasswordValue("");
|
||||||
|
passwordForm.reset();
|
||||||
|
codeForm.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleContinueToPassword() {
|
||||||
|
setStep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePasswordSubmit(
|
||||||
|
values: z.infer<typeof passwordSchema>
|
||||||
|
) {
|
||||||
|
setLoading(true);
|
||||||
|
setPasswordValue(values.password);
|
||||||
|
try {
|
||||||
|
const res = await api.post<
|
||||||
|
| AxiosResponse<DeleteMyAccountCodeRequestedResponse>
|
||||||
|
| AxiosResponse<DeleteMyAccountSuccessResponse>
|
||||||
|
>("/auth/delete-my-account", { password: values.password });
|
||||||
|
|
||||||
|
const data = res.data?.data;
|
||||||
|
|
||||||
|
if (data && "codeRequested" in data && data.codeRequested) {
|
||||||
|
setStep(2);
|
||||||
|
} else if (data && "success" in data && data.success) {
|
||||||
|
toast({
|
||||||
|
title: t("deleteAccountSuccess"),
|
||||||
|
description: t("deleteAccountSuccessMessage")
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
reset();
|
||||||
|
router.push("/auth/login");
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("deleteAccountError"),
|
||||||
|
description: formatAxiosError(err, t("deleteAccountError"))
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCodeSubmit(values: z.infer<typeof codeSchema>) {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await api.post<
|
||||||
|
AxiosResponse<DeleteMyAccountSuccessResponse>
|
||||||
|
>("/auth/delete-my-account", {
|
||||||
|
password: passwordValue,
|
||||||
|
code: values.code
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.data?.data?.success) {
|
||||||
|
toast({
|
||||||
|
title: t("deleteAccountSuccess"),
|
||||||
|
description: t("deleteAccountSuccessMessage")
|
||||||
|
});
|
||||||
|
setOpen(false);
|
||||||
|
reset();
|
||||||
|
router.push("/auth/login");
|
||||||
|
router.refresh();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("deleteAccountError"),
|
||||||
|
description: formatAxiosError(err, t("deleteAccountError"))
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Credenza
|
||||||
|
open={open}
|
||||||
|
onOpenChange={(val) => {
|
||||||
|
setOpen(val);
|
||||||
|
if (!val) reset();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CredenzaContent>
|
||||||
|
<CredenzaHeader>
|
||||||
|
<CredenzaTitle>
|
||||||
|
{t("deleteAccountConfirmTitle")}
|
||||||
|
</CredenzaTitle>
|
||||||
|
</CredenzaHeader>
|
||||||
|
<CredenzaBody>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{step === 0 && (
|
||||||
|
<>
|
||||||
|
{loadingPreview ? (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("loading")}...
|
||||||
|
</p>
|
||||||
|
) : preview ? (
|
||||||
|
<>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("deleteAccountConfirmMessage")}
|
||||||
|
</p>
|
||||||
|
<div className="rounded-md bg-muted p-3 space-y-2">
|
||||||
|
<p className="text-sm font-medium">
|
||||||
|
{t(
|
||||||
|
"deleteAccountPreviewAccount"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
{preview.orgs.length > 0 && (
|
||||||
|
<>
|
||||||
|
<p className="text-sm font-medium mt-2">
|
||||||
|
{t(
|
||||||
|
"deleteAccountPreviewOrgs"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside text-sm text-muted-foreground space-y-1">
|
||||||
|
{preview.orgs.map(
|
||||||
|
(org) => (
|
||||||
|
<li
|
||||||
|
key={
|
||||||
|
org.orgId
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{org.name ||
|
||||||
|
org.orgId}
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<p className="text-sm font-bold text-destructive">
|
||||||
|
{t("cannotbeUndone")}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step === 1 && (
|
||||||
|
<Form {...passwordForm}>
|
||||||
|
<form
|
||||||
|
id="delete-account-password-form"
|
||||||
|
onSubmit={passwordForm.handleSubmit(
|
||||||
|
handlePasswordSubmit
|
||||||
|
)}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={passwordForm.control}
|
||||||
|
name="password"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("password")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{step === 2 && (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="text-center">
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
{t("otpAuthDescription")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Form {...codeForm}>
|
||||||
|
<form
|
||||||
|
id="delete-account-code-form"
|
||||||
|
onSubmit={codeForm.handleSubmit(
|
||||||
|
handleCodeSubmit
|
||||||
|
)}
|
||||||
|
className="space-y-4"
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={codeForm.control}
|
||||||
|
name="code"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<InputOTP
|
||||||
|
maxLength={6}
|
||||||
|
{...field}
|
||||||
|
pattern={
|
||||||
|
REGEXP_ONLY_DIGITS_AND_CHARS
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
value: string
|
||||||
|
) => {
|
||||||
|
field.onChange(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputOTPGroup>
|
||||||
|
<InputOTPSlot
|
||||||
|
index={
|
||||||
|
0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputOTPSlot
|
||||||
|
index={
|
||||||
|
1
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputOTPSlot
|
||||||
|
index={
|
||||||
|
2
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputOTPSlot
|
||||||
|
index={
|
||||||
|
3
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputOTPSlot
|
||||||
|
index={
|
||||||
|
4
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<InputOTPSlot
|
||||||
|
index={
|
||||||
|
5
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</InputOTPGroup>
|
||||||
|
</InputOTP>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CredenzaBody>
|
||||||
|
<CredenzaFooter>
|
||||||
|
<CredenzaClose asChild>
|
||||||
|
<Button variant="outline">{t("close")}</Button>
|
||||||
|
</CredenzaClose>
|
||||||
|
{step === 0 && preview && !loadingPreview && (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={handleContinueToPassword}
|
||||||
|
>
|
||||||
|
{t("continue")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{step === 1 && (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
type="submit"
|
||||||
|
form="delete-account-password-form"
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{t("deleteAccountButton")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{step === 2 && (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
type="submit"
|
||||||
|
form="delete-account-code-form"
|
||||||
|
loading={loading}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{t("deleteAccountButton")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</CredenzaFooter>
|
||||||
|
</CredenzaContent>
|
||||||
|
</Credenza>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -397,7 +397,7 @@ export default function EditInternalResourceDialog({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const availableSites = sites.filter(
|
const availableSites = sites.filter(
|
||||||
(site) => site.type === "newt" && site.subnet
|
(site) => site.type === "newt"
|
||||||
);
|
);
|
||||||
|
|
||||||
const form = useForm<FormData>({
|
const form = useForm<FormData>({
|
||||||
|
|||||||
65
src/components/IdpGlobalModeBanner.tsx
Normal file
65
src/components/IdpGlobalModeBanner.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||||
|
import { Info } from "lucide-react";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
|
export function IdpGlobalModeBanner() {
|
||||||
|
const t = useTranslations();
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
const { isPaidUser, hasEnterpriseLicense } = usePaidStatus();
|
||||||
|
|
||||||
|
const identityProviderModeUndefined =
|
||||||
|
env.app.identityProviderMode === undefined;
|
||||||
|
const paidUserForOrgOidc = isPaidUser(tierMatrix.orgOidc);
|
||||||
|
const enterpriseUnlicensed =
|
||||||
|
build === "enterprise" && !hasEnterpriseLicense;
|
||||||
|
|
||||||
|
if (build === "saas") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!identityProviderModeUndefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminPanelLinkRenderer = (chunks: React.ReactNode) => (
|
||||||
|
<Link href="/admin/idp" className="font-medium underline">
|
||||||
|
{chunks}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Alert className="mb-6">
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
<AlertDescription>
|
||||||
|
{paidUserForOrgOidc
|
||||||
|
? t.rich("idpGlobalModeBanner", {
|
||||||
|
adminPanelLink: adminPanelLinkRenderer,
|
||||||
|
configDocsLink: (chunks) => (
|
||||||
|
<Link
|
||||||
|
href="https://docs.pangolin.net/manage/identity-providers/add-an-idp#organization-identity-providers"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="font-medium underline"
|
||||||
|
>
|
||||||
|
{chunks}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
: enterpriseUnlicensed
|
||||||
|
? t.rich("idpGlobalModeBannerLicenseRequired", {
|
||||||
|
adminPanelLink: adminPanelLinkRenderer
|
||||||
|
})
|
||||||
|
: t.rich("idpGlobalModeBannerUpgradeRequired", {
|
||||||
|
adminPanelLink: adminPanelLinkRenderer
|
||||||
|
})}
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -37,7 +37,7 @@ export const MachineClientsBanner = ({ orgId }: MachineClientsBannerProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="https://docs.pangolin.net/manage/clients/install-client#docker"
|
href="https://docs.pangolin.net/manage/clients/install-client#docker-pangolin-cli"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -38,15 +38,49 @@ const bannerContentClassName = "py-3 px-4";
|
|||||||
const bannerRowClassName =
|
const bannerRowClassName =
|
||||||
"flex items-center gap-2.5 text-sm text-muted-foreground";
|
"flex items-center gap-2.5 text-sm text-muted-foreground";
|
||||||
const bannerIconClassName = "size-4 shrink-0 text-purple-500";
|
const bannerIconClassName = "size-4 shrink-0 text-purple-500";
|
||||||
|
const docsLinkClassName =
|
||||||
|
"inline-flex items-center gap-1 font-medium text-purple-600 underline";
|
||||||
|
const PANGOLIN_CLOUD_SIGNUP_URL = "https://app.pangolin.net/auth/signup/";
|
||||||
|
const ENTERPRISE_DOCS_URL =
|
||||||
|
"https://docs.pangolin.net/self-host/enterprise-edition";
|
||||||
|
|
||||||
function getTierLinkRenderer(billingHref: string) {
|
function getTierLinkRenderer(billingHref: string) {
|
||||||
return function tierLinkRenderer(chunks: React.ReactNode) {
|
return function tierLinkRenderer(chunks: React.ReactNode) {
|
||||||
|
return (
|
||||||
|
<Link href={billingHref} className={docsLinkClassName}>
|
||||||
|
{chunks}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPangolinCloudLinkRenderer() {
|
||||||
|
return function pangolinCloudLinkRenderer(chunks: React.ReactNode) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={billingHref}
|
href={PANGOLIN_CLOUD_SIGNUP_URL}
|
||||||
className="inline-flex items-center gap-1 font-medium text-purple-600 underline"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={docsLinkClassName}
|
||||||
>
|
>
|
||||||
{chunks}
|
{chunks}
|
||||||
|
<ExternalLink className="size-3.5 shrink-0" />
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDocsLinkRenderer(href: string) {
|
||||||
|
return function docsLinkRenderer(chunks: React.ReactNode) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={href}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className={docsLinkClassName}
|
||||||
|
>
|
||||||
|
{chunks}
|
||||||
|
<ExternalLink className="size-3.5 shrink-0" />
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -66,6 +100,8 @@ export function PaidFeaturesAlert({ tiers }: Props) {
|
|||||||
const requiredTierName = requiredTier ? t(TIER_TRANSLATION_KEYS[requiredTier]) : null;
|
const requiredTierName = requiredTier ? t(TIER_TRANSLATION_KEYS[requiredTier]) : null;
|
||||||
const billingHref = orgId ? `/${orgId}/settings/billing` : "https://pangolin.net/pricing";
|
const billingHref = orgId ? `/${orgId}/settings/billing` : "https://pangolin.net/pricing";
|
||||||
const tierLinkRenderer = getTierLinkRenderer(billingHref);
|
const tierLinkRenderer = getTierLinkRenderer(billingHref);
|
||||||
|
const pangolinCloudLinkRenderer = getPangolinCloudLinkRenderer();
|
||||||
|
const enterpriseDocsLinkRenderer = getDocsLinkRenderer(ENTERPRISE_DOCS_URL);
|
||||||
|
|
||||||
if (env.flags.disableEnterpriseFeatures) {
|
if (env.flags.disableEnterpriseFeatures) {
|
||||||
return null;
|
return null;
|
||||||
@@ -103,7 +139,12 @@ export function PaidFeaturesAlert({ tiers }: Props) {
|
|||||||
<CardContent className={bannerContentClassName}>
|
<CardContent className={bannerContentClassName}>
|
||||||
<div className={bannerRowClassName}>
|
<div className={bannerRowClassName}>
|
||||||
<KeyRound className={bannerIconClassName} />
|
<KeyRound className={bannerIconClassName} />
|
||||||
<span>{t("licenseRequiredToUse")}</span>
|
<span>
|
||||||
|
{t.rich("licenseRequiredToUse", {
|
||||||
|
enterpriseLicenseLink: enterpriseDocsLinkRenderer,
|
||||||
|
pangolinCloudLink: pangolinCloudLinkRenderer
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -116,17 +157,8 @@ export function PaidFeaturesAlert({ tiers }: Props) {
|
|||||||
<KeyRound className={bannerIconClassName} />
|
<KeyRound className={bannerIconClassName} />
|
||||||
<span>
|
<span>
|
||||||
{t.rich("ossEnterpriseEditionRequired", {
|
{t.rich("ossEnterpriseEditionRequired", {
|
||||||
enterpriseEditionLink: (chunks) => (
|
enterpriseEditionLink: enterpriseDocsLinkRenderer,
|
||||||
<Link
|
pangolinCloudLink: pangolinCloudLinkRenderer
|
||||||
href="https://docs.pangolin.net/self-host/enterprise-edition"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline-flex items-center gap-1 font-medium text-purple-600 underline"
|
|
||||||
>
|
|
||||||
{chunks}
|
|
||||||
<ExternalLink className="size-3.5 shrink-0" />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ function getActionsCategories(root: boolean) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (root || build === "saas" || env.flags.useOrgOnlyIdp) {
|
if (root || build === "saas" || env.app.identityProviderMode === "org") {
|
||||||
actionsByCategory["Identity Provider (IDP)"] = {
|
actionsByCategory["Identity Provider (IDP)"] = {
|
||||||
[t("actionCreateIdp")]: "createIdp",
|
[t("actionCreateIdp")]: "createIdp",
|
||||||
[t("actionUpdateIdp")]: "updateIdp",
|
[t("actionUpdateIdp")]: "updateIdp",
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
|
|||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { formatAxiosError } from "@app/lib/api";
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
import { getUserDisplayName } from "@app/lib/getUserDisplayName";
|
||||||
import { Laptop, LogOut, Moon, Sun, Smartphone } from "lucide-react";
|
import { Laptop, LogOut, Moon, Sun, Smartphone, Trash2 } from "lucide-react";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { build } from "@server/build";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useUserContext } from "@app/hooks/useUserContext";
|
import { useUserContext } from "@app/hooks/useUserContext";
|
||||||
import Disable2FaForm from "./Disable2FaForm";
|
import Disable2FaForm from "./Disable2FaForm";
|
||||||
@@ -187,6 +189,20 @@ export default function ProfileIcon() {
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<LocaleSwitcher />
|
<LocaleSwitcher />
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
{user?.type === UserType.Internal && !user?.serverAdmin && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link
|
||||||
|
href="/auth/delete-account"
|
||||||
|
className="flex cursor-pointer items-center"
|
||||||
|
>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
<span>{t("deleteAccount")}</span>
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<DropdownMenuItem onClick={() => logout()}>
|
<DropdownMenuItem onClick={() => logout()}>
|
||||||
{/* <LogOut className="mr-2 h-4 w-4" /> */}
|
{/* <LogOut className="mr-2 h-4 w-4" /> */}
|
||||||
<span>{t("logout")}</span>
|
<span>{t("logout")}</span>
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ export default function RedirectToOrg({ targetOrgId }: RedirectToOrgProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const target = getInternalRedirectTarget(targetOrgId);
|
const target =
|
||||||
|
getInternalRedirectTarget(targetOrgId) ?? `/${targetOrgId}`;
|
||||||
router.replace(target);
|
router.replace(target);
|
||||||
} catch {
|
} catch {
|
||||||
router.replace(`/${targetOrgId}`);
|
router.replace(`/${targetOrgId}`);
|
||||||
|
|||||||
@@ -204,7 +204,9 @@ export default function SignupForm({
|
|||||||
? env.branding.logo?.authPage?.height || 44
|
? env.branding.logo?.authPage?.height || 44
|
||||||
: 44;
|
: 44;
|
||||||
|
|
||||||
const showOrgBanner = fromSmartLogin && (build === "saas" || env.flags.useOrgOnlyIdp);
|
const showOrgBanner =
|
||||||
|
fromSmartLogin &&
|
||||||
|
(build === "saas" || env.app.identityProviderMode === "org");
|
||||||
const orgBannerHref = redirect
|
const orgBannerHref = redirect
|
||||||
? `/auth/org?redirect=${encodeURIComponent(redirect)}`
|
? `/auth/org?redirect=${encodeURIComponent(redirect)}`
|
||||||
: "/auth/org";
|
: "/auth/org";
|
||||||
@@ -226,388 +228,398 @@ export default function SignupForm({
|
|||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
<CardHeader className="border-b">
|
<CardHeader className="border-b">
|
||||||
<div className="flex flex-row items-center justify-center">
|
<div className="flex flex-row items-center justify-center">
|
||||||
<BrandingLogo height={logoHeight} width={logoWidth} />
|
<BrandingLogo height={logoHeight} width={logoWidth} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center space-y-1 pt-3">
|
<div className="text-center space-y-1 pt-3">
|
||||||
<p className="text-muted-foreground">{getSubtitle()}</p>
|
<p className="text-muted-foreground">{getSubtitle()}</p>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("email")}</FormLabel>
|
<FormLabel>{t("email")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
disabled={!!emailParam}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="password"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<FormLabel>{t("password")}</FormLabel>
|
|
||||||
{passwordStrength.strength ===
|
|
||||||
"strong" && (
|
|
||||||
<Check className="h-4 w-4 text-green-500" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
|
||||||
{...field}
|
{...field}
|
||||||
onChange={(e) => {
|
disabled={!!emailParam}
|
||||||
field.onChange(e);
|
|
||||||
setPasswordValue(
|
|
||||||
e.target.value
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"strong" &&
|
|
||||||
"border-green-500 focus-visible:ring-green-500",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"medium" &&
|
|
||||||
"border-yellow-500 focus-visible:ring-yellow-500",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"weak" &&
|
|
||||||
passwordValue.length >
|
|
||||||
0 &&
|
|
||||||
"border-red-500 focus-visible:ring-red-500"
|
|
||||||
)}
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</FormControl>
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
{passwordValue.length > 0 && (
|
|
||||||
<div className="space-y-3 mt-2">
|
|
||||||
{/* Password Strength Meter */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex justify-between items-center">
|
|
||||||
<span className="text-sm font-medium text-foreground">
|
|
||||||
{t("passwordStrength")}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm font-semibold",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"strong" &&
|
|
||||||
"text-green-600 dark:text-green-400",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"medium" &&
|
|
||||||
"text-yellow-600 dark:text-yellow-400",
|
|
||||||
passwordStrength.strength ===
|
|
||||||
"weak" &&
|
|
||||||
"text-red-600 dark:text-red-400"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
`passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}`
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Progress
|
|
||||||
value={
|
|
||||||
passwordStrength.percentage
|
|
||||||
}
|
|
||||||
className="h-2"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Requirements Checklist */}
|
|
||||||
<div className="bg-muted rounded-lg p-3 space-y-2">
|
|
||||||
<div className="text-sm font-medium text-foreground mb-2">
|
|
||||||
{t("passwordRequirements")}
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-1 gap-1.5">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.length ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.length
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementLengthText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.uppercase ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.uppercase
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementUppercaseText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.lowercase ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.lowercase
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementLowercaseText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.number ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.number
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementNumberText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{passwordStrength
|
|
||||||
.requirements
|
|
||||||
.special ? (
|
|
||||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
|
||||||
) : (
|
|
||||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
|
||||||
)}
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"text-sm",
|
|
||||||
passwordStrength
|
|
||||||
.requirements
|
|
||||||
.special
|
|
||||||
? "text-green-600 dark:text-green-400"
|
|
||||||
: "text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"passwordRequirementSpecialText"
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Only show FormMessage when not showing our custom requirements */}
|
|
||||||
{passwordValue.length === 0 && (
|
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
)}
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
<FormField
|
||||||
<FormField
|
control={form.control}
|
||||||
control={form.control}
|
name="password"
|
||||||
name="confirmPassword"
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormItem>
|
||||||
<FormItem>
|
<div className="flex items-center gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<FormLabel>
|
||||||
<FormLabel>
|
{t("password")}
|
||||||
{t("confirmPassword")}
|
</FormLabel>
|
||||||
</FormLabel>
|
{passwordStrength.strength ===
|
||||||
{doPasswordsMatch && (
|
"strong" && (
|
||||||
<Check className="h-4 w-4 text-green-500" />
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<div className="relative">
|
|
||||||
<Input
|
|
||||||
type="password"
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
field.onChange(e);
|
|
||||||
setConfirmPasswordValue(
|
|
||||||
e.target.value
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
className={cn(
|
|
||||||
doPasswordsMatch &&
|
|
||||||
"border-green-500 focus-visible:ring-green-500",
|
|
||||||
confirmPasswordValue.length >
|
|
||||||
0 &&
|
|
||||||
!doPasswordsMatch &&
|
|
||||||
"border-red-500 focus-visible:ring-red-500"
|
|
||||||
)}
|
|
||||||
autoComplete="new-password"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
<FormControl>
|
||||||
{confirmPasswordValue.length > 0 &&
|
<div className="relative">
|
||||||
!doPasswordsMatch && (
|
<Input
|
||||||
<p className="text-sm text-red-600 mt-1">
|
type="password"
|
||||||
{t("passwordsDoNotMatch")}
|
{...field}
|
||||||
</p>
|
onChange={(e) => {
|
||||||
)}
|
field.onChange(e);
|
||||||
{/* Only show FormMessage when field is empty */}
|
setPasswordValue(
|
||||||
{confirmPasswordValue.length === 0 && (
|
e.target.value
|
||||||
<FormMessage />
|
|
||||||
)}
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{build === "saas" && (
|
|
||||||
<>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="agreeToTerms"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-center">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={(
|
|
||||||
checked
|
|
||||||
) => {
|
|
||||||
field.onChange(checked);
|
|
||||||
handleTermsChange(
|
|
||||||
checked as boolean
|
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
className={cn(
|
||||||
</FormControl>
|
passwordStrength.strength ===
|
||||||
<div className="leading-none">
|
"strong" &&
|
||||||
<FormLabel className="text-sm font-normal">
|
"border-green-500 focus-visible:ring-green-500",
|
||||||
<div>
|
passwordStrength.strength ===
|
||||||
{t(
|
"medium" &&
|
||||||
"signUpTerms.IAgreeToThe"
|
"border-yellow-500 focus-visible:ring-yellow-500",
|
||||||
)}{" "}
|
passwordStrength.strength ===
|
||||||
<a
|
"weak" &&
|
||||||
href="https://pangolin.net/terms-of-service.html"
|
passwordValue.length >
|
||||||
target="_blank"
|
0 &&
|
||||||
rel="noopener noreferrer"
|
"border-red-500 focus-visible:ring-red-500"
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"signUpTerms.termsOfService"
|
|
||||||
)}{" "}
|
|
||||||
</a>
|
|
||||||
{t("signUpTerms.and")}{" "}
|
|
||||||
<a
|
|
||||||
href="https://pangolin.net/privacy-policy.html"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="text-primary hover:underline"
|
|
||||||
>
|
|
||||||
{t(
|
|
||||||
"signUpTerms.privacyPolicy"
|
|
||||||
)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</FormLabel>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="marketingEmailConsent"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="flex flex-row items-start">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={
|
|
||||||
field.onChange
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<div className="leading-none">
|
|
||||||
<FormLabel className="text-sm font-normal">
|
|
||||||
{t(
|
|
||||||
"signUpMarketing.keepMeInTheLoop"
|
|
||||||
)}
|
)}
|
||||||
</FormLabel>
|
autoComplete="new-password"
|
||||||
<FormMessage />
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FormItem>
|
</FormControl>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{error && (
|
{passwordValue.length > 0 && (
|
||||||
<Alert variant="destructive">
|
<div className="space-y-3 mt-2">
|
||||||
<AlertDescription>{error}</AlertDescription>
|
{/* Password Strength Meter */}
|
||||||
</Alert>
|
<div className="space-y-2">
|
||||||
)}
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm font-medium text-foreground">
|
||||||
|
{t(
|
||||||
|
"passwordStrength"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm font-semibold",
|
||||||
|
passwordStrength.strength ===
|
||||||
|
"strong" &&
|
||||||
|
"text-green-600 dark:text-green-400",
|
||||||
|
passwordStrength.strength ===
|
||||||
|
"medium" &&
|
||||||
|
"text-yellow-600 dark:text-yellow-400",
|
||||||
|
passwordStrength.strength ===
|
||||||
|
"weak" &&
|
||||||
|
"text-red-600 dark:text-red-400"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
`passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}`
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={
|
||||||
|
passwordStrength.percentage
|
||||||
|
}
|
||||||
|
className="h-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button type="submit" className="w-full">
|
{/* Requirements Checklist */}
|
||||||
{t("createAccount")}
|
<div className="bg-muted rounded-lg p-3 space-y-2">
|
||||||
</Button>
|
<div className="text-sm font-medium text-foreground mb-2">
|
||||||
</form>
|
{t(
|
||||||
</Form>
|
"passwordRequirements"
|
||||||
</CardContent>
|
)}
|
||||||
</Card>
|
</div>
|
||||||
|
<div className="grid grid-cols-1 gap-1.5">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.length ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.length
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementLengthText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.uppercase ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.uppercase
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementUppercaseText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.lowercase ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.lowercase
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementLowercaseText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.number ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.number
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementNumberText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{passwordStrength
|
||||||
|
.requirements
|
||||||
|
.special ? (
|
||||||
|
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||||
|
) : (
|
||||||
|
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-sm",
|
||||||
|
passwordStrength
|
||||||
|
.requirements
|
||||||
|
.special
|
||||||
|
? "text-green-600 dark:text-green-400"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"passwordRequirementSpecialText"
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Only show FormMessage when not showing our custom requirements */}
|
||||||
|
{passwordValue.length === 0 && (
|
||||||
|
<FormMessage />
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="confirmPassword"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<FormLabel>
|
||||||
|
{t("confirmPassword")}
|
||||||
|
</FormLabel>
|
||||||
|
{doPasswordsMatch && (
|
||||||
|
<Check className="h-4 w-4 text-green-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
field.onChange(e);
|
||||||
|
setConfirmPasswordValue(
|
||||||
|
e.target.value
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
doPasswordsMatch &&
|
||||||
|
"border-green-500 focus-visible:ring-green-500",
|
||||||
|
confirmPasswordValue.length >
|
||||||
|
0 &&
|
||||||
|
!doPasswordsMatch &&
|
||||||
|
"border-red-500 focus-visible:ring-red-500"
|
||||||
|
)}
|
||||||
|
autoComplete="new-password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</FormControl>
|
||||||
|
{confirmPasswordValue.length > 0 &&
|
||||||
|
!doPasswordsMatch && (
|
||||||
|
<p className="text-sm text-red-600 mt-1">
|
||||||
|
{t("passwordsDoNotMatch")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{/* Only show FormMessage when field is empty */}
|
||||||
|
{confirmPasswordValue.length === 0 && (
|
||||||
|
<FormMessage />
|
||||||
|
)}
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{build === "saas" && (
|
||||||
|
<>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="agreeToTerms"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={(
|
||||||
|
checked
|
||||||
|
) => {
|
||||||
|
field.onChange(
|
||||||
|
checked
|
||||||
|
);
|
||||||
|
handleTermsChange(
|
||||||
|
checked as boolean
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
<div>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.IAgreeToThe"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/terms-of-service.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.termsOfService"
|
||||||
|
)}{" "}
|
||||||
|
</a>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.and"
|
||||||
|
)}{" "}
|
||||||
|
<a
|
||||||
|
href="https://pangolin.net/privacy-policy.html"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="text-primary hover:underline"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"signUpTerms.privacyPolicy"
|
||||||
|
)}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="marketingEmailConsent"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-start">
|
||||||
|
<FormControl>
|
||||||
|
<Checkbox
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<div className="leading-none">
|
||||||
|
<FormLabel className="text-sm font-normal">
|
||||||
|
{t(
|
||||||
|
"signUpMarketing.keepMeInTheLoop"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full">
|
||||||
|
{t("createAccount")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ export type CommandItem = string | { title: string; command: string };
|
|||||||
|
|
||||||
const PLATFORMS = [
|
const PLATFORMS = [
|
||||||
"unix",
|
"unix",
|
||||||
"windows",
|
|
||||||
"docker",
|
"docker",
|
||||||
"kubernetes",
|
"kubernetes",
|
||||||
"podman",
|
"podman",
|
||||||
"nixos"
|
"nixos",
|
||||||
|
"windows"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type Platform = (typeof PLATFORMS)[number];
|
type Platform = (typeof PLATFORMS)[number];
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { Button } from "./ui/button";
|
|||||||
|
|
||||||
export type CommandItem = string | { title: string; command: string };
|
export type CommandItem = string | { title: string; command: string };
|
||||||
|
|
||||||
const PLATFORMS = ["unix", "windows", "docker"] as const;
|
const PLATFORMS = ["unix", "docker", "windows"] as const;
|
||||||
|
|
||||||
type Platform = (typeof PLATFORMS)[number];
|
type Platform = (typeof PLATFORMS)[number];
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ export function OlmInstallCommands({
|
|||||||
All: [
|
All: [
|
||||||
{
|
{
|
||||||
title: t("install"),
|
title: t("install"),
|
||||||
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | bash`
|
command: `curl -fsSL https://static.pangolin.net/get-cli.sh | sudo bash`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("run"),
|
title: t("run"),
|
||||||
@@ -51,24 +51,12 @@ export function OlmInstallCommands({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
windows: {
|
|
||||||
x64: [
|
|
||||||
{
|
|
||||||
title: t("install"),
|
|
||||||
command: `curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t("run"),
|
|
||||||
command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
docker: {
|
docker: {
|
||||||
"Docker Compose": [
|
"Docker Compose": [
|
||||||
`services:
|
`services:
|
||||||
olm:
|
pangolin-cli:
|
||||||
image: fosrl/olm
|
image: fosrl/pangolin-cli
|
||||||
container_name: olm
|
container_name: pangolin-cli
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
network_mode: host
|
network_mode: host
|
||||||
cap_add:
|
cap_add:
|
||||||
@@ -77,11 +65,24 @@ export function OlmInstallCommands({
|
|||||||
- /dev/net/tun:/dev/net/tun
|
- /dev/net/tun:/dev/net/tun
|
||||||
environment:
|
environment:
|
||||||
- PANGOLIN_ENDPOINT=${endpoint}
|
- PANGOLIN_ENDPOINT=${endpoint}
|
||||||
- OLM_ID=${id}
|
- CLIENT_ID=${id}
|
||||||
- OLM_SECRET=${secret}`
|
- CLIENT_SECRET=${secret}`
|
||||||
],
|
],
|
||||||
"Docker Run": [
|
"Docker Run": [
|
||||||
`docker run -dit --network host --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun fosrl/olm --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
`docker run -dit --network host --cap-add NET_ADMIN --device /dev/net/tun:/dev/net/tun fosrl/pangolin-cli up client --id ${id} --secret ${secret} --endpoint ${endpoint} --attach`
|
||||||
|
]
|
||||||
|
},
|
||||||
|
windows: {
|
||||||
|
x64: [
|
||||||
|
{
|
||||||
|
title: t("install"),
|
||||||
|
command: `# Download and run the installer to install Olm first\n
|
||||||
|
curl -o olm.exe -L "https://github.com/fosrl/olm/releases/download/${version}/olm_windows_installer.exe"`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: t("run"),
|
||||||
|
command: `olm.exe --id ${id} --secret ${secret} --endpoint ${endpoint}`
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ export function consumeInternalRedirectPath(): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the full redirect target for an org: either `/${orgId}` or
|
* Returns the full redirect target if a valid internal_redirect was stored
|
||||||
* `/${orgId}${path}` if a valid internal_redirect was stored. Consumes the
|
* (consumes the stored value). Returns null if none was stored or expired.
|
||||||
* stored value.
|
* Paths starting with /auth/ are returned as-is; others are prefixed with orgId.
|
||||||
*/
|
*/
|
||||||
export function getInternalRedirectTarget(orgId: string): string {
|
export function getInternalRedirectTarget(orgId: string): string | null {
|
||||||
const path = consumeInternalRedirectPath();
|
const path = consumeInternalRedirectPath();
|
||||||
return path ? `/${orgId}${path}` : `/${orgId}`;
|
if (!path) return null;
|
||||||
|
return path.startsWith("/auth/") ? path : `/${orgId}${path}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,11 @@ export function pullEnv(): Env {
|
|||||||
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
|
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
|
||||||
? true
|
? true
|
||||||
: false
|
: false
|
||||||
}
|
},
|
||||||
|
identityProviderMode: process.env.IDENTITY_PROVIDER_MODE as
|
||||||
|
| "org"
|
||||||
|
| "global"
|
||||||
|
| undefined
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
||||||
@@ -64,8 +68,6 @@ export function pullEnv(): Env {
|
|||||||
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
||||||
? true
|
? true
|
||||||
: false,
|
: false,
|
||||||
useOrgOnlyIdp:
|
|
||||||
process.env.USE_ORG_ONLY_IDP === "true" ? true : false,
|
|
||||||
disableEnterpriseFeatures:
|
disableEnterpriseFeatures:
|
||||||
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
|
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
|
||||||
? true
|
? true
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export type Env = {
|
|||||||
product_updates: boolean;
|
product_updates: boolean;
|
||||||
new_releases: boolean;
|
new_releases: boolean;
|
||||||
};
|
};
|
||||||
|
identityProviderMode?: "global" | "org";
|
||||||
};
|
};
|
||||||
server: {
|
server: {
|
||||||
externalPort: string;
|
externalPort: string;
|
||||||
@@ -34,7 +35,6 @@ export type Env = {
|
|||||||
hideSupporterKey: boolean;
|
hideSupporterKey: boolean;
|
||||||
usePangolinDns: boolean;
|
usePangolinDns: boolean;
|
||||||
disableProductHelpBanners: boolean;
|
disableProductHelpBanners: boolean;
|
||||||
useOrgOnlyIdp: boolean;
|
|
||||||
disableEnterpriseFeatures: boolean;
|
disableEnterpriseFeatures: boolean;
|
||||||
};
|
};
|
||||||
branding: {
|
branding: {
|
||||||
|
|||||||
Reference in New Issue
Block a user