mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-25 06:16:40 +00:00
Add first i18n stuff
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import { DataTable } from "@app/components/ui/data-table";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
@@ -16,15 +17,18 @@ export function ResourcesDataTable<TData, TValue>({
|
||||
data,
|
||||
createResource
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
title="Resources"
|
||||
searchPlaceholder="Search resources..."
|
||||
searchPlaceholder={t('resourceSearch')}
|
||||
searchColumn="name"
|
||||
onAdd={createResource}
|
||||
addButtonText="Add Resource"
|
||||
addButtonText={t('resourceAdd')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||
import { Switch } from "@app/components/ui/switch";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { UpdateResourceResponse } from "@server/routers/resource";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export type ResourceRow = {
|
||||
id: number;
|
||||
@@ -53,6 +54,7 @@ type ResourcesTableProps = {
|
||||
|
||||
export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
@@ -63,11 +65,11 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
const deleteResource = (resourceId: number) => {
|
||||
api.delete(`/resource/${resourceId}`)
|
||||
.catch((e) => {
|
||||
console.error("Error deleting resource", e);
|
||||
console.error(t('resourceErrorDelte'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error deleting resource",
|
||||
description: formatAxiosError(e, "Error deleting resource")
|
||||
title: t('resourceErrorDelte'),
|
||||
description: formatAxiosError(e, t('resourceErrorDelte'))
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@@ -108,7 +110,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||
<span className="sr-only">Open menu</span>
|
||||
<span className="sr-only">{t('openMenu')}</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -118,7 +120,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.id}`}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
View settings
|
||||
{t('viewSettings')}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
@@ -127,7 +129,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">Delete</span>
|
||||
<span className="text-red-500">{t('delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -144,7 +146,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Name
|
||||
{t('name')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -160,7 +162,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Site
|
||||
{t('site')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -219,7 +221,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Authentication
|
||||
{t('authentication')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -231,12 +233,12 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
{resourceRow.authState === "protected" ? (
|
||||
<span className="text-green-500 flex items-center space-x-2">
|
||||
<ShieldCheck className="w-4 h-4" />
|
||||
<span>Protected</span>
|
||||
<span>{t('protected')}</span>
|
||||
</span>
|
||||
) : resourceRow.authState === "not_protected" ? (
|
||||
<span className="text-yellow-500 flex items-center space-x-2">
|
||||
<ShieldOff className="w-4 h-4" />
|
||||
<span>Not Protected</span>
|
||||
<span>{t('notProtected')}</span>
|
||||
</span>
|
||||
) : (
|
||||
<span>-</span>
|
||||
@@ -267,7 +269,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
href={`/${resourceRow.orgId}/settings/resources/${resourceRow.id}`}
|
||||
>
|
||||
<Button variant={"outlinePrimary"} className="ml-2">
|
||||
Edit
|
||||
{t('edit')}
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -289,23 +291,15 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||
dialog={
|
||||
<div>
|
||||
<p className="mb-2">
|
||||
Are you sure you want to remove the resource{" "}
|
||||
<b>
|
||||
{selectedResource?.name ||
|
||||
selectedResource?.id}
|
||||
</b>{" "}
|
||||
from the organization?
|
||||
{t('resourceQuestionRemove', {selectedResource: selectedResource?.name || selectedResource?.id})}
|
||||
</p>
|
||||
|
||||
<p className="mb-2">
|
||||
Once removed, the resource will no longer be
|
||||
accessible. All targets attached to the resource
|
||||
will be removed.
|
||||
{t('resourceMessageRemove')}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To confirm, please type the name of the resource
|
||||
below.
|
||||
{t('resourceMessageConfirm')}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -13,11 +13,13 @@ import {
|
||||
} from "@app/components/InfoSection";
|
||||
import Link from "next/link";
|
||||
import { Switch } from "@app/components/ui/switch";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
type ResourceInfoBoxType = {};
|
||||
|
||||
export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
const { resource, authInfo } = useResourceContext();
|
||||
const t = useTranslations();
|
||||
|
||||
let fullUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
||||
|
||||
@@ -25,7 +27,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
<Alert>
|
||||
<InfoIcon className="h-4 w-4" />
|
||||
<AlertTitle className="font-semibold">
|
||||
Resource Information
|
||||
{t('resourceInfo')}
|
||||
</AlertTitle>
|
||||
<AlertDescription className="mt-4">
|
||||
<InfoSections cols={4}>
|
||||
@@ -33,7 +35,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
<>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>
|
||||
Authentication
|
||||
{t('authentication')}
|
||||
</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{authInfo.password ||
|
||||
@@ -42,12 +44,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>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>Not Protected</span>
|
||||
<span>{t('notProtected')}</span>
|
||||
</div>
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
@@ -62,7 +64,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>Site</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t('site')}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
{resource.siteName}
|
||||
</InfoSectionContent>
|
||||
@@ -71,7 +73,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
) : (
|
||||
<>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>Protocol</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t('protocol')}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<span>
|
||||
{resource.protocol.toUpperCase()}
|
||||
@@ -79,7 +81,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>Port</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t('port')}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<CopyToClipboard
|
||||
text={resource.proxyPort!.toString()}
|
||||
@@ -90,9 +92,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
</>
|
||||
)}
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>Visibility</InfoSectionTitle>
|
||||
<InfoSectionTitle>{t('visibility')}</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
<span>{resource.enabled ? "Enabled" : "Disabled"}</span>
|
||||
<span>{resource.enabled ? t('enabled') : t('disabled')}</span>
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
</InfoSections>
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
BreadcrumbSeparator
|
||||
} from "@app/components/ui/breadcrumb";
|
||||
import Link from "next/link";
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
interface ResourceLayoutProps {
|
||||
children: React.ReactNode;
|
||||
@@ -30,6 +31,7 @@ interface ResourceLayoutProps {
|
||||
|
||||
export default async function ResourceLayout(props: ResourceLayoutProps) {
|
||||
const params = await props.params;
|
||||
const t = await getTranslations();
|
||||
|
||||
const { children } = props;
|
||||
|
||||
@@ -82,22 +84,22 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
||||
|
||||
const navItems = [
|
||||
{
|
||||
title: "General",
|
||||
title: t('general'),
|
||||
href: `/{orgId}/settings/resources/{resourceId}/general`
|
||||
},
|
||||
{
|
||||
title: "Proxy",
|
||||
title: t('proxy'),
|
||||
href: `/{orgId}/settings/resources/{resourceId}/proxy`
|
||||
}
|
||||
];
|
||||
|
||||
if (resource.http) {
|
||||
navItems.push({
|
||||
title: "Authentication",
|
||||
title: t('authentication'),
|
||||
href: `/{orgId}/settings/resources/{resourceId}/authentication`
|
||||
});
|
||||
navItems.push({
|
||||
title: "Rules",
|
||||
title: t('rules'),
|
||||
href: `/{orgId}/settings/resources/{resourceId}/rules`
|
||||
});
|
||||
}
|
||||
@@ -105,8 +107,8 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<SettingsSectionTitle
|
||||
title={`${resource?.name} Settings`}
|
||||
description="Configure the settings on your resource"
|
||||
title={t('resourceSetting', {resourceName: resource?.name})}
|
||||
description={t('resourceSettingDescription')}
|
||||
/>
|
||||
|
||||
<OrgProvider org={org}>
|
||||
|
||||
@@ -62,6 +62,7 @@ import { cn } from "@app/lib/cn";
|
||||
import { SquareArrowOutUpRight } from "lucide-react";
|
||||
import CopyTextBox from "@app/components/CopyTextBox";
|
||||
import Link from "next/link";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const baseResourceFormSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
@@ -104,6 +105,7 @@ export default function Page() {
|
||||
const api = createApiClient({ env });
|
||||
const { orgId } = useParams();
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
|
||||
const [loadingPage, setLoadingPage] = useState(true);
|
||||
const [sites, setSites] = useState<ListSitesResponse["sites"]>([]);
|
||||
@@ -117,15 +119,13 @@ export default function Page() {
|
||||
const resourceTypes: ReadonlyArray<ResourceTypeOption> = [
|
||||
{
|
||||
id: "http",
|
||||
title: "HTTPS Resource",
|
||||
description:
|
||||
"Proxy requests to your app over HTTPS using a subdomain or base domain."
|
||||
title: t('resourceHTTP'),
|
||||
description: t('resourceHTTPDescription')
|
||||
},
|
||||
{
|
||||
id: "raw",
|
||||
title: "Raw TCP/UDP Resource",
|
||||
description:
|
||||
"Proxy requests to your app over TCP/UDP using a port number.",
|
||||
title: t('resourceRaw'),
|
||||
description: t('resourceRawDescription'),
|
||||
disabled: !env.flags.allowRawResources
|
||||
}
|
||||
];
|
||||
@@ -300,8 +300,8 @@ export default function Page() {
|
||||
<>
|
||||
<div className="flex justify-between">
|
||||
<HeaderTitle
|
||||
title="Create Resource"
|
||||
description="Follow the steps below to create a new resource"
|
||||
title={t('resourceCreate')}
|
||||
description={t('resourceCreateDescription')}
|
||||
/>
|
||||
<Button
|
||||
variant="outline"
|
||||
@@ -309,7 +309,7 @@ export default function Page() {
|
||||
router.push(`/${orgId}/settings/resources`);
|
||||
}}
|
||||
>
|
||||
See All Resources
|
||||
{t('resourceSeeAll')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -320,7 +320,7 @@ export default function Page() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Resource Information
|
||||
{t('resourceInfo')}
|
||||
</SettingsSectionTitle>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -336,7 +336,7 @@ export default function Page() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Name
|
||||
{t('name')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -345,9 +345,7 @@ export default function Page() {
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
This is the
|
||||
display name for
|
||||
the resource.
|
||||
{t('resourceNameDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -359,7 +357,7 @@ export default function Page() {
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-col">
|
||||
<FormLabel>
|
||||
Site
|
||||
{t('site')}
|
||||
</FormLabel>
|
||||
<Popover>
|
||||
<PopoverTrigger
|
||||
@@ -384,19 +382,17 @@ export default function Page() {
|
||||
field.value
|
||||
)
|
||||
?.name
|
||||
: "Select site"}
|
||||
: t('siteSelect')}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0">
|
||||
<Command>
|
||||
<CommandInput placeholder="Search site" />
|
||||
<CommandInput placeholder={t('siteSearch')} />
|
||||
<CommandList>
|
||||
<CommandEmpty>
|
||||
No
|
||||
site
|
||||
found.
|
||||
{t('siteNotFound')}
|
||||
</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{sites.map(
|
||||
@@ -437,10 +433,7 @@ export default function Page() {
|
||||
</Popover>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
This site will
|
||||
provide
|
||||
connectivity to
|
||||
the resource.
|
||||
{t('siteSelectionDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -454,11 +447,10 @@ export default function Page() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Resource Type
|
||||
{t('resourceType')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Determine how you want to access your
|
||||
resource
|
||||
{t('resourceTypeDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -480,11 +472,10 @@ export default function Page() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
HTTPS Settings
|
||||
{t('resourceHTTPSSettings')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure how your resource will be
|
||||
accessed over HTTPS
|
||||
{t('resourceHTTPSSettingsDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -506,8 +497,7 @@ export default function Page() {
|
||||
}) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Domain
|
||||
Type
|
||||
{t('domainType')}
|
||||
</FormLabel>
|
||||
<Select
|
||||
value={
|
||||
@@ -531,11 +521,10 @@ export default function Page() {
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="subdomain">
|
||||
Subdomain
|
||||
{t('subdomain')}
|
||||
</SelectItem>
|
||||
<SelectItem value="basedomain">
|
||||
Base
|
||||
Domain
|
||||
{t('baseDomain')}
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -550,7 +539,7 @@ export default function Page() {
|
||||
) && (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Subdomain
|
||||
{t('subdomain')}
|
||||
</FormLabel>
|
||||
<div className="flex space-x-0">
|
||||
<div className="w-1/2">
|
||||
@@ -629,10 +618,7 @@ export default function Page() {
|
||||
</div>
|
||||
</div>
|
||||
<FormDescription>
|
||||
The subdomain
|
||||
where your
|
||||
resource will be
|
||||
accessible.
|
||||
{t('subdomnainDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -650,8 +636,7 @@ export default function Page() {
|
||||
}) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Base
|
||||
Domain
|
||||
{t('baseDomain')}
|
||||
</FormLabel>
|
||||
<Select
|
||||
onValueChange={
|
||||
@@ -702,11 +687,10 @@ export default function Page() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
TCP/UDP Settings
|
||||
{t('resourceRawSettings')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure how your resource will be
|
||||
accessed over TCP/UDP
|
||||
{t('resourceRawSettingsDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
@@ -724,7 +708,7 @@ export default function Page() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Protocol
|
||||
{t('protocol')}
|
||||
</FormLabel>
|
||||
<Select
|
||||
onValueChange={
|
||||
@@ -734,7 +718,7 @@ export default function Page() {
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select a protocol" />
|
||||
<SelectValue placeholder={t('protocolSelect')} />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
@@ -759,7 +743,7 @@ export default function Page() {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Port Number
|
||||
{t('resourcePortNumber')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
@@ -787,10 +771,7 @@ export default function Page() {
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
The external
|
||||
port number
|
||||
to proxy
|
||||
requests.
|
||||
{t('resourcePortNumberDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -810,7 +791,7 @@ export default function Page() {
|
||||
router.push(`/${orgId}/settings/resources`)
|
||||
}
|
||||
>
|
||||
Cancel
|
||||
{t('cancle')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -827,7 +808,7 @@ export default function Page() {
|
||||
}}
|
||||
loading={createLoading}
|
||||
>
|
||||
Create Resource
|
||||
{t('resourceCreate')}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsContainer>
|
||||
@@ -836,17 +817,17 @@ export default function Page() {
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Configuration Snippets
|
||||
{t('resourceConfig')}
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Copy and paste these configuration snippets to set up your TCP/UDP resource
|
||||
{t('resourceConfigDescription')}
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">
|
||||
Traefik: Add Entrypoints
|
||||
{t('resourceAddEntrypoints')}
|
||||
</h3>
|
||||
<CopyTextBox
|
||||
text={`entryPoints:
|
||||
@@ -858,7 +839,7 @@ export default function Page() {
|
||||
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-semibold">
|
||||
Gerbil: Expose Ports in Docker Compose
|
||||
{t('resourceExposePorts')}
|
||||
</h3>
|
||||
<CopyTextBox
|
||||
text={`ports:
|
||||
@@ -874,7 +855,7 @@ export default function Page() {
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>
|
||||
Learn how to configure TCP/UDP resources
|
||||
{t('resourceLearnRaw')}
|
||||
</span>
|
||||
<SquareArrowOutUpRight size={14} />
|
||||
</Link>
|
||||
@@ -890,7 +871,7 @@ export default function Page() {
|
||||
router.push(`/${orgId}/settings/resources`)
|
||||
}
|
||||
>
|
||||
Back to Resources
|
||||
{t('resourceBack')}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
@@ -900,7 +881,7 @@ export default function Page() {
|
||||
)
|
||||
}
|
||||
>
|
||||
Go to Resource
|
||||
{t('resourceGoTo')}
|
||||
</Button>
|
||||
</div>
|
||||
</SettingsContainer>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { cache } from "react";
|
||||
import { GetOrgResponse } from "@server/routers/org";
|
||||
import OrgProvider from "@app/providers/OrgProvider";
|
||||
import ResourcesSplashCard from "./ResourcesSplashCard";
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
type ResourcesPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -68,13 +69,15 @@ export default async function ResourcesPage(props: ResourcesPageProps) {
|
||||
};
|
||||
});
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ResourcesSplashCard /> */}
|
||||
|
||||
<SettingsSectionTitle
|
||||
title="Manage Resources"
|
||||
description="Create secure proxies to your private applications"
|
||||
title={t('resourceTitle')}
|
||||
description={t('resourceDescription')}
|
||||
/>
|
||||
|
||||
<OrgProvider org={org}>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import { DataTable } from "@app/components/ui/data-table";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
@@ -16,15 +17,18 @@ export function ShareLinksDataTable<TData, TValue>({
|
||||
data,
|
||||
createShareLink
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
title="Share Links"
|
||||
searchPlaceholder="Search share links..."
|
||||
searchPlaceholder={t('shareSearch')}
|
||||
searchColumn="name"
|
||||
onAdd={createShareLink}
|
||||
addButtonText="Create Share Link"
|
||||
addButtonText={t('shareCreate')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import { ListAccessTokensResponse } from "@server/routers/accessToken";
|
||||
import moment from "moment";
|
||||
import CreateShareLinkForm from "./CreateShareLinkForm";
|
||||
import { constructShareLink } from "@app/lib/shareLinks";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export type ShareLinkRow = {
|
||||
accessTokenId: string;
|
||||
@@ -54,6 +55,7 @@ export default function ShareLinksTable({
|
||||
orgId
|
||||
}: ShareLinksTableProps) {
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
@@ -67,11 +69,8 @@ export default function ShareLinksTable({
|
||||
async function deleteSharelink(id: string) {
|
||||
await api.delete(`/access-token/${id}`).catch((e) => {
|
||||
toast({
|
||||
title: "Failed to delete link",
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred deleting link"
|
||||
)
|
||||
title: t('shareErrorDelete'),
|
||||
description: formatAxiosError(e,t('shareErrorDeleteMessage'))
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,8 +78,8 @@ export default function ShareLinksTable({
|
||||
setRows(newRows);
|
||||
|
||||
toast({
|
||||
title: "Link deleted",
|
||||
description: "The link has been deleted"
|
||||
title: t('shareDeleted'),
|
||||
description: t('shareDeletedDesciption')
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,7 +101,7 @@ export default function ShareLinksTable({
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">
|
||||
Open menu
|
||||
{t('openMenu')}
|
||||
</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
@@ -116,7 +115,7 @@ export default function ShareLinksTable({
|
||||
}}
|
||||
>
|
||||
<button className="text-red-500">
|
||||
Delete
|
||||
{t('delete')}
|
||||
</button>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
@@ -136,7 +135,7 @@ export default function ShareLinksTable({
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Resource
|
||||
{t('resource')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -164,7 +163,7 @@ export default function ShareLinksTable({
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Title
|
||||
{t('title')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -243,7 +242,7 @@ export default function ShareLinksTable({
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Created
|
||||
{t('created')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -263,7 +262,7 @@ export default function ShareLinksTable({
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Expires
|
||||
{t('expires')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -273,7 +272,7 @@ export default function ShareLinksTable({
|
||||
if (r.expiresAt) {
|
||||
return moment(r.expiresAt).format("lll");
|
||||
}
|
||||
return "Never";
|
||||
return t('never');
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -286,7 +285,7 @@ export default function ShareLinksTable({
|
||||
deleteSharelink(row.original.accessTokenId)
|
||||
}
|
||||
>
|
||||
Delete
|
||||
{t('delete')}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import OrgProvider from "@app/providers/OrgProvider";
|
||||
import { ListAccessTokensResponse } from "@server/routers/accessToken";
|
||||
import ShareLinksTable, { ShareLinkRow } from "./ShareLinksTable";
|
||||
import ShareableLinksSplash from "./ShareLinksSplash";
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
type ShareLinksPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -51,13 +52,15 @@ export default async function ShareLinksPage(props: ShareLinksPageProps) {
|
||||
(token) => ({ ...token }) as ShareLinkRow
|
||||
);
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <ShareableLinksSplash /> */}
|
||||
|
||||
<SettingsSectionTitle
|
||||
title="Manage Share Links"
|
||||
description="Create shareable links to grant temporary or permanent access to your resources"
|
||||
title={t('shareTitle')}
|
||||
description={t('shareDescription')}
|
||||
/>
|
||||
|
||||
<OrgProvider org={org}>
|
||||
|
||||
@@ -50,15 +50,18 @@ import {
|
||||
CollapsibleTrigger
|
||||
} from "@app/components/ui/collapsible";
|
||||
import LoaderPlaceholder from "@app/components/PlaceHolderLoader";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
const createSiteFormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(2, {
|
||||
message: "Name must be at least 2 characters."
|
||||
message: {t('siteNameMin')}
|
||||
})
|
||||
.max(30, {
|
||||
message: "Name must not be longer than 30 characters."
|
||||
message: {t('siteNameMax')}
|
||||
}),
|
||||
method: z.enum(["wireguard", "newt", "local"])
|
||||
});
|
||||
@@ -169,8 +172,8 @@ export default function CreateSiteForm({
|
||||
if (!keypair || !siteDefaults) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
description: "Key pair or site defaults not found"
|
||||
title: {t('siteErrorCreate')},
|
||||
description: {t('siteErrorCreateKeyPair')}
|
||||
});
|
||||
setLoading?.(false);
|
||||
setIsLoading(false);
|
||||
@@ -188,8 +191,8 @@ export default function CreateSiteForm({
|
||||
if (!siteDefaults) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
description: "Site defaults not found"
|
||||
title: {t('siteErrorCreate')},
|
||||
description: {t('siteErrorCreateDefaults')}
|
||||
});
|
||||
setLoading?.(false);
|
||||
setIsLoading(false);
|
||||
@@ -212,7 +215,7 @@ export default function CreateSiteForm({
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error creating site",
|
||||
title: {t('siteErrorCreate')},
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
});
|
||||
@@ -285,13 +288,13 @@ PersistentKeepalive = 5`
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormLabel>{t('name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input autoComplete="off" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
This is the display name for the site.
|
||||
{t('siteNameDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -301,7 +304,7 @@ PersistentKeepalive = 5`
|
||||
name="method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Method</FormLabel>
|
||||
<FormLabel>{t('method')}</FormLabel>
|
||||
<FormControl>
|
||||
<Select
|
||||
value={field.value}
|
||||
@@ -331,7 +334,7 @@ PersistentKeepalive = 5`
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
This is how you will expose connections.
|
||||
{t('siteMethodDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
@@ -345,7 +348,7 @@ PersistentKeepalive = 5`
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span>
|
||||
Learn how to install Newt on your system
|
||||
{t('siteLearnNewt')}
|
||||
</span>
|
||||
<SquareArrowOutUpRight size={14} />
|
||||
</Link>
|
||||
@@ -356,13 +359,12 @@ PersistentKeepalive = 5`
|
||||
<>
|
||||
<CopyTextBox text={wgConfig} />
|
||||
<span className="text-sm text-muted-foreground mt-2">
|
||||
You will only be able to see the
|
||||
configuration once.
|
||||
{t('siteSeeConfigOnce')}
|
||||
</span>
|
||||
</>
|
||||
) : form.watch("method") === "wireguard" &&
|
||||
isLoading ? (
|
||||
<p>Loading WireGuard configuration...</p>
|
||||
<p>{t('siteLoadWGConfig')}</p>
|
||||
) : form.watch("method") === "newt" && siteDefaults ? (
|
||||
<>
|
||||
<div className="mb-2">
|
||||
@@ -378,8 +380,7 @@ PersistentKeepalive = 5`
|
||||
/>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
You will only be able to see the
|
||||
configuration once.
|
||||
{t('siteSeeConfigOnce')}
|
||||
</span>
|
||||
<div className="flex items-center justify-between space-x-4">
|
||||
<CollapsibleTrigger asChild>
|
||||
@@ -389,13 +390,12 @@ PersistentKeepalive = 5`
|
||||
className="p-0 flex items-center justify-between w-full"
|
||||
>
|
||||
<h4 className="text-sm font-semibold">
|
||||
Expand for Docker
|
||||
Deployment Details
|
||||
{t('siteDocker')}
|
||||
</h4>
|
||||
<div>
|
||||
<ChevronsUpDown className="h-4 w-4" />
|
||||
<span className="sr-only">
|
||||
Toggle
|
||||
{t('toggle')}
|
||||
</span>
|
||||
</div>
|
||||
</Button>
|
||||
@@ -403,7 +403,7 @@ PersistentKeepalive = 5`
|
||||
</div>
|
||||
<CollapsibleContent className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<b>Docker Compose</b>
|
||||
<b>{t('dockerCompose')}</b>
|
||||
<CopyTextBox
|
||||
text={
|
||||
newtConfigDockerCompose
|
||||
@@ -412,7 +412,7 @@ PersistentKeepalive = 5`
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<b>Docker Run</b>
|
||||
<b>{t('dockerRun')}</b>
|
||||
|
||||
<CopyTextBox
|
||||
text={newtConfigDockerRun}
|
||||
@@ -433,7 +433,7 @@ PersistentKeepalive = 5`
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span> Local sites do not tunnel, learn more</span>
|
||||
<span>{t('siteLearnLocal')}</span>
|
||||
<SquareArrowOutUpRight size={14} />
|
||||
</Link>
|
||||
)}
|
||||
@@ -450,7 +450,7 @@ PersistentKeepalive = 5`
|
||||
htmlFor="terms"
|
||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
>
|
||||
I have copied the config
|
||||
{t('siteConfirmCopy')}
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "@app/components/Credenza";
|
||||
import { SiteRow } from "./SitesTable";
|
||||
import CreateSiteForm from "./CreateSiteForm";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
type CreateSiteFormProps = {
|
||||
open: boolean;
|
||||
@@ -30,6 +31,7 @@ export default function CreateSiteFormModal({
|
||||
}: CreateSiteFormProps) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -42,9 +44,9 @@ export default function CreateSiteFormModal({
|
||||
>
|
||||
<CredenzaContent>
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>Create Site</CredenzaTitle>
|
||||
<CredenzaTitle>{t('siteCreate')}</CredenzaTitle>
|
||||
<CredenzaDescription>
|
||||
Create a new site to start connecting your resources
|
||||
{t('siteCreateDescription')}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
@@ -59,7 +61,7 @@ export default function CreateSiteFormModal({
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">Close</Button>
|
||||
<Button variant="outline">{t('close')}</Button>
|
||||
</CredenzaClose>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -70,7 +72,7 @@ export default function CreateSiteFormModal({
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Create Site
|
||||
{t('siteCreate')}
|
||||
</Button>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
ColumnDef,
|
||||
} from "@tanstack/react-table";
|
||||
import { DataTable } from "@app/components/ui/data-table";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
@@ -16,15 +17,18 @@ export function SitesDataTable<TData, TValue>({
|
||||
data,
|
||||
createSite
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
|
||||
const t = useTranslations();
|
||||
|
||||
return (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={data}
|
||||
title="Sites"
|
||||
searchPlaceholder="Search sites..."
|
||||
searchPlaceholder={t('searchSites')}
|
||||
searchColumn="name"
|
||||
onAdd={createSite}
|
||||
addButtonText="Add Site"
|
||||
addButtonText={t('siteAdd')}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,13 @@ 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";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const SitesSplashCard = () => {
|
||||
const [isDismissed, setIsDismissed] = useState(true);
|
||||
|
||||
const key = "sites-splash-card-dismissed";
|
||||
const t = useTranslations();
|
||||
|
||||
useEffect(() => {
|
||||
const dismissed = localStorage.getItem(key);
|
||||
@@ -42,22 +44,19 @@ export const SitesSplashCard = () => {
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-xl font-semibold flex items-center gap-2">
|
||||
<Globe className="text-blue-500" />
|
||||
Newt (Recommended)
|
||||
Newt ({t('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.
|
||||
{t('siteNewtDescription')}
|
||||
</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
|
||||
{t('siteRunsInDocker')}
|
||||
</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
|
||||
{t('siteRunsInShell')}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import { formatAxiosError } from "@app/lib/api";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import CreateSiteFormModal from "./CreateSiteModal";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export type SiteRow = {
|
||||
id: number;
|
||||
@@ -52,15 +53,16 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
const [rows, setRows] = useState<SiteRow[]>(sites);
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
const t = useTranslations();
|
||||
|
||||
const deleteSite = (siteId: number) => {
|
||||
api.delete(`/site/${siteId}`)
|
||||
.catch((e) => {
|
||||
console.error("Error deleting site", e);
|
||||
console.error(t('siteErrorDelete'), e);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Error deleting site",
|
||||
description: formatAxiosError(e, "Error deleting site")
|
||||
title: t('siteErrorDelete'),
|
||||
description: formatAxiosError(e, t('siteErrorDelete'))
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
@@ -94,7 +96,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
|
||||
>
|
||||
<DropdownMenuItem>
|
||||
View settings
|
||||
{t('viewSettings')}
|
||||
</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuItem
|
||||
@@ -103,7 +105,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
<span className="text-red-500">Delete</span>
|
||||
<span className="text-red-500">{t('delete')}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -120,7 +122,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Name
|
||||
{t('name')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -136,7 +138,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Online
|
||||
{t('online')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -151,14 +153,14 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
return (
|
||||
<span className="text-green-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||
<span>Online</span>
|
||||
<span>{t('online')}</span>
|
||||
</span>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<span className="text-neutral-500 flex items-center space-x-2">
|
||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||
<span>Offline</span>
|
||||
<span>{t('offline')}</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -177,7 +179,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Site
|
||||
{t('site')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -193,7 +195,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Data In
|
||||
{t('dataIn')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -209,7 +211,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Data Out
|
||||
{t('dataOut')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -225,7 +227,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
Connection Type
|
||||
{t('connectionType')}
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
@@ -252,7 +254,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
if (originalRow.type === "local") {
|
||||
return (
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>Local</span>
|
||||
<span>{t('local')}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -268,7 +270,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
href={`/${siteRow.orgId}/settings/sites/${siteRow.nice}`}
|
||||
>
|
||||
<Button variant={"outlinePrimary"} className="ml-2">
|
||||
Edit
|
||||
{t('edit')}
|
||||
<ArrowRight className="ml-2 w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
@@ -290,30 +292,22 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
dialog={
|
||||
<div className="space-y-4">
|
||||
<p>
|
||||
Are you sure you want to remove the site{" "}
|
||||
<b>{selectedSite?.name || selectedSite?.id}</b>{" "}
|
||||
from the organization?
|
||||
{t('siteQuestionRemove', {selectedSite: selectedSite?.name || selectedSite?.id})}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{t('siteMessageRemove')}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Once removed, the site will no longer be
|
||||
accessible.{" "}
|
||||
<b>
|
||||
All resources and targets associated with
|
||||
the site will also be removed.
|
||||
</b>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To confirm, please type the name of the site
|
||||
below.
|
||||
{t('siteMessageConfirm')}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
buttonText="Confirm Delete Site"
|
||||
buttonText={t('siteConfirmDelete')}
|
||||
onConfirm={async () => deleteSite(selectedSite!.id)}
|
||||
string={selectedSite.name}
|
||||
title="Delete Site"
|
||||
title={t('siteDelete')}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AxiosResponse } from "axios";
|
||||
import SitesTable, { SiteRow } from "./SitesTable";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import SitesSplashCard from "./SitesSplashCard";
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
type SitesPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -49,13 +50,15 @@ export default async function SitesPage(props: SitesPageProps) {
|
||||
};
|
||||
});
|
||||
|
||||
const t = await getTranslations();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <SitesSplashCard /> */}
|
||||
|
||||
<SettingsSectionTitle
|
||||
title="Manage Sites"
|
||||
description="Allow connectivity to your network through secure tunnels"
|
||||
title={t('siteManageSites')}
|
||||
description={t('siteDescription')}
|
||||
/>
|
||||
|
||||
<SitesTable sites={siteRows} orgId={params.orgId} />
|
||||
|
||||
Reference in New Issue
Block a user