mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-17 21:01:53 +00:00
Compare commits
12 Commits
crowdin_de
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bde633c5f | ||
|
|
a7c99f336f | ||
|
|
0d960181a2 | ||
|
|
b6862093d1 | ||
|
|
16c0f4eef4 | ||
|
|
a0fef89031 | ||
|
|
f15654ed11 | ||
|
|
0b41fe3d49 | ||
|
|
b9db0a4490 | ||
|
|
d9952b0762 | ||
|
|
6e271028f3 | ||
|
|
a724b07846 |
@@ -72,7 +72,7 @@
|
||||
"siteManageSites": "管理站点",
|
||||
"siteDescription": "创建和管理站点,启用与私人网络的连接",
|
||||
"sitesBannerTitle": "连接任何网络",
|
||||
"sitesBannerDescription": "站点是到远程网络的连接,使 Pangolin 能够向任何位置的用户提提供公共或私有的资源访问。你可以在任何能够运行二进制文件或容器的地方安装站点网络连接器(Newt),以建立连接。",
|
||||
"sitesBannerDescription": "站点是连接到远程网络的链接,允许Pangolin为用户提供资源访问,无论是公共还是私人。可以在任何可以运行二进制文件或容器的地方安装站点网络连接器(Newt)以建立连接。",
|
||||
"sitesBannerButtonText": "安装站点",
|
||||
"approvalsBannerTitle": "批准或拒绝设备访问",
|
||||
"approvalsBannerDescription": "审核、批准或拒绝用户的设备访问请求。 当需要设备批准时,用户必须先获得管理员批准,然后他们的设备才能连接到您的组织资源。",
|
||||
@@ -204,11 +204,11 @@
|
||||
"proxyResourceTitle": "管理公共资源",
|
||||
"proxyResourceDescription": "创建和管理可通过 Web 浏览器公开访问的资源",
|
||||
"publicResourcesBannerTitle": "基于 Web 的公共访问",
|
||||
"publicResourcesBannerDescription": "公共资源是 HTTPS 代理,可供互联网上的任何人通过 Web 浏览器访问。与私人资源不同,它们不需要客户端软件,并且可以包含身份和上下文感知的访问策略。",
|
||||
"publicResourcesBannerDescription": "公共资源是 HTTPS 代理,可以通过网络浏览器在互联网上的任何人访问。与私人资源不同,它们不需要客户端软件,并且可以包含身份和上下文感知的访问策略。",
|
||||
"clientResourceTitle": "管理私有资源",
|
||||
"clientResourceDescription": "创建和管理只能通过连接客户端访问的资源",
|
||||
"privateResourcesBannerTitle": "零信任私有访问",
|
||||
"privateResourcesBannerDescription": "私有资源采用零信任安全机制,确保只有获得明确授权的用户和机器才能访问。用户设备或机器客户端连接后,即可通过安全的虚拟专用网络访问这些资源。",
|
||||
"privateResourcesBannerTitle": "零信任的私人访问",
|
||||
"privateResourcesBannerDescription": "私人资源使用零信任安全性,确保只允许明确授予的用户和机器访问资源。可以连接用户设备或机器客户端,通过安全的虚拟专用网络访问这些资源。",
|
||||
"resourcesSearch": "搜索资源...",
|
||||
"resourceAdd": "添加资源",
|
||||
"resourceErrorDelte": "删除资源时出错",
|
||||
@@ -327,7 +327,7 @@
|
||||
"passToAuth": "传递至认证",
|
||||
"orgSettingsDescription": "配置组织设置",
|
||||
"orgGeneralSettings": "组织设置",
|
||||
"orgGeneralSettingsDescription": "管理组织的详细信息和配置",
|
||||
"orgGeneralSettingsDescription": "管理机构的详细信息和配置",
|
||||
"saveGeneralSettings": "保存常规设置",
|
||||
"saveSettings": "保存设置",
|
||||
"orgDangerZone": "危险区域",
|
||||
@@ -381,7 +381,7 @@
|
||||
"accessApprovalsDescription": "查看和管理待审批的组织访问权限",
|
||||
"description": "描述",
|
||||
"inviteTitle": "打开邀请",
|
||||
"inviteDescription": "管理其他用户加入组织的邀请",
|
||||
"inviteDescription": "管理其他用户加入机构的邀请",
|
||||
"inviteSearch": "搜索邀请...",
|
||||
"minutes": "分钟",
|
||||
"hours": "小时",
|
||||
@@ -425,12 +425,12 @@
|
||||
"apiKeysDelete": "删除 API 密钥",
|
||||
"apiKeysManage": "管理 API 密钥",
|
||||
"apiKeysDescription": "API 密钥用于认证集成 API",
|
||||
"provisioningKeysTitle": "预配密钥",
|
||||
"provisioningKeysManage": "管理预配密钥",
|
||||
"provisioningKeysTitle": "置备密钥",
|
||||
"provisioningKeysManage": "管理置备键",
|
||||
"provisioningKeysDescription": "置备密钥用于验证您组织的自动站点配置。",
|
||||
"provisioningManage": "预配",
|
||||
"provisioningDescription": "管理预配密钥,并审核待批准的站点。",
|
||||
"pendingSites": "待审批站点",
|
||||
"provisioningManage": "置备中",
|
||||
"provisioningDescription": "管理预配键和审查等待批准的站点。",
|
||||
"pendingSites": "待定站点",
|
||||
"siteApproveSuccess": "站点批准成功",
|
||||
"siteApproveError": "批准站点出错",
|
||||
"provisioningKeys": "置备键",
|
||||
@@ -467,11 +467,11 @@
|
||||
"provisioningKeysUpdateError": "更新预配键时出错",
|
||||
"provisioningKeysUpdated": "置备密钥已更新",
|
||||
"provisioningKeysUpdatedDescription": "您的更改已保存。",
|
||||
"provisioningKeysBannerTitle": "站点预配密钥",
|
||||
"provisioningKeysBannerDescription": "生成预配密钥,并将其与 Newt 连接器配合使用,即可在首次启动时自动创建站点,无需为每个站点单独配置凭据。",
|
||||
"provisioningKeysBannerTitle": "站点置备密钥",
|
||||
"provisioningKeysBannerDescription": "生成一个供应密钥,并将其与 Newt 连接器一起使用,以在首次启动时自动创建站点 - 无需为每个站点设置单独的凭据。",
|
||||
"provisioningKeysBannerButtonText": "了解更多",
|
||||
"pendingSitesBannerTitle": "待审批站点",
|
||||
"pendingSitesBannerDescription": "使用预配密钥连接的网站会在这里以供审核。",
|
||||
"pendingSitesBannerTitle": "待定站点",
|
||||
"pendingSitesBannerDescription": "使用供应密钥连接的站点将在此显示以供审核。",
|
||||
"pendingSitesBannerButtonText": "了解更多",
|
||||
"apiKeysSettings": "{apiKeyName} 设置",
|
||||
"userTitle": "管理所有用户",
|
||||
@@ -1059,7 +1059,7 @@
|
||||
"network": "网络",
|
||||
"manage": "管理",
|
||||
"sitesNotFound": "未找到站点。",
|
||||
"pangolinServerAdmin": "服务器管理 - Pangolin",
|
||||
"pangolinServerAdmin": "服务器管理员 - Pangolin",
|
||||
"licenseTierProfessional": "专业许可证",
|
||||
"licenseTierEnterprise": "企业许可证",
|
||||
"licenseTierPersonal": "个人许可证",
|
||||
@@ -1366,7 +1366,7 @@
|
||||
"supportKeyBuy": "购买支持者密钥",
|
||||
"logoutError": "注销错误",
|
||||
"signingAs": "登录为",
|
||||
"serverAdmin": "服务器管理",
|
||||
"serverAdmin": "服务器管理员",
|
||||
"managedSelfhosted": "托管自托管",
|
||||
"otpEnable": "启用双因子认证",
|
||||
"otpDisable": "禁用双因子认证",
|
||||
@@ -1536,8 +1536,8 @@
|
||||
"sidebarSites": "站点",
|
||||
"sidebarApprovals": "审批请求",
|
||||
"sidebarResources": "资源",
|
||||
"sidebarProxyResources": "公开资源",
|
||||
"sidebarClientResources": "私有资源",
|
||||
"sidebarProxyResources": "公开的",
|
||||
"sidebarClientResources": "非公开的",
|
||||
"sidebarPolicies": "共享策略",
|
||||
"sidebarResourcePolicies": "公共资源",
|
||||
"sidebarAccessControl": "访问控制",
|
||||
@@ -1549,15 +1549,15 @@
|
||||
"sidebarRoles": "角色",
|
||||
"sidebarShareableLinks": "可共享链接",
|
||||
"sidebarApiKeys": "API密钥",
|
||||
"sidebarProvisioning": "预配",
|
||||
"sidebarProvisioning": "置备中",
|
||||
"sidebarSettings": "设置",
|
||||
"sidebarAllUsers": "所有用户",
|
||||
"sidebarIdentityProviders": "身份提供商",
|
||||
"sidebarLicense": "证书",
|
||||
"sidebarClients": "客户端",
|
||||
"sidebarUserDevices": "用户设备",
|
||||
"sidebarMachineClients": "机器身份",
|
||||
"sidebarDomains": "域名",
|
||||
"sidebarMachineClients": "机",
|
||||
"sidebarDomains": "域",
|
||||
"sidebarGeneral": "管理",
|
||||
"sidebarLogAndAnalytics": "日志与分析",
|
||||
"sidebarBluePrints": "蓝图",
|
||||
@@ -1689,8 +1689,8 @@
|
||||
"alertingTabHealthChecks": "健康检查",
|
||||
"alertingRulesBannerTitle": "获取通知",
|
||||
"alertingRulesBannerDescription": "每条规则都连接要监视的对象(站点、健康检查或资源),触发时间(例如离线或不健康),以及如何通过电子邮件、Webhooks 或集成将通知发送给团队。使用此列表创建、启用和管理这些规则。",
|
||||
"alertingHealthChecksBannerTitle": "资源与健康监控",
|
||||
"alertingHealthChecksBannerDescription": "通过 HTTP 或 TCP 检查目标状态,并在服务异常或恢复时发送通知。资源中配置的健康检查也会显示在这里。",
|
||||
"alertingHealthChecksBannerTitle": "监视健康和资源",
|
||||
"alertingHealthChecksBannerDescription": "健康检查是您一次定义的 HTTP 或 TCP 监控。然后可以将它们用作告警规则中的来源,以便目标变得正常或不正常时得到通知。资源上的健康检查也会出现在此处。",
|
||||
"standaloneHcTableTitle": "健康检查",
|
||||
"standaloneHcSearchPlaceholder": "搜索健康检查…",
|
||||
"standaloneHcAddButton": "创建健康检查",
|
||||
@@ -1793,15 +1793,15 @@
|
||||
"initialSetupTitle": "初始服务器设置",
|
||||
"initialSetupDescription": "创建初始服务器管理员帐户。 只能存在一个服务器管理员。 您可以随时更改这些凭据。",
|
||||
"createAdminAccount": "创建管理员帐户",
|
||||
"setupErrorCreateAdmin": "创建管理员账户时发生错误。",
|
||||
"setupErrorCreateAdmin": "创建服务器管理员账户时发生错误。",
|
||||
"certificateStatus": "证书",
|
||||
"certificateStatusAutoRefreshHint": "状态自动刷新。",
|
||||
"loading": "加载中",
|
||||
"loadingEllipsis": "加载中……",
|
||||
"loadingAnalytics": "加载分析",
|
||||
"restart": "重启",
|
||||
"domains": "域名",
|
||||
"domainsDescription": "创建和管理组织中可用的域名",
|
||||
"domains": "域",
|
||||
"domainsDescription": "创建和管理组织中可用的域",
|
||||
"domainsSearch": "搜索域...",
|
||||
"domainAdd": "添加域",
|
||||
"domainAddDescription": "注册一个新域名到组织",
|
||||
@@ -2183,7 +2183,7 @@
|
||||
"roleTextImportAppend": "附加到现有",
|
||||
"roleTextImportMode": "导入模式",
|
||||
"roleTextImportPreview": "预览",
|
||||
"roleTextImportItemCount": "{count, plural, =0 {没有可导入的项目} one {1 个可导入项目} other {# 个可导入项目}}",
|
||||
"roleTextImportItemCount": "{count, plural, =0 {No items to import} one {1 item to import} other {# items to import}}",
|
||||
"roleTextImportTotalCount": "{existing} 个现有 + {imported} 个导入 = {total} 个总计",
|
||||
"roleTextImportConfirm": "导入",
|
||||
"roleTextImportInvalidFile": "不支持的文件类型",
|
||||
@@ -2235,8 +2235,8 @@
|
||||
"resourceEditDomain": "编辑域名",
|
||||
"siteName": "站点名称",
|
||||
"proxyPort": "端口",
|
||||
"resourcesTableProxyResources": "公开资源",
|
||||
"resourcesTableClientResources": "私有资源",
|
||||
"resourcesTableProxyResources": "公开的",
|
||||
"resourcesTableClientResources": "非公开的",
|
||||
"resourcesTableNoProxyResourcesFound": "未找到代理资源。",
|
||||
"resourcesTableNoInternalResourcesFound": "未找到内部资源。",
|
||||
"resourcesTableDestination": "目标",
|
||||
@@ -2925,7 +2925,7 @@
|
||||
"logRetentionRequestDescription": "保留请求日志的时间",
|
||||
"logRetentionAccessLabel": "访问日志保留",
|
||||
"logRetentionAccessDescription": "保留访问日志的时间",
|
||||
"logRetentionActionLabel": "审计日志保留",
|
||||
"logRetentionActionLabel": "动作日志保留",
|
||||
"logRetentionActionDescription": "保留操作日志的时间",
|
||||
"logRetentionConnectionLabel": "连接日志保留",
|
||||
"logRetentionConnectionDescription": "保留连接日志的时间",
|
||||
@@ -2938,11 +2938,11 @@
|
||||
"logRetentionForever": "永远的",
|
||||
"logRetentionEndOfFollowingYear": "下一年结束",
|
||||
"actionLogsDescription": "查看此机构执行的操作历史",
|
||||
"accessLogsDescription": "查看此组织资源的访问认证请求",
|
||||
"accessLogsDescription": "查看此机构资源的访问认证请求",
|
||||
"connectionLogs": "连接日志",
|
||||
"connectionLogsDescription": "查看此机构隧道的连接日志",
|
||||
"sidebarLogsConnection": "连接日志",
|
||||
"sidebarLogsStreaming": "事件流",
|
||||
"sidebarLogsStreaming": "流流",
|
||||
"sourceAddress": "源地址",
|
||||
"destinationAddress": "目的地址",
|
||||
"duration": "期限",
|
||||
|
||||
@@ -154,12 +154,8 @@ export async function createResourceRule(
|
||||
}
|
||||
|
||||
// Create the new resource rule
|
||||
const isInlinePolicy =
|
||||
resource.resourcePolicyId === null &&
|
||||
resource.defaultResourcePolicyId !== null;
|
||||
|
||||
if (isInlinePolicy) {
|
||||
const policyId = resource.defaultResourcePolicyId!;
|
||||
if (resource.resourcePolicyId !== null) {
|
||||
const policyId = resource.resourcePolicyId;
|
||||
const [newRule] = await db
|
||||
.insert(resourcePolicyRules)
|
||||
.values({
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { resourceRules, resourcePolicyRules, resources } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
@@ -73,14 +73,18 @@ export async function deleteResourceRule(
|
||||
);
|
||||
}
|
||||
|
||||
const isInlinePolicy =
|
||||
resource.resourcePolicyId === null &&
|
||||
resource.defaultResourcePolicyId !== null;
|
||||
|
||||
if (isInlinePolicy) {
|
||||
if (resource.resourcePolicyId !== null) {
|
||||
const [deletedRule] = await db
|
||||
.delete(resourcePolicyRules)
|
||||
.where(eq(resourcePolicyRules.ruleId, ruleId))
|
||||
.where(
|
||||
and(
|
||||
eq(resourcePolicyRules.ruleId, ruleId),
|
||||
eq(
|
||||
resourcePolicyRules.resourcePolicyId,
|
||||
resource.resourcePolicyId
|
||||
)
|
||||
)
|
||||
)
|
||||
.returning();
|
||||
|
||||
if (!deletedRule) {
|
||||
|
||||
@@ -141,16 +141,10 @@ export async function getResource(
|
||||
);
|
||||
}
|
||||
|
||||
const isInlinePolicy =
|
||||
resource.resourcePolicyId === null &&
|
||||
resource.defaultResourcePolicyId !== null;
|
||||
|
||||
let returnData = resource;
|
||||
if (isInlinePolicy) {
|
||||
if (resource.resourcePolicyId !== null) {
|
||||
// get the policy
|
||||
const policy = await queryInlinePolicy(
|
||||
resource.defaultResourcePolicyId!
|
||||
);
|
||||
const policy = await queryInlinePolicy(resource.resourcePolicyId);
|
||||
returnData = {
|
||||
...returnData,
|
||||
sso: policy?.sso || null,
|
||||
|
||||
@@ -140,15 +140,11 @@ export async function listResourceRules(
|
||||
);
|
||||
}
|
||||
|
||||
const isInlinePolicy =
|
||||
resource.resourcePolicyId === null &&
|
||||
resource.defaultResourcePolicyId !== null;
|
||||
|
||||
let rulesList: Awaited<ReturnType<typeof queryResourceRules>>;
|
||||
let totalCount: number;
|
||||
|
||||
if (isInlinePolicy) {
|
||||
const policyId = resource.defaultResourcePolicyId!;
|
||||
if (resource.resourcePolicyId !== null) {
|
||||
const policyId = resource.resourcePolicyId;
|
||||
const policyRules = await queryPolicyRules(policyId)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { resourceRules, resources } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { resourcePolicyRules, resourceRules, resources } from "@server/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import createHttpError from "http-errors";
|
||||
@@ -37,6 +37,29 @@ const updateResourceRuleSchema = z
|
||||
error: "At least one field must be provided for update"
|
||||
});
|
||||
|
||||
function getRuleValueValidationError(
|
||||
match: "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN" | "REGION",
|
||||
value: string
|
||||
): string | null {
|
||||
if (match === "CIDR" && !isValidCIDR(value)) {
|
||||
return "Invalid CIDR provided";
|
||||
}
|
||||
|
||||
if (match === "IP" && !isValidIP(value)) {
|
||||
return "Invalid IP provided";
|
||||
}
|
||||
|
||||
if (match === "PATH" && !isValidUrlGlobPattern(value)) {
|
||||
return "Invalid URL glob pattern provided";
|
||||
}
|
||||
|
||||
if (match === "REGION" && !isValidRegionId(value)) {
|
||||
return "Invalid region ID provided";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
registry.registerPath({
|
||||
method: "post",
|
||||
path: "/resource/{resourceId}/rule/{ruleId}",
|
||||
@@ -128,6 +151,68 @@ export async function updateResourceRule(
|
||||
);
|
||||
}
|
||||
|
||||
if (resource.resourcePolicyId !== null) {
|
||||
const [existingRule] = await db
|
||||
.select()
|
||||
.from(resourcePolicyRules)
|
||||
.where(
|
||||
and(
|
||||
eq(resourcePolicyRules.ruleId, ruleId),
|
||||
eq(
|
||||
resourcePolicyRules.resourcePolicyId,
|
||||
resource.resourcePolicyId
|
||||
)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!existingRule) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Resource rule with ID ${ruleId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const match = updateData.match || existingRule.match;
|
||||
const { value } = updateData;
|
||||
|
||||
if (value !== undefined) {
|
||||
const validationError = getRuleValueValidationError(
|
||||
match,
|
||||
value
|
||||
);
|
||||
if (validationError) {
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, validationError)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const [updatedRule] = await db
|
||||
.update(resourcePolicyRules)
|
||||
.set(updateData)
|
||||
.where(
|
||||
and(
|
||||
eq(resourcePolicyRules.ruleId, ruleId),
|
||||
eq(
|
||||
resourcePolicyRules.resourcePolicyId,
|
||||
resource.resourcePolicyId
|
||||
)
|
||||
)
|
||||
)
|
||||
.returning();
|
||||
|
||||
return response(res, {
|
||||
data: updatedRule,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "Resource rule updated successfully",
|
||||
status: HttpCode.OK
|
||||
});
|
||||
}
|
||||
|
||||
// Verify that the rule exists and belongs to the specified resource
|
||||
const [existingRule] = await db
|
||||
.select()
|
||||
@@ -157,43 +242,12 @@ export async function updateResourceRule(
|
||||
const { value } = updateData;
|
||||
|
||||
if (value !== undefined) {
|
||||
if (match === "CIDR") {
|
||||
if (!isValidCIDR(value)) {
|
||||
const validationError = getRuleValueValidationError(match, value);
|
||||
if (validationError) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Invalid CIDR provided"
|
||||
)
|
||||
createHttpError(HttpCode.BAD_REQUEST, validationError)
|
||||
);
|
||||
}
|
||||
} else if (match === "IP") {
|
||||
if (!isValidIP(value)) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Invalid IP provided"
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (match === "PATH") {
|
||||
if (!isValidUrlGlobPattern(value)) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Invalid URL glob pattern provided"
|
||||
)
|
||||
);
|
||||
}
|
||||
} else if (match === "REGION") {
|
||||
if (!isValidRegionId(value)) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.BAD_REQUEST,
|
||||
"Invalid region ID provided"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the rule
|
||||
|
||||
@@ -139,6 +139,7 @@ Restart=always
|
||||
RestartSec=2
|
||||
UMask=0077
|
||||
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
|
||||
[Install]
|
||||
|
||||
Reference in New Issue
Block a user