mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-25 15:35:17 +00:00
Compare commits
6 Commits
1.15.0-s.0
...
316b7e5653
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
316b7e5653 | ||
|
|
00fc1da33c | ||
|
|
9ef93df54f | ||
|
|
fd9fdf6399 | ||
|
|
8fa1701e06 | ||
|
|
4abe83f8a9 |
32
.github/workflows/cicd.yml
vendored
32
.github/workflows/cicd.yml
vendored
@@ -339,37 +339,37 @@ jobs:
|
|||||||
TAG=${{ env.TAG }}
|
TAG=${{ env.TAG }}
|
||||||
MAJOR_TAG=$(echo $TAG | cut -d. -f1)
|
MAJOR_TAG=$(echo $TAG | cut -d. -f1)
|
||||||
MINOR_TAG=$(echo $TAG | cut -d. -f1,2)
|
MINOR_TAG=$(echo $TAG | cut -d. -f1,2)
|
||||||
|
|
||||||
echo "Waiting for multi-arch manifests to be ready..."
|
echo "Waiting for multi-arch manifests to be ready..."
|
||||||
sleep 30
|
sleep 30
|
||||||
|
|
||||||
# Determine if this is an RC release
|
# Determine if this is an RC release
|
||||||
IS_RC="false"
|
IS_RC="false"
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
if echo "$TAG" | grep -qE "rc[0-9]+$"; then
|
||||||
IS_RC="true"
|
IS_RC="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$IS_RC" = "true" ]; then
|
if [ "$IS_RC" = "true" ]; then
|
||||||
echo "RC release detected - copying version-specific tags only"
|
echo "RC release detected - copying version-specific tags only"
|
||||||
|
|
||||||
# SQLite OSS
|
# SQLite OSS
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
|
||||||
skopeo copy --all --retry-times 3 \
|
skopeo copy --all --retry-times 3 \
|
||||||
docker://$DOCKERHUB_IMAGE:$TAG \
|
docker://$DOCKERHUB_IMAGE:$TAG \
|
||||||
docker://$GHCR_IMAGE:$TAG
|
docker://$GHCR_IMAGE:$TAG
|
||||||
|
|
||||||
# PostgreSQL OSS
|
# PostgreSQL OSS
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG}"
|
||||||
skopeo copy --all --retry-times 3 \
|
skopeo copy --all --retry-times 3 \
|
||||||
docker://$DOCKERHUB_IMAGE:postgresql-$TAG \
|
docker://$DOCKERHUB_IMAGE:postgresql-$TAG \
|
||||||
docker://$GHCR_IMAGE:postgresql-$TAG
|
docker://$GHCR_IMAGE:postgresql-$TAG
|
||||||
|
|
||||||
# SQLite Enterprise
|
# SQLite Enterprise
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-${TAG}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-${TAG}"
|
||||||
skopeo copy --all --retry-times 3 \
|
skopeo copy --all --retry-times 3 \
|
||||||
docker://$DOCKERHUB_IMAGE:ee-$TAG \
|
docker://$DOCKERHUB_IMAGE:ee-$TAG \
|
||||||
docker://$GHCR_IMAGE:ee-$TAG
|
docker://$GHCR_IMAGE:ee-$TAG
|
||||||
|
|
||||||
# PostgreSQL Enterprise
|
# PostgreSQL Enterprise
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG}"
|
||||||
skopeo copy --all --retry-times 3 \
|
skopeo copy --all --retry-times 3 \
|
||||||
@@ -377,7 +377,7 @@ jobs:
|
|||||||
docker://$GHCR_IMAGE:ee-postgresql-$TAG
|
docker://$GHCR_IMAGE:ee-postgresql-$TAG
|
||||||
else
|
else
|
||||||
echo "Regular release detected - copying all tags (latest, major, minor, full version)"
|
echo "Regular release detected - copying all tags (latest, major, minor, full version)"
|
||||||
|
|
||||||
# SQLite OSS - all tags
|
# SQLite OSS - all tags
|
||||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:${TAG_SUFFIX}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:${TAG_SUFFIX}"
|
||||||
@@ -385,7 +385,7 @@ jobs:
|
|||||||
docker://$DOCKERHUB_IMAGE:$TAG_SUFFIX \
|
docker://$DOCKERHUB_IMAGE:$TAG_SUFFIX \
|
||||||
docker://$GHCR_IMAGE:$TAG_SUFFIX
|
docker://$GHCR_IMAGE:$TAG_SUFFIX
|
||||||
done
|
done
|
||||||
|
|
||||||
# PostgreSQL OSS - all tags
|
# PostgreSQL OSS - all tags
|
||||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG_SUFFIX}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG_SUFFIX}"
|
||||||
@@ -393,7 +393,7 @@ jobs:
|
|||||||
docker://$DOCKERHUB_IMAGE:postgresql-$TAG_SUFFIX \
|
docker://$DOCKERHUB_IMAGE:postgresql-$TAG_SUFFIX \
|
||||||
docker://$GHCR_IMAGE:postgresql-$TAG_SUFFIX
|
docker://$GHCR_IMAGE:postgresql-$TAG_SUFFIX
|
||||||
done
|
done
|
||||||
|
|
||||||
# SQLite Enterprise - all tags
|
# SQLite Enterprise - all tags
|
||||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-${TAG_SUFFIX}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-${TAG_SUFFIX}"
|
||||||
@@ -401,7 +401,7 @@ jobs:
|
|||||||
docker://$DOCKERHUB_IMAGE:ee-$TAG_SUFFIX \
|
docker://$DOCKERHUB_IMAGE:ee-$TAG_SUFFIX \
|
||||||
docker://$GHCR_IMAGE:ee-$TAG_SUFFIX
|
docker://$GHCR_IMAGE:ee-$TAG_SUFFIX
|
||||||
done
|
done
|
||||||
|
|
||||||
# PostgreSQL Enterprise - all tags
|
# PostgreSQL Enterprise - all tags
|
||||||
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
|
||||||
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG_SUFFIX}"
|
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG_SUFFIX}"
|
||||||
@@ -410,7 +410,7 @@ jobs:
|
|||||||
docker://$GHCR_IMAGE:ee-postgresql-$TAG_SUFFIX
|
docker://$GHCR_IMAGE:ee-postgresql-$TAG_SUFFIX
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "All images copied successfully to GHCR!"
|
echo "All images copied successfully to GHCR!"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
@@ -442,7 +442,7 @@ jobs:
|
|||||||
|
|
||||||
# Determine if this is an RC release
|
# Determine if this is an RC release
|
||||||
IS_RC="false"
|
IS_RC="false"
|
||||||
if [[ "$TAG" == *"-rc."* ]]; then
|
if echo "$TAG" | grep -qE "rc[0-9]+$"; then
|
||||||
IS_RC="true"
|
IS_RC="true"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -490,11 +490,11 @@ jobs:
|
|||||||
--certificate-oidc-issuer "${issuer}" \
|
--certificate-oidc-issuer "${issuer}" \
|
||||||
--certificate-identity-regexp "${id_regex}" \
|
--certificate-identity-regexp "${id_regex}" \
|
||||||
"${REF}" -o text
|
"${REF}" -o text
|
||||||
|
|
||||||
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "All images signed and verified successfully!"
|
echo "All images signed and verified successfully!"
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
|
|||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -4,13 +4,13 @@
|
|||||||
},
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"[jsonc]": {
|
"[jsonc]": {
|
||||||
"editor.defaultFormatter": "vscode.json-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "vscode.typescript-language-features"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"[typescriptreact]": {
|
"[typescriptreact]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
@@ -19,4 +19,4 @@
|
|||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
},
|
},
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,8 +80,6 @@ Download the Pangolin client for your platform:
|
|||||||
- [Mac](https://pangolin.net/downloads/mac)
|
- [Mac](https://pangolin.net/downloads/mac)
|
||||||
- [Windows](https://pangolin.net/downloads/windows)
|
- [Windows](https://pangolin.net/downloads/windows)
|
||||||
- [Linux](https://pangolin.net/downloads/linux)
|
- [Linux](https://pangolin.net/downloads/linux)
|
||||||
- [iOS](https://pangolin.net/downloads/ios)
|
|
||||||
- [Android](https://pangolin.net/downloads/android)
|
|
||||||
|
|
||||||
## Get Started
|
## Get Started
|
||||||
|
|
||||||
|
|||||||
72
blueprint.py
Normal file
72
blueprint.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import requests
|
||||||
|
import yaml
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
# The file path for the YAML file to be read
|
||||||
|
# You can change this to the path of your YAML file
|
||||||
|
YAML_FILE_PATH = 'blueprint.yaml'
|
||||||
|
|
||||||
|
# The API endpoint and headers from the curl request
|
||||||
|
API_URL = 'http://api.pangolin.net/v1/org/test/blueprint'
|
||||||
|
HEADERS = {
|
||||||
|
'accept': '*/*',
|
||||||
|
'Authorization': 'Bearer <your_token_here>',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
def convert_and_send(file_path, url, headers):
|
||||||
|
"""
|
||||||
|
Reads a YAML file, converts its content to a JSON payload,
|
||||||
|
and sends it via a PUT request to a specified URL.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Read the YAML file content
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
yaml_content = file.read()
|
||||||
|
|
||||||
|
# Parse the YAML string to a Python dictionary
|
||||||
|
# This will be used to ensure the YAML is valid before sending
|
||||||
|
parsed_yaml = yaml.safe_load(yaml_content)
|
||||||
|
|
||||||
|
# convert the parsed YAML to a JSON string
|
||||||
|
json_payload = json.dumps(parsed_yaml)
|
||||||
|
print("Converted JSON payload:")
|
||||||
|
print(json_payload)
|
||||||
|
|
||||||
|
# Encode the JSON string to Base64
|
||||||
|
encoded_json = base64.b64encode(json_payload.encode('utf-8')).decode('utf-8')
|
||||||
|
|
||||||
|
# Create the final payload with the base64 encoded data
|
||||||
|
final_payload = {
|
||||||
|
"blueprint": encoded_json
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Sending the following Base64 encoded JSON payload:")
|
||||||
|
print(final_payload)
|
||||||
|
print("-" * 20)
|
||||||
|
|
||||||
|
# Make the PUT request with the base64 encoded payload
|
||||||
|
response = requests.put(url, headers=headers, json=final_payload)
|
||||||
|
|
||||||
|
# Print the API response for debugging
|
||||||
|
print(f"API Response Status Code: {response.status_code}")
|
||||||
|
print("API Response Content:")
|
||||||
|
print(response.text)
|
||||||
|
|
||||||
|
# Raise an exception for bad status codes (4xx or 5xx)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
print(f"Error: The file '{file_path}' was not found.")
|
||||||
|
except yaml.YAMLError as e:
|
||||||
|
print(f"Error parsing YAML file: {e}")
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"An error occurred during the API request: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An unexpected error occurred: {e}")
|
||||||
|
|
||||||
|
# Run the function
|
||||||
|
if __name__ == "__main__":
|
||||||
|
convert_and_send(YAML_FILE_PATH, API_URL, HEADERS)
|
||||||
|
|
||||||
70
blueprint.yaml
Normal file
70
blueprint.yaml
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
client-resources:
|
||||||
|
client-resource-nice-id-uno:
|
||||||
|
name: this is my resource
|
||||||
|
protocol: tcp
|
||||||
|
proxy-port: 3001
|
||||||
|
hostname: localhost
|
||||||
|
internal-port: 3000
|
||||||
|
site: lively-yosemite-toad
|
||||||
|
client-resource-nice-id-duce:
|
||||||
|
name: this is my resource
|
||||||
|
protocol: udp
|
||||||
|
proxy-port: 3000
|
||||||
|
hostname: localhost
|
||||||
|
internal-port: 3000
|
||||||
|
site: lively-yosemite-toad
|
||||||
|
|
||||||
|
proxy-resources:
|
||||||
|
resource-nice-id-uno:
|
||||||
|
name: this is my resource
|
||||||
|
protocol: http
|
||||||
|
full-domain: duce.test.example.com
|
||||||
|
host-header: example.com
|
||||||
|
tls-server-name: example.com
|
||||||
|
# auth:
|
||||||
|
# pincode: 123456
|
||||||
|
# password: sadfasdfadsf
|
||||||
|
# sso-enabled: true
|
||||||
|
# sso-roles:
|
||||||
|
# - Member
|
||||||
|
# sso-users:
|
||||||
|
# - owen@pangolin.net
|
||||||
|
# whitelist-users:
|
||||||
|
# - owen@pangolin.net
|
||||||
|
# auto-login-idp: 1
|
||||||
|
headers:
|
||||||
|
- name: X-Example-Header
|
||||||
|
value: example-value
|
||||||
|
- name: X-Another-Header
|
||||||
|
value: another-value
|
||||||
|
rules:
|
||||||
|
- action: allow
|
||||||
|
match: ip
|
||||||
|
value: 1.1.1.1
|
||||||
|
- action: deny
|
||||||
|
match: cidr
|
||||||
|
value: 2.2.2.2/32
|
||||||
|
- action: pass
|
||||||
|
match: path
|
||||||
|
value: /admin
|
||||||
|
targets:
|
||||||
|
- site: lively-yosemite-toad
|
||||||
|
path: /path
|
||||||
|
pathMatchType: prefix
|
||||||
|
hostname: localhost
|
||||||
|
method: http
|
||||||
|
port: 8000
|
||||||
|
- site: slim-alpine-chipmunk
|
||||||
|
hostname: localhost
|
||||||
|
path: /yoman
|
||||||
|
pathMatchType: exact
|
||||||
|
method: http
|
||||||
|
port: 8001
|
||||||
|
resource-nice-id-duce:
|
||||||
|
name: this is other resource
|
||||||
|
protocol: tcp
|
||||||
|
proxy-port: 3000
|
||||||
|
targets:
|
||||||
|
- site: lively-yosemite-toad
|
||||||
|
hostname: localhost
|
||||||
|
port: 3000
|
||||||
@@ -31,7 +31,7 @@ import { pickPort } from "@server/routers/target/helpers";
|
|||||||
import { resourcePassword } from "@server/db";
|
import { resourcePassword } from "@server/db";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "../isLicencedOrSubscribed";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
|
||||||
export type ProxyResourcesResults = {
|
export type ProxyResourcesResults = {
|
||||||
@@ -213,7 +213,11 @@ export async function updateProxyResources(
|
|||||||
// Update existing resource
|
// Update existing resource
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
if (!isLicensed) {
|
if (build == "enterprise" && !isLicensed) {
|
||||||
|
logger.warn(
|
||||||
|
"Server is not licensed! Clearing set maintenance screen values"
|
||||||
|
);
|
||||||
|
// null the maintenance mode fields if not licensed
|
||||||
resourceData.maintenance = undefined;
|
resourceData.maintenance = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -590,7 +594,7 @@ export async function updateProxyResources(
|
|||||||
existingRule.action !== getRuleAction(rule.action) ||
|
existingRule.action !== getRuleAction(rule.action) ||
|
||||||
existingRule.match !== rule.match.toUpperCase() ||
|
existingRule.match !== rule.match.toUpperCase() ||
|
||||||
existingRule.value !==
|
existingRule.value !==
|
||||||
getRuleValue(rule.match.toUpperCase(), rule.value) ||
|
getRuleValue(rule.match.toUpperCase(), rule.value) ||
|
||||||
existingRule.priority !== intendedPriority
|
existingRule.priority !== intendedPriority
|
||||||
) {
|
) {
|
||||||
validateRule(rule);
|
validateRule(rule);
|
||||||
@@ -649,7 +653,11 @@ export async function updateProxyResources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
if (!isLicensed) {
|
if (build == "enterprise" && !isLicensed) {
|
||||||
|
logger.warn(
|
||||||
|
"Server is not licensed! Clearing set maintenance screen values"
|
||||||
|
);
|
||||||
|
// null the maintenance mode fields if not licensed
|
||||||
resourceData.maintenance = undefined;
|
resourceData.maintenance = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { getUniqueClientName } from "@server/db/names";
|
import { getUniqueClientName } from "@server/db/names";
|
||||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { sendTerminateClient } from "@server/routers/client/terminate";
|
import { sendTerminateClient } from "@server/routers/client/terminate";
|
||||||
import { and, eq, notInArray, type InferInsertModel } from "drizzle-orm";
|
import { and, eq, notInArray, type InferInsertModel } from "drizzle-orm";
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
import { build } from "@server/build";
|
||||||
|
import license from "#dynamic/license/license";
|
||||||
|
import { getOrgTierData } from "#dynamic/lib/billing";
|
||||||
|
import { TierId } from "@server/lib/billing/tiers";
|
||||||
|
|
||||||
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> {
|
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> {
|
||||||
return false;
|
if (build === "enterprise") {
|
||||||
}
|
return await license.isUnlocked();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (build === "saas") {
|
||||||
|
const { tier } = await getOrgTierData(orgId);
|
||||||
|
return tier === TierId.STANDARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of a proprietary work.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2025 Fossorial, Inc.
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* This file is licensed under the Fossorial Commercial License.
|
|
||||||
* You may not use this file except in compliance with the License.
|
|
||||||
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
|
|
||||||
*
|
|
||||||
* This file is not licensed under the AGPLv3.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { build } from "@server/build";
|
|
||||||
import license from "#private/license/license";
|
|
||||||
import { getOrgTierData } from "#private/lib/billing";
|
|
||||||
import { TierId } from "@server/lib/billing/tiers";
|
|
||||||
|
|
||||||
export async function isLicensedOrSubscribed(orgId: string): Promise<boolean> {
|
|
||||||
if (build === "enterprise") {
|
|
||||||
return await license.isUnlocked();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (build === "saas") {
|
|
||||||
const { tier } = await getOrgTierData(orgId);
|
|
||||||
return tier === TierId.STANDARD;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
@@ -26,8 +26,7 @@ const applyBlueprintSchema = z
|
|||||||
message: `Invalid YAML: ${error instanceof Error ? error.message : "Unknown error"}`
|
message: `Invalid YAML: ${error instanceof Error ? error.message : "Unknown error"}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
source: z.enum(["API", "UI", "CLI"]).optional()
|
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
@@ -85,7 +84,7 @@ export async function applyYAMLBlueprint(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { blueprint: contents, name, source = "UI" } = parsedBody.data;
|
const { blueprint: contents, name } = parsedBody.data;
|
||||||
|
|
||||||
logger.debug(`Received blueprint:`, contents);
|
logger.debug(`Received blueprint:`, contents);
|
||||||
|
|
||||||
@@ -108,7 +107,7 @@ export async function applyYAMLBlueprint(
|
|||||||
blueprint = await applyBlueprint({
|
blueprint = await applyBlueprint({
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
source,
|
source: "UI",
|
||||||
configData: parsedConfig
|
configData: parsedConfig
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { Blueprint } from "@server/db";
|
import type { Blueprint } from "@server/db";
|
||||||
|
|
||||||
export type BlueprintSource = "API" | "UI" | "NEWT" | "CLI";
|
export type BlueprintSource = "API" | "UI" | "NEWT";
|
||||||
|
|
||||||
export type BlueprintData = Omit<Blueprint, "source"> & {
|
export type BlueprintData = Omit<Blueprint, "source"> & {
|
||||||
source: BlueprintSource;
|
source: BlueprintSource;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { getUserDeviceName } from "@server/db/names";
|
import { getUserDeviceName } from "@server/db/names";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
|
|
||||||
const getClientSchema = z.strictObject({
|
const getClientSchema = z.strictObject({
|
||||||
clientId: z
|
clientId: z
|
||||||
@@ -78,108 +78,58 @@ function getPlatformPostureData(
|
|||||||
|
|
||||||
// Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Antivirus status
|
// Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Antivirus status
|
||||||
if (normalizedPlatform === "windows") {
|
if (normalizedPlatform === "windows") {
|
||||||
if (
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
fingerprint.diskEncrypted !== null &&
|
|
||||||
fingerprint.diskEncrypted !== undefined
|
|
||||||
) {
|
|
||||||
posture.diskEncrypted = fingerprint.diskEncrypted;
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
fingerprint.firewallEnabled !== null &&
|
|
||||||
fingerprint.firewallEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.firewallEnabled = fingerprint.firewallEnabled;
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||||
fingerprint.tpmAvailable !== null &&
|
|
||||||
fingerprint.tpmAvailable !== undefined
|
|
||||||
) {
|
|
||||||
posture.tpmAvailable = fingerprint.tpmAvailable;
|
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.windowsAntivirusEnabled !== null && fingerprint.windowsAntivirusEnabled !== undefined) {
|
||||||
fingerprint.windowsAntivirusEnabled !== null &&
|
posture.windowsAntivirusEnabled = fingerprint.windowsAntivirusEnabled;
|
||||||
fingerprint.windowsAntivirusEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.windowsAntivirusEnabled =
|
|
||||||
fingerprint.windowsAntivirusEnabled;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode
|
// macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode
|
||||||
else if (normalizedPlatform === "macos") {
|
else if (normalizedPlatform === "macos") {
|
||||||
if (
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
fingerprint.diskEncrypted !== null &&
|
|
||||||
fingerprint.diskEncrypted !== undefined
|
|
||||||
) {
|
|
||||||
posture.diskEncrypted = fingerprint.diskEncrypted;
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) {
|
||||||
fingerprint.biometricsEnabled !== null &&
|
|
||||||
fingerprint.biometricsEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
posture.biometricsEnabled = fingerprint.biometricsEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
fingerprint.firewallEnabled !== null &&
|
|
||||||
fingerprint.firewallEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.firewallEnabled = fingerprint.firewallEnabled;
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.macosSipEnabled !== null && fingerprint.macosSipEnabled !== undefined) {
|
||||||
fingerprint.macosSipEnabled !== null &&
|
|
||||||
fingerprint.macosSipEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.macosSipEnabled = fingerprint.macosSipEnabled;
|
posture.macosSipEnabled = fingerprint.macosSipEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.macosGatekeeperEnabled !== null && fingerprint.macosGatekeeperEnabled !== undefined) {
|
||||||
fingerprint.macosGatekeeperEnabled !== null &&
|
|
||||||
fingerprint.macosGatekeeperEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.macosGatekeeperEnabled = fingerprint.macosGatekeeperEnabled;
|
posture.macosGatekeeperEnabled = fingerprint.macosGatekeeperEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.macosFirewallStealthMode !== null && fingerprint.macosFirewallStealthMode !== undefined) {
|
||||||
fingerprint.macosFirewallStealthMode !== null &&
|
posture.macosFirewallStealthMode = fingerprint.macosFirewallStealthMode;
|
||||||
fingerprint.macosFirewallStealthMode !== undefined
|
|
||||||
) {
|
|
||||||
posture.macosFirewallStealthMode =
|
|
||||||
fingerprint.macosFirewallStealthMode;
|
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) {
|
||||||
fingerprint.autoUpdatesEnabled !== null &&
|
|
||||||
fingerprint.autoUpdatesEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled;
|
posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Linux: Hard drive encryption, Firewall, AppArmor, SELinux, TPM availability
|
// Linux: Hard drive encryption, Firewall, AppArmor, SELinux, TPM availability
|
||||||
else if (normalizedPlatform === "linux") {
|
else if (normalizedPlatform === "linux") {
|
||||||
if (
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
fingerprint.diskEncrypted !== null &&
|
|
||||||
fingerprint.diskEncrypted !== undefined
|
|
||||||
) {
|
|
||||||
posture.diskEncrypted = fingerprint.diskEncrypted;
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) {
|
||||||
fingerprint.firewallEnabled !== null &&
|
|
||||||
fingerprint.firewallEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.firewallEnabled = fingerprint.firewallEnabled;
|
posture.firewallEnabled = fingerprint.firewallEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.linuxAppArmorEnabled !== null && fingerprint.linuxAppArmorEnabled !== undefined) {
|
||||||
fingerprint.linuxAppArmorEnabled !== null &&
|
|
||||||
fingerprint.linuxAppArmorEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.linuxAppArmorEnabled = fingerprint.linuxAppArmorEnabled;
|
posture.linuxAppArmorEnabled = fingerprint.linuxAppArmorEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.linuxSELinuxEnabled !== null && fingerprint.linuxSELinuxEnabled !== undefined) {
|
||||||
fingerprint.linuxSELinuxEnabled !== null &&
|
|
||||||
fingerprint.linuxSELinuxEnabled !== undefined
|
|
||||||
) {
|
|
||||||
posture.linuxSELinuxEnabled = fingerprint.linuxSELinuxEnabled;
|
posture.linuxSELinuxEnabled = fingerprint.linuxSELinuxEnabled;
|
||||||
}
|
}
|
||||||
if (
|
if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) {
|
||||||
fingerprint.tpmAvailable !== null &&
|
|
||||||
fingerprint.tpmAvailable !== undefined
|
|
||||||
) {
|
|
||||||
posture.tpmAvailable = fingerprint.tpmAvailable;
|
posture.tpmAvailable = fingerprint.tpmAvailable;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -189,10 +139,7 @@ function getPlatformPostureData(
|
|||||||
}
|
}
|
||||||
// Android: Screen lock, Biometric configuration, Hard drive encryption
|
// Android: Screen lock, Biometric configuration, Hard drive encryption
|
||||||
else if (normalizedPlatform === "android") {
|
else if (normalizedPlatform === "android") {
|
||||||
if (
|
if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) {
|
||||||
fingerprint.diskEncrypted !== null &&
|
|
||||||
fingerprint.diskEncrypted !== undefined
|
|
||||||
) {
|
|
||||||
posture.diskEncrypted = fingerprint.diskEncrypted;
|
posture.diskEncrypted = fingerprint.diskEncrypted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,31 +236,33 @@ export async function getClient(
|
|||||||
// Build fingerprint data if available
|
// Build fingerprint data if available
|
||||||
const fingerprintData = client.currentFingerprint
|
const fingerprintData = client.currentFingerprint
|
||||||
? {
|
? {
|
||||||
username: client.currentFingerprint.username || null,
|
username: client.currentFingerprint.username || null,
|
||||||
hostname: client.currentFingerprint.hostname || null,
|
hostname: client.currentFingerprint.hostname || null,
|
||||||
platform: client.currentFingerprint.platform || null,
|
platform: client.currentFingerprint.platform || null,
|
||||||
osVersion: client.currentFingerprint.osVersion || null,
|
osVersion: client.currentFingerprint.osVersion || null,
|
||||||
kernelVersion:
|
kernelVersion:
|
||||||
client.currentFingerprint.kernelVersion || null,
|
client.currentFingerprint.kernelVersion || null,
|
||||||
arch: client.currentFingerprint.arch || null,
|
arch: client.currentFingerprint.arch || null,
|
||||||
deviceModel: client.currentFingerprint.deviceModel || null,
|
deviceModel: client.currentFingerprint.deviceModel || null,
|
||||||
serialNumber: client.currentFingerprint.serialNumber || null,
|
serialNumber: client.currentFingerprint.serialNumber || null,
|
||||||
firstSeen: client.currentFingerprint.firstSeen || null,
|
firstSeen: client.currentFingerprint.firstSeen || null,
|
||||||
lastSeen: client.currentFingerprint.lastSeen || null
|
lastSeen: client.currentFingerprint.lastSeen || null
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
// Build posture data if available (platform-specific)
|
// Build posture data if available (platform-specific)
|
||||||
// Only return posture data if org is licensed/subscribed
|
// Only return posture data if org is licensed/subscribed
|
||||||
let postureData: PostureData | null = null;
|
let postureData: PostureData | null = null;
|
||||||
const isOrgLicensed = await isLicensedOrSubscribed(
|
if (build !== "oss") {
|
||||||
client.clients.orgId
|
const isOrgLicensed = await isLicensedOrSubscribed(
|
||||||
);
|
client.clients.orgId
|
||||||
if (isOrgLicensed) {
|
|
||||||
postureData = getPlatformPostureData(
|
|
||||||
client.currentFingerprint?.platform || null,
|
|
||||||
client.currentFingerprint
|
|
||||||
);
|
);
|
||||||
|
if (isOrgLicensed) {
|
||||||
|
postureData = getPlatformPostureData(
|
||||||
|
client.currentFingerprint?.platform || null,
|
||||||
|
client.currentFingerprint
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: GetClientResponse = {
|
const data: GetClientResponse = {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { build } from "@server/build";
|
|||||||
import { getOrgTierData } from "#dynamic/lib/billing";
|
import { getOrgTierData } from "#dynamic/lib/billing";
|
||||||
import { TierId } from "@server/lib/billing/tiers";
|
import { TierId } from "@server/lib/billing/tiers";
|
||||||
import { cache } from "@server/lib/cache";
|
import { cache } from "@server/lib/cache";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
|
|
||||||
const updateOrgParamsSchema = z.strictObject({
|
const updateOrgParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -89,7 +89,7 @@ export async function updateOrg(
|
|||||||
const { orgId } = parsedParams.data;
|
const { orgId } = parsedParams.data;
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
if (!isLicensed) {
|
if (build == "enterprise" && !isLicensed) {
|
||||||
parsedBody.data.requireTwoFactor = undefined;
|
parsedBody.data.requireTwoFactor = undefined;
|
||||||
parsedBody.data.maxSessionLengthHours = undefined;
|
parsedBody.data.maxSessionLengthHours = undefined;
|
||||||
parsedBody.data.passwordExpiryDays = undefined;
|
parsedBody.data.passwordExpiryDays = undefined;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import { OpenAPITags } from "@server/openApi";
|
|||||||
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||||
import { validateAndConstructDomain } from "@server/lib/domainUtils";
|
import { validateAndConstructDomain } from "@server/lib/domainUtils";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
|
|
||||||
const updateResourceParamsSchema = z.strictObject({
|
const updateResourceParamsSchema = z.strictObject({
|
||||||
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
resourceId: z.string().transform(Number).pipe(z.int().positive())
|
||||||
@@ -342,7 +342,11 @@ async function updateHttpResource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(resource.orgId);
|
const isLicensed = await isLicensedOrSubscribed(resource.orgId);
|
||||||
if (!isLicensed) {
|
if (build == "enterprise" && !isLicensed) {
|
||||||
|
logger.warn(
|
||||||
|
"Server is not licensed! Clearing set maintenance screen values"
|
||||||
|
);
|
||||||
|
// null the maintenance mode fields if not licensed
|
||||||
updateData.maintenanceModeEnabled = undefined;
|
updateData.maintenanceModeEnabled = undefined;
|
||||||
updateData.maintenanceModeType = undefined;
|
updateData.maintenanceModeType = undefined;
|
||||||
updateData.maintenanceTitle = undefined;
|
updateData.maintenanceTitle = undefined;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { ActionsEnum } from "@server/auth/actions";
|
|||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
|
|
||||||
const createRoleParamsSchema = z.strictObject({
|
const createRoleParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -101,7 +101,7 @@ export async function createRole(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
if (!isLicensed) {
|
if (build === "oss" || !isLicensed) {
|
||||||
roleData.requireDeviceApproval = undefined;
|
roleData.requireDeviceApproval = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import HttpCode from "@server/types/HttpCode";
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { build } from "@server/build";
|
||||||
|
import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
const updateRoleParamsSchema = z.strictObject({
|
const updateRoleParamsSchema = z.strictObject({
|
||||||
@@ -111,7 +112,7 @@ export async function updateRole(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isLicensed = await isLicensedOrSubscribed(orgId);
|
const isLicensed = await isLicensedOrSubscribed(orgId);
|
||||||
if (!isLicensed) {
|
if (build === "oss" || !isLicensed) {
|
||||||
updateData.requireDeviceApproval = undefined;
|
updateData.requireDeviceApproval = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,15 +110,6 @@ export default function BlueprintDetailsForm({
|
|||||||
Dashboard
|
Dashboard
|
||||||
</Badge>
|
</Badge>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{blueprint.source === "CLI" && (
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="inline-flex items-center gap-1 "
|
|
||||||
>
|
|
||||||
<Terminal className="w-3 h-3 flex-none" />
|
|
||||||
CLI
|
|
||||||
</Badge>
|
|
||||||
)}{" "}
|
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
|
|||||||
@@ -128,19 +128,6 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) {
|
|||||||
</Badge>
|
</Badge>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
case "CLI": {
|
|
||||||
return (
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="inline-flex items-center gap-1"
|
|
||||||
>
|
|
||||||
<span className="inline-flex items-center gap-1 ">
|
|
||||||
<Terminal className="w-3 h-3" />
|
|
||||||
CLI
|
|
||||||
</span>
|
|
||||||
</Badge>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user