mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-27 11:12:55 +00:00
Control updates from the ui
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { db } from "@server/db";
|
||||
import { db, orgs, sites } from "@server/db";
|
||||
import { newts } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { NextFunction, Request, Response } from "express";
|
||||
@@ -148,12 +148,13 @@ export async function getNewtVersion(
|
||||
|
||||
try {
|
||||
// Verify newt credentials
|
||||
const existingNewtRes = await db
|
||||
const [existingNewt] = await db
|
||||
.select()
|
||||
.from(newts)
|
||||
.where(eq(newts.newtId, newtId));
|
||||
.where(eq(newts.newtId, newtId))
|
||||
.limit(1);
|
||||
|
||||
if (!existingNewtRes || !existingNewtRes.length) {
|
||||
if (!existingNewt) {
|
||||
if (config.getRawConfig().app.log_failed_attempts) {
|
||||
logger.info(
|
||||
`Newt version check: no newt found with ID ${newtId}. IP: ${req.ip}.`
|
||||
@@ -164,7 +165,15 @@ export async function getNewtVersion(
|
||||
);
|
||||
}
|
||||
|
||||
const existingNewt = existingNewtRes[0];
|
||||
if (!existingNewt.siteId) {
|
||||
logger.warn(`Newt ${newtId} has no associated site`);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.UNAUTHORIZED,
|
||||
"Not associated with a site"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const validSecret = await verifyPassword(
|
||||
secret,
|
||||
@@ -181,6 +190,64 @@ export async function getNewtVersion(
|
||||
);
|
||||
}
|
||||
|
||||
// check if udpates are enabled for the org or the site
|
||||
const [site] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, existingNewt.siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!site) {
|
||||
logger.warn(`Site with ID ${existingNewt.siteId} not found`);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Associated site not found"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const [org] = await db
|
||||
.select()
|
||||
.from(orgs)
|
||||
.where(eq(orgs.orgId, site.orgId))
|
||||
.limit(1);
|
||||
|
||||
if (!org) {
|
||||
logger.warn(`Org with ID ${site.orgId} not found`);
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Associated organization not found"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
let doUpdate = false;
|
||||
|
||||
if (site.autoUpdateOverrideOrg) {
|
||||
doUpdate = site.autoUpdateEnabled;
|
||||
} else {
|
||||
doUpdate = org.settingsEnableGlobalNewtAutoUpdate;
|
||||
}
|
||||
|
||||
if (!doUpdate) {
|
||||
// return no content http code
|
||||
return response(res, {
|
||||
data: {
|
||||
latestVersion: existingNewt.version ?? "",
|
||||
currentIsLatest: true,
|
||||
downloadUrl: "",
|
||||
sha256: ""
|
||||
},
|
||||
success: true,
|
||||
error: false,
|
||||
message:
|
||||
"Auto-updates are disabled for this site and organization",
|
||||
status: HttpCode.NO_CONTENT
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch latest release info (version + asset digests) in one API call.
|
||||
const releaseInfo = await getLatestReleaseInfo();
|
||||
|
||||
|
||||
@@ -40,7 +40,8 @@ const updateOrgBodySchema = z
|
||||
settingsLogRetentionDaysConnection: z
|
||||
.number()
|
||||
.min(build === "saas" ? 0 : -1)
|
||||
.optional()
|
||||
.optional(),
|
||||
settingsEnableGlobalNewtAutoUpdate: z.boolean().optional()
|
||||
})
|
||||
.refine((data) => Object.keys(data).length > 0, {
|
||||
error: "At least one field must be provided for update"
|
||||
@@ -118,6 +119,15 @@ export async function updateOrg(
|
||||
if (!hasPasswordExpirationFeature) {
|
||||
parsedBody.data.passwordExpiryDays = undefined;
|
||||
}
|
||||
|
||||
const hasNewtAutoUpdateFeature = await isLicensedOrSubscribed(
|
||||
orgId,
|
||||
tierMatrix[TierFeature.NewtAutoUpdate]
|
||||
);
|
||||
if (!hasNewtAutoUpdateFeature) {
|
||||
parsedBody.data.settingsEnableGlobalNewtAutoUpdate = false; // force it off
|
||||
}
|
||||
|
||||
if (build == "saas") {
|
||||
const { tier } = await getOrgTierData(orgId);
|
||||
|
||||
@@ -136,8 +146,10 @@ export async function updateOrg(
|
||||
|
||||
if (maxRetentionDays !== null) {
|
||||
if (
|
||||
parsedBody.data.settingsLogRetentionDaysRequest !== undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysRequest > maxRetentionDays
|
||||
parsedBody.data.settingsLogRetentionDaysRequest !==
|
||||
undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysRequest >
|
||||
maxRetentionDays
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -147,8 +159,10 @@ export async function updateOrg(
|
||||
);
|
||||
}
|
||||
if (
|
||||
parsedBody.data.settingsLogRetentionDaysAccess !== undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysAccess > maxRetentionDays
|
||||
parsedBody.data.settingsLogRetentionDaysAccess !==
|
||||
undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysAccess >
|
||||
maxRetentionDays
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -158,8 +172,10 @@ export async function updateOrg(
|
||||
);
|
||||
}
|
||||
if (
|
||||
parsedBody.data.settingsLogRetentionDaysAction !== undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysAction > maxRetentionDays
|
||||
parsedBody.data.settingsLogRetentionDaysAction !==
|
||||
undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysAction >
|
||||
maxRetentionDays
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -169,8 +185,10 @@ export async function updateOrg(
|
||||
);
|
||||
}
|
||||
if (
|
||||
parsedBody.data.settingsLogRetentionDaysConnection !== undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysConnection > maxRetentionDays
|
||||
parsedBody.data.settingsLogRetentionDaysConnection !==
|
||||
undefined &&
|
||||
parsedBody.data.settingsLogRetentionDaysConnection >
|
||||
maxRetentionDays
|
||||
) {
|
||||
return next(
|
||||
createHttpError(
|
||||
@@ -196,7 +214,9 @@ export async function updateOrg(
|
||||
settingsLogRetentionDaysAction:
|
||||
parsedBody.data.settingsLogRetentionDaysAction,
|
||||
settingsLogRetentionDaysConnection:
|
||||
parsedBody.data.settingsLogRetentionDaysConnection
|
||||
parsedBody.data.settingsLogRetentionDaysConnection,
|
||||
settingsEnableGlobalNewtAutoUpdate:
|
||||
parsedBody.data.settingsEnableGlobalNewtAutoUpdate
|
||||
})
|
||||
.where(eq(orgs.orgId, orgId))
|
||||
.returning();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { db, Site } from "@server/db";
|
||||
import { sites } from "@server/db";
|
||||
import { eq, and, ne } from "drizzle-orm";
|
||||
import response from "@server/lib/response";
|
||||
@@ -9,7 +9,8 @@ import createHttpError from "http-errors";
|
||||
import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { OpenAPITags, registry } from "@server/openApi";
|
||||
import { isValidCIDR } from "@server/lib/validators";
|
||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||
import { tierMatrix, TierFeature } from "@server/lib/billing/tierMatrix";
|
||||
|
||||
const updateSiteParamsSchema = z.strictObject({
|
||||
siteId: z.string().transform(Number).pipe(z.int().positive())
|
||||
@@ -21,18 +22,8 @@ const updateSiteBodySchema = z
|
||||
niceId: z.string().min(1).max(255).optional(),
|
||||
dockerSocketEnabled: z.boolean().optional(),
|
||||
status: z.enum(["pending", "approved"]).optional(),
|
||||
// remoteSubnets: z.string().optional()
|
||||
// subdomain: z
|
||||
// .string()
|
||||
// .min(1)
|
||||
// .max(255)
|
||||
// .transform((val) => val.toLowerCase())
|
||||
// .optional()
|
||||
// pubKey: z.string().optional(),
|
||||
// subnet: z.string().optional(),
|
||||
// exitNode: z.number().int().positive().optional(),
|
||||
// megabytesIn: z.number().int().nonnegative().optional(),
|
||||
// megabytesOut: z.number().int().nonnegative().optional(),
|
||||
autoUpdateEnabled: z.boolean().optional(),
|
||||
autoUpdateOverrideOrg: z.boolean().optional()
|
||||
})
|
||||
.refine((data) => Object.keys(data).length > 0, {
|
||||
error: "At least one field must be provided for update"
|
||||
@@ -85,9 +76,24 @@ export async function updateSite(
|
||||
const { siteId } = parsedParams.data;
|
||||
const updateData = parsedBody.data;
|
||||
|
||||
const [existingSite] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(eq(sites.siteId, siteId))
|
||||
.limit(1);
|
||||
|
||||
if (!existingSite) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Site with ID ${siteId} not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// if niceId is provided, check if it's already in use by another site
|
||||
if (updateData.niceId) {
|
||||
const [existingSite] = await db
|
||||
const [existingSiteNiceIdOverlap] = await db
|
||||
.select()
|
||||
.from(sites)
|
||||
.where(
|
||||
@@ -99,7 +105,7 @@ export async function updateSite(
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existingSite) {
|
||||
if (existingSiteNiceIdOverlap) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.CONFLICT,
|
||||
@@ -109,6 +115,15 @@ export async function updateSite(
|
||||
}
|
||||
}
|
||||
|
||||
const hasNewtAutoUpdateFeature = await isLicensedOrSubscribed(
|
||||
existingSite.orgId,
|
||||
tierMatrix[TierFeature.NewtAutoUpdate]
|
||||
);
|
||||
if (!hasNewtAutoUpdateFeature) {
|
||||
parsedBody.data.autoUpdateEnabled = false; // force it off
|
||||
parsedBody.data.autoUpdateOverrideOrg = false; // force it off
|
||||
}
|
||||
|
||||
// // if remoteSubnets is provided, ensure it's a valid comma-separated list of cidrs
|
||||
// if (updateData.remoteSubnets) {
|
||||
// const subnets = updateData.remoteSubnets
|
||||
|
||||
Reference in New Issue
Block a user