Compare commits

...

15 Commits

Author SHA1 Message Date
miloschwartz
8ee4ee7baf remove bg-muted on target sep 2025-12-04 22:11:27 -05:00
Owen
b1b0702886 Make query optional 2025-12-04 22:07:48 -05:00
Owen
92aed108cd Update package 2025-12-04 22:07:48 -05:00
miloschwartz
2dcc94cd14 fix hc port NaN issue 2025-12-04 22:03:37 -05:00
miloschwartz
a7185ff913 add auth info tip 2025-12-04 21:28:42 -05:00
miloschwartz
04e73515b8 add alias to client resources table 2025-12-04 21:21:48 -05:00
miloschwartz
2bad9daaea move edit resource to proxy subpath 2025-12-04 21:18:17 -05:00
miloschwartz
54670e150d simplify create site wizard 2025-12-04 21:12:14 -05:00
miloschwartz
761ed1de9a ensure unique niceId for site resources and normal resources 2025-12-04 21:07:14 -05:00
miloschwartz
078692c818 invalidate queries on save 2025-12-04 17:56:11 -05:00
Milo Schwartz
54e2d95b55 Merge pull request #1977 from Fredkiss3/fix/some-fixes
fix: bugs introduced in `separate-tables`
2025-12-04 14:25:30 -08:00
Fred KISSIE
ba09479827 ♻️ organize imports 2025-12-04 22:50:17 +01:00
Fred KISSIE
1c5c36fc12 ♻️ set the staleTime to Zero for queries so that they are refetched everytime 2025-12-04 22:50:04 +01:00
Fred KISSIE
d37ff6e15b 🐛 resource rols & resource clients shouldn't have the same query key 2025-12-04 22:49:40 +01:00
Fred KISSIE
0ceed4c812 📦 update lockfile 2025-12-04 20:30:41 +01:00
26 changed files with 3948 additions and 2077 deletions

View File

@@ -1112,6 +1112,7 @@
"create": "Create",
"orgs": "Organizations",
"loginError": "An error occurred while logging in",
"loginRequiredForDevice": "Login is required to authenticate your device.",
"passwordForgot": "Forgot your password?",
"otpAuth": "Two-Factor Authentication",
"otpAuthDescription": "Enter the code from your authenticator app or one of your single-use backup codes.",
@@ -1509,14 +1510,15 @@
"enableHealthChecksDescription": "Monitor the health of this target. You can monitor a different endpoint than the target if required.",
"healthScheme": "Method",
"healthSelectScheme": "Select Method",
"healthCheckPortInvalid": "Health check port must be between 1 and 65535",
"healthCheckPath": "Path",
"healthHostname": "IP / Host",
"healthPort": "Port",
"healthCheckPathDescription": "The path to check for health status.",
"healthyIntervalSeconds": "Healthy Interval",
"unhealthyIntervalSeconds": "Unhealthy Interval",
"healthyIntervalSeconds": "Healthy Interval (sec)",
"unhealthyIntervalSeconds": "Unhealthy Interval (sec)",
"IntervalSeconds": "Healthy Interval",
"timeoutSeconds": "Timeout",
"timeoutSeconds": "Timeout (sec)",
"timeIsInSeconds": "Time is in seconds",
"retryAttempts": "Retry Attempts",
"expectedResponseCodes": "Expected Response Codes",
@@ -1557,6 +1559,7 @@
"resourcesTableNoProxyResourcesFound": "No proxy resources found.",
"resourcesTableNoInternalResourcesFound": "No internal resources found.",
"resourcesTableDestination": "Destination",
"resourcesTableAlias": "Alias",
"resourcesTableClients": "Clients",
"resourcesTableAndOnlyAccessibleInternally": "and are only accessible internally when connected with a client.",
"resourcesTableNoTargets": "No targets",
@@ -2233,5 +2236,6 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Not you? Use a different account.",
"deviceLoginDeviceRequestingAccessToAccount": "A device is requesting access to this account.",
"noData": "No Data"
"noData": "No Data",
"machineClients": "Machine Clients"
}

5649
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -113,7 +113,7 @@
"qrcode.react": "4.2.0",
"react": "19.2.1",
"react-day-picker": "9.11.1",
"react-dom": "19.2.0",
"react-dom": "19.2.1",
"react-easy-sort": "^1.8.0",
"react-hook-form": "7.66.0",
"react-icons": "^5.5.0",

View File

