mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-01-01 17:20:14 -06:00
separated port configuration to Domains section
This commit is contained in:
@@ -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} />
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
|
||||
112
src/app/project/app/[tabName]/domains/internal-hostnames.tsx
Normal file
112
src/app/project/app/[tabName]/domains/internal-hostnames.tsx
Normal 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 >
|
||||
</>;
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ export const appRateLimitsZodModel = z.object({
|
||||
cpuReservation: stringToOptionalNumber,
|
||||
cpuLimit: stringToOptionalNumber,
|
||||
replicas: stringToNumber,
|
||||
defaultPort: stringToNumber,
|
||||
})
|
||||
|
||||
export type AppRateLimitsModel = z.infer<typeof appRateLimitsZodModel>;
|
||||
8
src/model/default-port.model.ts
Normal file
8
src/model/default-port.model.ts
Normal 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>;
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user