mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-10 04:36:38 +00:00
major ui tweaks and refactoring
This commit is contained in:
@@ -36,6 +36,9 @@ import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { SiteRow } from "./SitesTable";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { ArrowUpRight } from "lucide-react";
|
||||
|
||||
const createSiteFormSchema = z.object({
|
||||
name: z
|
||||
@@ -274,6 +277,24 @@ PersistentKeepalive = 5`
|
||||
You will only be able to see the configuration once.
|
||||
</span>
|
||||
|
||||
{form.watch("method") === "newt" && (
|
||||
<>
|
||||
<br />
|
||||
<Link
|
||||
className="text-sm text-primary flex items-center gap-1"
|
||||
href="https://docs.fossorial.io/Newt/install"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>
|
||||
{" "}
|
||||
Learn how to install Newt on your system
|
||||
</span>
|
||||
<ArrowUpRight className="w-5 h-5" />
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Checkbox
|
||||
id="terms"
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
@@ -89,7 +90,7 @@ export function SitesDataTable<TData, TValue>({
|
||||
<Plus className="mr-2 h-4 w-4" /> Add Site
|
||||
</Button>
|
||||
</div>
|
||||
<div className="border rounded-md">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
@@ -141,7 +142,7 @@ export function SitesDataTable<TData, TValue>({
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</TableContainer>
|
||||
<div className="mt-4">
|
||||
<DataTablePagination table={table} />
|
||||
</div>
|
||||
|
||||
98
src/app/[orgId]/settings/sites/SitesSplashCard.tsx
Normal file
98
src/app/[orgId]/settings/sites/SitesSplashCard.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowRight, DockIcon as Docker, Globe, Server, X } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
export const SitesSplashCard = () => {
|
||||
const [isDismissed, setIsDismissed] = useState(true);
|
||||
|
||||
const key = "sites-splash-card-dismissed";
|
||||
|
||||
useEffect(() => {
|
||||
const dismissed = localStorage.getItem(key);
|
||||
if (dismissed === "true") {
|
||||
setIsDismissed(true);
|
||||
} else {
|
||||
setIsDismissed(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDismiss = () => {
|
||||
setIsDismissed(true);
|
||||
localStorage.setItem(key, "true");
|
||||
};
|
||||
|
||||
if (isDismissed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full mx-auto overflow-hidden mb-8 hidden md:block relative">
|
||||
<button
|
||||
onClick={handleDismiss}
|
||||
className="absolute top-2 right-2 p-2"
|
||||
aria-label="Dismiss"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
<CardContent className="grid gap-6 p-6 sm:grid-cols-2">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-2">
|
||||
<Globe className="text-blue-500" />
|
||||
Newt (Recommended)
|
||||
</h3>
|
||||
<p className="text-sm">
|
||||
For the best user experience, use Newt. It uses
|
||||
WireGuard under the hood and allows you to address your
|
||||
private resources by their LAN address on your private
|
||||
network from within the Pangolin dashboard.
|
||||
</p>
|
||||
<ul className="text-sm text-muted-foreground space-y-2">
|
||||
<li className="flex items-center gap-2">
|
||||
<Server className="text-green-500 w-4 h-4" />
|
||||
Runs in Docker
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Server className="text-green-500 w-4 h-4" />
|
||||
Runs in shell on macOS, Linux, and Windows
|
||||
</li>
|
||||
</ul>
|
||||
<Button className="w-full" variant="secondary">
|
||||
<Link
|
||||
href="https://docs.fossorial.io/Newt/install"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center"
|
||||
>
|
||||
Install Newt <ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-2">
|
||||
Basic WireGuard
|
||||
</h3>
|
||||
<p className="text-sm">
|
||||
Use any WireGuard client to connect. You will have to
|
||||
address your internal resources using the peer IP.
|
||||
</p>
|
||||
<ul className="text-sm text-muted-foreground space-y-2">
|
||||
<li className="flex items-center gap-2">
|
||||
<Docker className="text-purple-500 w-4 h-4" />
|
||||
Compatible with all WireGuard clients
|
||||
</li>
|
||||
<li className="flex items-center gap-2">
|
||||
<Server className="text-purple-500 w-4 h-4" />
|
||||
Manual configuration required
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default SitesSplashCard;
|
||||
@@ -256,7 +256,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
<Link
|
||||
href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
|
||||
>
|
||||
<Button variant={"gray"} className="ml-2">
|
||||
<Button variant={"outline"} className="ml-2">
|
||||
Edit
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
|
||||
@@ -10,20 +10,29 @@ import {
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
FormMessage
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { useSiteContext } from "@app/hooks/useSiteContext";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useToast } from "@app/hooks/useToast";
|
||||
import { useRouter } from "next/navigation";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import { formatAxiosError } from "@app/lib/api";;
|
||||
import {
|
||||
SettingsContainer,
|
||||
SettingsSection,
|
||||
SettingsSectionHeader,
|
||||
SettingsSectionTitle,
|
||||
SettingsSectionDescription,
|
||||
SettingsSectionBody,
|
||||
SettingsSectionForm,
|
||||
SettingsSectionFooter
|
||||
} from "@app/components/Settings";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
|
||||
const GeneralFormSchema = z.object({
|
||||
name: z.string(),
|
||||
name: z.string()
|
||||
});
|
||||
|
||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||
@@ -39,15 +48,15 @@ export default function GeneralPage() {
|
||||
const form = useForm<GeneralFormValues>({
|
||||
resolver: zodResolver(GeneralFormSchema),
|
||||
defaultValues: {
|
||||
name: site?.name,
|
||||
name: site?.name
|
||||
},
|
||||
mode: "onChange",
|
||||
mode: "onChange"
|
||||
});
|
||||
|
||||
async function onSubmit(data: GeneralFormValues) {
|
||||
await api
|
||||
.post(`/site/${site?.siteId}`, {
|
||||
name: data.name,
|
||||
name: data.name
|
||||
})
|
||||
.catch((e) => {
|
||||
toast({
|
||||
@@ -56,7 +65,7 @@ export default function GeneralPage() {
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while updating the site."
|
||||
),
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,39 +75,53 @@ export default function GeneralPage() {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4 max-w-xl">
|
||||
<SettingsSectionTitle
|
||||
title="General Settings"
|
||||
description="Configure the general settings for this site"
|
||||
size="1xl"
|
||||
/>
|
||||
<SettingsContainer>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
General Settings
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure the general settings for this site
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is the display name of the site
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit">Save Changes</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
</>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
id="general-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
This is the display name of the
|
||||
site
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<SettingsSectionFooter>
|
||||
<Button type="submit" form="general-settings-form">
|
||||
Save Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ListSitesResponse } from "@server/routers/site";
|
||||
import { AxiosResponse } from "axios";
|
||||
import SitesTable, { SiteRow } from "./SitesTable";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import SitesSplashCard from "./SitesSplashCard";
|
||||
|
||||
type SitesPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -47,6 +48,8 @@ export default async function SitesPage(props: SitesPageProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SitesSplashCard />
|
||||
|
||||
<SettingsSectionTitle
|
||||
title="Manage Sites"
|
||||
description="Allow connectivity to your network through secure tunnels"
|
||||
|
||||
Reference in New Issue
Block a user