mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-18 23:05:21 +00:00
Merge branch 'dev' of https://github.com/fosrl/pangolin into dev
This commit is contained in:
71
package-lock.json
generated
71
package-lock.json
generated
@@ -44,7 +44,6 @@
|
|||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.90.21",
|
"@tanstack/react-query": "5.90.21",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"@xyflow/react": "^12.8.4",
|
|
||||||
"arctic": "3.7.0",
|
"arctic": "3.7.0",
|
||||||
"axios": "1.13.5",
|
"axios": "1.13.5",
|
||||||
"better-sqlite3": "11.9.1",
|
"better-sqlite3": "11.9.1",
|
||||||
@@ -8719,6 +8718,7 @@
|
|||||||
"version": "3.0.7",
|
"version": "3.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz",
|
||||||
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
"integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/d3-selection": "*"
|
"@types/d3-selection": "*"
|
||||||
@@ -8834,6 +8834,7 @@
|
|||||||
"version": "3.0.11",
|
"version": "3.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz",
|
||||||
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
"integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/d3-shape": {
|
"node_modules/@types/d3-shape": {
|
||||||
@@ -8868,6 +8869,7 @@
|
|||||||
"version": "3.0.9",
|
"version": "3.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz",
|
||||||
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
"integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/d3-selection": "*"
|
"@types/d3-selection": "*"
|
||||||
@@ -8877,6 +8879,7 @@
|
|||||||
"version": "3.0.8",
|
"version": "3.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz",
|
||||||
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
"integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/d3-interpolate": "*",
|
"@types/d3-interpolate": "*",
|
||||||
@@ -9680,38 +9683,6 @@
|
|||||||
"win32"
|
"win32"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@xyflow/react": {
|
|
||||||
"version": "12.8.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.8.4.tgz",
|
|
||||||
"integrity": "sha512-bqUu4T5QSHiCFPkoH+b+LROKwQJdLvcjhGbNW9c1dLafCBRjmH1IYz0zPE+lRDXCtQ9kRyFxz3tG19+8VORJ1w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@xyflow/system": "0.0.68",
|
|
||||||
"classcat": "^5.0.3",
|
|
||||||
"zustand": "^4.4.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=17",
|
|
||||||
"react-dom": ">=17"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@xyflow/system": {
|
|
||||||
"version": "0.0.68",
|
|
||||||
"resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.68.tgz",
|
|
||||||
"integrity": "sha512-QDG2wxIG4qX+uF8yzm1ULVZrcXX3MxPBoxv7O52FWsX87qIImOqifUhfa/TwsvLdzn7ic2DDBH1uI8TKbdNTYA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/d3-drag": "^3.0.7",
|
|
||||||
"@types/d3-interpolate": "^3.0.4",
|
|
||||||
"@types/d3-selection": "^3.0.10",
|
|
||||||
"@types/d3-transition": "^3.0.8",
|
|
||||||
"@types/d3-zoom": "^3.0.8",
|
|
||||||
"d3-drag": "^3.0.0",
|
|
||||||
"d3-interpolate": "^3.0.1",
|
|
||||||
"d3-selection": "^3.0.0",
|
|
||||||
"d3-zoom": "^3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/accepts": {
|
"node_modules/accepts": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
|
||||||
@@ -10564,12 +10535,6 @@
|
|||||||
"url": "https://polar.sh/cva"
|
"url": "https://polar.sh/cva"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/classcat": {
|
|
||||||
"version": "5.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
|
||||||
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/cli-spinners": {
|
"node_modules/cli-spinners": {
|
||||||
"version": "2.9.2",
|
"version": "2.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
|
||||||
@@ -19881,34 +19846,6 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"zod": "^3.25.0 || ^4.0.0"
|
"zod": "^3.25.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"node_modules/zustand": {
|
|
||||||
"version": "4.5.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
|
|
||||||
"integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"use-sync-external-store": "^1.2.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12.7.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": ">=16.8",
|
|
||||||
"immer": ">=9.0.6",
|
|
||||||
"react": ">=16.8"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"@types/react": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"immer": {
|
|
||||||
"optional": true
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export async function getValidCertificatesForDomains(
|
export async function getValidCertificatesForDomains(
|
||||||
domains: Set<string>
|
domains: Set<string>,
|
||||||
|
useCache: boolean
|
||||||
): Promise<
|
): Promise<
|
||||||
Array<{
|
Array<{
|
||||||
id: number;
|
id: number;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of a proprietary work.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025-2026 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 { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, statusHistory } from "@server/db";
|
import { db, statusHistory } from "@server/db";
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ 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 { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { and, eq, like, sql } from "drizzle-orm";
|
import { and, eq, isNotNull, like, sql } from "drizzle-orm";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
@@ -85,6 +85,7 @@ export async function listHealthChecks(
|
|||||||
|
|
||||||
const whereClause = and(
|
const whereClause = and(
|
||||||
eq(targetHealthCheck.orgId, orgId),
|
eq(targetHealthCheck.orgId, orgId),
|
||||||
|
isNotNull(targetHealthCheck.hcMode), // filter out the null ones attached to targets
|
||||||
query
|
query
|
||||||
? like(
|
? like(
|
||||||
sql`LOWER(${targetHealthCheck.name})`,
|
sql`LOWER(${targetHealthCheck.name})`,
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ export default function HealthChecksTable({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
disabled={!isPaid}
|
disabled={!isPaid || !!r.resourceId}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelected(r);
|
setSelected(r);
|
||||||
setDeleteOpen(true);
|
setDeleteOpen(true);
|
||||||
@@ -339,18 +339,31 @@ export default function HealthChecksTable({
|
|||||||
{t("delete")}
|
{t("delete")}
|
||||||
</span>
|
</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Button
|
{r.resourceId && r.resourceName && r.resourceNiceId ? (
|
||||||
variant="outline"
|
<Link href={`/${orgId}/settings/resources/proxy/${r.resourceNiceId}`}>
|
||||||
disabled={!isPaid}
|
<Button
|
||||||
onClick={() => {
|
variant="outline"
|
||||||
setSelected(r);
|
disabled={!isPaid}
|
||||||
setCredenzaOpen(true);
|
>
|
||||||
}}
|
{t("edit")}
|
||||||
>
|
</Button>
|
||||||
{t("edit")}
|
</Link>
|
||||||
</Button>
|
) : (
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
disabled={!isPaid}
|
||||||
|
onClick={() => {
|
||||||
|
setSelected(r);
|
||||||
|
setCredenzaOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("edit")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import { createApiClient, formatAxiosError } from "@app/lib/api";
|
|||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { toast } from "@app/hooks/useToast";
|
import { toast } from "@app/hooks/useToast";
|
||||||
import { orgQueries } from "@app/lib/queries";
|
import { orgQueries } from "@app/lib/queries";
|
||||||
|
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
|
||||||
interface UptimeAlertSectionProps {
|
interface UptimeAlertSectionProps {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
@@ -49,6 +52,8 @@ export default function UptimeAlertSection({
|
|||||||
}: UptimeAlertSectionProps) {
|
}: UptimeAlertSectionProps) {
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
const isPaid = isPaidUser(tierMatrix.alertingRules);
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [name, setName] = useState(
|
const [name, setName] = useState(
|
||||||
@@ -219,82 +224,90 @@ export default function UptimeAlertSection({
|
|||||||
</CredenzaHeader>
|
</CredenzaHeader>
|
||||||
<CredenzaBody>
|
<CredenzaBody>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<PaidFeaturesAlert tiers={tierMatrix.alertingRules} />
|
||||||
<Label htmlFor="alert-name">Name</Label>
|
<fieldset
|
||||||
<Input
|
disabled={!isPaid}
|
||||||
id="alert-name"
|
className={!isPaid ? "opacity-50 pointer-events-none" : ""}
|
||||||
value={name}
|
>
|
||||||
onChange={(e) => setName(e.target.value)}
|
<div className="space-y-4">
|
||||||
placeholder="Alert name"
|
<div className="space-y-2">
|
||||||
/>
|
<Label htmlFor="alert-name">Name</Label>
|
||||||
</div>
|
<Input
|
||||||
<div className="space-y-2">
|
id="alert-name"
|
||||||
<Label>Notify Users</Label>
|
value={name}
|
||||||
<TagInput
|
onChange={(e) => setName(e.target.value)}
|
||||||
activeTagIndex={activeUserTagIndex}
|
placeholder="Alert name"
|
||||||
setActiveTagIndex={setActiveUserTagIndex}
|
/>
|
||||||
placeholder="Select users..."
|
</div>
|
||||||
size="sm"
|
<div className="space-y-2">
|
||||||
tags={userTags}
|
<Label>Notify Users</Label>
|
||||||
setTags={(newTags) => {
|
<TagInput
|
||||||
const next =
|
activeTagIndex={activeUserTagIndex}
|
||||||
typeof newTags === "function"
|
setActiveTagIndex={setActiveUserTagIndex}
|
||||||
? newTags(userTags)
|
placeholder="Select users..."
|
||||||
: newTags;
|
size="sm"
|
||||||
setUserTags(next as Tag[]);
|
tags={userTags}
|
||||||
}}
|
setTags={(newTags) => {
|
||||||
enableAutocomplete
|
const next =
|
||||||
autocompleteOptions={allUsers}
|
typeof newTags === "function"
|
||||||
restrictTagsToAutocompleteOptions
|
? newTags(userTags)
|
||||||
allowDuplicates={false}
|
: newTags;
|
||||||
sortTags
|
setUserTags(next as Tag[]);
|
||||||
/>
|
}}
|
||||||
</div>
|
enableAutocomplete
|
||||||
<div className="space-y-2">
|
autocompleteOptions={allUsers}
|
||||||
<Label>Notify Roles</Label>
|
restrictTagsToAutocompleteOptions
|
||||||
<TagInput
|
allowDuplicates={false}
|
||||||
activeTagIndex={activeRoleTagIndex}
|
sortTags
|
||||||
setActiveTagIndex={setActiveRoleTagIndex}
|
/>
|
||||||
placeholder="Select roles..."
|
</div>
|
||||||
size="sm"
|
<div className="space-y-2">
|
||||||
tags={roleTags}
|
<Label>Notify Roles</Label>
|
||||||
setTags={(newTags) => {
|
<TagInput
|
||||||
const next =
|
activeTagIndex={activeRoleTagIndex}
|
||||||
typeof newTags === "function"
|
setActiveTagIndex={setActiveRoleTagIndex}
|
||||||
? newTags(roleTags)
|
placeholder="Select roles..."
|
||||||
: newTags;
|
size="sm"
|
||||||
setRoleTags(next as Tag[]);
|
tags={roleTags}
|
||||||
}}
|
setTags={(newTags) => {
|
||||||
enableAutocomplete
|
const next =
|
||||||
autocompleteOptions={allRoles}
|
typeof newTags === "function"
|
||||||
restrictTagsToAutocompleteOptions
|
? newTags(roleTags)
|
||||||
allowDuplicates={false}
|
: newTags;
|
||||||
sortTags
|
setRoleTags(next as Tag[]);
|
||||||
/>
|
}}
|
||||||
</div>
|
enableAutocomplete
|
||||||
<div className="space-y-2">
|
autocompleteOptions={allRoles}
|
||||||
<Label>Additional Emails</Label>
|
restrictTagsToAutocompleteOptions
|
||||||
<TagInput
|
allowDuplicates={false}
|
||||||
activeTagIndex={activeEmailTagIndex}
|
sortTags
|
||||||
setActiveTagIndex={setActiveEmailTagIndex}
|
/>
|
||||||
placeholder="Enter email addresses..."
|
</div>
|
||||||
size="sm"
|
<div className="space-y-2">
|
||||||
tags={emailTags}
|
<Label>Additional Emails</Label>
|
||||||
setTags={(newTags) => {
|
<TagInput
|
||||||
const next =
|
activeTagIndex={activeEmailTagIndex}
|
||||||
typeof newTags === "function"
|
setActiveTagIndex={setActiveEmailTagIndex}
|
||||||
? newTags(emailTags)
|
placeholder="Enter email addresses..."
|
||||||
: newTags;
|
size="sm"
|
||||||
setEmailTags(next as Tag[]);
|
tags={emailTags}
|
||||||
}}
|
setTags={(newTags) => {
|
||||||
allowDuplicates={false}
|
const next =
|
||||||
sortTags
|
typeof newTags === "function"
|
||||||
validateTag={(tag) =>
|
? newTags(emailTags)
|
||||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(tag)
|
: newTags;
|
||||||
}
|
setEmailTags(next as Tag[]);
|
||||||
delimiterList={[",", "Enter"]}
|
}}
|
||||||
/>
|
allowDuplicates={false}
|
||||||
</div>
|
sortTags
|
||||||
|
validateTag={(tag) =>
|
||||||
|
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(tag)
|
||||||
|
}
|
||||||
|
delimiterList={[",", "Enter"]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
<CredenzaFooter>
|
<CredenzaFooter>
|
||||||
@@ -304,7 +317,7 @@ export default function UptimeAlertSection({
|
|||||||
<Button
|
<Button
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading || !isPaid}
|
||||||
>
|
>
|
||||||
Create Alert
|
Create Alert
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in New Issue
Block a user