mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-22 08:45:24 +00:00
Sure up some things with browserAccessType
This commit is contained in:
@@ -159,7 +159,8 @@ export const resources = pgTable("resources", {
|
|||||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
||||||
postAuthPath: text("postAuthPath"),
|
postAuthPath: text("postAuthPath"),
|
||||||
health: varchar("health").default("unknown"), // "healthy", "unhealthy", "unknown"
|
health: varchar("health").default("unknown"), // "healthy", "unhealthy", "unknown"
|
||||||
wildcard: boolean("wildcard").notNull().default(false)
|
wildcard: boolean("wildcard").notNull().default(false),
|
||||||
|
browserAccessType: text("browserAccessType").default("http") // rdp, ssh, http, vnc
|
||||||
});
|
});
|
||||||
|
|
||||||
export const targets = pgTable("targets", {
|
export const targets = pgTable("targets", {
|
||||||
@@ -196,9 +197,11 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
siteId: integer("siteId").references(() => sites.siteId, {
|
siteId: integer("siteId")
|
||||||
|
.references(() => sites.siteId, {
|
||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}).notNull(),
|
})
|
||||||
|
.notNull(),
|
||||||
name: varchar("name"),
|
name: varchar("name"),
|
||||||
hcEnabled: boolean("hcEnabled").notNull().default(false),
|
hcEnabled: boolean("hcEnabled").notNull().default(false),
|
||||||
hcPath: varchar("hcPath"),
|
hcPath: varchar("hcPath"),
|
||||||
@@ -1097,7 +1100,9 @@ export const roundTripMessageTracker = pgTable("roundTripMessageTracker", {
|
|||||||
complete: boolean("complete").notNull().default(false)
|
complete: boolean("complete").notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const statusHistory = pgTable("statusHistory", {
|
export const statusHistory = pgTable(
|
||||||
|
"statusHistory",
|
||||||
|
{
|
||||||
id: serial("id").primaryKey(),
|
id: serial("id").primaryKey(),
|
||||||
entityType: varchar("entityType").notNull(),
|
entityType: varchar("entityType").notNull(),
|
||||||
entityId: integer("entityId").notNull(),
|
entityId: integer("entityId").notNull(),
|
||||||
@@ -1105,11 +1110,20 @@ export const statusHistory = pgTable("statusHistory", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
status: varchar("status").notNull(),
|
status: varchar("status").notNull(),
|
||||||
timestamp: integer("timestamp").notNull(),
|
timestamp: integer("timestamp").notNull()
|
||||||
}, (table) => [
|
},
|
||||||
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
|
(table) => [
|
||||||
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
|
index("idx_statusHistory_entity").on(
|
||||||
]);
|
table.entityType,
|
||||||
|
table.entityId,
|
||||||
|
table.timestamp
|
||||||
|
),
|
||||||
|
index("idx_statusHistory_org_timestamp").on(
|
||||||
|
table.orgId,
|
||||||
|
table.timestamp
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
export type Org = InferSelectModel<typeof orgs>;
|
export type Org = InferSelectModel<typeof orgs>;
|
||||||
export type User = InferSelectModel<typeof users>;
|
export type User = InferSelectModel<typeof users>;
|
||||||
|
|||||||
@@ -180,7 +180,8 @@ export const resources = sqliteTable("resources", {
|
|||||||
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
|
||||||
postAuthPath: text("postAuthPath"),
|
postAuthPath: text("postAuthPath"),
|
||||||
health: text("health").default("unknown"), // "healthy", "unhealthy", "unknown"
|
health: text("health").default("unknown"), // "healthy", "unhealthy", "unknown"
|
||||||
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false)
|
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false),
|
||||||
|
browserAccessType: text("browserAccessType").default("http") // rdp, ssh, http, vnc
|
||||||
});
|
});
|
||||||
|
|
||||||
export const targets = sqliteTable("targets", {
|
export const targets = sqliteTable("targets", {
|
||||||
@@ -219,9 +220,11 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
|
|||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
})
|
})
|
||||||
.notNull(),
|
.notNull(),
|
||||||
siteId: integer("siteId").references(() => sites.siteId, {
|
siteId: integer("siteId")
|
||||||
|
.references(() => sites.siteId, {
|
||||||
onDelete: "cascade"
|
onDelete: "cascade"
|
||||||
}).notNull(),
|
})
|
||||||
|
.notNull(),
|
||||||
name: text("name"),
|
name: text("name"),
|
||||||
hcEnabled: integer("hcEnabled", { mode: "boolean" })
|
hcEnabled: integer("hcEnabled", { mode: "boolean" })
|
||||||
.notNull()
|
.notNull()
|
||||||
@@ -1196,7 +1199,9 @@ export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", {
|
|||||||
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
|
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const statusHistory = sqliteTable("statusHistory", {
|
export const statusHistory = sqliteTable(
|
||||||
|
"statusHistory",
|
||||||
|
{
|
||||||
id: integer("id").primaryKey({ autoIncrement: true }),
|
id: integer("id").primaryKey({ autoIncrement: true }),
|
||||||
entityType: text("entityType").notNull(), // "site" | "healthCheck"
|
entityType: text("entityType").notNull(), // "site" | "healthCheck"
|
||||||
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
|
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
|
||||||
@@ -1204,11 +1209,20 @@ export const statusHistory = sqliteTable("statusHistory", {
|
|||||||
.notNull()
|
.notNull()
|
||||||
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
.references(() => orgs.orgId, { onDelete: "cascade" }),
|
||||||
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
|
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
|
||||||
timestamp: integer("timestamp").notNull(), // unix epoch seconds
|
timestamp: integer("timestamp").notNull() // unix epoch seconds
|
||||||
}, (table) => [
|
},
|
||||||
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
|
(table) => [
|
||||||
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
|
index("idx_statusHistory_entity").on(
|
||||||
]);
|
table.entityType,
|
||||||
|
table.entityId,
|
||||||
|
table.timestamp
|
||||||
|
),
|
||||||
|
index("idx_statusHistory_org_timestamp").on(
|
||||||
|
table.orgId,
|
||||||
|
table.timestamp
|
||||||
|
)
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
export type Org = InferSelectModel<typeof orgs>;
|
export type Org = InferSelectModel<typeof orgs>;
|
||||||
export type User = InferSelectModel<typeof users>;
|
export type User = InferSelectModel<typeof users>;
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ export type ResourceWithTargets = {
|
|||||||
headerAuthId: number | null;
|
headerAuthId: number | null;
|
||||||
wildcard: boolean;
|
wildcard: boolean;
|
||||||
health: string | null;
|
health: string | null;
|
||||||
|
browserAccessType: string | null;
|
||||||
targets: Array<{
|
targets: Array<{
|
||||||
targetId: number;
|
targetId: number;
|
||||||
ip: string;
|
ip: string;
|
||||||
@@ -178,7 +179,8 @@ function queryResourcesBase() {
|
|||||||
headerAuthId: resourceHeaderAuth.headerAuthId,
|
headerAuthId: resourceHeaderAuth.headerAuthId,
|
||||||
headerAuthExtendedCompatibilityId:
|
headerAuthExtendedCompatibilityId:
|
||||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
|
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
|
||||||
health: resources.health
|
health: resources.health,
|
||||||
|
browserAccessType: resources.browserAccessType
|
||||||
})
|
})
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.leftJoin(
|
.leftJoin(
|
||||||
@@ -478,6 +480,7 @@ export async function listResources(
|
|||||||
protocol: row.protocol,
|
protocol: row.protocol,
|
||||||
proxyPort: row.proxyPort,
|
proxyPort: row.proxyPort,
|
||||||
wildcard: row.wildcard,
|
wildcard: row.wildcard,
|
||||||
|
browserAccessType: row.browserAccessType,
|
||||||
enabled: row.enabled,
|
enabled: row.enabled,
|
||||||
domainId: row.domainId,
|
domainId: row.domainId,
|
||||||
headerAuthId: row.headerAuthId,
|
headerAuthId: row.headerAuthId,
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ import {
|
|||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
import { OpenAPITags } from "@server/openApi";
|
import { OpenAPITags } from "@server/openApi";
|
||||||
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
|
||||||
import { validateAndConstructDomain, checkWildcardDomainConflict } from "@server/lib/domainUtils";
|
import {
|
||||||
|
validateAndConstructDomain,
|
||||||
|
checkWildcardDomainConflict
|
||||||
|
} from "@server/lib/domainUtils";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
@@ -68,7 +71,8 @@ const updateHttpResourceBodySchema = z
|
|||||||
maintenanceTitle: z.string().max(255).nullable().optional(),
|
maintenanceTitle: z.string().max(255).nullable().optional(),
|
||||||
maintenanceMessage: z.string().max(2000).nullable().optional(),
|
maintenanceMessage: z.string().max(2000).nullable().optional(),
|
||||||
maintenanceEstimatedTime: z.string().max(100).nullable().optional(),
|
maintenanceEstimatedTime: z.string().max(100).nullable().optional(),
|
||||||
postAuthPath: z.string().nullable().optional()
|
postAuthPath: z.string().nullable().optional(),
|
||||||
|
browserAccessType: z.enum(["http", "ssh", "rdp", "vnc"]).optional()
|
||||||
})
|
})
|
||||||
.refine((data) => Object.keys(data).length > 0, {
|
.refine((data) => Object.keys(data).length > 0, {
|
||||||
error: "At least one field must be provided for update"
|
error: "At least one field must be provided for update"
|
||||||
|
|||||||
@@ -507,7 +507,9 @@ export default function GeneralForm() {
|
|||||||
name: data.name,
|
name: data.name,
|
||||||
niceId: data.niceId,
|
niceId: data.niceId,
|
||||||
subdomain: data.subdomain
|
subdomain: data.subdomain
|
||||||
? toASCII(finalizeSubdomainSanitize(data.subdomain, true))
|
? toASCII(
|
||||||
|
finalizeSubdomainSanitize(data.subdomain, true)
|
||||||
|
)
|
||||||
: undefined,
|
: undefined,
|
||||||
domainId: data.domainId,
|
domainId: data.domainId,
|
||||||
proxyPort: data.proxyPort
|
proxyPort: data.proxyPort
|
||||||
@@ -555,7 +557,9 @@ export default function GeneralForm() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsContainer>
|
<SettingsContainer>
|
||||||
{resource?.resourceId && resource?.orgId && (
|
{resource?.resourceId &&
|
||||||
|
resource?.orgId &&
|
||||||
|
resource.browserAccessType == "http" && (
|
||||||
<UptimeAlertSection
|
<UptimeAlertSection
|
||||||
orgId={resource.orgId}
|
orgId={resource.orgId}
|
||||||
resourceId={resource.resourceId}
|
resourceId={resource.resourceId}
|
||||||
|
|||||||
@@ -121,6 +121,10 @@ export default function ReverseProxyTargetsPage(props: {
|
|||||||
const params = use(props.params);
|
const params = use(props.params);
|
||||||
const { resource, updateResource } = useResourceContext();
|
const { resource, updateResource } = useResourceContext();
|
||||||
|
|
||||||
|
const [targetMode, setTargetMode] = useState<
|
||||||
|
"http" | "ssh" | "rdp" | "vnc"
|
||||||
|
>((resource.browserAccessType as "http" | "ssh" | "rdp" | "vnc") || "http");
|
||||||
|
|
||||||
const { data: remoteTargets = [], isLoading: isLoadingTargets } = useQuery(
|
const { data: remoteTargets = [], isLoading: isLoadingTargets } = useQuery(
|
||||||
resourceQueries.resourceTargets({
|
resourceQueries.resourceTargets({
|
||||||
resourceId: resource.resourceId
|
resourceId: resource.resourceId
|
||||||
@@ -137,9 +141,12 @@ export default function ReverseProxyTargetsPage(props: {
|
|||||||
orgId={params.orgId}
|
orgId={params.orgId}
|
||||||
initialTargets={remoteTargets}
|
initialTargets={remoteTargets}
|
||||||
resource={resource}
|
resource={resource}
|
||||||
|
targetMode={targetMode}
|
||||||
|
setTargetMode={setTargetMode}
|
||||||
|
updateResource={updateResource}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{resource.http && (
|
{resource.http && targetMode === "http" && (
|
||||||
<ProxyResourceHttpForm
|
<ProxyResourceHttpForm
|
||||||
resource={resource}
|
resource={resource}
|
||||||
updateResource={updateResource}
|
updateResource={updateResource}
|
||||||
@@ -159,11 +166,17 @@ export default function ReverseProxyTargetsPage(props: {
|
|||||||
function ProxyResourceTargetsForm({
|
function ProxyResourceTargetsForm({
|
||||||
orgId,
|
orgId,
|
||||||
initialTargets,
|
initialTargets,
|
||||||
resource
|
resource,
|
||||||
|
targetMode,
|
||||||
|
setTargetMode,
|
||||||
|
updateResource
|
||||||
}: {
|
}: {
|
||||||
initialTargets: LocalTarget[];
|
initialTargets: LocalTarget[];
|
||||||
orgId: string;
|
orgId: string;
|
||||||
resource: GetResourceResponse;
|
resource: GetResourceResponse;
|
||||||
|
targetMode: "http" | "ssh" | "rdp" | "vnc";
|
||||||
|
setTargetMode: (mode: "http" | "ssh" | "rdp" | "vnc") => void;
|
||||||
|
updateResource: ResourceContextType["updateResource"];
|
||||||
}) {
|
}) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
@@ -201,9 +214,6 @@ function ProxyResourceTargetsForm({
|
|||||||
const [selectedTargetForHealthCheck, setSelectedTargetForHealthCheck] =
|
const [selectedTargetForHealthCheck, setSelectedTargetForHealthCheck] =
|
||||||
useState<LocalTarget | null>(null);
|
useState<LocalTarget | null>(null);
|
||||||
|
|
||||||
const [targetMode, setTargetMode] = useState<
|
|
||||||
"http" | "ssh" | "rdp" | "vnc"
|
|
||||||
>("http");
|
|
||||||
const [bgDestination, setBgDestination] = useState("");
|
const [bgDestination, setBgDestination] = useState("");
|
||||||
const [bgDestinationPort, setBgDestinationPort] = useState("");
|
const [bgDestinationPort, setBgDestinationPort] = useState("");
|
||||||
const [bgSiteId, setBgSiteId] = useState<number | null>(null);
|
const [bgSiteId, setBgSiteId] = useState<number | null>(null);
|
||||||
@@ -938,11 +948,30 @@ function ProxyResourceTargetsForm({
|
|||||||
<span className="text-sm font-medium">Target Type</span>
|
<span className="text-sm font-medium">Target Type</span>
|
||||||
<Select
|
<Select
|
||||||
value={targetMode}
|
value={targetMode}
|
||||||
onValueChange={(v) =>
|
onValueChange={async (v) => {
|
||||||
setTargetMode(
|
const mode = v as
|
||||||
v as "http" | "ssh" | "rdp" | "vnc"
|
| "http"
|
||||||
|
| "ssh"
|
||||||
|
| "rdp"
|
||||||
|
| "vnc";
|
||||||
|
setTargetMode(mode);
|
||||||
|
try {
|
||||||
|
await api.post(
|
||||||
|
`/resource/${resource.resourceId}`,
|
||||||
|
{ browserAccessType: mode }
|
||||||
|
);
|
||||||
|
updateResource({ browserAccessType: mode });
|
||||||
|
} catch (err) {
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: t("settingsErrorUpdate"),
|
||||||
|
description: formatAxiosError(
|
||||||
|
err,
|
||||||
|
t("settingsErrorUpdateDescription")
|
||||||
)
|
)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-36">
|
<SelectTrigger className="w-36">
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export default async function ProxyResourcesPage(
|
|||||||
fullDomain: resource.fullDomain ?? null,
|
fullDomain: resource.fullDomain ?? null,
|
||||||
ssl: resource.ssl,
|
ssl: resource.ssl,
|
||||||
wildcard: resource.wildcard,
|
wildcard: resource.wildcard,
|
||||||
|
browserAccessType: resource.browserAccessType,
|
||||||
targets: resource.targets?.map((target) => ({
|
targets: resource.targets?.map((target) => ({
|
||||||
targetId: target.targetId,
|
targetId: target.targetId,
|
||||||
ip: target.ip,
|
ip: target.ip,
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export type ResourceRow = {
|
|||||||
name: string;
|
name: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
|
browserAccessType: string | null;
|
||||||
authState: string;
|
authState: string;
|
||||||
http: boolean;
|
http: boolean;
|
||||||
protocol: string;
|
protocol: string;
|
||||||
@@ -493,6 +494,12 @@ export default function ProxyResourcesTable({
|
|||||||
),
|
),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const resourceRow = row.original;
|
const resourceRow = row.original;
|
||||||
|
if (
|
||||||
|
!resourceRow.http ||
|
||||||
|
resourceRow.browserAccessType !== "http"
|
||||||
|
) {
|
||||||
|
return <span>-</span>;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<TargetStatusCell
|
<TargetStatusCell
|
||||||
targets={resourceRow.targets}
|
targets={resourceRow.targets}
|
||||||
@@ -521,6 +528,12 @@ export default function ProxyResourcesTable({
|
|||||||
header: () => <span className="p-3">{t("uptime30d")}</span>,
|
header: () => <span className="p-3">{t("uptime30d")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const resourceRow = row.original;
|
const resourceRow = row.original;
|
||||||
|
if (
|
||||||
|
!resourceRow.http ||
|
||||||
|
resourceRow.browserAccessType !== "http"
|
||||||
|
) {
|
||||||
|
return <span>-</span>;
|
||||||
|
}
|
||||||
return <UptimeMiniBar resourceId={resourceRow.id} days={30} />;
|
return <UptimeMiniBar resourceId={resourceRow.id} days={30} />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user