@@ -42,11 +42,17 @@ export async function getUniqueResourceName(orgId: string): Promise<string> {
}
const name = generateName();
const count = await db
.select({ niceId: resources.niceId, orgId: resources.orgId })
.from(resources)
.where(and(eq(resources.niceId, name), eq(resources.orgId, orgId)));
if (count.length === 0) {
const [resourceCount, siteResourceCount] = await Promise.all([
db
.select({ niceId: resources.niceId, orgId: resources.orgId })
.from(resources)
.where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))),
db
.select({ niceId: siteResources.niceId, orgId: siteResources.orgId })
.from(siteResources)
.where(and(eq(siteResources.niceId, name), eq(siteResources.orgId, orgId)))
]);
if (resourceCount.length === 0 && siteResourceCount.length === 0) {
return name;
}
loops++;
@@ -61,11 +67,17 @@ export async function getUniqueSiteResourceName(orgId: string): Promise<string>
}
const name = generateName();
const count = await db
.select({ niceId: siteResources.niceId, orgId: siteResources.orgId })
.from(siteResources)
.where(and(eq(siteResources.niceId, name), eq(siteResources.orgId, orgId)));
if (count.length === 0) {
const [resourceCount, siteResourceCount] = await Promise.all([
db
.select({ niceId: resources.niceId, orgId: resources.orgId })
.from(resources)
.where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))),
db
.select({ niceId: siteResources.niceId, orgId: siteResources.orgId })
.from(siteResources)
.where(and(eq(siteResources.niceId, name), eq(siteResources.orgId, orgId)))
]);
if (resourceCount.length === 0 && siteResourceCount.length === 0) {
return name;
}
loops++;

View File

@@ -73,7 +73,7 @@ function createDb() {
return withReplicas(
DrizzlePostgres(primaryPool, {
logger: process.env.NODE_ENV === "development"
logger: process.env.QUERY_LOGGING === "true"
}),
replicas as any
);

View File

@@ -262,7 +262,7 @@ export default function Page() {
if (res && res.status === 201) {
const data = res.data.data;
router.push(`/${orgId}/settings/clients/${data.clientId}`);
router.push(`/${orgId}/settings/clients/machine/${data.clientId}`);
}
setCreateLoading(false);
@@ -426,32 +426,32 @@ export default function Page() {
)}
/>
<FormField
control={form.control}
name="subnet"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("address")}
</FormLabel>
<FormControl>
<Input
autoComplete="off"
placeholder={t(
"subnetPlaceholder"
)}
{...field}
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"addressDescription"
)}
</FormDescription>
</FormItem>
)}
/>
{/* <FormField */}
{/* control={form.control} */}
{/* name="subnet" */}
{/* render={({ field }) => ( */}
{/* <FormItem> */}
{/* <FormLabel> */}
{/* {t("address")} */}
{/* </FormLabel> */}
{/* <FormControl> */}
{/* <Input */}
{/* autoComplete="off" */}
{/* placeholder={t( */}
{/* "subnetPlaceholder" */}
{/* )} */}
{/* {...field} */}
{/* /> */}
{/* </FormControl> */}
{/* <FormMessage /> */}
{/* <FormDescription> */}
{/* {t( */}
{/* "addressDescription" */}
{/* )} */}
{/* </FormDescription> */}
{/* </FormItem> */}
{/* )} */}
{/* /> */}
{/* <FormField */}
{/* control={form.control} */}

View File

