Merge remote-tracking branch 'upstream/dev' into feature-i18n

This commit is contained in:
Marvin
2025-06-05 17:27:13 +00:00
9 changed files with 300 additions and 3087 deletions

View File

@@ -33,7 +33,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
{t('resourceInfo')}
</AlertTitle>
<AlertDescription className="mt-4">
<InfoSections cols={isEnabled ? 5 : 4}>
<InfoSections cols={4}>
{resource.http ? (
<>
<InfoSection>

View File

@@ -60,7 +60,8 @@ import {
SettingsSectionDescription,
SettingsSectionBody,
SettingsSectionFooter,
SettingsSectionForm
SettingsSectionForm,
SettingsSectionGrid
} from "@app/components/Settings";
import { SwitchInput } from "@app/components/SwitchInput";
import { useRouter } from "next/navigation";
@@ -73,6 +74,7 @@ import {
CollapsibleTrigger
} from "@app/components/ui/collapsible";
import { ContainersSelector } from "@app/components/ContainersSelector";
import { FaDocker } from "react-icons/fa";
import { useTranslations } from "next-intl";
const addTargetSchema = z.object({
@@ -770,8 +772,7 @@ export default function ReverseProxyTargets(props: {
<FormControl>
<Input id="ip" {...field} />
</FormControl>
<FormMessage />
{site && site.type == 'newt' && (
{site && site.type == "newt" && (
<ContainersSelector
site={site}
onContainerSelect={(
@@ -791,6 +792,7 @@ export default function ReverseProxyTargets(props: {
}}
/>
)}
<FormMessage />
</FormItem>
)}
/>
@@ -885,57 +887,175 @@ export default function ReverseProxyTargets(props: {
</SettingsSection>
{resource.http && (
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('proxyAdditional')}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('proxyAdditionalDescription')}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm>
<Form {...proxySettingsForm}>
<form
onSubmit={proxySettingsForm.handleSubmit(
saveProxySettings
)}
className="space-y-4"
id="proxy-settings-form"
>
<FormField
control={proxySettingsForm.control}
name="setHostHeader"
render={({ field }) => (
<FormItem>
<FormLabel>
{t('proxyCustomHeader')}
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
{t('proxyCustomHeaderDescription')}
</FormDescription>
<FormMessage />
</FormItem>
<SettingsSectionGrid cols={2}>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Secure Connection Configuration
</SettingsSectionTitle>
<SettingsSectionDescription>
Configure SSL/TLS settings for your resource
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm>
<Form {...tlsSettingsForm}>
<form
onSubmit={tlsSettingsForm.handleSubmit(
saveTlsSettings
)}
/>
</form>
</Form>
</SettingsSectionForm>
</SettingsSectionBody>
<SettingsSectionFooter>
<Button
type="submit"
loading={proxySettingsLoading}
form="proxy-settings-form"
>
{t('proxyAdditionalSubmit')}
</Button>
</SettingsSectionFooter>
</SettingsSection>
className="space-y-4"
id="tls-settings-form"
>
<FormField
control={tlsSettingsForm.control}
name="ssl"
render={({ field }) => (
<FormItem>
<FormControl>
<SwitchInput
id="ssl-toggle"
label="Enable SSL (https)"
defaultChecked={
field.value
}
onCheckedChange={(
val
) => {
field.onChange(
val
);
}}
/>
</FormControl>
</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"
>
<p className="text-sm text-muted-foreground">
Advanced TLS
Settings
</p>
<div>
<ChevronsUpDown className="h-4 w-4" />
<span className="sr-only">
Toggle
</span>
</div>
</Button>
</CollapsibleTrigger>
</div>
<CollapsibleContent className="space-y-2">
<FormField
control={
tlsSettingsForm.control
}
name="tlsServerName"
render={({ field }) => (
<FormItem>
<FormLabel>
TLS Server Name
(SNI)
</FormLabel>
<FormControl>
<Input
{...field}
/>
</FormControl>
<FormDescription>
The TLS Server
Name to use for
SNI. Leave empty
to use the
default.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</CollapsibleContent>
</Collapsible>
</form>
</Form>
</SettingsSectionForm>
</SettingsSectionBody>
<SettingsSectionFooter>
<Button
type="submit"
loading={httpsTlsLoading}
form="tls-settings-form"
>
Save Settings
</Button>
</SettingsSectionFooter>
</SettingsSection>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Additional Proxy Settings
</SettingsSectionTitle>
<SettingsSectionDescription>
Configure how your resource handles proxy
settings
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm>
<Form {...proxySettingsForm}>
<form
onSubmit={proxySettingsForm.handleSubmit(
saveProxySettings
)}
className="space-y-4"
id="proxy-settings-form"
>
<FormField
control={proxySettingsForm.control}
name="setHostHeader"
render={({ field }) => (
<FormItem>
<FormLabel>
Custom Host Header
</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormDescription>
The host header to set
when proxying requests.
Leave empty to use the
default.
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</SettingsSectionForm>
</SettingsSectionBody>
<SettingsSectionFooter>
<Button
type="submit"
loading={proxySettingsLoading}
form="proxy-settings-form"
>
Save Settings
</Button>
</SettingsSectionFooter>
</SettingsSection>
</SettingsSectionGrid>
)}
</SettingsContainer>
);

View File

@@ -287,15 +287,15 @@ export default function LicensePage() {
<div className="space-y-1 leading-none">
<FormLabel>
{t('licenseAgreement')}
<br />
<Link
href="https://fossorial.io/license.html"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline"
>
{t('fossorialLicense')}
</Link>
{/* <br /> */}
{/* <Link */}
{/* href="https://fossorial.io/license.html" */}
{/* target="_blank" */}
{/* rel="noopener noreferrer" */}
{/* className="text-primary hover:underline" */}
{/* > */}
{/* {t('fossorialLicense')} */}
{/* </Link> */}
</FormLabel>
<FormMessage />
</div>
@@ -484,32 +484,32 @@ export default function LicensePage() {
</div>
)}
</div>
<SettingsSectionFooter>
{!licenseStatus?.isHostLicensed ? (
<>
<Button
onClick={() => {
setPurchaseMode("license");
setIsPurchaseModalOpen(true);
}}
>
{t('licensePurchase')}
</Button>
</>
) : (
<>
<Button
variant="outline"
onClick={() => {
setPurchaseMode("additional-sites");
setIsPurchaseModalOpen(true);
}}
>
{t('licensePurchaseSites')}
</Button>
</>
)}
</SettingsSectionFooter>
{/* <SettingsSectionFooter> */}
{/* {!licenseStatus?.isHostLicensed ? ( */}
{/* <> */}
{/* <Button */}
{/* onClick={() => { */}
{/* setPurchaseMode("license"); */}
{/* setIsPurchaseModalOpen(true); */}
{/* }} */}
{/* > */}
{/* {t('licensePurchase')} */}
{/* </Button> */}
{/* </> */}
{/* ) : ( */}
{/* <> */}
{/* <Button */}
{/* variant="outline" */}
{/* onClick={() => { */}
{/* setPurchaseMode("additional-sites"); */}
{/* setIsPurchaseModalOpen(true); */}
{/* }} */}
{/* > */}
{/* {t('licensePurchaseSites')} */}
{/* </Button> */}
{/* </> */}
{/* )} */}
{/* </SettingsSectionFooter> */}
</SettingsSection>
</SettingsSectionGrid>
<LicenseKeysDataTable

View File

@@ -9,23 +9,15 @@ import {
} from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger
} from "@/components/ui/dialog";
import {
Drawer,
DrawerClose,
DrawerContent,
DrawerDescription,
DrawerFooter,
DrawerHeader,
DrawerTitle,
DrawerTrigger
} from "@/components/ui/drawer";
Credenza,
CredenzaBody,
CredenzaClose,
CredenzaContent,
CredenzaDescription,
CredenzaFooter,
CredenzaHeader,
CredenzaTitle
} from "@/components/Credenza";
import {
Table,
TableBody,
@@ -53,7 +45,7 @@ import { ScrollArea } from "@/components/ui/scroll-area";
import { Search, RefreshCw, Filter, Columns } from "lucide-react";
import { GetSiteResponse, Container } from "@server/routers/site";
import { useDockerSocket } from "@app/hooks/useDockerSocket";
import { useMediaQuery } from "@app/hooks/useMediaQuery";
import { FaDocker } from "react-icons/fa";
// Type definitions based on the JSON structure
@@ -67,13 +59,11 @@ export const ContainersSelector: FC<ContainerSelectorProps> = ({
onContainerSelect
}) => {
const [open, setOpen] = useState(false);
const isDesktop = useMediaQuery("(min-width: 768px)");
const { isAvailable, containers, fetchContainers } = useDockerSocket(
site
);
const { isAvailable, containers, fetchContainers } = useDockerSocket(site);
useEffect(() => {
console.log("DockerSocket isAvailable:", isAvailable);
if (isAvailable) {
fetchContainers();
}
@@ -90,76 +80,41 @@ export const ContainersSelector: FC<ContainerSelectorProps> = ({
setOpen(false);
};
if (isDesktop) {
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild>
<Button
type="button"
variant="squareOutline"
size="icon"
className="absolute top-[35%] right-0"
>
<span className="scale-125">🐋</span>
</Button>
</DialogTrigger>
<DialogContent className="max-w-[75vw] max-h-[75vh] flex flex-col">
<DialogHeader>
<DialogTitle>
Containers in <b>{site.name}</b>
</DialogTitle>
<DialogDescription>
Select any container (w/ port) to use as target for
your resource
</DialogDescription>
</DialogHeader>
<div className="flex-1 overflow-hidden min-h-0">
<DockerContainersTable
containers={containers}
onContainerSelect={handleContainerSelect}
onRefresh={() => fetchContainers()}
/>
</div>
</DialogContent>
</Dialog>
);
}
return (
<Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>
<Button
type="button"
variant="squareOutline"
size="icon"
className="absolute top-[35%] right-0"
>
<span className="scale-125">🐋</span>
</Button>
</DrawerTrigger>
<DrawerContent>
<DrawerHeader className="text-left">
<DrawerTitle>
Containers in <b>{site.name}</b>
</DrawerTitle>
<DrawerDescription>
Select any container to use as target for your resource
</DrawerDescription>
</DrawerHeader>
<div className="px-4">
<DockerContainersTable
containers={containers}
onContainerSelect={handleContainerSelect}
onRefresh={fetchContainers}
/>
</div>
<DrawerFooter className="pt-2">
<DrawerClose asChild>
<Button variant="outline">Cancel</Button>
</DrawerClose>
</DrawerFooter>
</DrawerContent>
</Drawer>
<>
<a
type="button"
className="text-sm text-primary hover:underline cursor-pointer"
onClick={() => setOpen(true)}
>
View Docker Containers
</a>
<Credenza open={open} onOpenChange={setOpen}>
<CredenzaContent className="max-w-[75vw] max-h-[75vh] flex flex-col">
<CredenzaHeader>
<CredenzaTitle>Containers in {site.name}</CredenzaTitle>
<CredenzaDescription>
Select any container to use as a hostname for this
target. Click a port to use select a port.
</CredenzaDescription>
</CredenzaHeader>
<CredenzaBody>
<div className="flex-1 overflow-hidden min-h-0">
<DockerContainersTable
containers={containers}
onContainerSelect={handleContainerSelect}
onRefresh={() => fetchContainers()}
/>
</div>
</CredenzaBody>
<CredenzaFooter>
<CredenzaClose asChild>
<Button variant="outline">Close</Button>
</CredenzaClose>
</CredenzaFooter>
</CredenzaContent>
</Credenza>
</>
);
};
@@ -446,7 +401,7 @@ const DockerContainersTable: FC<{
if (initialFilters.length === 0) {
return (
<div className="border rounded-md max-h-[500px] overflow-hidden flex flex-col">
<div className="rounded-md max-h-[500px] overflow-hidden flex flex-col">
<div className="flex-1 flex items-center justify-center py-8">
<div className="text-center text-muted-foreground space-y-3">
{(hideContainersWithoutPorts ||
@@ -497,8 +452,8 @@ const DockerContainersTable: FC<{
}
return (
<div className="border rounded-md max-h-[500px] overflow-hidden flex flex-col">
<div className="p-3 border-b bg-background space-y-3">
<div className="rounded-md max-h-[500px] overflow-hidden flex flex-col">
<div className="p-1 space-y-3">
<div className="flex items-center gap-2">
<div className="relative flex-1">
<Search className="absolute left-2 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
@@ -639,14 +594,11 @@ const DockerContainersTable: FC<{
</div>
<div className="overflow-auto relative flex-1">
<Table sticky>
<TableHeader sticky className="bg-background border-b">
<TableHeader sticky className="border-b">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => (
<TableHead
key={header.id}
className="bg-background"
>
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(