From 765a199f6b578149358e1529429260325edf4ad8 Mon Sep 17 00:00:00 2001 From: Lftobs Date: Fri, 11 Sep 2026 11:11:24 +0100 Subject: [PATCH 1/2] refactor(web,api): redesign keys and settings pages with tabbed navigation - Update compose environment inheritance, refactor keys and settings views with modern dashboard components, hero headers, and tabbed layouts. --- .../orchestrator/__tests__/compose.test.ts | 91 ++- apps/api/src/orchestrator/compose.ts | 2 +- .../databases/CreateDatabaseDialog.tsx | 187 +++--- .../src/components/databases/DatabaseCard.tsx | 203 ++++--- .../components/settings/ApiKeysSection.tsx | 273 +++++++-- .../settings/GithubIntegrationSection.tsx | 245 ++++++-- .../components/settings/ServersSection.tsx | 546 +++++++++++------- .../settings/SharedEnvVarsSection.tsx | 308 +++++++--- .../src/components/settings/SmtpSection.tsx | 235 ++++++-- .../components/settings/SshKeyPoolSection.tsx | 282 ++++++--- apps/web/src/routes/Databases.tsx | 143 +++-- apps/web/src/routes/Keys.tsx | 194 ++++++- apps/web/src/routes/Settings.tsx | 117 +++- apps/web/src/routes/SharedEnv.tsx | 81 ++- 14 files changed, 2165 insertions(+), 742 deletions(-) diff --git a/apps/api/src/orchestrator/__tests__/compose.test.ts b/apps/api/src/orchestrator/__tests__/compose.test.ts index b86cc46..2597901 100644 --- a/apps/api/src/orchestrator/__tests__/compose.test.ts +++ b/apps/api/src/orchestrator/__tests__/compose.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, mock, test, afterEach } from "bun:test"; import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -121,3 +121,92 @@ services: } }); }); + +describe("spawnComposeCommand env isolation", () => { + afterEach(() => { + mock.restore(); + }); + + test("does not pass process.env to docker compose subprocess", async () => { + process.env.DATABASE_URL = "postgresql://dequel:secret@localhost:5432/dequel"; + process.env.PORT = "3001"; + process.env.WORKSPACE_ROOT = "./workspace"; + + let capturedEnv: Record | undefined; + const originalSpawn = (await import("node:child_process")).spawn; + mock.module("node:child_process", () => ({ + spawn: (...args: any[]) => { + capturedEnv = args[2]?.env; + const child = originalSpawn(...args); + setTimeout(() => child.kill("SIGTERM"), 10); + return child; + }, + })); + + const dir = mkdtempSync(join(tmpdir(), "dequel-compose-env-test-")); + try { + writeFileSync( + join(dir, "docker-compose.yml"), + `version: '3.8'\nservices:\n app:\n image: alpine\n command: echo "hello"`, + ); + + const { buildWithCompose } = await import("../compose"); + const projectEnv = { MY_APP_VAR: "hello", ANOTHER_VAR: "world" }; + + try { + await buildWithCompose(dir, "test-project", async () => {}, null, projectEnv); + } catch { + // Expected to fail since we're killing the process + } + + expect(capturedEnv).toBeDefined(); + expect(capturedEnv).not.toHaveProperty("DATABASE_URL"); + expect(capturedEnv).not.toHaveProperty("PORT"); + expect(capturedEnv).not.toHaveProperty("WORKSPACE_ROOT"); + expect(capturedEnv).toHaveProperty("MY_APP_VAR", "hello"); + expect(capturedEnv).toHaveProperty("ANOTHER_VAR", "world"); + } finally { + rmSync(dir, { recursive: true, force: true }); + delete process.env.DATABASE_URL; + delete process.env.PORT; + delete process.env.WORKSPACE_ROOT; + } + }); + + test("passes empty env when no project env vars provided", async () => { + process.env.DATABASE_URL = "postgresql://dequel:secret@localhost:5432/dequel"; + + let capturedEnv: Record | undefined; + const originalSpawn = (await import("node:child_process")).spawn; + mock.module("node:child_process", () => ({ + spawn: (...args: any[]) => { + capturedEnv = args[2]?.env; + const child = originalSpawn(...args); + setTimeout(() => child.kill("SIGTERM"), 10); + return child; + }, + })); + + const dir = mkdtempSync(join(tmpdir(), "dequel-compose-env-test-")); + try { + writeFileSync( + join(dir, "docker-compose.yml"), + `version: '3.8'\nservices:\n app:\n image: alpine\n command: echo "hello"`, + ); + + const { buildWithCompose } = await import("../compose"); + + try { + await buildWithCompose(dir, "test-project", async () => {}, null, undefined); + } catch { + // Expected to fail since we're killing the process + } + + expect(capturedEnv).toBeDefined(); + expect(capturedEnv).toEqual({}); + } finally { + rmSync(dir, { recursive: true, force: true }); + delete process.env.DATABASE_URL; + } + }); +}); diff --git a/apps/api/src/orchestrator/compose.ts b/apps/api/src/orchestrator/compose.ts index 5b3998a..d25df56 100644 --- a/apps/api/src/orchestrator/compose.ts +++ b/apps/api/src/orchestrator/compose.ts @@ -201,7 +201,7 @@ const spawnComposeCommand = ( return new Promise((resolve, reject) => { const child = spawn(dockerBin, ["compose", ...args], { cwd, - env: { ...process.env, ...(envVars || {}) }, + env: envVars || {}, stdio: ["ignore", "pipe", "pipe"], }); diff --git a/apps/web/src/components/databases/CreateDatabaseDialog.tsx b/apps/web/src/components/databases/CreateDatabaseDialog.tsx index 9d14f03..04a05cf 100644 --- a/apps/web/src/components/databases/CreateDatabaseDialog.tsx +++ b/apps/web/src/components/databases/CreateDatabaseDialog.tsx @@ -1,3 +1,4 @@ +import { Database, HardDrive, ShieldAlert, Cpu } from "lucide-react"; import { useEffect, useState } from "react"; import * as api from "../../api/client"; import type { DatabaseType, Project } from "../../types"; @@ -68,36 +69,46 @@ export function CreateDatabaseDialog({ return ( - - - Create managed database - - Public access is enabled by default and protected by database credentials plus the network allowlist. - + + +
+
+ +
+
+ Provision Managed Database + + Public access endpoint enabled with optional CIDR network IP allowlisting. + +
+
+
-
-