mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-06 12:27:39 +00:00
Add resource column to hc and remove —
This commit is contained in:
@@ -22,7 +22,7 @@ import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||
import { useEffect, useState, useTransition } from "react";
|
||||
|
||||
function formatBytes(bytes: number | null): string {
|
||||
if (bytes === null || bytes === undefined) return "—";
|
||||
if (bytes === null || bytes === undefined) return "-";
|
||||
if (bytes === 0) return "0 B";
|
||||
const units = ["B", "KB", "MB", "GB", "TB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
@@ -33,7 +33,7 @@ function formatBytes(bytes: number | null): string {
|
||||
function formatDuration(startedAt: number, endedAt: number | null): string {
|
||||
if (endedAt === null || endedAt === undefined) return "Active";
|
||||
const durationSec = endedAt - startedAt;
|
||||
if (durationSec < 0) return "—";
|
||||
if (durationSec < 0) return "-";
|
||||
if (durationSec < 60) return `${durationSec}s`;
|
||||
if (durationSec < 3600) {
|
||||
const m = Math.floor(durationSec / 60);
|
||||
@@ -460,7 +460,7 @@ export default function ConnectionLogsPage() {
|
||||
}
|
||||
return (
|
||||
<span className="whitespace-nowrap">
|
||||
{row.original.resourceName ?? "—"}
|
||||
{row.original.resourceName ?? "-"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -503,7 +503,7 @@ export default function ConnectionLogsPage() {
|
||||
}
|
||||
return (
|
||||
<span className="whitespace-nowrap">
|
||||
{row.original.clientName ?? "—"}
|
||||
{row.original.clientName ?? "-"}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -538,7 +538,7 @@ export default function ConnectionLogsPage() {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return <span>—</span>;
|
||||
return <span>-</span>;
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -612,23 +612,23 @@ export default function ConnectionLogsPage() {
|
||||
<div>
|
||||
<strong>Session ID:</strong>{" "}
|
||||
<span className="font-mono">
|
||||
{row.sessionId ?? "—"}
|
||||
{row.sessionId ?? "-"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Protocol:</strong>{" "}
|
||||
{row.protocol?.toUpperCase() ?? "—"}
|
||||
{row.protocol?.toUpperCase() ?? "-"}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Source:</strong>{" "}
|
||||
<span className="font-mono">
|
||||
{row.sourceAddr ?? "—"}
|
||||
{row.sourceAddr ?? "-"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Destination:</strong>{" "}
|
||||
<span className="font-mono">
|
||||
{row.destAddr ?? "—"}
|
||||
{row.destAddr ?? "-"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -638,7 +638,7 @@ export default function ConnectionLogsPage() {
|
||||
</div>*/}
|
||||
{/*<div>
|
||||
<strong>Resource:</strong>{" "}
|
||||
{row.resourceName ?? "—"}
|
||||
{row.resourceName ?? "-"}
|
||||
{row.resourceNiceId && (
|
||||
<span className="text-muted-foreground ml-1">
|
||||
({row.resourceNiceId})
|
||||
@@ -646,7 +646,7 @@ export default function ConnectionLogsPage() {
|
||||
)}
|
||||
</div>*/}
|
||||
<div>
|
||||
<strong>Site:</strong> {row.siteName ?? "—"}
|
||||
<strong>Site:</strong> {row.siteName ?? "-"}
|
||||
{row.siteNiceId && (
|
||||
<span className="text-muted-foreground ml-1">
|
||||
({row.siteNiceId})
|
||||
@@ -654,7 +654,7 @@ export default function ConnectionLogsPage() {
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Site ID:</strong> {row.siteId ?? "—"}
|
||||
<strong>Site ID:</strong> {row.siteId ?? "-"}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Started At:</strong>{" "}
|
||||
@@ -662,7 +662,7 @@ export default function ConnectionLogsPage() {
|
||||
? new Date(
|
||||
row.startedAt * 1000
|
||||
).toLocaleString()
|
||||
: "—"}
|
||||
: "-"}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Ended At:</strong>{" "}
|
||||
@@ -676,7 +676,7 @@ export default function ConnectionLogsPage() {
|
||||
</div>
|
||||
{/*<div>
|
||||
<strong>Resource ID:</strong>{" "}
|
||||
{row.siteResourceId ?? "—"}
|
||||
{row.siteResourceId ?? "-"}
|
||||
</div>*/}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -434,7 +434,7 @@ export default function StreamingDestinationsPage() {
|
||||
disabled={!isEnterprise}
|
||||
/>
|
||||
))}
|
||||
{/* Add card is always clickable — paywall is enforced inside the picker */}
|
||||
{/* Add card is always clickable - paywall is enforced inside the picker */}
|
||||
<AddDestinationCard onClick={openCreate} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -341,19 +341,6 @@ function ProxyResourceTargetsForm({
|
||||
header: () => <span className="p-3">{t("healthCheck")}</span>,
|
||||
cell: ({ row }) => {
|
||||
const status = row.original.hcHealth || "unknown";
|
||||
const isEnabled = row.original.hcEnabled;
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case "healthy":
|
||||
return "green";
|
||||
case "unhealthy":
|
||||
return "red";
|
||||
case "unknown":
|
||||
default:
|
||||
return "secondary";
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusText = (status: string) => {
|
||||
switch (status) {
|
||||
@@ -367,19 +354,7 @@ function ProxyResourceTargetsForm({
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case "healthy":
|
||||
return <CircleCheck className="w-3 h-3" />;
|
||||
case "unhealthy":
|
||||
return <CircleX className="w-3 h-3" />;
|
||||
case "unknown":
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className="flex items-center justify-center w-full">
|
||||
{row.original.siteType === "newt" ? (
|
||||
<Button
|
||||
@@ -390,12 +365,15 @@ function ProxyResourceTargetsForm({
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={`flex items-center gap-2 ${status === "healthy" ? "text-green-500" : status === "unhealthy" ? "text-destructive" : ""}`}
|
||||
className={`flex items-center gap-2 ${status === "healthy" ? "text-green-500" : status === "unhealthy" ? "text-destructive" : "text-neutral-500"}`}
|
||||
>
|
||||
<Settings className="h-4 w-4 text-foreground" />
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${status === "healthy" ? "bg-green-500" : status === "unhealthy" ? "bg-destructive" : "bg-neutral-500"}`}
|
||||
></div>
|
||||
{getStatusText(status)}
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
) : (
|
||||
<span>-</span>
|
||||
)}
|
||||
|
||||
@@ -425,7 +425,7 @@ export default function Page() {
|
||||
setRemoteExitNodeOptions(exitNodeOptions);
|
||||
|
||||
if (exitNodeOptions.length === 0) {
|
||||
// No remote exit nodes available — remove local option and default to newt
|
||||
// No remote exit nodes available - remove local option and default to newt
|
||||
setTunnelTypes((prev: any) =>
|
||||
prev.filter((item: any) => item.id !== "local")
|
||||
);
|
||||
@@ -434,7 +434,7 @@ export default function Page() {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch remote exit nodes:", error);
|
||||
// If fetch fails, no remote exit nodes available — remove local option and default to newt
|
||||
// If fetch fails, no remote exit nodes available - remove local option and default to newt
|
||||
setTunnelTypes((prev: any) =>
|
||||
prev.filter((item: any) => item.id !== "local")
|
||||
);
|
||||
|
||||
@@ -49,7 +49,7 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("offline")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -133,7 +133,7 @@ function aggregateStatusDotClass(status: AggregateSitesStatus): string {
|
||||
return "bg-yellow-500";
|
||||
case "allOffline":
|
||||
default:
|
||||
return "bg-gray-500";
|
||||
return "bg-neutral-500";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ function ClientResourceSitesStatusCell({
|
||||
"h-2 w-2 shrink-0 rounded-full",
|
||||
isOnline
|
||||
? "bg-green-500"
|
||||
: "bg-gray-500"
|
||||
: "bg-neutral-500"
|
||||
)}
|
||||
/>
|
||||
<span className="truncate">
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function ExitNodeInfoCard({}: ExitNodeInfoCardProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("offline")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -146,7 +146,7 @@ export default function ExitNodesTable({
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("offline")}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -61,6 +61,9 @@ export type HealthCheckRow = {
|
||||
hcTlsServerName: string | null;
|
||||
hcHealthyThreshold: number | null;
|
||||
hcUnhealthyThreshold: number | null;
|
||||
resourceId: number | null;
|
||||
resourceName: string | null;
|
||||
resourceNiceId: string | null;
|
||||
};
|
||||
|
||||
export type HealthCheckCredenzaProps =
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "@/components/ui/select";
|
||||
import { StrategySelect } from "@app/components/StrategySelect";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { HeadersInput } from "@app/components/HeadersInput";
|
||||
import {
|
||||
@@ -103,22 +104,27 @@ export function HealthCheckFormFields({
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("healthCheckStrategy")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={(value) =>
|
||||
handleChange("hcMode", value, field.onChange)
|
||||
}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="http">HTTP</SelectItem>
|
||||
<SelectItem value="tcp">TCP</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormControl>
|
||||
<StrategySelect
|
||||
cols={2}
|
||||
options={[
|
||||
{
|
||||
id: "http",
|
||||
title: "HTTP",
|
||||
description: "Validates connectivity and checks the HTTP response status."
|
||||
},
|
||||
{
|
||||
id: "tcp",
|
||||
title: "TCP",
|
||||
description: "Verifies TCP connectivity only, without inspecting the response."
|
||||
}
|
||||
]}
|
||||
value={field.value}
|
||||
onChange={(value) =>
|
||||
handleChange("hcMode", value, field.onChange)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
|
||||
@@ -19,17 +19,17 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { orgQueries } from "@app/lib/queries";
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ArrowUpDown, MoreHorizontal } from "lucide-react";
|
||||
import { ArrowUpDown, ArrowUpRight, MoreHorizontal } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useState } from "react";
|
||||
import { Span } from "next/dist/trace";
|
||||
import Link from "next/link";
|
||||
|
||||
type StandaloneHealthChecksTableProps = {
|
||||
orgId: string;
|
||||
};
|
||||
|
||||
function formatTarget(row: HealthCheckRow): string {
|
||||
if (!row.hcHostname) return "—";
|
||||
if (!row.hcHostname) return "-";
|
||||
if (row.hcMode === "tcp") {
|
||||
if (!row.hcPort) return row.hcHostname;
|
||||
return `${row.hcHostname}:${row.hcPort}`;
|
||||
@@ -154,7 +154,7 @@ export default function HealthChecksTable({
|
||||
),
|
||||
cell: ({ row }) => (
|
||||
<span>
|
||||
{row.original.hcMode?.toUpperCase() ?? "—"}
|
||||
{row.original.hcMode?.toUpperCase() ?? "-"}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
@@ -166,6 +166,27 @@ export default function HealthChecksTable({
|
||||
),
|
||||
cell: ({ row }) => <span>{formatTarget(row.original)}</span>
|
||||
},
|
||||
{
|
||||
id: "resource",
|
||||
friendlyName: "Resource",
|
||||
header: () => (
|
||||
<span className="p-3">Resource</span>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const r = row.original;
|
||||
if (!r.resourceId || !r.resourceName || !r.resourceNiceId) {
|
||||
return <span className="text-neutral-400">-</span>;
|
||||
}
|
||||
return (
|
||||
<Link href={`/${orgId}/settings/resources/proxy/${r.resourceNiceId}`}>
|
||||
<Button variant="outline" size="sm">
|
||||
{r.resourceName}
|
||||
<ArrowUpRight className="ml-2 h-3 w-3" />
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "health",
|
||||
friendlyName: t("standaloneHcColumnHealth"),
|
||||
@@ -191,7 +212,7 @@ export default function HealthChecksTable({
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{healthLabel.unknown}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function LocaleSwitcherSelect({
|
||||
});
|
||||
// Persist locale to the database (fire-and-forget)
|
||||
api.post("/user/locale", { locale }).catch(() => {
|
||||
// Silently ignore errors — cookie is already set as fallback
|
||||
// Silently ignore errors - cookie is already set as fallback
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +293,7 @@ export default function MachineClientsTable({
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("disconnected")}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -204,7 +204,7 @@ export default function PendingSitesTable({
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("offline")}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -79,7 +79,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</span>
|
||||
) : (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>Offline</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("offline")}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -212,7 +212,7 @@ export default function SitesTable({
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("offline")}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -427,7 +427,7 @@ export default function UserDevicesTable({
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<div className="w-2 h-2 bg-neutral-500 rounded-full"></div>
|
||||
<span>{t("disconnected")}</span>
|
||||
</span>
|
||||
);
|
||||
|
||||
@@ -291,6 +291,9 @@ export const orgQueries = {
|
||||
hcTlsServerName: string | null;
|
||||
hcHealthyThreshold: number | null;
|
||||
hcUnhealthyThreshold: number | null;
|
||||
resourceId: number | null;
|
||||
resourceName: string | null;
|
||||
resourceNiceId: string | null;
|
||||
}[];
|
||||
pagination: {
|
||||
total: number;
|
||||
|
||||
@@ -17,14 +17,14 @@ export async function getUserLocale(): Promise<Locale> {
|
||||
return cookieLocale as Locale;
|
||||
}
|
||||
|
||||
// No cookie found — try to restore from user's saved locale in DB
|
||||
// No cookie found - try to restore from user's saved locale in DB
|
||||
try {
|
||||
const res = await internal.get("/user", await authCookieHeader());
|
||||
const userLocale = res.data?.data?.locale;
|
||||
if (userLocale && locales.includes(userLocale as Locale)) {
|
||||
// Try to cache in a cookie so subsequent requests skip the API
|
||||
// call. cookies().set() is only permitted in Server Actions and
|
||||
// Route Handlers — not during rendering — so we isolate it so
|
||||
// Route Handlers - not during rendering - so we isolate it so
|
||||
// that a write failure doesn't prevent the locale from being
|
||||
// returned for the current request.
|
||||
try {
|
||||
@@ -40,7 +40,7 @@ export async function getUserLocale(): Promise<Locale> {
|
||||
return userLocale as Locale;
|
||||
}
|
||||
} catch {
|
||||
// User not logged in or API unavailable — fall through
|
||||
// User not logged in or API unavailable - fall through
|
||||
}
|
||||
|
||||
const headerList = await headers();
|
||||
|
||||
Reference in New Issue
Block a user