Reintroduce clients conditionally

This commit is contained in:
Owen
2025-07-14 11:43:13 -07:00
parent a35add3fc6
commit b75800c583
9 changed files with 331 additions and 270 deletions

View File

@@ -35,7 +35,7 @@ import {
SettingsSectionFooter
} from "@app/components/Settings";
import { useUserContext } from "@app/hooks/useUserContext";
import { useTranslations } from 'next-intl';
import { useTranslations } from "next-intl";
// Updated schema to include subnet field
const GeneralFormSchema = z.object({
@@ -53,6 +53,7 @@ export default function GeneralPage() {
const api = createApiClient(useEnvContext());
const { user } = useUserContext();
const t = useTranslations();
const { env } = useEnvContext();
const [loadingDelete, setLoadingDelete] = useState(false);
const [loadingSave, setLoadingSave] = useState(false);
@@ -73,8 +74,8 @@ export default function GeneralPage() {
`/org/${org?.org.orgId}`
);
toast({
title: t('orgDeleted'),
description: t('orgDeletedMessage')
title: t("orgDeleted"),
description: t("orgDeletedMessage")
});
if (res.status === 200) {
pickNewOrgAndNavigate();
@@ -83,8 +84,8 @@ export default function GeneralPage() {
console.error(err);
toast({
variant: "destructive",
title: t('orgErrorDelete'),
description: formatAxiosError(err, t('orgErrorDeleteMessage'))
title: t("orgErrorDelete"),
description: formatAxiosError(err, t("orgErrorDeleteMessage"))
});
} finally {
setLoadingDelete(false);
@@ -111,8 +112,8 @@ export default function GeneralPage() {
console.error(err);
toast({
variant: "destructive",
title: t('orgErrorFetch'),
description: formatAxiosError(err, t('orgErrorFetchMessage'))
title: t("orgErrorFetch"),
description: formatAxiosError(err, t("orgErrorFetchMessage"))
});
}
}
@@ -126,16 +127,16 @@ export default function GeneralPage() {
})
.then(() => {
toast({
title: t('orgUpdated'),
description: t('orgUpdatedDescription')
title: t("orgUpdated"),
description: t("orgUpdatedDescription")
});
router.refresh();
})
.catch((e) => {
toast({
variant: "destructive",
title: t('orgErrorUpdate'),
description: formatAxiosError(e, t('orgErrorUpdateMessage'))
title: t("orgErrorUpdate"),
description: formatAxiosError(e, t("orgErrorUpdateMessage"))
});
})
.finally(() => {
@@ -153,28 +154,26 @@ export default function GeneralPage() {
dialog={
<div>
<p className="mb-2">
{t('orgQuestionRemove', {selectedOrg: org?.org.name})}
</p>
<p className="mb-2">
{t('orgMessageRemove')}
</p>
<p>
{t('orgMessageConfirm')}
{t("orgQuestionRemove", {
selectedOrg: org?.org.name
})}
</p>
<p className="mb-2">{t("orgMessageRemove")}</p>
<p>{t("orgMessageConfirm")}</p>
</div>
}
buttonText={t('orgDeleteConfirm')}
buttonText={t("orgDeleteConfirm")}
onConfirm={deleteOrg}
string={org?.org.name || ""}
title={t('orgDelete')}
title={t("orgDelete")}
/>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('orgGeneralSettings')}
{t("orgGeneralSettings")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('orgGeneralSettingsDescription')}
{t("orgGeneralSettingsDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -190,37 +189,40 @@ export default function GeneralPage() {
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormLabel>{t("name")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
<FormDescription>
{t('orgDisplayName')}
{t("orgDisplayName")}
</FormDescription>
</FormItem>
)}
/>
{/* <FormField */}
{/* control={form.control} */}
{/* name="subnet" */}
{/* render={({ field }) => ( */}
{/* <FormItem> */}
{/* <FormLabel>Subnet</FormLabel> */}
{/* <FormControl> */}
{/* <Input */}
{/* {...field} */}
{/* disabled={true} */}
{/* /> */}
{/* </FormControl> */}
{/* <FormMessage /> */}
{/* <FormDescription> */}
{/* The subnet for this organization's network configuration. */}
{/* </FormDescription> */}
{/* </FormItem> */}
{/* )} */}
{/* /> */}
{env.flags.enableClients && (
<FormField
control={form.control}
name="subnet"
render={({ field }) => (
<FormItem>
<FormLabel>Subnet</FormLabel>
<FormControl>
<Input
{...field}
disabled={true}
/>
</FormControl>
<FormMessage />
<FormDescription>
The subnet for this
organization's network
configuration.
</FormDescription>
</FormItem>
)}
/>
)}
</form>
</Form>
</SettingsSectionForm>
@@ -232,15 +234,17 @@ export default function GeneralPage() {
loading={loadingSave}
disabled={loadingSave}
>
{t('saveGeneralSettings')}
{t("saveGeneralSettings")}
</Button>
</SettingsSectionFooter>
</SettingsSection>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>{t('orgDangerZone')}</SettingsSectionTitle>
<SettingsSectionTitle>
{t("orgDangerZone")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('orgDangerZoneDescription')}
{t("orgDangerZoneDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionFooter>
@@ -251,7 +255,7 @@ export default function GeneralPage() {
loading={loadingDelete}
disabled={loadingDelete}
>
{t('orgDelete')}
{t("orgDelete")}
</Button>
</SettingsSectionFooter>
</SettingsSection>

View File

@@ -86,7 +86,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
return (
<UserProvider user={user}>
<Layout orgId={params.orgId} orgs={orgs} navItems={orgNavSections}>
<Layout orgId={params.orgId} orgs={orgs} navItems={orgNavSections(env.flags.enableClients)}>
{children}
</Layout>
</UserProvider>

View File

@@ -98,9 +98,9 @@ export default function Page() {
.object({
name: z
.string()
.min(2, { message: t('nameMin', {len: 2}) })
.min(2, { message: t("nameMin", { len: 2 }) })
.max(30, {
message: t('nameMax', {len: 30})
message: t("nameMax", { len: 30 })
}),
method: z.enum(["newt", "wireguard", "local"]),
copied: z.boolean(),
@@ -115,7 +115,7 @@ export default function Page() {
return true;
},
{
message: t('sitesConfirmCopy'),
message: t("sitesConfirmCopy"),
path: ["copied"]
}
);
@@ -127,21 +127,29 @@ export default function Page() {
>([
{
id: "newt",
title: t('siteNewtTunnel'),
description: t('siteNewtTunnelDescription'),
title: t("siteNewtTunnel"),
description: t("siteNewtTunnelDescription"),
disabled: true
},
...(env.flags.disableBasicWireguardSites ? [] : [{
id: "wireguard" as SiteType,
title: t('siteWg'),
description: t('siteWgDescription'),
disabled: true
}]),
...(env.flags.disableLocalSites ? [] : [{
id: "local" as SiteType,
title: t('local'),
description: t('siteLocalDescription')
}])
...(env.flags.disableBasicWireguardSites
? []
: [
{
id: "wireguard" as SiteType,
title: t("siteWg"),
description: t("siteWgDescription"),
disabled: true
}
]),
...(env.flags.disableLocalSites
? []
: [
{
id: "local" as SiteType,
title: t("local"),
description: t("siteLocalDescription")
}
])
]);
const [loadingPage, setLoadingPage] = useState(true);
@@ -319,7 +327,7 @@ WantedBy=default.target`
};
const getCommand = () => {
const placeholder = [t('unknownCommand')];
const placeholder = [t("unknownCommand")];
if (!commands) {
return placeholder;
}
@@ -384,8 +392,8 @@ WantedBy=default.target`
if (!siteDefaults || !wgConfig) {
toast({
variant: "destructive",
title: t('siteErrorCreate'),
description: t('siteErrorCreateKeyPair')
title: t("siteErrorCreate"),
description: t("siteErrorCreateKeyPair")
});
setCreateLoading(false);
return;
@@ -402,8 +410,8 @@ WantedBy=default.target`
if (!siteDefaults) {
toast({
variant: "destructive",
title: t('siteErrorCreate'),
description: t('siteErrorCreateDefaults')
title: t("siteErrorCreate"),
description: t("siteErrorCreateDefaults")
});
setCreateLoading(false);
return;
@@ -415,7 +423,7 @@ WantedBy=default.target`
exitNodeId: siteDefaults.exitNodeId,
secret: siteDefaults.newtSecret,
newtId: siteDefaults.newtId,
// address: clientAddress
address: clientAddress
};
}
@@ -426,7 +434,7 @@ WantedBy=default.target`
.catch((e) => {
toast({
variant: "destructive",
title: t('siteErrorCreate'),
title: t("siteErrorCreate"),
description: formatAxiosError(e)
});
});
@@ -452,14 +460,23 @@ WantedBy=default.target`
);
if (!response.ok) {
throw new Error(
t('newtErrorFetchReleases', {err: response.statusText})
t("newtErrorFetchReleases", {
err: response.statusText
})
);
}
const data = await response.json();
const latestVersion = data.tag_name;
newtVersion = latestVersion;
} catch (error) {
console.error(t('newtErrorFetchLatest', {err: error instanceof Error ? error.message : String(error)}));
console.error(
t("newtErrorFetchLatest", {
err:
error instanceof Error
? error.message
: String(error)
})
);
}
const generatedKeypair = generateKeypair();
@@ -526,8 +543,8 @@ WantedBy=default.target`
<>
<div className="flex justify-between">
<HeaderTitle
title={t('siteCreate')}
description={t('siteCreateDescription2')}
title={t("siteCreate")}
description={t("siteCreateDescription2")}
/>
<Button
variant="outline"
@@ -535,7 +552,7 @@ WantedBy=default.target`
router.push(`/${orgId}/settings/sites`);
}}
>
{t('siteSeeAll')}
{t("siteSeeAll")}
</Button>
</div>
@@ -545,7 +562,7 @@ WantedBy=default.target`
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('siteInfo')}
{t("siteInfo")}
</SettingsSectionTitle>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -561,7 +578,7 @@ WantedBy=default.target`
render={({ field }) => (
<FormItem>
<FormLabel>
{t('name')}
{t("name")}
</FormLabel>
<FormControl>
<Input
@@ -571,47 +588,54 @@ WantedBy=default.target`
</FormControl>
<FormMessage />
<FormDescription>
{t('siteNameDescription')}
{t(
"siteNameDescription"
)}
</FormDescription>
</FormItem>
)}
/>
{/* <FormField */}
{/* control={form.control} */}
{/* name="clientAddress" */}
{/* render={({ field }) => ( */}
{/* <FormItem> */}
{/* <FormLabel> */}
{/* Site Address */}
{/* </FormLabel> */}
{/* <FormControl> */}
{/* <Input */}
{/* autoComplete="off" */}
{/* value={ */}
{/* clientAddress */}
{/* } */}
{/* onChange={( */}
{/* e */}
{/* ) => { */}
{/* setClientAddress( */}
{/* e.target */}
{/* .value */}
{/* ); */}
{/* field.onChange( */}
{/* e.target */}
{/* .value */}
{/* ); */}
{/* }} */}
{/* /> */}
{/* </FormControl> */}
{/* <FormMessage /> */}
{/* <FormDescription> */}
{/* Specify the IP */}
{/* address of the host. */}
{/* </FormDescription> */}
{/* </FormItem> */}
{/* )} */}
{/* /> */}
{env.flags.enableClients && (
<FormField
control={form.control}
name="clientAddress"
render={({ field }) => (
<FormItem>
<FormLabel>
Site Address
</FormLabel>
<FormControl>
<Input
autoComplete="off"
value={
clientAddress
}
onChange={(
e
) => {
setClientAddress(
e
.target
.value
);
field.onChange(
e
.target
.value
);
}}
/>
</FormControl>
<FormMessage />
<FormDescription>
Specify the IP
address of the
host.
</FormDescription>
</FormItem>
)}
/>
)}
</form>
</Form>
</SettingsSectionForm>
@@ -622,10 +646,10 @@ WantedBy=default.target`
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('tunnelType')}
{t("tunnelType")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('siteTunnelDescription')}
{t("siteTunnelDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -646,17 +670,19 @@ WantedBy=default.target`
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('siteNewtCredentials')}
{t("siteNewtCredentials")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('siteNewtCredentialsDescription')}
{t(
"siteNewtCredentialsDescription"
)}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<InfoSections cols={3}>
<InfoSection>
<InfoSectionTitle>
{t('newtEndpoint')}
{t("newtEndpoint")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard
@@ -668,7 +694,7 @@ WantedBy=default.target`
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t('newtId')}
{t("newtId")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard
@@ -678,7 +704,7 @@ WantedBy=default.target`
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t('newtSecretKey')}
{t("newtSecretKey")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard
@@ -691,10 +717,12 @@ WantedBy=default.target`
<Alert variant="neutral" className="">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('siteCredentialsSave')}
{t("siteCredentialsSave")}
</AlertTitle>
<AlertDescription>
{t('siteCredentialsSaveDescription')}
{t(
"siteCredentialsSaveDescription"
)}
</AlertDescription>
</Alert>
@@ -743,16 +771,16 @@ WantedBy=default.target`
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('siteInstallNewt')}
{t("siteInstallNewt")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('siteInstallNewtDescription')}
{t("siteInstallNewtDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<div>
<p className="font-bold mb-3">
{t('operatingSystem')}
{t("operatingSystem")}
</p>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{platforms.map((os) => (
@@ -780,8 +808,8 @@ WantedBy=default.target`
{["docker", "podman"].includes(
platform
)
? t('method')
: t('architecture')}
? t("method")
: t("architecture")}
</p>
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
{getArchitectures().map(
@@ -808,7 +836,7 @@ WantedBy=default.target`
</div>
<div className="pt-4">
<p className="font-bold mb-3">
{t('commands')}
{t("commands")}
</p>
<div className="mt-2">
<CopyTextBox
@@ -829,10 +857,10 @@ WantedBy=default.target`
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t('WgConfiguration')}
{t("WgConfiguration")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t('WgConfigurationDescription')}
{t("WgConfigurationDescription")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
@@ -853,10 +881,12 @@ WantedBy=default.target`
<Alert variant="neutral">
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">
{t('siteCredentialsSave')}
{t("siteCredentialsSave")}
</AlertTitle>
<AlertDescription>
{t('siteCredentialsSaveDescription')}
{t(
"siteCredentialsSaveDescription"
)}
</AlertDescription>
</Alert>
@@ -891,7 +921,9 @@ WantedBy=default.target`
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{t('siteConfirmCopy')}
{t(
"siteConfirmCopy"
)}
</label>
</div>
<FormMessage />
@@ -913,7 +945,7 @@ WantedBy=default.target`
router.push(`/${orgId}/settings/sites`);
}}
>
{t('cancel')}
{t("cancel")}
</Button>
<Button
type="button"
@@ -923,7 +955,7 @@ WantedBy=default.target`
form.handleSubmit(onSubmit)();
}}
>
{t('siteCreate')}
{t("siteCreate")}
</Button>
</div>
</div>