mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-02 07:56:38 +00:00
Merge UI into new screen
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
FormMessage
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||
import {
|
||||
Credenza,
|
||||
@@ -41,7 +42,7 @@ import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils";
|
||||
import { UpdateResourceResponse } from "@server/routers/resource";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Globe } from "lucide-react";
|
||||
import { AlertCircle, Globe } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { toASCII, toUnicode } from "punycode";
|
||||
@@ -49,6 +50,17 @@ import { useActionState, useMemo, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import z from "zod";
|
||||
import { build } from "@server/build";
|
||||
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
|
||||
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
|
||||
import { Alert, AlertDescription } from "@app/components/ui/alert";
|
||||
import { RadioGroup, RadioGroupItem } from "@app/components/ui/radio-group";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipProvider,
|
||||
TooltipTrigger
|
||||
} from "@app/components/ui/tooltip";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
|
||||
export default function GeneralForm() {
|
||||
const params = useParams();
|
||||
@@ -56,6 +68,8 @@ export default function GeneralForm() {
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
const [editDomainOpen, setEditDomainOpen] = useState(false);
|
||||
const { isUnlocked } = useLicenseStatusContext();
|
||||
const subscription = useSubscriptionStatusContext();
|
||||
|
||||
const { env } = useEnvContext();
|
||||
|
||||
@@ -80,6 +94,14 @@ export default function GeneralForm() {
|
||||
baseDomain: string;
|
||||
} | null>(null);
|
||||
|
||||
// Check if security features are disabled due to licensing/subscription
|
||||
const isSecurityFeatureDisabled = () => {
|
||||
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
|
||||
const isSaasNotSubscribed =
|
||||
build === "saas" && !subscription?.isSubscribed();
|
||||
return isEnterpriseNotLicensed || isSaasNotSubscribed;
|
||||
};
|
||||
|
||||
const GeneralFormSchema = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
@@ -87,7 +109,12 @@ export default function GeneralForm() {
|
||||
name: z.string().min(1).max(255),
|
||||
niceId: z.string().min(1).max(255).optional(),
|
||||
domainId: z.string().optional(),
|
||||
proxyPort: z.int().min(1).max(65535).optional()
|
||||
proxyPort: z.number().int().min(1).max(65535).optional(),
|
||||
maintenanceModeEnabled: z.boolean().optional(),
|
||||
maintenanceModeType: z.enum(["forced", "automatic"]).optional(),
|
||||
maintenanceTitle: z.string().max(255).optional(),
|
||||
maintenanceMessage: z.string().max(2000).optional(),
|
||||
maintenanceEstimatedTime: z.string().max(100).optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
@@ -106,6 +133,8 @@ export default function GeneralForm() {
|
||||
}
|
||||
);
|
||||
|
||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(GeneralFormSchema),
|
||||
defaultValues: {
|
||||
@@ -114,11 +143,22 @@ export default function GeneralForm() {
|
||||
niceId: resource.niceId,
|
||||
subdomain: resource.subdomain ? resource.subdomain : undefined,
|
||||
domainId: resource.domainId || undefined,
|
||||
proxyPort: resource.proxyPort || undefined
|
||||
proxyPort: resource.proxyPort || undefined,
|
||||
maintenanceModeEnabled: resource.maintenanceModeEnabled || false,
|
||||
maintenanceModeType: resource.maintenanceModeType || "automatic",
|
||||
maintenanceTitle:
|
||||
resource.maintenanceTitle || "We'll be back soon!",
|
||||
maintenanceMessage:
|
||||
resource.maintenanceMessage ||
|
||||
"We are currently performing scheduled maintenance. Please check back soon.",
|
||||
maintenanceEstimatedTime: resource.maintenanceEstimatedTime || ""
|
||||
},
|
||||
mode: "onChange"
|
||||
});
|
||||
|
||||
const isMaintenanceEnabled = form.watch("maintenanceModeEnabled");
|
||||
const maintenanceModeType = form.watch("maintenanceModeType");
|
||||
|
||||
const [, formAction, saveLoading] = useActionState(onSubmit, null);
|
||||
|
||||
async function onSubmit() {
|
||||
@@ -138,7 +178,13 @@ export default function GeneralForm() {
|
||||
? toASCII(data.subdomain)
|
||||
: undefined,
|
||||
domainId: data.domainId,
|
||||
proxyPort: data.proxyPort
|
||||
proxyPort: data.proxyPort,
|
||||
maintenanceModeEnabled: data.maintenanceModeEnabled,
|
||||
maintenanceModeType: data.maintenanceModeType,
|
||||
maintenanceTitle: data.maintenanceTitle || null,
|
||||
maintenanceMessage: data.maintenanceMessage || null,
|
||||
maintenanceEstimatedTime:
|
||||
data.maintenanceEstimatedTime || null
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
@@ -162,10 +208,12 @@ export default function GeneralForm() {
|
||||
subdomain: data.subdomain,
|
||||
fullDomain: updated.fullDomain,
|
||||
proxyPort: data.proxyPort,
|
||||
domainId: data.domainId
|
||||
// ...(!resource.http && {
|
||||
// enableProxy: data.enableProxy
|
||||
// })
|
||||
domainId: data.domainId,
|
||||
maintenanceModeEnabled: data.maintenanceModeEnabled,
|
||||
maintenanceModeType: data.maintenanceModeType,
|
||||
maintenanceTitle: data.maintenanceTitle || null,
|
||||
maintenanceMessage: data.maintenanceMessage || null,
|
||||
maintenanceEstimatedTime: data.maintenanceEstimatedTime || null
|
||||
});
|
||||
|
||||
toast({
|
||||
@@ -289,8 +337,9 @@ export default function GeneralForm() {
|
||||
<Input
|
||||
type="number"
|
||||
value={
|
||||
field.value ??
|
||||
""
|
||||
field.value !== undefined
|
||||
? String(field.value)
|
||||
: ""
|
||||
}
|
||||
onChange={(e) =>
|
||||
field.onChange(
|
||||
@@ -339,6 +388,239 @@ export default function GeneralForm() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<PaidFeaturesAlert></PaidFeaturesAlert>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="maintenanceModeEnabled"
|
||||
render={({ field }) => {
|
||||
const isDisabled =
|
||||
isSecurityFeatureDisabled();
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormControl>
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
asChild
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<SwitchInput
|
||||
id="enable-maintenance"
|
||||
checked={
|
||||
field.value
|
||||
}
|
||||
label={t(
|
||||
"enableMaintenanceMode"
|
||||
)}
|
||||
disabled={
|
||||
isDisabled
|
||||
}
|
||||
onCheckedChange={(
|
||||
val
|
||||
) => {
|
||||
if (
|
||||
!isDisabled
|
||||
) {
|
||||
form.setValue(
|
||||
"maintenanceModeEnabled",
|
||||
val
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</FormControl>
|
||||
</div>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"showMaintenancePage"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
{isMaintenanceEnabled && (
|
||||
<div className="space-y-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="maintenanceModeType"
|
||||
render={({ field }) => (
|
||||
<FormItem className="space-y-3">
|
||||
<FormLabel>
|
||||
{t(
|
||||
"maintenanceModeType"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RadioGroup
|
||||
onValueChange={
|
||||
field.onChange
|
||||
}
|
||||
defaultValue={
|
||||
field.value
|
||||
}
|
||||
disabled={isSecurityFeatureDisabled()}
|
||||
className="flex flex-col space-y-1"
|
||||
>
|
||||
<FormItem className="flex items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="automatic" />
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel className="font-normal">
|
||||
<strong>
|
||||
{t(
|
||||
"automatic"
|
||||
)}
|
||||
</strong>{" "}
|
||||
(
|
||||
{t(
|
||||
"recommended"
|
||||
)}
|
||||
)
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"automaticModeDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
<FormItem className="flex items-start space-x-3 space-y-0">
|
||||
<FormControl>
|
||||
<RadioGroupItem value="forced" />
|
||||
</FormControl>
|
||||
<div className="space-y-1 leading-none">
|
||||
<FormLabel className="font-normal">
|
||||
<strong>
|
||||
{t(
|
||||
"forced"
|
||||
)}
|
||||
</strong>
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"forcedModeDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
{maintenanceModeType ===
|
||||
"forced" && (
|
||||
<Alert>
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>
|
||||
<strong>
|
||||
{t("warning:")}
|
||||
</strong>{" "}
|
||||
{t(
|
||||
"forcedeModeWarning"
|
||||
)}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="maintenanceTitle"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t("pageTitle")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isSecurityFeatureDisabled()}
|
||||
placeholder="We'll be back soon!"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"pageTitleDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="maintenanceMessage"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t(
|
||||
"maintenancePageMessage"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
rows={4}
|
||||
disabled={isSecurityFeatureDisabled()}
|
||||
placeholder={t(
|
||||
"maintenancePageMessagePlaceholder"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"maintenancePageMessageDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="maintenanceEstimatedTime"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t(
|
||||
"maintenancePageTimeTitle"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
disabled={isSecurityFeatureDisabled()}
|
||||
placeholder={t(
|
||||
"maintenanceTime"
|
||||
)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"maintenanceEstimatedTimeDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
|
||||
Reference in New Issue
Block a user