Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3ec1a5f
feat: added settings tab in project-overview drawer to edit app setti…
biersoeckli Sep 18, 2026
437c725
feat: small ui optimizations in card settings tables
biersoeckli Sep 18, 2026
65a3fd2
fix: resolve linter errors
biersoeckli Sep 20, 2026
4a7348b
feat: add live per-app build status streaming
aperolschpritz Sep 20, 2026
d849a58
feat: enhance DrawerOverview and DrawerSettings components with impro…
biersoeckli Sep 20, 2026
49872fb
feat: refactor build status apperance in project canvas
biersoeckli Sep 20, 2026
9fa3ca6
feat: removed buggy logs archive and export feature
biersoeckli Sep 20, 2026
7c0a28b
feat: extend project canvas with app configuration settings in contex…
biersoeckli Sep 20, 2026
06f4af8
feat: add database credentials section to NodeDetailsDrawer for non-A…
biersoeckli Sep 20, 2026
47169a3
Merge branch 'main' into feat/extend-project-canva-view-with-app-conf…
aperolschpritz Sep 20, 2026
ae168e7
feat: enhance project network graph with queryParams
biersoeckli Sep 20, 2026
a8be8a0
feat: enhance project network graph context menu app configuration se…
biersoeckli Sep 20, 2026
30c1251
feat: update settings section and enhance app status actions with too…
biersoeckli Sep 20, 2026
5a416c6
feat: enhance AppStatusActions with domain opening functionality and …
biersoeckli Sep 20, 2026
50b1fa5
feat: integrate app lifecycle management and enhance project network …
biersoeckli Sep 20, 2026
e4708de
feat: moved app rename dialog to drawer
biersoeckli Sep 20, 2026
9eee28f
feat: enhance nested drawer with deployment logs functionality and im…
biersoeckli Sep 21, 2026
91ed573
feat: implement build and pods status streaming services with SSE and…
biersoeckli Sep 21, 2026
18628ff
fix: small ui fixes in canva view and added webhook settings
biersoeckli Sep 21, 2026
7f59d5a
feat: added backup tab to drawer in project canva view
biersoeckli Sep 21, 2026
1b25b54
feat: enhance project network graph with context menu and app creatio…
biersoeckli Sep 22, 2026
0fcc498
feat: improved logs ui in project drawer
biersoeckli Sep 22, 2026
21533fc
feat: fixed scroll area issues in project graph drawer
biersoeckli Sep 23, 2026
003aec6
fix: refactor ScrollArea component to conditionally apply overflow st…
biersoeckli Sep 23, 2026
b7303f7
feat: redesign 404 and error pages with shadcn empty state
aperolschpritz Sep 23, 2026
eb66d58
fix: date parser problem in format.utils.ts
biersoeckli Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
"elysia": "^1.4.28",
"exact-mirror": "^1.0.0",
"file-type": "^22.0.1",
"lucide-react": "^0.465.0",
"lucide-react": "^1.47.0",
"moment": "^2.30.1",
"next": "^15",
"next-auth": "^4.24.14",
Expand Down
103 changes: 103 additions & 0 deletions src/app/api/build-status/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import buildLiveStatusService from "@/server/services/build-live-status.service";
import buildStatusService from "@/server/services/standalone-services/build-status-pub-sub.service";
import buildWatchService from "@/server/services/standalone-services/build-watch.service";
import { getAuthUserSession, simpleRoute } from "@/server/utils/action-wrapper.utils";
import { StreamUtils } from "@/shared/utils/stream.utils";

// Prevents this route's response from being cached
export const dynamic = "force-dynamic";

