v1.0.19
This commit is contained in:
1 parent
4b430340db
commit
9c451aedf9
120 files changed
+7777
-6046
No files matched your search
@@ -1,9 +1,10 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { listComposeStacks, deployStack, saveStackComposeFile, writeStackEnvFile, writeRawStackEnvFile, saveStackEnvVarsToDb } from '$lib/server/stacks';
|
||||
import { EnvironmentNotFoundError } from '$lib/server/docker';
|
||||
import { EnvironmentNotFoundError, DockerConnectionError } from '$lib/server/docker';
|
||||
import { upsertStackSource, getStackSources } from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditStack } from '$lib/server/audit';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
@@ -62,8 +63,11 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
if (error instanceof EnvironmentNotFoundError) {
|
||||
return json({ error: 'Environment not found' }, { status: 404 });
|
||||
}
|
||||
// Silently return empty for connection errors (offline environments)
|
||||
if (error instanceof DockerConnectionError) {
|
||||
return json([]);
|
||||
}
|
||||
console.error('Error listing compose stacks:', error);
|
||||
// Return empty array instead of error to allow UI to load
|
||||
return json([]);
|
||||
}
|
||||
};
|
||||
@@ -162,20 +166,7 @@ export const POST: RequestHandler = async (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Deploy and start the stack
|
||||
const result = await deployStack({
|
||||
name,
|
||||
compose,
|
||||
envId: envIdNum,
|
||||
composePath: composePath || undefined,
|
||||
envPath: envPath || undefined
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
return json({ error: result.error, output: result.output }, { status: 400 });
|
||||
}
|
||||
|
||||
// Record the stack as internally created with custom paths if provided
|
||||
// Record the stack in DB before deploying - ensures it exists even if deploy fails
|
||||
await upsertStackSource({
|
||||
stackName: name,
|
||||
environmentId: envIdNum,
|
||||
@@ -184,10 +175,31 @@ export const POST: RequestHandler = async (event) => {
|
||||
envPath: envPath || undefined
|
||||
});
|
||||
|
||||
// Audit log (create + deploy in one action)
|
||||
await auditStack(event, 'deploy', name, envIdNum);
|
||||
// Deploy via SSE to keep connection alive during long operations
|
||||
return createJobResponse(async (send) => {
|
||||
try {
|
||||
const result = await deployStack({
|
||||
name,
|
||||
compose,
|
||||
envId: envIdNum,
|
||||
composePath: composePath || undefined,
|
||||
envPath: envPath || undefined
|
||||
});
|
||||
|
||||
return json({ success: true, started: true, output: result.output });
|
||||
if (!result.success) {
|
||||
send('result', { success: false, error: result.error, output: result.output });
|
||||
return;
|
||||
}
|
||||
|
||||
// Audit log (create + deploy in one action)
|
||||
await auditStack(event, 'deploy', name, envIdNum);
|
||||
|
||||
send('result', { success: true, started: true, output: result.output });
|
||||
} catch (error: any) {
|
||||
console.error('Error deploying compose stack:', error);
|
||||
send('result', { success: false, error: error.message || 'Failed to deploy stack' });
|
||||
}
|
||||
}, request);
|
||||
} catch (error: any) {
|
||||
console.error('Error creating compose stack:', error);
|
||||
return json({ error: error.message || 'Failed to create stack' }, { status: 500 });
|
||||
|
||||
@@ -2,6 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getStackComposeFile, deployStack, saveStackComposeFile } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
|
||||
// GET /api/stacks/[name]/compose - Get compose file content
|
||||
export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
@@ -66,7 +67,6 @@ export const PUT: RequestHandler = async ({ params, request, url, cookies }) =>
|
||||
? { composePath, envPath, moveFromDir, oldComposePath, oldEnvPath }
|
||||
: undefined;
|
||||
|
||||
let result;
|
||||
if (restart) {
|
||||
// Deploy with docker compose up -d --force-recreate
|
||||
// Force recreate ensures env var changes are applied
|
||||
@@ -79,19 +79,34 @@ export const PUT: RequestHandler = async ({ params, request, url, cookies }) =>
|
||||
}
|
||||
// Get authoritative paths from DB/filesystem for deploy
|
||||
const composeInfo = await getStackComposeFile(name, envIdNum);
|
||||
result = await deployStack({
|
||||
name,
|
||||
compose: content,
|
||||
envId: envIdNum,
|
||||
forceRecreate: true,
|
||||
composePath: composeInfo.composePath || undefined,
|
||||
envPath: composeInfo.envPath || undefined
|
||||
});
|
||||
} else {
|
||||
// Just save the file without restarting (update operation, not create)
|
||||
result = await saveStackComposeFile(name, content, false, envIdNum, pathOptions);
|
||||
|
||||
// Deploy via SSE to keep connection alive during long operations
|
||||
return createJobResponse(async (send) => {
|
||||
try {
|
||||
const result = await deployStack({
|
||||
name,
|
||||
compose: content,
|
||||
envId: envIdNum,
|
||||
forceRecreate: true,
|
||||
composePath: composeInfo.composePath || undefined,
|
||||
envPath: composeInfo.envPath || undefined
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
send('result', { success: false, error: result.error });
|
||||
return;
|
||||
}
|
||||
send('result', { success: true });
|
||||
} catch (error: any) {
|
||||
console.error(`Error deploying stack ${name}:`, error);
|
||||
send('result', { success: false, error: error.message || 'Failed to deploy stack' });
|
||||
}
|
||||
}, request);
|
||||
}
|
||||
|
||||
// Just save the file without restarting (update operation, not create)
|
||||
const result = await saveStackComposeFile(name, content, false, envIdNum, pathOptions);
|
||||
|
||||
if (!result.success) {
|
||||
return json({ error: result.error }, { status: 500 });
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import { downStack, ComposeFileNotFoundError } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditStack } from '$lib/server/audit';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
@@ -21,31 +22,35 @@ export const POST: RequestHandler = async (event) => {
|
||||
return json({ error: 'Access denied to this environment' }, { status: 403 });
|
||||
}
|
||||
|
||||
// Parse body BEFORE creating SSE response (body can only be read once)
|
||||
let removeVolumes = false;
|
||||
try {
|
||||
// Parse body for optional removeVolumes flag
|
||||
let removeVolumes = false;
|
||||
try {
|
||||
const body = await request.json();
|
||||
removeVolumes = body.removeVolumes === true;
|
||||
} catch {
|
||||
// No body or invalid JSON - use defaults
|
||||
}
|
||||
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await downStack(stackName, envIdNum, removeVolumes);
|
||||
|
||||
// Audit log
|
||||
await auditStack(event, 'down', stackName, envIdNum, { removeVolumes });
|
||||
|
||||
if (!result.success) {
|
||||
return json({ success: false, error: result.error }, { status: 400 });
|
||||
}
|
||||
return json({ success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
return json({ error: error.message }, { status: 404 });
|
||||
}
|
||||
console.error('Error downing compose stack:', error);
|
||||
return json({ error: 'Failed to down compose stack' }, { status: 500 });
|
||||
const body = await request.json();
|
||||
removeVolumes = body.removeVolumes === true;
|
||||
} catch {
|
||||
// No body or invalid JSON - use defaults
|
||||
}
|
||||
|
||||
return createJobResponse(async (send) => {
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await downStack(stackName, envIdNum, removeVolumes);
|
||||
|
||||
// Audit log
|
||||
await auditStack(event, 'down', stackName, envIdNum, { removeVolumes });
|
||||
|
||||
if (!result.success) {
|
||||
send('result', { success: false, error: result.error });
|
||||
return;
|
||||
}
|
||||
send('result', { success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
send('result', { success: false, error: error.message });
|
||||
return;
|
||||
}
|
||||
console.error('Error downing compose stack:', error);
|
||||
send('result', { success: false, error: 'Failed to down compose stack' });
|
||||
}
|
||||
}, request);
|
||||
};
|
||||
+2
-2
@@ -2,7 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import { getStackEnvVars, setStackEnvVars, getStackSource } from '$lib/server/db';
|
||||
import { findStackDir } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
@@ -94,7 +94,7 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
// Internal/adopted stacks: non-secrets from file, secrets from DB
|
||||
if (envFilePath && existsSync(envFilePath)) {
|
||||
try {
|
||||
const content = await Bun.file(envFilePath).text();
|
||||
const content = readFileSync(envFilePath, 'utf-8');
|
||||
const fileVars = parseEnvFile(content);
|
||||
for (const [key, value] of Object.entries(fileVars)) {
|
||||
variables.push({ key, value, isSecret: false });
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import { findStackDir, getStackDir } from '$lib/server/stacks';
|
||||
import { getStackSource } from '$lib/server/db';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { existsSync, rmSync } from 'node:fs';
|
||||
import { existsSync, rmSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
@@ -58,7 +58,7 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
let content = '';
|
||||
if (envFilePath && existsSync(envFilePath)) {
|
||||
try {
|
||||
content = await Bun.file(envFilePath).text();
|
||||
content = readFileSync(envFilePath, 'utf-8');
|
||||
} catch {
|
||||
// File read failed
|
||||
}
|
||||
@@ -154,7 +154,7 @@ export const PUT: RequestHandler = async ({ params, url, cookies, request }) =>
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
await Bun.write(envFilePath, content);
|
||||
writeFileSync(envFilePath, content);
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import { restartStack, ComposeFileNotFoundError } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditStack } from '$lib/server/audit';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
@@ -21,22 +22,26 @@ export const POST: RequestHandler = async (event) => {
|
||||
return json({ error: 'Access denied to this environment' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await restartStack(stackName, envIdNum);
|
||||
return createJobResponse(async (send) => {
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await restartStack(stackName, envIdNum);
|
||||
|
||||
// Audit log
|
||||
await auditStack(event, 'restart', stackName, envIdNum);
|
||||
// Audit log
|
||||
await auditStack(event, 'restart', stackName, envIdNum);
|
||||
|
||||
if (!result.success) {
|
||||
return json({ success: false, error: result.error }, { status: 400 });
|
||||
if (!result.success) {
|
||||
send('result', { success: false, error: result.error });
|
||||
return;
|
||||
}
|
||||
send('result', { success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
send('result', { success: false, error: error.message });
|
||||
return;
|
||||
}
|
||||
console.error('Error restarting compose stack:', error);
|
||||
send('result', { success: false, error: 'Failed to restart compose stack' });
|
||||
}
|
||||
return json({ success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
return json({ error: error.message }, { status: 404 });
|
||||
}
|
||||
console.error('Error restarting compose stack:', error);
|
||||
return json({ error: 'Failed to restart compose stack' }, { status: 500 });
|
||||
}
|
||||
}, event.request);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import { startStack, ComposeFileNotFoundError } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditStack } from '$lib/server/audit';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
@@ -21,22 +22,26 @@ export const POST: RequestHandler = async (event) => {
|
||||
return json({ error: 'Access denied to this environment' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await startStack(stackName, envIdNum);
|
||||
return createJobResponse(async (send) => {
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await startStack(stackName, envIdNum);
|
||||
|
||||
// Audit log
|
||||
await auditStack(event, 'start', stackName, envIdNum);
|
||||
// Audit log
|
||||
await auditStack(event, 'start', stackName, envIdNum);
|
||||
|
||||
if (!result.success) {
|
||||
return json({ success: false, error: result.error }, { status: 400 });
|
||||
if (!result.success) {
|
||||
send('result', { success: false, error: result.error });
|
||||
return;
|
||||
}
|
||||
send('result', { success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
send('result', { success: false, error: error.message });
|
||||
return;
|
||||
}
|
||||
console.error('Error starting compose stack:', error);
|
||||
send('result', { success: false, error: 'Failed to start compose stack' });
|
||||
}
|
||||
return json({ success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
return json({ error: error.message }, { status: 404 });
|
||||
}
|
||||
console.error('Error starting compose stack:', error);
|
||||
return json({ error: 'Failed to start compose stack' }, { status: 500 });
|
||||
}
|
||||
}, event.request);
|
||||
};
|
||||
@@ -2,6 +2,7 @@ import { json } from '@sveltejs/kit';
|
||||
import { stopStack, ComposeFileNotFoundError } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { auditStack } from '$lib/server/audit';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const POST: RequestHandler = async (event) => {
|
||||
@@ -21,22 +22,26 @@ export const POST: RequestHandler = async (event) => {
|
||||
return json({ error: 'Access denied to this environment' }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await stopStack(stackName, envIdNum);
|
||||
return createJobResponse(async (send) => {
|
||||
try {
|
||||
const stackName = decodeURIComponent(params.name);
|
||||
const result = await stopStack(stackName, envIdNum);
|
||||
|
||||
// Audit log
|
||||
await auditStack(event, 'stop', stackName, envIdNum);
|
||||
// Audit log
|
||||
await auditStack(event, 'stop', stackName, envIdNum);
|
||||
|
||||
if (!result.success) {
|
||||
return json({ success: false, error: result.error }, { status: 400 });
|
||||
if (!result.success) {
|
||||
send('result', { success: false, error: result.error });
|
||||
return;
|
||||
}
|
||||
send('result', { success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
send('result', { success: false, error: error.message });
|
||||
return;
|
||||
}
|
||||
console.error('Error stopping compose stack:', error);
|
||||
send('result', { success: false, error: 'Failed to stop compose stack' });
|
||||
}
|
||||
return json({ success: true, output: result.output });
|
||||
} catch (error) {
|
||||
if (error instanceof ComposeFileNotFoundError) {
|
||||
return json({ error: error.message }, { status: 404 });
|
||||
}
|
||||
console.error('Error stopping compose stack:', error);
|
||||
return json({ error: 'Failed to stop compose stack' }, { status: 500 });
|
||||
}
|
||||
}, event.request);
|
||||
};
|
||||
@@ -23,6 +23,7 @@ export const POST: RequestHandler = async ({ request, cookies }) => {
|
||||
|
||||
// Detect which stacks are already running on any environment
|
||||
const discoveredWithRunning = await detectRunningStacks(result.discovered);
|
||||
discoveredWithRunning.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return json({
|
||||
...result,
|
||||
|
||||
Reference in new issue
Block a user