fixed some bugs

This commit is contained in:
biersoeckli
2024-10-24 14:29:24 +00:00
parent 35c838df81
commit 01c7263ab9
10 changed files with 89 additions and 17 deletions

View File

@@ -30,7 +30,6 @@ export default function AppTabs({
<TabsTrigger value="environment">Environment</TabsTrigger>
<TabsTrigger value="domains">Domains</TabsTrigger>
<TabsTrigger value="storage">Storage</TabsTrigger>
<TabsTrigger value="logs">Logs</TabsTrigger>
</TabsList>
<TabsContent value="overview">Domains, Logs, etc.</TabsContent>
<TabsContent value="general" className="space-y-4">
@@ -44,7 +43,6 @@ export default function AppTabs({
<DomainsList app={app} />
</TabsContent>
<TabsContent value="storage">storage</TabsContent>
<TabsContent value="logs">logs</TabsContent>
</Tabs>
)
}

View File

@@ -20,6 +20,9 @@ import { toast } from "sonner";
import { AppEnvVariablesModel, appEnvVariablesZodModel } from "@/model/env-edit.model";
import { Textarea } from "@/components/ui/textarea";
import { AppExtendedModel } from "@/model/app-extended.model";
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { Button } from "@/components/ui/button";
import { CheckIcon, CrossIcon } from "lucide-react";
export default function DomainsList({ app }: {
@@ -31,8 +34,32 @@ export default function DomainsList({ app }: {
<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.</CardDescription>
</CardHeader>
<CardContent>
<Table>
<TableCaption>{app.appDomains.length} Domains</TableCaption>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Port</TableHead>
<TableHead>SSL</TableHead>
<TableHead>Action</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{app.appDomains.map(domain => (
<TableRow key={domain.hostname}>
<TableCell className="font-medium">{domain.hostname}</TableCell>
<TableCell className="font-medium">{domain.port}</TableCell>
<TableCell className="font-medium">{domain.useSsl ? <CheckIcon /> : <CrossIcon />}</TableCell>
<TableCell className="font-medium"><Button variant="ghost">Edit</Button></TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
<CardFooter>
<Button>Add Domain</Button>
</CardFooter>
</Card >
</>;

View File

@@ -70,6 +70,5 @@ export default function EnvEdit({ app }: {
</form>
</Form >
</Card >
</>;
}

View File

@@ -40,7 +40,14 @@ export const saveGeneralAppSourceInfo = async (prevState: any, inputData: AppSou
export const saveGeneralAppRateLimits = async (prevState: any, inputData: AppRateLimitsModel, appId: string) =>
saveFormAction(inputData, appRateLimitsZodModel, async (validatedData) => {
console.log(validatedData)
if (validatedData.replicas < 1) {
throw new ServiceException('Replica Count must be at least 1');
}
await getAuthUserSession();
const existingApp = await appService.getById(appId);
await appService.save({
...existingApp,
...validatedData,
id: appId,
});
});

View File

@@ -36,11 +36,10 @@ export default function GeneralAppRateLimits({ app }: {
FormUtils.mapValidationErrorsToForm<typeof appRateLimitsZodModel>(state, form);
}, [state]);
const sourceTypeField = form.watch();
return <>
<Card>
<CardHeader>
<CardTitle>Rate Limits</CardTitle>
<CardTitle>Container Configuration</CardTitle>
<CardDescription>Provide optional rate Limits per running container instance.</CardDescription>
</CardHeader>
<Form {...form}>
@@ -48,6 +47,22 @@ export default function GeneralAppRateLimits({ app }: {
return formAction(data);
})()}>
<CardContent className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<FormField
control={form.control}
name="replicas"
render={({ field }) => (
<FormItem>
<FormLabel>Replica Count</FormLabel>
<FormControl>
<Input type="number" {...field} value={field.value} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<FormField
@@ -107,8 +122,9 @@ export default function GeneralAppRateLimits({ app }: {
/>
</div>
</CardContent>
<CardFooter>
<CardFooter className="gap-4">
<SubmitButton>Save</SubmitButton>
<p className="text-red-500">{state?.message}</p>
</CardFooter>
</form>
</Form >

View File

@@ -10,6 +10,9 @@ import {
import projectService from "@/server/services/project.service";
import PageTitle from "@/components/custom/page-title";
import AppTabs from "./app-tabs";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { SubmitButton } from "@/components/custom/submit-button";
import { Button } from "@/components/ui/button";
export default async function AppPage({
searchParams,
@@ -46,6 +49,13 @@ export default async function AppPage({
title={app.name}
subtitle={`App ID: "${app.id}"`}>
</PageTitle>
<Card>
<CardContent className="p-4 flex gap-4">
<Button>Deploy</Button>
<Button variant="secondary">Start</Button>
<Button variant="secondary">Rebuild</Button>
</CardContent>
</Card >
<AppTabs app={app} tabName={params.tabName} />
</div>
)

View File

@@ -0,0 +1,7 @@
import { redirect } from "next/navigation";
// redirects to default route "general" for the app
export async function GET(request: Request) {
const url = new URL(request.url);
redirect(`/project/app/general?appId=${url.searchParams.get("appId")}`);
}

View File

@@ -8,17 +8,23 @@ import { formatDateTime } from "@/lib/format.utils";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { MoreHorizontal } from "lucide-react";
import { Toast } from "@/lib/toast.utils";
import { Project } from "@prisma/client";
import { App, Project } from "@prisma/client";
import { deleteApp } from "./actions";
export default function AppTable({ data }: { data: Project[] }) {
export default function AppTable({ data }: { data: App[] }) {
return <>
<SimpleDataTable columns={[
['id', 'ID', false],
['name', 'Name', true],
['sourceType', 'Source Type', false, (item) => item.sourceType === 'GIT' ? 'Git' : 'Container'],
['replicas', 'Replica Count', false],
['memoryLimit', 'Memory Limit', false],
['memoryReservation', 'Memory Reservation', false],
['cpuLimit', 'CPU Limit', false],
['cpuReservation', 'CPU Reservation', false],
["createdAt", "Created At", true, (item) => formatDateTime(item.createdAt)],
["updatedAt", "Updated At", false, (item) => formatDateTime(item.updatedAt)],
]}

View File

@@ -1,10 +1,12 @@
import { stringToNumber, stringToOptionalNumber } from "@/lib/zod.utils";
import { z } from "zod";
export const appRateLimitsZodModel = z.object({
memoryReservation: z.number().nullish(),
memoryLimit: z.number().nullish(),
cpuReservation: z.number().nullish(),
cpuLimit: z.number().nullish(),
memoryReservation: stringToOptionalNumber,
memoryLimit: stringToOptionalNumber,
cpuReservation: stringToOptionalNumber,
cpuLimit: stringToOptionalNumber,
replicas: stringToNumber,
})
export type AppRateLimitsModel = z.infer<typeof appRateLimitsZodModel>;

View File

@@ -17,7 +17,7 @@ export class SuccessActionResult<T> extends ServerActionResult<undefined, T> {
}
export class ErrorActionResult<TErrorData> extends ServerActionResult<TErrorData, undefined> {
constructor(errors: FormZodErrorValidationCallback<TErrorData>, message?: string) {
constructor(errors?: FormZodErrorValidationCallback<TErrorData>, message?: string) {
super('error', undefined, message, errors);
}
}