separated port configuration to Domains section

This commit is contained in:
biersoeckli
2024-11-05 18:59:07 +00:00
parent 83b091298e
commit 98cbb88804
8 changed files with 145 additions and 37 deletions

View File

@@ -12,6 +12,7 @@ import { AppExtendedModel } from "@/model/app-extended.model";
import { BuildJobModel } from "@/model/build-job";
import BuildsTab from "./overview/deployments";
import Logs from "./overview/logs";
import InternalHostnames from "./domains/internal-hostnames";
export default function AppTabs({
app,
@@ -48,6 +49,7 @@ export default function AppTabs({
</TabsContent>
<TabsContent value="domains" className="space-y-4">
<DomainsList app={app} />
<InternalHostnames app={app} />
</TabsContent>
<TabsContent value="storage" className="space-y-4">
<StorageList app={app} />

View File

@@ -1,5 +1,7 @@
'use server'
import { AppRateLimitsModel } from "@/model/app-rate-limits.model";
import { AppDefaultPortsModel, appdefaultPortZodModel } from "@/model/default-port.model";
import { appDomainEditZodModel } from "@/model/domain-edit.model";
import { SuccessActionResult } from "@/model/server-action-error-return.model";
import appService from "@/server/services/app.service";
@@ -20,9 +22,20 @@ export const saveDomain = async (prevState: any, inputData: z.infer<typeof actio
});
});
export const deleteDomain = async (domainId: string) =>
simpleAction(async () => {
await getAuthUserSession();
await appService.deleteDomainById(domainId);
return new SuccessActionResult(undefined, 'Successfully deleted domain');
export const deleteDomain = async (domainId: string) =>
simpleAction(async () => {
await getAuthUserSession();
await appService.deleteDomainById(domainId);
return new SuccessActionResult(undefined, 'Successfully deleted domain');
});
export const saveDefaultPortConfiguration = async (prevState: any, inputData: AppDefaultPortsModel, appId: string) =>
saveFormAction(inputData, appdefaultPortZodModel, async (validatedData) => {
await getAuthUserSession();
const existingApp = await appService.getById(appId);
await appService.save({
...existingApp,
...validatedData,
id: appId,
});
});

View File

@@ -36,28 +36,11 @@ export default function DomainsList({ app }: {
app: AppExtendedModel
}) {
const internalUrl = StringUtils.toServiceName(app.id);
return <>
<Card>
<CardHeader>
<CardTitle>Domains</CardTitle>
<CardDescription>Add custom domains to your application. If your app has a domain configured, it will be public and accessible via the internet. <br />
<div className="flex gap-1">
<div>Internal Hostname: <Code>{internalUrl}</Code></div>
<div className="self-center">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild><QuestionMarkCircledIcon /></TooltipTrigger>
<TooltipContent>
<p className="max-w-[350px]">
Other app can connect to this app using this hostname. This hostname is valid for all internal connections within the same project.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
<CardDescription>Add custom domains to your application. If your app has a domain configured, it will be public and accessible via the internet.
</CardDescription>
</CardHeader>
<CardContent>

View File

@@ -0,0 +1,112 @@
'use client';
import { SubmitButton } from "@/components/custom/submit-button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { FormUtils } from "@/lib/form.utilts";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { saveDefaultPortConfiguration } from "./actions";
import { useFormState } from "react-dom";
import { ServerActionResult } from "@/model/server-action-error-return.model";
import { Input } from "@/components/ui/input";
import { useEffect } from "react";
import { toast } from "sonner";
import { AppExtendedModel } from "@/model/app-extended.model";
import { AppDefaultPortsModel, appdefaultPortZodModel } from "@/model/default-port.model";
import { StringUtils } from "@/server/utils/string.utils";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
import { QuestionMarkCircledIcon } from "@radix-ui/react-icons";
import { Code } from "@/components/custom/code";
import { ListUtils } from "@/server/utils/list.utils";
export default function InternalHostnames({ app }: {
app: AppExtendedModel
}) {
const internalUrl = StringUtils.toServiceName(app.id);
const form = useForm<AppDefaultPortsModel>({
resolver: zodResolver(appdefaultPortZodModel),
defaultValues: app
});
const [state, formAction] = useFormState((state: ServerActionResult<any, any>, payload: AppDefaultPortsModel) =>
saveDefaultPortConfiguration(state, payload, app.id), FormUtils.getInitialFormState<typeof appdefaultPortZodModel>());
useEffect(() => {
if (state.status === 'success') {
toast.success('Data Saved');
}
FormUtils.mapValidationErrorsToForm<typeof appdefaultPortZodModel>(state, form);
}, [state]);
return <>
<Card>
<CardHeader>
<CardTitle>Default Port</CardTitle>
<CardDescription>Provide a default port for the application to connect to the app from other apps in same project.</CardDescription>
</CardHeader>
<Form {...form}>
<form action={(e) => form.handleSubmit((data) => {
return formAction(data);
})()}>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="defaultPort"
render={({ field }) => (
<FormItem>
<FormLabel>Default Port</FormLabel>
<FormControl>
<Input type="number" {...field} value={field.value} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
<CardFooter className="gap-4">
<SubmitButton>Save</SubmitButton>
<p className="text-red-500">{state?.message}</p>
</CardFooter>
</form>
</Form >
</Card >
<Card>
<CardHeader>
<CardTitle>Internal Hostnames</CardTitle>
<CardDescription>Internal hostnames can be used to connect to this app from other apps in the same project. </CardDescription>
</CardHeader>
<CardContent>
{ListUtils.removeDuplicates([
app.defaultPort,
...app.appDomains.map(d => d.port)
]).map(port => (
<div key={port} className="flex gap-1 pb-2">
<div><Code>{internalUrl + ':' + port}</Code></div>
<div className="self-center">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild><QuestionMarkCircledIcon /></TooltipTrigger>
<TooltipContent>
<p className="max-w-[350px]">
Other app can connect to this app using this hostname. This hostname is valid for all internal connections within the same project.
</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div>
))}
</CardContent>
</Card >
</>;
}

View File

@@ -62,19 +62,6 @@ export default function GeneralAppRateLimits({ app }: {
</FormItem>
)}
/>
<FormField
control={form.control}
name="defaultPort"
render={({ field }) => (
<FormItem>
<FormLabel>Default Port</FormLabel>
<FormControl>
<Input type="number" {...field} value={field.value} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-2 gap-4">

View File

@@ -7,7 +7,6 @@ export const appRateLimitsZodModel = z.object({
cpuReservation: stringToOptionalNumber,
cpuLimit: stringToOptionalNumber,
replicas: stringToNumber,
defaultPort: stringToNumber,
})
export type AppRateLimitsModel = z.infer<typeof appRateLimitsZodModel>;

View File

@@ -0,0 +1,8 @@
import { stringToNumber, stringToOptionalNumber } from "@/lib/zod.utils";
import { z } from "zod";
export const appdefaultPortZodModel = z.object({
defaultPort: stringToNumber,
})
export type AppDefaultPortsModel = z.infer<typeof appdefaultPortZodModel>;

View File

@@ -1,5 +1,9 @@
export class ListUtils {
static removeDuplicates<T>(array: T[]): T[] {
return Array.from(new Set(array));
}
static sortByDate<T>(array: T[], dateSelector: (item: T) => Date, descending = false): T[] {
return array.toSorted((a, b) => {
const dateA = dateSelector(a);