mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-12 12:48:59 +00:00
Compare commits
108 Commits
1.15.3-s.1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bff9d33ee6 | ||
|
|
edf506953b | ||
|
|
5e11746549 | ||
|
|
1ae315e303 | ||
|
|
758b03ab25 | ||
|
|
e756fad573 | ||
|
|
3547450b03 | ||
|
|
733f6692c6 | ||
|
|
2d83160b16 | ||
|
|
256fa880dd | ||
|
|
b08c5f5c67 | ||
|
|
d0862a2d26 | ||
|
|
e97340ed52 | ||
|
|
e27c81eea6 | ||
|
|
7f7f3d43b2 | ||
|
|
4b1b772098 | ||
|
|
f66b88490f | ||
|
|
18f9157169 | ||
|
|
6eb82a807b | ||
|
|
bf57a97833 | ||
|
|
e9e2093220 | ||
|
|
c3540da2e3 | ||
|
|
d228cf56dd | ||
|
|
8f4cecd963 | ||
|
|
66adff44bb | ||
|
|
be41c094dc | ||
|
|
273848ca18 | ||
|
|
1e9dbead3b | ||
|
|
aeaa8ba133 | ||
|
|
24654af635 | ||
|
|
e88a21d6db | ||
|
|
bcd01badaf | ||
|
|
8e063506e0 | ||
|
|
84f5d6137a | ||
|
|
0a8565f5e8 | ||
|
|
bd8da25a46 | ||
|
|
a841f588dd | ||
|
|
75a4362ce3 | ||
|
|
e763e001e5 | ||
|
|
69475a0ae7 | ||
|
|
53e14c2ad7 | ||
|
|
1edc33148a | ||
|
|
a4cbfc74e4 | ||
|
|
c0d25aeb02 | ||
|
|
40f49bf6da | ||
|
|
0bfce87dc6 | ||
|
|
2a0655e9de | ||
|
|
a86cfa5934 | ||
|
|
54b77523c5 | ||
|
|
ba06c8928d | ||
|
|
c8a4ac1ed4 | ||
|
|
143acbae48 | ||
|
|
937f6fdae8 | ||
|
|
ba7239ac08 | ||
|
|
2e748274c0 | ||
|
|
eab2750953 | ||
|
|
17b6cb0c73 | ||
|
|
ce74489df5 | ||
|
|
342b188fae | ||
|
|
fa6fee7b55 | ||
|
|
c53d5a4d7d | ||
|
|
521e905724 | ||
|
|
4623090050 | ||
|
|
dd9e5cc541 | ||
|
|
626be6a347 | ||
|
|
56327ed503 | ||
|
|
9ff863db5e | ||
|
|
e2ac6e6d4d | ||
|
|
df4101875a | ||
|
|
3f5c788d48 | ||
|
|
94ac3ec76e | ||
|
|
af7263a0b1 | ||
|
|
035396f95c | ||
|
|
f318f6304b | ||
|
|
9d0ff472e5 | ||
|
|
d27482e812 | ||
|
|
69c2212ea0 | ||
|
|
10be9bcd56 | ||
|
|
f531def0d2 | ||
|
|
ed40eae655 | ||
|
|
ba5ae6ed04 | ||
|
|
0a6301697e | ||
|
|
13b4fc6725 | ||
|
|
a095dddd01 | ||
|
|
1b5cfaa49b | ||
|
|
66f3fabbae | ||
|
|
0be8fb7931 | ||
|
|
431e6ffaae | ||
|
|
7d8185e0ee | ||
|
|
dff45748bd | ||
|
|
e6464929ff | ||
|
|
122053939d | ||
|
|
300b4a3706 | ||
|
|
81ef2db7f8 | ||
|
|
c41e8be3e8 | ||
|
|
41bab0ce0b | ||
|
|
5f26b9eeea | ||
|
|
1cca69ad23 | ||
|
|
410ed3949b | ||
|
|
efc6ef3075 | ||
|
|
e101ac341b | ||
|
|
6cfc7b7c69 | ||
|
|
313acabc86 | ||
|
|
34cced872f | ||
|
|
ac09e3aaf9 | ||
|
|
a8f6b6c1da | ||
|
|
f899326189 | ||
|
|
f2ba4b270f |
@@ -31,4 +31,5 @@ dist
|
||||
migrations/
|
||||
config/
|
||||
build.ts
|
||||
tsconfig.json
|
||||
tsconfig.json
|
||||
migrations/
|
||||
|
||||
189
.github/workflows/cicd.yml
vendored
189
.github/workflows/cicd.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI/CD Pipeline
|
||||
name: Public 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.
|
||||
@@ -440,6 +440,10 @@ 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
|
||||
@@ -471,94 +475,123 @@ 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
|
||||
|
||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
|
||||
REF="${BASE_IMAGE}@${DIGEST}"
|
||||
echo "Resolved digest: ${REF}"
|
||||
# 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}"
|
||||
|
||||
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}"
|
||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then
|
||||
VERIFIED_INDEX=true
|
||||
else
|
||||
VERIFIED_INDEX=false
|
||||
fi
|
||||
|
||||
echo "==> cosign verify (keyless policy) ${REF}"
|
||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then
|
||||
VERIFIED_INDEX_KEYLESS=true
|
||||
else
|
||||
VERIFIED_INDEX_KEYLESS=false
|
||||
fi
|
||||
|
||||
# If index verification fails, attempt to verify child platform manifests
|
||||
if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then
|
||||
echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
CHILD_VERIFIED=false
|
||||
|
||||
for ARCH in arm64 amd64; do
|
||||
CHILD_TAG="${IMAGE_TAG}-${ARCH}"
|
||||
echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}"
|
||||
CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)"
|
||||
if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then
|
||||
CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}"
|
||||
echo "==> cosign verify (public key) child ${CHILD_REF}"
|
||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then
|
||||
CHILD_VERIFIED=true
|
||||
echo "Public key verification succeeded for child ${CHILD_REF}"
|
||||
else
|
||||
echo "Public key verification failed for child ${CHILD_REF}"
|
||||
# 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 (keyless policy) child ${CHILD_REF}"
|
||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then
|
||||
CHILD_VERIFIED=true
|
||||
echo "Keyless verification succeeded for child ${CHILD_REF}"
|
||||
else
|
||||
echo "Keyless verification failed for child ${CHILD_REF}"
|
||||
fi
|
||||
else
|
||||
echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${CHILD_VERIFIED}" != "true" ]; then
|
||||
echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
exit 10
|
||||
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
|
||||
fi
|
||||
|
||||
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
echo "==> cosign verify (keyless policy) ${REF}"
|
||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then
|
||||
VERIFIED_INDEX_KEYLESS=true
|
||||
else
|
||||
VERIFIED_INDEX_KEYLESS=false
|
||||
fi
|
||||
|
||||
# If index verification fails, attempt to verify child platform manifests
|
||||
if [ "${VERIFIED_INDEX}" != "true" ] || [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then
|
||||
echo "Index verification not available; attempting child manifest verification for ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
CHILD_VERIFIED=false
|
||||
|
||||
for ARCH in arm64 amd64; do
|
||||
CHILD_TAG="${IMAGE_TAG}-${ARCH}"
|
||||
echo "Resolving child digest for ${BASE_IMAGE}:${CHILD_TAG}"
|
||||
CHILD_DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${CHILD_TAG} | jq -r '.Digest' || true)"
|
||||
if [ -n "${CHILD_DIGEST}" ] && [ "${CHILD_DIGEST}" != "null" ]; then
|
||||
CHILD_REF="${BASE_IMAGE}@${CHILD_DIGEST}"
|
||||
echo "==> cosign verify (public key) child ${CHILD_REF}"
|
||||
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${CHILD_REF}' -o text"; then
|
||||
CHILD_VERIFIED=true
|
||||
echo "Public key verification succeeded for child ${CHILD_REF}"
|
||||
else
|
||||
echo "Public key verification failed for child ${CHILD_REF}"
|
||||
fi
|
||||
|
||||
echo "==> cosign verify (keyless policy) child ${CHILD_REF}"
|
||||
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${CHILD_REF}' -o text"; then
|
||||
CHILD_VERIFIED=true
|
||||
echo "Keyless verification succeeded for child ${CHILD_REF}"
|
||||
else
|
||||
echo "Keyless verification failed for child ${CHILD_REF}"
|
||||
fi
|
||||
else
|
||||
echo "No child digest found for ${BASE_IMAGE}:${CHILD_TAG}; skipping"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${CHILD_VERIFIED}" != "true" ]; then
|
||||
echo "Failed to verify index and no child manifests verified for ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
) || TAG_FAILED=true
|
||||
|
||||
if [ "$TAG_FAILED" = "true" ]; then
|
||||
echo "⚠️ WARNING: Failed to sign/verify ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
FAILED_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}")
|
||||
else
|
||||
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||
SUCCESSFUL_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}")
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
echo "All images signed and verified successfully!"
|
||||
# Report summary
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "Sign and Verify Summary"
|
||||
echo "=========================================="
|
||||
echo "Successful: ${#SUCCESSFUL_TAGS[@]}"
|
||||
echo "Failed: ${#FAILED_TAGS[@]}"
|
||||
echo ""
|
||||
|
||||
if [ ${#FAILED_TAGS[@]} -gt 0 ]; then
|
||||
echo "Failed tags:"
|
||||
for tag in "${FAILED_TAGS[@]}"; do
|
||||
echo " - $tag"
|
||||
done
|
||||
echo ""
|
||||
echo "⚠️ WARNING: Some tags failed to sign/verify, but continuing anyway"
|
||||
else
|
||||
echo "✓ All images signed and verified successfully!"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
post-run:
|
||||
|
||||
426
.github/workflows/cicd.yml.backup
vendored
426
.github/workflows/cicd.yml.backup
vendored
@@ -1,426 +0,0 @@
|
||||
name: CI/CD Pipeline
|
||||
|
||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write # for GHCR push
|
||||
id-token: write # for Cosign Keyless (OIDC) Signing
|
||||
|
||||
# Required secrets:
|
||||
# - DOCKER_HUB_USERNAME / DOCKER_HUB_ACCESS_TOKEN: push to Docker Hub
|
||||
# - GITHUB_TOKEN: used for GHCR login and OIDC keyless signing
|
||||
# - COSIGN_PRIVATE_KEY / COSIGN_PASSWORD / COSIGN_PUBLIC_KEY: for key-based signing
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9]+.[0-9]+.[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+"
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pre-run:
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||
role-duration-seconds: 3600
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Verify AWS identity
|
||||
run: aws sts get-caller-identity
|
||||
|
||||
- name: Start EC2 instances
|
||||
run: |
|
||||
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
|
||||
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
|
||||
echo "EC2 instances started"
|
||||
|
||||
|
||||
release-arm:
|
||||
name: Build and Release (ARM64)
|
||||
runs-on: [self-hosted, linux, arm64, us-east-1]
|
||||
needs: [pre-run]
|
||||
if: >-
|
||||
${{
|
||||
needs.pre-run.result == 'success'
|
||||
}}
|
||||
# Job-level timeout to avoid runaway or stuck runs
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
# Target images
|
||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Monitor storage space
|
||||
run: |
|
||||
THRESHOLD=75
|
||||
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
|
||||
echo "Used space: $USED_SPACE%"
|
||||
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
|
||||
echo "Used space is below the threshold of 75% free. Running Docker system prune."
|
||||
echo y | docker system prune -a
|
||||
else
|
||||
echo "Storage space is above the threshold. No action needed."
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Extract tag name
|
||||
id: get-tag
|
||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
||||
cat server/lib/consts.ts
|
||||
shell: bash
|
||||
|
||||
- name: Check if release candidate
|
||||
id: check-rc
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
echo "IS_RC=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IS_RC=false" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Build and push Docker images (Docker Hub - ARM64)
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [ "$IS_RC" = "true" ]; then
|
||||
make build-rc-arm tag=$TAG
|
||||
else
|
||||
make build-release-arm tag=$TAG
|
||||
fi
|
||||
echo "Built & pushed ARM64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
|
||||
shell: bash
|
||||
|
||||
release-amd:
|
||||
name: Build and Release (AMD64)
|
||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
||||
needs: [pre-run]
|
||||
if: >-
|
||||
${{
|
||||
needs.pre-run.result == 'success'
|
||||
}}
|
||||
# Job-level timeout to avoid runaway or stuck runs
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
# Target images
|
||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Monitor storage space
|
||||
run: |
|
||||
THRESHOLD=75
|
||||
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
|
||||
echo "Used space: $USED_SPACE%"
|
||||
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
|
||||
echo "Used space is below the threshold of 75% free. Running Docker system prune."
|
||||
echo y | docker system prune -a
|
||||
else
|
||||
echo "Storage space is above the threshold. No action needed."
|
||||
fi
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Extract tag name
|
||||
id: get-tag
|
||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
||||
cat server/lib/consts.ts
|
||||
shell: bash
|
||||
|
||||
- name: Check if release candidate
|
||||
id: check-rc
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
echo "IS_RC=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IS_RC=false" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Build and push Docker images (Docker Hub - AMD64)
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [ "$IS_RC" = "true" ]; then
|
||||
make build-rc-amd tag=$TAG
|
||||
else
|
||||
make build-release-amd tag=$TAG
|
||||
fi
|
||||
echo "Built & pushed AMD64 images to: ${{ env.DOCKERHUB_IMAGE }}:${TAG}"
|
||||
shell: bash
|
||||
|
||||
create-manifest:
|
||||
name: Create Multi-Arch Manifests
|
||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
||||
needs: [release-arm, release-amd]
|
||||
if: >-
|
||||
${{
|
||||
needs.release-arm.result == 'success' &&
|
||||
needs.release-amd.result == 'success'
|
||||
}}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: docker.io
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
|
||||
- name: Extract tag name
|
||||
id: get-tag
|
||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Check if release candidate
|
||||
id: check-rc
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [[ "$TAG" == *"-rc."* ]]; then
|
||||
echo "IS_RC=true" >> $GITHUB_ENV
|
||||
else
|
||||
echo "IS_RC=false" >> $GITHUB_ENV
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Create multi-arch manifests
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
if [ "$IS_RC" = "true" ]; then
|
||||
make create-manifests-rc tag=$TAG
|
||||
else
|
||||
make create-manifests tag=$TAG
|
||||
fi
|
||||
echo "Created multi-arch manifests for tag: ${TAG}"
|
||||
shell: bash
|
||||
|
||||
sign-and-package:
|
||||
name: Sign and Package
|
||||
runs-on: [self-hosted, linux, x64, us-east-1]
|
||||
needs: [release-arm, release-amd, create-manifest]
|
||||
if: >-
|
||||
${{
|
||||
needs.release-arm.result == 'success' &&
|
||||
needs.release-amd.result == 'success' &&
|
||||
needs.create-manifest.result == 'success'
|
||||
}}
|
||||
# Job-level timeout to avoid runaway or stuck runs
|
||||
timeout-minutes: 120
|
||||
env:
|
||||
# Target images
|
||||
DOCKERHUB_IMAGE: docker.io/fosrl/${{ github.event.repository.name }}
|
||||
GHCR_IMAGE: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Extract tag name
|
||||
id: get-tag
|
||||
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: 1.24
|
||||
|
||||
- name: Update version in package.json
|
||||
run: |
|
||||
TAG=${{ env.TAG }}
|
||||
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
|
||||
cat server/lib/consts.ts
|
||||
shell: bash
|
||||
|
||||
- name: Pull latest Gerbil version
|
||||
id: get-gerbil-tag
|
||||
run: |
|
||||
LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name')
|
||||
echo "LATEST_GERBIL_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Pull latest Badger version
|
||||
id: get-badger-tag
|
||||
run: |
|
||||
LATEST_TAG=$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name')
|
||||
echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
|
||||
- name: Update install/main.go
|
||||
run: |
|
||||
PANGOLIN_VERSION=${{ env.TAG }}
|
||||
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }}
|
||||
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
|
||||
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go
|
||||
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go
|
||||
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go
|
||||
echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION"
|
||||
cat install/main.go
|
||||
shell: bash
|
||||
|
||||
- name: Build installer
|
||||
working-directory: install
|
||||
run: |
|
||||
make go-build-release
|
||||
|
||||
- name: Upload artifacts from /install/bin
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: install-bin
|
||||
path: install/bin/
|
||||
|
||||
- name: Install skopeo + jq
|
||||
# skopeo: copy/inspect images between registries
|
||||
# jq: JSON parsing tool used to extract digest values
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y skopeo jq
|
||||
skopeo --version
|
||||
shell: bash
|
||||
|
||||
- name: Login to GHCR
|
||||
env:
|
||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
||||
run: |
|
||||
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
|
||||
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
|
||||
shell: bash
|
||||
|
||||
- name: Copy tag from Docker Hub to GHCR
|
||||
# Mirror the already-built image (all architectures) to GHCR so we can sign it
|
||||
# Wait a bit for both architectures to be available in Docker Hub manifest
|
||||
env:
|
||||
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG=${{ env.TAG }}
|
||||
echo "Waiting for multi-arch manifest to be ready..."
|
||||
sleep 30
|
||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
|
||||
skopeo copy --all --retry-times 3 \
|
||||
docker://$DOCKERHUB_IMAGE:$TAG \
|
||||
docker://$GHCR_IMAGE:$TAG
|
||||
shell: bash
|
||||
|
||||
- name: Login to GitHub Container Registry (for cosign)
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install cosign
|
||||
# cosign is used to sign and verify container images (key and keyless)
|
||||
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
|
||||
|
||||
- name: Dual-sign and verify (GHCR & Docker Hub)
|
||||
# Sign each image by digest using keyless (OIDC) and key-based signing,
|
||||
# then verify both the public key signature and the keyless OIDC signature.
|
||||
env:
|
||||
TAG: ${{ env.TAG }}
|
||||
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
|
||||
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
|
||||
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
|
||||
COSIGN_YES: "true"
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
issuer="https://token.actions.githubusercontent.com"
|
||||
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
|
||||
|
||||
for IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
|
||||
echo "Processing ${IMAGE}:${TAG}"
|
||||
|
||||
DIGEST="$(skopeo inspect --retry-times 3 docker://${IMAGE}:${TAG} | jq -r '.Digest')"
|
||||
REF="${IMAGE}@${DIGEST}"
|
||||
echo "Resolved digest: ${REF}"
|
||||
|
||||
echo "==> cosign sign (keyless) --recursive ${REF}"
|
||||
cosign sign --recursive "${REF}"
|
||||
|
||||
echo "==> cosign sign (key) --recursive ${REF}"
|
||||
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
|
||||
|
||||
echo "==> cosign verify (public key) ${REF}"
|
||||
cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text
|
||||
|
||||
echo "==> cosign verify (keyless policy) ${REF}"
|
||||
cosign verify \
|
||||
--certificate-oidc-issuer "${issuer}" \
|
||||
--certificate-identity-regexp "${id_regex}" \
|
||||
"${REF}" -o text
|
||||
done
|
||||
shell: bash
|
||||
|
||||
post-run:
|
||||
needs: [pre-run, release-arm, release-amd, create-manifest, sign-and-package]
|
||||
if: >-
|
||||
${{
|
||||
always() &&
|
||||
needs.pre-run.result == 'success' &&
|
||||
(needs.release-arm.result == 'success' || needs.release-arm.result == 'skipped' || needs.release-arm.result == 'failure') &&
|
||||
(needs.release-amd.result == 'success' || needs.release-amd.result == 'skipped' || needs.release-amd.result == 'failure') &&
|
||||
(needs.create-manifest.result == 'success' || needs.create-manifest.result == 'skipped' || needs.create-manifest.result == 'failure') &&
|
||||
(needs.sign-and-package.result == 'success' || needs.sign-and-package.result == 'skipped' || needs.sign-and-package.result == 'failure')
|
||||
}}
|
||||
runs-on: ubuntu-latest
|
||||
permissions: write-all
|
||||
steps:
|
||||
- name: Configure AWS credentials
|
||||
uses: aws-actions/configure-aws-credentials@v2
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
|
||||
role-duration-seconds: 3600
|
||||
aws-region: ${{ secrets.AWS_REGION }}
|
||||
|
||||
- name: Verify AWS identity
|
||||
run: aws sts get-caller-identity
|
||||
|
||||
- name: Stop EC2 instances
|
||||
run: |
|
||||
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
|
||||
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
|
||||
echo "EC2 instances stopped"
|
||||
2
.github/workflows/saas.yml
vendored
2
.github/workflows/saas.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: CI/CD Pipeline
|
||||
name: SAAS Pipeline
|
||||
|
||||
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
|
||||
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -34,7 +34,7 @@ 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:sqlite:push
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Увеличаване на броя на сайтовете",
|
||||
"idpManage": "Управление на доставчици на идентичност",
|
||||
"idpManageDescription": "Прегледайте и управлявайте доставчици на идентичност в системата",
|
||||
"idpGlobalModeBanner": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в <adminPanelLink>администраторския панел</adminPanelLink>. За да активирате IdPs за всяка организация, редактирайте конфигурацията на сървъра и задайте режима на IdP към org. <configDocsLink>Вижте документацията</configDocsLink>. Ако желаете да продължите да използвате глобалните IdPs и да премахнете това от настройките на организацията, изрично задайте режима на global в конфигурацията.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в <adminPanelLink>администраторския панел</adminPanelLink>. За да използвате доставчици на идентичност за всяка организация, трябва да надстроите до изданието Enterprise.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Доставчиците на идентичност (IdPs) за всяка организация са деактивирани на този сървър. Използват се глобални IdPs (споделени между всички организации). Управлявайте глобалните IdPs в <adminPanelLink>администраторския панел</adminPanelLink>. За да използвате доставчици на идентичност за всяка организация, е необходим лиценз за изданието Enterprise.",
|
||||
"idpDeletedDescription": "Доставчик на идентичност успешно изтрит",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Сигурни ли сте, че искате да изтриете доставчика за идентичност?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Край на следващата година",
|
||||
"actionLogsDescription": "Прегледайте историята на действията, извършени в тази организация",
|
||||
"accessLogsDescription": "Прегледайте заявките за удостоверяване на достъпа до ресурсите в тази организация",
|
||||
"licenseRequiredToUse": "Необходим е лиценз Enterprise, за да се използва тази функция.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> се изисква за използване на тази функция.",
|
||||
"licenseRequiredToUse": "Изисква се лиценз за <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink>, за да използвате тази функция. Тази функция е също достъпна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "Необходимо е <enterpriseEditionLink>изданието Enterprise</enterpriseEditionLink>, за да използвате тази функция. Тази функция е също достъпна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Решавач на сертификати",
|
||||
"certResolverDescription": "Изберете решавач на сертификати за използване за този ресурс.",
|
||||
"selectCertResolver": "Изберете решавач на сертификати",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Zvýšit počet stránek",
|
||||
"idpManage": "Spravovat poskytovatele identity",
|
||||
"idpManageDescription": "Zobrazit a spravovat poskytovatele identity v systému",
|
||||
"idpGlobalModeBanner": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v <adminPanelLink>admin panelu</adminPanelLink>. Chcete-li povolit IdP pro každou organizaci, upravte konfiguraci serveru a nastavte IdP režim na org. <configDocsLink>Viz dokumentace</configDocsLink>. Pokud chcete pokračovat v používání globálních IdP a zmizet z nastavení organizace, explicitně nastavte režim na globální v konfiguraci.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Spravujte globální IdP v <adminPanelLink>admin panelu</adminPanelLink>. Chcete-li použít poskytovatele identity pro každou organizaci, musíte přejít na Enterprise vydání.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Poskytovatelé identity (IdP) pro každou organizaci jsou na tomto serveru zakázáni. Používá globální IdP (sdílené napříč všemi organizacemi). Správa globálních IdP v <adminPanelLink>admin panelu</adminPanelLink>. Chcete-li použít poskytovatele identity pro každou organizaci, je vyžadována Enterprise licence.",
|
||||
"idpDeletedDescription": "Poskytovatel identity byl úspěšně odstraněn",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Jste si jisti, že chcete trvale odstranit poskytovatele identity?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Konec následujícího roku",
|
||||
"actionLogsDescription": "Zobrazit historii akcí provedených v této organizaci",
|
||||
"accessLogsDescription": "Zobrazit žádosti o ověření přístupu pro zdroje v této organizaci",
|
||||
"licenseRequiredToUse": "Pro použití této funkce je vyžadována licence pro podnikání.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> je vyžadována pro použití této funkce.",
|
||||
"licenseRequiredToUse": "Pro použití této funkce je vyžadována licence <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Tato funkce je také dostupná v <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> je vyžadována pro použití této funkce. Tato funkce je také k dispozici v <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Oddělovač certifikátů",
|
||||
"certResolverDescription": "Vyberte řešitele certifikátů pro tento dokument.",
|
||||
"selectCertResolver": "Vyberte řešič certifikátů",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Anzahl der Standorte erhöhen",
|
||||
"idpManage": "Identitätsanbieter verwalten",
|
||||
"idpManageDescription": "Identitätsanbieter im System anzeigen und verwalten",
|
||||
"idpGlobalModeBanner": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt über alle Organisationen). Verwalten Sie globale IdPs im <adminPanelLink>Admin-Panel</adminPanelLink>. Um IdPs pro Organisation zu aktivieren, bearbeiten Sie die Server-Konfiguration und setzen Sie den IdP-Modus auf org. <configDocsLink>Siehe Dokumentation</configDocsLink>. Wenn Sie weiterhin globale IdPs verwenden und diese in den Organisationseinstellungen verschwinden lassen wollen, setzen Sie den Modus explizit auf global in der Konfiguration.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im <adminPanelLink>Admin-Panel</adminPanelLink>verwalten. Um Identitätsanbieter pro Organisation nutzen zu können, müssen Sie zur Enterprise Edition upgraden.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Identitätsanbieter (IdPs) pro Organisation sind auf diesem Server deaktiviert. Es verwendet globale IdPs (geteilt in allen Organisationen). Globale IdPs im <adminPanelLink>Admin-Panel</adminPanelLink>verwalten. Um Identitätsanbieter pro Organisation zu verwenden, ist eine Enterprise-Lizenz erforderlich.",
|
||||
"idpDeletedDescription": "Identitätsanbieter erfolgreich gelöscht",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Sind Sie sicher, dass Sie den Identitätsanbieter dauerhaft löschen möchten?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Ende des folgenden Jahres",
|
||||
"actionLogsDescription": "Verlauf der in dieser Organisation durchgeführten Aktionen anzeigen",
|
||||
"accessLogsDescription": "Zugriffsauth-Anfragen für Ressourcen in dieser Organisation anzeigen",
|
||||
"licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine Enterprise-Lizenz erforderlich.",
|
||||
"ossEnterpriseEditionRequired": "Die <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> wird benötigt, um diese Funktion nutzen zu können.",
|
||||
"licenseRequiredToUse": "Um diese Funktion nutzen zu können, ist eine <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> Lizenz erforderlich. Diese Funktion ist auch in der <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> verfügbar.",
|
||||
"ossEnterpriseEditionRequired": "Um diese Funktion nutzen zu können, ist die <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> erforderlich. Diese Funktion ist auch in der <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> verfügbar.",
|
||||
"certResolver": "Zertifikatsauflöser",
|
||||
"certResolverDescription": "Wählen Sie den Zertifikatslöser aus, der für diese Ressource verwendet werden soll.",
|
||||
"selectCertResolver": "Zertifikatsauflöser auswählen",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Increase site count",
|
||||
"idpManage": "Manage Identity Providers",
|
||||
"idpManageDescription": "View and manage identity providers in the system",
|
||||
"idpGlobalModeBanner": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the <adminPanelLink>admin panel</adminPanelLink>. To enable IdPs per organization, edit the server config and set IdP mode to org. <configDocsLink>See the docs</configDocsLink>. If you want to continue using global IdPs and make this disappear from the organization settings, explicitly set the mode to global in the config.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the <adminPanelLink>admin panel</adminPanelLink>. To use identity providers per organization, you must upgrade to the Enterprise edition.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Identity providers (IdPs) per organization are disabled on this server. It is using global IdPs (shared across all organizations). Manage global IdPs in the <adminPanelLink>admin panel</adminPanelLink>. To use identity providers per organization, an Enterprise license is required.",
|
||||
"idpDeletedDescription": "Identity provider deleted successfully",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Are you sure you want to permanently delete the identity provider?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "End of following year",
|
||||
"actionLogsDescription": "View a history of actions performed in this organization",
|
||||
"accessLogsDescription": "View access auth requests for resources in this organization",
|
||||
"licenseRequiredToUse": "An Enterprise license is required to use this feature.",
|
||||
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature.",
|
||||
"licenseRequiredToUse": "An <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> license is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "The <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is required to use this feature. This feature is also available in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Certificate Resolver",
|
||||
"certResolverDescription": "Select the certificate resolver to use for this resource.",
|
||||
"selectCertResolver": "Select Certificate Resolver",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Aumentar el número de sitios",
|
||||
"idpManage": "Administrar proveedores de identidad",
|
||||
"idpManageDescription": "Ver y administrar proveedores de identidad en el sistema",
|
||||
"idpGlobalModeBanner": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el <adminPanelLink>panel de administración</adminPanelLink>. Para habilitar los IdPs por organización, edita la configuración del servidor y establece el modo de IdP en org. <configDocsLink>Consulta la documentación</configDocsLink>. Si deseas seguir utilizando IdPs globales y hacer que esto desaparezca de las configuraciones de la organización, establece explícitamente el modo en global en la configuración.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el <adminPanelLink>panel de administración</adminPanelLink>. Para usar proveedores de identidad por organización, debes actualizar a la edición Empresarial.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando identificadores globales (compartidos en todas las organizaciones). Gestionar identificaciones globales en el panel <adminPanelLink>de administración</adminPanelLink>. Para utilizar proveedores de identidad por organización, se requiere una licencia de empresa.",
|
||||
"idpDeletedDescription": "Proveedor de identidad eliminado correctamente",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Fin del año siguiente",
|
||||
"actionLogsDescription": "Ver un historial de acciones realizadas en esta organización",
|
||||
"accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización",
|
||||
"licenseRequiredToUse": "Se requiere una licencia Enterprise para utilizar esta función.",
|
||||
"ossEnterpriseEditionRequired": "La <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> es necesaria para utilizar esta función.",
|
||||
"licenseRequiredToUse": "Se requiere una licencia <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> para utilizar esta función. Esta característica también está disponible en <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "La <enterpriseEditionLink>versión Enterprise</enterpriseEditionLink> es necesaria para utilizar esta función. Esta función también está disponible en <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Resolver certificado",
|
||||
"certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.",
|
||||
"selectCertResolver": "Seleccionar Resolver Certificado",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Augmenter le nombre de sites",
|
||||
"idpManage": "Gérer les fournisseurs d'identité",
|
||||
"idpManageDescription": "Voir et gérer les fournisseurs d'identité dans le système",
|
||||
"idpGlobalModeBanner": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérez les IdPs globaux dans le panneau d'administration <adminPanelLink></adminPanelLink>. Pour activer les IdPs par organisation, éditez la configuration du serveur et réglez le mode IdP sur org. <configDocsLink>Voir la documentation</configDocsLink>. Si vous voulez continuer à utiliser les IdPs globaux et faire disparaître cela des paramètres de l'organisation, définissez explicitement le mode à global dans la configuration.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration <adminPanelLink></adminPanelLink>. Pour utiliser les fournisseurs d'identité par organisation, vous devez passer à l'édition Entreprise.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Les fournisseurs d'identité (IdPs) par organisation sont désactivés sur ce serveur. Il utilise des IdPs globaux (partagés entre toutes les organisations). Gérer les IdPs globaux dans le panneau d'administration <adminPanelLink></adminPanelLink>. Pour utiliser les fournisseurs d'identité par organisation, une licence d'entreprise est requise.",
|
||||
"idpDeletedDescription": "Fournisseur d'identité supprimé avec succès",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Êtes-vous sûr de vouloir supprimer définitivement le fournisseur d'identité?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Fin de l'année suivante",
|
||||
"actionLogsDescription": "Voir l'historique des actions effectuées dans cette organisation",
|
||||
"accessLogsDescription": "Voir les demandes d'authentification d'accès aux ressources de cette organisation",
|
||||
"licenseRequiredToUse": "Une licence Entreprise est nécessaire pour utiliser cette fonctionnalité.",
|
||||
"ossEnterpriseEditionRequired": "La version <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> est requise pour utiliser cette fonctionnalité.",
|
||||
"licenseRequiredToUse": "Une licence <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> est nécessaire pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "La version <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> est requise pour utiliser cette fonctionnalité. Cette fonctionnalité est également disponible dans <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Résolveur de certificat",
|
||||
"certResolverDescription": "Sélectionnez le solveur de certificat à utiliser pour cette ressource.",
|
||||
"selectCertResolver": "Sélectionnez le résolveur de certificat",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Aumenta conteggio siti",
|
||||
"idpManage": "Gestisci Provider di Identità",
|
||||
"idpManageDescription": "Visualizza e gestisci i provider di identità nel sistema",
|
||||
"idpGlobalModeBanner": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Sta utilizzando IdP globali (condivisi in tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione <adminPanelLink></adminPanelLink>. Per abilitare IdP per organizzazione, modificare la configurazione del server e impostare la modalità IdP su org. <configDocsLink>Vedere i documenti</configDocsLink>. Se si desidera continuare a utilizzare IdP globali e far sparire questo dalle impostazioni dell'organizzazione, impostare esplicitamente la modalità globale nella configurazione.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci gli IdP globali nel pannello di amministrazione <adminPanelLink></adminPanelLink>. Per utilizzare i provider di identità per organizzazione, è necessario aggiornare all'edizione Enterprise.",
|
||||
"idpGlobalModeBannerLicenseRequired": "I provider di identità (IdP) per organizzazione sono disabilitati su questo server. Utilizza IdP globali (condivisi tra tutte le organizzazioni). Gestisci IdP globali nel pannello di amministrazione <adminPanelLink></adminPanelLink>. Per utilizzare provider di identità per organizzazione, è richiesta una licenza Enterprise.",
|
||||
"idpDeletedDescription": "Provider di identità eliminato con successo",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Sei sicuro di voler eliminare definitivamente il provider di identità?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Fine dell'anno successivo",
|
||||
"actionLogsDescription": "Visualizza una cronologia delle azioni eseguite in questa organizzazione",
|
||||
"accessLogsDescription": "Visualizza le richieste di autenticazione di accesso per le risorse in questa organizzazione",
|
||||
"licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza Enterprise.",
|
||||
"ossEnterpriseEditionRequired": "L' <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> è necessaria per utilizzare questa funzione.",
|
||||
"licenseRequiredToUse": "Per utilizzare questa funzione è necessaria una licenza <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Questa funzionalità è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "L' <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> è necessaria per utilizzare questa funzione. Questa funzionalità è disponibile anche in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Risolutore Di Certificato",
|
||||
"certResolverDescription": "Selezionare il risolutore di certificati da usare per questa risorsa.",
|
||||
"selectCertResolver": "Seleziona Risolutore Di Certificato",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "사이트 수 증가",
|
||||
"idpManage": "아이덴티티 공급자 관리",
|
||||
"idpManageDescription": "시스템에서 ID 제공자를 보고 관리합니다",
|
||||
"idpGlobalModeBanner": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. <adminPanelLink>관리자 패널</adminPanelLink>에서 글로벌 IdP를 관리하십시오. 조직별 IdP를 활성화하려면 서버 설정을 편집하고 IdP 모드를 조직으로 설정하십시오. <configDocsLink>문서 보기</configDocsLink>. 글로벌 IdP 사용을 계속하고 조직 설정에서 이 항목을 제거하려면 설정에서 모드를 글로벌로 명시적으로 설정하십시오.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. <adminPanelLink>관리자 패널</adminPanelLink>에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 Enterprise 에디션으로 업그레이드해야 합니다.",
|
||||
"idpGlobalModeBannerLicenseRequired": "조직별 신원 제공자(IdP)는 이 서버에서 비활성화되었습니다. 이 서버는 모든 조직에 걸쳐 공유된 글로벌 IdP를 사용 중입니다. <adminPanelLink>관리자 패널</adminPanelLink>에서 글로벌 IdP를 관리하십시오. 조직별 신원 제공자를 사용하려면 엔터프라이즈 라이선스가 필요합니다.",
|
||||
"idpDeletedDescription": "신원 공급자가 성공적으로 삭제되었습니다",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "아이덴티티 공급자를 영구적으로 삭제하시겠습니까?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "다음 연도 말",
|
||||
"actionLogsDescription": "이 조직에서 수행된 작업의 기록을 봅니다",
|
||||
"accessLogsDescription": "이 조직의 자원에 대한 접근 인증 요청을 확인합니다",
|
||||
"licenseRequiredToUse": "이 기능을 사용하려면 Enterprise 라이선스가 필요합니다.",
|
||||
"ossEnterpriseEditionRequired": "이 기능을 사용하려면 <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink>이 필요합니다.",
|
||||
"licenseRequiredToUse": "이 기능을 사용하려면 <enterpriseLicenseLink>엔터프라이즈 에디션</enterpriseLicenseLink> 라이선스가 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다.",
|
||||
"ossEnterpriseEditionRequired": "이 기능을 사용하려면 <enterpriseEditionLink>엔터프라이즈 에디션</enterpriseEditionLink>이 필요합니다. 이 기능은 <pangolinCloudLink>판골린 클라우드</pangolinCloudLink>에서도 사용할 수 있습니다.",
|
||||
"certResolver": "인증서 해결사",
|
||||
"certResolverDescription": "이 리소스에 사용할 인증서 해결사를 선택하세요.",
|
||||
"selectCertResolver": "인증서 해결사 선택",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"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?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"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",
|
||||
"licenseRequiredToUse": "En Enterprise lisens er påkrevd for å bruke denne funksjonen.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen.",
|
||||
"licenseRequiredToUse": "En <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisens er påkrevd for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> er nødvendig for å bruke denne funksjonen. Denne funksjonen er også tilgjengelig i <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Sertifikat løser",
|
||||
"certResolverDescription": "Velg sertifikatløser som skal brukes for denne ressursen.",
|
||||
"selectCertResolver": "Velg sertifikatløser",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"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?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"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",
|
||||
"licenseRequiredToUse": "Een Enterprise-licentie is vereist om deze functie te gebruiken.",
|
||||
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken.",
|
||||
"licenseRequiredToUse": "Een <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> licentie is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "De <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> is vereist om deze functie te gebruiken. Deze functie is ook beschikbaar in <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Certificaat Resolver",
|
||||
"certResolverDescription": "Selecteer de certificaat resolver die moet worden gebruikt voor deze resource.",
|
||||
"selectCertResolver": "Certificaat Resolver selecteren",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Zwiększ liczbę witryn",
|
||||
"idpManage": "Zarządzaj dostawcami tożsamości",
|
||||
"idpManageDescription": "Wyświetl i zarządzaj dostawcami tożsamości w systemie",
|
||||
"idpGlobalModeBanner": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używa globalnych IdP (współdzielonych ze wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym <adminPanelLink></adminPanelLink>. Aby włączyć IdP na organizację, edytuj konfigurację serwera i ustaw tryb IdP na org. <configDocsLink>Zobacz dokumentację</configDocsLink>. Jeśli chcesz nadal używać globalnych IdP i sprawić, że zniknie to z ustawień organizacji, wyraźnie ustaw tryb globalny w konfiguracji.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym <adminPanelLink></adminPanelLink>. Aby korzystać z dostawców tożsamości na organizację, musisz zaktualizować do edycji Enterprise.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Dostawcy tożsamości (IdPs) na organizację są wyłączeni na tym serwerze. Używają globalnych IdP (współdzielonych między wszystkimi organizacjami). Zarządzaj globalnymi IdP w panelu administracyjnym <adminPanelLink></adminPanelLink>. Aby korzystać z dostawców tożsamości na organizację, wymagana jest licencja Enterprise.",
|
||||
"idpDeletedDescription": "Dostawca tożsamości został pomyślnie usunięty",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Czy na pewno chcesz trwale usunąć dostawcę tożsamości?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Koniec następnego roku",
|
||||
"actionLogsDescription": "Zobacz historię działań wykonywanych w tej organizacji",
|
||||
"accessLogsDescription": "Wyświetl prośby o autoryzację dostępu do zasobów w tej organizacji",
|
||||
"licenseRequiredToUse": "Licencja Enterprise jest wymagana do korzystania z tej funkcji.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> jest wymagany do korzystania z tej funkcji.",
|
||||
"licenseRequiredToUse": "Do korzystania z tej funkcji wymagana jest licencja <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> . Ta funkcja jest również dostępna w <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> jest wymagany do korzystania z tej funkcji. Ta funkcja jest również dostępna w <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Rozwiązywanie certyfikatów",
|
||||
"certResolverDescription": "Wybierz resolver certyfikatów do użycia dla tego zasobu.",
|
||||
"selectCertResolver": "Wybierz Resolver certyfikatów",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Aumentar contagem de sites",
|
||||
"idpManage": "Gerir Provedores de Identidade",
|
||||
"idpManageDescription": "Visualizar e gerir provedores de identidade no sistema",
|
||||
"idpGlobalModeBanner": "Provedores de identidade (Pds) por organização estão desabilitados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie IdPs no painel <adminPanelLink>admin</adminPanelLink>. Para habilitar IdPs por organização, edite a configuração do servidor e defina o modo IdP como org. <configDocsLink>Veja a documentação</configDocsLink>. Se quiser continuar usando IdPs globais e fazer isso desaparecer das configurações da organização, defina explicitamente o modo como global na configuração.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no <adminPanelLink>painel administrativo</adminPanelLink>. Para usar provedores de identidade por organização, você deve atualizar para a edição Enterprise.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Os provedores de identidade (IdPs) por organização estão desativados neste servidor. Ele está usando IdPs globais (compartilhados entre todas as organizações). Gerencie os IdPs globais no <adminPanelLink>painel administrativo</adminPanelLink>. Para usar provedores de identidade por organização, é necessário uma licença Enterprise.",
|
||||
"idpDeletedDescription": "Provedor de identidade eliminado com sucesso",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Tem certeza que deseja eliminar permanentemente o provedor de identidade?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Fim do ano seguinte",
|
||||
"actionLogsDescription": "Visualizar histórico de ações realizadas nesta organização",
|
||||
"accessLogsDescription": "Ver solicitações de autenticação de recursos nesta organização",
|
||||
"licenseRequiredToUse": "É necessária uma licença empresarial para usar esse recurso.",
|
||||
"ossEnterpriseEditionRequired": "O <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> é necessário para usar este recurso.",
|
||||
"licenseRequiredToUse": "Uma licença <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> é necessária para usar este recurso. Este recurso também está disponível no <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "O <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> é necessário para usar este recurso. Este recurso também está disponível no <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Resolvedor de Certificado",
|
||||
"certResolverDescription": "Selecione o resolvedor de certificados para este recurso.",
|
||||
"selectCertResolver": "Selecionar solucionador de certificado",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Увеличить количество сайтов",
|
||||
"idpManage": "Управление поставщиками удостоверений",
|
||||
"idpManageDescription": "Просмотр и управление поставщиками удостоверений в системе",
|
||||
"idpGlobalModeBanner": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в <adminPanelLink>админ-панели</adminPanelLink>. Чтобы включить IdP для каждой организации, отредактируйте конфигурацию сервера и установите режим IdP в org. <configDocsLink>См. документацию</configDocsLink>. Если вы хотите продолжать использовать глобальные IdP и скрыть это из настроек организации, явно установите режим в глобальном конфиге.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в <adminPanelLink>админ-панели</adminPanelLink>. Чтобы использовать поставщиков удостоверений для каждой организации, необходимо обновить систему до версии Enterprise.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Поставщики удостоверений (IdP) для каждой организации отключены на этом сервере. Используются глобальные IdP (общие для всех организаций). Управляйте глобальными IdP в <adminPanelLink>админ-панели</adminPanelLink>. Для использования поставщиков удостоверений на организацию требуется лицензия Enterprise.",
|
||||
"idpDeletedDescription": "Поставщик удостоверений успешно удалён",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Вы уверены, что хотите навсегда удалить поставщика удостоверений?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Конец следующего года",
|
||||
"actionLogsDescription": "Просмотр истории действий, выполненных в этой организации",
|
||||
"accessLogsDescription": "Просмотр запросов авторизации доступа к ресурсам этой организации",
|
||||
"licenseRequiredToUse": "Для использования этой функции требуется лицензия предприятия.",
|
||||
"ossEnterpriseEditionRequired": "Для использования этой функции требуется корпоративная версия <enterpriseEditionLink></enterpriseEditionLink>.",
|
||||
"licenseRequiredToUse": "Лицензия на <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> требуется для использования этой функции. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"ossEnterpriseEditionRequired": "Для использования этой функции требуется <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink>. Эта функция также доступна в <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>.",
|
||||
"certResolver": "Резольвер сертификата",
|
||||
"certResolverDescription": "Выберите резолвер сертификата, который будет использоваться для этого ресурса.",
|
||||
"selectCertResolver": "Выберите резолвер сертификата",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "Site sayısını artır",
|
||||
"idpManage": "Kimlik Sağlayıcılarını Yönet",
|
||||
"idpManageDescription": "Sistem içindeki kimlik sağlayıcıları görün ve yönetin",
|
||||
"idpGlobalModeBanner": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri <adminPanelLink> yönetici panelinde </adminPanelLink>yönetin. Örgüt başına IdP'leri etkinleştirmek için, sunucu yapılandırmasını düzenleyin ve IdP modunu 'org' olarak ayarlayın. <configDocsLink> Belgeleri inceleyin </configDocsLink>. Küresel IdP'leri kullanmaya devam etmek istiyorsanız ve bunun örgüt ayarlarından kaybolmasını istiyorsanız, yapılandırmada modu otomatik olarak 'global' olarak ayarlayın.",
|
||||
"idpGlobalModeBannerUpgradeRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri <adminPanelLink> yönetici panelinde </adminPanelLink>yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için, Enterprise sürümüne yükseltmeniz gerekmektedir.",
|
||||
"idpGlobalModeBannerLicenseRequired": "Bu sunucuda örgüt başına kimlik sağlayıcılar (IdP'ler) devre dışı bırakılmıştır. Tüm örgütler arasında paylaşılan küresel IdP'leri kullanıyor. Küresel IdP'leri <adminPanelLink> yönetici panelinde </adminPanelLink>yönetin. Örgüt başına kimlik sağlayıcılar kullanmak için Enterprise lisansı gereklidir.",
|
||||
"idpDeletedDescription": "Kimlik sağlayıcı başarıyla silindi",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "Kimlik sağlayıcısını kalıcı olarak silmek istediğinizden emin misiniz?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "Bir sonraki yılın sonu",
|
||||
"actionLogsDescription": "Bu organizasyondaki eylemler geçmişini görüntüleyin",
|
||||
"accessLogsDescription": "Bu organizasyondaki kaynaklar için erişim kimlik doğrulama isteklerini görüntüleyin",
|
||||
"licenseRequiredToUse": "Bu özelliği kullanmak için bir kurumsal lisans gereklidir.",
|
||||
"ossEnterpriseEditionRequired": "Bu özelliği kullanmak için <enterpriseEditionLink>Kurumsal Sürüm</enterpriseEditionLink> gereklidir.",
|
||||
"licenseRequiredToUse": "Bu özelliği kullanmak için bir <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> lisansı gereklidir. Bu özellik ayrıca <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>'da da mevcuttur.",
|
||||
"ossEnterpriseEditionRequired": "Bu özelliği kullanmak için <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> gereklidir. Bu özellik ayrıca <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>'da da mevcuttur.",
|
||||
"certResolver": "Sertifika Çözücü",
|
||||
"certResolverDescription": "Bu kaynak için kullanılacak sertifika çözücüsünü seçin.",
|
||||
"selectCertResolver": "Sertifika Çözücü Seçin",
|
||||
|
||||
@@ -791,6 +791,9 @@
|
||||
"sitestCountIncrease": "增加站点数量",
|
||||
"idpManage": "管理身份提供商",
|
||||
"idpManageDescription": "查看和管理系统中的身份提供商",
|
||||
"idpGlobalModeBanner": "此服务器上禁用了每个组织的身份提供商(Idps)。 它正在使用全局IdP(所有组织共享)。在 <adminPanelLink>管理面板</adminPanelLink>中管理全局IdP。 要启用每个组织的 IdP,请编辑服务器配置并将 IdP 模式设置为 org。 <configDocsLink>请参阅文档</configDocsLink>。 如果您想要继续使用全局IdP并使其从组织设置中消失,请在配置中将模式设置为全局模式。",
|
||||
"idpGlobalModeBannerUpgradeRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 <adminPanelLink>管理面板</adminPanelLink>管理全局身份。要使用每个组织的身份提供者,您必须升级到企业版本。",
|
||||
"idpGlobalModeBannerLicenseRequired": "此服务器上禁用了每个组织的身份提供商(Idps)。它正在使用全局身份提供商(所有组织共享)。 在 <adminPanelLink>管理面板</adminPanelLink>管理全局身份。要使用每个组织的身份提供者,需要企业许可证。",
|
||||
"idpDeletedDescription": "身份提供商删除成功",
|
||||
"idpOidc": "OAuth2/OIDC",
|
||||
"idpQuestionRemove": "您确定要永久删除身份提供者吗?",
|
||||
@@ -2280,8 +2283,8 @@
|
||||
"logRetentionEndOfFollowingYear": "下一年结束",
|
||||
"actionLogsDescription": "查看此机构执行的操作历史",
|
||||
"accessLogsDescription": "查看此机构资源的访问认证请求",
|
||||
"licenseRequiredToUse": "需要企业许可证才能使用此功能。",
|
||||
"ossEnterpriseEditionRequired": "需要 <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> 才能使用此功能。",
|
||||
"licenseRequiredToUse": "需要 <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> 许可才能使用此功能。此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> 中使用。",
|
||||
"ossEnterpriseEditionRequired": "<enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> 需要使用此功能。此功能也可在 <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> 中使用。",
|
||||
"certResolver": "证书解决器",
|
||||
"certResolverDescription": "选择用于此资源的证书解析器。",
|
||||
"selectCertResolver": "选择证书解析",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"scripts": {
|
||||
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
|
||||
"dev:check": "npx tsc --noEmit && npm run format:check",
|
||||
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:generate && npm run db:sqlite:push",
|
||||
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push",
|
||||
"db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts",
|
||||
"db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts",
|
||||
"db:pg:push": "npx tsx server/db/pg/migrate.ts",
|
||||
|
||||
@@ -79,6 +79,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", {
|
||||
subscriptionItemId: integer("subscriptionItemId").primaryKey({
|
||||
autoIncrement: true
|
||||
}),
|
||||
stripeSubscriptionItemId: text("stripeSubscriptionItemId"),
|
||||
subscriptionId: text("subscriptionId")
|
||||
.notNull()
|
||||
.references(() => subscriptions.subscriptionId, {
|
||||
|
||||
@@ -2,7 +2,7 @@ import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
// This is a placeholder value replaced by the build process
|
||||
export const APP_VERSION = "1.15.0";
|
||||
export const APP_VERSION = "1.15.3";
|
||||
|
||||
export const __FILENAME = fileURLToPath(import.meta.url);
|
||||
export const __DIRNAME = path.dirname(__FILENAME);
|
||||
|
||||
@@ -65,6 +65,11 @@ export class PrivateConfig {
|
||||
this.rawPrivateConfig.branding?.logo?.dark_path || undefined;
|
||||
}
|
||||
|
||||
if (this.rawPrivateConfig.app.identity_provider_mode) {
|
||||
process.env.IDENTITY_PROVIDER_MODE =
|
||||
this.rawPrivateConfig.app.identity_provider_mode;
|
||||
}
|
||||
|
||||
process.env.BRANDING_LOGO_AUTH_WIDTH = this.rawPrivateConfig.branding
|
||||
?.logo?.auth_page?.width
|
||||
? this.rawPrivateConfig.branding?.logo?.auth_page?.width.toString()
|
||||
@@ -129,10 +134,6 @@ export class PrivateConfig {
|
||||
process.env.USE_PANGOLIN_DNS =
|
||||
this.rawPrivateConfig.flags.use_pangolin_dns.toString();
|
||||
}
|
||||
if (this.rawPrivateConfig.flags.use_org_only_idp) {
|
||||
process.env.USE_ORG_ONLY_IDP =
|
||||
this.rawPrivateConfig.flags.use_org_only_idp.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public getRawPrivateConfig() {
|
||||
|
||||
@@ -25,7 +25,8 @@ export const privateConfigSchema = z.object({
|
||||
app: z
|
||||
.object({
|
||||
region: z.string().optional().default("default"),
|
||||
base_domain: z.string().optional()
|
||||
base_domain: z.string().optional(),
|
||||
identity_provider_mode: z.enum(["global", "org"]).optional()
|
||||
})
|
||||
.optional()
|
||||
.default({
|
||||
@@ -95,7 +96,7 @@ export const privateConfigSchema = z.object({
|
||||
.object({
|
||||
enable_redis: z.boolean().optional().default(false),
|
||||
use_pangolin_dns: z.boolean().optional().default(false),
|
||||
use_org_only_idp: z.boolean().optional().default(false),
|
||||
use_org_only_idp: z.boolean().optional()
|
||||
})
|
||||
.optional()
|
||||
.prefault({}),
|
||||
@@ -181,7 +182,29 @@ export const privateConfigSchema = z.object({
|
||||
// localFilePath: z.string().optional()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
})
|
||||
.transform((data) => {
|
||||
// this to maintain backwards compatibility with the old config file
|
||||
const identityProviderMode = data.app?.identity_provider_mode;
|
||||
const useOrgOnlyIdp = data.flags?.use_org_only_idp;
|
||||
|
||||
if (identityProviderMode !== undefined) {
|
||||
return data;
|
||||
}
|
||||
if (useOrgOnlyIdp === true) {
|
||||
return {
|
||||
...data,
|
||||
app: { ...data.app, identity_provider_mode: "org" as const }
|
||||
};
|
||||
}
|
||||
if (useOrgOnlyIdp === false) {
|
||||
return {
|
||||
...data,
|
||||
app: { ...data.app, identity_provider_mode: "global" as const }
|
||||
};
|
||||
}
|
||||
return data;
|
||||
});
|
||||
|
||||
export function readPrivateConfigFile() {
|
||||
if (build == "oss") {
|
||||
|
||||
@@ -113,7 +113,7 @@ export async function generateNewEnterpriseLicense(
|
||||
}
|
||||
|
||||
const tier = licenseData.tier === "big_license" ? LicenseId.BIG_LICENSE : LicenseId.SMALL_LICENSE;
|
||||
const tierPrice = getLicensePriceSet()[tier]
|
||||
const tierPrice = getLicensePriceSet()[tier];
|
||||
|
||||
const session = await stripe!.checkout.sessions.create({
|
||||
client_reference_id: keyId.toString(),
|
||||
|
||||
@@ -27,6 +27,7 @@ import config from "@server/lib/config";
|
||||
import { CreateOrgIdpResponse } from "@server/routers/orgIdp/types";
|
||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import privateConfig from "#private/lib/config";
|
||||
|
||||
const paramsSchema = z.strictObject({ orgId: z.string().nonempty() });
|
||||
|
||||
@@ -92,6 +93,18 @@ export async function createOrgOidcIdp(
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||
"org"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const {
|
||||
clientId,
|
||||
clientSecret,
|
||||
|
||||
@@ -22,6 +22,7 @@ import { fromError } from "zod-validation-error";
|
||||
import { idp, idpOidcConfig, idpOrg } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import privateConfig from "#private/lib/config";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
@@ -59,6 +60,18 @@ export async function deleteOrgIdp(
|
||||
|
||||
const { idpId } = parsedParams.data;
|
||||
|
||||
if (
|
||||
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||
"org"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Check if IDP exists
|
||||
const [existingIdp] = await db
|
||||
.select()
|
||||
|
||||
@@ -26,6 +26,7 @@ import { encrypt } from "@server/lib/crypto";
|
||||
import config from "@server/lib/config";
|
||||
import { isSubscribed } from "#private/lib/isSubscribed";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import privateConfig from "#private/lib/config";
|
||||
|
||||
const paramsSchema = z
|
||||
.object({
|
||||
@@ -97,6 +98,18 @@ export async function updateOrgOidcIdp(
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
privateConfig.getRawPrivateConfig().app.identity_provider_mode !==
|
||||
"org"
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Organization-specific IdP creation is not allowed in the current identity provider mode. Set app.identity_provider_mode to 'org' in the private configuration to enable this feature."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const { idpId, orgId } = parsedParams.data;
|
||||
const {
|
||||
clientId,
|
||||
|
||||
@@ -26,6 +26,7 @@ import { generateId } from "@server/auth/sessions/app";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||
import { getUniqueClientName } from "@server/db/names";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const createClientParamsSchema = z.strictObject({
|
||||
orgId: z.string()
|
||||
@@ -195,6 +196,12 @@ export async function createClient(
|
||||
const randomExitNode =
|
||||
exitNodesList[Math.floor(Math.random() * exitNodesList.length)];
|
||||
|
||||
if (!randomExitNode) {
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, `No exit nodes available. ${build == "saas" ? "Please contact support." : "You need to install gerbil to use the clients."}`)
|
||||
);
|
||||
}
|
||||
|
||||
const [adminRole] = await trx
|
||||
.select()
|
||||
.from(roles)
|
||||
|
||||
@@ -17,6 +17,7 @@ import m9 from "./scriptsPg/1.12.0";
|
||||
import m10 from "./scriptsPg/1.13.0";
|
||||
import m11 from "./scriptsPg/1.14.0";
|
||||
import m12 from "./scriptsPg/1.15.0";
|
||||
import m13 from "./scriptsPg/1.15.3";
|
||||
|
||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||
@@ -34,7 +35,8 @@ const migrations = [
|
||||
{ version: "1.12.0", run: m9 },
|
||||
{ version: "1.13.0", run: m10 },
|
||||
{ version: "1.14.0", run: m11 },
|
||||
{ version: "1.15.0", run: m12 }
|
||||
{ version: "1.15.0", run: m12 },
|
||||
{ version: "1.15.3", run: m13 }
|
||||
// Add new migrations here as they are created
|
||||
] as {
|
||||
version: string;
|
||||
|
||||
@@ -35,6 +35,7 @@ import m30 from "./scriptsSqlite/1.12.0";
|
||||
import m31 from "./scriptsSqlite/1.13.0";
|
||||
import m32 from "./scriptsSqlite/1.14.0";
|
||||
import m33 from "./scriptsSqlite/1.15.0";
|
||||
import m34 from "./scriptsSqlite/1.15.3";
|
||||
|
||||
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER
|
||||
// EXCEPT FOR THE DATABASE AND THE SCHEMA
|
||||
@@ -68,7 +69,8 @@ const migrations = [
|
||||
{ version: "1.12.0", run: m30 },
|
||||
{ version: "1.13.0", run: m31 },
|
||||
{ version: "1.14.0", run: m32 },
|
||||
{ version: "1.15.0", run: m33 }
|
||||
{ version: "1.15.0", run: m33 },
|
||||
{ version: "1.15.3", run: m34 }
|
||||
// Add new migrations here as they are created
|
||||
] as const;
|
||||
|
||||
|
||||
39
server/setup/scriptsPg/1.15.3.ts
Normal file
39
server/setup/scriptsPg/1.15.3.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { db } from "@server/db/pg/driver";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { __DIRNAME } from "@server/lib/consts";
|
||||
|
||||
const version = "1.15.3";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
try {
|
||||
await db.execute(sql`BEGIN`);
|
||||
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "limits" ADD COLUMN "override" boolean DEFAULT false;`
|
||||
);
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "subscriptionItems" ADD COLUMN "stripeSubscriptionItemId" varchar(255);`
|
||||
);
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "subscriptionItems" ADD COLUMN "featureId" varchar(255);`
|
||||
);
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "subscriptions" ADD COLUMN "version" integer;`
|
||||
);
|
||||
await db.execute(
|
||||
sql`ALTER TABLE "subscriptions" ADD COLUMN "type" varchar(50);`
|
||||
);
|
||||
|
||||
await db.execute(sql`COMMIT`);
|
||||
console.log("Migrated database");
|
||||
} catch (e) {
|
||||
await db.execute(sql`ROLLBACK`);
|
||||
console.log("Unable to migrate database");
|
||||
console.log(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
||||
29
server/setup/scriptsSqlite/1.15.3.ts
Normal file
29
server/setup/scriptsSqlite/1.15.3.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { __DIRNAME, APP_PATH } from "@server/lib/consts";
|
||||
import Database from "better-sqlite3";
|
||||
import path from "path";
|
||||
|
||||
const version = "1.15.3";
|
||||
|
||||
export default async function migration() {
|
||||
console.log(`Running setup script ${version}...`);
|
||||
|
||||
const location = path.join(APP_PATH, "db", "db.sqlite");
|
||||
const db = new Database(location);
|
||||
|
||||
try {
|
||||
db.transaction(() => {
|
||||
db.prepare(`ALTER TABLE 'limits' ADD 'override' integer DEFAULT false;`).run();
|
||||
db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'featureId' text;`).run();
|
||||
db.prepare(`ALTER TABLE 'subscriptionItems' ADD 'stripeSubscriptionItemId' text;`).run();
|
||||
db.prepare(`ALTER TABLE 'subscriptions' ADD 'version' integer;`).run();
|
||||
db.prepare(`ALTER TABLE 'subscriptions' ADD 'type' text;`).run();
|
||||
})();
|
||||
|
||||
console.log(`Migrated database`);
|
||||
} catch (e) {
|
||||
console.log("Failed to migrate db:", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.log(`${version} migration complete`);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import IdpTable, { IdpRow } from "@app/components/OrgIdpTable";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
import { IdpGlobalModeBanner } from "@app/components/IdpGlobalModeBanner";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
|
||||
type OrgIdpPageProps = {
|
||||
@@ -36,6 +37,8 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
|
||||
description={t("idpManageDescription")}
|
||||
/>
|
||||
|
||||
<IdpGlobalModeBanner />
|
||||
|
||||
<PaidFeaturesAlert tiers={tierMatrix.orgOidc} />
|
||||
|
||||
<IdpTable idps={idps} orgId={params.orgId} />
|
||||
|
||||
@@ -44,7 +44,6 @@ import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import type { OrgContextType } from "@app/contexts/orgContext";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { isAppPageRouteDefinition } from "next/dist/server/route-definitions/app-page-route-definition";
|
||||
|
||||
// Session length options in hours
|
||||
const SESSION_LENGTH_OPTIONS = [
|
||||
@@ -219,6 +218,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
<SelectContent>
|
||||
{LOG_RETENTION_OPTIONS.filter(
|
||||
(option) => {
|
||||
if (build != "saas") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let maxDays: number;
|
||||
|
||||
if (!subscriptionTier) {
|
||||
@@ -314,6 +317,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
<SelectContent>
|
||||
{LOG_RETENTION_OPTIONS.filter(
|
||||
(option) => {
|
||||
if (build != "saas") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let maxDays: number;
|
||||
|
||||
if (!subscriptionTier) {
|
||||
@@ -411,6 +418,10 @@ function LogRetentionSectionForm({ org }: SectionFormProps) {
|
||||
<SelectContent>
|
||||
{LOG_RETENTION_OPTIONS.filter(
|
||||
(option) => {
|
||||
if (build != "saas") {
|
||||
return true;
|
||||
}
|
||||
|
||||
let maxDays: number;
|
||||
|
||||
if (!subscriptionTier) {
|
||||
|
||||
@@ -132,7 +132,7 @@ export default function ResourceAuthenticationPage() {
|
||||
const { data: orgIdps = [], isLoading: isLoadingOrgIdps } = useQuery(
|
||||
orgQueries.identityProviders({
|
||||
orgId: org.org.orgId,
|
||||
useOrgOnlyIdp: env.flags.useOrgOnlyIdp
|
||||
useOrgOnlyIdp: env.app.identityProviderMode === "org"
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ export default async function UsersPage(props: PageProps) {
|
||||
title={t("userTitle")}
|
||||
description={t("userDescription")}
|
||||
/>
|
||||
<Alert variant="neutral" className="mb-6">
|
||||
<Alert className="mb-6">
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t("userAbount")}
|
||||
|
||||
@@ -76,12 +76,13 @@ export default async function Page(props: {
|
||||
|
||||
// Only use SmartLoginForm if NOT (OSS build OR org-only IdP enabled)
|
||||
const useSmartLogin =
|
||||
build === "saas" || (build === "enterprise" && env.flags.useOrgOnlyIdp);
|
||||
build === "saas" ||
|
||||
(build === "enterprise" && env.app.identityProviderMode === "org");
|
||||
|
||||
let loginIdps: LoginFormIDP[] = [];
|
||||
if (!useSmartLogin) {
|
||||
// Load IdPs for DashboardLoginForm (OSS or org-only IdP mode)
|
||||
if (build === "oss" || !env.flags.useOrgOnlyIdp) {
|
||||
if (build === "oss" || env.app.identityProviderMode !== "org") {
|
||||
const idpsRes = await cache(
|
||||
async () =>
|
||||
await priv.get<AxiosResponse<ListIdpsResponse>>("/idp")
|
||||
@@ -165,7 +166,8 @@ export default async function Page(props: {
|
||||
forceLogin={forceLogin}
|
||||
showOrgLogin={
|
||||
!isInvite &&
|
||||
(build === "saas" || env.flags.useOrgOnlyIdp)
|
||||
(build === "saas" ||
|
||||
env.app.identityProviderMode === "org")
|
||||
}
|
||||
searchParams={searchParams}
|
||||
defaultUser={defaultUser}
|
||||
@@ -188,7 +190,8 @@ export default async function Page(props: {
|
||||
</p>
|
||||
)}
|
||||
|
||||
{!isInvite && (build === "saas" || env.flags.useOrgOnlyIdp) ? (
|
||||
{!isInvite &&
|
||||
(build === "saas" || env.app.identityProviderMode === "org") ? (
|
||||
<OrgSignInLink
|
||||
href={`/auth/org${buildQueryString(searchParams)}`}
|
||||
linkText={t("orgAuthSignInToOrg")}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default async function OrgAuthPage(props: {
|
||||
|
||||
const env = pullEnv();
|
||||
|
||||
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
||||
if (build !== "saas" && env.app.identityProviderMode !== "org") {
|
||||
const queryString = new URLSearchParams(searchParams as any).toString();
|
||||
redirect(`/auth/login${queryString ? `?${queryString}` : ""}`);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ export default async function OrgAuthPage(props: {
|
||||
|
||||
const env = pullEnv();
|
||||
|
||||
if (build !== "saas" && !env.flags.useOrgOnlyIdp) {
|
||||
if (build !== "saas" && env.app.identityProviderMode !== "org") {
|
||||
redirect("/");
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ export default async function ResourceAuthPage(props: {
|
||||
}
|
||||
|
||||
let loginIdps: LoginFormIDP[] = [];
|
||||
if (build === "saas" || env.flags.useOrgOnlyIdp) {
|
||||
if (build === "saas" || env.app.identityProviderMode === "org") {
|
||||
if (subscribed) {
|
||||
const idpsRes = await cache(
|
||||
async () =>
|
||||
|
||||
@@ -124,7 +124,8 @@ export const orgNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
// PaidFeaturesAlert
|
||||
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
|
||||
build === "saas" ||
|
||||
env?.flags.useOrgOnlyIdp
|
||||
env?.app.identityProviderMode === "org" ||
|
||||
(env?.app.identityProviderMode === undefined && build !== "oss")
|
||||
? [
|
||||
{
|
||||
title: "sidebarIdentityProviders",
|
||||
@@ -251,7 +252,9 @@ export const adminNavSections = (env?: Env): SidebarNavSection[] => [
|
||||
href: "/admin/api-keys",
|
||||
icon: <KeyRound className="size-4 flex-none" />
|
||||
},
|
||||
...(build === "oss" || !env?.flags.useOrgOnlyIdp
|
||||
...(build === "oss" ||
|
||||
env?.app.identityProviderMode === "global" ||
|
||||
env?.app.identityProviderMode === undefined
|
||||
? [
|
||||
{
|
||||
title: "sidebarIdentityProviders",
|
||||
|
||||
@@ -322,7 +322,7 @@ export default function AuthPageBrandingForm({
|
||||
</div>
|
||||
|
||||
{build === "saas" ||
|
||||
env.env.flags.useOrgOnlyIdp ? (
|
||||
env.env.app.identityProviderMode === "org" ? (
|
||||
<>
|
||||
<div className="mt-3 mb-6">
|
||||
<SettingsSectionTitle>
|
||||
|
||||
65
src/components/IdpGlobalModeBanner.tsx
Normal file
65
src/components/IdpGlobalModeBanner.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||
import { Info } from "lucide-react";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { build } from "@server/build";
|
||||
|
||||
export function IdpGlobalModeBanner() {
|
||||
const t = useTranslations();
|
||||
const { env } = useEnvContext();
|
||||
const { isPaidUser, hasEnterpriseLicense } = usePaidStatus();
|
||||
|
||||
const identityProviderModeUndefined =
|
||||
env.app.identityProviderMode === undefined;
|
||||
const paidUserForOrgOidc = isPaidUser(tierMatrix.orgOidc);
|
||||
const enterpriseUnlicensed =
|
||||
build === "enterprise" && !hasEnterpriseLicense;
|
||||
|
||||
if (build === "saas") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!identityProviderModeUndefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const adminPanelLinkRenderer = (chunks: React.ReactNode) => (
|
||||
<Link href="/admin/idp" className="font-medium underline">
|
||||
{chunks}
|
||||
</Link>
|
||||
);
|
||||
|
||||
return (
|
||||
<Alert className="mb-6">
|
||||
<Info className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
{paidUserForOrgOidc
|
||||
? t.rich("idpGlobalModeBanner", {
|
||||
adminPanelLink: adminPanelLinkRenderer,
|
||||
configDocsLink: (chunks) => (
|
||||
<Link
|
||||
href="https://docs.pangolin.net/manage/identity-providers/add-an-idp#organization-identity-providers"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="font-medium underline"
|
||||
>
|
||||
{chunks}
|
||||
</Link>
|
||||
)
|
||||
})
|
||||
: enterpriseUnlicensed
|
||||
? t.rich("idpGlobalModeBannerLicenseRequired", {
|
||||
adminPanelLink: adminPanelLinkRenderer
|
||||
})
|
||||
: t.rich("idpGlobalModeBannerUpgradeRequired", {
|
||||
adminPanelLink: adminPanelLinkRenderer
|
||||
})}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
@@ -38,15 +38,49 @@ const bannerContentClassName = "py-3 px-4";
|
||||
const bannerRowClassName =
|
||||
"flex items-center gap-2.5 text-sm text-muted-foreground";
|
||||
const bannerIconClassName = "size-4 shrink-0 text-purple-500";
|
||||
const docsLinkClassName =
|
||||
"inline-flex items-center gap-1 font-medium text-purple-600 underline";
|
||||
const PANGOLIN_CLOUD_SIGNUP_URL = "https://app.pangolin.net/auth/signup/";
|
||||
const ENTERPRISE_DOCS_URL =
|
||||
"https://docs.pangolin.net/self-host/enterprise-edition";
|
||||
|
||||
function getTierLinkRenderer(billingHref: string) {
|
||||
return function tierLinkRenderer(chunks: React.ReactNode) {
|
||||
return (
|
||||
<Link href={billingHref} className={docsLinkClassName}>
|
||||
{chunks}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getPangolinCloudLinkRenderer() {
|
||||
return function pangolinCloudLinkRenderer(chunks: React.ReactNode) {
|
||||
return (
|
||||
<Link
|
||||
href={billingHref}
|
||||
className="inline-flex items-center gap-1 font-medium text-purple-600 underline"
|
||||
href={PANGOLIN_CLOUD_SIGNUP_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={docsLinkClassName}
|
||||
>
|
||||
{chunks}
|
||||
<ExternalLink className="size-3.5 shrink-0" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function getDocsLinkRenderer(href: string) {
|
||||
return function docsLinkRenderer(chunks: React.ReactNode) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={docsLinkClassName}
|
||||
>
|
||||
{chunks}
|
||||
<ExternalLink className="size-3.5 shrink-0" />
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
@@ -66,6 +100,8 @@ export function PaidFeaturesAlert({ tiers }: Props) {
|
||||
const requiredTierName = requiredTier ? t(TIER_TRANSLATION_KEYS[requiredTier]) : null;
|
||||
const billingHref = orgId ? `/${orgId}/settings/billing` : "https://pangolin.net/pricing";
|
||||
const tierLinkRenderer = getTierLinkRenderer(billingHref);
|
||||
const pangolinCloudLinkRenderer = getPangolinCloudLinkRenderer();
|
||||
const enterpriseDocsLinkRenderer = getDocsLinkRenderer(ENTERPRISE_DOCS_URL);
|
||||
|
||||
if (env.flags.disableEnterpriseFeatures) {
|
||||
return null;
|
||||
@@ -103,7 +139,12 @@ export function PaidFeaturesAlert({ tiers }: Props) {
|
||||
<CardContent className={bannerContentClassName}>
|
||||
<div className={bannerRowClassName}>
|
||||
<KeyRound className={bannerIconClassName} />
|
||||
<span>{t("licenseRequiredToUse")}</span>
|
||||
<span>
|
||||
{t.rich("licenseRequiredToUse", {
|
||||
enterpriseLicenseLink: enterpriseDocsLinkRenderer,
|
||||
pangolinCloudLink: pangolinCloudLinkRenderer
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -116,17 +157,8 @@ export function PaidFeaturesAlert({ tiers }: Props) {
|
||||
<KeyRound className={bannerIconClassName} />
|
||||
<span>
|
||||
{t.rich("ossEnterpriseEditionRequired", {
|
||||
enterpriseEditionLink: (chunks) => (
|
||||
<Link
|
||||
href="https://docs.pangolin.net/self-host/enterprise-edition"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 font-medium text-purple-600 underline"
|
||||
>
|
||||
{chunks}
|
||||
<ExternalLink className="size-3.5 shrink-0" />
|
||||
</Link>
|
||||
)
|
||||
enterpriseEditionLink: enterpriseDocsLinkRenderer,
|
||||
pangolinCloudLink: pangolinCloudLinkRenderer
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -118,7 +118,7 @@ function getActionsCategories(root: boolean) {
|
||||
}
|
||||
};
|
||||
|
||||
if (root || build === "saas" || env.flags.useOrgOnlyIdp) {
|
||||
if (root || build === "saas" || env.app.identityProviderMode === "org") {
|
||||
actionsByCategory["Identity Provider (IDP)"] = {
|
||||
[t("actionCreateIdp")]: "createIdp",
|
||||
[t("actionUpdateIdp")]: "updateIdp",
|
||||
|
||||
@@ -204,7 +204,9 @@ export default function SignupForm({
|
||||
? env.branding.logo?.authPage?.height || 44
|
||||
: 44;
|
||||
|
||||
const showOrgBanner = fromSmartLogin && (build === "saas" || env.flags.useOrgOnlyIdp);
|
||||
const showOrgBanner =
|
||||
fromSmartLogin &&
|
||||
(build === "saas" || env.app.identityProviderMode === "org");
|
||||
const orgBannerHref = redirect
|
||||
? `/auth/org?redirect=${encodeURIComponent(redirect)}`
|
||||
: "/auth/org";
|
||||
@@ -226,388 +228,398 @@ export default function SignupForm({
|
||||
</Alert>
|
||||
)}
|
||||
<Card className="w-full max-w-md">
|
||||
<CardHeader className="border-b">
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<BrandingLogo height={logoHeight} width={logoWidth} />
|
||||
</div>
|
||||
<div className="text-center space-y-1 pt-3">
|
||||
<p className="text-muted-foreground">{getSubtitle()}</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("email")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={!!emailParam}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>{t("password")}</FormLabel>
|
||||
{passwordStrength.strength ===
|
||||
"strong" && (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
)}
|
||||
</div>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<CardHeader className="border-b">
|
||||
<div className="flex flex-row items-center justify-center">
|
||||
<BrandingLogo height={logoHeight} width={logoWidth} />
|
||||
</div>
|
||||
<div className="text-center space-y-1 pt-3">
|
||||
<p className="text-muted-foreground">{getSubtitle()}</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-6">
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("email")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="password"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
setPasswordValue(
|
||||
e.target.value
|
||||
);
|
||||
}}
|
||||
className={cn(
|
||||
passwordStrength.strength ===
|
||||
"strong" &&
|
||||
"border-green-500 focus-visible:ring-green-500",
|
||||
passwordStrength.strength ===
|
||||
"medium" &&
|
||||
"border-yellow-500 focus-visible:ring-yellow-500",
|
||||
passwordStrength.strength ===
|
||||
"weak" &&
|
||||
passwordValue.length >
|
||||
0 &&
|
||||
"border-red-500 focus-visible:ring-red-500"
|
||||
)}
|
||||
autoComplete="new-password"
|
||||
disabled={!!emailParam}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
|
||||
{passwordValue.length > 0 && (
|
||||
<div className="space-y-3 mt-2">
|
||||
{/* Password Strength Meter */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{t("passwordStrength")}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-semibold",
|
||||
passwordStrength.strength ===
|
||||
"strong" &&
|
||||
"text-green-600 dark:text-green-400",
|
||||
passwordStrength.strength ===
|
||||
"medium" &&
|
||||
"text-yellow-600 dark:text-yellow-400",
|
||||
passwordStrength.strength ===
|
||||
"weak" &&
|
||||
"text-red-600 dark:text-red-400"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
`passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}`
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={
|
||||
passwordStrength.percentage
|
||||
}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Requirements Checklist */}
|
||||
<div className="bg-muted rounded-lg p-3 space-y-2">
|
||||
<div className="text-sm font-medium text-foreground mb-2">
|
||||
{t("passwordRequirements")}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.length ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.length
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementLengthText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.uppercase ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.uppercase
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementUppercaseText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.lowercase ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.lowercase
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementLowercaseText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.number ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.number
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementNumberText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.special ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.special
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementSpecialText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Only show FormMessage when not showing our custom requirements */}
|
||||
{passwordValue.length === 0 && (
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>
|
||||
{t("confirmPassword")}
|
||||
</FormLabel>
|
||||
{doPasswordsMatch && (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
)}
|
||||
</div>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="password"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
setConfirmPasswordValue(
|
||||
e.target.value
|
||||
);
|
||||
}}
|
||||
className={cn(
|
||||
doPasswordsMatch &&
|
||||
"border-green-500 focus-visible:ring-green-500",
|
||||
confirmPasswordValue.length >
|
||||
0 &&
|
||||
!doPasswordsMatch &&
|
||||
"border-red-500 focus-visible:ring-red-500"
|
||||
)}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>
|
||||
{t("password")}
|
||||
</FormLabel>
|
||||
{passwordStrength.strength ===
|
||||
"strong" && (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
{confirmPasswordValue.length > 0 &&
|
||||
!doPasswordsMatch && (
|
||||
<p className="text-sm text-red-600 mt-1">
|
||||
{t("passwordsDoNotMatch")}
|
||||
</p>
|
||||
)}
|
||||
{/* Only show FormMessage when field is empty */}
|
||||
{confirmPasswordValue.length === 0 && (
|
||||
<FormMessage />
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{build === "saas" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="agreeToTerms"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={(
|
||||
checked
|
||||
) => {
|
||||
field.onChange(checked);
|
||||
handleTermsChange(
|
||||
checked as boolean
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="password"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
setPasswordValue(
|
||||
e.target.value
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="leading-none">
|
||||
<FormLabel className="text-sm font-normal">
|
||||
<div>
|
||||
{t(
|
||||
"signUpTerms.IAgreeToThe"
|
||||
)}{" "}
|
||||
<a
|
||||
href="https://pangolin.net/terms-of-service.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{t(
|
||||
"signUpTerms.termsOfService"
|
||||
)}{" "}
|
||||
</a>
|
||||
{t("signUpTerms.and")}{" "}
|
||||
<a
|
||||
href="https://pangolin.net/privacy-policy.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{t(
|
||||
"signUpTerms.privacyPolicy"
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</FormLabel>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="marketingEmailConsent"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="leading-none">
|
||||
<FormLabel className="text-sm font-normal">
|
||||
{t(
|
||||
"signUpMarketing.keepMeInTheLoop"
|
||||
className={cn(
|
||||
passwordStrength.strength ===
|
||||
"strong" &&
|
||||
"border-green-500 focus-visible:ring-green-500",
|
||||
passwordStrength.strength ===
|
||||
"medium" &&
|
||||
"border-yellow-500 focus-visible:ring-yellow-500",
|
||||
passwordStrength.strength ===
|
||||
"weak" &&
|
||||
passwordValue.length >
|
||||
0 &&
|
||||
"border-red-500 focus-visible:ring-red-500"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormMessage />
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</FormControl>
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{passwordValue.length > 0 && (
|
||||
<div className="space-y-3 mt-2">
|
||||
{/* Password Strength Meter */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{t(
|
||||
"passwordStrength"
|
||||
)}
|
||||
</span>
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm font-semibold",
|
||||
passwordStrength.strength ===
|
||||
"strong" &&
|
||||
"text-green-600 dark:text-green-400",
|
||||
passwordStrength.strength ===
|
||||
"medium" &&
|
||||
"text-yellow-600 dark:text-yellow-400",
|
||||
passwordStrength.strength ===
|
||||
"weak" &&
|
||||
"text-red-600 dark:text-red-400"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
`passwordStrength${passwordStrength.strength.charAt(0).toUpperCase() + passwordStrength.strength.slice(1)}`
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<Progress
|
||||
value={
|
||||
passwordStrength.percentage
|
||||
}
|
||||
className="h-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
{t("createAccount")}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* Requirements Checklist */}
|
||||
<div className="bg-muted rounded-lg p-3 space-y-2">
|
||||
<div className="text-sm font-medium text-foreground mb-2">
|
||||
{t(
|
||||
"passwordRequirements"
|
||||
)}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.length ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.length
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementLengthText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.uppercase ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.uppercase
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementUppercaseText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.lowercase ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.lowercase
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementLowercaseText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.number ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.number
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementNumberText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{passwordStrength
|
||||
.requirements
|
||||
.special ? (
|
||||
<Check className="h-3.5 w-3.5 text-green-500 flex-shrink-0" />
|
||||
) : (
|
||||
<X className="h-3.5 w-3.5 text-muted-foreground flex-shrink-0" />
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
"text-sm",
|
||||
passwordStrength
|
||||
.requirements
|
||||
.special
|
||||
? "text-green-600 dark:text-green-400"
|
||||
: "text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{t(
|
||||
"passwordRequirementSpecialText"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Only show FormMessage when not showing our custom requirements */}
|
||||
{passwordValue.length === 0 && (
|
||||
<FormMessage />
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormLabel>
|
||||
{t("confirmPassword")}
|
||||
</FormLabel>
|
||||
{doPasswordsMatch && (
|
||||
<Check className="h-4 w-4 text-green-500" />
|
||||
)}
|
||||
</div>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
type="password"
|
||||
{...field}
|
||||
onChange={(e) => {
|
||||
field.onChange(e);
|
||||
setConfirmPasswordValue(
|
||||
e.target.value
|
||||
);
|
||||
}}
|
||||
className={cn(
|
||||
doPasswordsMatch &&
|
||||
"border-green-500 focus-visible:ring-green-500",
|
||||
confirmPasswordValue.length >
|
||||
0 &&
|
||||
!doPasswordsMatch &&
|
||||
"border-red-500 focus-visible:ring-red-500"
|
||||
)}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
{confirmPasswordValue.length > 0 &&
|
||||
!doPasswordsMatch && (
|
||||
<p className="text-sm text-red-600 mt-1">
|
||||
{t("passwordsDoNotMatch")}
|
||||
</p>
|
||||
)}
|
||||
{/* Only show FormMessage when field is empty */}
|
||||
{confirmPasswordValue.length === 0 && (
|
||||
<FormMessage />
|
||||
)}
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{build === "saas" && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="agreeToTerms"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={(
|
||||
checked
|
||||
) => {
|
||||
field.onChange(
|
||||
checked
|
||||
);
|
||||
handleTermsChange(
|
||||
checked as boolean
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="leading-none">
|
||||
<FormLabel className="text-sm font-normal">
|
||||
<div>
|
||||
{t(
|
||||
"signUpTerms.IAgreeToThe"
|
||||
)}{" "}
|
||||
<a
|
||||
href="https://pangolin.net/terms-of-service.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{t(
|
||||
"signUpTerms.termsOfService"
|
||||
)}{" "}
|
||||
</a>
|
||||
{t(
|
||||
"signUpTerms.and"
|
||||
)}{" "}
|
||||
<a
|
||||
href="https://pangolin.net/privacy-policy.html"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{t(
|
||||
"signUpTerms.privacyPolicy"
|
||||
)}
|
||||
</a>
|
||||
</div>
|
||||
</FormLabel>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="marketingEmailConsent"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="leading-none">
|
||||
<FormLabel className="text-sm font-normal">
|
||||
{t(
|
||||
"signUpMarketing.keepMeInTheLoop"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormMessage />
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<Alert variant="destructive">
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
{t("createAccount")}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,11 @@ export function pullEnv(): Env {
|
||||
process.env.NEW_RELEASES_NOTIFICATION_ENABLED === "true"
|
||||
? true
|
||||
: false
|
||||
}
|
||||
},
|
||||
identityProviderMode: process.env.IDENTITY_PROVIDER_MODE as
|
||||
| "org"
|
||||
| "global"
|
||||
| undefined
|
||||
},
|
||||
email: {
|
||||
emailEnabled: process.env.EMAIL_ENABLED === "true" ? true : false
|
||||
@@ -64,8 +68,6 @@ export function pullEnv(): Env {
|
||||
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS === "true"
|
||||
? true
|
||||
: false,
|
||||
useOrgOnlyIdp:
|
||||
process.env.USE_ORG_ONLY_IDP === "true" ? true : false,
|
||||
disableEnterpriseFeatures:
|
||||
process.env.DISABLE_ENTERPRISE_FEATURES === "true"
|
||||
? true
|
||||
|
||||
@@ -8,6 +8,7 @@ export type Env = {
|
||||
product_updates: boolean;
|
||||
new_releases: boolean;
|
||||
};
|
||||
identityProviderMode?: "global" | "org";
|
||||
};
|
||||
server: {
|
||||
externalPort: string;
|
||||
@@ -34,7 +35,6 @@ export type Env = {
|
||||
hideSupporterKey: boolean;
|
||||
usePangolinDns: boolean;
|
||||
disableProductHelpBanners: boolean;
|
||||
useOrgOnlyIdp: boolean;
|
||||
disableEnterpriseFeatures: boolean;
|
||||
};
|
||||
branding: {
|
||||
|
||||
Reference in New Issue
Block a user