mirror of
https://github.com/fosrl/pangolin.git
synced 2025-12-05 19:17:29 -06:00
Compare commits
15 Commits
77306e8c97
...
8ee4ee7baf
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ee4ee7baf | ||
|
|
b1b0702886 | ||
|
|
92aed108cd | ||
|
|
2dcc94cd14 | ||
|
|
a7185ff913 | ||
|
|
04e73515b8 | ||
|
|
2bad9daaea | ||
|
|
54670e150d | ||
|
|
761ed1de9a | ||
|
|
078692c818 | ||
|
|
54e2d95b55 | ||
|
|
ba09479827 | ||
|
|
1c5c36fc12 | ||
|
|
d37ff6e15b | ||
|
|
0ceed4c812 |
@@ -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
5649
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
|
||||
@@ -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++;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -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} */}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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() {
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }>;
|
||||
|
||||
@@ -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 {
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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`
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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`
|
||||
)
|
||||
}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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")}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user