mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-17 11:06:30 +02:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 776f3ea59f | |||
| c6360a3f25 | |||
| 134758621a | |||
| ca6d06b5a4 | |||
| 0572dc5a12 | |||
| 888c5860ef | |||
| 331cdfd1c1 | |||
| f41ffd95f3 | |||
| 3c79bdb764 | |||
| c32ac607d3 | |||
| 4011ea9fa0 | |||
| d7ebe3a114 | |||
| 53dd5836b5 | |||
| 42b7e8c843 |
+2
-4
@@ -28,9 +28,7 @@ LICENSE
|
||||
CONTRIBUTING.md
|
||||
dist
|
||||
.git
|
||||
server/migrations/
|
||||
migrations/
|
||||
config/
|
||||
build.ts
|
||||
tsconfig.json
|
||||
Dockerfile*
|
||||
drizzle.config.ts
|
||||
tsconfig.json
|
||||
@@ -1 +0,0 @@
|
||||
* @oschwartz10612 @miloschwartz
|
||||
+12
-2
@@ -44,9 +44,19 @@ updates:
|
||||
schedule:
|
||||
interval: "daily"
|
||||
groups:
|
||||
patch-updates:
|
||||
dev-patch-updates:
|
||||
dependency-type: "development"
|
||||
update-types:
|
||||
- "patch"
|
||||
minor-updates:
|
||||
dev-minor-updates:
|
||||
dependency-type: "development"
|
||||
update-types:
|
||||
- "minor"
|
||||
prod-patch-updates:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
- "patch"
|
||||
prod-minor-updates:
|
||||
dependency-type: "production"
|
||||
update-types:
|
||||
- "minor"
|
||||
+60
-117
@@ -1,4 +1,4 @@
|
||||
name: Public CICD Pipeline
|
||||
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.
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
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
|
||||
@@ -62,7 +62,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Monitor storage space
|
||||
run: |
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Monitor storage space
|
||||
run: |
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
@@ -201,10 +201,10 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
@@ -256,7 +256,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Extract tag name
|
||||
id: get-tag
|
||||
@@ -264,9 +264,9 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: 1.25
|
||||
go-version: 1.24
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
@@ -289,17 +289,25 @@ jobs:
|
||||
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 \
|
||||
PANGOLIN_VERSION=${{ env.TAG }} \
|
||||
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }} \
|
||||
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
|
||||
shell: bash
|
||||
make go-build-release
|
||||
|
||||
- name: Upload artifacts from /install/bin
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: install-bin
|
||||
path: install/bin/
|
||||
@@ -331,37 +339,37 @@ jobs:
|
||||
TAG=${{ env.TAG }}
|
||||
MAJOR_TAG=$(echo $TAG | cut -d. -f1)
|
||||
MINOR_TAG=$(echo $TAG | cut -d. -f1,2)
|
||||
|
||||
|
||||
echo "Waiting for multi-arch manifests to be ready..."
|
||||
sleep 30
|
||||
|
||||
|
||||
# Determine if this is an RC release
|
||||
IS_RC="false"
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
if echo "$TAG" | grep -qE "rc[0-9]+$"; then
|
||||
IS_RC="true"
|
||||
fi
|
||||
|
||||
|
||||
if [ "$IS_RC" = "true" ]; then
|
||||
echo "RC release detected - copying version-specific tags only"
|
||||
|
||||
|
||||
# SQLite OSS
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
|
||||
skopeo copy --all --retry-times 3 \
|
||||
docker://$DOCKERHUB_IMAGE:$TAG \
|
||||
docker://$GHCR_IMAGE:$TAG
|
||||
|
||||
|
||||
# PostgreSQL OSS
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG}"
|
||||
skopeo copy --all --retry-times 3 \
|
||||
docker://$DOCKERHUB_IMAGE:postgresql-$TAG \
|
||||
docker://$GHCR_IMAGE:postgresql-$TAG
|
||||
|
||||
|
||||
# SQLite Enterprise
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-${TAG}"
|
||||
skopeo copy --all --retry-times 3 \
|
||||
docker://$DOCKERHUB_IMAGE:ee-$TAG \
|
||||
docker://$GHCR_IMAGE:ee-$TAG
|
||||
|
||||
|
||||
# PostgreSQL Enterprise
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG}"
|
||||
skopeo copy --all --retry-times 3 \
|
||||
@@ -369,7 +377,7 @@ jobs:
|
||||
docker://$GHCR_IMAGE:ee-postgresql-$TAG
|
||||
else
|
||||
echo "Regular release detected - copying all tags (latest, major, minor, full version)"
|
||||
|
||||
|
||||
# SQLite OSS - all tags
|
||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:${TAG_SUFFIX}"
|
||||
@@ -377,7 +385,7 @@ jobs:
|
||||
docker://$DOCKERHUB_IMAGE:$TAG_SUFFIX \
|
||||
docker://$GHCR_IMAGE:$TAG_SUFFIX
|
||||
done
|
||||
|
||||
|
||||
# PostgreSQL OSS - all tags
|
||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG_SUFFIX}"
|
||||
@@ -385,7 +393,7 @@ jobs:
|
||||
docker://$DOCKERHUB_IMAGE:postgresql-$TAG_SUFFIX \
|
||||
docker://$GHCR_IMAGE:postgresql-$TAG_SUFFIX
|
||||
done
|
||||
|
||||
|
||||
# SQLite Enterprise - all tags
|
||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-${TAG_SUFFIX}"
|
||||
@@ -393,7 +401,7 @@ jobs:
|
||||
docker://$DOCKERHUB_IMAGE:ee-$TAG_SUFFIX \
|
||||
docker://$GHCR_IMAGE:ee-$TAG_SUFFIX
|
||||
done
|
||||
|
||||
|
||||
# PostgreSQL Enterprise - all tags
|
||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG_SUFFIX}"
|
||||
@@ -402,12 +410,12 @@ jobs:
|
||||
docker://$GHCR_IMAGE:ee-postgresql-$TAG_SUFFIX
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
echo "All images copied successfully to GHCR!"
|
||||
shell: bash
|
||||
|
||||
- name: Login to GitHub Container Registry (for cosign)
|
||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -415,7 +423,7 @@ jobs:
|
||||
|
||||
- name: Install cosign
|
||||
# cosign is used to sign and verify container images (key and keyless)
|
||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
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,
|
||||
@@ -432,13 +440,9 @@ jobs:
|
||||
issuer="https://token.actions.githubusercontent.com"
|
||||
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
|
||||
IS_RC="false"
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
if echo "$TAG" | grep -qE "rc[0-9]+$"; then
|
||||
IS_RC="true"
|
||||
fi
|
||||
|
||||
@@ -467,92 +471,31 @@ jobs:
|
||||
for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
||||
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
|
||||
echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
TAG_FAILED=false
|
||||
|
||||
# Wrap the entire tag processing in error handling
|
||||
(
|
||||
set -e
|
||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
|
||||
REF="${BASE_IMAGE}@${DIGEST}"
|
||||
echo "Resolved digest: ${REF}"
|
||||
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}"
|
||||
cosign sign --recursive "${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 sign (key) --recursive ${REF}"
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
||||
|
||||
# Retry wrapper for verification to handle registry propagation delays
|
||||
retry_verify() {
|
||||
local cmd="$1"
|
||||
local attempts=6
|
||||
local delay=5
|
||||
local i=1
|
||||
until eval "$cmd"; do
|
||||
if [ $i -ge $attempts ]; then
|
||||
echo "Verification failed after $attempts attempts"
|
||||
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}"
|
||||
cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
echo "==> cosign verify (keyless policy) ${REF}"
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer "${issuer}" \
|
||||
--certificate-identity-regexp "${id_regex}" \
|
||||
"${REF}" -o text
|
||||
|
||||
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
done
|
||||
done
|
||||
|
||||
# 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
|
||||
|
||||
echo "All images signed and verified successfully!"
|
||||
shell: bash
|
||||
|
||||
post-run:
|
||||
@@ -570,7 +513,7 @@ jobs:
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
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
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
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"
|
||||
@@ -21,12 +21,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '22'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
skopeo --version
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
|
||||
- name: Input check
|
||||
run: |
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
uses: aws-actions/configure-aws-credentials@v5
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||
role-duration-seconds: 3600
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: SAAS Pipeline
|
||||
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.
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
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
|
||||
@@ -54,42 +54,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Download MaxMind GeoLite2 databases
|
||||
env:
|
||||
MAXMIND_LICENSE_KEY: ${{ secrets.MAXMIND_LICENSE_KEY }}
|
||||
run: |
|
||||
echo "Downloading MaxMind GeoLite2 databases..."
|
||||
|
||||
# Download GeoLite2-Country
|
||||
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
|
||||
-o GeoLite2-Country.tar.gz
|
||||
|
||||
# Download GeoLite2-ASN
|
||||
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
|
||||
-o GeoLite2-ASN.tar.gz
|
||||
|
||||
# Extract the .mmdb files
|
||||
tar -xzf GeoLite2-Country.tar.gz --strip-components=1 --wildcards '*.mmdb'
|
||||
tar -xzf GeoLite2-ASN.tar.gz --strip-components=1 --wildcards '*.mmdb'
|
||||
|
||||
# Verify files exist
|
||||
if [ ! -f "GeoLite2-Country.mmdb" ]; then
|
||||
echo "ERROR: Failed to download GeoLite2-Country.mmdb"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -f "GeoLite2-ASN.mmdb" ]; then
|
||||
echo "ERROR: Failed to download GeoLite2-ASN.mmdb"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Clean up tar files
|
||||
rm -f GeoLite2-Country.tar.gz GeoLite2-ASN.tar.gz
|
||||
|
||||
echo "MaxMind databases downloaded successfully"
|
||||
ls -lh GeoLite2-*.mmdb
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Monitor storage space
|
||||
run: |
|
||||
@@ -104,7 +69,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
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
|
||||
@@ -145,7 +110,7 @@ jobs:
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v6
|
||||
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
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
with:
|
||||
days-before-stale: 14
|
||||
days-before-close: 14
|
||||
|
||||
@@ -14,12 +14,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '24'
|
||||
node-version: '22'
|
||||
|
||||
- name: Copy config file
|
||||
run: cp config/config.example.yml config/config.yml
|
||||
@@ -34,10 +34,10 @@ jobs:
|
||||
run: npm run set:oss
|
||||
|
||||
- name: Generate database migrations
|
||||
run: npm run db:generate
|
||||
run: npm run db:sqlite:generate
|
||||
|
||||
- name: Apply database migrations
|
||||
run: npm run db:push
|
||||
run: npm run db:sqlite:push
|
||||
|
||||
- name: Test with tsc
|
||||
run: npx tsc --noEmit
|
||||
@@ -62,7 +62,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Copy config file
|
||||
run: cp config/config.example.yml config/config.yml
|
||||
|
||||
- name: Build Docker image sqlite
|
||||
run: make dev-build-sqlite
|
||||
@@ -71,7 +74,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Copy config file
|
||||
run: cp config/config.example.yml config/config.yml
|
||||
|
||||
- name: Build Docker image pg
|
||||
run: make dev-build-pg
|
||||
|
||||
+1
-3
@@ -51,6 +51,4 @@ dynamic/
|
||||
scratch/
|
||||
tsconfig.json
|
||||
hydrateSaas.ts
|
||||
CLAUDE.md
|
||||
drizzle.config.ts
|
||||
server/setup/migrations.ts
|
||||
CLAUDE.md
|
||||
Vendored
+2
-2
@@ -4,7 +4,7 @@
|
||||
},
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"[jsonc]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
@@ -19,4 +19,4 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
}
|
||||
|
||||
+58
-54
@@ -1,51 +1,77 @@
|
||||
# FROM node:24-slim AS base
|
||||
FROM public.ecr.aws/docker/library/node:24-slim AS base
|
||||
FROM node:24-alpine AS builder
|
||||
|
||||
# OCI Image Labels - Build Args for dynamic values
|
||||
ARG VERSION="dev"
|
||||
ARG REVISION=""
|
||||
ARG CREATED=""
|
||||
ARG LICENSE="AGPL-3.0"
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
FROM base AS builder-dev
|
||||
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
ARG BUILD=oss
|
||||
ARG DATABASE=sqlite
|
||||
|
||||
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
|
||||
npm run set:$DATABASE && \
|
||||
npm run set:$BUILD && \
|
||||
npm run db:generate && \
|
||||
npm run build && \
|
||||
npm run build:cli && \
|
||||
test -f dist/server.mjs
|
||||
# Derive title and description based on BUILD type
|
||||
ARG IMAGE_TITLE="Pangolin"
|
||||
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
|
||||
|
||||
# Create placeholder files for MaxMind databases to avoid COPY errors
|
||||
# Real files should be present for saas builds, placeholders for oss builds
|
||||
RUN touch /app/GeoLite2-Country.mmdb /app/GeoLite2-ASN.mmdb
|
||||
RUN apk add --no-cache curl tzdata python3 make g++
|
||||
|
||||
FROM base AS builder
|
||||
# COPY package.json package-lock.json ./
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
RUN npm ci --omit=dev
|
||||
COPY . .
|
||||
|
||||
# FROM node:24-slim AS runner
|
||||
FROM public.ecr.aws/docker/library/node:24-slim AS runner
|
||||
RUN echo "export * from \"./$DATABASE\";" > server/db/index.ts
|
||||
RUN echo "export const driver: \"pg\" | \"sqlite\" = \"$DATABASE\";" >> server/db/index.ts
|
||||
|
||||
RUN echo "export const build = \"$BUILD\" as \"saas\" | \"enterprise\" | \"oss\";" > server/build.ts
|
||||
|
||||
# Copy the appropriate TypeScript configuration based on build type
|
||||
RUN if [ "$BUILD" = "oss" ]; then cp tsconfig.oss.json tsconfig.json; \
|
||||
elif [ "$BUILD" = "saas" ]; then cp tsconfig.saas.json tsconfig.json; \
|
||||
elif [ "$BUILD" = "enterprise" ]; then cp tsconfig.enterprise.json tsconfig.json; \
|
||||
fi
|
||||
|
||||
# if the build is oss then remove the server/private directory
|
||||
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi
|
||||
|
||||
RUN if [ "$DATABASE" = "pg" ]; then npx drizzle-kit generate --dialect postgresql --schema ./server/db/pg/schema --out init; else npx drizzle-kit generate --dialect $DATABASE --schema ./server/db/$DATABASE/schema --out init; fi
|
||||
|
||||
RUN mkdir -p dist
|
||||
RUN npm run next:build
|
||||
RUN node esbuild.mjs -e server/index.ts -o dist/server.mjs -b $BUILD
|
||||
RUN if [ "$DATABASE" = "pg" ]; then \
|
||||
node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs; \
|
||||
else \
|
||||
node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs; \
|
||||
fi
|
||||
|
||||
# test to make sure the build output is there and error if not
|
||||
RUN test -f dist/server.mjs
|
||||
|
||||
RUN npm run build:cli
|
||||
|
||||
# Prune dev dependencies and clean up to prepare for copy to runner
|
||||
RUN npm prune --omit=dev && npm cache clean --force
|
||||
|
||||
FROM node:24-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y curl tzdata && rm -rf /var/lib/apt/lists/*
|
||||
# Only curl and tzdata needed at runtime - no build tools!
|
||||
RUN apk add --no-cache curl tzdata
|
||||
|
||||
# Copy pre-built node_modules from builder (already pruned to production only)
|
||||
# This includes the compiled native modules like better-sqlite3
|
||||
COPY --from=builder /app/node_modules ./node_modules
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
COPY --from=builder-dev /app/.next/standalone ./
|
||||
COPY --from=builder-dev /app/.next/static ./.next/static
|
||||
COPY --from=builder-dev /app/dist ./dist
|
||||
COPY --from=builder-dev /app/server/migrations ./dist/init
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/init ./dist/init
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
|
||||
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
|
||||
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
|
||||
@@ -55,28 +81,6 @@ COPY server/db/ios_models.json ./dist/ios_models.json
|
||||
COPY server/db/mac_models.json ./dist/mac_models.json
|
||||
COPY public ./public
|
||||
|
||||
# Copy MaxMind databases for SaaS builds
|
||||
ARG BUILD=oss
|
||||
|
||||
RUN mkdir -p ./maxmind
|
||||
|
||||
# Copy MaxMind databases (placeholders exist for oss builds, real files for saas)
|
||||
COPY --from=builder-dev /app/GeoLite2-Country.mmdb ./maxmind/GeoLite2-Country.mmdb
|
||||
COPY --from=builder-dev /app/GeoLite2-ASN.mmdb ./maxmind/GeoLite2-ASN.mmdb
|
||||
|
||||
# Remove MaxMind databases for non-saas builds (keep only for saas)
|
||||
RUN if [ "$BUILD" != "saas" ]; then rm -rf ./maxmind; fi
|
||||
|
||||
# OCI Image Labels - Build Args for dynamic values
|
||||
ARG VERSION="dev"
|
||||
ARG REVISION=""
|
||||
ARG CREATED=""
|
||||
ARG LICENSE="AGPL-3.0"
|
||||
|
||||
# Derive title and description based on BUILD type
|
||||
ARG IMAGE_TITLE="Pangolin"
|
||||
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
|
||||
|
||||
# OCI Image Labels
|
||||
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
|
||||
LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \
|
||||
|
||||
+1
-3
@@ -1,9 +1,7 @@
|
||||
FROM node:24-alpine
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache python3 make g++
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
# Install dependencies
|
||||
|
||||
@@ -37,51 +37,35 @@
|
||||
|
||||
<p align="center">
|
||||
<strong>
|
||||
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
||||
Start testing Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
|
||||
</strong>
|
||||
</p>
|
||||
|
||||
Pangolin is an open-source, identity-based remote access platform built on WireGuard that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources with NAT traversal, all with granular access controls.
|
||||
Pangolin is an open-source, identity-based remote access platform built on WireGuard that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources, all with zero-trust security and granular access control.
|
||||
|
||||
## Installation
|
||||
|
||||
- Get started for free with [Pangolin Cloud](https://app.pangolin.net/).
|
||||
- Or, check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to self-host Pangolin.
|
||||
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
|
||||
- Check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to install and set up Pangolin.
|
||||
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
|
||||
|
||||
<img src="public/screenshots/hero.png" alt="Pangolin" width="100%" />
|
||||
<img src="public/screenshots/hero.png" />
|
||||
|
||||
## Deployment Options
|
||||
|
||||
- **Pangolin Cloud** - Fully managed service - no infrastructure required.
|
||||
- **Self-Host: Community Edition** - Free, open source, and licensed under AGPL-3.
|
||||
- **Self-Host: Enterprise Edition** - Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses making less than \$100K USD gross annual revenue.
|
||||
| <img width=500 /> | Description |
|
||||
|-----------------|--------------|
|
||||
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
|
||||
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |
|
||||
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing — no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/nodes) and connect to our control plane. |
|
||||
|
||||
## Key Features
|
||||
|
||||
### Connect remote networks with sites and NAT traversal
|
||||
|
||||
Pangolin's site connectors provide gateways into networks so you can access any networked resources. Sites use outbound tunnels and intelligent NAT traversal to make networks behind restrictive firewalls available for authorized access without public IPs or open ports. Easily deploy a site as a binary or container on any platform.
|
||||
|
||||
<img src="public/screenshots/sites.png" alt="Sites" width="100%" />
|
||||
|
||||
### Browser-based reverse proxy access
|
||||
|
||||
Expose web applications through identity and context-aware tunneled reverse proxies. Users access applications through any web browser with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet.
|
||||
|
||||
<img src="public/clip.gif" alt="Reverse proxy access" width="100%" />
|
||||
|
||||
### Client-based private resource access
|
||||
|
||||
Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. Add redundancy by routing traffic through multiple connectors in your network.
|
||||
|
||||
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
|
||||
|
||||
### Give users and roles access to resources
|
||||
|
||||
Use Pangolin's built in users or bring your own identity provider and set up role based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define.
|
||||
|
||||
<img src="public/screenshots/users.png" alt="Users from identity provider with roles" width="100%" />
|
||||
| <img width=500 /> | <img width=500 /> |
|
||||
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
|
||||
| **Connect remote networks with sites**<br /><br />Pangolin's lightweight site connectors create secure tunnels from remote networks without requiring public IP addresses or open ports. Sites make any network anywhere available for authorized access. | <img src="public/screenshots/sites.png" width=500 /><tr></tr> |
|
||||
| **Browser-based reverse proxy access**<br /><br />Expose web applications through identity and context-aware tunneled reverse proxies. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet. Users access applications through any web browser with authentication and granular access control. | <img src="public/clip.gif" width=500 /><tr></tr> |
|
||||
| **Client-based private resource access**<br /><br />Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. | <img src="public/screenshots/private-resources.png" width=500 /><tr></tr> |
|
||||
| **Zero-trust granular access**<br /><br />Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications and services you explicitly define, reducing security risk and attack surface. | <img src="public/screenshots/user-devices.png" width=500 /><tr></tr> |
|
||||
|
||||
## Download Clients
|
||||
|
||||
@@ -90,21 +74,20 @@ Download the Pangolin client for your platform:
|
||||
- [Mac](https://pangolin.net/downloads/mac)
|
||||
- [Windows](https://pangolin.net/downloads/windows)
|
||||
- [Linux](https://pangolin.net/downloads/linux)
|
||||
- [iOS](https://pangolin.net/downloads/ios)
|
||||
- [Android](https://pangolin.net/downloads/android)
|
||||
|
||||
## Get Started
|
||||
|
||||
### Sign up now
|
||||
|
||||
Create a free account at [app.pangolin.net](https://app.pangolin.net) to get started with Pangolin Cloud.
|
||||
|
||||
### Check out the docs
|
||||
|
||||
We encourage everyone to read the full documentation first, which is
|
||||
available at [docs.pangolin.net](https://docs.pangolin.net). This README provides only a very brief subset of
|
||||
the docs to illustrate some basic ideas.
|
||||
|
||||
### Sign up and try now
|
||||
|
||||
For Pangolin's managed service, you will first need to create an account at
|
||||
[app.pangolin.net](https://app.pangolin.net). We have a generous free tier to get started.
|
||||
|
||||
## Licensing
|
||||
|
||||
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl.html). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
|
||||
@@ -112,3 +95,7 @@ Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License
|
||||
## Contributions
|
||||
|
||||
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
|
||||
|
||||
---
|
||||
|
||||
WireGuard® is a registered trademark of Jason A. Donenfeld.
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
If you discover a security vulnerability, please follow the steps below to responsibly disclose it to us:
|
||||
|
||||
1. **Do not create a public GitHub issue or discussion post.** This could put the security of other users at risk.
|
||||
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) with the following information:
|
||||
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) or send a **private** message to a maintainer on [Discord](https://discord.gg/HCJR8Xhme4). Include:
|
||||
|
||||
- Description and location of the vulnerability.
|
||||
- Potential impact of the vulnerability.
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
import requests
|
||||
import yaml
|
||||
import json
|
||||
import base64
|
||||
|
||||
# The file path for the YAML file to be read
|
||||
# You can change this to the path of your YAML file
|
||||
YAML_FILE_PATH = 'blueprint.yaml'
|
||||
|
||||
# The API endpoint and headers from the curl request
|
||||
API_URL = 'http://api.pangolin.net/v1/org/test/blueprint'
|
||||
HEADERS = {
|
||||
'accept': '*/*',
|
||||
'Authorization': 'Bearer <your_token_here>',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
def convert_and_send(file_path, url, headers):
|
||||
"""
|
||||
Reads a YAML file, converts its content to a JSON payload,
|
||||
and sends it via a PUT request to a specified URL.
|
||||
"""
|
||||
try:
|
||||
# Read the YAML file content
|
||||
with open(file_path, 'r') as file:
|
||||
yaml_content = file.read()
|
||||
|
||||
# Parse the YAML string to a Python dictionary
|
||||
# This will be used to ensure the YAML is valid before sending
|
||||
parsed_yaml = yaml.safe_load(yaml_content)
|
||||
|
||||
# convert the parsed YAML to a JSON string
|
||||
json_payload = json.dumps(parsed_yaml)
|
||||
print("Converted JSON payload:")
|
||||
print(json_payload)
|
||||
|
||||
# Encode the JSON string to Base64
|
||||
encoded_json = base64.b64encode(json_payload.encode('utf-8')).decode('utf-8')
|
||||
|
||||
# Create the final payload with the base64 encoded data
|
||||
final_payload = {
|
||||
"blueprint": encoded_json
|
||||
}
|
||||
|
||||
print("Sending the following Base64 encoded JSON payload:")
|
||||
print(final_payload)
|
||||
print("-" * 20)
|
||||
|
||||
# Make the PUT request with the base64 encoded payload
|
||||
response = requests.put(url, headers=headers, json=final_payload)
|
||||
|
||||
# Print the API response for debugging
|
||||
print(f"API Response Status Code: {response.status_code}")
|
||||
print("API Response Content:")
|
||||
print(response.text)
|
||||
|
||||
# Raise an exception for bad status codes (4xx or 5xx)
|
||||
response.raise_for_status()
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"Error: The file '{file_path}' was not found.")
|
||||
except yaml.YAMLError as e:
|
||||
print(f"Error parsing YAML file: {e}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"An error occurred during the API request: {e}")
|
||||
except Exception as e:
|
||||
print(f"An unexpected error occurred: {e}")
|
||||
|
||||
# Run the function
|
||||
if __name__ == "__main__":
|
||||
convert_and_send(YAML_FILE_PATH, API_URL, HEADERS)
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
client-resources:
|
||||
client-resource-nice-id-uno:
|
||||
name: this is my resource
|
||||
protocol: tcp
|
||||
proxy-port: 3001
|
||||
hostname: localhost
|
||||
internal-port: 3000
|
||||
site: lively-yosemite-toad
|
||||
client-resource-nice-id-duce:
|
||||
name: this is my resource
|
||||
protocol: udp
|
||||
proxy-port: 3000
|
||||
hostname: localhost
|
||||
internal-port: 3000
|
||||
site: lively-yosemite-toad
|
||||
|
||||
proxy-resources:
|
||||
resource-nice-id-uno:
|
||||
name: this is my resource
|
||||
protocol: http
|
||||
full-domain: duce.test.example.com
|
||||
host-header: example.com
|
||||
tls-server-name: example.com
|
||||
# auth:
|
||||
# pincode: 123456
|
||||
# password: sadfasdfadsf
|
||||
# sso-enabled: true
|
||||
# sso-roles:
|
||||
# - Member
|
||||
# sso-users:
|
||||
# - owen@pangolin.net
|
||||
# whitelist-users:
|
||||
# - owen@pangolin.net
|
||||
# auto-login-idp: 1
|
||||
headers:
|
||||
- name: X-Example-Header
|
||||
value: example-value
|
||||
- name: X-Another-Header
|
||||
value: another-value
|
||||
rules:
|
||||
- action: allow
|
||||
match: ip
|
||||
value: 1.1.1.1
|
||||
- action: deny
|
||||
match: cidr
|
||||
value: 2.2.2.2/32
|
||||
- action: pass
|
||||
match: path
|
||||
value: /admin
|
||||
targets:
|
||||
- site: lively-yosemite-toad
|
||||
path: /path
|
||||
pathMatchType: prefix
|
||||
hostname: localhost
|
||||
method: http
|
||||
port: 8000
|
||||
- site: slim-alpine-chipmunk
|
||||
hostname: localhost
|
||||
path: /yoman
|
||||
pathMatchType: exact
|
||||
method: http
|
||||
port: 8001
|
||||
resource-nice-id-duce:
|
||||
name: this is other resource
|
||||
protocol: tcp
|
||||
proxy-port: 3000
|
||||
targets:
|
||||
- site: lively-yosemite-toad
|
||||
hostname: localhost
|
||||
port: 3000
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: Create API Key
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/api-key
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"isRoot": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Delete API Key
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
delete {
|
||||
url: http://localhost:3000/api/v1/api-key/dm47aacqxxn3ubj
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: List API Key Actions
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/actions
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: List Org API Keys
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/org/home-lab/api-keys
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: List Root API Keys
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/root/api-keys
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: Set API Key Actions
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/actions
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"actionIds": ["listSites"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: Set API Key Orgs
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/api-key/ex0izu2c37fjz9x/orgs
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"orgIds": ["home-lab"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
meta {
|
||||
name: API Keys
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
meta {
|
||||
name: 2fa-disable
|
||||
type: http
|
||||
seq: 6
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/2fa/disable
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"password": "aaaaa-1A",
|
||||
"code": "377289"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: 2fa-enable
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/2fa/enable
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"code": "374138"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: 2fa-request
|
||||
type: http
|
||||
seq: 5
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/2fa/request
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"password": "aaaaa-1A"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
meta {
|
||||
name: change-password
|
||||
type: http
|
||||
seq: 9
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/change-password
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"oldPassword": "",
|
||||
"newPassword": ""
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
meta {
|
||||
name: login
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/login
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "admin@fosrl.io",
|
||||
"password": "Password123!"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: logout
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:4000/api/v1/auth/logout
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: reset-password-request
|
||||
type: http
|
||||
seq: 10
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/reset-password/request
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "milo@pangolin.net"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
meta {
|
||||
name: reset-password
|
||||
type: http
|
||||
seq: 11
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/reset-password
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"token": "3uhsbom72dwdhboctwrtntyd6jrlg4jtf5oaxy4k",
|
||||
"newPassword": "aaaaa-1A",
|
||||
"code": "6irqCGR3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
meta {
|
||||
name: signup
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/auth/signup
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"email": "numbat@pangolin.net",
|
||||
"password": "Password123!"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: verify-email-request
|
||||
type: http
|
||||
seq: 8
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/verify-email/request
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
meta {
|
||||
name: verify-email
|
||||
type: http
|
||||
seq: 7
|
||||
}
|
||||
|
||||
post {
|
||||
url: http://localhost:3000/api/v1/auth/verify-email
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"code": "50317187"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: verify-user
|
||||
type: http
|
||||
seq: 4
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3001/api/v1/badger/verify-user?sessionId=mb52273jkb6t3oys2bx6ur5x7rcrkl26c7warg3e
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
sessionId: mb52273jkb6t3oys2bx6ur5x7rcrkl26c7warg3e
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
meta {
|
||||
name: createClient
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/site/1/client
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"siteId": 1,
|
||||
"name": "test",
|
||||
"type": "olm",
|
||||
"subnet": "100.90.129.4/30",
|
||||
"olmId": "029yzunhx6nh3y5",
|
||||
"secret": "l0ymp075y3d4rccb25l6sqpgar52k09etunui970qq5gj7x6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: pickClientDefaults
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/site/1/pick-client-defaults
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
meta {
|
||||
name: Create OIDC Provider
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/org/home-lab/idp/oidc
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"clientId": "JJoSvHCZcxnXT2sn6CObj6a21MuKNRXs3kN5wbys",
|
||||
"clientSecret": "2SlGL2wOGgMEWLI9yUuMAeFxre7qSNJVnXMzyepdNzH1qlxYnC4lKhhQ6a157YQEkYH3vm40KK4RCqbYiF8QIweuPGagPX3oGxEj2exwutoXFfOhtq4hHybQKoFq01Z3",
|
||||
"authUrl": "http://localhost:9000/application/o/authorize/",
|
||||
"tokenUrl": "http://localhost:9000/application/o/token/",
|
||||
"scopes": ["email", "openid", "profile"],
|
||||
"userIdentifier": "email"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Generate OIDC URL
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
meta {
|
||||
name: IDP
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Traefik Config
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3001/api/v1/traefik-config
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
meta {
|
||||
name: Internal
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Create Newt
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/newt
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
meta {
|
||||
name: Get Token
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/auth/newt/get-token
|
||||
body: json
|
||||
auth: none
|
||||
}
|
||||
|
||||
body:json {
|
||||
{
|
||||
"newtId": "o0d4rdxq3stnz7b",
|
||||
"secret": "sy7l09fnaesd03iwrfp9m3qf0ryn19g0zf3dqieaazb4k7vk"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
meta {
|
||||
name: createOlm
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:3000/api/v1/olm
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
meta {
|
||||
name: Olm
|
||||
seq: 15
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Check Id
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/org/checkId
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: listOrgs
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: createRemoteExitNode
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
put {
|
||||
url: http://localhost:4000/api/v1/org/org_i21aifypnlyxur2/remote-exit-node
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: listResourcesByOrg
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: listResourcesBySite
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/site/1/resources?limit=10&offset=0
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
limit: 10
|
||||
offset: 0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Get Site
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/org/test/sites/mexican-mole-lizard-windy
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: listSites
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
meta {
|
||||
name: listTargets
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/resource/web.main.localhost/targets?limit=10&offset=0
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
|
||||
params:query {
|
||||
limit: 10
|
||||
offset: 0
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: Test
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1
|
||||
body: none
|
||||
auth: inherit
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: traefik-config
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3001/api/v1/traefik-config
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: adminListUsers
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
get {
|
||||
url: http://localhost:3000/api/v1/users
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: adminRemoveUser
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
delete {
|
||||
url: http://localhost:3000/api/v1/user/ky5r7ivqs8wc7u4
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
meta {
|
||||
name: getUser
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
get {
|
||||
url:
|
||||
body: none
|
||||
auth: none
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "1",
|
||||
"name": "Pangolin",
|
||||
"type": "collection",
|
||||
"ignore": [
|
||||
"node_modules",
|
||||
".git"
|
||||
],
|
||||
"presets": {
|
||||
"requestType": "http",
|
||||
"requestUrl": "http://localhost:3000/api/v1"
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import { CommandModule } from "yargs";
|
||||
import { db, clients, olms, currentFingerprint, userClients, approvals } from "@server/db";
|
||||
import { eq, and, inArray } from "drizzle-orm";
|
||||
|
||||
type DeleteClientArgs = {
|
||||
orgId: string;
|
||||
niceId: string;
|
||||
};
|
||||
|
||||
export const deleteClient: CommandModule<{}, DeleteClientArgs> = {
|
||||
command: "delete-client",
|
||||
describe:
|
||||
"Delete a client and all associated data (OLMs, current fingerprint, userClients, approvals). Snapshots are preserved.",
|
||||
builder: (yargs) => {
|
||||
return yargs
|
||||
.option("orgId", {
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
describe: "The organization ID"
|
||||
})
|
||||
.option("niceId", {
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
describe: "The client niceId (identifier)"
|
||||
});
|
||||
},
|
||||
handler: async (argv: { orgId: string; niceId: string }) => {
|
||||
try {
|
||||
const { orgId, niceId } = argv;
|
||||
|
||||
console.log(
|
||||
`Deleting client with orgId: ${orgId}, niceId: ${niceId}...`
|
||||
);
|
||||
|
||||
// Find the client
|
||||
const [client] = await db
|
||||
.select()
|
||||
.from(clients)
|
||||
.where(and(eq(clients.orgId, orgId), eq(clients.niceId, niceId)))
|
||||
.limit(1);
|
||||
|
||||
if (!client) {
|
||||
console.error(
|
||||
`Error: Client with orgId "${orgId}" and niceId "${niceId}" not found.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const clientId = client.clientId;
|
||||
console.log(`Found client with clientId: ${clientId}`);
|
||||
|
||||
// Find all OLMs associated with this client
|
||||
const associatedOlms = await db
|
||||
.select()
|
||||
.from(olms)
|
||||
.where(eq(olms.clientId, clientId));
|
||||
|
||||
console.log(`Found ${associatedOlms.length} OLM(s) associated with this client`);
|
||||
|
||||
// Delete in a transaction to ensure atomicity
|
||||
await db.transaction(async (trx) => {
|
||||
// Delete currentFingerprint entries for the associated OLMs
|
||||
// Note: We delete these explicitly before deleting OLMs to ensure
|
||||
// we have control, even though cascade would handle it
|
||||
let fingerprintCount = 0;
|
||||
if (associatedOlms.length > 0) {
|
||||
const olmIds = associatedOlms.map((olm) => olm.olmId);
|
||||
const deletedFingerprints = await trx
|
||||
.delete(currentFingerprint)
|
||||
.where(inArray(currentFingerprint.olmId, olmIds))
|
||||
.returning();
|
||||
fingerprintCount = deletedFingerprints.length;
|
||||
}
|
||||
console.log(`Deleted ${fingerprintCount} current fingerprint(s)`);
|
||||
|
||||
// Delete OLMs
|
||||
// Note: OLMs have onDelete: "set null" for clientId, so we need to delete them explicitly
|
||||
const deletedOlms = await trx
|
||||
.delete(olms)
|
||||
.where(eq(olms.clientId, clientId))
|
||||
.returning();
|
||||
console.log(`Deleted ${deletedOlms.length} OLM(s)`);
|
||||
|
||||
// Delete approvals
|
||||
// Note: Approvals have onDelete: "cascade" but we delete explicitly for clarity
|
||||
const deletedApprovals = await trx
|
||||
.delete(approvals)
|
||||
.where(eq(approvals.clientId, clientId))
|
||||
.returning();
|
||||
console.log(`Deleted ${deletedApprovals.length} approval(s)`);
|
||||
|
||||
// Delete userClients
|
||||
// Note: userClients have onDelete: "cascade" but we delete explicitly for clarity
|
||||
const deletedUserClients = await trx
|
||||
.delete(userClients)
|
||||
.where(eq(userClients.clientId, clientId))
|
||||
.returning();
|
||||
console.log(`Deleted ${deletedUserClients.length} userClient association(s)`);
|
||||
|
||||
// Finally, delete the client itself
|
||||
const deletedClients = await trx
|
||||
.delete(clients)
|
||||
.where(eq(clients.clientId, clientId))
|
||||
.returning();
|
||||
console.log(`Deleted client: ${deletedClients[0]?.name || niceId}`);
|
||||
});
|
||||
|
||||
console.log("\nClient deletion completed successfully!");
|
||||
console.log("\nSummary:");
|
||||
console.log(` - Client: ${niceId} (clientId: ${clientId})`);
|
||||
console.log(` - Olm(s): ${associatedOlms.length}`);
|
||||
console.log(` - Current fingerprints: deleted`);
|
||||
console.log(` - Approvals: deleted`);
|
||||
console.log(` - UserClients: deleted`);
|
||||
console.log(` - Snapshots: preserved (not deleted)`);
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Error deleting client:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
import { CommandModule } from "yargs";
|
||||
import { db, orgs } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { encrypt } from "@server/lib/crypto";
|
||||
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
|
||||
import { generateCA } from "@server/lib/sshCA";
|
||||
import fs from "fs";
|
||||
import yaml from "js-yaml";
|
||||
|
||||
type GenerateOrgCaKeysArgs = {
|
||||
orgId: string;
|
||||
secret?: string;
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
export const generateOrgCaKeys: CommandModule<{}, GenerateOrgCaKeysArgs> = {
|
||||
command: "generate-org-ca-keys",
|
||||
describe:
|
||||
"Generate SSH CA public/private key pair for an organization and store them in the database (private key encrypted with server secret)",
|
||||
builder: (yargs) => {
|
||||
return yargs
|
||||
.option("orgId", {
|
||||
type: "string",
|
||||
demandOption: true,
|
||||
describe: "The organization ID"
|
||||
})
|
||||
.option("secret", {
|
||||
type: "string",
|
||||
describe:
|
||||
"Server secret used to encrypt the CA private key. If omitted, read from config file (config.yml or config.yaml)."
|
||||
})
|
||||
.option("force", {
|
||||
type: "boolean",
|
||||
default: false,
|
||||
describe:
|
||||
"Overwrite existing CA keys for the org if they already exist"
|
||||
});
|
||||
},
|
||||
handler: async (argv: {
|
||||
orgId: string;
|
||||
secret?: string;
|
||||
force?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
const { orgId, force } = argv;
|
||||
let secret = argv.secret;
|
||||
|
||||
if (!secret) {
|
||||
const configPath = fs.existsSync(configFilePath1)
|
||||
? configFilePath1
|
||||
: fs.existsSync(configFilePath2)
|
||||
? configFilePath2
|
||||
: null;
|
||||
|
||||
if (!configPath) {
|
||||
console.error(
|
||||
"Error: No server secret provided and config file not found. " +
|
||||
"Expected config.yml or config.yaml in the config directory, or pass --secret."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const configContent = fs.readFileSync(configPath, "utf8");
|
||||
const config = yaml.load(configContent) as {
|
||||
server?: { secret?: string };
|
||||
};
|
||||
|
||||
if (!config?.server?.secret) {
|
||||
console.error(
|
||||
"Error: No server.secret in config file. Pass --secret or set server.secret in config."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
secret = config.server.secret;
|
||||
}
|
||||
|
||||
const [org] = await db
|
||||
.select({
|
||||
orgId: orgs.orgId,
|
||||
sshCaPrivateKey: orgs.sshCaPrivateKey,
|
||||
sshCaPublicKey: orgs.sshCaPublicKey
|
||||
})
|
||||
.from(orgs)
|
||||
.where(eq(orgs.orgId, orgId))
|
||||
.limit(1);
|
||||
|
||||
if (!org) {
|
||||
console.error(`Error: Organization with orgId "${orgId}" not found.`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (org.sshCaPrivateKey != null || org.sshCaPublicKey != null) {
|
||||
if (!force) {
|
||||
console.error(
|
||||
"Error: This organization already has CA keys. Use --force to overwrite."
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const ca = generateCA(`pangolin-ssh-ca-${orgId}`);
|
||||
const encryptedPrivateKey = encrypt(ca.privateKeyPem, secret);
|
||||
|
||||
await db
|
||||
.update(orgs)
|
||||
.set({
|
||||
sshCaPrivateKey: encryptedPrivateKey,
|
||||
sshCaPublicKey: ca.publicKeyOpenSSH
|
||||
})
|
||||
.where(eq(orgs.orgId, orgId));
|
||||
|
||||
console.log("SSH CA keys generated and stored for org:", orgId);
|
||||
console.log("\nPublic key (OpenSSH format):");
|
||||
console.log(ca.publicKeyOpenSSH);
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error("Error generating org CA keys:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -7,8 +7,6 @@ import { resetUserSecurityKeys } from "@cli/commands/resetUserSecurityKeys";
|
||||
import { clearExitNodes } from "./commands/clearExitNodes";
|
||||
import { rotateServerSecret } from "./commands/rotateServerSecret";
|
||||
import { clearLicenseKeys } from "./commands/clearLicenseKeys";
|
||||
import { deleteClient } from "./commands/deleteClient";
|
||||
import { generateOrgCaKeys } from "./commands/generateOrgCaKeys";
|
||||
|
||||
yargs(hideBin(process.argv))
|
||||
.scriptName("pangctl")
|
||||
@@ -17,7 +15,5 @@ yargs(hideBin(process.argv))
|
||||
.command(clearExitNodes)
|
||||
.command(rotateServerSecret)
|
||||
.command(clearLicenseKeys)
|
||||
.command(deleteClient)
|
||||
.command(generateOrgCaKeys)
|
||||
.demandCommand()
|
||||
.help().argv;
|
||||
|
||||
+18
-21
@@ -1,30 +1,27 @@
|
||||
# To see all available options, please visit the docs:
|
||||
# https://docs.pangolin.net/
|
||||
|
||||
gerbil:
|
||||
start_port: 51820
|
||||
base_endpoint: "{{.DashboardDomain}}"
|
||||
# https://docs.pangolin.net/self-host/advanced/config-file
|
||||
|
||||
app:
|
||||
dashboard_url: "https://{{.DashboardDomain}}"
|
||||
log_level: "info"
|
||||
telemetry:
|
||||
anonymous_usage: true
|
||||
dashboard_url: http://localhost:3002
|
||||
log_level: debug
|
||||
|
||||
domains:
|
||||
domain1:
|
||||
base_domain: "{{.BaseDomain}}"
|
||||
domain1:
|
||||
base_domain: example.com
|
||||
|
||||
server:
|
||||
secret: "{{.Secret}}"
|
||||
cors:
|
||||
origins: ["https://{{.DashboardDomain}}"]
|
||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
||||
allowed_headers: ["X-CSRF-Token", "Content-Type"]
|
||||
credentials: false
|
||||
secret: my_secret_key
|
||||
|
||||
gerbil:
|
||||
base_endpoint: example.com
|
||||
|
||||
orgs:
|
||||
block_size: 24
|
||||
subnet_group: 100.90.137.0/20
|
||||
|
||||
flags:
|
||||
require_email_verification: false
|
||||
disable_signup_without_invite: true
|
||||
disable_user_create_org: false
|
||||
allow_raw_resources: true
|
||||
require_email_verification: false
|
||||
disable_signup_without_invite: true
|
||||
disable_user_create_org: true
|
||||
allow_raw_resources: true
|
||||
enable_integration_api: true
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
*-journal
|
||||
@@ -21,8 +21,9 @@ http:
|
||||
|
||||
# Next.js router (handles everything except API and WebSocket paths)
|
||||
next-router:
|
||||
rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)"
|
||||
rule: "Host(`{{.DashboardDomain}}`)"
|
||||
service: next-service
|
||||
priority: 10
|
||||
entryPoints:
|
||||
- websecure
|
||||
middlewares:
|
||||
@@ -34,6 +35,7 @@ http:
|
||||
api-router:
|
||||
rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)"
|
||||
service: api-service
|
||||
priority: 100
|
||||
entryPoints:
|
||||
- websecure
|
||||
middlewares:
|
||||
@@ -51,12 +53,3 @@ http:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://pangolin:3000" # API/WebSocket server
|
||||
|
||||
tcp:
|
||||
serversTransports:
|
||||
pp-transport-v1:
|
||||
proxyProtocol:
|
||||
version: 1
|
||||
pp-transport-v2:
|
||||
proxyProtocol:
|
||||
version: 2
|
||||
|
||||
@@ -3,52 +3,32 @@ api:
|
||||
dashboard: true
|
||||
|
||||
providers:
|
||||
http:
|
||||
endpoint: "http://pangolin:3001/api/v1/traefik-config"
|
||||
pollInterval: "5s"
|
||||
file:
|
||||
filename: "/etc/traefik/dynamic_config.yml"
|
||||
directory: "/var/dynamic"
|
||||
watch: true
|
||||
|
||||
experimental:
|
||||
plugins:
|
||||
badger:
|
||||
moduleName: "github.com/fosrl/badger"
|
||||
version: "{{.BadgerVersion}}"
|
||||
version: "v1.3.0"
|
||||
|
||||
log:
|
||||
level: "INFO"
|
||||
level: "DEBUG"
|
||||
format: "common"
|
||||
maxSize: 100
|
||||
maxBackups: 3
|
||||
maxAge: 3
|
||||
compress: true
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
email: "{{.LetsEncryptEmail}}"
|
||||
storage: "/letsencrypt/acme.json"
|
||||
caServer: "https://acme-v02.api.letsencrypt.org/directory"
|
||||
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
websecure:
|
||||
address: ":443"
|
||||
address: ":9443"
|
||||
transport:
|
||||
respondingTimeouts:
|
||||
readTimeout: "30m"
|
||||
http:
|
||||
tls:
|
||||
certResolver: "letsencrypt"
|
||||
encodedCharacters:
|
||||
allowEncodedSlash: true
|
||||
allowEncodedQuestionMark: true
|
||||
|
||||
serversTransport:
|
||||
insecureSkipVerify: true
|
||||
|
||||
ping:
|
||||
entryPoint: "web"
|
||||
|
||||
@@ -4,12 +4,6 @@ services:
|
||||
image: fosrl/pangolin:latest
|
||||
container_name: pangolin
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1g
|
||||
reservations:
|
||||
memory: 256m
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
healthcheck:
|
||||
|
||||
+2
-8
@@ -6,12 +6,6 @@ import path from "path";
|
||||
import fs from "fs";
|
||||
// import { glob } from "glob";
|
||||
|
||||
// Read default build type from server/build.ts
|
||||
let build = "oss";
|
||||
const buildFile = fs.readFileSync(path.resolve("server/build.ts"), "utf8");
|
||||
const m = buildFile.match(/export\s+const\s+build\s*=\s*["'](oss|saas|enterprise)["']/);
|
||||
if (m) build = m[1];
|
||||
|
||||
const banner = `
|
||||
// patch __dirname
|
||||
// import { fileURLToPath } from "url";
|
||||
@@ -43,7 +37,7 @@ const argv = yargs(hideBin(process.argv))
|
||||
describe: "Build type (oss, saas, enterprise)",
|
||||
type: "string",
|
||||
choices: ["oss", "saas", "enterprise"],
|
||||
default: build
|
||||
default: "oss"
|
||||
})
|
||||
.help()
|
||||
.alias("help", "h").argv;
|
||||
@@ -281,7 +275,7 @@ esbuild
|
||||
})
|
||||
],
|
||||
sourcemap: "inline",
|
||||
target: "node24"
|
||||
target: "node22"
|
||||
})
|
||||
.then((result) => {
|
||||
// Check if there were any errors in the build result
|
||||
|
||||
+34
-17
@@ -1,24 +1,41 @@
|
||||
all: go-build-release
|
||||
|
||||
# Build with version injection via ldflags
|
||||
# Versions can be passed via: make go-build-release PANGOLIN_VERSION=x.x.x GERBIL_VERSION=x.x.x BADGER_VERSION=x.x.x
|
||||
# Or fetched automatically if not provided (requires curl and jq)
|
||||
|
||||
PANGOLIN_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name')
|
||||
GERBIL_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name')
|
||||
BADGER_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name')
|
||||
|
||||
LDFLAGS = -X main.pangolinVersion=$(PANGOLIN_VERSION) \
|
||||
-X main.gerbilVersion=$(GERBIL_VERSION) \
|
||||
-X main.badgerVersion=$(BADGER_VERSION)
|
||||
all: update-versions go-build-release put-back
|
||||
dev-all: dev-update-versions dev-build dev-clean
|
||||
|
||||
go-build-release:
|
||||
@echo "Building with versions - Pangolin: $(PANGOLIN_VERSION), Gerbil: $(GERBIL_VERSION), Badger: $(BADGER_VERSION)"
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/installer_linux_amd64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o bin/installer_linux_arm64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/installer_linux_amd64
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/installer_linux_arm64
|
||||
|
||||
clean:
|
||||
rm -f bin/installer_linux_amd64
|
||||
rm -f bin/installer_linux_arm64
|
||||
|
||||
.PHONY: all go-build-release clean
|
||||
update-versions:
|
||||
@echo "Fetching latest versions..."
|
||||
cp main.go main.go.bak && \
|
||||
$(MAKE) dev-update-versions
|
||||
|
||||
put-back:
|
||||
mv main.go.bak main.go
|
||||
|
||||
dev-update-versions:
|
||||
if [ -z "$(tag)" ]; then \
|
||||
PANGOLIN_VERSION=$$(curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name'); \
|
||||
else \
|
||||
PANGOLIN_VERSION=$(tag); \
|
||||
fi && \
|
||||
GERBIL_VERSION=$$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') && \
|
||||
BADGER_VERSION=$$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') && \
|
||||
echo "Latest versions - Pangolin: $$PANGOLIN_VERSION, Gerbil: $$GERBIL_VERSION, Badger: $$BADGER_VERSION" && \
|
||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$$PANGOLIN_VERSION\"/" main.go && \
|
||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$$GERBIL_VERSION\"/" main.go && \
|
||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$$BADGER_VERSION\"/" main.go && \
|
||||
echo "Updated main.go with latest versions"
|
||||
|
||||
dev-build: go-build-release
|
||||
|
||||
dev-clean:
|
||||
@echo "Restoring version values ..."
|
||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"replaceme\"/" main.go && \
|
||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"replaceme\"/" main.go && \
|
||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"replaceme\"/" main.go
|
||||
@echo "Restored version strings in main.go"
|
||||
|
||||
+26
-25
@@ -99,6 +99,11 @@ func ReadAppConfig(configPath string) (*AppConfigValues, error) {
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// findPattern finds the start of a pattern in a string
|
||||
func findPattern(s, pattern string) int {
|
||||
return bytes.Index([]byte(s), []byte(pattern))
|
||||
}
|
||||
|
||||
func copyDockerService(sourceFile, destFile, serviceName string) error {
|
||||
// Read source file
|
||||
sourceData, err := os.ReadFile(sourceFile)
|
||||
@@ -113,19 +118,19 @@ func copyDockerService(sourceFile, destFile, serviceName string) error {
|
||||
}
|
||||
|
||||
// Parse source Docker Compose YAML
|
||||
var sourceCompose map[string]any
|
||||
var sourceCompose map[string]interface{}
|
||||
if err := yaml.Unmarshal(sourceData, &sourceCompose); err != nil {
|
||||
return fmt.Errorf("error parsing source Docker Compose file: %w", err)
|
||||
}
|
||||
|
||||
// Parse destination Docker Compose YAML
|
||||
var destCompose map[string]any
|
||||
var destCompose map[string]interface{}
|
||||
if err := yaml.Unmarshal(destData, &destCompose); err != nil {
|
||||
return fmt.Errorf("error parsing destination Docker Compose file: %w", err)
|
||||
}
|
||||
|
||||
// Get services section from source
|
||||
sourceServices, ok := sourceCompose["services"].(map[string]any)
|
||||
sourceServices, ok := sourceCompose["services"].(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("services section not found in source file or has invalid format")
|
||||
}
|
||||
@@ -137,10 +142,10 @@ func copyDockerService(sourceFile, destFile, serviceName string) error {
|
||||
}
|
||||
|
||||
// Get or create services section in destination
|
||||
destServices, ok := destCompose["services"].(map[string]any)
|
||||
destServices, ok := destCompose["services"].(map[string]interface{})
|
||||
if !ok {
|
||||
// If services section doesn't exist, create it
|
||||
destServices = make(map[string]any)
|
||||
destServices = make(map[string]interface{})
|
||||
destCompose["services"] = destServices
|
||||
}
|
||||
|
||||
@@ -182,21 +187,17 @@ func backupConfig() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func MarshalYAMLWithIndent(data any, indent int) (resp []byte, err error) {
|
||||
func MarshalYAMLWithIndent(data interface{}, indent int) ([]byte, error) {
|
||||
buffer := new(bytes.Buffer)
|
||||
encoder := yaml.NewEncoder(buffer)
|
||||
encoder.SetIndent(indent)
|
||||
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
err := encoder.Encode(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if cerr := encoder.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
|
||||
defer encoder.Close()
|
||||
return buffer.Bytes(), nil
|
||||
}
|
||||
|
||||
@@ -208,7 +209,7 @@ func replaceInFile(filepath, oldStr, newStr string) error {
|
||||
}
|
||||
|
||||
// Replace the string
|
||||
newContent := strings.ReplaceAll(string(content), oldStr, newStr)
|
||||
newContent := strings.Replace(string(content), oldStr, newStr, -1)
|
||||
|
||||
// Write the modified content back to the file
|
||||
err = os.WriteFile(filepath, []byte(newContent), 0644)
|
||||
@@ -227,28 +228,28 @@ func CheckAndAddTraefikLogVolume(composePath string) error {
|
||||
}
|
||||
|
||||
// Parse YAML into a generic map
|
||||
var compose map[string]any
|
||||
var compose map[string]interface{}
|
||||
if err := yaml.Unmarshal(data, &compose); err != nil {
|
||||
return fmt.Errorf("error parsing compose file: %w", err)
|
||||
}
|
||||
|
||||
// Get services section
|
||||
services, ok := compose["services"].(map[string]any)
|
||||
services, ok := compose["services"].(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("services section not found or invalid")
|
||||
}
|
||||
|
||||
// Get traefik service
|
||||
traefik, ok := services["traefik"].(map[string]any)
|
||||
traefik, ok := services["traefik"].(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("traefik service not found or invalid")
|
||||
}
|
||||
|
||||
// Check volumes
|
||||
logVolume := "./config/traefik/logs:/var/log/traefik"
|
||||
var volumes []any
|
||||
var volumes []interface{}
|
||||
|
||||
if existingVolumes, ok := traefik["volumes"].([]any); ok {
|
||||
if existingVolumes, ok := traefik["volumes"].([]interface{}); ok {
|
||||
// Check if volume already exists
|
||||
for _, v := range existingVolumes {
|
||||
if v.(string) == logVolume {
|
||||
@@ -294,13 +295,13 @@ func MergeYAML(baseFile, overlayFile string) error {
|
||||
}
|
||||
|
||||
// Parse base YAML into a map
|
||||
var baseMap map[string]any
|
||||
var baseMap map[string]interface{}
|
||||
if err := yaml.Unmarshal(baseContent, &baseMap); err != nil {
|
||||
return fmt.Errorf("error parsing base YAML: %v", err)
|
||||
}
|
||||
|
||||
// Parse overlay YAML into a map
|
||||
var overlayMap map[string]any
|
||||
var overlayMap map[string]interface{}
|
||||
if err := yaml.Unmarshal(overlayContent, &overlayMap); err != nil {
|
||||
return fmt.Errorf("error parsing overlay YAML: %v", err)
|
||||
}
|
||||
@@ -323,8 +324,8 @@ func MergeYAML(baseFile, overlayFile string) error {
|
||||
}
|
||||
|
||||
// mergeMap recursively merges two maps
|
||||
func mergeMap(base, overlay map[string]any) map[string]any {
|
||||
result := make(map[string]any)
|
||||
func mergeMap(base, overlay map[string]interface{}) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
|
||||
// Copy all key-values from base map
|
||||
for k, v := range base {
|
||||
@@ -335,8 +336,8 @@ func mergeMap(base, overlay map[string]any) map[string]any {
|
||||
for k, v := range overlay {
|
||||
// If both maps have the same key and both values are maps, merge recursively
|
||||
if baseVal, ok := base[k]; ok {
|
||||
if baseMap, isBaseMap := baseVal.(map[string]any); isBaseMap {
|
||||
if overlayMap, isOverlayMap := v.(map[string]any); isOverlayMap {
|
||||
if baseMap, isBaseMap := baseVal.(map[string]interface{}); isBaseMap {
|
||||
if overlayMap, isOverlayMap := v.(map[string]interface{}); isOverlayMap {
|
||||
result[k] = mergeMap(baseMap, overlayMap)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -81,19 +81,11 @@ entryPoints:
|
||||
transport:
|
||||
respondingTimeouts:
|
||||
readTimeout: "30m"
|
||||
http3:
|
||||
advertisedPort: 443
|
||||
http:
|
||||
tls:
|
||||
certResolver: "letsencrypt"
|
||||
middlewares:
|
||||
middlewares:
|
||||
- crowdsec@file
|
||||
encodedCharacters:
|
||||
allowEncodedSlash: true
|
||||
allowEncodedQuestionMark: true
|
||||
|
||||
serversTransport:
|
||||
insecureSkipVerify: true
|
||||
|
||||
ping:
|
||||
entryPoint: "web"
|
||||
insecureSkipVerify: true
|
||||
@@ -4,12 +4,6 @@ services:
|
||||
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
|
||||
container_name: pangolin
|
||||
restart: unless-stopped
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 1g
|
||||
reservations:
|
||||
memory: 256m
|
||||
volumes:
|
||||
- ./config:/app/config
|
||||
healthcheck:
|
||||
@@ -38,14 +32,15 @@ services:
|
||||
- 51820:51820/udp
|
||||
- 21820:21820/udp
|
||||
- 443:443
|
||||
- 443:443/udp # For http3 QUIC if desired
|
||||
- 80:80
|
||||
{{end}}
|
||||
traefik:
|
||||
image: docker.io/traefik:v3.6
|
||||
container_name: traefik
|
||||
restart: unless-stopped
|
||||
{{if .InstallGerbil}} network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
|
||||
{{if .InstallGerbil}}
|
||||
network_mode: service:gerbil # Ports appear on the gerbil service
|
||||
{{end}}{{if not .InstallGerbil}}
|
||||
ports:
|
||||
- 443:443
|
||||
- 80:80
|
||||
|
||||
@@ -40,8 +40,6 @@ entryPoints:
|
||||
transport:
|
||||
respondingTimeouts:
|
||||
readTimeout: "30m"
|
||||
http3:
|
||||
advertisedPort: 443
|
||||
http:
|
||||
tls:
|
||||
certResolver: "letsencrypt"
|
||||
|
||||
@@ -144,13 +144,12 @@ func installDocker() error {
|
||||
}
|
||||
|
||||
func startDockerService() error {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
if runtime.GOOS == "linux" {
|
||||
cmd := exec.Command("systemctl", "enable", "--now", "docker")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
case "darwin":
|
||||
} else if runtime.GOOS == "darwin" {
|
||||
// On macOS, Docker is usually started via the Docker Desktop application
|
||||
fmt.Println("Please start Docker Desktop manually on macOS.")
|
||||
return nil
|
||||
@@ -303,7 +302,7 @@ func pullContainers(containerType SupportedContainer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
// startContainers starts the containers using the appropriate command.
|
||||
@@ -326,7 +325,7 @@ func startContainers(containerType SupportedContainer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
// stopContainers stops the containers using the appropriate command.
|
||||
@@ -348,7 +347,7 @@ func stopContainers(containerType SupportedContainer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
// restartContainer restarts a specific container using the appropriate command.
|
||||
@@ -370,5 +369,5 @@ func restartContainer(container string, containerType SupportedContainer) error
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("unsupported container type: %s", containerType)
|
||||
return fmt.Errorf("Unsupported container type: %s", containerType)
|
||||
}
|
||||
|
||||
+10
-19
@@ -27,18 +27,9 @@ func installCrowdsec(config Config) error {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll("config/crowdsec/db", 0755); err != nil {
|
||||
fmt.Printf("Error creating config files: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := os.MkdirAll("config/crowdsec/acquis.d", 0755); err != nil {
|
||||
fmt.Printf("Error creating config files: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := os.MkdirAll("config/traefik/logs", 0755); err != nil {
|
||||
fmt.Printf("Error creating config files: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.MkdirAll("config/crowdsec/db", 0755)
|
||||
os.MkdirAll("config/crowdsec/acquis.d", 0755)
|
||||
os.MkdirAll("config/traefik/logs", 0755)
|
||||
|
||||
if err := copyDockerService("config/crowdsec/docker-compose.yml", "docker-compose.yml", "crowdsec"); err != nil {
|
||||
fmt.Printf("Error copying docker service: %v\n", err)
|
||||
@@ -162,34 +153,34 @@ func CheckAndAddCrowdsecDependency(composePath string) error {
|
||||
}
|
||||
|
||||
// Parse YAML into a generic map
|
||||
var compose map[string]any
|
||||
var compose map[string]interface{}
|
||||
if err := yaml.Unmarshal(data, &compose); err != nil {
|
||||
return fmt.Errorf("error parsing compose file: %w", err)
|
||||
}
|
||||
|
||||
// Get services section
|
||||
services, ok := compose["services"].(map[string]any)
|
||||
services, ok := compose["services"].(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("services section not found or invalid")
|
||||
}
|
||||
|
||||
// Get traefik service
|
||||
traefik, ok := services["traefik"].(map[string]any)
|
||||
traefik, ok := services["traefik"].(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("traefik service not found or invalid")
|
||||
}
|
||||
|
||||
// Get dependencies
|
||||
dependsOn, ok := traefik["depends_on"].(map[string]any)
|
||||
dependsOn, ok := traefik["depends_on"].(map[string]interface{})
|
||||
if ok {
|
||||
// Append the new block for crowdsec
|
||||
dependsOn["crowdsec"] = map[string]any{
|
||||
dependsOn["crowdsec"] = map[string]interface{}{
|
||||
"condition": "service_healthy",
|
||||
}
|
||||
} else {
|
||||
// No dependencies exist, create it
|
||||
traefik["depends_on"] = map[string]any{
|
||||
"crowdsec": map[string]any{
|
||||
traefik["depends_on"] = map[string]interface{}{
|
||||
"crowdsec": map[string]interface{}{
|
||||
"condition": "service_healthy",
|
||||
},
|
||||
}
|
||||
|
||||
+3
-31
@@ -1,38 +1,10 @@
|
||||
module installer
|
||||
|
||||
go 1.25.0
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/huh v1.0.0
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
golang.org/x/term v0.42.0
|
||||
golang.org/x/term v0.38.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/catppuccin/go v0.3.0 // indirect
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.6 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/x/ansi v0.9.3 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sync v0.15.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
)
|
||||
require golang.org/x/sys v0.39.0 // indirect
|
||||
|
||||
+4
-77
@@ -1,80 +1,7 @@
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
|
||||
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
|
||||
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
|
||||
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
|
||||
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
|
||||
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/huh v1.0.0 h1:wOnedH8G4qzJbmhftTqrpppyqHakl/zbbNdXIWJyIxw=
|
||||
github.com/charmbracelet/huh v1.0.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
|
||||
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
|
||||
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
|
||||
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
|
||||
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
|
||||
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
|
||||
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
|
||||
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
|
||||
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
|
||||
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
+66
-182
@@ -1,208 +1,92 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/charmbracelet/huh"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// pangolinTheme is the custom theme using brand colors
|
||||
var pangolinTheme = ThemePangolin()
|
||||
|
||||
// isAccessibleMode checks if we should use accessible mode (simple prompts)
|
||||
// This is true for: non-TTY, TERM=dumb, or ACCESSIBLE env var set
|
||||
func isAccessibleMode() bool {
|
||||
// Check if stdin is not a terminal (piped input, CI, etc.)
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return true
|
||||
}
|
||||
// Check for dumb terminal
|
||||
if os.Getenv("TERM") == "dumb" {
|
||||
return true
|
||||
}
|
||||
// Check for explicit accessible mode request
|
||||
if os.Getenv("ACCESSIBLE") != "" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// handleAbort checks if the error is a user abort (Ctrl+C) and exits if so
|
||||
func handleAbort(err error) {
|
||||
if err != nil && errors.Is(err, huh.ErrUserAborted) {
|
||||
fmt.Println("\nInstallation cancelled.")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// runField runs a single field with the Pangolin theme, handling accessible mode
|
||||
func runField(field huh.Field) error {
|
||||
if isAccessibleMode() {
|
||||
return field.RunAccessible(os.Stdout, os.Stdin)
|
||||
}
|
||||
form := huh.NewForm(huh.NewGroup(field)).WithTheme(pangolinTheme)
|
||||
return form.Run()
|
||||
}
|
||||
|
||||
func readString(prompt string, defaultValue string) string {
|
||||
var value string
|
||||
|
||||
title := prompt
|
||||
func readString(reader *bufio.Reader, prompt string, defaultValue string) string {
|
||||
if defaultValue != "" {
|
||||
title = fmt.Sprintf("%s (default: %s)", prompt, defaultValue)
|
||||
fmt.Printf("%s (default: %s): ", prompt, defaultValue)
|
||||
} else {
|
||||
fmt.Print(prompt + ": ")
|
||||
}
|
||||
|
||||
input := huh.NewInput().
|
||||
Title(title).
|
||||
Value(&value)
|
||||
|
||||
// If no default value, this field is required
|
||||
if defaultValue == "" {
|
||||
input = input.Validate(func(s string) error {
|
||||
if s == "" {
|
||||
return fmt.Errorf("this field is required")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
err := runField(input)
|
||||
handleAbort(err)
|
||||
|
||||
if value == "" {
|
||||
value = defaultValue
|
||||
}
|
||||
|
||||
// Print the answer so it remains visible in terminal history (skip in accessible mode as it already shows)
|
||||
if !isAccessibleMode() {
|
||||
fmt.Printf("%s: %s\n", prompt, value)
|
||||
}
|
||||
|
||||
return value
|
||||
return input
|
||||
}
|
||||
|
||||
func readPassword(prompt string) string {
|
||||
var value string
|
||||
func readStringNoDefault(reader *bufio.Reader, prompt string) string {
|
||||
fmt.Print(prompt + ": ")
|
||||
input, _ := reader.ReadString('\n')
|
||||
return strings.TrimSpace(input)
|
||||
}
|
||||
|
||||
func readPassword(prompt string, reader *bufio.Reader) string {
|
||||
if term.IsTerminal(int(syscall.Stdin)) {
|
||||
fmt.Print(prompt + ": ")
|
||||
// Read password without echo if we're in a terminal
|
||||
password, err := term.ReadPassword(int(syscall.Stdin))
|
||||
fmt.Println() // Add a newline since ReadPassword doesn't add one
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
input := strings.TrimSpace(string(password))
|
||||
if input == "" {
|
||||
return readPassword(prompt, reader)
|
||||
}
|
||||
return input
|
||||
} else {
|
||||
// Fallback to reading from stdin if not in a terminal
|
||||
return readString(reader, prompt, "")
|
||||
}
|
||||
}
|
||||
|
||||
func readBool(reader *bufio.Reader, prompt string, defaultValue bool) bool {
|
||||
defaultStr := "no"
|
||||
if defaultValue {
|
||||
defaultStr = "yes"
|
||||
}
|
||||
for {
|
||||
input := huh.NewInput().
|
||||
Title(prompt).
|
||||
Value(&value).
|
||||
EchoMode(huh.EchoModePassword).
|
||||
Validate(func(s string) error {
|
||||
if s == "" {
|
||||
return fmt.Errorf("password is required")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err := runField(input)
|
||||
handleAbort(err)
|
||||
|
||||
if value != "" {
|
||||
// Print confirmation without revealing the password
|
||||
if !isAccessibleMode() {
|
||||
fmt.Printf("%s: %s\n", prompt, "********")
|
||||
}
|
||||
return value
|
||||
input := readString(reader, prompt+" (yes/no)", defaultStr)
|
||||
lower := strings.ToLower(input)
|
||||
if lower == "yes" {
|
||||
return true
|
||||
} else if lower == "no" {
|
||||
return false
|
||||
} else {
|
||||
fmt.Println("Please enter 'yes' or 'no'.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func readBool(prompt string, defaultValue bool) bool {
|
||||
var value = defaultValue
|
||||
|
||||
confirm := huh.NewConfirm().
|
||||
Title(prompt).
|
||||
Value(&value).
|
||||
Affirmative("Yes").
|
||||
Negative("No")
|
||||
|
||||
err := runField(confirm)
|
||||
handleAbort(err)
|
||||
|
||||
// Print the answer so it remains visible in terminal history
|
||||
if !isAccessibleMode() {
|
||||
answer := "No"
|
||||
if value {
|
||||
answer = "Yes"
|
||||
func readBoolNoDefault(reader *bufio.Reader, prompt string) bool {
|
||||
for {
|
||||
input := readStringNoDefault(reader, prompt+" (yes/no)")
|
||||
lower := strings.ToLower(input)
|
||||
if lower == "yes" {
|
||||
return true
|
||||
} else if lower == "no" {
|
||||
return false
|
||||
} else {
|
||||
fmt.Println("Please enter 'yes' or 'no'.")
|
||||
}
|
||||
fmt.Printf("%s: %s\n", prompt, answer)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func readBoolNoDefault(prompt string) bool {
|
||||
var value bool
|
||||
|
||||
confirm := huh.NewConfirm().
|
||||
Title(prompt).
|
||||
Value(&value).
|
||||
Affirmative("Yes").
|
||||
Negative("No")
|
||||
|
||||
err := runField(confirm)
|
||||
handleAbort(err)
|
||||
|
||||
// Print the answer so it remains visible in terminal history
|
||||
if !isAccessibleMode() {
|
||||
answer := "No"
|
||||
if value {
|
||||
answer = "Yes"
|
||||
}
|
||||
fmt.Printf("%s: %s\n", prompt, answer)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func readInt(prompt string, defaultValue int) int {
|
||||
var value string
|
||||
|
||||
title := fmt.Sprintf("%s (default: %d)", prompt, defaultValue)
|
||||
|
||||
input := huh.NewInput().
|
||||
Title(title).
|
||||
Value(&value).
|
||||
Validate(func(s string) error {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
_, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("please enter a valid number")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
err := runField(input)
|
||||
handleAbort(err)
|
||||
|
||||
if value == "" {
|
||||
// Print the answer so it remains visible in terminal history
|
||||
if !isAccessibleMode() {
|
||||
fmt.Printf("%s: %d\n", prompt, defaultValue)
|
||||
}
|
||||
func readInt(reader *bufio.Reader, prompt string, defaultValue int) int {
|
||||
input := readString(reader, prompt, fmt.Sprintf("%d", defaultValue))
|
||||
if input == "" {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
result, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
if !isAccessibleMode() {
|
||||
fmt.Printf("%s: %d\n", prompt, defaultValue)
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Print the answer so it remains visible in terminal history
|
||||
if !isAccessibleMode() {
|
||||
fmt.Printf("%s: %d\n", prompt, result)
|
||||
}
|
||||
|
||||
return result
|
||||
value := defaultValue
|
||||
fmt.Sscanf(input, "%d", &value)
|
||||
return value
|
||||
}
|
||||
|
||||
+101
-219
@@ -1,35 +1,29 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"bufio"
|
||||
"embed"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Version variables injected at build time via -ldflags
|
||||
var (
|
||||
pangolinVersion string
|
||||
gerbilVersion string
|
||||
badgerVersion string
|
||||
)
|
||||
|
||||
// DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD
|
||||
func loadVersions(config *Config) {
|
||||
config.PangolinVersion = pangolinVersion
|
||||
config.GerbilVersion = gerbilVersion
|
||||
config.BadgerVersion = badgerVersion
|
||||
config.PangolinVersion = "replaceme"
|
||||
config.GerbilVersion = "replaceme"
|
||||
config.BadgerVersion = "replaceme"
|
||||
}
|
||||
|
||||
//go:embed config/*
|
||||
@@ -87,19 +81,14 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
|
||||
var config Config
|
||||
var alreadyInstalled = false
|
||||
|
||||
// Determine installation directory
|
||||
installDir := findOrSelectInstallDirectory()
|
||||
if err := os.Chdir(installDir); err != nil {
|
||||
fmt.Printf("Error changing to installation directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// check if there is already a config file
|
||||
if _, err := os.Stat("config/config.yml"); err != nil {
|
||||
config = collectUserInput()
|
||||
config = collectUserInput(reader)
|
||||
|
||||
loadVersions(&config)
|
||||
config.DoCrowdsecInstall = false
|
||||
@@ -112,10 +101,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := moveFile("config/docker-compose.yml", "docker-compose.yml"); err != nil {
|
||||
fmt.Printf("Error moving docker-compose.yml: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
moveFile("config/docker-compose.yml", "docker-compose.yml")
|
||||
|
||||
fmt.Println("\nConfiguration files created successfully!")
|
||||
|
||||
@@ -130,17 +116,13 @@ func main() {
|
||||
|
||||
fmt.Println("\n=== Starting installation ===")
|
||||
|
||||
if readBool("Would you like to install and start the containers?", true) {
|
||||
if readBool(reader, "Would you like to install and start the containers?", true) {
|
||||
|
||||
config.InstallationContainerType = podmanOrDocker()
|
||||
config.InstallationContainerType = podmanOrDocker(reader)
|
||||
|
||||
if !isDockerInstalled() && runtime.GOOS == "linux" && config.InstallationContainerType == Docker {
|
||||
if readBool("Docker is not installed. Would you like to install it?", true) {
|
||||
if err := installDocker(); err != nil {
|
||||
fmt.Printf("Error installing Docker: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if readBool(reader, "Docker is not installed. Would you like to install it?", true) {
|
||||
installDocker()
|
||||
// try to start docker service but ignore errors
|
||||
if err := startDockerService(); err != nil {
|
||||
fmt.Println("Error starting Docker service:", err)
|
||||
@@ -149,7 +131,7 @@ func main() {
|
||||
}
|
||||
// wait 10 seconds for docker to start checking if docker is running every 2 seconds
|
||||
fmt.Println("Waiting for Docker to start...")
|
||||
for range 5 {
|
||||
for i := 0; i < 5; i++ {
|
||||
if isDockerRunning() {
|
||||
fmt.Println("Docker is running!")
|
||||
break
|
||||
@@ -184,7 +166,7 @@ func main() {
|
||||
fmt.Println("\n=== MaxMind Database Update ===")
|
||||
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
|
||||
fmt.Println("MaxMind GeoLite2 Country database found.")
|
||||
if readBool("Would you like to update the MaxMind database to the latest version?", false) {
|
||||
if readBool(reader, "Would you like to update the MaxMind database to the latest version?", false) {
|
||||
if err := downloadMaxMindDatabase(); err != nil {
|
||||
fmt.Printf("Error updating MaxMind database: %v\n", err)
|
||||
fmt.Println("You can try updating it manually later if needed.")
|
||||
@@ -192,7 +174,7 @@ func main() {
|
||||
}
|
||||
} else {
|
||||
fmt.Println("MaxMind GeoLite2 Country database not found.")
|
||||
if readBool("Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
|
||||
if readBool(reader, "Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
|
||||
if err := downloadMaxMindDatabase(); err != nil {
|
||||
fmt.Printf("Error downloading MaxMind database: %v\n", err)
|
||||
fmt.Println("You can try downloading it manually later if needed.")
|
||||
@@ -209,11 +191,11 @@ func main() {
|
||||
if !checkIsCrowdsecInstalledInCompose() {
|
||||
fmt.Println("\n=== CrowdSec Install ===")
|
||||
// check if crowdsec is installed
|
||||
if readBool("Would you like to install CrowdSec?", false) {
|
||||
if readBool(reader, "Would you like to install CrowdSec?", false) {
|
||||
fmt.Println("This installer constitutes a minimal viable CrowdSec deployment. CrowdSec will add extra complexity to your Pangolin installation and may not work to the best of its abilities out of the box. Users are expected to implement configuration adjustments on their own to achieve the best security posture. Consult the CrowdSec documentation for detailed configuration instructions.")
|
||||
|
||||
// BUG: crowdsec installation will be skipped if the user chooses to install on the first installation.
|
||||
if readBool("Are you willing to manage CrowdSec?", false) {
|
||||
if readBool(reader, "Are you willing to manage CrowdSec?", false) {
|
||||
if config.DashboardDomain == "" {
|
||||
traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml")
|
||||
if err != nil {
|
||||
@@ -242,8 +224,8 @@ func main() {
|
||||
fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail)
|
||||
fmt.Printf("Badger Version: %s\n", config.BadgerVersion)
|
||||
|
||||
if !readBool("Are these values correct?", true) {
|
||||
config = collectUserInput()
|
||||
if !readBool(reader, "Are these values correct?", true) {
|
||||
config = collectUserInput(reader)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +234,7 @@ func main() {
|
||||
if detectedType == Undefined {
|
||||
// If detection fails, prompt the user
|
||||
fmt.Println("Unable to detect container type from existing installation.")
|
||||
config.InstallationContainerType = podmanOrDocker()
|
||||
config.InstallationContainerType = podmanOrDocker(reader)
|
||||
} else {
|
||||
config.InstallationContainerType = detectedType
|
||||
fmt.Printf("Detected container type: %s\n", config.InstallationContainerType)
|
||||
@@ -294,119 +276,8 @@ func main() {
|
||||
fmt.Printf("\nTo complete the initial setup, please visit:\nhttps://%s/auth/initial-setup\n", config.DashboardDomain)
|
||||
}
|
||||
|
||||
func hasExistingInstall(dir string) bool {
|
||||
configPath := filepath.Join(dir, "config", "config.yml")
|
||||
_, err := os.Stat(configPath)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func findOrSelectInstallDirectory() string {
|
||||
const defaultInstallDir = "/opt/pangolin"
|
||||
|
||||
// Get current working directory
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting current directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 1. Check current directory for existing install
|
||||
if hasExistingInstall(cwd) {
|
||||
fmt.Printf("Found existing Pangolin installation in current directory: %s\n", cwd)
|
||||
return cwd
|
||||
}
|
||||
|
||||
// 2. Check default location (/opt/pangolin) for existing install
|
||||
if cwd != defaultInstallDir && hasExistingInstall(defaultInstallDir) {
|
||||
fmt.Printf("\nFound existing Pangolin installation at: %s\n", defaultInstallDir)
|
||||
if readBool(fmt.Sprintf("Would you like to use the existing installation at %s?", defaultInstallDir), true) {
|
||||
return defaultInstallDir
|
||||
}
|
||||
}
|
||||
|
||||
// 3. No existing install found, prompt for installation directory
|
||||
fmt.Println("\n=== Installation Directory ===")
|
||||
fmt.Println("No existing Pangolin installation detected.")
|
||||
|
||||
installDir := readString("Enter the installation directory", defaultInstallDir)
|
||||
|
||||
// Expand ~ to home directory if present
|
||||
if strings.HasPrefix(installDir, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting home directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
installDir = filepath.Join(home, installDir[1:])
|
||||
}
|
||||
|
||||
// Convert to absolute path
|
||||
absPath, err := filepath.Abs(installDir)
|
||||
if err != nil {
|
||||
fmt.Printf("Error resolving path: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
installDir = absPath
|
||||
|
||||
// Check if directory exists
|
||||
if _, err := os.Stat(installDir); os.IsNotExist(err) {
|
||||
// Directory doesn't exist, create it
|
||||
if readBool(fmt.Sprintf("Directory %s does not exist. Create it?", installDir), true) {
|
||||
if err := os.MkdirAll(installDir, 0755); err != nil {
|
||||
fmt.Printf("Error creating directory: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Created directory: %s\n", installDir)
|
||||
|
||||
// Offer to change ownership if running via sudo
|
||||
changeDirectoryOwnership(installDir)
|
||||
} else {
|
||||
fmt.Println("Installation cancelled.")
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Installation directory: %s\n", installDir)
|
||||
return installDir
|
||||
}
|
||||
|
||||
func changeDirectoryOwnership(dir string) {
|
||||
// Check if we're running via sudo by looking for SUDO_USER
|
||||
sudoUser := os.Getenv("SUDO_USER")
|
||||
if sudoUser == "" || os.Geteuid() != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
sudoUID := os.Getenv("SUDO_UID")
|
||||
sudoGID := os.Getenv("SUDO_GID")
|
||||
|
||||
if sudoUID == "" || sudoGID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("\nRunning as root via sudo (original user: %s)\n", sudoUser)
|
||||
if readBool(fmt.Sprintf("Would you like to change ownership of %s to user '%s'? This makes it easier to manage config files without sudo.", dir, sudoUser), true) {
|
||||
uid, err := strconv.Atoi(sudoUID)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Could not parse SUDO_UID: %v\n", err)
|
||||
return
|
||||
}
|
||||
gid, err := strconv.Atoi(sudoGID)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: Could not parse SUDO_GID: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Chown(dir, uid, gid); err != nil {
|
||||
fmt.Printf("Warning: Could not change ownership: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf("Changed ownership of %s to %s\n", dir, sudoUser)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func podmanOrDocker() SupportedContainer {
|
||||
inputContainer := readString("Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
||||
func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
|
||||
inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
|
||||
|
||||
chosenContainer := Docker
|
||||
if strings.EqualFold(inputContainer, "docker") {
|
||||
@@ -418,8 +289,7 @@ func podmanOrDocker() SupportedContainer {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch chosenContainer {
|
||||
case Podman:
|
||||
if chosenContainer == Podman {
|
||||
if !isPodmanInstalled() {
|
||||
fmt.Println("Podman or podman-compose is not installed. Please install both manually. Automated installation will be available in a later release.")
|
||||
os.Exit(1)
|
||||
@@ -428,7 +298,7 @@ func podmanOrDocker() SupportedContainer {
|
||||
if err := exec.Command("bash", "-c", "cat /etc/sysctl.d/99-podman.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start=' || cat /etc/sysctl.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
|
||||
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
|
||||
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
|
||||
approved := readBool("The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system\". Approve?", true)
|
||||
approved := readBool(reader, "The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system\". Approve?", true)
|
||||
if approved {
|
||||
if os.Geteuid() != 0 {
|
||||
fmt.Println("You need to run the installer as root for such a configuration.")
|
||||
@@ -440,7 +310,7 @@ func podmanOrDocker() SupportedContainer {
|
||||
// Linux only.
|
||||
|
||||
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system"); err != nil {
|
||||
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
|
||||
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
@@ -450,7 +320,7 @@ func podmanOrDocker() SupportedContainer {
|
||||
fmt.Println("Unprivileged ports have been configured.")
|
||||
}
|
||||
|
||||
case Docker:
|
||||
} else if chosenContainer == Docker {
|
||||
// check if docker is not installed and the user is root
|
||||
if !isDockerInstalled() {
|
||||
if os.Geteuid() != 0 {
|
||||
@@ -465,7 +335,7 @@ func podmanOrDocker() SupportedContainer {
|
||||
fmt.Println("The installer will not be able to run docker commands without running it as root.")
|
||||
os.Exit(1)
|
||||
}
|
||||
default:
|
||||
} else {
|
||||
// This shouldn't happen unless there's a third container runtime.
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -473,35 +343,35 @@ func podmanOrDocker() SupportedContainer {
|
||||
return chosenContainer
|
||||
}
|
||||
|
||||
func collectUserInput() Config {
|
||||
func collectUserInput(reader *bufio.Reader) Config {
|
||||
config := Config{}
|
||||
|
||||
// Basic configuration
|
||||
fmt.Println("\n=== Basic Configuration ===")
|
||||
|
||||
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
|
||||
config.IsEnterprise = readBoolNoDefault(reader, "Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
|
||||
|
||||
config.BaseDomain = readString("Enter your base domain (no subdomain e.g. example.com)", "")
|
||||
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
|
||||
|
||||
// Set default dashboard domain after base domain is collected
|
||||
defaultDashboardDomain := ""
|
||||
if config.BaseDomain != "" {
|
||||
defaultDashboardDomain = "pangolin." + config.BaseDomain
|
||||
}
|
||||
config.DashboardDomain = readString("Enter the domain for the Pangolin dashboard", defaultDashboardDomain)
|
||||
config.LetsEncryptEmail = readString("Enter email for Let's Encrypt certificates", "")
|
||||
config.InstallGerbil = readBool("Do you want to use Gerbil to allow tunneled connections", true)
|
||||
config.DashboardDomain = readString(reader, "Enter the domain for the Pangolin dashboard", defaultDashboardDomain)
|
||||
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
|
||||
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
|
||||
|
||||
// Email configuration
|
||||
fmt.Println("\n=== Email Configuration ===")
|
||||
config.EnableEmail = readBool("Enable email functionality (SMTP)", false)
|
||||
config.EnableEmail = readBool(reader, "Enable email functionality (SMTP)", false)
|
||||
|
||||
if config.EnableEmail {
|
||||
config.EmailSMTPHost = readString("Enter SMTP host", "")
|
||||
config.EmailSMTPPort = readInt("Enter SMTP port (default 587)", 587)
|
||||
config.EmailSMTPUser = readString("Enter SMTP username", "")
|
||||
config.EmailSMTPPass = readPassword("Enter SMTP password")
|
||||
config.EmailNoReply = readString("Enter no-reply email address (often the same as SMTP username)", "")
|
||||
config.EmailSMTPHost = readString(reader, "Enter SMTP host", "")
|
||||
config.EmailSMTPPort = readInt(reader, "Enter SMTP port (default 587)", 587)
|
||||
config.EmailSMTPUser = readString(reader, "Enter SMTP username", "")
|
||||
config.EmailSMTPPass = readString(reader, "Enter SMTP password", "") // Should this be readPassword?
|
||||
config.EmailNoReply = readString(reader, "Enter no-reply email address (often the same as SMTP username)", "")
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
@@ -522,8 +392,8 @@ func collectUserInput() Config {
|
||||
|
||||
fmt.Println("\n=== Advanced Configuration ===")
|
||||
|
||||
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
|
||||
config.EnableGeoblocking = readBool("Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
|
||||
config.EnableIPv6 = readBool(reader, "Is your server IPv6 capable?", true)
|
||||
config.EnableGeoblocking = readBool(reader, "Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
|
||||
|
||||
if config.DashboardDomain == "" {
|
||||
fmt.Println("Error: Dashboard Domain name is required")
|
||||
@@ -534,23 +404,15 @@ func collectUserInput() Config {
|
||||
}
|
||||
|
||||
func createConfigFiles(config Config) error {
|
||||
if err := os.MkdirAll("config", 0755); err != nil {
|
||||
return fmt.Errorf("failed to create config directory: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll("config/letsencrypt", 0755); err != nil {
|
||||
return fmt.Errorf("failed to create letsencrypt directory: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll("config/db", 0755); err != nil {
|
||||
return fmt.Errorf("failed to create db directory: %v", err)
|
||||
}
|
||||
if err := os.MkdirAll("config/logs", 0755); err != nil {
|
||||
return fmt.Errorf("failed to create logs directory: %v", err)
|
||||
}
|
||||
os.MkdirAll("config", 0755)
|
||||
os.MkdirAll("config/letsencrypt", 0755)
|
||||
os.MkdirAll("config/db", 0755)
|
||||
os.MkdirAll("config/logs", 0755)
|
||||
|
||||
// Walk through all embedded files
|
||||
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, walkErr error) (err error) {
|
||||
if walkErr != nil {
|
||||
return walkErr
|
||||
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Skip the root fs directory itself
|
||||
@@ -601,11 +463,7 @@ func createConfigFiles(config Config) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s: %v", path, err)
|
||||
}
|
||||
defer func() {
|
||||
if cerr := outFile.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
defer outFile.Close()
|
||||
|
||||
// Execute template
|
||||
if err := tmpl.Execute(outFile, config); err != nil {
|
||||
@@ -621,26 +479,18 @@ func createConfigFiles(config Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(src, dst string) (err error) {
|
||||
func copyFile(src, dst string) error {
|
||||
source, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if cerr := source.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
defer source.Close()
|
||||
|
||||
destination, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if cerr := destination.Close(); cerr != nil && err == nil {
|
||||
err = cerr
|
||||
}
|
||||
}()
|
||||
defer destination.Close()
|
||||
|
||||
_, err = io.Copy(destination, source)
|
||||
return err
|
||||
@@ -711,24 +561,22 @@ func showSetupTokenInstructions(containerType SupportedContainer, dashboardDomai
|
||||
fmt.Println("To get your setup token, you need to:")
|
||||
fmt.Println("")
|
||||
fmt.Println("1. Start the containers")
|
||||
switch containerType {
|
||||
case Docker:
|
||||
if containerType == Docker {
|
||||
fmt.Println(" docker compose up -d")
|
||||
case Podman:
|
||||
} else if containerType == Podman {
|
||||
fmt.Println(" podman-compose up -d")
|
||||
} else {
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("2. Wait for the Pangolin container to start and generate the token")
|
||||
fmt.Println("")
|
||||
fmt.Println("3. Check the container logs for the setup token")
|
||||
switch containerType {
|
||||
case Docker:
|
||||
if containerType == Docker {
|
||||
fmt.Println(" docker logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
|
||||
case Podman:
|
||||
} else if containerType == Podman {
|
||||
fmt.Println(" podman logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
|
||||
} else {
|
||||
}
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Println("4. Look for output like")
|
||||
fmt.Println(" === SETUP TOKEN GENERATED ===")
|
||||
@@ -744,12 +592,43 @@ func showSetupTokenInstructions(containerType SupportedContainer, dashboardDomai
|
||||
}
|
||||
|
||||
func generateRandomSecretKey() string {
|
||||
secret := make([]byte, 32)
|
||||
_, err := rand.Read(secret)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to generate random secret key: %v", err))
|
||||
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
const length = 32
|
||||
|
||||
var seededRand *rand.Rand = rand.New(
|
||||
rand.NewSource(time.Now().UnixNano()))
|
||||
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[seededRand.Intn(len(charset))]
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(secret)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getPublicIP() string {
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := client.Get("https://ifconfig.io/ip")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
ip := strings.TrimSpace(string(body))
|
||||
|
||||
// Validate that it's a valid IP address
|
||||
if net.ParseIP(ip) != nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// Run external commands with stdio/stderr attached.
|
||||
@@ -764,7 +643,10 @@ func checkPortsAvailable(port int) error {
|
||||
addr := fmt.Sprintf(":%d", port)
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ERROR: port %d is occupied or cannot be bound: %w", port, err)
|
||||
return fmt.Errorf(
|
||||
"ERROR: port %d is occupied or cannot be bound: %w\n\n",
|
||||
port, err,
|
||||
)
|
||||
}
|
||||
if closeErr := ln.Close(); closeErr != nil {
|
||||
fmt.Fprintf(os.Stderr,
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/huh"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// Pangolin brand colors (converted from oklch to hex)
|
||||
var (
|
||||
// Primary orange/amber - oklch(0.6717 0.1946 41.93)
|
||||
primaryColor = lipgloss.AdaptiveColor{Light: "#D97706", Dark: "#F59E0B"}
|
||||
// Muted foreground
|
||||
mutedColor = lipgloss.AdaptiveColor{Light: "#737373", Dark: "#A3A3A3"}
|
||||
// Success green
|
||||
successColor = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"}
|
||||
// Error red - oklch(0.577 0.245 27.325)
|
||||
errorColor = lipgloss.AdaptiveColor{Light: "#DC2626", Dark: "#EF4444"}
|
||||
// Normal text
|
||||
normalFg = lipgloss.AdaptiveColor{Light: "#171717", Dark: "#FAFAFA"}
|
||||
)
|
||||
|
||||
// ThemePangolin returns a huh theme using Pangolin brand colors
|
||||
func ThemePangolin() *huh.Theme {
|
||||
t := huh.ThemeBase()
|
||||
|
||||
// Focused state styles
|
||||
t.Focused.Base = t.Focused.Base.BorderForeground(primaryColor)
|
||||
t.Focused.Title = t.Focused.Title.Foreground(primaryColor).Bold(true)
|
||||
t.Focused.Description = t.Focused.Description.Foreground(mutedColor)
|
||||
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(errorColor)
|
||||
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(errorColor)
|
||||
t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(primaryColor)
|
||||
t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(primaryColor)
|
||||
t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(primaryColor)
|
||||
t.Focused.Option = t.Focused.Option.Foreground(normalFg)
|
||||
t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(primaryColor)
|
||||
t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(successColor).SetString("✓ ")
|
||||
t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(mutedColor).SetString(" ")
|
||||
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(lipgloss.Color("#FFFFFF")).Background(primaryColor)
|
||||
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(normalFg).Background(lipgloss.AdaptiveColor{Light: "#E5E5E5", Dark: "#404040"})
|
||||
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(primaryColor)
|
||||
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(primaryColor)
|
||||
|
||||
// Blurred state inherits from focused but with hidden border
|
||||
t.Blurred = t.Focused
|
||||
t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder())
|
||||
t.Blurred.Title = t.Blurred.Title.Foreground(mutedColor).Bold(false)
|
||||
t.Blurred.TextInput.Prompt = t.Blurred.TextInput.Prompt.Foreground(mutedColor)
|
||||
|
||||
return t
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
# --- Configuration ---
|
||||
# The header text to be added to the files.
|
||||
HEADER_TEXT = """/*
|
||||
* This file is part of a proprietary work.
|
||||
*
|
||||
* Copyright (c) 2025-2026 Fossorial, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is licensed under the Fossorial Commercial License.
|
||||
* You may not use this file except in compliance with the License.
|
||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
||||
*
|
||||
* This file is not licensed under the AGPLv3.
|
||||
*/
|
||||
"""
|
||||
|
||||
HEADER_NORMALIZED = HEADER_TEXT.strip()
|
||||
|
||||
|
||||
def extract_leading_block_comment(content):
|
||||
"""
|
||||
If the file content begins with a /* ... */ block comment, return the
|
||||
full text of that comment (including the delimiters) and the index at
|
||||
which the rest of the file starts (after any trailing newlines).
|
||||
Returns (None, 0) when no such comment is found.
|
||||
"""
|
||||
stripped = content.lstrip()
|
||||
if not stripped.startswith('/*'):
|
||||
return None, 0
|
||||
|
||||
# Account for any leading whitespace before the comment
|
||||
comment_start = content.index('/*')
|
||||
end_marker = content.find('*/', comment_start + 2)
|
||||
if end_marker == -1:
|
||||
return None, 0
|
||||
|
||||
comment_end = end_marker + 2 # position just after '*/'
|
||||
comment_text = content[comment_start:comment_end].strip()
|
||||
|
||||
# Advance past any whitespace / newlines that follow the closing */
|
||||
rest_start = comment_end
|
||||
while rest_start < len(content) and content[rest_start] in '\n\r':
|
||||
rest_start += 1
|
||||
|
||||
return comment_text, rest_start
|
||||
|
||||
|
||||
def should_add_header(file_path):
|
||||
"""
|
||||
Checks if a file should receive the commercial license header.
|
||||
Returns True if 'server/private' is in the path.
|
||||
"""
|
||||
if 'server/private' in file_path.lower():
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def process_directory(root_dir):
|
||||
"""
|
||||
Recursively scans a directory and adds/replaces/removes headers in
|
||||
qualifying .ts or .tsx files, skipping any 'node_modules' directories.
|
||||
"""
|
||||
print(f"Scanning directory: {root_dir}")
|
||||
files_processed = 0
|
||||
files_modified = 0
|
||||
|
||||
for root, dirs, files in os.walk(root_dir):
|
||||
# Exclude 'node_modules' directories from the scan.
|
||||
if 'node_modules' in dirs:
|
||||
dirs.remove('node_modules')
|
||||
|
||||
for file in files:
|
||||
if not (file.endswith('.ts') or file.endswith('.tsx')):
|
||||
continue
|
||||
|
||||
file_path = os.path.join(root, file)
|
||||
files_processed += 1
|
||||
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
original_content = f.read()
|
||||
|
||||
existing_comment, body_start = extract_leading_block_comment(
|
||||
original_content
|
||||
)
|
||||
has_any_header = existing_comment is not None
|
||||
has_correct_header = existing_comment == HEADER_NORMALIZED
|
||||
|
||||
body = original_content[body_start:] if has_any_header else original_content
|
||||
|
||||
if should_add_header(file_path):
|
||||
if has_correct_header:
|
||||
print(f"Header up-to-date: {file_path}")
|
||||
else:
|
||||
# Either no header exists or the header is outdated - write
|
||||
# the correct one.
|
||||
action = "Replaced header in" if has_any_header else "Added header to"
|
||||
new_content = HEADER_NORMALIZED + '\n\n' + body
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
print(f"{action}: {file_path}")
|
||||
files_modified += 1
|
||||
else:
|
||||
if has_any_header:
|
||||
# Remove the header - it shouldn't be here.
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(body)
|
||||
print(f"Removed header from: {file_path}")
|
||||
files_modified += 1
|
||||
else:
|
||||
print(f"No header needed: {file_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error processing file {file_path}: {e}")
|
||||
|
||||
print("\n--- Scan Complete ---")
|
||||
print(f"Total .ts or .tsx files found: {files_processed}")
|
||||
print(f"Files modified (added/replaced/removed): {files_modified}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Get the target directory from the command line arguments.
|
||||
# If no directory is provided, it uses the current directory ('.').
|
||||
if len(sys.argv) > 1:
|
||||
target_directory = sys.argv[1]
|
||||
else:
|
||||
target_directory = '.' # Default to current directory
|
||||
|
||||
if not os.path.isdir(target_directory):
|
||||
print(f"Error: Directory '{target_directory}' not found.")
|
||||
sys.exit(1)
|
||||
|
||||
process_directory(os.path.abspath(target_directory))
|
||||
+30
-637
File diff suppressed because it is too large
Load Diff
+29
-636
File diff suppressed because it is too large
Load Diff
+36
-643
File diff suppressed because it is too large
Load Diff
+49
-656
File diff suppressed because it is too large
Load Diff
+26
-633
File diff suppressed because it is too large
Load Diff
+28
-635
File diff suppressed because it is too large
Load Diff
+67
-674
File diff suppressed because it is too large
Load Diff
+26
-633
File diff suppressed because it is too large
Load Diff
+26
-633
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"contactSalesEnable": "Kontakt salgsavdelingen for å aktivere denne funksjonen.",
|
||||
"contactSalesBookDemo": "Bestill en demo",
|
||||
"contactSalesOr": "eller",
|
||||
"contactSalesContactUs": "kontakt oss",
|
||||
"setupCreate": "Opprett organisasjonen, nettstedet og ressursene",
|
||||
"headerAuthCompatibilityInfo": "Aktiver dette for å tvinge frem en 401 Uautorisert-respons når en autentiseringstoken mangler. Dette kreves for nettlesere eller spesifikke HTTP-biblioteker som ikke sender legitimasjon uten en serverutfordring.",
|
||||
"headerAuthCompatibility": "Utvidet kompatibilitet",
|
||||
@@ -22,16 +18,6 @@
|
||||
"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.",
|
||||
"dismiss": "Avvis",
|
||||
"subscriptionViolationMessage": "Du er utenfor grensen for gjeldende plan. Rett problemet ved å fjerne nettsteder, brukere eller andre ressurser for å bli innenfor planen din.",
|
||||
"trialBannerMessage": "Din prøveperiode utløper om {countdown}. Oppgrader for å beholde tilgangen.",
|
||||
"trialBannerExpired": "Prøveperioden din har utløpt. Oppgrader nå for å gjenopprette tilgangen.",
|
||||
"trialActive": "Gratis prøveversjon aktiv",
|
||||
"trialExpired": "Prøveperioden er utløpt",
|
||||
"trialHasEnded": "Din prøveperiode har avsluttet.",
|
||||
"trialDaysRemaining": "{count, plural, one {# dag igjen} other {# dager igjen}}",
|
||||
"trialDaysLeftShort": "{days}d igjen av prøveperioden",
|
||||
"trialGoToBilling": "Gå til faktureringssiden",
|
||||
"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.",
|
||||
"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.",
|
||||
@@ -160,11 +146,6 @@
|
||||
"createLink": "Opprett lenke",
|
||||
"resourcesNotFound": "Ingen ressurser funnet",
|
||||
"resourceSearch": "Søk i ressurser",
|
||||
"machineSearch": "Søk etter maskiner",
|
||||
"machinesSearch": "Søk etter maskinklienter...",
|
||||
"machineNotFound": "Ingen maskiner funnet",
|
||||
"userDeviceSearch": "Søk etter brukerenheter",
|
||||
"userDevicesSearch": "Søk etter brukerenheter...",
|
||||
"openMenu": "Åpne meny",
|
||||
"resource": "Ressurs",
|
||||
"title": "Tittel",
|
||||
@@ -192,7 +173,6 @@
|
||||
"resourceHTTPDescription": "Proxy forespørsler over HTTPS ved å bruke et fullstendig kvalifisert domenenavn.",
|
||||
"resourceRaw": "Rå TCP/UDP-ressurs",
|
||||
"resourceRawDescription": "Proxy forespørsler over rå TCP/UDP ved å bruke et portnummer.",
|
||||
"resourceRawDescriptionCloud": "Proxy forespørsler om rå TCP/UDP ved hjelp av et portnummer. Krever sider for å koble til en ekstern node.",
|
||||
"resourceCreate": "Opprett ressurs",
|
||||
"resourceCreateDescription": "Følg trinnene nedenfor for å opprette en ny ressurs",
|
||||
"resourceSeeAll": "Se alle ressurser",
|
||||
@@ -219,7 +199,6 @@
|
||||
"protocolSelect": "Velg en protokoll",
|
||||
"resourcePortNumber": "Portnummer",
|
||||
"resourcePortNumberDescription": "Det eksterne portnummeret for proxy forespørsler.",
|
||||
"back": "Tilbake",
|
||||
"cancel": "Avbryt",
|
||||
"resourceConfig": "Konfigurasjonsutdrag",
|
||||
"resourceConfigDescription": "Kopier og lim inn disse konfigurasjons-øyeblikkene for å sette opp TCP/UDP ressursen",
|
||||
@@ -265,25 +244,11 @@
|
||||
"orgErrorDeleteMessage": "Det oppsto en feil under sletting av organisasjonen.",
|
||||
"orgDeleted": "Organisasjon slettet",
|
||||
"orgDeletedMessage": "Organisasjonen og tilhørende data er slettet.",
|
||||
"deleteAccount": "Slett konto",
|
||||
"deleteAccountDescription": "Slett kontoen din permanent, alle organisasjoner du eier, og alle data i disse organisasjonene. Dette kan ikke angres.",
|
||||
"deleteAccountButton": "Slett konto",
|
||||
"deleteAccountConfirmTitle": "Slett konto",
|
||||
"deleteAccountConfirmMessage": "Dette vil slette kontoen din, alle organisasjoner du eier og alle data i disse organisasjonene. Dette kan ikke gjøres om.",
|
||||
"deleteAccountConfirmString": "Slett konto",
|
||||
"deleteAccountSuccess": "Kontoen er slettet",
|
||||
"deleteAccountSuccessMessage": "Kontoen din er slettet.",
|
||||
"deleteAccountError": "Kunne ikke slette konto",
|
||||
"deleteAccountPreviewAccount": "Din konto",
|
||||
"deleteAccountPreviewOrgs": "Organisasjoner du eier (og alle deres data)",
|
||||
"orgMissing": "Organisasjons-ID Mangler",
|
||||
"orgMissingMessage": "Kan ikke regenerere invitasjon uten en organisasjons-ID.",
|
||||
"accessUsersManage": "Administrer brukere",
|
||||
"accessUserManage": "Administrer brukere",
|
||||
"accessUsersDescription": "Inviter og behandle brukere med tilgang til denne organisasjonen",
|
||||
"accessUsersSearch": "Søk etter brukere...",
|
||||
"accessUsersRoleFilterCount": "{count, plural, one {# rolle} other {# roller}}",
|
||||
"accessUsersRoleFilterClear": "Fjern rollesøkefiltre",
|
||||
"accessUserCreate": "Opprett bruker",
|
||||
"accessUserRemove": "Fjern bruker",
|
||||
"username": "Brukernavn",
|
||||
@@ -343,54 +308,6 @@
|
||||
"apiKeysDelete": "Slett API-nøkkel",
|
||||
"apiKeysManage": "Administrer API-nøkler",
|
||||
"apiKeysDescription": "API-nøkler brukes for å autentisere med integrasjons-API",
|
||||
"provisioningKeysTitle": "Foreløpig nøkkel",
|
||||
"provisioningKeysManage": "Behandle bestemmende nøkler",
|
||||
"provisioningKeysDescription": "Bestemmelsesnøkler brukes til å godkjenne automatisert nettstedsløsning for din organisasjon.",
|
||||
"provisioningManage": "Levering",
|
||||
"provisioningDescription": "Administrer foreløpig nøkler og gjennomgå ventende nettsteder som venter på godkjenning.",
|
||||
"pendingSites": "Ventende nettsteder",
|
||||
"siteApproveSuccess": "Vellykket godkjenning av nettsted",
|
||||
"siteApproveError": "Feil ved godkjenning av side",
|
||||
"provisioningKeys": "Foreløpig nøkler",
|
||||
"searchProvisioningKeys": "Søk varer i lagrings nøkler...",
|
||||
"provisioningKeysAdd": "Generer fremvisende nøkkel",
|
||||
"provisioningKeysErrorDelete": "Feil under sletting av foreløpig nøkkel",
|
||||
"provisioningKeysErrorDeleteMessage": "Feil under sletting av foreløpig nøkkel",
|
||||
"provisioningKeysQuestionRemove": "Er du sikker på at du vil fjerne denne midlertidig nøkkelen fra organisasjonen?",
|
||||
"provisioningKeysMessageRemove": "Når nøkkelen er fjernet, kan den ikke lenger brukes til anleggsavsetning.",
|
||||
"provisioningKeysDeleteConfirm": "Bekreft sletting av bestemmelsesnøkkel",
|
||||
"provisioningKeysDelete": "Slett bestemmelsesnøkkel",
|
||||
"provisioningKeysCreate": "Generer fremvisende nøkkel",
|
||||
"provisioningKeysCreateDescription": "Generer en ny foreløpig nøkkel til organisasjonen",
|
||||
"provisioningKeysSeeAll": "Se alle foreløpig nøkler",
|
||||
"provisioningKeysSave": "Lagre den midlertidig nøkkelen",
|
||||
"provisioningKeysSaveDescription": "Du kan bare se denne én gang. Kopier det til et sikkert sted.",
|
||||
"provisioningKeysErrorCreate": "Feil under oppretting av foreløpig nøkkel",
|
||||
"provisioningKeysList": "Ny provisorisk nøkkel",
|
||||
"provisioningKeysMaxBatchSize": "Maks størrelse på bunt",
|
||||
"provisioningKeysUnlimitedBatchSize": "Ubegrenset mengde bunt (ingen begrensning)",
|
||||
"provisioningKeysMaxBatchUnlimited": "Ubegrenset",
|
||||
"provisioningKeysMaxBatchSizeInvalid": "Angi en gyldig sjakkstørrelse (1–1 000.000).",
|
||||
"provisioningKeysValidUntil": "Gyldig til",
|
||||
"provisioningKeysValidUntilHint": "La stå tomt for ingen utløp.",
|
||||
"provisioningKeysValidUntilInvalid": "Angi en gyldig dato og klokkeslett.",
|
||||
"provisioningKeysNumUsed": "Antall ganger brukt",
|
||||
"provisioningKeysLastUsed": "Sist brukt",
|
||||
"provisioningKeysNoExpiry": "Ingen utløpsdato",
|
||||
"provisioningKeysNeverUsed": "Aldri",
|
||||
"provisioningKeysEdit": "Rediger bestemmelsesnøkkel",
|
||||
"provisioningKeysEditDescription": "Oppdater maksimal størrelse for bunt og utløpstid for denne nøkkelen.",
|
||||
"provisioningKeysApproveNewSites": "Godkjenn nye nettsteder",
|
||||
"provisioningKeysApproveNewSitesDescription": "Godkjenn automatisk nettsteder som registrerer deg med denne nøkkelen.",
|
||||
"provisioningKeysUpdateError": "Feil under oppdatering av foreløpig nøkkel",
|
||||
"provisioningKeysUpdated": "Foreslå nøkkel oppdatert",
|
||||
"provisioningKeysUpdatedDescription": "Dine endringer er lagret.",
|
||||
"provisioningKeysBannerTitle": "Sidens bestemmende nøkler",
|
||||
"provisioningKeysBannerDescription": "Generer en provisjonsnøkkel og bruk den med Newt-kontakten for automatisk opprettelse av nettsteder ved første oppstart - ingen behov for å sette opp separate legitimasjoner for hvert nettsted.",
|
||||
"provisioningKeysBannerButtonText": "Lær mer",
|
||||
"pendingSitesBannerTitle": "Ventende nettsteder",
|
||||
"pendingSitesBannerDescription": "Nettsteder som kobler seg til ved bruk av en provisjonsnøkkel vises her for vurdering.",
|
||||
"pendingSitesBannerButtonText": "Lær mer",
|
||||
"apiKeysSettings": "{apiKeyName} Innstillinger",
|
||||
"userTitle": "Administrer alle brukere",
|
||||
"userDescription": "Vis og administrer alle brukere i systemet",
|
||||
@@ -420,10 +337,6 @@
|
||||
"licenseErrorKeyActivate": "Aktivering av lisensnøkkel feilet",
|
||||
"licenseErrorKeyActivateDescription": "Det oppstod en feil under aktivering av lisensnøkkelen.",
|
||||
"licenseAbout": "Om Lisensiering",
|
||||
"licenseBannerTitle": "Aktiver din bedriftslisens",
|
||||
"licenseBannerDescription": "Lås opp bedriftsfunksjoner for din egenvertede Pangolin-instans. Kjøp en lisensnøkkel for å aktivere premium-funksjoner og legg den inn nedenfor.",
|
||||
"licenseBannerGetLicense": "Få en lisens",
|
||||
"licenseBannerViewDocs": "Vis dokumentasjon",
|
||||
"communityEdition": "Fellesskapsutgave",
|
||||
"licenseAboutDescription": "Dette er for bedrifts- og foretaksbrukere som bruker Pangolin i et kommersielt miljø. Hvis du bruker Pangolin til personlig bruk, kan du ignorere denne seksjonen.",
|
||||
"licenseKeyActivated": "Lisensnøkkel aktivert",
|
||||
@@ -546,8 +459,6 @@
|
||||
"filterByApprovalState": "Filtrer etter godkjenningsstatus",
|
||||
"approvalListEmpty": "Ingen godkjenninger",
|
||||
"approvalState": "Godkjennings tilstand",
|
||||
"approvalLoadMore": "Last mer",
|
||||
"loadingApprovals": "Laster inn godkjenninger",
|
||||
"approve": "Godkjenn",
|
||||
"approved": "Godkjent",
|
||||
"denied": "Avvist",
|
||||
@@ -581,12 +492,9 @@
|
||||
"userSaved": "Bruker lagret",
|
||||
"userSavedDescription": "Brukeren har blitt oppdatert.",
|
||||
"autoProvisioned": "Auto avlyst",
|
||||
"autoProvisionSettings": "Auto leveringsinnstillinger",
|
||||
"autoProvisionedDescription": "Tillat denne brukeren å bli automatisk administrert av en identitetsleverandør",
|
||||
"accessControlsDescription": "Administrer hva denne brukeren kan få tilgang til og gjøre i organisasjonen",
|
||||
"accessControlsSubmit": "Lagre tilgangskontroller",
|
||||
"singleRolePerUserPlanNotice": "Din plan støtter bare én rolle per bruker.",
|
||||
"singleRolePerUserEditionNotice": "Denne utgaven støtter bare én rolle per bruker.",
|
||||
"roles": "Roller",
|
||||
"accessUsersRoles": "Administrer brukere og roller",
|
||||
"accessUsersRolesDescription": "Inviter brukere og legg dem til roller for å administrere tilgang til organisasjonen",
|
||||
@@ -643,8 +551,6 @@
|
||||
"targetErrorInvalidPortDescription": "Vennligst skriv inn et gyldig portnummer",
|
||||
"targetErrorNoSite": "Ingen nettsted valgt",
|
||||
"targetErrorNoSiteDescription": "Velg et nettsted for målet",
|
||||
"targetTargetsCleared": "Mål ryddet",
|
||||
"targetTargetsClearedDescription": "Alle mål har blitt fjernet fra denne ressursen",
|
||||
"targetCreated": "Mål opprettet",
|
||||
"targetCreatedDescription": "Målet har blitt opprettet",
|
||||
"targetErrorCreate": "Kunne ikke opprette målet",
|
||||
@@ -728,7 +634,6 @@
|
||||
"resourcesErrorUpdate": "Feilet å slå av/på ressurs",
|
||||
"resourcesErrorUpdateDescription": "En feil oppstod under oppdatering av ressursen",
|
||||
"access": "Tilgang",
|
||||
"accessControl": "Tilgangskontroll",
|
||||
"shareLink": "{resource} Del Lenke",
|
||||
"resourceSelect": "Velg ressurs",
|
||||
"shareLinks": "Del lenker",
|
||||
@@ -869,7 +774,6 @@
|
||||
"accessRoleRemoved": "Rolle fjernet",
|
||||
"accessRoleRemovedDescription": "Rollen er vellykket fjernet.",
|
||||
"accessRoleRequiredRemove": "Før du sletter denne rollen, vennligst velg en ny rolle å overføre eksisterende medlemmer til.",
|
||||
"network": "Nettverk",
|
||||
"manage": "Administrer",
|
||||
"sitesNotFound": "Ingen områder funnet.",
|
||||
"pangolinServerAdmin": "Server Admin - Pangolin",
|
||||
@@ -885,9 +789,6 @@
|
||||
"sitestCountIncrease": "Øk antall områder",
|
||||
"idpManage": "Administrer Identitetsleverandører",
|
||||
"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",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Er du sikker på at du vil slette identitetsleverandøren permanent?",
|
||||
@@ -913,7 +814,6 @@
|
||||
"idpDisplayName": "Et visningsnavn for denne identitetsleverandøren",
|
||||
"idpAutoProvisionUsers": "Automatisk brukerklargjøring",
|
||||
"idpAutoProvisionUsersDescription": "Når aktivert, opprettes brukere automatisk i systemet ved første innlogging, med mulighet til å tilordne brukere til roller og organisasjoner.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "Du kan konfigurere autoprovisjonsinnstillingene når identitetsleverandøren er opprettet.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Leverandørtype",
|
||||
"idpTypeDescription": "Velg typen identitetsleverandør du ønsker å konfigurere",
|
||||
@@ -965,7 +865,7 @@
|
||||
"defaultMappingsRole": "Standard rolletilordning",
|
||||
"defaultMappingsRoleDescription": "Resultatet av dette uttrykket må returnere rollenavnet slik det er definert i organisasjonen som en streng.",
|
||||
"defaultMappingsOrg": "Standard organisasjonstilordning",
|
||||
"defaultMappingsOrgDescription": "Når denne er satt, må uttrykket returnere organisasjons-IDen eller «true» for at brukeren skal få tilgang til den organisasjonen. Når den ikke er satt, er det nok å definere en rolletilordning: brukeren gis tilgang så lenge en gyldig rolletilknytting kan løses for dem i organisasjonen.",
|
||||
"defaultMappingsOrgDescription": "Dette uttrykket må returnere organisasjons-ID-en eller «true» for å gi brukeren tilgang til organisasjonen.",
|
||||
"defaultMappingsSubmit": "Lagre standard tilordninger",
|
||||
"orgPoliciesEdit": "Rediger Organisasjonspolicy",
|
||||
"org": "Organisasjon",
|
||||
@@ -1112,12 +1012,12 @@
|
||||
"pangolinSetup": "Oppsett - Pangolin",
|
||||
"orgNameRequired": "Organisasjonsnavn er påkrevd",
|
||||
"orgIdRequired": "Organisasjons-ID er påkrevd",
|
||||
"orgIdMaxLength": "Organisasjons-ID må maksimalt være 32 tegn",
|
||||
"orgErrorCreate": "En feil oppstod under oppretting av organisasjon",
|
||||
"pageNotFound": "Siden ble ikke funnet",
|
||||
"pageNotFoundDescription": "Oops! Siden du leter etter finnes ikke.",
|
||||
"overview": "Oversikt",
|
||||
"home": "Hjem",
|
||||
"accessControl": "Tilgangskontroll",
|
||||
"settings": "Innstillinger",
|
||||
"usersAll": "Alle brukere",
|
||||
"license": "Lisens",
|
||||
@@ -1180,12 +1080,6 @@
|
||||
"actionGetUser": "Hent bruker",
|
||||
"actionGetOrgUser": "Hent organisasjonsbruker",
|
||||
"actionListOrgDomains": "List opp organisasjonsdomener",
|
||||
"actionGetDomain": "Få Domene",
|
||||
"actionCreateOrgDomain": "Opprett domene",
|
||||
"actionUpdateOrgDomain": "Oppdater domene",
|
||||
"actionDeleteOrgDomain": "Slett domene",
|
||||
"actionGetDNSRecords": "Hent DNS-oppføringer",
|
||||
"actionRestartOrgDomain": "Omstart Domene",
|
||||
"actionCreateSite": "Opprett område",
|
||||
"actionDeleteSite": "Slett område",
|
||||
"actionGetSite": "Hent område",
|
||||
@@ -1197,7 +1091,6 @@
|
||||
"setupTokenDescription": "Skriv inn oppsetttoken fra serverkonsollen.",
|
||||
"setupTokenRequired": "Oppsetttoken er nødvendig",
|
||||
"actionUpdateSite": "Oppdater område",
|
||||
"actionResetSiteBandwidth": "Tilbakestill organisasjons-båndbredde",
|
||||
"actionListSiteRoles": "List opp tillatte områderoller",
|
||||
"actionCreateResource": "Opprett ressurs",
|
||||
"actionDeleteResource": "Slett ressurs",
|
||||
@@ -1227,7 +1120,6 @@
|
||||
"actionRemoveUser": "Fjern bruker",
|
||||
"actionListUsers": "List opp brukere",
|
||||
"actionAddUserRole": "Legg til brukerrolle",
|
||||
"actionSetUserOrgRoles": "Angi brukerroller",
|
||||
"actionGenerateAccessToken": "Generer tilgangstoken",
|
||||
"actionDeleteAccessToken": "Slett tilgangstoken",
|
||||
"actionListAccessTokens": "List opp tilgangstokener",
|
||||
@@ -1272,9 +1164,7 @@
|
||||
"actionViewLogs": "Vis logger",
|
||||
"noneSelected": "Ingen valgt",
|
||||
"orgNotFound2": "Ingen organisasjoner funnet.",
|
||||
"search": "Søk…",
|
||||
"searchPlaceholder": "Søk...",
|
||||
"emptySearchOptions": "Ingen valg funnet",
|
||||
"searchProgress": "Søker...",
|
||||
"create": "Opprett",
|
||||
"orgs": "Organisasjoner",
|
||||
"loginError": "En uventet feil oppstod. Vennligst prøv igjen.",
|
||||
@@ -1338,14 +1228,12 @@
|
||||
"sidebarClientResources": "Privat",
|
||||
"sidebarAccessControl": "Tilgangskontroll",
|
||||
"sidebarLogsAndAnalytics": "Logger og analyser",
|
||||
"sidebarTeam": "Lag",
|
||||
"sidebarUsers": "Brukere",
|
||||
"sidebarAdmin": "Administrator",
|
||||
"sidebarInvitations": "Invitasjoner",
|
||||
"sidebarRoles": "Roller",
|
||||
"sidebarShareableLinks": "Lenker",
|
||||
"sidebarApiKeys": "API-nøkler",
|
||||
"sidebarProvisioning": "Levering",
|
||||
"sidebarSettings": "Innstillinger",
|
||||
"sidebarAllUsers": "Alle brukere",
|
||||
"sidebarIdentityProviders": "Identitetsleverandører",
|
||||
@@ -1357,166 +1245,8 @@
|
||||
"sidebarGeneral": "Administrer",
|
||||
"sidebarLogAndAnalytics": "Logg og analyser",
|
||||
"sidebarBluePrints": "Tegninger",
|
||||
"sidebarAlerting": "Varsling",
|
||||
"sidebarHealthChecks": "Helsekontroller",
|
||||
"sidebarOrganization": "Organisasjon",
|
||||
"sidebarManagement": "Administrasjon",
|
||||
"sidebarBillingAndLicenses": "Fakturering & lisenser",
|
||||
"sidebarLogsAnalytics": "Analyser",
|
||||
"alertingTitle": "Varsling",
|
||||
"alertingDescription": "Definer kilder, triggere og handlinger for varsler",
|
||||
"alertingRules": "Varslingsregler",
|
||||
"alertingSearchRules": "Søk i regler…",
|
||||
"alertingAddRule": "Opprett regel",
|
||||
"alertingColumnSource": "Kilde",
|
||||
"alertingColumnTrigger": "Utløser",
|
||||
"alertingColumnActions": "Handlinger",
|
||||
"alertingColumnEnabled": "Aktivert",
|
||||
"alertingDeleteQuestion": "Vennligst bekreft at du vil slette denne varslingsregelen.",
|
||||
"alertingDeleteRule": "Slett varslingsregel",
|
||||
"alertingRuleDeleted": "Varslingsregel slettet",
|
||||
"alertingRuleSaved": "Varslingsregel lagret",
|
||||
"alertingRuleSavedCreatedDescription": "Din nye varslingsregel ble opprettet. Du kan fortsette å redigere den på denne siden.",
|
||||
"alertingRuleSavedUpdatedDescription": "Endringene dine i denne varslingsregelen ble lagret.",
|
||||
"alertingEditRule": "Rediger varslingsregel",
|
||||
"alertingCreateRule": "Opprett varslingsregel",
|
||||
"alertingRuleCredenzaDescription": "Velg hva som skal overvåkes, når det skal varsles, og hvordan du vil bli informert",
|
||||
"alertingRuleNamePlaceholder": "Produksjonsside nede",
|
||||
"alertingRuleEnabled": "Regel aktivert",
|
||||
"alertingSectionSource": "Kilde",
|
||||
"alertingSourceType": "Kildetype",
|
||||
"alertingSourceSite": "Område",
|
||||
"alertingSourceHealthCheck": "Helsekontroll",
|
||||
"alertingPickSites": "Områder",
|
||||
"alertingPickHealthChecks": "Helsekontroller",
|
||||
"alertingPickResources": "Ressurser",
|
||||
"alertingAllSites": "Alle områder",
|
||||
"alertingAllSitesDescription": "Varsler for alle områder",
|
||||
"alertingSpecificSites": "Spesifikke områder",
|
||||
"alertingSpecificSitesDescription": "Velg spesifikke områder for overvåking",
|
||||
"alertingAllHealthChecks": "Alle helsekontroller",
|
||||
"alertingAllHealthChecksDescription": "Varsler for alle helsekontroller",
|
||||
"alertingSpecificHealthChecks": "Spesifikke helsekontroller",
|
||||
"alertingSpecificHealthChecksDescription": "Velg spesifikke helsekontroller for overvåking",
|
||||
"alertingAllResources": "Alle ressurser",
|
||||
"alertingAllResourcesDescription": "Varsler for alle ressurser",
|
||||
"alertingSpecificResources": "Spesifikke ressurser",
|
||||
"alertingSpecificResourcesDescription": "Velg spesifikke ressurser for overvåking",
|
||||
"alertingSelectResources": "Velg ressurser…",
|
||||
"alertingResourcesSelected": "{count} ressurser valgt",
|
||||
"alertingResourcesEmpty": "No resources with targets in the first 10 results.",
|
||||
"alertingSectionTrigger": "Utløser",
|
||||
"alertingTrigger": "Når skal det varsles",
|
||||
"alertingTriggerSiteOnline": "Nettsted er online",
|
||||
"alertingTriggerSiteOffline": "Nettsted er offline",
|
||||
"alertingTriggerSiteToggle": "Endringer i nettstedstatus",
|
||||
"alertingTriggerHcHealthy": "Helsekontroll sunn",
|
||||
"alertingTriggerHcUnhealthy": "Helsekontroll usunn",
|
||||
"alertingTriggerHcToggle": "Endringer i helsekontrollstatus",
|
||||
"alertingTriggerResourceHealthy": "Ressurs sunn",
|
||||
"alertingTriggerResourceUnhealthy": "Ressurs usunn",
|
||||
"alertingSearchHealthChecks": "Søk i helsekontroller…",
|
||||
"alertingHealthChecksEmpty": "Ingen tilgjengelige helsekontroller.",
|
||||
"alertingTriggerResourceToggle": "Endringer i ressursstatus",
|
||||
"alertingSourceResource": "Ressurs",
|
||||
"alertingSectionActions": "Handlinger",
|
||||
"alertingAddAction": "Legg til handling",
|
||||
"alertingActionNotify": "E-post",
|
||||
"alertingActionNotifyDescription": "Send e-postvarsler til brukere eller roller",
|
||||
"alertingActionWebhook": "Webhook",
|
||||
"alertingActionWebhookDescription": "Send en HTTP-forespørsel til et tilpasset endepunkt",
|
||||
"alertingExternalIntegration": "Ekstern integrasjon",
|
||||
"alertingExternalPagerDutyDescription": "Send varsler til PagerDuty for hendelseshåndtering",
|
||||
"alertingExternalOpsgenieDescription": "Rute varsler til Opsgenie for vakt håndtering",
|
||||
"alertingExternalServiceNowDescription": "Opprett ServiceNow hendelser fra varslingseventer",
|
||||
"alertingExternalIncidentIoDescription": "Utløs Incident.io arbeidsflyter fra varsels begivenheter",
|
||||
"alertingActionType": "Handlings type",
|
||||
"alertingNotifyUsers": "Brukere",
|
||||
"alertingNotifyRoles": "Roller",
|
||||
"alertingNotifyEmails": "E-postadresser",
|
||||
"alertingEmailPlaceholder": "Legg til e-post og trykk Enter",
|
||||
"alertingWebhookMethod": "HTTP-metode",
|
||||
"alertingWebhookSecret": "Signeringshemmelig (valgfritt)",
|
||||
"alertingWebhookSecretPlaceholder": "HMAC-hemmelig",
|
||||
"alertingWebhookHeaders": "Overskrifter",
|
||||
"alertingAddHeader": "Legg til header",
|
||||
"alertingSelectSites": "Velg områder…",
|
||||
"alertingSitesSelected": "{count} områder valgt",
|
||||
"alertingSelectHealthChecks": "Velg helsekontroller…",
|
||||
"alertingHealthChecksSelected": "{count} helsekontroller valgt",
|
||||
"alertingNoHealthChecks": "Ingen mål med helsekontroller aktivert",
|
||||
"alertingHealthCheckStub": "Valg av helsekontrollkilde er ikke sluttført ennå - du kan fortsatt konfigurere triggere og handlinger.",
|
||||
"alertingSelectUsers": "Velg brukere…",
|
||||
"alertingUsersSelected": "{count} brukere valgt",
|
||||
"alertingSelectRoles": "Velg roller…",
|
||||
"alertingRolesSelected": "{count} roller valgt",
|
||||
"alertingSummarySites": "Områder ({count})",
|
||||
"alertingSummaryAllSites": "Alle områder",
|
||||
"alertingSummaryHealthChecks": "Helsekontroller ({count})",
|
||||
"alertingSummaryAllHealthChecks": "Alle helsekoner",
|
||||
"alertingSummaryResources": "Ressurser ({count})",
|
||||
"alertingSummaryAllResources": "Alle ressurser",
|
||||
"alertingErrorNameRequired": "Skriv inn et navn",
|
||||
"alertingErrorActionsMin": "Legg til minst én handling",
|
||||
"alertingErrorPickSites": "Velg minst ett område",
|
||||
"alertingErrorPickHealthChecks": "Velg minst én helsekontroll",
|
||||
"alertingErrorPickResources": "Velg minst én ressurs",
|
||||
"alertingErrorTriggerSite": "Velg en triggetjeneste for nettsted",
|
||||
"alertingErrorTriggerHealth": "Velg en triggetjeneste for helsekontroll",
|
||||
"alertingErrorTriggerResource": "Velg en triggetjeneste for ressurs",
|
||||
"alertingErrorNotifyRecipients": "Velg brukere, roller, eller minst én e-post",
|
||||
"alertingConfigureSource": "Konfigurer kilde",
|
||||
"alertingConfigureTrigger": "Konfigurer trigger",
|
||||
"alertingConfigureActions": "Konfigurer handlinger",
|
||||
"alertingBackToRules": "Tilbake til regler",
|
||||
"alertingRuleCooldown": "Nedkjøling (sekunder)",
|
||||
"alertingRuleCooldownDescription": "Minimum tid mellom gjentatte varsler for samme regel. Sett til 0 for å skyte hver gang.",
|
||||
"alertingDraftBadge": "Utkast - lagre for å lagre denne regelen",
|
||||
"alertingSidebarHint": "Klikk på et steg på lerretet for å redigere det her.",
|
||||
"alertingGraphCanvasTitle": "Regel Flyt",
|
||||
"alertingGraphCanvasDescription": "Visuell oversikt over kilde, trigger og handlinger. Velg en node for å redigere den i panelet.",
|
||||
"alertingNodeNotConfigured": "Ikke konfigurert ennå",
|
||||
"alertingNodeActionsCount": "{count, plural, one {# handling} other {# handlinger}}",
|
||||
"alertingNodeRoleSource": "Kilde",
|
||||
"alertingNodeRoleTrigger": "Utløser",
|
||||
"alertingNodeRoleAction": "Handling",
|
||||
"alertingTabRules": "Varslingsregler",
|
||||
"alertingTabHealthChecks": "Helsekontroller",
|
||||
"alertingRulesBannerTitle": "Bli varslet",
|
||||
"alertingRulesBannerDescription": "Hver regel binder sammen hva som skal overvåkes (et område, helsekontroll eller ressurs), når det skal varsles (for eksempel offline eller usunn), og hvordan varsle teamet ditt via e-post, webhooks eller integrasjoner. Bruk denne listen for å opprette, aktivere og administrere disse reglene.",
|
||||
"alertingHealthChecksBannerTitle": "Overvåk helse & ressurser",
|
||||
"alertingHealthChecksBannerDescription": "Helsekontroller er HTTP- eller TCP-monitorer du definerer én gang. Du kan deretter bruke dem som kilder i varslingsregler slik at du blir varslet når et mål blir sunt eller usunt. Helsekontroller på ressurser vises også her.",
|
||||
"standaloneHcTableTitle": "Helsekontroller",
|
||||
"standaloneHcSearchPlaceholder": "Søk i helsekontroller…",
|
||||
"standaloneHcAddButton": "Opprett helsekontroll",
|
||||
"standaloneHcCreateTitle": "Opprett helsekontroll",
|
||||
"standaloneHcEditTitle": "Rediger helsekontroll",
|
||||
"standaloneHcDescription": "Konfigurer en HTTP- eller TCP-helsekontroll for bruk i varslingsregler.",
|
||||
"standaloneHcNameLabel": "Navn",
|
||||
"standaloneHcNamePlaceholder": "Min HTTP-monitor",
|
||||
"standaloneHcDeleteTitle": "Slett helsekontroll",
|
||||
"standaloneHcDeleteQuestion": "Vennligst bekreft at du vil slette denne helsekontrollen.",
|
||||
"standaloneHcDeleted": "Helsekontroll slettet",
|
||||
"standaloneHcSaved": "Helsekontroll lagret",
|
||||
"standaloneHcColumnHealth": "Helse",
|
||||
"standaloneHcColumnMode": "Modus",
|
||||
"standaloneHcColumnTarget": "Mål",
|
||||
"standaloneHcHealthStateHealthy": "Sunn",
|
||||
"standaloneHcHealthStateUnhealthy": "Usunn",
|
||||
"standaloneHcHealthStateUnknown": "Ukjent",
|
||||
"standaloneHcFilterAnySite": "Alle områder",
|
||||
"standaloneHcFilterAnyResource": "Alle ressurser",
|
||||
"standaloneHcFilterMode": "Modus",
|
||||
"standaloneHcFilterModeHttp": "HTTP",
|
||||
"standaloneHcFilterModeTcp": "TCP",
|
||||
"standaloneHcFilterModeSnmp": "SNMP",
|
||||
"standaloneHcFilterModePing": "Ping",
|
||||
"standaloneHcFilterHealth": "Helse",
|
||||
"standaloneHcFilterEnabled": "Aktivert",
|
||||
"standaloneHcFilterEnabledOn": "Aktivert",
|
||||
"standaloneHcFilterEnabledOff": "Deaktivert",
|
||||
"standaloneHcFilterSiteIdFallback": "Område {id}",
|
||||
"standaloneHcFilterResourceIdFallback": "Ressurs {id}",
|
||||
"blueprints": "Tegninger",
|
||||
"blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer",
|
||||
"blueprintAdd": "Legg til blåkopi",
|
||||
@@ -1537,6 +1267,7 @@
|
||||
"parsedContents": "Parastinnhold (kun lese)",
|
||||
"enableDockerSocket": "Aktiver Docker blåkopi",
|
||||
"enableDockerSocketDescription": "Aktiver skraping av Docker Socket for blueprint Etiketter. Socket bane må brukes for nye.",
|
||||
"enableDockerSocketLink": "Lær mer",
|
||||
"viewDockerContainers": "Vis Docker-containere",
|
||||
"containersIn": "Containere i {siteName}",
|
||||
"selectContainerDescription": "Velg en hvilken som helst container for å bruke som vertsnavn for dette målet. Klikk på en port for å bruke en port.",
|
||||
@@ -1664,7 +1395,6 @@
|
||||
"domainPickerNamespace": "Navnerom: {namespace}",
|
||||
"domainPickerShowMore": "Vis mer",
|
||||
"regionSelectorTitle": "Velg Region",
|
||||
"domainPickerRemoteExitNodeWarning": "Tilbudte domener støttes ikke når sider kobles til eksterne avkjøringsnoder. For ressurser som skal være tilgjengelige på eksterne noder, brukes et egendefinert domene i stedet.",
|
||||
"regionSelectorInfo": "Å velge en region hjelper oss med å gi bedre ytelse for din lokasjon. Du trenger ikke være i samme region som serveren.",
|
||||
"regionSelectorPlaceholder": "Velg en region",
|
||||
"regionSelectorComingSoon": "Kommer snart",
|
||||
@@ -1674,11 +1404,10 @@
|
||||
"billingUsageLimitsOverview": "Oversikt over bruksgrenser",
|
||||
"billingMonitorUsage": "Overvåk bruken din i forhold til konfigurerte grenser. Hvis du trenger økte grenser, vennligst kontakt support@pangolin.net.",
|
||||
"billingDataUsage": "Databruk",
|
||||
"billingSites": "Områder",
|
||||
"billingUsers": "Brukere",
|
||||
"billingDomains": "Domener",
|
||||
"billingOrganizations": "Orger",
|
||||
"billingRemoteExitNodes": "Eksterne Noder",
|
||||
"billingOnlineTime": "Online tid for nettsteder",
|
||||
"billingUsers": "Aktive brukere",
|
||||
"billingDomains": "Aktive domener",
|
||||
"billingRemoteExitNodes": "Aktive selvstyrte noder",
|
||||
"billingNoLimitConfigured": "Ingen grense konfigurert",
|
||||
"billingEstimatedPeriod": "Estimert faktureringsperiode",
|
||||
"billingIncludedUsage": "Inkludert Bruk",
|
||||
@@ -1703,24 +1432,15 @@
|
||||
"billingFailedToGetPortalUrl": "Mislyktes å hente portal URL",
|
||||
"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.",
|
||||
"billingSInfo": "Hvor mange nettsteder du kan bruke",
|
||||
"billingUsersInfo": "Hvor mange brukere du kan bruke",
|
||||
"billingDomainInfo": "Hvor mange domener du kan bruke",
|
||||
"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",
|
||||
"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.",
|
||||
"billingUsersInfo": "Du lades for hver bruker i organisasjonen. Fakturering beregnes daglig basert på antall aktive brukerkontoer i dine org.",
|
||||
"billingDomainInfo": "Du lades for hvert domene i organisasjonen. Fakturering beregnes daglig basert på antallet aktive domenekontoer i din org.",
|
||||
"billingRemoteExitNodesInfo": "Du lades for hver håndterte node i organisasjonen. Fakturering beregnes daglig basert på antallet aktive håndterte noder i dine org.",
|
||||
"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.",
|
||||
"failed": "Mislyktes",
|
||||
"createNewOrgDescription": "Opprett en ny organisasjon",
|
||||
"organization": "Organisasjon",
|
||||
"primary": "Primær",
|
||||
"port": "Port",
|
||||
"securityKeyManage": "Administrer sikkerhetsnøkler",
|
||||
"securityKeyDescription": "Legg til eller fjern sikkerhetsnøkler for passordløs autentisering",
|
||||
@@ -1792,42 +1512,6 @@
|
||||
"resourcePortRequired": "Portnummer er påkrevd for ikke-HTTP-ressurser",
|
||||
"resourcePortNotAllowed": "Portnummer skal ikke angis for HTTP-ressurser",
|
||||
"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})",
|
||||
"billingPastDueTitle": "Betalingen har forfalt",
|
||||
"billingPastDueDescription": "Betalingen er forfalt. Vennligst oppdater betalingsmetoden din for å fortsette å bruke den gjeldende funksjonsplanen din. Hvis du ikke har løst deg, vil abonnementet ditt avbrytes, og du vil bli tilbakestilt til gratistiden.",
|
||||
"billingUnpaidTitle": "Abonnement ubetalt",
|
||||
"billingUnpaidDescription": "Ditt abonnement er ubetalt og du har blitt tilbakestilt til gratis kasse. Vennligst oppdater din betalingsmetode for å gjenopprette abonnementet.",
|
||||
"billingIncompleteTitle": "Betaling ufullstendig",
|
||||
"billingIncompleteDescription": "Betalingen er ufullstendig. Vennligst fullfør betalingsprosessen for å aktivere abonnementet.",
|
||||
"billingIncompleteExpiredTitle": "Betaling utløpt",
|
||||
"billingIncompleteExpiredDescription": "Din betaling ble aldri fullført, og har utløpt. Du har blitt tilbakestilt til gratis dekk. Vennligst abonner på nytt for å gjenopprette tilgangen til betalte funksjoner.",
|
||||
"billingManageSubscription": "Administrere ditt abonnement",
|
||||
"billingResolvePaymentIssue": "Vennligst løs ditt betalingsproblem før du oppgraderer eller nedgraderer betalingen",
|
||||
"signUpTerms": {
|
||||
"IAgreeToThe": "Jeg godtar",
|
||||
"termsOfService": "brukervilkårene",
|
||||
@@ -1901,24 +1585,6 @@
|
||||
"timeIsInSeconds": "Tid er i sekunder",
|
||||
"requireDeviceApproval": "Krev enhetsgodkjenning",
|
||||
"requireDeviceApprovalDescription": "Brukere med denne rollen trenger nye enheter godkjent av en admin før de kan koble seg og få tilgang til ressurser.",
|
||||
"sshAccess": "SSH tilgang",
|
||||
"roleAllowSsh": "Tillat SSH",
|
||||
"roleAllowSshAllow": "Tillat",
|
||||
"roleAllowSshDisallow": "Forby",
|
||||
"roleAllowSshDescription": "Tillat brukere med denne rollen å koble til ressurser via SSH. Når deaktivert får rollen ikke tilgang til SSH.",
|
||||
"sshSudoMode": "Sudo tilgang",
|
||||
"sshSudoModeNone": "Ingen",
|
||||
"sshSudoModeNoneDescription": "Brukeren kan ikke kjøre kommandoer med sudo.",
|
||||
"sshSudoModeFull": "Full Sudo",
|
||||
"sshSudoModeFullDescription": "Brukeren kan kjøre hvilken som helst kommando med sudo.",
|
||||
"sshSudoModeCommands": "Kommandoer",
|
||||
"sshSudoModeCommandsDescription": "Brukeren kan bare kjøre de angitte kommandoene med sudo.",
|
||||
"sshSudo": "Tillat sudo",
|
||||
"sshSudoCommands": "Sudo kommandoer",
|
||||
"sshSudoCommandsDescription": "Kommaseparert liste med kommandoer brukeren kan kjøre med sudo.",
|
||||
"sshCreateHomeDir": "Opprett hjemmappe",
|
||||
"sshUnixGroups": "Unix grupper",
|
||||
"sshUnixGroupsDescription": "Kommaseparerte Unix grupper for å legge brukeren til på mål-verten.",
|
||||
"retryAttempts": "Forsøk på nytt",
|
||||
"expectedResponseCodes": "Forventede svarkoder",
|
||||
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
|
||||
@@ -1935,15 +1601,6 @@
|
||||
"healthCheckIntervalMin": "Sjekkeintervallet må være minst 5 sekunder",
|
||||
"healthCheckTimeoutMin": "Timeout må være minst 1 sekund",
|
||||
"healthCheckRetryMin": "Forsøk på nytt må være minst 1",
|
||||
"healthCheckMode": "Sjekk modus",
|
||||
"healthCheckStrategy": "Strategi",
|
||||
"healthCheckModeDescription": "TCP-modus verifiserer kun tilkobling. HTTP-modus validerer HTTP-responsen.",
|
||||
"healthyThreshold": "Sunnhets terskel",
|
||||
"healthyThresholdDescription": "Suksesser på rad som kreves før man markerer som sunn.",
|
||||
"unhealthyThreshold": "Usunn terskel",
|
||||
"unhealthyThresholdDescription": "Feil på rad som kreves før man markerer som usunn.",
|
||||
"healthCheckHealthyThresholdMin": "Sunnhet terskel må være minst 1",
|
||||
"healthCheckUnhealthyThresholdMin": "Usunn terskel må være minst 1",
|
||||
"httpMethod": "HTTP-metode",
|
||||
"selectHttpMethod": "Velg HTTP-metode",
|
||||
"domainPickerSubdomainLabel": "Underdomene",
|
||||
@@ -1968,8 +1625,6 @@
|
||||
"resourcesTableNoInternalResourcesFound": "Ingen interne ressurser funnet.",
|
||||
"resourcesTableDestination": "Destinasjon",
|
||||
"resourcesTableAlias": "Alias",
|
||||
"resourcesTableAliasAddress": "Alias adresse",
|
||||
"resourcesTableAliasAddressInfo": "Denne adressen er en del av organisasjonens undernettverk. Den brukes til å løse aliasposter ved hjelp av intern DNS-oppløsning.",
|
||||
"resourcesTableClients": "Klienter",
|
||||
"resourcesTableAndOnlyAccessibleInternally": "og er kun tilgjengelig internt når de er koblet til med en klient.",
|
||||
"resourcesTableNoTargets": "Ingen mål",
|
||||
@@ -2003,11 +1658,6 @@
|
||||
"editInternalResourceDialogModePort": "Port",
|
||||
"editInternalResourceDialogModeHost": "Vert",
|
||||
"editInternalResourceDialogModeCidr": "CIDR",
|
||||
"editInternalResourceDialogModeHttp": "HTTP",
|
||||
"editInternalResourceDialogModeHttps": "HTTPS",
|
||||
"editInternalResourceDialogScheme": "Skjema",
|
||||
"editInternalResourceDialogEnableSsl": "Aktiver SSL",
|
||||
"editInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.",
|
||||
"editInternalResourceDialogDestination": "Destinasjon",
|
||||
"editInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
|
||||
"editInternalResourceDialogDestinationIPDescription": "IP eller vertsnavn til ressursen på nettstedets nettverk.",
|
||||
@@ -2023,7 +1673,6 @@
|
||||
"createInternalResourceDialogName": "Navn",
|
||||
"createInternalResourceDialogSite": "Område",
|
||||
"selectSite": "Velg område...",
|
||||
"multiSitesSelectorSitesCount": "{count, plural, one {# sted} other {# steder}}",
|
||||
"noSitesFound": "Ingen områder funnet.",
|
||||
"createInternalResourceDialogProtocol": "Protokoll",
|
||||
"createInternalResourceDialogTcp": "TCP",
|
||||
@@ -2052,19 +1701,11 @@
|
||||
"createInternalResourceDialogModePort": "Port",
|
||||
"createInternalResourceDialogModeHost": "Vert",
|
||||
"createInternalResourceDialogModeCidr": "CIDR",
|
||||
"createInternalResourceDialogModeHttp": "HTTP",
|
||||
"createInternalResourceDialogModeHttps": "HTTPS",
|
||||
"scheme": "Skjema",
|
||||
"createInternalResourceDialogScheme": "Skjema",
|
||||
"createInternalResourceDialogEnableSsl": "Aktiver SSL",
|
||||
"createInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.",
|
||||
"createInternalResourceDialogDestination": "Destinasjon",
|
||||
"createInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
|
||||
"createInternalResourceDialogDestinationCidrDescription": "CIDR-rekkevidden til ressursen på nettstedets nettverk.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Et valgfritt internt DNS-alias for denne ressursen.",
|
||||
"internalResourceDownstreamSchemeRequired": "Skjema er påkrevd for HTTP-ressurser",
|
||||
"internalResourceHttpPortRequired": "Destinasjonsport er nødvendig for HTTP-ressurser",
|
||||
"siteConfiguration": "Konfigurasjon",
|
||||
"siteAcceptClientConnections": "Godta klientforbindelser",
|
||||
"siteAcceptClientConnectionsDescription": "Tillat brukere og klienter å få tilgang til ressurser på denne siden. Dette kan endres senere.",
|
||||
@@ -2150,40 +1791,6 @@
|
||||
"exitNode": "Utgangsnode",
|
||||
"country": "Land",
|
||||
"rulesMatchCountry": "For tiden basert på kilde IP",
|
||||
"region": "Fylke",
|
||||
"selectRegion": "Velg region",
|
||||
"searchRegions": "Søk etter områder...",
|
||||
"noRegionFound": "Ingen region funnet.",
|
||||
"rulesMatchRegion": "Velg en regional gruppering av land",
|
||||
"rulesErrorInvalidRegion": "Ugyldig område",
|
||||
"rulesErrorInvalidRegionDescription": "Vennligst velg et gyldig område.",
|
||||
"regionAfrica": "Afrika",
|
||||
"regionNorthernAfrica": "[country name] Nord-Afrika",
|
||||
"regionEasternAfrica": "Øst-Afrika",
|
||||
"regionMiddleAfrica": "Middle Africa",
|
||||
"regionSouthernAfrica": "Sør-Afrika",
|
||||
"regionWesternAfrica": "[country name] Vest-Afrika",
|
||||
"regionAmericas": "Amerika",
|
||||
"regionCaribbean": "Karibia",
|
||||
"regionCentralAmerica": "Sentral-Amerika",
|
||||
"regionSouthAmerica": "Sør-Amerika",
|
||||
"regionNorthernAmerica": "Nord-Amerika",
|
||||
"regionAsia": "Asia",
|
||||
"regionCentralAsia": "Sentral-Asia",
|
||||
"regionEasternAsia": "Øst-Asia",
|
||||
"regionSouthEasternAsia": "Sørøst-Asia",
|
||||
"regionSouthernAsia": "Sørlige Asia",
|
||||
"regionWesternAsia": "Vest-Asia",
|
||||
"regionEurope": "Europa",
|
||||
"regionEasternEurope": "Øst-Europa",
|
||||
"regionNorthernEurope": "Nord-Europa",
|
||||
"regionSouthernEurope": "Sørlige Europa",
|
||||
"regionWesternEurope": "Vest-Europa",
|
||||
"regionOceania": "Oceania",
|
||||
"regionAustraliaAndNewZealand": "Australia og New Zealand",
|
||||
"regionMelanesia": "Melanesia",
|
||||
"regionMicronesia": "Micronesia",
|
||||
"regionPolynesia": "Polynesia",
|
||||
"managedSelfHosted": {
|
||||
"title": "Administrert selv-hostet",
|
||||
"description": "Sikre og lavvedlikeholdsservere, selvbetjente Pangolin med ekstra klokker, og understell",
|
||||
@@ -2222,7 +1829,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Internasjonalt domene oppdaget",
|
||||
"willbestoredas": "Vil bli lagret som:",
|
||||
"roleMappingDescription": "Bestem hvordan roller tildeles brukere når de logger inn med denne identitetsleverandøren.",
|
||||
"roleMappingDescription": "Bestem hvordan roller tilordnes brukere når innloggingen er aktivert når autog-rapportering er aktivert.",
|
||||
"selectRole": "Velg en rolle",
|
||||
"roleMappingExpression": "Uttrykk",
|
||||
"selectRolePlaceholder": "Velg en rolle",
|
||||
@@ -2232,25 +1839,6 @@
|
||||
"invalidValue": "Ugyldig verdi",
|
||||
"idpTypeLabel": "Identitet leverandør type",
|
||||
"roleMappingExpressionPlaceholder": "F.eks. inneholder(grupper, 'admin') && 'Admin' ⋅'Medlem'",
|
||||
"roleMappingModeFixedRoles": "Fast roller",
|
||||
"roleMappingModeMappingBuilder": "Kartlegger bygger",
|
||||
"roleMappingModeRawExpression": "Rå uttrykk",
|
||||
"roleMappingFixedRolesPlaceholderSelect": "Velg en eller flere roller",
|
||||
"roleMappingFixedRolesPlaceholderFreeform": "Skriv inn rollenavn (eksakt treff per organisasjon)",
|
||||
"roleMappingFixedRolesDescriptionSameForAll": "Tilordne den samme rollen som er satt til hver automatisk midlertidig bruker.",
|
||||
"roleMappingFixedRolesDescriptionDefaultPolicy": "For standard policyer, type rollenavn som eksisterer i hver organisasjon der brukerne tilbys. Navn må stemmer nøyaktig.",
|
||||
"roleMappingClaimPath": "Krev sti",
|
||||
"roleMappingClaimPathPlaceholder": "grupper",
|
||||
"roleMappingClaimPathDescription": "Sti i i token nyttelast som inneholder kildeverdier (for eksempel grupper).",
|
||||
"roleMappingMatchValue": "Treff verdi",
|
||||
"roleMappingAssignRoles": "Tilordne roller",
|
||||
"roleMappingAddMappingRule": "Legg til tilordningsregel",
|
||||
"roleMappingRawExpressionResultDescription": "Uttrykk skal vurderes til en streng eller en tekststreng.",
|
||||
"roleMappingRawExpressionResultDescriptionSingleRole": "Uttrykk må evaluere til en streng (en rollenavn).",
|
||||
"roleMappingMatchValuePlaceholder": "Match verdi (for eksempel: admin)",
|
||||
"roleMappingAssignRolesPlaceholderFreeform": "Angi rollenavn (eksakt per org)",
|
||||
"roleMappingBuilderFreeformRowHint": "Rollenavn må samsvare med en rolle i hver målorganisasjon.",
|
||||
"roleMappingRemoveRule": "Fjern",
|
||||
"idpGoogleConfiguration": "Google Konfigurasjon",
|
||||
"idpGoogleConfigurationDescription": "Konfigurer Google OAuth2 legitimasjonen",
|
||||
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
|
||||
@@ -2287,9 +1875,6 @@
|
||||
"authPageBrandingQuestionRemove": "Er du sikker på at du vil fjerne merkevarebyggingen for autentiseringssider?",
|
||||
"authPageBrandingDeleteConfirm": "Bekreft sletting av merkevarebygging",
|
||||
"brandingLogoURL": "Logo URL",
|
||||
"brandingLogoURLOrPath": "Logoen URL eller sti",
|
||||
"brandingLogoPathDescription": "Skriv inn en URL eller en lokal bane.",
|
||||
"brandingLogoURLDescription": "Skriv inn en offentlig tilgjengelig nettadresse til din logobilde.",
|
||||
"brandingPrimaryColor": "Primærfarge",
|
||||
"brandingLogoWidth": "Bredde (px)",
|
||||
"brandingLogoHeight": "Høyde (px)",
|
||||
@@ -2314,10 +1899,8 @@
|
||||
"selectDomainForOrgAuthPage": "Velg et domene for organisasjonens autentiseringsside",
|
||||
"domainPickerProvidedDomain": "Gitt domene",
|
||||
"domainPickerFreeProvidedDomain": "Gratis oppgitt domene",
|
||||
"domainPickerFreeDomainsPaidFeature": "Angitte domener er en betalingsfunksjon. Abonner for å få et domene inkludert i din plan – ingen behov for å ta med ditt eget.",
|
||||
"domainPickerVerified": "Bekreftet",
|
||||
"domainPickerUnverified": "Uverifisert",
|
||||
"domainPickerManual": "Manuell",
|
||||
"domainPickerInvalidSubdomainStructure": "Dette underdomenet inneholder ugyldige tegn eller struktur. Det vil automatisk bli utsatt når du lagrer.",
|
||||
"domainPickerError": "Feil",
|
||||
"domainPickerErrorLoadDomains": "Kan ikke laste organisasjonens domener",
|
||||
@@ -2341,13 +1924,6 @@
|
||||
"orgAuthBackToSignIn": "Tilbake til standard innlogging",
|
||||
"orgAuthNoAccount": "Har du ikke konto?",
|
||||
"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.",
|
||||
"orgAuthPageDisabled": "Informasjons-siden for organisasjon er deaktivert.",
|
||||
"domainRestartedDescription": "Domene-verifiseringen ble startet på nytt",
|
||||
@@ -2535,32 +2111,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": "Funksjoner for bedrifter, 50 brukere, 100 nettsteder og prioritert support."
|
||||
}
|
||||
},
|
||||
"personalUseOnly": "Kun personlig bruk (gratis lisens - ingen kasse)",
|
||||
"buttons": {
|
||||
"continueToCheckout": "Fortsett til kassen"
|
||||
},
|
||||
"toasts": {
|
||||
"checkoutError": {
|
||||
"title": "Feil ved utsjekk",
|
||||
"description": "Kan ikke starte kassen. Prøv på nytt."
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
"instanceName": "Forekomst navn",
|
||||
@@ -2624,7 +2174,6 @@
|
||||
"validPassword": "Gyldig passord",
|
||||
"validEmail": "Valid email",
|
||||
"validSSO": "Valid SSO",
|
||||
"connectedClient": "Tilkoblet klient",
|
||||
"resourceBlocked": "Ressurs blokkert",
|
||||
"droppedByRule": "Legg i regelen",
|
||||
"noSessions": "Ingen økter",
|
||||
@@ -2650,8 +2199,6 @@
|
||||
"logRetentionAccessDescription": "Hvor lenge du vil beholde adgangslogger",
|
||||
"logRetentionActionLabel": "Handlings logg nytt",
|
||||
"logRetentionActionDescription": "Hvor lenge handlingen skal lagres",
|
||||
"logRetentionConnectionLabel": "Logg nyhet",
|
||||
"logRetentionConnectionDescription": "Hvor lenge du vil beholde tilkoblingslogger",
|
||||
"logRetentionDisabled": "Deaktivert",
|
||||
"logRetention3Days": "3 dager",
|
||||
"logRetention7Days": "7 dager",
|
||||
@@ -2662,15 +2209,7 @@
|
||||
"logRetentionEndOfFollowingYear": "Slutt på neste år",
|
||||
"actionLogsDescription": "Vis historikk for handlinger som er utført i denne organisasjonen",
|
||||
"accessLogsDescription": "Vis autoriseringsforespørsler for ressurser i denne organisasjonen",
|
||||
"connectionLogs": "Loggfiler for tilkobling",
|
||||
"connectionLogsDescription": "Vis tilkoblingslogger for tunneler i denne organisasjonen",
|
||||
"sidebarLogsConnection": "Loggfiler for tilkobling",
|
||||
"sidebarLogsStreaming": "Strømming",
|
||||
"sourceAddress": "Kilde adresse",
|
||||
"destinationAddress": "Måladresse (Automatic Translation)",
|
||||
"duration": "Varighet",
|
||||
"licenseRequiredToUse": "En <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisens eller <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> er påkrevd for å bruke denne funksjonen. <bookADemoLink>Bestill en demo eller POC prøveversjon</bookADemoLink>.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Bestill en demo eller POC studie</bookADemoLink>.",
|
||||
"licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.",
|
||||
"certResolver": "Sertifikat løser",
|
||||
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
|
||||
"selectCertResolver": "Velg sertifikatløser",
|
||||
@@ -2812,9 +2351,6 @@
|
||||
"machineClients": "Maskinklienter",
|
||||
"install": "Installer",
|
||||
"run": "Kjør",
|
||||
"envFile": "Miljøfil",
|
||||
"serviceFile": "Tjenestefil",
|
||||
"enableAndStart": "Aktiver og start",
|
||||
"clientNameDescription": "Visningsnavnet til klienten som kan endres senere.",
|
||||
"clientAddress": "Klientadresse (avansert)",
|
||||
"setupFailedToFetchSubnet": "Kunne ikke hente standard undernett",
|
||||
@@ -2864,27 +2400,12 @@
|
||||
"editInternalResourceDialogDestinationLabel": "Destinasjon",
|
||||
"editInternalResourceDialogDestinationDescription": "Spesifiser destinasjonsadressen for den interne ressursen. Dette kan være et vertsnavn, IP-adresse eller CIDR-sjikt avhengig av valgt modus. Valgfrie oppsett av intern DNS-alias for enklere identifikasjon.",
|
||||
"editInternalResourceDialogPortRestrictionsDescription": "Begrens tilgang til spesifikke TCP/UDP-porter eller tillate/blokkere alle porter.",
|
||||
"createInternalResourceDialogHttpConfiguration": "HTTP-konfigurasjon",
|
||||
"createInternalResourceDialogHttpConfigurationDescription": "Velg domenet klienter vil bruke for å nå denne ressursen via HTTP eller HTTPS.",
|
||||
"editInternalResourceDialogHttpConfiguration": "HTTP-konfigurasjon",
|
||||
"editInternalResourceDialogHttpConfigurationDescription": "Velg domenet klienter vil bruke for å nå denne ressursen via HTTP eller HTTPS.",
|
||||
"editInternalResourceDialogTcp": "TCP",
|
||||
"editInternalResourceDialogUdp": "UDP",
|
||||
"editInternalResourceDialogIcmp": "ICMP",
|
||||
"editInternalResourceDialogAccessControl": "Tilgangskontroll",
|
||||
"editInternalResourceDialogAccessControlDescription": "Kontroller hvilke roller, brukere og maskinklienter som har tilgang til denne ressursen når den er koblet til. Administratorer har alltid tilgang.",
|
||||
"editInternalResourceDialogPortRangeValidationError": "Portsjiktet må være \"*\" for alle porter, eller en kommaseparert liste med porter og sjikt (f.eks. \"80,443,8000-9000\"). Porter må være mellom 1 og 65535.",
|
||||
"internalResourceAuthDaemonStrategy": "SSH Auth Daemon Sted",
|
||||
"internalResourceAuthDaemonStrategyDescription": "Velg hvor SSH-autentisering daemon kjører: på nettstedet (Newt) eller på en ekstern vert.",
|
||||
"internalResourceAuthDaemonDescription": "SSH-godkjenning daemon håndterer SSH-nøkkel signering og PAM autentisering for denne ressursen. Velg om den kjører på nettstedet (Newt) eller på en separat ekstern vert. Se <docsLink>dokumentasjonen</docsLink> for mer.",
|
||||
"internalResourceAuthDaemonDocsUrl": "https://docs.pangolin.net",
|
||||
"internalResourceAuthDaemonStrategyPlaceholder": "Velg strategi",
|
||||
"internalResourceAuthDaemonStrategyLabel": "Sted",
|
||||
"internalResourceAuthDaemonSite": "På nettsted",
|
||||
"internalResourceAuthDaemonSiteDescription": "Autentiser daemon kjører på nettstedet (Newt).",
|
||||
"internalResourceAuthDaemonRemote": "Ekstern vert",
|
||||
"internalResourceAuthDaemonRemoteDescription": "Autentiser daemon kjører på en vert som ikke er nettstedet.",
|
||||
"internalResourceAuthDaemonPort": "Daemon Port (valgfritt)",
|
||||
"orgAuthWhatsThis": "Hvor kan jeg finne min organisasjons-ID?",
|
||||
"learnMore": "Lær mer",
|
||||
"backToHome": "Gå tilbake til start",
|
||||
@@ -2906,8 +2427,6 @@
|
||||
"maintenancePageMessagePlaceholder": "Vi kommer snart tilbake! Vårt nettsted gjennomgår for øyeblikket planlagt vedlikehold.",
|
||||
"maintenancePageMessageDescription": "Detaljert beskjed som forklarer vedlikeholdet",
|
||||
"maintenancePageTimeTitle": "Estimert ferdigstillelsestid (Valgfritt)",
|
||||
"privateMaintenanceScreenTitle": "Privat plassholder skjerm",
|
||||
"privateMaintenanceScreenMessage": "Dette domenet brukes på en privatressurs. Koble til ved å bruke Pangolin-klienten for å få tilgang til denne ressursen.",
|
||||
"maintenanceTime": "f.eks. 2 timer, 1. november kl. 17:00",
|
||||
"maintenanceEstimatedTimeDescription": "Når du forventer at vedlikeholdet er ferdigstilt",
|
||||
"editDomain": "Rediger domene",
|
||||
@@ -2970,8 +2489,8 @@
|
||||
"logIn": "Logg inn",
|
||||
"deviceInformation": "Enhetens informasjon",
|
||||
"deviceInformationDescription": "Informasjon om enheten og agenten",
|
||||
"deviceSecurity": "Enhetens sikkerhet",
|
||||
"deviceSecurityDescription": "Sikkerhetsstillings informasjon om utstyr",
|
||||
"deviceSecurity": "Device Security",
|
||||
"deviceSecurityDescription": "Device security posture information",
|
||||
"platform": "Plattform",
|
||||
"macosVersion": "macOS versjon",
|
||||
"windowsVersion": "Windows versjon",
|
||||
@@ -2984,16 +2503,16 @@
|
||||
"hostname": "Hostname",
|
||||
"firstSeen": "Først sett",
|
||||
"lastSeen": "Sist sett",
|
||||
"biometricsEnabled": "Biometri aktivert",
|
||||
"diskEncrypted": "Disk kryptert",
|
||||
"firewallEnabled": "Brannmur aktivert",
|
||||
"autoUpdatesEnabled": "Automatiske oppdateringer aktivert",
|
||||
"tpmAvailable": "TPM tilgjengelig",
|
||||
"windowsAntivirusEnabled": "Antivirus aktivert",
|
||||
"macosSipEnabled": "System Integritetsbeskyttelse (SIP)",
|
||||
"biometricsEnabled": "Biometrics Enabled",
|
||||
"diskEncrypted": "Disk Encrypted",
|
||||
"firewallEnabled": "Firewall Enabled",
|
||||
"autoUpdatesEnabled": "Auto Updates Enabled",
|
||||
"tpmAvailable": "TPM Available",
|
||||
"windowsDefenderEnabled": "Windows Defender Enabled",
|
||||
"macosSipEnabled": "System Integrity Protection (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Brannmur Usynlig Modus",
|
||||
"linuxAppArmorEnabled": "Rustning",
|
||||
"macosFirewallStealthMode": "Firewall Stealth Mode",
|
||||
"linuxAppArmorEnabled": "AppArmor",
|
||||
"linuxSELinuxEnabled": "SELinux",
|
||||
"deviceSettingsDescription": "Vis enhetsinformasjon og innstillinger",
|
||||
"devicePendingApprovalDescription": "Denne enheten venter på godkjenning",
|
||||
@@ -3016,131 +2535,5 @@
|
||||
"approvalsEmptyStateStep2Title": "Aktiver enhetsgodkjenninger",
|
||||
"approvalsEmptyStateStep2Description": "Rediger en rolle og aktiver alternativet 'Kreve enhetsgodkjenninger'. Brukere med denne rollen vil trenge administratorgodkjenning for nye enheter.",
|
||||
"approvalsEmptyStatePreviewDescription": "Forhåndsvisning: Når aktivert, ventende enhets forespørsler vil vises her for vurdering",
|
||||
"approvalsEmptyStateButtonText": "Administrer Roller",
|
||||
"domainErrorTitle": "Vi har problemer med å verifisere domenet ditt",
|
||||
"idpAdminAutoProvisionPoliciesTabHint": "Konfigurer rollegartlegging og organisasjonspolicyer på <policiesTabLink>Auto leveringsinnstillinger</policiesTabLink> fanen.",
|
||||
"streamingTitle": "Hendelse Strømming",
|
||||
"streamingDescription": "Stream hendelser fra din organisasjon til eksterne destinasjoner i sanntid.",
|
||||
"streamingUnnamedDestination": "Plassering uten navn",
|
||||
"streamingNoUrlConfigured": "Ingen URL konfigurert",
|
||||
"streamingAddDestination": "Legg til mål",
|
||||
"streamingHttpWebhookTitle": "HTTP Webhook",
|
||||
"streamingHttpWebhookDescription": "Send hendelser til alle HTTP-endepunkter med fleksibel autentisering og maling.",
|
||||
"streamingS3Title": "Amazon S3",
|
||||
"streamingS3Description": "Strøm hendelser til en S3-kompatibel objektlagringskjøt. Kommer snart.",
|
||||
"streamingDatadogTitle": "Datadog",
|
||||
"streamingDatadogDescription": "Videresend arrangementer direkte til din Datadog-konto. Kommer snart.",
|
||||
"streamingTypePickerDescription": "Velg en måltype for å komme i gang.",
|
||||
"streamingFailedToLoad": "Kan ikke laste inn destinasjoner",
|
||||
"streamingUnexpectedError": "En uventet feil oppstod.",
|
||||
"streamingFailedToUpdate": "Kunne ikke oppdatere destinasjon",
|
||||
"streamingDeletedSuccess": "Målet ble slettet",
|
||||
"streamingFailedToDelete": "Kunne ikke slette destinasjon",
|
||||
"streamingDeleteTitle": "Slett mål",
|
||||
"streamingDeleteButtonText": "Slett mål",
|
||||
"streamingDeleteDialogAreYouSure": "Er du sikker på at du vil slette",
|
||||
"streamingDeleteDialogThisDestination": "denne destinasjonen",
|
||||
"streamingDeleteDialogPermanentlyRemoved": "? Alle konfigurasjoner vil bli slettet permanent.",
|
||||
"httpDestEditTitle": "Rediger mål",
|
||||
"httpDestAddTitle": "Legg til HTTP-destinasjon",
|
||||
"httpDestEditDescription": "Oppdater konfigurasjonen for denne HTTP-hendelsesstrømmedestinasjonen.",
|
||||
"httpDestAddDescription": "Konfigurer et nytt HTTP endepunkt for å motta organisasjonens hendelser.",
|
||||
"S3DestEditTitle": "Rediger destinasjon",
|
||||
"S3DestAddTitle": "Legg til S3 destinasjon",
|
||||
"S3DestEditDescription": "Oppdatere konfigurasjonen for denne S3-hendelsesstrømmingsdestinasjonen.",
|
||||
"S3DestAddDescription": "Konfigurer et nytt S3-endepunkt for å motta organisasjonens hendelser.",
|
||||
"datadogDestEditTitle": "Rediger destinasjon",
|
||||
"datadogDestAddTitle": "Legg til Datadog destinasjon",
|
||||
"datadogDestEditDescription": "Oppdatere konfigurasjonen for denne Datadog-hendelsesstrømmingsdestinasjonen.",
|
||||
"datadogDestAddDescription": "Konfigurer et nytt Datadog-endepunkt for å motta organisasjonens hendelser.",
|
||||
"httpDestTabSettings": "Innstillinger",
|
||||
"httpDestTabHeaders": "Overskrifter",
|
||||
"httpDestTabBody": "Innhold",
|
||||
"httpDestTabLogs": "Logger",
|
||||
"httpDestNamePlaceholder": "Min HTTP destinasjon",
|
||||
"httpDestUrlLabel": "Destinasjons URL",
|
||||
"httpDestUrlErrorHttpRequired": "URL-adressen må bruke httpp eller https",
|
||||
"httpDestUrlErrorHttpsRequired": "HTTPS er nødvendig for distribusjon av sky",
|
||||
"httpDestUrlErrorInvalid": "Skriv inn en gyldig nettadresse (f.eks. https://eksempel.com/webhook)",
|
||||
"httpDestAuthTitle": "Autentisering",
|
||||
"httpDestAuthDescription": "Velg hvordan ønsker til sluttpunktet ditt er autentisert.",
|
||||
"httpDestAuthNoneTitle": "Ingen godkjenning",
|
||||
"httpDestAuthNoneDescription": "Sender forespørsler uten autorisasjonsoverskrift.",
|
||||
"httpDestAuthBearerTitle": "Bærer Symbol",
|
||||
"httpDestAuthBearerDescription": "Legger til en Autorisasjon: Bearer '<token>' header til hver forespørsel.",
|
||||
"httpDestAuthBearerPlaceholder": "Din API-nøkkel eller token",
|
||||
"httpDestAuthBasicTitle": "Standard Auth",
|
||||
"httpDestAuthBasicDescription": "Legger til en Autorisasjon: Basic '<credentials>' header. Gi legitimasjon som brukernavn:passord.",
|
||||
"httpDestAuthBasicPlaceholder": "brukernavn:passord",
|
||||
"httpDestAuthCustomTitle": "Egendefinert topptekst",
|
||||
"httpDestAuthCustomDescription": "Angi et egendefinert HTTP headers navn og verdi for autentisering (f.eks X-API-Key).",
|
||||
"httpDestAuthCustomHeaderNamePlaceholder": "Topptekst navn (f.eks X-API-Key)",
|
||||
"httpDestAuthCustomHeaderValuePlaceholder": "Header verdi",
|
||||
"httpDestCustomHeadersTitle": "Egendefinerte HTTP-overskrifter",
|
||||
"httpDestCustomHeadersDescription": "Legg til egendefinerte overskrifter til hver utgående forespørsel. Nyttig for statisk tokens eller en egendefinert innholdstype. Som standard blir innholdstype: applikasjon/json sendt.",
|
||||
"httpDestNoHeadersConfigured": "Ingen egendefinerte overskrifter konfigurert. Klikk \"Legg til topptekst\" for å legge til en.",
|
||||
"httpDestHeaderNamePlaceholder": "Navn på topptekst",
|
||||
"httpDestHeaderValuePlaceholder": "Verdi",
|
||||
"httpDestAddHeader": "Legg til topptekst",
|
||||
"httpDestBodyTemplateTitle": "Egendefinert hovedmal",
|
||||
"httpDestBodyTemplateDescription": "Kontroller JSON nyttelaststrukturen sendt til ditt endepunkt. Hvis deaktivert, sendes et standard JSON-objekt for hver hendelse.",
|
||||
"httpDestEnableBodyTemplate": "Aktiver egendefinert meldingsmal",
|
||||
"httpDestBodyTemplateLabel": "Kroppsmal (JSON)",
|
||||
"httpDestBodyTemplateHint": "Bruk designmal variabler for å referere til eventfelt i din betaling.",
|
||||
"httpDestPayloadFormatTitle": "Mål format",
|
||||
"httpDestPayloadFormatDescription": "Hvordan blir hendelser serialisert inn i hver forespørselsorgan.",
|
||||
"httpDestFormatJsonArrayTitle": "JSON liste",
|
||||
"httpDestFormatJsonArrayDescription": "Én forespørsel per batch, innholdet er en JSON-liste. Kompatibel med de mest generiske webhooks og Datadog.",
|
||||
"httpDestFormatNdjsonTitle": "NDJSON",
|
||||
"httpDestFormatNdjsonDescription": "Én forespørsel per sats, innholdet er nytt avgrenset JSON - et objekt per linje, ingen ytterarray. Kreves av Splunk HEC, Elastisk/OpenSearch, og Grafana Loki.",
|
||||
"httpDestFormatSingleTitle": "En hendelse per forespørsel",
|
||||
"httpDestFormatSingleDescription": "Sender en separat HTTP POST for hver enkelt hendelse. Bruk bare for endepunkter som ikke kan håndtere batcher.",
|
||||
"httpDestLogTypesTitle": "Logg typer",
|
||||
"httpDestLogTypesDescription": "Velg hvilke loggtyper som blir videresendt til dette målet. Bare aktiverte loggtyper vil bli strømmet.",
|
||||
"httpDestAccessLogsTitle": "Tilgangslogger (Automatic Translation)",
|
||||
"httpDestAccessLogsDescription": "Adgangsforsøk for ressurser, inkludert godkjente og nektet forespørsler.",
|
||||
"httpDestActionLogsTitle": "Handlingslogger",
|
||||
"httpDestActionLogsDescription": "Administrative tiltak som utføres av brukere innenfor organisasjonen.",
|
||||
"httpDestConnectionLogsTitle": "Loggfiler for tilkobling",
|
||||
"httpDestConnectionLogsDescription": "Utstyrs- og tunneltilkoblingshendelser, inkludert forbindelser og frakobling.",
|
||||
"httpDestRequestLogsTitle": "Forespørselslogger (Automatic Translation)",
|
||||
"httpDestRequestLogsDescription": "HTTP-forespørsel logger for bekreftede ressurser, inkludert metode, bane og responskode.",
|
||||
"httpDestSaveChanges": "Lagre endringer",
|
||||
"httpDestCreateDestination": "Opprett mål",
|
||||
"httpDestUpdatedSuccess": "Målet er oppdatert",
|
||||
"httpDestCreatedSuccess": "Målet er opprettet",
|
||||
"httpDestUpdateFailed": "Kunne ikke oppdatere destinasjon",
|
||||
"httpDestCreateFailed": "Kan ikke opprette mål",
|
||||
"followRedirects": "Følg videresendinger",
|
||||
"followRedirectsDescription": "Følg automatisk HTTP-videresendinger for forespørsler.",
|
||||
"alertingErrorWebhookUrl": "Vennligst skriv inn en gyldig URL for webhooken.",
|
||||
"healthCheckStrategyHttp": "Validerer tilkobling og sjekker HTTP-responsstatus.",
|
||||
"healthCheckStrategyTcp": "Bekrefter kun TCP-tilkobling, uten å inspisere responsen.",
|
||||
"healthCheckStrategySnmp": "Utfører en SNMP get-forespørsel for å sjekke helsen til nettverksenheter og infrastruktur.",
|
||||
"healthCheckStrategyIcmp": "Bruker ICMP ekko forespørsler (ping) for å sjekke om en ressurs er tilgjengelig og responsiv.",
|
||||
"healthCheckTabStrategy": "Strategi",
|
||||
"healthCheckTabConnection": "Tilkobling",
|
||||
"healthCheckTabAdvanced": "Avansert",
|
||||
"healthCheckStrategyNotAvailable": "Denne strategien er ikke tilgjengelig. Vennligst kontakt salgsavdelingen for å aktivere denne funksjonen.",
|
||||
"uptime30d": "Oppetid (30d)",
|
||||
"idpAddActionCreateNew": "Opprett ny identitetsleverandør",
|
||||
"idpAddActionImportFromOrg": "Importer fra en annen organisasjon",
|
||||
"idpImportDialogTitle": "Importer identitetsleverandør",
|
||||
"idpImportDialogDescription": "Velg en identitetsleverandør fra en organisasjon der du er admin. Den vil bli knyttet til denne organisasjonen.",
|
||||
"idpImportSearchPlaceholder": "Søk etter organisasjons- eller leverandørnavn...",
|
||||
"idpImportEmpty": "Ingen identitetsleverandører funnet.",
|
||||
"idpImportedDescription": "Identitetsleverandøren ble importert vellykket.",
|
||||
"idpDeleteGlobalQuestion": "Er du sikker på at du vil slette denne identitetsleverandøren permanent?",
|
||||
"idpDeleteGlobalDescription": "Dette vil slette identitetsleverandøren permanent fra alle organisasjoner den er tilknyttet.",
|
||||
"idpUnassociateTitle": "Frakoble identitetsleverandør",
|
||||
"idpUnassociateQuestion": "Er du sikker på at du vil frakoble denne identitetsleverandøren fra denne organisasjonen?",
|
||||
"idpUnassociateDescription": "Alle brukere knyttet til denne identitetsleverandøren vil bli fjernet fra denne organisasjonen, men identitetsleverandøren vil fortsatt eksistere for andre tilknyttede organisasjoner.",
|
||||
"idpUnassociateConfirm": "Bekreft frakobling av identitetsleverandør",
|
||||
"idpUnassociateWarning": "Dette kan ikke angres for denne organisasjonen.",
|
||||
"idpUnassociatedDescription": "Identitetsleverandør er vellykket frakoblet fra denne organisasjonen",
|
||||
"idpUnassociateMenu": "Frakoble",
|
||||
"idpDeleteAllOrgsMenu": "Slett",
|
||||
"publicIpEndpoint": "Endepunkt",
|
||||
"lastTriggeredAt": "Siste utløste",
|
||||
"reject": "Avvis"
|
||||
"approvalsEmptyStateButtonText": "Administrer Roller"
|
||||
}
|
||||
|
||||
+26
-633
@@ -1,8 +1,4 @@
|
||||
{
|
||||
"contactSalesEnable": "Neem contact op met de verkoopafdeling om deze functie in te schakelen.",
|
||||
"contactSalesBookDemo": "Boek een demo",
|
||||
"contactSalesOr": "of",
|
||||
"contactSalesContactUs": "neem contact met ons op",
|
||||
"setupCreate": "Maak de organisatie, site en bronnen aan",
|
||||
"headerAuthCompatibilityInfo": "Schakel dit in om een 401 Niet Geautoriseerd antwoord af te dwingen wanneer een authenticatietoken ontbreekt. Dit is vereist voor browsers of specifieke HTTP-bibliotheken die geen referenties verzenden zonder een serveruitdaging.",
|
||||
"headerAuthCompatibility": "Uitgebreide compatibiliteit",
|
||||
@@ -22,16 +18,6 @@
|
||||
"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.",
|
||||
"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.",
|
||||
"trialBannerMessage": "Uw proefversie verloopt over {countdown}. Upgrade om toegang te behouden.",
|
||||
"trialBannerExpired": "Uw proefperiode is verlopen. Upgrade nu om toegang te herstellen.",
|
||||
"trialActive": "Gratis proefversie actief",
|
||||
"trialExpired": "Proefversie verlopen",
|
||||
"trialHasEnded": "Uw proefperiode is geëindigd.",
|
||||
"trialDaysRemaining": "{count, plural, one {# dag resterend} other {# dagen resterend}}",
|
||||
"trialDaysLeftShort": "{days}d over in proefversie",
|
||||
"trialGoToBilling": "Ga naar factureringspagina",
|
||||
"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.",
|
||||
"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.",
|
||||
@@ -160,11 +146,6 @@
|
||||
"createLink": "Koppeling aanmaken",
|
||||
"resourcesNotFound": "Geen bronnen gevonden",
|
||||
"resourceSearch": "Zoek bronnen",
|
||||
"machineSearch": "Zoek machines",
|
||||
"machinesSearch": "Zoek machine-clients...",
|
||||
"machineNotFound": "Geen machines gevonden",
|
||||
"userDeviceSearch": "Gebruikersapparaten zoeken",
|
||||
"userDevicesSearch": "Gebruikersapparaten zoeken...",
|
||||
"openMenu": "Menu openen",
|
||||
"resource": "Bron",
|
||||
"title": "Aanspreektitel",
|
||||
@@ -192,7 +173,6 @@
|
||||
"resourceHTTPDescription": "Proxyverzoeken via HTTPS met een volledig gekwalificeerde domeinnaam.",
|
||||
"resourceRaw": "TCP/UDP bron",
|
||||
"resourceRawDescription": "Proxyverzoeken via ruwe TCP/UDP met een poortnummer.",
|
||||
"resourceRawDescriptionCloud": "Proxy verzoeken over rauwe TCP/UDP met behulp van een poortnummer. Vereist sites om verbinding te maken met een remote node.",
|
||||
"resourceCreate": "Bron maken",
|
||||
"resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken",
|
||||
"resourceSeeAll": "Alle bronnen bekijken",
|
||||
@@ -219,7 +199,6 @@
|
||||
"protocolSelect": "Selecteer een protocol",
|
||||
"resourcePortNumber": "Nummer van poort",
|
||||
"resourcePortNumberDescription": "Het externe poortnummer naar proxyverzoeken.",
|
||||
"back": "Achterzijde",
|
||||
"cancel": "Annuleren",
|
||||
"resourceConfig": "Configuratie tekstbouwstenen",
|
||||
"resourceConfigDescription": "Kopieer en plak deze configuratie-snippets om de TCP/UDP-bron in te stellen",
|
||||
@@ -265,25 +244,11 @@
|
||||
"orgErrorDeleteMessage": "Er is een fout opgetreden tijdens het verwijderen van de organisatie.",
|
||||
"orgDeleted": "Organisatie verwijderd",
|
||||
"orgDeletedMessage": "De organisatie en haar gegevens zijn verwijderd.",
|
||||
"deleteAccount": "Verwijder account",
|
||||
"deleteAccountDescription": "Verwijdert permanent uw account, alle organisaties die u bezit, en alle gegevens binnen deze organisaties. Dit kan niet ongedaan worden gemaakt.",
|
||||
"deleteAccountButton": "Verwijder account",
|
||||
"deleteAccountConfirmTitle": "Verwijder account",
|
||||
"deleteAccountConfirmMessage": "Dit zal uw account permanent wissen, alle organisaties die u bezit, en alle gegevens binnen deze organisaties. Dit kan niet ongedaan worden gemaakt.",
|
||||
"deleteAccountConfirmString": "verwijder account",
|
||||
"deleteAccountSuccess": "Account verwijderd",
|
||||
"deleteAccountSuccessMessage": "Uw account is verwijderd.",
|
||||
"deleteAccountError": "Kan account niet verwijderen",
|
||||
"deleteAccountPreviewAccount": "Uw account",
|
||||
"deleteAccountPreviewOrgs": "Organisaties die je bezit (en al hun gegevens)",
|
||||
"orgMissing": "Organisatie-ID ontbreekt",
|
||||
"orgMissingMessage": "Niet in staat om de uitnodiging te regenereren zonder organisatie-ID.",
|
||||
"accessUsersManage": "Gebruikers beheren",
|
||||
"accessUserManage": "Beheer gebruiker",
|
||||
"accessUsersDescription": "Nodig uit en beheer gebruikers met toegang tot deze organisatie",
|
||||
"accessUsersSearch": "Gebruikers zoeken...",
|
||||
"accessUsersRoleFilterCount": "{count, plural, one {# rol} other {# rollen}}",
|
||||
"accessUsersRoleFilterClear": "Rolfilters wissen",
|
||||
"accessUserCreate": "Gebruiker aanmaken",
|
||||
"accessUserRemove": "Gebruiker verwijderen",
|
||||
"username": "Gebruikersnaam",
|
||||
@@ -343,54 +308,6 @@
|
||||
"apiKeysDelete": "API-sleutel verwijderen",
|
||||
"apiKeysManage": "API-sleutels beheren",
|
||||
"apiKeysDescription": "API-sleutels worden gebruikt om te verifiëren met de integratie-API",
|
||||
"provisioningKeysTitle": "Vertrekkende sleutel",
|
||||
"provisioningKeysManage": "Beheren van Provisioning Sleutels",
|
||||
"provisioningKeysDescription": "Provisionerende sleutels worden gebruikt om geautomatiseerde sitebepaling voor uw organisatie te verifiëren.",
|
||||
"provisioningManage": "Provisie",
|
||||
"provisioningDescription": "Voorzieningssleutels beheren en sites beoordelen in afwachting van goedkeuring.",
|
||||
"pendingSites": "Openstaande sites",
|
||||
"siteApproveSuccess": "Site succesvol goedgekeurd",
|
||||
"siteApproveError": "Fout bij goedkeuren website",
|
||||
"provisioningKeys": "Verhelderende sleutels",
|
||||
"searchProvisioningKeys": "Zoek provisioningsleutels ...",
|
||||
"provisioningKeysAdd": "Genereer Provisioning Sleutel",
|
||||
"provisioningKeysErrorDelete": "Fout bij verwijderen provisioning sleutel",
|
||||
"provisioningKeysErrorDeleteMessage": "Fout bij verwijderen provisioning sleutel",
|
||||
"provisioningKeysQuestionRemove": "Weet u zeker dat u deze proefsleutel van de organisatie wilt verwijderen?",
|
||||
"provisioningKeysMessageRemove": "Eenmaal verwijderd, kan de sleutel niet meer worden gebruikt voor site-instructie.",
|
||||
"provisioningKeysDeleteConfirm": "Bevestig Verwijderen Provisione-sleutel",
|
||||
"provisioningKeysDelete": "Provisione-sleutel verwijderen",
|
||||
"provisioningKeysCreate": "Genereer Provisioning Sleutel",
|
||||
"provisioningKeysCreateDescription": "Een nieuwe provisioningsleutel voor de organisatie genereren",
|
||||
"provisioningKeysSeeAll": "Bekijk alle provisioning sleutels",
|
||||
"provisioningKeysSave": "Sla de provisioning sleutel op",
|
||||
"provisioningKeysSaveDescription": "Je kunt dit slechts één keer zien. Kopieer het naar een veilige plaats.",
|
||||
"provisioningKeysErrorCreate": "Fout bij aanmaken provisioning sleutel",
|
||||
"provisioningKeysList": "Nieuwe provisioning sleutel",
|
||||
"provisioningKeysMaxBatchSize": "Maximale batchgrootte",
|
||||
"provisioningKeysUnlimitedBatchSize": "Onbeperkte batchgrootte (geen limiet)",
|
||||
"provisioningKeysMaxBatchUnlimited": "Onbeperkt",
|
||||
"provisioningKeysMaxBatchSizeInvalid": "Voer een geldige maximale batchgrootte in (1–1.000,000).",
|
||||
"provisioningKeysValidUntil": "Geldig tot",
|
||||
"provisioningKeysValidUntilHint": "Laat leeg voor geen vervaldatum.",
|
||||
"provisioningKeysValidUntilInvalid": "Voer een geldige datum en tijd in.",
|
||||
"provisioningKeysNumUsed": "Aantal keer gebruikt",
|
||||
"provisioningKeysLastUsed": "Laatst gebruikt",
|
||||
"provisioningKeysNoExpiry": "Geen vervaldatum",
|
||||
"provisioningKeysNeverUsed": "Nooit",
|
||||
"provisioningKeysEdit": "Wijzig Provisioning Sleutel",
|
||||
"provisioningKeysEditDescription": "Werk de maximale batchgrootte en verlooptijd voor deze sleutel bij.",
|
||||
"provisioningKeysApproveNewSites": "Goedkeuren van nieuwe sites",
|
||||
"provisioningKeysApproveNewSitesDescription": "Automatisch sites goedkeuren die zich registreren met deze sleutel.",
|
||||
"provisioningKeysUpdateError": "Fout tijdens bijwerken provisioning sleutel",
|
||||
"provisioningKeysUpdated": "Provisie sleutel bijgewerkt",
|
||||
"provisioningKeysUpdatedDescription": "Uw wijzigingen zijn opgeslagen.",
|
||||
"provisioningKeysBannerTitle": "Bewerkingssleutels voor websites",
|
||||
"provisioningKeysBannerDescription": "Genereer een inrichtingssleutel en gebruik deze met de Newt-connector om automatisch sites te maken bij de eerste opstart - er is geen behoefte om aparte inloggegevens voor elke site in te stellen.",
|
||||
"provisioningKeysBannerButtonText": "Meer informatie",
|
||||
"pendingSitesBannerTitle": "Openstaande sites",
|
||||
"pendingSitesBannerDescription": "Sites die verbinding maken met een inrichtingssleutel verschijnen hier voor beoordeling.",
|
||||
"pendingSitesBannerButtonText": "Meer informatie",
|
||||
"apiKeysSettings": "{apiKeyName} instellingen",
|
||||
"userTitle": "Alle gebruikers beheren",
|
||||
"userDescription": "Bekijk en beheer alle gebruikers in het systeem",
|
||||
@@ -420,10 +337,6 @@
|
||||
"licenseErrorKeyActivate": "Licentiesleutel activeren mislukt",
|
||||
"licenseErrorKeyActivateDescription": "Er is een fout opgetreden tijdens het activeren van de licentiesleutel.",
|
||||
"licenseAbout": "Over licenties",
|
||||
"licenseBannerTitle": "Activeer Uw Enterprise Licentie",
|
||||
"licenseBannerDescription": "Ontgrendel enterprise-functies voor uw zelf-gehoste Pangolin-instantie. Koop een licentiesleutel om premium mogelijkheden te activeren, voeg deze vervolgens hieronder toe.",
|
||||
"licenseBannerGetLicense": "Koop een Licentie",
|
||||
"licenseBannerViewDocs": "Bekijk Documentatie",
|
||||
"communityEdition": "Community editie",
|
||||
"licenseAboutDescription": "Dit geldt voor gebruikers van bedrijven en ondernemingen die Pangolin in gebruiken in een commerciële omgeving. Als u Pangolin gebruikt voor persoonlijk gebruik, kunt u dit gedeelte negeren.",
|
||||
"licenseKeyActivated": "Licentiesleutel geactiveerd",
|
||||
@@ -546,8 +459,6 @@
|
||||
"filterByApprovalState": "Filter op goedkeuringsstatus",
|
||||
"approvalListEmpty": "Geen goedkeuringen",
|
||||
"approvalState": "Goedkeuring status",
|
||||
"approvalLoadMore": "Meer laden",
|
||||
"loadingApprovals": "Goedkeuringen laden",
|
||||
"approve": "Goedkeuren",
|
||||
"approved": "Goedgekeurd",
|
||||
"denied": "Geweigerd",
|
||||
@@ -581,12 +492,9 @@
|
||||
"userSaved": "Gebruiker opgeslagen",
|
||||
"userSavedDescription": "De gebruiker is bijgewerkt.",
|
||||
"autoProvisioned": "Automatisch bevestigen",
|
||||
"autoProvisionSettings": "Auto Provisie Instellingen",
|
||||
"autoProvisionedDescription": "Toestaan dat deze gebruiker automatisch wordt beheerd door een identiteitsprovider",
|
||||
"accessControlsDescription": "Beheer wat deze gebruiker toegang heeft tot en doet in de organisatie",
|
||||
"accessControlsSubmit": "Bewaar Toegangsbesturing",
|
||||
"singleRolePerUserPlanNotice": "Uw plan ondersteunt slechts één rol per gebruiker.",
|
||||
"singleRolePerUserEditionNotice": "Deze editie ondersteunt slechts één rol per gebruiker.",
|
||||
"roles": "Rollen",
|
||||
"accessUsersRoles": "Beheer Gebruikers & Rollen",
|
||||
"accessUsersRolesDescription": "Nodig gebruikers uit en voeg ze toe aan de rollen om toegang tot de organisatie te beheren",
|
||||
@@ -643,8 +551,6 @@
|
||||
"targetErrorInvalidPortDescription": "Voer een geldig poortnummer in",
|
||||
"targetErrorNoSite": "Geen site geselecteerd",
|
||||
"targetErrorNoSiteDescription": "Selecteer een site voor het doel",
|
||||
"targetTargetsCleared": "Doelen gewist",
|
||||
"targetTargetsClearedDescription": "Alle doelen zijn verwijderd van deze bron",
|
||||
"targetCreated": "Doel aangemaakt",
|
||||
"targetCreatedDescription": "Doel is succesvol aangemaakt",
|
||||
"targetErrorCreate": "Kan doel niet aanmaken",
|
||||
@@ -728,7 +634,6 @@
|
||||
"resourcesErrorUpdate": "Bron wisselen mislukt",
|
||||
"resourcesErrorUpdateDescription": "Er is een fout opgetreden tijdens het bijwerken van het document",
|
||||
"access": "Toegangsrechten",
|
||||
"accessControl": "Toegangs controle",
|
||||
"shareLink": "{resource} Share link",
|
||||
"resourceSelect": "Selecteer resource",
|
||||
"shareLinks": "Links delen",
|
||||
@@ -869,7 +774,6 @@
|
||||
"accessRoleRemoved": "Rol verwijderd",
|
||||
"accessRoleRemovedDescription": "De rol is succesvol verwijderd.",
|
||||
"accessRoleRequiredRemove": "Voordat u deze rol verwijdert, selecteer een nieuwe rol om bestaande leden aan te dragen.",
|
||||
"network": "Netwerk",
|
||||
"manage": "Beheren",
|
||||
"sitesNotFound": "Geen sites gevonden.",
|
||||
"pangolinServerAdmin": "Serverbeheer - Pangolin",
|
||||
@@ -885,9 +789,6 @@
|
||||
"sitestCountIncrease": "Toename van site vergroten",
|
||||
"idpManage": "Identiteitsaanbieders 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",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Weet u zeker dat u de identiteitsprovider permanent wilt verwijderen?",
|
||||
@@ -913,7 +814,6 @@
|
||||
"idpDisplayName": "Een weergavenaam voor deze identiteitsprovider",
|
||||
"idpAutoProvisionUsers": "Auto Provisie Gebruikers",
|
||||
"idpAutoProvisionUsersDescription": "Wanneer ingeschakeld, worden gebruikers automatisch in het systeem aangemaakt wanneer ze de eerste keer inloggen met de mogelijkheid om gebruikers toe te wijzen aan rollen en organisaties.",
|
||||
"idpAutoProvisionConfigureAfterCreate": "U kunt automatische voorzieningsinstellingen configureren zodra de identiteitsprovider is aangemaakt.",
|
||||
"licenseBadge": "EE",
|
||||
"idpType": "Type provider",
|
||||
"idpTypeDescription": "Selecteer het type identiteitsprovider dat u wilt configureren",
|
||||
@@ -965,7 +865,7 @@
|
||||
"defaultMappingsRole": "Standaard Rol Toewijzing",
|
||||
"defaultMappingsRoleDescription": "Het resultaat van deze uitdrukking moet de rolnaam zoals gedefinieerd in de organisatie als tekenreeks teruggeven.",
|
||||
"defaultMappingsOrg": "Standaard organisatie mapping",
|
||||
"defaultMappingsOrgDescription": "Wanneer ingesteld, moet deze expressie de organisatie-ID of waar retourneren voor de gebruiker om toegang te krijgen tot die organisatie. Als het niet is ingesteld, is het definiëren van een roltoewijzing voldoende: de gebruiker is toegestaan zolang een geldige roltoewijzing voor hen binnen de organisatie kan worden opgelost.",
|
||||
"defaultMappingsOrgDescription": "Deze expressie moet de org-ID teruggeven of waar om de gebruiker toegang te geven tot de organisatie.",
|
||||
"defaultMappingsSubmit": "Standaard toewijzingen opslaan",
|
||||
"orgPoliciesEdit": "Organisatie beleid bewerken",
|
||||
"org": "Organisatie",
|
||||
@@ -1112,12 +1012,12 @@
|
||||
"pangolinSetup": "Instellen - Pangolin",
|
||||
"orgNameRequired": "Organisatienaam is vereist",
|
||||
"orgIdRequired": "Organisatie-ID is vereist",
|
||||
"orgIdMaxLength": "Organisatie-ID mag maximaal 32 tekens lang zijn",
|
||||
"orgErrorCreate": "Fout opgetreden tijdens het aanmaken org",
|
||||
"pageNotFound": "Pagina niet gevonden",
|
||||
"pageNotFoundDescription": "Oeps! De pagina die je zoekt bestaat niet.",
|
||||
"overview": "Overzicht.",
|
||||
"home": "Startpagina",
|
||||
"accessControl": "Toegangs controle",
|
||||
"settings": "Instellingen",
|
||||
"usersAll": "Alle gebruikers",
|
||||
"license": "Licentie",
|
||||
@@ -1180,12 +1080,6 @@
|
||||
"actionGetUser": "Gebruiker ophalen",
|
||||
"actionGetOrgUser": "Krijg organisatie-gebruiker",
|
||||
"actionListOrgDomains": "Lijst organisatie domeinen",
|
||||
"actionGetDomain": "Domein verkrijgen",
|
||||
"actionCreateOrgDomain": "Domein aanmaken",
|
||||
"actionUpdateOrgDomain": "Domein bijwerken",
|
||||
"actionDeleteOrgDomain": "Domein verwijderen",
|
||||
"actionGetDNSRecords": "Krijg DNS Records",
|
||||
"actionRestartOrgDomain": "Domein opnieuw starten",
|
||||
"actionCreateSite": "Site aanmaken",
|
||||
"actionDeleteSite": "Site verwijderen",
|
||||
"actionGetSite": "Site ophalen",
|
||||
@@ -1197,7 +1091,6 @@
|
||||
"setupTokenDescription": "Voer het setup-token in vanaf de serverconsole.",
|
||||
"setupTokenRequired": "Setup-token is vereist",
|
||||
"actionUpdateSite": "Site bijwerken",
|
||||
"actionResetSiteBandwidth": "Reset organisatieschandbreedte",
|
||||
"actionListSiteRoles": "Toon toegestane sitenollen",
|
||||
"actionCreateResource": "Bron maken",
|
||||
"actionDeleteResource": "Document verwijderen",
|
||||
@@ -1227,7 +1120,6 @@
|
||||
"actionRemoveUser": "Gebruiker verwijderen",
|
||||
"actionListUsers": "Gebruikers weergeven",
|
||||
"actionAddUserRole": "Gebruikersrol toevoegen",
|
||||
"actionSetUserOrgRoles": "Stel gebruikersrollen in",
|
||||
"actionGenerateAccessToken": "Genereer Toegangstoken",
|
||||
"actionDeleteAccessToken": "Verwijder toegangstoken",
|
||||
"actionListAccessTokens": "Lijst toegangstokens",
|
||||
@@ -1272,9 +1164,7 @@
|
||||
"actionViewLogs": "Logboeken bekijken",
|
||||
"noneSelected": "Niet geselecteerd",
|
||||
"orgNotFound2": "Geen organisaties gevonden.",
|
||||
"search": "Zoeken…",
|
||||
"searchPlaceholder": "Zoeken...",
|
||||
"emptySearchOptions": "Geen opties gevonden",
|
||||
"searchProgress": "Zoeken...",
|
||||
"create": "Aanmaken",
|
||||
"orgs": "Organisaties",
|
||||
"loginError": "Er is een onverwachte fout opgetreden. Probeer het opnieuw.",
|
||||
@@ -1338,14 +1228,12 @@
|
||||
"sidebarClientResources": "Privé",
|
||||
"sidebarAccessControl": "Toegangs controle",
|
||||
"sidebarLogsAndAnalytics": "Logs & Analytics",
|
||||
"sidebarTeam": "Team",
|
||||
"sidebarUsers": "Gebruikers",
|
||||
"sidebarAdmin": "Beheerder",
|
||||
"sidebarInvitations": "Uitnodigingen",
|
||||
"sidebarRoles": "Rollen",
|
||||
"sidebarShareableLinks": "Koppelingen",
|
||||
"sidebarApiKeys": "API sleutels",
|
||||
"sidebarProvisioning": "Provisie",
|
||||
"sidebarSettings": "Instellingen",
|
||||
"sidebarAllUsers": "Alle gebruikers",
|
||||
"sidebarIdentityProviders": "Identiteit aanbieders",
|
||||
@@ -1357,166 +1245,8 @@
|
||||
"sidebarGeneral": "Beheren",
|
||||
"sidebarLogAndAnalytics": "Log & Analytics",
|
||||
"sidebarBluePrints": "Blauwdrukken",
|
||||
"sidebarAlerting": "Waarschuwingen",
|
||||
"sidebarHealthChecks": "Gezondheidscontroles",
|
||||
"sidebarOrganization": "Organisatie",
|
||||
"sidebarManagement": "Beheer",
|
||||
"sidebarBillingAndLicenses": "Facturatie & Licenties",
|
||||
"sidebarLogsAnalytics": "Analyses",
|
||||
"alertingTitle": "Waarschuwingen",
|
||||
"alertingDescription": "Definieer bronnen, triggers en acties voor meldingen",
|
||||
"alertingRules": "Waarschuwingsregels",
|
||||
"alertingSearchRules": "Zoek regels…",
|
||||
"alertingAddRule": "Regel aanmaken",
|
||||
"alertingColumnSource": "Bron",
|
||||
"alertingColumnTrigger": "Trigger",
|
||||
"alertingColumnActions": "Acties",
|
||||
"alertingColumnEnabled": "Ingeschakeld",
|
||||
"alertingDeleteQuestion": "Bevestig alstublieft dat u deze waarschuwingsregel wilt verwijderen.",
|
||||
"alertingDeleteRule": "Verwijder waarschuwingsregel",
|
||||
"alertingRuleDeleted": "Waarschuwingsregel verwijderd",
|
||||
"alertingRuleSaved": "Waarschuwingsregel opgeslagen",
|
||||
"alertingRuleSavedCreatedDescription": "Uw nieuwe waarschuwingsregel is aangemaakt. U kunt deze op deze pagina blijven bewerken.",
|
||||
"alertingRuleSavedUpdatedDescription": "Uw wijzigingen in deze waarschuwingsregel zijn opgeslagen.",
|
||||
"alertingEditRule": "Bewerk waarschuwingsregel",
|
||||
"alertingCreateRule": "Waarschuwingsregel aanmaken",
|
||||
"alertingRuleCredenzaDescription": "Kies wat te bekijken, wanneer het moet gebeuren en hoe te waarschuwen",
|
||||
"alertingRuleNamePlaceholder": "Productiesite offline",
|
||||
"alertingRuleEnabled": "Regel ingeschakeld",
|
||||
"alertingSectionSource": "Bron",
|
||||
"alertingSourceType": "Brontype",
|
||||
"alertingSourceSite": "Site",
|
||||
"alertingSourceHealthCheck": "Gezondheidscontrole",
|
||||
"alertingPickSites": "Sites",
|
||||
"alertingPickHealthChecks": "Gezondheidscontroles",
|
||||
"alertingPickResources": "Bronnen",
|
||||
"alertingAllSites": "Alle sites",
|
||||
"alertingAllSitesDescription": "Waarschuwing voor elke site",
|
||||
"alertingSpecificSites": "Specifieke sites",
|
||||
"alertingSpecificSitesDescription": "Kies specifieke sites om in de gaten te houden",
|
||||
"alertingAllHealthChecks": "Alle Gezondheidscontroles",
|
||||
"alertingAllHealthChecksDescription": "Waarschuwing voor elke gezondheidscontrole",
|
||||
"alertingSpecificHealthChecks": "Specifieke Gezondheidscontroles",
|
||||
"alertingSpecificHealthChecksDescription": "Kies specifieke gezondheidscontroles om in de gaten te houden",
|
||||
"alertingAllResources": "Alle bronnen",
|
||||
"alertingAllResourcesDescription": "Waarschuwing voor elke bron",
|
||||
"alertingSpecificResources": "Specifieke bronnen",
|
||||
"alertingSpecificResourcesDescription": "Kies specifieke bronnen om in de gaten te houden",
|
||||
"alertingSelectResources": "Selecteer bronnen…",
|
||||
"alertingResourcesSelected": "{count} bronnen geselecteerd",
|
||||
"alertingResourcesEmpty": "Geen bronnen met doelen in de eerste 10 resultaten.",
|
||||
"alertingSectionTrigger": "Trigger",
|
||||
"alertingTrigger": "Wanneer te waarschuwen",
|
||||
"alertingTriggerSiteOnline": "Site online",
|
||||
"alertingTriggerSiteOffline": "Site offline",
|
||||
"alertingTriggerSiteToggle": "Site status wijzigt",
|
||||
"alertingTriggerHcHealthy": "Gezondheidscontrole gezond",
|
||||
"alertingTriggerHcUnhealthy": "Gezondheidscontrole ongezond",
|
||||
"alertingTriggerHcToggle": "Gezondheidscontrole status verandert",
|
||||
"alertingTriggerResourceHealthy": "Bron gezond",
|
||||
"alertingTriggerResourceUnhealthy": "Bron ongezond",
|
||||
"alertingSearchHealthChecks": "Zoek gezondheidscontroles…",
|
||||
"alertingHealthChecksEmpty": "Geen gezondheidscontroles beschikbaar.",
|
||||
"alertingTriggerResourceToggle": "Bronstatus wijzigt",
|
||||
"alertingSourceResource": "Bron",
|
||||
"alertingSectionActions": "Acties",
|
||||
"alertingAddAction": "Actie toevoegen",
|
||||
"alertingActionNotify": "E-mail",
|
||||
"alertingActionNotifyDescription": "Stuur e-mailmeldingen naar gebruikers of rollen",
|
||||
"alertingActionWebhook": "Webhook",
|
||||
"alertingActionWebhookDescription": "Stuur een HTTP-verzoek naar een aangepast eindpunt",
|
||||
"alertingExternalIntegration": "Externe integratie",
|
||||
"alertingExternalPagerDutyDescription": "Stuur waarschuwingen naar PagerDuty voor incidentbeheer",
|
||||
"alertingExternalOpsgenieDescription": "Routeer waarschuwingen naar Opsgenie voor wachtdienstbeheer",
|
||||
"alertingExternalServiceNowDescription": "Maak ServiceNow-incidenten aan vanuit waarschuwingsgebeurtenissen",
|
||||
"alertingExternalIncidentIoDescription": "Trigger Incident.io workflows van waarschuwingsgebeurtenissen",
|
||||
"alertingActionType": "Actietype",
|
||||
"alertingNotifyUsers": "Gebruikers",
|
||||
"alertingNotifyRoles": "Rollen",
|
||||
"alertingNotifyEmails": "E-mailadressen",
|
||||
"alertingEmailPlaceholder": "Voeg e-mail toe en druk op Enter",
|
||||
"alertingWebhookMethod": "HTTP-methode",
|
||||
"alertingWebhookSecret": "Ondertekengeheim (optioneel)",
|
||||
"alertingWebhookSecretPlaceholder": "HMAC-geheim",
|
||||
"alertingWebhookHeaders": "Headers",
|
||||
"alertingAddHeader": "Header toevoegen",
|
||||
"alertingSelectSites": "Selecteer sites…",
|
||||
"alertingSitesSelected": "{count} sites geselecteerd",
|
||||
"alertingSelectHealthChecks": "Selecteer gezondheidscontroles…",
|
||||
"alertingHealthChecksSelected": "{count} gezondheidscontroles geselecteerd",
|
||||
"alertingNoHealthChecks": "Geen doelen met ingeschakelde gezondheidscontroles",
|
||||
"alertingHealthCheckStub": "Gezondheidscontrole brondeselectie is nog niet gekoppeld - u kunt nog steeds triggers en acties configureren.",
|
||||
"alertingSelectUsers": "Selecteer gebruikers…",
|
||||
"alertingUsersSelected": "{count} gebruikers geselecteerd",
|
||||
"alertingSelectRoles": "Selecteer rollen…",
|
||||
"alertingRolesSelected": "{count} rollen geselecteerd",
|
||||
"alertingSummarySites": "Sites ({count})",
|
||||
"alertingSummaryAllSites": "Alle sites",
|
||||
"alertingSummaryHealthChecks": "Gezondheidscontroles ({count})",
|
||||
"alertingSummaryAllHealthChecks": "Alle gezondheidscontroles",
|
||||
"alertingSummaryResources": "Bronnen ({count})",
|
||||
"alertingSummaryAllResources": "Alle bronnen",
|
||||
"alertingErrorNameRequired": "Voer een naam in",
|
||||
"alertingErrorActionsMin": "Voeg minimaal één actie toe",
|
||||
"alertingErrorPickSites": "Selecteer minimaal één site",
|
||||
"alertingErrorPickHealthChecks": "Selecteer minimaal één gezondheidscontrole",
|
||||
"alertingErrorPickResources": "Selecteer minimaal één bron",
|
||||
"alertingErrorTriggerSite": "Kies een site-trigger",
|
||||
"alertingErrorTriggerHealth": "Kies een gezondheidscontrole-trigger",
|
||||
"alertingErrorTriggerResource": "Kies een bron-trigger",
|
||||
"alertingErrorNotifyRecipients": "Kies gebruikers, rollen of ten minste één e-mail",
|
||||
"alertingConfigureSource": "Bron configureren",
|
||||
"alertingConfigureTrigger": "Trigger configureren",
|
||||
"alertingConfigureActions": "Acties configureren",
|
||||
"alertingBackToRules": "Terug naar regels",
|
||||
"alertingRuleCooldown": "Aflkoelperiode (seconden)",
|
||||
"alertingRuleCooldownDescription": "Minimale tijd tussen herhaalwaarschuwingen voor dezelfde regel. Zet op 0 om elke keer te laten vuren.",
|
||||
"alertingDraftBadge": "Concept - opslaan om deze regel op te slaan",
|
||||
"alertingSidebarHint": "Klik op een stap in het canvas om deze hier te bewerken.",
|
||||
"alertingGraphCanvasTitle": "Regelstroom",
|
||||
"alertingGraphCanvasDescription": "Visueel overzicht van bron, trigger en acties. Selecteer een node om deze in het paneel te bewerken.",
|
||||
"alertingNodeNotConfigured": "Nog niet geconfigureerd",
|
||||
"alertingNodeActionsCount": "{count, plural, one {# actie} other {# acties}}",
|
||||
"alertingNodeRoleSource": "Bron",
|
||||
"alertingNodeRoleTrigger": "Trigger",
|
||||
"alertingNodeRoleAction": "Actie",
|
||||
"alertingTabRules": "Waarschuwingsregels",
|
||||
"alertingTabHealthChecks": "Gezondheidscontroles",
|
||||
"alertingRulesBannerTitle": "Meldingen ontvangen",
|
||||
"alertingRulesBannerDescription": "Elke regel koppelt wat te bekijken (een site, gezondheidscontrole of bron), wanneer te vuren (bijvoorbeeld offline of ongezond), en hoe uw team te waarschuwen via e-mail, webhooks of integraties. Gebruik deze lijst om die regels te maken, in te schakelen en te beheren.",
|
||||
"alertingHealthChecksBannerTitle": "Gezondheid & bronnen bewaken",
|
||||
"alertingHealthChecksBannerDescription": "Gezondheidscontroles zijn HTTP- of TCP-monitoren die u één keer definieert. U kunt ze vervolgens als bronnen in waarschuwingsregels gebruiken, zodat u meldingen krijgt wanneer een doelwit gezond of ongezond wordt. Gezondheidscontroles van bronnen verschijnen ook hier.",
|
||||
"standaloneHcTableTitle": "Gezondheidscontroles",
|
||||
"standaloneHcSearchPlaceholder": "Zoek gezondheidscontroles…",
|
||||
"standaloneHcAddButton": "Gezondheidscontrole aanmaken",
|
||||
"standaloneHcCreateTitle": "Gezondheidscontrole aanmaken",
|
||||
"standaloneHcEditTitle": "Gezondheidscontrole bewerken",
|
||||
"standaloneHcDescription": "Configureer een HTTP- of TCP-gezondheidscontrole voor gebruik in waarschuwingsregels.",
|
||||
"standaloneHcNameLabel": "Naam",
|
||||
"standaloneHcNamePlaceholder": "Mijn HTTP-monitor",
|
||||
"standaloneHcDeleteTitle": "Gezondheidscontrole verwijderen",
|
||||
"standaloneHcDeleteQuestion": "Bevestig alstublieft dat u deze gezondheidscontrole wilt verwijderen.",
|
||||
"standaloneHcDeleted": "Gezondheidscontrole verwijderd",
|
||||
"standaloneHcSaved": "Gezondheidscontrole opgeslagen",
|
||||
"standaloneHcColumnHealth": "Gezondheid",
|
||||
"standaloneHcColumnMode": "Modus",
|
||||
"standaloneHcColumnTarget": "Doelwit",
|
||||
"standaloneHcHealthStateHealthy": "Gezond",
|
||||
"standaloneHcHealthStateUnhealthy": "Ongezond",
|
||||
"standaloneHcHealthStateUnknown": "Onbekend",
|
||||
"standaloneHcFilterAnySite": "Alle sites",
|
||||
"standaloneHcFilterAnyResource": "Alle bronnen",
|
||||
"standaloneHcFilterMode": "Modus",
|
||||
"standaloneHcFilterModeHttp": "HTTP",
|
||||
"standaloneHcFilterModeTcp": "TCP",
|
||||
"standaloneHcFilterModeSnmp": "SNMP",
|
||||
"standaloneHcFilterModePing": "Ping",
|
||||
"standaloneHcFilterHealth": "Gezondheid",
|
||||
"standaloneHcFilterEnabled": "Ingeschakeld",
|
||||
"standaloneHcFilterEnabledOn": "Ingeschakeld",
|
||||
"standaloneHcFilterEnabledOff": "Uitgeschakeld",
|
||||
"standaloneHcFilterSiteIdFallback": "Site {id}",
|
||||
"standaloneHcFilterResourceIdFallback": "Bron {id}",
|
||||
"blueprints": "Blauwdrukken",
|
||||
"blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.",
|
||||
"blueprintAdd": "Blauwdruk toevoegen",
|
||||
@@ -1537,6 +1267,7 @@
|
||||
"parsedContents": "Geparseerde inhoud (alleen lezen)",
|
||||
"enableDockerSocket": "Schakel Docker Blauwdruk in",
|
||||
"enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.",
|
||||
"enableDockerSocketLink": "Meer informatie",
|
||||
"viewDockerContainers": "Bekijk Docker containers",
|
||||
"containersIn": "Containers in {siteName}",
|
||||
"selectContainerDescription": "Selecteer een container om als hostnaam voor dit doel te gebruiken. Klik op een poort om een poort te gebruiken.",
|
||||
@@ -1664,7 +1395,6 @@
|
||||
"domainPickerNamespace": "Naamruimte: {namespace}",
|
||||
"domainPickerShowMore": "Meer weergeven",
|
||||
"regionSelectorTitle": "Selecteer Regio",
|
||||
"domainPickerRemoteExitNodeWarning": "Opgegeven domeinen worden niet ondersteund wanneer websites verbinding maken met externe sluitnodes. Gebruik in plaats daarvan een aangepast domein. Om bronnen beschikbaar te maken op externe nodes.",
|
||||
"regionSelectorInfo": "Het selecteren van een regio helpt ons om betere prestaties te leveren voor uw locatie. U hoeft niet in dezelfde regio als uw server te zijn.",
|
||||
"regionSelectorPlaceholder": "Kies een regio",
|
||||
"regionSelectorComingSoon": "Komt binnenkort",
|
||||
@@ -1674,11 +1404,10 @@
|
||||
"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.",
|
||||
"billingDataUsage": "Gegevensgebruik",
|
||||
"billingSites": "Sites",
|
||||
"billingUsers": "Gebruikers",
|
||||
"billingDomains": "Domeinen",
|
||||
"billingOrganizations": "Ordenen",
|
||||
"billingRemoteExitNodes": "Externe knooppunten",
|
||||
"billingOnlineTime": "Site Online Tijd",
|
||||
"billingUsers": "Actieve Gebruikers",
|
||||
"billingDomains": "Actieve Domeinen",
|
||||
"billingRemoteExitNodes": "Actieve Zelfgehoste Nodes",
|
||||
"billingNoLimitConfigured": "Geen limiet ingesteld",
|
||||
"billingEstimatedPeriod": "Geschatte Facturatie Periode",
|
||||
"billingIncludedUsage": "Opgenomen Gebruik",
|
||||
@@ -1703,24 +1432,15 @@
|
||||
"billingFailedToGetPortalUrl": "Niet gelukt om portal URL te krijgen",
|
||||
"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.",
|
||||
"billingSInfo": "Hoeveel sites u kunt gebruiken",
|
||||
"billingUsersInfo": "Hoeveel gebruikers je kan gebruiken",
|
||||
"billingDomainInfo": "Hoeveel domeinen je kunt gebruiken",
|
||||
"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",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"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.",
|
||||
"failed": "Mislukt",
|
||||
"createNewOrgDescription": "Maak een nieuwe organisatie",
|
||||
"organization": "Organisatie",
|
||||
"primary": "Primair",
|
||||
"port": "Poort",
|
||||
"securityKeyManage": "Beveiligingssleutels beheren",
|
||||
"securityKeyDescription": "Voeg beveiligingssleutels toe of verwijder ze voor wachtwoordloze authenticatie",
|
||||
@@ -1792,42 +1512,6 @@
|
||||
"resourcePortRequired": "Poortnummer is vereist voor niet-HTTP-bronnen",
|
||||
"resourcePortNotAllowed": "Poortnummer mag niet worden ingesteld voor HTTP-bronnen",
|
||||
"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})",
|
||||
"billingPastDueTitle": "Vervaldatum betaling",
|
||||
"billingPastDueDescription": "Uw betaling is verlopen. Werk uw betaalmethode bij om uw huidige abonnementsfuncties te blijven gebruiken. Als dit niet is opgelost, zal je abonnement worden geannuleerd en zal je worden teruggezet naar de vrije rang.",
|
||||
"billingUnpaidTitle": "Abonnement Onbetaald",
|
||||
"billingUnpaidDescription": "Uw abonnement is niet betaald en u bent teruggekeerd naar het gratis niveau. Update uw betalingsmethode om uw abonnement te herstellen.",
|
||||
"billingIncompleteTitle": "Betaling onvolledig",
|
||||
"billingIncompleteDescription": "Uw betaling is onvolledig. Voltooi alstublieft het betalingsproces om uw abonnement te activeren.",
|
||||
"billingIncompleteExpiredTitle": "Betaling verlopen",
|
||||
"billingIncompleteExpiredDescription": "Uw betaling is nooit voltooid en verlopen. U bent teruggekeerd naar de gratis niveaus. Abonneer u opnieuw om de toegang tot betaalde functies te herstellen.",
|
||||
"billingManageSubscription": "Beheer uw abonnement",
|
||||
"billingResolvePaymentIssue": "Gelieve uw betalingsprobleem op te lossen voor het upgraden of downgraden",
|
||||
"signUpTerms": {
|
||||
"IAgreeToThe": "Ik ga akkoord met de",
|
||||
"termsOfService": "servicevoorwaarden",
|
||||
@@ -1901,24 +1585,6 @@
|
||||
"timeIsInSeconds": "Tijd is in seconden",
|
||||
"requireDeviceApproval": "Vereist goedkeuring van apparaat",
|
||||
"requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
|
||||
"sshAccess": "SSH toegang",
|
||||
"roleAllowSsh": "SSH toestaan",
|
||||
"roleAllowSshAllow": "Toestaan",
|
||||
"roleAllowSshDisallow": "Weigeren",
|
||||
"roleAllowSshDescription": "Sta gebruikers met deze rol toe om verbinding te maken met bronnen via SSH. Indien uitgeschakeld kan de rol geen gebruik maken van SSH toegang.",
|
||||
"sshSudoMode": "Sudo toegang",
|
||||
"sshSudoModeNone": "geen",
|
||||
"sshSudoModeNoneDescription": "Gebruiker kan geen commando's uitvoeren met sudo.",
|
||||
"sshSudoModeFull": "Volledige Sudo",
|
||||
"sshSudoModeFullDescription": "Gebruiker kan elk commando uitvoeren met een sudo.",
|
||||
"sshSudoModeCommands": "Opdrachten",
|
||||
"sshSudoModeCommandsDescription": "Gebruiker kan alleen de opgegeven commando's uitvoeren met de sudo.",
|
||||
"sshSudo": "sudo toestaan",
|
||||
"sshSudoCommands": "Sudo Commando's",
|
||||
"sshSudoCommandsDescription": "Komma's gescheiden lijst van commando's waar de gebruiker een sudo mee mag uitvoeren.",
|
||||
"sshCreateHomeDir": "Maak Home Directory",
|
||||
"sshUnixGroups": "Unix groepen",
|
||||
"sshUnixGroupsDescription": "Door komma's gescheiden Unix-groepen om de gebruiker toe te voegen aan de doelhost.",
|
||||
"retryAttempts": "Herhaal Pogingen",
|
||||
"expectedResponseCodes": "Verwachte Reactiecodes",
|
||||
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
|
||||
@@ -1935,15 +1601,6 @@
|
||||
"healthCheckIntervalMin": "Controle interval moet minimaal 5 seconden zijn",
|
||||
"healthCheckTimeoutMin": "Timeout moet minimaal 1 seconde zijn",
|
||||
"healthCheckRetryMin": "Herhaal pogingen moet minimaal 1 zijn",
|
||||
"healthCheckMode": "Controlemodus",
|
||||
"healthCheckStrategy": "Strategie",
|
||||
"healthCheckModeDescription": "TCP-modus verifieert alleen connectiviteit. HTTP-modus valideert de HTTP-respons.",
|
||||
"healthyThreshold": "Gezonde drempel",
|
||||
"healthyThresholdDescription": "Opeenvolgende successen vereist voordat gemarkeerd wordt als gezond.",
|
||||
"unhealthyThreshold": "Ongezonde drempel",
|
||||
"unhealthyThresholdDescription": "Opeenvolgende fouten vereist voordat gemarkeerd wordt als ongezond.",
|
||||
"healthCheckHealthyThresholdMin": "Gezonde drempel moet minimaal 1 zijn",
|
||||
"healthCheckUnhealthyThresholdMin": "Ongezonde drempel moet minimaal 1 zijn",
|
||||
"httpMethod": "HTTP-methode",
|
||||
"selectHttpMethod": "Selecteer HTTP-methode",
|
||||
"domainPickerSubdomainLabel": "Subdomein",
|
||||
@@ -1968,8 +1625,6 @@
|
||||
"resourcesTableNoInternalResourcesFound": "Geen interne bronnen gevonden.",
|
||||
"resourcesTableDestination": "Bestemming",
|
||||
"resourcesTableAlias": "Alias",
|
||||
"resourcesTableAliasAddress": "Alias adres",
|
||||
"resourcesTableAliasAddressInfo": "Dit adres is onderdeel van het hulpprogramma subnet van de organisatie. Het wordt gebruikt om aliasrecords op te lossen met behulp van interne DNS-resolutie.",
|
||||
"resourcesTableClients": "Clienten",
|
||||
"resourcesTableAndOnlyAccessibleInternally": "en zijn alleen intern toegankelijk wanneer verbonden met een client.",
|
||||
"resourcesTableNoTargets": "Geen doelen",
|
||||
@@ -2003,11 +1658,6 @@
|
||||
"editInternalResourceDialogModePort": "Poort",
|
||||
"editInternalResourceDialogModeHost": "Hostnaam",
|
||||
"editInternalResourceDialogModeCidr": "CIDR",
|
||||
"editInternalResourceDialogModeHttp": "HTTP",
|
||||
"editInternalResourceDialogModeHttps": "HTTPS",
|
||||
"editInternalResourceDialogScheme": "Schema",
|
||||
"editInternalResourceDialogEnableSsl": "SSL inschakelen",
|
||||
"editInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.",
|
||||
"editInternalResourceDialogDestination": "Bestemming",
|
||||
"editInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
|
||||
"editInternalResourceDialogDestinationIPDescription": "Het IP of hostnaam adres van de bron op het netwerk van de site.",
|
||||
@@ -2023,7 +1673,6 @@
|
||||
"createInternalResourceDialogName": "Naam",
|
||||
"createInternalResourceDialogSite": "Site",
|
||||
"selectSite": "Selecteer site...",
|
||||
"multiSitesSelectorSitesCount": "{count, plural, one {# site} other {# sites}}",
|
||||
"noSitesFound": "Geen sites gevonden.",
|
||||
"createInternalResourceDialogProtocol": "Protocol",
|
||||
"createInternalResourceDialogTcp": "TCP",
|
||||
@@ -2052,19 +1701,11 @@
|
||||
"createInternalResourceDialogModePort": "Poort",
|
||||
"createInternalResourceDialogModeHost": "Hostnaam",
|
||||
"createInternalResourceDialogModeCidr": "CIDR",
|
||||
"createInternalResourceDialogModeHttp": "HTTP",
|
||||
"createInternalResourceDialogModeHttps": "HTTPS",
|
||||
"scheme": "Schema",
|
||||
"createInternalResourceDialogScheme": "Schema",
|
||||
"createInternalResourceDialogEnableSsl": "SSL inschakelen",
|
||||
"createInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.",
|
||||
"createInternalResourceDialogDestination": "Bestemming",
|
||||
"createInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
|
||||
"createInternalResourceDialogDestinationCidrDescription": "Het CIDR-bereik van het document op het netwerk van de site.",
|
||||
"createInternalResourceDialogAlias": "Alias",
|
||||
"createInternalResourceDialogAliasDescription": "Een optionele interne DNS-alias voor dit document.",
|
||||
"internalResourceDownstreamSchemeRequired": "Schema is vereist voor HTTP-bronnen",
|
||||
"internalResourceHttpPortRequired": "Bestemmingspoort is vereist voor HTTP-bronnen",
|
||||
"siteConfiguration": "Configuratie",
|
||||
"siteAcceptClientConnections": "Accepteer clientverbindingen",
|
||||
"siteAcceptClientConnectionsDescription": "Sta gebruikersapparaten en clients toegang toe tot bronnen op deze site. Dit kan later worden gewijzigd.",
|
||||
@@ -2150,40 +1791,6 @@
|
||||
"exitNode": "Exit Node",
|
||||
"country": "Land",
|
||||
"rulesMatchCountry": "Momenteel gebaseerd op bron IP",
|
||||
"region": "Regio",
|
||||
"selectRegion": "Selecteer regio",
|
||||
"searchRegions": "Zoek regio's...",
|
||||
"noRegionFound": "Geen regio gevonden.",
|
||||
"rulesMatchRegion": "Selecteer een regionale groepering van landen",
|
||||
"rulesErrorInvalidRegion": "Ongeldige regio",
|
||||
"rulesErrorInvalidRegionDescription": "Selecteer een geldige regio.",
|
||||
"regionAfrica": "Afrika",
|
||||
"regionNorthernAfrica": "Noord-Afrika",
|
||||
"regionEasternAfrica": "Oost Afrika",
|
||||
"regionMiddleAfrica": "Midden Afrika",
|
||||
"regionSouthernAfrica": "Zuidelijk Afrika",
|
||||
"regionWesternAfrica": "Westelijk Afrika",
|
||||
"regionAmericas": "Amerika's",
|
||||
"regionCaribbean": "Caraïben",
|
||||
"regionCentralAmerica": "Midden-Amerika",
|
||||
"regionSouthAmerica": "Zuid Amerika",
|
||||
"regionNorthernAmerica": "Noord-Amerika",
|
||||
"regionAsia": "Azië",
|
||||
"regionCentralAsia": "Centraal-Azië",
|
||||
"regionEasternAsia": "Oost-Azië",
|
||||
"regionSouthEasternAsia": "Zuid-Oost-Azië",
|
||||
"regionSouthernAsia": "Zuid-Azië",
|
||||
"regionWesternAsia": "Westelijk Azië",
|
||||
"regionEurope": "Europa",
|
||||
"regionEasternEurope": "Oost-Europa",
|
||||
"regionNorthernEurope": "Noord-Europa",
|
||||
"regionSouthernEurope": "Zuid-Europa",
|
||||
"regionWesternEurope": "West-Europa",
|
||||
"regionOceania": "Oceania",
|
||||
"regionAustraliaAndNewZealand": "Australië en Nieuw-Zeeland",
|
||||
"regionMelanesia": "Melanesia",
|
||||
"regionMicronesia": "Micronesia",
|
||||
"regionPolynesia": "Polynesia",
|
||||
"managedSelfHosted": {
|
||||
"title": "Beheerde Self-Hosted",
|
||||
"description": "betrouwbaardere en slecht onderhouden Pangolin server met extra klokken en klokkenluiders",
|
||||
@@ -2222,7 +1829,7 @@
|
||||
},
|
||||
"internationaldomaindetected": "Internationaal Domein Gedetecteerd",
|
||||
"willbestoredas": "Zal worden opgeslagen als:",
|
||||
"roleMappingDescription": "Bepaal hoe rollen aan gebruikers worden toegewezen wanneer ze zich aanmelden met deze identiteitsprovider.",
|
||||
"roleMappingDescription": "Bepaal hoe rollen worden toegewezen aan gebruikers wanneer ze inloggen wanneer Auto Provision is ingeschakeld.",
|
||||
"selectRole": "Selecteer een rol",
|
||||
"roleMappingExpression": "Expressie",
|
||||
"selectRolePlaceholder": "Kies een rol",
|
||||
@@ -2232,25 +1839,6 @@
|
||||
"invalidValue": "Ongeldige waarde",
|
||||
"idpTypeLabel": "Identiteit provider type",
|
||||
"roleMappingExpressionPlaceholder": "bijvoorbeeld bevat (groepen, 'admin') && 'Admin' ½ 'Member'",
|
||||
"roleMappingModeFixedRoles": "Vaste rollen",
|
||||
"roleMappingModeMappingBuilder": "Toewijzing Bouwer",
|
||||
"roleMappingModeRawExpression": "Ruwe expressie",
|
||||
"roleMappingFixedRolesPlaceholderSelect": "Selecteer één of meer rollen",
|
||||
"roleMappingFixedRolesPlaceholderFreeform": "Typ rolnamen (exacte overeenkomst per organisatie)",
|
||||
"roleMappingFixedRolesDescriptionSameForAll": "Wijs dezelfde rolset toe aan elke auto-provisioned gebruiker.",
|
||||
"roleMappingFixedRolesDescriptionDefaultPolicy": "Voor standaardbeleid, typ rolnamen die bestaan in elke organisatie waar gebruikers worden opgegeven. Namen moeten exact overeenkomen.",
|
||||
"roleMappingClaimPath": "Claim pad",
|
||||
"roleMappingClaimPathPlaceholder": "Groepen",
|
||||
"roleMappingClaimPathDescription": "Pad in de token payload die bronwaarden bevat (bijvoorbeeld groepen).",
|
||||
"roleMappingMatchValue": "Kies een waarde",
|
||||
"roleMappingAssignRoles": "Rollen toewijzen",
|
||||
"roleMappingAddMappingRule": "Toewijzingsregel toevoegen",
|
||||
"roleMappingRawExpressionResultDescription": "Expressie moet een tekenreeks of tekenreeks evalueren.",
|
||||
"roleMappingRawExpressionResultDescriptionSingleRole": "Expressie moet evalueren naar een tekenreeks (een naam met één rol).",
|
||||
"roleMappingMatchValuePlaceholder": "Overeenkomende waarde (bijvoorbeeld: admin)",
|
||||
"roleMappingAssignRolesPlaceholderFreeform": "Typ rolnamen (exact per org)",
|
||||
"roleMappingBuilderFreeformRowHint": "Rol namen moeten overeenkomen met een rol in elke doelorganisatie.",
|
||||
"roleMappingRemoveRule": "Verwijderen",
|
||||
"idpGoogleConfiguration": "Google Configuratie",
|
||||
"idpGoogleConfigurationDescription": "Configureer de Google OAuth2-referenties",
|
||||
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
|
||||
@@ -2287,9 +1875,6 @@
|
||||
"authPageBrandingQuestionRemove": "Weet u zeker dat u de branding voor Auth-pagina's wilt verwijderen?",
|
||||
"authPageBrandingDeleteConfirm": "Bevestig verwijder Branding",
|
||||
"brandingLogoURL": "Het logo-URL",
|
||||
"brandingLogoURLOrPath": "Logo URL of pad",
|
||||
"brandingLogoPathDescription": "Voer een URL of een lokaal pad in.",
|
||||
"brandingLogoURLDescription": "Voer een openbaar toegankelijke URL in voor uw logo afbeelding.",
|
||||
"brandingPrimaryColor": "Primaire kleur",
|
||||
"brandingLogoWidth": "Breedte (px)",
|
||||
"brandingLogoHeight": "Hoogte (px)",
|
||||
@@ -2314,10 +1899,8 @@
|
||||
"selectDomainForOrgAuthPage": "Selecteer een domein voor de authenticatiepagina van de organisatie",
|
||||
"domainPickerProvidedDomain": "Opgegeven domein",
|
||||
"domainPickerFreeProvidedDomain": "Gratis verstrekt domein",
|
||||
"domainPickerFreeDomainsPaidFeature": "Geleverde domeinen zijn een betaalde functie. Abonneer je om een domein bij je plan te krijgen - je hoeft er zelf geen mee te brengen.",
|
||||
"domainPickerVerified": "Geverifieerd",
|
||||
"domainPickerUnverified": "Ongeverifieerd",
|
||||
"domainPickerManual": "Handleiding",
|
||||
"domainPickerInvalidSubdomainStructure": "Dit subdomein bevat ongeldige tekens of structuur. Het zal automatisch worden gesaneerd wanneer u opslaat.",
|
||||
"domainPickerError": "Foutmelding",
|
||||
"domainPickerErrorLoadDomains": "Fout bij het laden van organisatiedomeinen",
|
||||
@@ -2341,13 +1924,6 @@
|
||||
"orgAuthBackToSignIn": "Terug naar standaard aanmelden",
|
||||
"orgAuthNoAccount": "Nog geen account?",
|
||||
"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.",
|
||||
"orgAuthPageDisabled": "Pagina voor organisatie-authenticatie is uitgeschakeld.",
|
||||
"domainRestartedDescription": "Domeinverificatie met succes opnieuw gestart",
|
||||
@@ -2535,32 +2111,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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, 100 sites en prioritaire ondersteuning."
|
||||
}
|
||||
},
|
||||
"personalUseOnly": "Alleen voor persoonlijk gebruik (gratis licentie - geen afrekening)",
|
||||
"buttons": {
|
||||
"continueToCheckout": "Doorgaan naar afrekenen"
|
||||
},
|
||||
"toasts": {
|
||||
"checkoutError": {
|
||||
"title": "Fout bij afrekenen",
|
||||
"description": "Kan de afhandeling niet starten. Probeer het opnieuw."
|
||||
}
|
||||
}
|
||||
},
|
||||
"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.",
|
||||
"instanceName": "Naam instantie",
|
||||
@@ -2624,7 +2174,6 @@
|
||||
"validPassword": "Geldig wachtwoord",
|
||||
"validEmail": "Valid email",
|
||||
"validSSO": "Valid SSO",
|
||||
"connectedClient": "Verbonden Client",
|
||||
"resourceBlocked": "Bron geblokkeerd",
|
||||
"droppedByRule": "Achtergelaten door regel",
|
||||
"noSessions": "Geen sessies",
|
||||
@@ -2650,8 +2199,6 @@
|
||||
"logRetentionAccessDescription": "Hoe lang de toegangslogboeken behouden blijven",
|
||||
"logRetentionActionLabel": "Actie log bewaring",
|
||||
"logRetentionActionDescription": "Hoe lang de action logs behouden moeten blijven",
|
||||
"logRetentionConnectionLabel": "Connectie log bewaring",
|
||||
"logRetentionConnectionDescription": "Hoe lang de verbindingslogs onderhouden",
|
||||
"logRetentionDisabled": "Uitgeschakeld",
|
||||
"logRetention3Days": "3 dagen",
|
||||
"logRetention7Days": "7 dagen",
|
||||
@@ -2662,15 +2209,7 @@
|
||||
"logRetentionEndOfFollowingYear": "Einde van volgend jaar",
|
||||
"actionLogsDescription": "Bekijk een geschiedenis van acties die worden uitgevoerd in deze organisatie",
|
||||
"accessLogsDescription": "Toegangsverificatieverzoeken voor resources in deze organisatie bekijken",
|
||||
"connectionLogs": "Connectie Logs",
|
||||
"connectionLogsDescription": "Toon verbindingslogs voor tunnels in deze organisatie",
|
||||
"sidebarLogsConnection": "Connectie Logs",
|
||||
"sidebarLogsStreaming": "Streamen",
|
||||
"sourceAddress": "Bron adres",
|
||||
"destinationAddress": "Adres bestemming",
|
||||
"duration": "Duur",
|
||||
"licenseRequiredToUse": "Een <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> licentie of <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> is vereist om deze functie te gebruiken. <bookADemoLink>Boek een demo of POC trial</bookADemoLink>.",
|
||||
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Boek een demo of POC trial</bookADemoLink>.",
|
||||
"licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.",
|
||||
"certResolver": "Certificaat Resolver",
|
||||
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
|
||||
"selectCertResolver": "Certificaat Resolver selecteren",
|
||||
@@ -2812,9 +2351,6 @@
|
||||
"machineClients": "Machine Clienten",
|
||||
"install": "Installeren",
|
||||
"run": "Uitvoeren",
|
||||
"envFile": "Omgevingsbestand",
|
||||
"serviceFile": "Servicebestand",
|
||||
"enableAndStart": "Inschakelen en Starten",
|
||||
"clientNameDescription": "De weergavenaam van de client die later gewijzigd kan worden.",
|
||||
"clientAddress": "Klant adres (Geavanceerd)",
|
||||
"setupFailedToFetchSubnet": "Kan standaard subnet niet ophalen",
|
||||
@@ -2864,27 +2400,12 @@
|
||||
"editInternalResourceDialogDestinationLabel": "Bestemming",
|
||||
"editInternalResourceDialogDestinationDescription": "Specificeer het bestemmingsadres voor de interne bron. Dit kan een hostnaam, IP-adres of CIDR-bereik zijn, afhankelijk van de geselecteerde modus. Stel optioneel een interne DNS-alias in voor eenvoudigere identificatie.",
|
||||
"editInternalResourceDialogPortRestrictionsDescription": "Beperk toegang tot specifieke TCP/UDP-poorten of sta alle poorten toe/blokkeer.",
|
||||
"createInternalResourceDialogHttpConfiguration": "HTTP-configuratie",
|
||||
"createInternalResourceDialogHttpConfigurationDescription": "Kies het domein dat cliënten zullen gebruiken om deze bron via HTTP of HTTPS te bereiken.",
|
||||
"editInternalResourceDialogHttpConfiguration": "HTTP-configuratie",
|
||||
"editInternalResourceDialogHttpConfigurationDescription": "Kies het domein dat cliënten zullen gebruiken om deze bron via HTTP of HTTPS te bereiken.",
|
||||
"editInternalResourceDialogTcp": "TCP",
|
||||
"editInternalResourceDialogUdp": "UDP",
|
||||
"editInternalResourceDialogIcmp": "ICMP",
|
||||
"editInternalResourceDialogAccessControl": "Toegangs controle",
|
||||
"editInternalResourceDialogAccessControlDescription": "Beheer welke rollen, gebruikers en machineclients toegang hebben tot deze bron wanneer ze zijn verbonden. Beheerders hebben altijd toegang.",
|
||||
"editInternalResourceDialogPortRangeValidationError": "Poortbereik moet \"*\" zijn voor alle poorten, of een komma-gescheiden lijst van poorten en bereiken (bijv. \"80,443,8000-9000\"). Poorten moeten tussen 1 en 65535 zijn.",
|
||||
"internalResourceAuthDaemonStrategy": "SSH Auth Daemon locatie",
|
||||
"internalResourceAuthDaemonStrategyDescription": "Kies waar de SSH authenticatie daemon wordt uitgevoerd: op de website (Newt) of op een externe host.",
|
||||
"internalResourceAuthDaemonDescription": "De SSH authenticatie daemon zorgt voor SSH sleutelondertekening en PAM authenticatie voor deze resource. Kies of het wordt uitgevoerd op de website (Nieuw) of op een afzonderlijke externe host. Zie <docsLink>de documentatie</docsLink> voor meer.",
|
||||
"internalResourceAuthDaemonDocsUrl": "https://docs.pangolin.net",
|
||||
"internalResourceAuthDaemonStrategyPlaceholder": "Selecteer strategie",
|
||||
"internalResourceAuthDaemonStrategyLabel": "Locatie",
|
||||
"internalResourceAuthDaemonSite": "In de site",
|
||||
"internalResourceAuthDaemonSiteDescription": "Auth daemon draait op de site (Newt).",
|
||||
"internalResourceAuthDaemonRemote": "Externe host",
|
||||
"internalResourceAuthDaemonRemoteDescription": "Authenticatiedaemon draait op een host die niet de site is.",
|
||||
"internalResourceAuthDaemonPort": "Daemon poort (optioneel)",
|
||||
"orgAuthWhatsThis": "Waar kan ik mijn organisatie-ID vinden?",
|
||||
"learnMore": "Meer informatie",
|
||||
"backToHome": "Ga terug naar startpagina",
|
||||
@@ -2906,8 +2427,6 @@
|
||||
"maintenancePageMessagePlaceholder": "We keren snel terug! Onze site ondergaat momenteel gepland onderhoud.",
|
||||
"maintenancePageMessageDescription": "Gedetailleerd bericht dat het onderhoud uitlegt",
|
||||
"maintenancePageTimeTitle": "Geschatte voltooiingstijd (optioneel)",
|
||||
"privateMaintenanceScreenTitle": "Privéscherm maintenance screen",
|
||||
"privateMaintenanceScreenMessage": "Dit domein wordt gebruikt op een privébron. Verbind met de Pangolin client om toegang te krijgen tot deze bron.",
|
||||
"maintenanceTime": "bijv. 2 uur, 1 nov om 17:00",
|
||||
"maintenanceEstimatedTimeDescription": "Wanneer u verwacht dat het onderhoud voltooid is",
|
||||
"editDomain": "Domein bewerken",
|
||||
@@ -2970,8 +2489,8 @@
|
||||
"logIn": "Log in",
|
||||
"deviceInformation": "Apparaat informatie",
|
||||
"deviceInformationDescription": "Informatie over het apparaat en de agent",
|
||||
"deviceSecurity": "Apparaat beveiliging",
|
||||
"deviceSecurityDescription": "Apparaat beveiligingsinformatie",
|
||||
"deviceSecurity": "Device Security",
|
||||
"deviceSecurityDescription": "Device security posture information",
|
||||
"platform": "Platform",
|
||||
"macosVersion": "macOS versie",
|
||||
"windowsVersion": "Windows versie",
|
||||
@@ -2984,16 +2503,16 @@
|
||||
"hostname": "Hostname",
|
||||
"firstSeen": "Eerst gezien",
|
||||
"lastSeen": "Laatst gezien op",
|
||||
"biometricsEnabled": "Biometrie ingeschakeld",
|
||||
"diskEncrypted": "Schijf versleuteld",
|
||||
"firewallEnabled": "Firewall ingeschakeld",
|
||||
"autoUpdatesEnabled": "Auto Updates Ingeschakeld",
|
||||
"tpmAvailable": "TPM beschikbaar",
|
||||
"windowsAntivirusEnabled": "Antivirus ingeschakeld",
|
||||
"macosSipEnabled": "Systeemintegriteitsbescherming (SIP)",
|
||||
"biometricsEnabled": "Biometrics Enabled",
|
||||
"diskEncrypted": "Disk Encrypted",
|
||||
"firewallEnabled": "Firewall Enabled",
|
||||
"autoUpdatesEnabled": "Auto Updates Enabled",
|
||||
"tpmAvailable": "TPM Available",
|
||||
"windowsDefenderEnabled": "Windows Defender Enabled",
|
||||
"macosSipEnabled": "System Integrity Protection (SIP)",
|
||||
"macosGatekeeperEnabled": "Gatekeeper",
|
||||
"macosFirewallStealthMode": "Firewall Verberg Modus",
|
||||
"linuxAppArmorEnabled": "Appharnas",
|
||||
"macosFirewallStealthMode": "Firewall Stealth Mode",
|
||||
"linuxAppArmorEnabled": "AppArmor",
|
||||
"linuxSELinuxEnabled": "SELinux",
|
||||
"deviceSettingsDescription": "Apparaatinformatie en -instellingen bekijken",
|
||||
"devicePendingApprovalDescription": "Dit apparaat wacht op goedkeuring",
|
||||
@@ -3016,131 +2535,5 @@
|
||||
"approvalsEmptyStateStep2Title": "Toestel goedkeuringen inschakelen",
|
||||
"approvalsEmptyStateStep2Description": "Bewerk een rol en schakel de optie 'Vereist Apparaat Goedkeuringen' in. Gebruikers met deze rol hebben admin goedkeuring nodig voor nieuwe apparaten.",
|
||||
"approvalsEmptyStatePreviewDescription": "Voorbeeld: Indien ingeschakeld, zullen in afwachting van apparaatverzoeken hier verschijnen om te beoordelen",
|
||||
"approvalsEmptyStateButtonText": "Rollen beheren",
|
||||
"domainErrorTitle": "We ondervinden problemen bij het controleren van uw domein",
|
||||
"idpAdminAutoProvisionPoliciesTabHint": "Configureer rolverrekening en organisatie beleid in het <policiesTabLink>Auto Provision Settings</policiesTabLink> tab.",
|
||||
"streamingTitle": "Event streaming",
|
||||
"streamingDescription": "Stream events van uw organisatie naar externe bestemmingen in realtime.",
|
||||
"streamingUnnamedDestination": "Naamloze bestemming",
|
||||
"streamingNoUrlConfigured": "Geen URL ingesteld",
|
||||
"streamingAddDestination": "Bestemming toevoegen",
|
||||
"streamingHttpWebhookTitle": "HTTP Webhook",
|
||||
"streamingHttpWebhookDescription": "Stuur gebeurtenissen naar elk HTTP eindpunt met flexibele authenticatie en template.",
|
||||
"streamingS3Title": "Amazon S3",
|
||||
"streamingS3Description": "Stream events naar een S3-compatibele object-opslagemmer. Binnenkort beschikbaar.",
|
||||
"streamingDatadogTitle": "Datadog",
|
||||
"streamingDatadogDescription": "Stuur gebeurtenissen rechtstreeks door naar je Datadog account. Binnenkort beschikbaar.",
|
||||
"streamingTypePickerDescription": "Kies een bestemmingstype om te beginnen.",
|
||||
"streamingFailedToLoad": "Laden van bestemmingen mislukt",
|
||||
"streamingUnexpectedError": "Er is een onverwachte fout opgetreden.",
|
||||
"streamingFailedToUpdate": "Bijwerken bestemming mislukt",
|
||||
"streamingDeletedSuccess": "Bestemming succesvol verwijderd",
|
||||
"streamingFailedToDelete": "Verwijderen van bestemming mislukt",
|
||||
"streamingDeleteTitle": "Verwijder bestemming",
|
||||
"streamingDeleteButtonText": "Verwijder bestemming",
|
||||
"streamingDeleteDialogAreYouSure": "Weet u zeker dat u wilt verwijderen",
|
||||
"streamingDeleteDialogThisDestination": "deze bestemming",
|
||||
"streamingDeleteDialogPermanentlyRemoved": "? Alle configuratie zal permanent worden verwijderd.",
|
||||
"httpDestEditTitle": "Bewerk bestemming",
|
||||
"httpDestAddTitle": "Voeg HTTP bestemming toe",
|
||||
"httpDestEditDescription": "Werk de configuratie voor deze HTTP-event streaming bestemming bij.",
|
||||
"httpDestAddDescription": "Configureer een nieuw HTTP-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.",
|
||||
"S3DestEditTitle": "Bestemming bewerken",
|
||||
"S3DestAddTitle": "S3-bestemming toevoegen",
|
||||
"S3DestEditDescription": "Werk de configuratie bij voor deze S3-gebeurtenisstreamingbestemming.",
|
||||
"S3DestAddDescription": "Configureer een nieuw S3-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.",
|
||||
"datadogDestEditTitle": "Bestemming bewerken",
|
||||
"datadogDestAddTitle": "Datadog-bestemming toevoegen",
|
||||
"datadogDestEditDescription": "Werk de configuratie bij voor deze Datadog-gebeurtenisstreamingbestemming.",
|
||||
"datadogDestAddDescription": "Configureer een nieuw Datadog-eindpunt om de gebeurtenissen van uw organisatie te ontvangen.",
|
||||
"httpDestTabSettings": "Instellingen",
|
||||
"httpDestTabHeaders": "Kopteksten",
|
||||
"httpDestTabBody": "Lichaam",
|
||||
"httpDestTabLogs": "Logboeken",
|
||||
"httpDestNamePlaceholder": "Mijn HTTP-bestemming",
|
||||
"httpDestUrlLabel": "Bestemming URL",
|
||||
"httpDestUrlErrorHttpRequired": "URL moet http of https gebruiken",
|
||||
"httpDestUrlErrorHttpsRequired": "HTTPS is vereist op cloud implementaties",
|
||||
"httpDestUrlErrorInvalid": "Voer een geldige URL in (bijv. https://example.com/webhook)",
|
||||
"httpDestAuthTitle": "Authenticatie",
|
||||
"httpDestAuthDescription": "Kies hoe verzoeken voor uw eindpunt zijn geverifieerd.",
|
||||
"httpDestAuthNoneTitle": "Geen authenticatie",
|
||||
"httpDestAuthNoneDescription": "Stuurt verzoeken zonder toestemmingskop.",
|
||||
"httpDestAuthBearerTitle": "Betere Token",
|
||||
"httpDestAuthBearerDescription": "Voegt een Authorization: Bearer '<token>' header toe aan elk verzoek.",
|
||||
"httpDestAuthBearerPlaceholder": "Uw API-sleutel of -token",
|
||||
"httpDestAuthBasicTitle": "Basis authenticatie",
|
||||
"httpDestAuthBasicDescription": "Voegt een Authorization: Basic '<credentials>' header toe. Verstrek inloggegevens als gebruikersnaam:wachtwoord.",
|
||||
"httpDestAuthBasicPlaceholder": "Gebruikersnaam:wachtwoord",
|
||||
"httpDestAuthCustomTitle": "Aangepaste koptekst",
|
||||
"httpDestAuthCustomDescription": "Specificeer een aangepaste HTTP header naam en waarde voor authenticatie (bijv. X-API-Key).",
|
||||
"httpDestAuthCustomHeaderNamePlaceholder": "Header naam (bijv. X-API-Key)",
|
||||
"httpDestAuthCustomHeaderValuePlaceholder": "Header waarde",
|
||||
"httpDestCustomHeadersTitle": "Aangepaste HTTP Headers",
|
||||
"httpDestCustomHeadersDescription": "Voeg aangepaste headers toe aan elk uitgaande verzoek. Handig voor statische tokens of een aangepast Content-Type. Standaard Content-Type: application/json wordt verzonden.",
|
||||
"httpDestNoHeadersConfigured": "Geen aangepaste headers geconfigureerd. Klik op \"Header\" om er een toe te voegen.",
|
||||
"httpDestHeaderNamePlaceholder": "Naam koptekst",
|
||||
"httpDestHeaderValuePlaceholder": "Waarde",
|
||||
"httpDestAddHeader": "Koptekst toevoegen",
|
||||
"httpDestBodyTemplateTitle": "Aangepaste Body Sjabloon",
|
||||
"httpDestBodyTemplateDescription": "Bestuur de JSON payload structuur verzonden naar uw eindpunt. Indien uitgeschakeld, wordt een standaard JSON object verzonden voor elke event.",
|
||||
"httpDestEnableBodyTemplate": "Aangepaste lichaam sjabloon inschakelen",
|
||||
"httpDestBodyTemplateLabel": "Body sjabloon (JSON)",
|
||||
"httpDestBodyTemplateHint": "Gebruik sjabloonvariabelen om te verwijzen naar gebeurtenisvelden in uw payload.",
|
||||
"httpDestPayloadFormatTitle": "Payload formaat",
|
||||
"httpDestPayloadFormatDescription": "Hoe evenementen worden geserialiseerd in elk verzoeklichaam.",
|
||||
"httpDestFormatJsonArrayTitle": "JSON matrix",
|
||||
"httpDestFormatJsonArrayDescription": "Eén verzoek per batch, lichaam is een JSON-array. Compatibel met de meeste algemene webhooks en Datadog.",
|
||||
"httpDestFormatNdjsonTitle": "NDJSON",
|
||||
"httpDestFormatNdjsonDescription": "Eén aanvraag per batch, lichaam is nieuwe JSON gescheiden - één object per regel, geen buitenste array. Vereist door Splunk HEC, Elastic / OpenSearch, en Grafana Loki.",
|
||||
"httpDestFormatSingleTitle": "Eén afspraak per verzoek",
|
||||
"httpDestFormatSingleDescription": "Stuurt een aparte HTTP POST voor elk individueel event. Gebruik alleen voor eindpunten die geen batches kunnen verwerken.",
|
||||
"httpDestLogTypesTitle": "Log soorten",
|
||||
"httpDestLogTypesDescription": "Kies welke log types doorgestuurd worden naar deze bestemming. Alleen ingeschakelde log types worden gestreden.",
|
||||
"httpDestAccessLogsTitle": "Toegang tot logboek",
|
||||
"httpDestAccessLogsDescription": "Hulpbrontoegangspogingen, inclusief geauthenticeerde en weigerde aanvragen.",
|
||||
"httpDestActionLogsTitle": "Actie logs",
|
||||
"httpDestActionLogsDescription": "Administratieve acties uitgevoerd door gebruikers binnen de organisatie.",
|
||||
"httpDestConnectionLogsTitle": "Connectie Logs",
|
||||
"httpDestConnectionLogsDescription": "Verbinding met de Site en tunnel maken verbroken, inclusief verbindingen en verbindingen.",
|
||||
"httpDestRequestLogsTitle": "Logboeken aanvragen",
|
||||
"httpDestRequestLogsDescription": "HTTP request logs voor proxied hulpmiddelen, waaronder methode, pad en response code.",
|
||||
"httpDestSaveChanges": "Wijzigingen opslaan",
|
||||
"httpDestCreateDestination": "Maak bestemming aan",
|
||||
"httpDestUpdatedSuccess": "Bestemming succesvol bijgewerkt",
|
||||
"httpDestCreatedSuccess": "Bestemming succesvol aangemaakt",
|
||||
"httpDestUpdateFailed": "Bijwerken bestemming mislukt",
|
||||
"httpDestCreateFailed": "Aanmaken bestemming mislukt",
|
||||
"followRedirects": "Volg omleidingen",
|
||||
"followRedirectsDescription": "Volg automatisch HTTP-omleidingen voor verzoeken.",
|
||||
"alertingErrorWebhookUrl": "Voer een geldige URL voor de webhook in.",
|
||||
"healthCheckStrategyHttp": "Valideert connectiviteit en controleert de HTTP-responsstatus.",
|
||||
"healthCheckStrategyTcp": "Verifieert alleen TCP-connectiviteit zonder de respons te inspecteren.",
|
||||
"healthCheckStrategySnmp": "Maakt een SNMP-verzoek om de gezondheid van netwerkapparaten en infrastructuur te controleren.",
|
||||
"healthCheckStrategyIcmp": "Gebruikt ICMP-verzoeken (pings) om te controleren of een bron bereikbaar en responsief is.",
|
||||
"healthCheckTabStrategy": "Strategie",
|
||||
"healthCheckTabConnection": "Verbinding",
|
||||
"healthCheckTabAdvanced": "Geavanceerd",
|
||||
"healthCheckStrategyNotAvailable": "Deze strategie is niet beschikbaar. Neem contact op met sales om deze functie in te schakelen.",
|
||||
"uptime30d": "Beschikbaarheid (30d)",
|
||||
"idpAddActionCreateNew": "Nieuwe identiteitsprovider aanmaken",
|
||||
"idpAddActionImportFromOrg": "Importeer vanuit een andere organisatie",
|
||||
"idpImportDialogTitle": "Importeer Identiteitsprovider",
|
||||
"idpImportDialogDescription": "Kies een identiteitsprovider van een organisatie waar u beheerder bent. Het wordt gekoppeld aan deze organisatie.",
|
||||
"idpImportSearchPlaceholder": "Zoek op organisatie- of providernamen...",
|
||||
"idpImportEmpty": "Geen identiteitsproviders gevonden.",
|
||||
"idpImportedDescription": "Identiteitsprovider succesvol geïmporteerd.",
|
||||
"idpDeleteGlobalQuestion": "Weet u zeker dat u deze identiteitsprovider permanent wilt verwijderen?",
|
||||
"idpDeleteGlobalDescription": "Hiermee wordt de identiteitsprovider permanent verwijderd uit alle organisaties waarmee het is geassocieerd.",
|
||||
"idpUnassociateTitle": "Koppel Identiteitsprovider los",
|
||||
"idpUnassociateQuestion": "Weet u zeker dat u deze identiteitsprovider van deze organisatie wilt loskoppelen?",
|
||||
"idpUnassociateDescription": "Alle gebruikers die aan deze identiteitsprovider zijn gekoppeld, worden uit deze organisatie verwijderd, maar de identiteitsprovider blijft bestaan voor andere gerelateerde organisaties.",
|
||||
"idpUnassociateConfirm": "Bevestig ontkoppelen identiteitsprovider",
|
||||
"idpUnassociateWarning": "Dit kan niet ongedaan worden gemaakt voor deze organisatie.",
|
||||
"idpUnassociatedDescription": "Identiteitsprovider succesvol losgekoppeld van deze organisatie",
|
||||
"idpUnassociateMenu": "Ontkoppelen",
|
||||
"idpDeleteAllOrgsMenu": "Verwijderen",
|
||||
"publicIpEndpoint": "Eindpunt",
|
||||
"lastTriggeredAt": "Laatste Trigger",
|
||||
"reject": "Afwijzen"
|
||||
"approvalsEmptyStateButtonText": "Rollen beheren"
|
||||
}
|
||||
|
||||
+27
-634
File diff suppressed because it is too large
Load Diff
+27
-634
File diff suppressed because it is too large
Load Diff
+30
-637
File diff suppressed because it is too large
Load Diff
+27
-634
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user