export async function POST() {
return simpleRoute(async () => {
const session = await getAuthUserSession();

void buildWatchService.startWatch();
await buildStatusService.ensureSeeded();

let appLookup = await buildLiveStatusService.getBuildableAppLookup(session);

const encoder = new TextEncoder();
let shouldStopStreaming = false;
let unsubscribe: (() => void) | null = null;
let heartbeat: ReturnType<typeof setInterval> | null = null;

const customReadable = new ReadableStream({
async start(controller) {
const sendData = (data: unknown) => {
if (shouldStopStreaming) {
return;
}
try {
controller.enqueue(encoder.encode(StreamUtils.encodeSseData(data)));
} catch (e) {
console.error(`[BUILD STATUS] Error while enqueueing build status data: `, e);
shouldStopStreaming = true;
unsubscribe?.();
unsubscribe = null;
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
controller.close();
}
};

unsubscribe = buildStatusService.subscribe(async (status) => {
if (shouldStopStreaming || status.workloadType !== 'app') {
return;
}

let appInfo = appLookup.get(status.workloadId);
if (!appInfo) {
// A new app might have been created while streaming, refresh the lookup.
appLookup = await buildLiveStatusService.getBuildableAppLookup(session);
appInfo = appLookup.get(status.workloadId);
}
if (!appInfo) {
return;
}

sendData(buildLiveStatusService.mapBuildToStatus(status, appInfo));
});

try {
sendData(buildLiveStatusService.getInitialStatus(appLookup));
} catch (e) {
console.error("Error fetching initial build status", e);
}

heartbeat = setInterval(() => {
if (shouldStopStreaming) return;
try {
controller.enqueue(encoder.encode(': ping\n\n'));
} catch (error) {
console.error('[BUILD STATUS] Error while sending heartbeat:', error);
shouldStopStreaming = true;
unsubscribe?.();
unsubscribe = null;
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
controller.close();
}
}, 25_000);
},
cancel() {
console.log("[BUILD STATUS] Client left, cancelling build status stream");
shouldStopStreaming = true;
if (unsubscribe) {
unsubscribe();
unsubscribe = null;
}
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
},
});

return new Response(customReadable, {
headers: {
Connection: "keep-alive",
"Content-Encoding": "none",
"Cache-Control": "no-cache, no-transform",
"Content-Type": "text/event-stream; charset=utf-8",
},
});
});
}
22 changes: 22 additions & 0 deletions src/app/api/deployment-status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export async function POST() {
const encoder = new TextEncoder();
let shouldStopStreaming = false;
let unsubscribe: (() => void) | null = null;
let heartbeat: ReturnType<typeof setInterval> | null = null;

// Fetch all projects and apps to build a lookup map
let appLookup = await deploymentLiveStatusService.getAppLookup(session);
Expand All @@ -37,6 +38,10 @@ export async function POST() {
} catch (e) {
console.error(`[ENQUEUE ERROR] Error while enqueueing Deployment Status data: `, e);
shouldStopStreaming = true;
unsubscribe?.();
unsubscribe = null;
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
controller.close();
}
};
Expand Down Expand Up @@ -85,6 +90,21 @@ export async function POST() {

sendData(status);
});

heartbeat = setInterval(() => {
if (shouldStopStreaming) return;
try {
controller.enqueue(encoder.encode(': ping\n\n'));
} catch (error) {
console.error('[ENQUEUE ERROR] Error while sending deployment status heartbeat:', error);
shouldStopStreaming = true;
unsubscribe?.();
unsubscribe = null;
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
controller.close();
}
}, 25_000);
},
cancel() {
console.log("[LEAVE] Cancelling deployment status stream");
Expand All @@ -93,6 +113,8 @@ export async function POST() {
unsubscribe();
unsubscribe = null;
}
if (heartbeat) clearInterval(heartbeat);
heartbeat = null;
}
});

Expand Down
48 changes: 0 additions & 48 deletions src/app/api/logs-download/route.ts

This file was deleted.

28 changes: 28 additions & 0 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use client';

import ErrorState from '@/components/custom/error-state';
import { CircleAlert } from 'lucide-react';
import { useEffect } from 'react';

export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);

return (
<ErrorState
icon={<CircleAlert />}
mediaClassName="bg-destructive/10 text-destructive"
title="Something went wrong"
description="An unexpected error occurred while loading this page. You can try again, or return to the dashboard."
digest={error.digest}
onRetry={reset}
/>
);
}
16 changes: 10 additions & 6 deletions src/app/error/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import ErrorState from '@/components/custom/error-state';
import { TriangleAlert } from 'lucide-react';

