add snippets to create resource

This commit is contained in:
miloschwartz
2025-04-28 21:45:43 -04:00
parent 599d0a52bf
commit 18e6f16ce7
5 changed files with 634 additions and 496 deletions

View File

@@ -137,7 +137,8 @@ LQIDAQAB
hostId: this.hostId, hostId: this.hostId,
isHostLicensed: true, isHostLicensed: true,
isLicenseValid: false, isLicenseValid: false,
maxSites: undefined maxSites: undefined,
usedSites: siteCount.value
}; };
try { try {

View File

@@ -67,6 +67,12 @@ import { SwitchInput } from "@app/components/SwitchInput";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { isTargetValid } from "@server/lib/validators"; import { isTargetValid } from "@server/lib/validators";
import { tlsNameSchema } from "@server/lib/schemas"; import { tlsNameSchema } from "@server/lib/schemas";
import { ChevronsUpDown } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger
} from "@app/components/ui/collapsible";
const addTargetSchema = z.object({ const addTargetSchema = z.object({
ip: z.string().refine(isTargetValid), ip: z.string().refine(isTargetValid),
@@ -145,6 +151,7 @@ export default function ReverseProxyTargets(props: {
const [proxySettingsLoading, setProxySettingsLoading] = useState(false); const [proxySettingsLoading, setProxySettingsLoading] = useState(false);
const [pageLoading, setPageLoading] = useState(true); const [pageLoading, setPageLoading] = useState(true);
const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
const router = useRouter(); const router = useRouter();
const addTargetForm = useForm({ const addTargetForm = useForm({
@@ -589,26 +596,57 @@ export default function ReverseProxyTargets(props: {
</FormItem> </FormItem>
)} )}
/> />
<Collapsible
open={isAdvancedOpen}
onOpenChange={setIsAdvancedOpen}
className="space-y-2"
>
<div className="flex items-center justify-between space-x-4">
<CollapsibleTrigger asChild>
<Button
variant="text"
size="sm"
className="p-0 flex items-center justify-start gap-2 w-full"
>
<h4 className="text-sm font-semibold">
Advanced TLS Settings
</h4>
<div>
<ChevronsUpDown className="h-4 w-4" />
<span className="sr-only">
Toggle
</span>
</div>
</Button>
</CollapsibleTrigger>
</div>
<CollapsibleContent className="space-y-2">
<FormField <FormField
control={tlsSettingsForm.control} control={
tlsSettingsForm.control
}
name="tlsServerName" name="tlsServerName"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
TLS Server Name (SNI) TLS Server Name
(SNI)
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input {...field} />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
The TLS Server Name to use The TLS Server Name
for SNI. Leave empty to use to use for SNI.
Leave empty to use
the default. the default.
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
</CollapsibleContent>
</Collapsible>
</form> </form>
</Form> </Form>
</SettingsSectionForm> </SettingsSectionForm>

View File

@@ -59,6 +59,9 @@ import {
} from "@app/components/ui/popover"; } from "@app/components/ui/popover";
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons"; import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import { cn } from "@app/lib/cn"; import { cn } from "@app/lib/cn";
import { SquareArrowOutUpRight } from "lucide-react";
import CopyTextBox from "@app/components/CopyTextBox";
import Link from "next/link";
const baseResourceFormSchema = z.object({ const baseResourceFormSchema = z.object({
name: z.string().min(1).max(255), name: z.string().min(1).max(255),
@@ -108,6 +111,8 @@ export default function Page() {
{ domainId: string; baseDomain: string }[] { domainId: string; baseDomain: string }[]
>([]); >([]);
const [createLoading, setCreateLoading] = useState(false); const [createLoading, setCreateLoading] = useState(false);
const [showSnippets, setShowSnippets] = useState(false);
const [resourceId, setResourceId] = useState<number | null>(null);
const resourceTypes: ReadonlyArray<ResourceTypeOption> = [ const resourceTypes: ReadonlyArray<ResourceTypeOption> = [
{ {
@@ -202,7 +207,14 @@ export default function Page() {
if (res && res.status === 201) { if (res && res.status === 201) {
const id = res.data.data.resourceId; const id = res.data.data.resourceId;
setResourceId(id);
if (isHttp) {
router.push(`/${orgId}/settings/resources/${id}`); router.push(`/${orgId}/settings/resources/${id}`);
} else {
setShowSnippets(true);
router.refresh();
}
} }
} catch (e) { } catch (e) {
console.error("Error creating resource:", e); console.error("Error creating resource:", e);
@@ -301,6 +313,7 @@ export default function Page() {
{!loadingPage && ( {!loadingPage && (
<div> <div>
{!showSnippets ? (
<SettingsContainer> <SettingsContainer>
<SettingsSection> <SettingsSection>
<SettingsSectionHeader> <SettingsSectionHeader>
@@ -324,13 +337,15 @@ export default function Page() {
Name Name
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input {...field} /> <Input
{...field}
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
This is the display This is the
name for the display name for
resource. the resource.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@@ -421,8 +436,9 @@ export default function Page() {
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
This site will This site will
provide connectivity provide
to the resource. connectivity to
the resource.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@@ -483,10 +499,13 @@ export default function Page() {
httpForm.control httpForm.control
} }
name="isBaseDomain" name="isBaseDomain"
render={({ field }) => ( render={({
field
}) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Domain Type Domain
Type
</FormLabel> </FormLabel>
<Select <Select
value={ value={
@@ -608,9 +627,10 @@ export default function Page() {
</div> </div>
</div> </div>
<FormDescription> <FormDescription>
The subdomain where The subdomain
your resource will where your
be accessible. resource will be
accessible.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@@ -623,10 +643,13 @@ export default function Page() {
httpForm.control httpForm.control
} }
name="domainId" name="domainId"
render={({ field }) => ( render={({
field
}) => (
<FormItem> <FormItem>
<FormLabel> <FormLabel>
Base Domain Base
Domain
</FormLabel> </FormLabel>
<Select <Select
onValueChange={ onValueChange={
@@ -692,7 +715,9 @@ export default function Page() {
id="tcp-udp-settings-form" id="tcp-udp-settings-form"
> >
<Controller <Controller
control={tcpUdpForm.control} control={
tcpUdpForm.control
}
name="protocol" name="protocol"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
@@ -725,7 +750,9 @@ export default function Page() {
/> />
<FormField <FormField
control={tcpUdpForm.control} control={
tcpUdpForm.control
}
name="proxyPort" name="proxyPort"
render={({ field }) => ( render={({ field }) => (
<FormItem> <FormItem>
@@ -759,8 +786,9 @@ export default function Page() {
<FormMessage /> <FormMessage />
<FormDescription> <FormDescription>
The external The external
port number to port number
proxy requests. to proxy
requests.
</FormDescription> </FormDescription>
</FormItem> </FormItem>
)} )}
@@ -771,7 +799,6 @@ export default function Page() {
</SettingsSectionBody> </SettingsSectionBody>
</SettingsSection> </SettingsSection>
)} )}
</SettingsContainer>
<div className="flex justify-end space-x-2 mt-8"> <div className="flex justify-end space-x-2 mt-8">
<Button <Button
@@ -801,6 +828,81 @@ export default function Page() {
Create Resource Create Resource
</Button> </Button>
</div> </div>
</SettingsContainer>
) : (
<SettingsContainer>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Configuration Snippets
</SettingsSectionTitle>
<SettingsSectionDescription>
Copy and paste these configuration snippets to set up your TCP/UDP resource
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<div className="space-y-6">
<div className="space-y-4">
<h3 className="text-lg font-semibold">
Traefik: Add Entrypoints
</h3>
<CopyTextBox
text={`entryPoints:
${tcpUdpForm.getValues("protocol")}-${tcpUdpForm.getValues("proxyPort")}:
address: ":${tcpUdpForm.getValues("proxyPort")}/${tcpUdpForm.getValues("protocol")}"`}
wrapText={false}
/>
</div>
<div className="space-y-4">
<h3 className="text-lg font-semibold">
Gerbil: Expose Ports in Docker Compose
</h3>
<CopyTextBox
text={`ports:
- ${tcpUdpForm.getValues("proxyPort")}:${tcpUdpForm.getValues("proxyPort")}${tcpUdpForm.getValues("protocol") === "tcp" ? "" : "/" + tcpUdpForm.getValues("protocol")}`}
wrapText={false}
/>
</div>
<Link
className="text-sm text-primary flex items-center gap-1"
href="https://docs.fossorial.io/Pangolin/tcp-udp"
target="_blank"
rel="noopener noreferrer"
>
<span>
Learn how to configure TCP/UDP resources
</span>
<SquareArrowOutUpRight size={14} />
</Link>
</div>
</SettingsSectionBody>
</SettingsSection>
<div className="flex justify-end space-x-2 mt-8">
<Button
type="button"
variant="outline"
onClick={() =>
router.push(`/${orgId}/settings/resources`)
}
>
Back to Resources
</Button>
<Button
type="button"
onClick={() =>
router.push(
`/${orgId}/settings/resources/${resourceId}`
)
}
>
Go to Resource
</Button>
</div>
</SettingsContainer>
)}
</div> </div>
)} )}
</> </>

View File

@@ -57,9 +57,7 @@ import {
import { CheckIcon, ChevronsUpDown } from "lucide-react"; import { CheckIcon, ChevronsUpDown } from "lucide-react";
import { Checkbox } from "@app/components/ui/checkbox"; import { Checkbox } from "@app/components/ui/checkbox";
import { GenerateAccessTokenResponse } from "@server/routers/accessToken"; import { GenerateAccessTokenResponse } from "@server/routers/accessToken";
import { import { constructShareLink } from "@app/lib/shareLinks";
constructShareLink
} from "@app/lib/shareLinks";
import { ShareLinkRow } from "./ShareLinksTable"; import { ShareLinkRow } from "./ShareLinksTable";
import { QRCodeCanvas, QRCodeSVG } from "qrcode.react"; import { QRCodeCanvas, QRCodeSVG } from "qrcode.react";
import { import {
@@ -528,11 +526,9 @@ export default function CreateShareLinkForm({
accessTokenId accessTokenId
} }
token={accessToken} token={accessToken}
resourceUrl={ resourceUrl={form.getValues(
form.getValues(
"resourceUrl" "resourceUrl"
) )}
}
/> />
</div> </div>
</div> </div>

View File

@@ -33,9 +33,10 @@ export default function LicenseViolation() {
return ( return (
<div className="fixed bottom-0 left-0 right-0 w-full bg-yellow-500 text-black p-4 text-center z-50"> <div className="fixed bottom-0 left-0 right-0 w-full bg-yellow-500 text-black p-4 text-center z-50">
<p> <p>
License Violation: Using {licenseStatus.usedSites} sites License Violation: This server is using{" "}
exceeds your licensed limit of {licenseStatus.maxSites}{" "} {licenseStatus.usedSites} sites which exceeds its licensed
sites. Follow license terms to continue using all features. limit of {licenseStatus.maxSites} sites. Follow license
terms to continue using all features.
</p> </p>
</div> </div>
); );