Merge branch 'dev' into refactor/batch-status-requests

This commit is contained in:
Fred KISSIE
2026-07-20 18:38:00 +01:00
202 changed files with 6263 additions and 884 deletions
+7 -2
View File
@@ -4,9 +4,13 @@ import { getCachedSubscription } from "./getCachedSubscription";
import { priv } from ".";
import { AxiosResponse } from "axios";
import { GetLicenseStatusResponse } from "@server/routers/license/types";
import { Tier } from "@server/types/Tiers";
export const isOrgSubscribed = cache(async (orgId: string) => {
const DEFAULT_PAID_TIERS: Tier[] = ["tier1", "tier2", "tier3", "enterprise"];
export const isOrgSubscribed = cache(async (orgId: string, tiers?: Tier[]) => {
let subscribed = false;
const allowedTiers = tiers ?? DEFAULT_PAID_TIERS;
if (build === "enterprise") {
try {
@@ -20,7 +24,8 @@ export const isOrgSubscribed = cache(async (orgId: string) => {
try {
const subRes = await getCachedSubscription(orgId);
subscribed =
(subRes.data.data.tier == "tier1" || subRes.data.data.tier == "tier2" || subRes.data.data.tier == "tier3" || subRes.data.data.tier == "enterprise") &&
!!subRes.data.data.tier &&
allowedTiers.includes(subRes.data.data.tier as Tier) &&
subRes.data.data.active;
} catch {}
}
+1
View File
@@ -0,0 +1 @@
export const LAST_USED_IDP_COOKIE_NAME = "p__last_used_idp";
+24
View File
@@ -0,0 +1,24 @@
import type {
Control,
FieldValues,
UseFormSetValue,
UseFormWatch
} from "react-hook-form";
export function asAnyControl<T extends FieldValues>(
control: Control<T>
): Control<any> {
return control as Control<any>;
}
export function asAnySetValue<T extends FieldValues>(
setValue: UseFormSetValue<T>
): UseFormSetValue<any> {
return setValue as UseFormSetValue<any>;
}
export function asAnyWatch<T extends FieldValues>(
watch: UseFormWatch<T>
): UseFormWatch<any> {
return watch as UseFormWatch<any>;
}
+5 -1
View File
@@ -1,6 +1,7 @@
import { priv } from "@app/lib/api";
import { isOrgSubscribed } from "@app/lib/api/isOrgSubscribed";
import { build } from "@server/build";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { LoadLoginPageBrandingResponse } from "@server/routers/loginPage/types";
import { AxiosResponse } from "axios";
@@ -11,7 +12,10 @@ export async function loadOrgLoginPageBranding(orgId: string): Promise<{
return { primaryColor: null };
}
const subscribed = await isOrgSubscribed(orgId);
const subscribed = await isOrgSubscribed(
orgId,
tierMatrix.loginPageBranding
);
if (!subscribed) {
return { primaryColor: null };
}
+2 -2
View File
@@ -165,7 +165,6 @@ export function buildCreateSiteResourcePayload(
siteIds: data.siteIds,
mode: data.mode,
destination: isNativeSsh ? undefined : (data.destination ?? undefined),
enabled: true,
...(data.mode === "http" && {
scheme: data.scheme,
ssl: data.ssl ?? false,
@@ -342,7 +341,8 @@ export function createGeneralFormSchema(t: TranslateFn) {
.string()
.min(1)
.max(255)
.regex(/^[a-zA-Z0-9-]+$/)
.regex(/^[a-zA-Z0-9-]+$/),
enabled: z.boolean()
});
}
+36
View File
@@ -0,0 +1,36 @@
"use client";
import type { Selectedsite } from "@app/components/site-selector";
import type { SiteResourceData } from "@app/lib/privateResourceForm";
export function buildSelectedSitesForResource(
resource: Pick<SiteResourceData, "siteIds" | "siteNames">
): Selectedsite[] {
return resource.siteIds.map((siteId, idx) => ({
name: resource.siteNames[idx] ?? "",
siteId,
type: "newt" as const
}));
}
export function getSshSingleSiteMode(
authDaemonMode?: string | null,
pamMode?: string | null
): boolean {
return (
authDaemonMode === "native" ||
(pamMode === "push" && authDaemonMode === "site")
);
}
export function getSshUseMultiSiteTargetForm(
isNative: boolean,
authDaemonMode?: string | null,
pamMode?: string | null
): boolean {
if (isNative) {
return false;
}
return authDaemonMode !== "site" || pamMode === "passthrough";
}
+12 -5
View File
@@ -672,9 +672,13 @@ export const orgQueries = {
queryOptions({
queryKey: ["SITE_STATUS_HISTORY", siteId, days] as const,
queryFn: async ({ signal, meta }) => {
const tzOffsetMinutes = -new Date().getTimezoneOffset();
const res = await meta!.api.get<
AxiosResponse<StatusHistoryResponse>
>(`/site/${siteId}/status-history?days=${days}`, { signal });
>(
`/site/${siteId}/status-history?days=${days}&tzOffsetMinutes=${tzOffsetMinutes}`,
{ signal }
);
return res.data.data;
}
}),
@@ -689,11 +693,13 @@ export const orgQueries = {
queryOptions({
queryKey: ["RESOURCE_STATUS_HISTORY", resourceId, days] as const,
queryFn: async ({ signal, meta }) => {
const tzOffsetMinutes = -new Date().getTimezoneOffset();
const res = await meta!.api.get<
AxiosResponse<StatusHistoryResponse>
>(`/resource/${resourceId}/status-history?days=${days}`, {
signal
});
>(
`/resource/${resourceId}/status-history?days=${days}&tzOffsetMinutes=${tzOffsetMinutes}`,
{ signal }
);
return res.data.data;
}
}),
@@ -715,10 +721,11 @@ export const orgQueries = {
days
] as const,
queryFn: async ({ signal, meta }) => {
const tzOffsetMinutes = -new Date().getTimezoneOffset();
const res = await meta!.api.get<
AxiosResponse<StatusHistoryResponse>
>(
`/org/${orgId}/health-check/${healthCheckId}/status-history?days=${days}`,
`/org/${orgId}/health-check/${healthCheckId}/status-history?days=${days}&tzOffsetMinutes=${tzOffsetMinutes}`,
{ signal }
);
return res.data.data;
+32
View File
@@ -0,0 +1,32 @@
/**
* Set a cookie on the client side in javascript code, not on the server
* @param name
* @param value
* @param days
* @param options
*/
export function setClientCookie(
name: string,
value: string,
options: {
days?: number;
path?: string;
secure?: boolean;
sameSite?: "Strict" | "Lax" | "None";
} = {}
): void {
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
if (options.days) {
const date = new Date();
date.setTime(date.getTime() + options.days * 864e5);
cookie += `; expires=${date.toUTCString()}`;
}
cookie += `; path=${options.path ?? "/"}`;
if (options.secure) cookie += "; Secure";
if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
document.cookie = cookie;
}