@@ -466,7 +466,7 @@ export default function GeneralPage() {
cell: ({ row }) => {
return (
<Link
href={`/${row.original.orgId}/settings/resources/${row.original.resourceNiceId}`}
href={`/${row.original.orgId}/settings/resources/proxy/${row.original.resourceNiceId}`}
>
<Button
variant="outline"

View File

@@ -499,7 +499,7 @@ export default function GeneralPage() {
cell: ({ row }) => {
return (
<Link
href={`/${row.original.orgId}/settings/resources/${row.original.resourceNiceId}`}
href={`/${row.original.orgId}/settings/resources/proxy/${row.original.resourceNiceId}`}
onClick={(e) => e.stopPropagation()}
>
<Button
@@ -793,4 +793,4 @@ export default function GeneralPage() {
/>
</>
);
}
}

View File

@@ -1,5 +1,5 @@
import ClientResourcesTable from "@app/components/ClientResourcesTable";
import type { InternalResourceRow } from "@app/components/ClientResourcesTable";
import ClientResourcesTable from "@app/components/ClientResourcesTable";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
@@ -10,7 +10,6 @@ import type { ListAllSiteResourcesByOrgResponse } from "@server/routers/siteReso
import type { AxiosResponse } from "axios";
import { getTranslations } from "next-intl/server";
import { redirect } from "next/navigation";
import { cache } from "react";
export interface ClientResourcesPageProps {
params: Promise<{ orgId: string }>;

View File

@@ -27,9 +27,9 @@ import {
} from "@app/components/ui/form";
import { ListUsersResponse } from "@server/routers/user";
import { Binary, Key, Bot } from "lucide-react";
import SetResourcePasswordForm from "../../../../../../components/SetResourcePasswordForm";
import SetResourcePincodeForm from "../../../../../../components/SetResourcePincodeForm";
import SetResourceHeaderAuthForm from "../../../../../../components/SetResourceHeaderAuthForm";
import SetResourcePasswordForm from "components/SetResourcePasswordForm";
import SetResourcePincodeForm from "@app/components/SetResourcePincodeForm";
import SetResourceHeaderAuthForm from "@app/components/SetResourceHeaderAuthForm";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import {

View File

@@ -54,7 +54,7 @@ import DomainPicker from "@app/components/DomainPicker";
import { Globe } from "lucide-react";
import { build } from "@server/build";
import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils";
import { DomainRow } from "../../../../../../components/DomainsTable";
import { DomainRow } from "@app/components/DomainsTable";
import { toASCII, toUnicode } from "punycode";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
@@ -235,7 +235,7 @@ export default function GeneralForm() {
});
if (data.niceId && data.niceId !== resource?.niceId) {
router.replace(`/${updated.orgId}/settings/resources/${data.niceId}/general`);
router.replace(`/${updated.orgId}/settings/resources/proxy/${data.niceId}/general`);
} else {
router.refresh();
}

View File

@@ -12,7 +12,7 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { GetOrgResponse } from "@server/routers/org";
import OrgProvider from "@app/providers/OrgProvider";
import { cache } from "react";
import ResourceInfoBox from "../../../../../components/ResourceInfoBox";
import ResourceInfoBox from "@app/components/ResourceInfoBox";
import { GetSiteResponse } from "@server/routers/site";
import { getTranslations } from 'next-intl/server';
@@ -77,22 +77,22 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
const navItems = [
{
title: t('general'),
href: `/{orgId}/settings/resources/{niceId}/general`
href: `/{orgId}/settings/resources/proxy/{niceId}/general`
},
{
title: t('proxy'),
href: `/{orgId}/settings/resources/{niceId}/proxy`
href: `/{orgId}/settings/resources/proxy/{niceId}/proxy`
}
];
if (resource.http) {
navItems.push({
title: t('authentication'),
href: `/{orgId}/settings/resources/{niceId}/authentication`
href: `/{orgId}/settings/resources/proxy/{niceId}/authentication`
});
navItems.push({
title: t('rules'),
href: `/{orgId}/settings/resources/{niceId}/rules`
href: `/{orgId}/settings/resources/proxy/{niceId}/rules`
});
}

View File

@@ -5,6 +5,6 @@ export default async function ResourcePage(props: {
}) {
const params = await props.params;
redirect(
`/${params.orgId}/settings/resources/${params.niceId}/proxy`
`/${params.orgId}/settings/resources/proxy/${params.niceId}/proxy`
);
}

View File

@@ -691,6 +691,7 @@ export default function ReverseProxyTargets(props: {
target.port <= 0 ||
isNaN(target.port)
);
console.log(targetsWithInvalidFields);
if (targetsWithInvalidFields.length > 0) {
toast({
variant: "destructive",
@@ -1136,7 +1137,7 @@ export default function ReverseProxyTargets(props: {
)}
{resource.http && (
<div className="flex items-center justify-center bg-muted px-2 h-9">
<div className="flex items-center justify-center px-2 h-9">
{"://"}
</div>
)}
@@ -1184,7 +1185,7 @@ export default function ReverseProxyTargets(props: {
}
}}
/>
<div className="flex items-center justify-center bg-muted px-2 h-9">
<div className="flex items-center justify-center px-2 h-9">
{":"}
</div>
<Input
@@ -1833,9 +1834,7 @@ export default function ReverseProxyTargets(props: {
30
}}
onChanges={async (config) => {
console.log("here");
if (selectedTargetForHealthCheck) {
console.log(config);
updateTargetHealthCheck(
selectedTargetForHealthCheck.targetId,
config

View File

@@ -604,7 +604,7 @@ export default function Page() {
}
if (isHttp) {
router.push(`/${orgId}/settings/resources/${niceId}`);
router.push(`/${orgId}/settings/resources/proxy/${niceId}`);
} else {
const tcpUdpData = tcpUdpForm.getValues();
// Only show config snippets if enableProxy is explicitly true
@@ -613,7 +613,7 @@ export default function Page() {
router.refresh();
// } else {
// // If enableProxy is false or undefined, go directly to resource page
// router.push(`/${orgId}/settings/resources/${id}`);
// router.push(`/${orgId}/settings/resources/proxy/${id}`);
// }
}
}
@@ -1040,7 +1040,7 @@ export default function Page() {
)}
{isHttp && (
<div className="flex items-center justify-center bg-muted px-2 h-9">
<div className="flex items-center justify-center px-2 h-9">
{"://"}
</div>
)}
@@ -1088,7 +1088,7 @@ export default function Page() {
}
}}
/>
<div className="flex items-center justify-center bg-muted px-2 h-9">
<div className="flex items-center justify-center px-2 h-9">
{":"}
</div>
<Input
@@ -1898,7 +1898,7 @@ export default function Page() {
type="button"
onClick={() =>
router.push(
`/${orgId}/settings/resources/${niceId}/proxy`
`/${orgId}/settings/resources/proxy/${niceId}/proxy`
)
}
>

View File

@@ -708,7 +708,7 @@ WantedBy=default.target`
</FormItem>
)}
/>
{form.watch("method") ===
{/*form.watch("method") ===
"newt" && (
<FormField
control={form.control}
@@ -751,36 +751,31 @@ WantedBy=default.target`
</FormItem>
)}
/>
)}
)*/}
</form>
</Form>
</SettingsSectionForm>
{tunnelTypes.length > 1 && (
<>
<div className="mb-2">
<span>{t("type")}</span>
</div>
<StrategySelect
options={tunnelTypes}
defaultValue={form.getValues(
"method"
)}
onChange={(value) => {
form.setValue("method", value);
}}
cols={3}
/>
</>
)}
</SettingsSectionBody>
</SettingsSection>
{tunnelTypes.length > 1 && (
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t("tunnelType")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t("siteTunnelDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<StrategySelect
options={tunnelTypes}
defaultValue={form.getValues("method")}
onChange={(value) => {
form.setValue("method", value);
}}
cols={3}
/>
</SettingsSectionBody>
</SettingsSection>
)}
{form.watch("method") === "newt" && (
<>
<SettingsSection>

View File

@@ -14,11 +14,7 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import {
ArrowUpDown,
ArrowUpRight,
MoreHorizontal
} from "lucide-react";
import { ArrowUpDown, ArrowUpRight, MoreHorizontal } from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
@@ -206,52 +202,34 @@ export default function ClientResourcesTable({
),
cell: ({ row }) => {
const resourceRow = row.original;
let displayText: string;
let copyText: string;
// if (
// resourceRow.mode === "port" &&
// // resourceRow.protocol &&
// // resourceRow.proxyPort &&
// // resourceRow.destinationPort
// ) {
// // const protocol = resourceRow.protocol.toUpperCase();
// // For port mode: site part uses alias or site address, destination part uses destination IP
// // If site address has CIDR notation, extract just the IP address
// let siteAddress = resourceRow.siteAddress;
// if (siteAddress && siteAddress.includes("/")) {
// siteAddress = siteAddress.split("/")[0];
// }
// const siteDisplay = resourceRow.alias || siteAddress;
// // displayText = `${protocol} ${siteDisplay}:${resourceRow.proxyPort} -> ${resourceRow.destination}:${resourceRow.destinationPort}`;
// // copyText = `${siteDisplay}:${resourceRow.proxyPort}`;
// } else if (resourceRow.mode === "host") {
if (resourceRow.mode === "host") {
// For host mode: use alias if available, otherwise use destination
const destinationDisplay =
resourceRow.alias || resourceRow.destination;
displayText = destinationDisplay;
copyText = destinationDisplay;
} else if (resourceRow.mode === "cidr") {
displayText = resourceRow.destination;
copyText = resourceRow.destination;
} else {
const destinationDisplay =
resourceRow.alias || resourceRow.destination;
displayText = destinationDisplay;
copyText = destinationDisplay;
}
return (
<CopyToClipboard
text={copyText}
text={resourceRow.destination}
isLink={false}
displayText={displayText}
displayText={resourceRow.destination}
/>
);
}
},
{
accessorKey: "alias",
friendlyName: t("resourcesTableAlias"),
header: () => (
<span className="p-3">{t("resourcesTableAlias")}</span>
),
cell: ({ row }) => {
const resourceRow = row.original;
return resourceRow.alias ? (
<CopyToClipboard
text={resourceRow.alias}
isLink={false}
displayText={resourceRow.alias}
/>
) : (
<span>"-"</span>
);
}
},
{
id: "actions",
enableHiding: false,

View File

@@ -528,7 +528,7 @@ export default function CreateInternalResourceDialog({
</FormItem>
)}
/>
{/*
{/*
{mode === "port" && (
<>
<div className="grid grid-cols-2 gap-4">
@@ -811,7 +811,7 @@ export default function CreateInternalResourceDialog({
render={({ field }) => (
<FormItem className="flex flex-col items-start">
<FormLabel>
{t("clients")}
{t("machineClients")}
</FormLabel>
<FormControl>
<TagInput
@@ -861,12 +861,6 @@ export default function CreateInternalResourceDialog({
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"resourceClientDescription"
) ||
"Machine clients that can access this resource"}
</FormDescription>
</FormItem>
)}
/>

View File

@@ -45,7 +45,7 @@ import { ListClientsResponse } from "@server/routers/client/listClients";
import { Tag, TagInput } from "@app/components/tags/tag-input";
import { AxiosResponse } from "axios";
import { UserType } from "@server/types/UserTypes";
import { useQueries, useQuery } from "@tanstack/react-query";
import { useQueries, useQuery, useQueryClient } from "@tanstack/react-query";
import { orgQueries, resourceQueries } from "@app/lib/queries";
type InternalResourceData = {
@@ -80,6 +80,7 @@ export default function EditInternalResourceDialog({
}: EditInternalResourceDialogProps) {
const t = useTranslations();
const api = createApiClient(useEnvContext());
const queryClient = useQueryClient();
const [isSubmitting, setIsSubmitting] = useState(false);
const formSchema = z.object({
@@ -310,6 +311,16 @@ export default function EditInternalResourceDialog({
// })
// ]);
await queryClient.invalidateQueries(
resourceQueries.resourceRoles({ resourceId: resource.id })
);
await queryClient.invalidateQueries(
resourceQueries.resourceUsers({ resourceId: resource.id })
);
await queryClient.invalidateQueries(
resourceQueries.resourceClients({ resourceId: resource.id })
);
toast({
title: t("editInternalResourceDialogSuccess"),
description: t(
@@ -367,6 +378,8 @@ export default function EditInternalResourceDialog({
users: [],
clients: []
});
// Reset initialization flag so form can re-initialize with fresh data when reopened
hasInitialized.current = false;
}
setOpen(open);
}}
@@ -730,7 +743,7 @@ export default function EditInternalResourceDialog({
render={({ field }) => (
<FormItem className="flex flex-col items-start">
<FormLabel>
{t("clients")}
{t("machineClients")}
</FormLabel>
<FormControl>
<TagInput
@@ -780,12 +793,6 @@ export default function EditInternalResourceDialog({
/>
</FormControl>
<FormMessage />
<FormDescription>
{t(
"resourceClientDescription"
) ||
"Machine clients that can access this resource"}
</FormDescription>
</FormItem>
)}
/>

View File

@@ -80,17 +80,33 @@ export default function HealthCheckDialog({
hcMethod: z
.string()
.min(1, { message: t("healthCheckMethodRequired") }),
hcInterval: z.int()
hcInterval: z
.int()
.positive()
.min(5, { message: t("healthCheckIntervalMin") }),
hcTimeout: z.int()
hcTimeout: z
.int()
.positive()
.min(1, { message: t("healthCheckTimeoutMin") }),
hcStatus: z.int().positive().min(100).optional().nullable(),
hcHeaders: z.array(z.object({ name: z.string(), value: z.string() })).nullable().optional(),
hcHeaders: z
.array(z.object({ name: z.string(), value: z.string() }))
.nullable()
.optional(),
hcScheme: z.string().optional(),
hcHostname: z.string(),
hcPort: z.number().positive().gt(0).lte(65535),
hcPort: z
.string()
.min(1, { message: t("healthCheckPortInvalid") })
.refine(
(val) => {
const port = parseInt(val);
return port > 0 && port <= 65535;
},
{
message: t("healthCheckPortInvalid")
}
),
hcFollowRedirects: z.boolean(),
hcMode: z.string(),
hcUnhealthyInterval: z.int().positive().min(5)
@@ -126,7 +142,9 @@ export default function HealthCheckDialog({
hcHeaders: initialConfig?.hcHeaders,
hcScheme: getDefaultScheme(),
hcHostname: initialConfig?.hcHostname,
hcPort: initialConfig?.hcPort,
hcPort: initialConfig?.hcPort
? initialConfig.hcPort.toString()
: "",
hcFollowRedirects: initialConfig?.hcFollowRedirects,
hcMode: initialConfig?.hcMode,
hcUnhealthyInterval: initialConfig?.hcUnhealthyInterval
@@ -139,10 +157,15 @@ export default function HealthCheckDialog({
try {
const currentValues = form.getValues();
const updatedValues = { ...currentValues, [fieldName]: value };
await onChanges({
// Convert hcPort from string to number before passing to parent
const configToSend: HealthCheckConfig = {
...updatedValues,
hcPort: parseInt(updatedValues.hcPort),
hcStatus: updatedValues.hcStatus || null
});
};
await onChanges(configToSend);
} catch (error) {
toast({
title: t("healthCheckError"),
@@ -210,14 +233,20 @@ export default function HealthCheckDialog({
{t("healthScheme")}
</FormLabel>
<Select
onValueChange={(value) => {
field.onChange(value);
onValueChange={(
value
) => {
field.onChange(
value
);
handleFieldChange(
"hcScheme",
value
);
}}
defaultValue={field.value}
defaultValue={
field.value
}
>
<FormControl>
<SelectTrigger>
@@ -281,10 +310,8 @@ export default function HealthCheckDialog({
{...field}
onChange={(e) => {
const value =
parseInt(
e.target
.value
);
e.target
.value;
field.onChange(
value
);
@@ -483,10 +510,6 @@ export default function HealthCheckDialog({
</FormItem>
)}
/>
<FormDescription>
{t("timeIsInSeconds")}
</FormDescription>
</div>
{/* Expected Response Codes */}
@@ -544,7 +567,9 @@ export default function HealthCheckDialog({
<HeadersInput
value={field.value}
onChange={(value) => {
field.onChange(value);
field.onChange(
value
);
handleFieldChange(
"hcHeaders",
value

View File

@@ -361,6 +361,15 @@ export default function LoginForm({
return (
<div className="space-y-4">
{forceLogin && (
<Alert variant="neutral">
<AlertDescription className="flex items-center gap-2">
<LockIcon className="w-4 h-4" />
{t("loginRequiredForDevice")}
</AlertDescription>
</Alert>
)}
{showSecurityKeyPrompt && (
<Alert>
<FingerprintIcon className="w-5 h-5 mr-2" />

View File

@@ -493,7 +493,7 @@ export default function ProxyResourcesTable({
<DropdownMenuContent align="end">
<Link
className="block w-full"
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.nice}`}
href={`/${resourceRow.orgId}/settings/resources/proxy/${resourceRow.nice}`}
>
<DropdownMenuItem>
{t("viewSettings")}
@@ -512,7 +512,7 @@ export default function ProxyResourcesTable({
</DropdownMenuContent>
</DropdownMenu>
<Link
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.nice}`}
href={`/${resourceRow.orgId}/settings/resources/proxy/${resourceRow.nice}`}
>
<Button variant={"outline"}>
{t("edit")}

View File

@@ -124,7 +124,7 @@ export default function ShareLinksTable({
cell: ({ row }) => {
const r = row.original;
return (
<Link href={`/${orgId}/settings/resources/${r.resourceNiceId}`}>
<Link href={`/${orgId}/settings/resources/proxy/${r.resourceNiceId}`}>
<Button variant="outline">
{r.resourceName}
<ArrowUpRight className="ml-2 h-4 w-4" />

View File

@@ -19,7 +19,7 @@ export function TanstackQueryProvider({ children }: ReactQueryProviderProps) {
defaultOptions: {
queries: {
retry: 2, // retry twice by default
staleTime: durationToMs(5, "minutes"),
staleTime: 0,
meta: {
api
}

View File

@@ -219,7 +219,7 @@ export const resourceQueries = {
}),
resourceClients: ({ resourceId }: { resourceId: number }) =>
queryOptions({
queryKey: ["RESOURCES", resourceId, "ROLES"] as const,
queryKey: ["RESOURCES", resourceId, "CLIENTS"] as const,
queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get<
AxiosResponse<ListSiteResourceClientsResponse>