export default function ErrorPage() {

return (
<div>
Error Page
</div>
)
}
<ErrorState
icon={<TriangleAlert />}
mediaClassName="bg-destructive/10 text-destructive"
title="Something went wrong"
description="An unexpected error occurred. Please try again or return to the dashboard."
/>
);
}
54 changes: 26 additions & 28 deletions src/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,39 @@
'use client' // Error boundaries must be Client Components
'use client';

import { cn } from "@/frontend/utils/utils";
import { AlertCircle } from "lucide-react"
import { Inter } from "next/font/google";
import ErrorState from '@/components/custom/error-state';
import { cn } from '@/frontend/utils/utils';
import { TriangleAlert } from 'lucide-react';
import { Inter } from 'next/font/google';

const inter = Inter({
subsets: ["latin"],
variable: "--font-sans",
subsets: ['latin'],
variable: '--font-sans',
});

export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html >
<body className={cn(
"min-h-screen bg-background font-sans antialiased",
inter.variable
)}>
<div className="h-screen w-fuäll flex flex-col items-center justify-center p-4 space-y-4 bg-background text-foreground">
<div className="flex flex-col items-center justify-center space-y-2 text-center max-w-md">
<div className="rounded-full bg-destructive/10 p-3">
<AlertCircle className="h-8 w-8 text-destructive" />
</div>
<h2 className="text-2xl font-bold tracking-tight">Something went wrong!</h2>
<p className="text-muted-foreground mt-4">
An unexpected error occurred. Please check if your authorized for this action and try again.
</p>
<p className="text-xs text-muted-foreground mt-6">
Digest: {error.digest}
</p>
</div>
</div>
<html lang="en">
<body
className={cn(
'min-h-screen bg-background font-sans antialiased',
inter.variable
)}
>
<ErrorState
icon={<TriangleAlert />}
mediaClassName="bg-destructive/10 text-destructive"
title="Something went wrong"
description="A critical error occurred. Please try again, or reload the page to get back to the dashboard."
digest={error.digest}
onRetry={reset}
/>
</body>
</html>
)
);
}
6 changes: 4 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { BreadcrumbsGenerator } from "../components/custom/breadcrumbs-generator
import { getUserSession } from "@/server/utils/action-wrapper.utils";
import { InputDialog } from "@/components/custom/input-dialog";
import PodsStatusPollingProvider from "@/components/custom/pods-status-polling-provider";
import BuildStatusPollingProvider from "@/components/custom/build-status-polling-provider";
import { GenericDialog } from "@/components/custom/generic-dialog";

const inter = Inter({
Expand Down Expand Up @@ -50,8 +51,8 @@ export default async function RootLayout({
<SidebarProvider defaultOpen={defaultOpen}>
<AppSidebar />
<main className="flex min-w-0 flex-1 flex-col items-center">
<div className="w-full min-w-0 max-w-8xl px-2 lg:px-4">
<div className="min-w-0 flex-col p-6 md:flex">
<div className="w-full min-w-0 max-w-8xl px-2 has-[[data-project-network-graph]]:max-w-none lg:px-4">
<div className="min-w-0 flex-col p-6 md:flex has-[[data-project-network-graph]]:pb-0">
{userIsLoggedIn && <BreadcrumbsGenerator />}
<Suspense fallback={<FullLoadingSpinner />}>
{children}
Expand All @@ -66,6 +67,7 @@ export default async function RootLayout({
<InputDialog />
<GenericDialog />
{userIsLoggedIn && <PodsStatusPollingProvider />}
{userIsLoggedIn && <BuildStatusPollingProvider />}
</body>
</html>
);
Expand Down
12 changes: 12 additions & 0 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import ErrorState from '@/components/custom/error-state';
import { FileQuestion } from 'lucide-react';

export default function NotFound() {
return (
<ErrorState
icon={<FileQuestion className="text-muted-foreground" />}
title="Page not found"
description="The page you are looking for doesn't exist or may have been moved."
/>
);
}
2 changes: 1 addition & 1 deletion src/app/project/[projectId]/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export const createApp = async (appName: string, projectId: string, appId?: stri
projectId
});

return new SuccessActionResult(returnData, "App created successfully.");
return new SuccessActionResult(returnData, "Saved successfully.");
});

export const createAppFromTemplate = async (prevState: any, inputData: AppTemplateModel, projectId: string) =>
Expand Down
Loading
Loading