mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-25 14:26:39 +00:00
Basic clients working
This commit is contained in:
@@ -48,7 +48,7 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title="Manage Clients"
|
||||
title="Manage Clients (beta)"
|
||||
description="Clients are devices that can connect to your sites"
|
||||
/>
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useEffect, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { RotateCw } from "lucide-react";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { build } from "@server/build";
|
||||
|
||||
type ResourceInfoBoxType = {};
|
||||
|
||||
@@ -34,7 +35,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
<Alert>
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
{t('resourceInfo')}
|
||||
{t("resourceInfo")}
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mt-4">
|
||||
<InfoSections cols={4}>
|
||||
@@ -42,7 +43,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
<>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t('authentication')}
|
||||
{t("authentication")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{authInfo.password ||
|
||||
@@ -51,12 +52,12 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
authInfo.whitelist ? (
|
||||
<div className="flex items-start space-x-2 text-green-500">
|
||||
<ShieldCheck className="w-4 h-4 mt-0.5" />
|
||||
<span>{t('protected')}</span>
|
||||
<span>{t("protected")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center space-x-2 text-yellow-500">
|
||||
<ShieldOff className="w-4 h-4" />
|
||||
<span>{t('notProtected')}</span>
|
||||
<span>{t("notProtected")}</span>
|
||||
</div>
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
@@ -71,7 +72,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t('site')}</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t("site")}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{resource.siteName}
|
||||
</InfoSectionContent>
|
||||
@@ -98,7 +99,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
) : (
|
||||
<>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t('protocol')}</InfoSectionTitle>
|
||||
<InfoSectionTitle>
|
||||
{t("protocol")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<span>
|
||||
{resource.protocol.toUpperCase()}
|
||||
@@ -106,7 +109,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t('port')}</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t("port")}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard
|
||||
text={resource.proxyPort!.toString()}
|
||||
@@ -114,13 +117,29 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
/>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
{build == "oss" && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
{t("externalProxyEnabled")}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<span>
|
||||
{resource.enableProxy
|
||||
? t("enabled")
|
||||
: t("disabled")}
|
||||
</span>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>{t('visibility')}</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t("visibility")}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<span>
|
||||
{resource.enabled ? t('enabled') : t('disabled')}
|
||||
{resource.enabled
|
||||
? t("enabled")
|
||||
: t("disabled")}
|
||||
</span>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
|
||||
@@ -66,6 +66,7 @@ import {
|
||||
} from "@server/routers/resource";
|
||||
import { SwitchInput } from "@app/components/SwitchInput";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Checkbox } from "@app/components/ui/checkbox";
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
@@ -78,6 +79,7 @@ import {
|
||||
} from "@app/components/Credenza";
|
||||
import DomainPicker from "@app/components/DomainPicker";
|
||||
import { Globe } from "lucide-react";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const TransferFormSchema = z.object({
|
||||
siteId: z.number()
|
||||
@@ -118,25 +120,31 @@ export default function GeneralForm() {
|
||||
fullDomain: string;
|
||||
} | null>(null);
|
||||
|
||||
const GeneralFormSchema = z.object({
|
||||
enabled: z.boolean(),
|
||||
subdomain: z.string().optional(),
|
||||
name: z.string().min(1).max(255),
|
||||
domainId: z.string().optional(),
|
||||
proxyPort: z.number().int().min(1).max(65535).optional()
|
||||
}).refine((data) => {
|
||||
// For non-HTTP resources, proxyPort should be defined
|
||||
if (!resource.http) {
|
||||
return data.proxyPort !== undefined;
|
||||
}
|
||||
// For HTTP resources, proxyPort should be undefined
|
||||
return data.proxyPort === undefined;
|
||||
}, {
|
||||
message: !resource.http
|
||||
? "Port number is required for non-HTTP resources"
|
||||
: "Port number should not be set for HTTP resources",
|
||||
path: ["proxyPort"]
|
||||
});
|
||||
const GeneralFormSchema = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
subdomain: z.string().optional(),
|
||||
name: z.string().min(1).max(255),
|
||||
domainId: z.string().optional(),
|
||||
proxyPort: z.number().int().min(1).max(65535).optional(),
|
||||
enableProxy: z.boolean().optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// For non-HTTP resources, proxyPort should be defined
|
||||
if (!resource.http) {
|
||||
return data.proxyPort !== undefined;
|
||||
}
|
||||
// For HTTP resources, proxyPort should be undefined
|
||||
return data.proxyPort === undefined;
|
||||
},
|
||||
{
|
||||
message: !resource.http
|
||||
? "Port number is required for non-HTTP resources"
|
||||
: "Port number should not be set for HTTP resources",
|
||||
path: ["proxyPort"]
|
||||
}
|
||||
);
|
||||
|
||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||
|
||||
@@ -147,7 +155,8 @@ export default function GeneralForm() {
|
||||
name: resource.name,
|
||||
subdomain: resource.subdomain ? resource.subdomain : undefined,
|
||||
domainId: resource.domainId || undefined,
|
||||
proxyPort: resource.proxyPort || undefined
|
||||
proxyPort: resource.proxyPort || undefined,
|
||||
enableProxy: resource.enableProxy || false
|
||||
},
|
||||
mode: "onChange"
|
||||
});
|
||||
@@ -211,7 +220,8 @@ export default function GeneralForm() {
|
||||
name: data.name,
|
||||
subdomain: data.subdomain,
|
||||
domainId: data.domainId,
|
||||
proxyPort: data.proxyPort
|
||||
proxyPort: data.proxyPort,
|
||||
enableProxy: data.enableProxy
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
@@ -238,7 +248,8 @@ export default function GeneralForm() {
|
||||
name: data.name,
|
||||
subdomain: data.subdomain,
|
||||
fullDomain: resource.fullDomain,
|
||||
proxyPort: data.proxyPort
|
||||
proxyPort: data.proxyPort,
|
||||
enableProxy: data.enableProxy
|
||||
});
|
||||
|
||||
router.refresh();
|
||||
@@ -357,16 +368,29 @@ export default function GeneralForm() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("resourcePortNumber")}
|
||||
{t(
|
||||
"resourcePortNumber"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) =>
|
||||
value={
|
||||
field.value ??
|
||||
""
|
||||
}
|
||||
onChange={(
|
||||
e
|
||||
) =>
|
||||
field.onChange(
|
||||
e.target.value
|
||||
? parseInt(e.target.value)
|
||||
e
|
||||
.target
|
||||
.value
|
||||
? parseInt(
|
||||
e
|
||||
.target
|
||||
.value
|
||||
)
|
||||
: undefined
|
||||
)
|
||||
}
|
||||
@@ -374,11 +398,49 @@ export default function GeneralForm() {
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
{t("resourcePortNumberDescription")}
|
||||
{t(
|
||||
"resourcePortNumberDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{build == "oss" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enableProxy"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
variant={
|
||||
"outlinePrimarySquare"
|
||||
}
|
||||
checked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>
|
||||
{t(
|
||||
"resourceEnableProxy"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"resourceEnableProxyDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Input } from "@app/components/ui/input";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { Checkbox } from "@app/components/ui/checkbox";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { ListSitesResponse } from "@server/routers/site";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
@@ -64,6 +65,7 @@ import CopyTextBox from "@app/components/CopyTextBox";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from "next-intl";
|
||||
import DomainPicker from "@app/components/DomainPicker";
|
||||
import { build } from "@server/build";
|
||||
|
||||
const baseResourceFormSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
@@ -78,7 +80,8 @@ const httpResourceFormSchema = z.object({
|
||||
|
||||
const tcpUdpResourceFormSchema = z.object({
|
||||
protocol: z.string(),
|
||||
proxyPort: z.number().int().min(1).max(65535)
|
||||
proxyPort: z.number().int().min(1).max(65535),
|
||||
enableProxy: z.boolean().default(false)
|
||||
});
|
||||
|
||||
type BaseResourceFormValues = z.infer<typeof baseResourceFormSchema>;
|
||||
@@ -144,7 +147,8 @@ export default function Page() {
|
||||
resolver: zodResolver(tcpUdpResourceFormSchema),
|
||||
defaultValues: {
|
||||
protocol: "tcp",
|
||||
proxyPort: undefined
|
||||
proxyPort: undefined,
|
||||
enableProxy: false
|
||||
}
|
||||
});
|
||||
|
||||
@@ -163,16 +167,17 @@ export default function Page() {
|
||||
|
||||
if (isHttp) {
|
||||
const httpData = httpForm.getValues();
|
||||
Object.assign(payload, {
|
||||
subdomain: httpData.subdomain,
|
||||
domainId: httpData.domainId,
|
||||
protocol: "tcp",
|
||||
});
|
||||
Object.assign(payload, {
|
||||
subdomain: httpData.subdomain,
|
||||
domainId: httpData.domainId,
|
||||
protocol: "tcp"
|
||||
});
|
||||
} else {
|
||||
const tcpUdpData = tcpUdpForm.getValues();
|
||||
Object.assign(payload, {
|
||||
protocol: tcpUdpData.protocol,
|
||||
proxyPort: tcpUdpData.proxyPort
|
||||
proxyPort: tcpUdpData.proxyPort,
|
||||
enableProxy: tcpUdpData.enableProxy
|
||||
});
|
||||
}
|
||||
|
||||
@@ -198,8 +203,15 @@ export default function Page() {
|
||||
if (isHttp) {
|
||||
router.push(`/${orgId}/settings/resources/${id}`);
|
||||
} else {
|
||||
setShowSnippets(true);
|
||||
router.refresh();
|
||||
const tcpUdpData = tcpUdpForm.getValues();
|
||||
// Only show config snippets if enableProxy is explicitly true
|
||||
if (tcpUdpData.enableProxy === true) {
|
||||
setShowSnippets(true);
|
||||
router.refresh();
|
||||
} else {
|
||||
// If enableProxy is false or undefined, go directly to resource page
|
||||
router.push(`/${orgId}/settings/resources/${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -603,6 +615,46 @@ export default function Page() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{build == "oss" && (
|
||||
<FormField
|
||||
control={
|
||||
tcpUdpForm.control
|
||||
}
|
||||
name="enableProxy"
|
||||
render={({
|
||||
field
|
||||
}) => (
|
||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
variant={
|
||||
"outlinePrimarySquare"
|
||||
}
|
||||
checked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel>
|
||||
{t(
|
||||
"resourceEnableProxy"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"resourceEnableProxyDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
|
||||
Reference in New Issue
Block a user