mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-22 15:22:12 +00:00
Compare commits
11 Commits
dependabot
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a76c680914 | ||
|
|
4fd3d6078e | ||
|
|
bc28290d8e | ||
|
|
241610579c | ||
|
|
7c15c428b3 | ||
|
|
f3a52e31d1 | ||
|
|
5e26ceaf02 | ||
|
|
d6fe357fcb | ||
|
|
f9cc52ece9 | ||
|
|
522ca671b5 | ||
|
|
d94af2b8ea |
8
.github/workflows/cicd.yml
vendored
8
.github/workflows/cicd.yml
vendored
@@ -62,7 +62,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Monitor storage space
|
- name: Monitor storage space
|
||||||
run: |
|
run: |
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Monitor storage space
|
- name: Monitor storage space
|
||||||
run: |
|
run: |
|
||||||
@@ -201,7 +201,7 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
@@ -256,7 +256,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Extract tag name
|
- name: Extract tag name
|
||||||
id: get-tag
|
id: get-tag
|
||||||
|
|||||||
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
@@ -62,7 +62,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Build Docker image sqlite
|
- name: Build Docker image sqlite
|
||||||
run: make dev-build-sqlite
|
run: make dev-build-sqlite
|
||||||
@@ -71,7 +71,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Build Docker image pg
|
- name: Build Docker image pg
|
||||||
run: make dev-build-pg
|
run: make dev-build-pg
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { drizzle as DrizzleSqlite } from "drizzle-orm/better-sqlite3";
|
import { drizzle as DrizzleSqlite } from "drizzle-orm/better-sqlite3";
|
||||||
import Database from "better-sqlite3";
|
import Database from "better-sqlite3";
|
||||||
import type BetterSqlite3 from "better-sqlite3";
|
|
||||||
import * as schema from "./schema/schema";
|
import * as schema from "./schema/schema";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
@@ -12,68 +11,31 @@ export const exists = checkFileExists(location);
|
|||||||
|
|
||||||
bootstrapVolume();
|
bootstrapVolume();
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps better-sqlite3 Statement to call `finalize()` immediately after
|
|
||||||
* execution, freeing native sqlite3_stmt memory deterministically instead
|
|
||||||
* of waiting for GC. Fixes steady off-heap growth under load (#2120).
|
|
||||||
* WARNING: Finalizes after first execution — incompatible with drizzle's
|
|
||||||
* reusable .prepare() builders. No such usage exists in this codebase.
|
|
||||||
*/
|
|
||||||
function autoFinalizeStatement(
|
|
||||||
stmt: BetterSqlite3.Statement
|
|
||||||
): BetterSqlite3.Statement {
|
|
||||||
const wrapExec = <T extends (...args: any[]) => any>(fn: T): T => {
|
|
||||||
return function (this: any, ...args: any[]) {
|
|
||||||
try {
|
|
||||||
return fn.apply(this, args);
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
// finalize() exists on the native Statement at runtime but
|
|
||||||
// is missing from @types/better-sqlite3.
|
|
||||||
(stmt as any).finalize();
|
|
||||||
} catch {
|
|
||||||
// Already finalized — harmless
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} as unknown as T;
|
|
||||||
};
|
|
||||||
|
|
||||||
stmt.run = wrapExec(stmt.run);
|
|
||||||
stmt.get = wrapExec(stmt.get);
|
|
||||||
stmt.all = wrapExec(stmt.all);
|
|
||||||
|
|
||||||
return stmt;
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDb() {
|
function createDb() {
|
||||||
const sqlite = new Database(location);
|
const sqlite = new Database(location);
|
||||||
|
|
||||||
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
|
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
|
||||||
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
// Enable WAL mode — allows concurrent readers + single writer, preventing
|
||||||
// contention across subsystems (verifySession, Traefik, audit, ping).
|
// contention across subsystems (verifySession, Traefik, audit, ping).
|
||||||
|
// NOTE: journal_mode persists in the DB file once set; unsetting this
|
||||||
|
// env var does NOT revert an existing WAL database.
|
||||||
sqlite.pragma("journal_mode = WAL");
|
sqlite.pragma("journal_mode = WAL");
|
||||||
// NORMAL sync mode: safe with WAL, reduces write lock hold time.
|
// NORMAL sync mode: safe with WAL, reduces write lock hold time.
|
||||||
sqlite.pragma("synchronous = NORMAL");
|
sqlite.pragma("synchronous = NORMAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait up to 5s on SQLITE_BUSY instead of failing — prevents audit log
|
// No busy_timeout pragma: better-sqlite3 already arms
|
||||||
// retry loops that accumulate memory.
|
// sqlite3_busy_timeout(db, 5000) via its default `timeout` option
|
||||||
sqlite.pragma("busy_timeout = 5000");
|
// (lib/database.js), so an explicit pragma is redundant.
|
||||||
|
|
||||||
// 64 MB page cache (default 2 MB) — reduces I/O round-trips on large
|
// Intentionally NOT setting cache_size or mmap_size: a large page cache plus
|
||||||
// TraefikConfigManager JOINs that block the event loop.
|
// a multi-hundred-MB mmap region inflate RSS and cause page-cache thrashing
|
||||||
sqlite.pragma("cache_size = -65536");
|
// on small (~1 GB) instances. Leave SQLite on its conservative defaults.
|
||||||
|
|
||||||
// 256 MB memory-mapped I/O — OS serves reads from page cache directly,
|
// Intentionally NOT wrapping prepare()/statements: better-sqlite3 finalizes
|
||||||
// reducing event-loop blocking.
|
// sqlite3_stmt in the Statement destructor at GC, and drizzle-orm prepares a
|
||||||
sqlite.pragma("mmap_size = 268435456");
|
// fresh statement per query (no statement cache), so statements cannot
|
||||||
|
// accumulate. better-sqlite3 11.x exposes no Statement.finalize() at all.
|
||||||
// Wrap prepare() so every drizzle-orm statement is auto-finalized after
|
|
||||||
// first use, preventing sqlite3_stmt accumulation between GC cycles.
|
|
||||||
const originalPrepare = sqlite.prepare.bind(sqlite);
|
|
||||||
(sqlite as any).prepare = function autoFinalizePrepare(source: string) {
|
|
||||||
return autoFinalizeStatement(originalPrepare(source));
|
|
||||||
};
|
|
||||||
|
|
||||||
return DrizzleSqlite(sqlite, {
|
return DrizzleSqlite(sqlite, {
|
||||||
schema
|
schema
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
import { FeatureId, getFeatureMeterId } from "./features";
|
import { FeatureId, getFeatureMeterId } from "./features";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import cache from "#dynamic/lib/cache";
|
import { regionalCache as cache } from "#dynamic/lib/cache";
|
||||||
|
|
||||||
export function noop() {
|
export function noop() {
|
||||||
if (build !== "saas") {
|
if (build !== "saas") {
|
||||||
@@ -22,7 +22,6 @@ export function noop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class UsageService {
|
export class UsageService {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (noop()) {
|
if (noop()) {
|
||||||
return;
|
return;
|
||||||
@@ -57,7 +56,10 @@ export class UsageService {
|
|||||||
try {
|
try {
|
||||||
let usage;
|
let usage;
|
||||||
if (transaction) {
|
if (transaction) {
|
||||||
const orgIdToUse = await this.getBillingOrg(orgId, transaction);
|
const orgIdToUse = await this.getBillingOrg(
|
||||||
|
orgId,
|
||||||
|
transaction
|
||||||
|
);
|
||||||
usage = await this.internalAddUsage(
|
usage = await this.internalAddUsage(
|
||||||
orgIdToUse,
|
orgIdToUse,
|
||||||
featureId,
|
featureId,
|
||||||
|
|||||||
@@ -48,18 +48,18 @@ export async function applyBlueprint({
|
|||||||
name,
|
name,
|
||||||
source = "API"
|
source = "API"
|
||||||
}: ApplyBlueprintArgs): Promise<Blueprint> {
|
}: ApplyBlueprintArgs): Promise<Blueprint> {
|
||||||
// Validate the input data
|
let blueprintSucceeded: boolean = false;
|
||||||
|
let blueprintMessage = "";
|
||||||
|
let error: any | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
const validationResult = ConfigSchema.safeParse(configData);
|
const validationResult = ConfigSchema.safeParse(configData);
|
||||||
if (!validationResult.success) {
|
if (!validationResult.success) {
|
||||||
throw new Error(fromError(validationResult.error).toString());
|
throw new Error(fromError(validationResult.error).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: Config = validationResult.data;
|
const config: Config = validationResult.data;
|
||||||
let blueprintSucceeded: boolean = false;
|
|
||||||
let blueprintMessage: string;
|
|
||||||
let error: any | null = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let proxyResourcesResults: PublicResourcesResults = [];
|
let proxyResourcesResults: PublicResourcesResults = [];
|
||||||
let clientResourcesResults: ClientResourcesResults = [];
|
let clientResourcesResults: ClientResourcesResults = [];
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { isValidUrlGlobPattern } from "./validators";
|
import {
|
||||||
|
getResourceRuleValueValidationError,
|
||||||
|
isValidUrlGlobPattern
|
||||||
|
} from "./validators";
|
||||||
import { assertEquals } from "@test/assert";
|
import { assertEquals } from "@test/assert";
|
||||||
|
|
||||||
function runTests() {
|
function runTests() {
|
||||||
@@ -236,6 +239,43 @@ function runTests() {
|
|||||||
"Path with isolated percent sign should be invalid"
|
"Path with isolated percent sign should be invalid"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ASN validation tests
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", "AS15169"),
|
||||||
|
null,
|
||||||
|
"Standard ASN should be valid"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", " As15169 "),
|
||||||
|
null,
|
||||||
|
"Standard ASN should be valid with mixed case and whitespace"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", "ALL"),
|
||||||
|
null,
|
||||||
|
"ALL ASN selector should be valid"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", " all "),
|
||||||
|
null,
|
||||||
|
"ALL ASN selector should be valid with mixed case and whitespace"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", "AS0"),
|
||||||
|
null,
|
||||||
|
"AS0 alias should be valid"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", " as0 "),
|
||||||
|
null,
|
||||||
|
"AS0 alias should be valid with mixed case and whitespace"
|
||||||
|
);
|
||||||
|
assertEquals(
|
||||||
|
getResourceRuleValueValidationError("ASN", "not-an-asn"),
|
||||||
|
"Invalid ASN provided",
|
||||||
|
"Invalid ASN should return an error"
|
||||||
|
);
|
||||||
|
|
||||||
console.log("All tests passed!");
|
console.log("All tests passed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ export function getResourceRuleValueValidationError(
|
|||||||
? null
|
? null
|
||||||
: "Invalid country code provided";
|
: "Invalid country code provided";
|
||||||
case "ASN":
|
case "ASN":
|
||||||
return /^AS\d+$/i.test(value.trim())
|
const normalizedValue = value.trim().toUpperCase();
|
||||||
|
return /^AS\d+$/.test(normalizedValue) ||
|
||||||
|
normalizedValue === "ALL" ||
|
||||||
|
normalizedValue === "AS0"
|
||||||
? null
|
? null
|
||||||
: "Invalid ASN provided";
|
: "Invalid ASN provided";
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { certificates, db } from "@server/db";
|
|||||||
import { and, eq, isNotNull, or, inArray, sql } from "drizzle-orm";
|
import { and, eq, isNotNull, or, inArray, sql } from "drizzle-orm";
|
||||||
import { decrypt } from "@server/lib/crypto";
|
import { decrypt } from "@server/lib/crypto";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import cache from "#private/lib/cache";
|
import { regionalCache as cache } from "#private/lib/cache";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
|
|
||||||
// Define the return type for clarity and type safety
|
// Define the return type for clarity and type safety
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ 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 { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
||||||
import cache from "#private/lib/cache";
|
import { regionalCache as cache } from "#private/lib/cache";
|
||||||
import semver from "semver";
|
import semver from "semver";
|
||||||
|
|
||||||
let stalePangolinNodeVersion: string | null = null;
|
let stalePangolinNodeVersion: string | null = null;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { verifyPassword } from "@server/auth/password";
|
|||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import cache from "#dynamic/lib/cache";
|
import { regionalCache as cache } from "#dynamic/lib/cache";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
|
||||||
// Stale-while-revalidate in-memory fallback for the releases API.
|
// Stale-while-revalidate in-memory fallback for the releases API.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { MessageHandler } from "@server/routers/ws";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { Newt } from "@server/db";
|
import { Newt } from "@server/db";
|
||||||
import { applyNewtDockerBlueprint } from "@server/lib/blueprints/applyNewtDockerBlueprint";
|
import { applyNewtDockerBlueprint } from "@server/lib/blueprints/applyNewtDockerBlueprint";
|
||||||
import cache from "#dynamic/lib/cache";
|
import cache from "#dynamic/lib/cache"; // not using regional here because we dont know where the site is
|
||||||
|
|
||||||
export const handleDockerStatusMessage: MessageHandler = async (context) => {
|
export const handleDockerStatusMessage: MessageHandler = async (context) => {
|
||||||
const { message, client, sendToClient } = context;
|
const { message, client, sendToClient } = context;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { handleFingerprintInsertion } from "./fingerprintingUtils";
|
|||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { canCompress } from "@server/lib/clientVersionChecks";
|
import { canCompress } from "@server/lib/clientVersionChecks";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import cache from "#dynamic/lib/cache";
|
import cache from "#dynamic/lib/cache"; // not using regional here because we need this in the register message handler before we know where the client is
|
||||||
|
|
||||||
const HOLEPUNCH_STALE_CHAIN_THRESHOLD = 18;
|
const HOLEPUNCH_STALE_CHAIN_THRESHOLD = 18;
|
||||||
const HOLEPUNCH_STALE_CHAIN_TTL_SECONDS = 1800;
|
const HOLEPUNCH_STALE_CHAIN_TTL_SECONDS = 1800;
|
||||||
|
|||||||
@@ -15,8 +15,7 @@ import logger from "@server/logger";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromZodError } from "zod-validation-error";
|
import { fromZodError } from "zod-validation-error";
|
||||||
import type { PaginatedResponse } from "@server/types/Pagination";
|
import type { PaginatedResponse } from "@server/types/Pagination";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { regionalCache as cache } from "#dynamic/lib/cache";
|
||||||
import { localCache } from "#dynamic/lib/cache";
|
|
||||||
|
|
||||||
const USER_RESOURCE_ALIASES_CACHE_TTL_SEC = 60;
|
const USER_RESOURCE_ALIASES_CACHE_TTL_SEC = 60;
|
||||||
|
|
||||||
@@ -153,7 +152,7 @@ export async function listUserResourceAliases(
|
|||||||
pageSize
|
pageSize
|
||||||
);
|
);
|
||||||
const cachedData: ListUserResourceAliasesResponse | undefined =
|
const cachedData: ListUserResourceAliasesResponse | undefined =
|
||||||
localCache.get(cacheKey);
|
await cache.get(cacheKey);
|
||||||
|
|
||||||
if (cachedData) {
|
if (cachedData) {
|
||||||
return response<ListUserResourceAliasesResponse>(res, {
|
return response<ListUserResourceAliasesResponse>(res, {
|
||||||
@@ -211,7 +210,11 @@ export async function listUserResourceAliases(
|
|||||||
page
|
page
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
localCache.set(cacheKey, data, USER_RESOURCE_ALIASES_CACHE_TTL_SEC);
|
await cache.set(
|
||||||
|
cacheKey,
|
||||||
|
data,
|
||||||
|
USER_RESOURCE_ALIASES_CACHE_TTL_SEC
|
||||||
|
);
|
||||||
return response<ListUserResourceAliasesResponse>(res, {
|
return response<ListUserResourceAliasesResponse>(res, {
|
||||||
data,
|
data,
|
||||||
success: true,
|
success: true,
|
||||||
@@ -256,7 +259,7 @@ export async function listUserResourceAliases(
|
|||||||
page
|
page
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
localCache.set(cacheKey, data, USER_RESOURCE_ALIASES_CACHE_TTL_SEC);
|
await cache.set(cacheKey, data, USER_RESOURCE_ALIASES_CACHE_TTL_SEC);
|
||||||
|
|
||||||
return response<ListUserResourceAliasesResponse>(res, {
|
return response<ListUserResourceAliasesResponse>(res, {
|
||||||
data,
|
data,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
siteLabels,
|
siteLabels,
|
||||||
type Label
|
type Label
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import cache from "#dynamic/lib/cache";
|
import { regionalCache as cache } from "#dynamic/lib/cache";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ Restart=always
|
|||||||
RestartSec=2
|
RestartSec=2
|
||||||
UMask=0077
|
UMask=0077
|
||||||
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -83,9 +83,19 @@ export function createPolicyRuleValueSchema(t: TranslateFn, match: string) {
|
|||||||
{ message: t("rulesErrorInvalidCountryDescription") }
|
{ message: t("rulesErrorInvalidCountryDescription") }
|
||||||
);
|
);
|
||||||
case "ASN":
|
case "ASN":
|
||||||
return required.refine((value) => /^AS\d+$/i.test(value.trim()), {
|
return required.refine(
|
||||||
|
(value) => {
|
||||||
|
const normalizedValue = value.trim().toUpperCase();
|
||||||
|
return (
|
||||||
|
/^AS\d+$/.test(normalizedValue) ||
|
||||||
|
normalizedValue === "ALL" ||
|
||||||
|
normalizedValue === "AS0"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{
|
||||||
message: t("rulesErrorInvalidAsnDescription")
|
message: t("rulesErrorInvalidAsnDescription")
|
||||||
});
|
}
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return required;
|
return required;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user