1.0.30
This commit is contained in:
49 files changed
+1414
-492
No files matched your search
@@ -10,7 +10,9 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
|
||||
const auth = await authorize(cookies);
|
||||
|
||||
const tail = parseInt(url.searchParams.get('tail') || '100');
|
||||
const tail = url.searchParams.get('tail') || '100';
|
||||
const since = url.searchParams.get('since') || undefined;
|
||||
const until = url.searchParams.get('until') || undefined;
|
||||
const envId = url.searchParams.get('env');
|
||||
const envIdNum = envId ? parseInt(envId) : undefined;
|
||||
|
||||
@@ -20,7 +22,7 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const logs = await getContainerLogs(params.id, tail, envIdNum);
|
||||
const logs = await getContainerLogs(params.id, tail === 'all' ? 'all' : parseInt(tail), envIdNum, since, until);
|
||||
return json({ logs });
|
||||
} catch (error: any) {
|
||||
console.error('Error getting container logs:', error?.message || error, error?.stack);
|
||||
|
||||
@@ -90,7 +90,7 @@ function parseDockerFrame(buffer: Buffer, offset: number): { type: number; size:
|
||||
/**
|
||||
* Handle logs streaming for Hawser Edge connections
|
||||
*/
|
||||
async function handleEdgeLogsStream(containerId: string, tail: string, environmentId: number): Promise<Response> {
|
||||
async function handleEdgeLogsStream(containerId: string, tail: string, environmentId: number, since?: string, until?: string): Promise<Response> {
|
||||
// Check if edge agent is connected
|
||||
if (!isEdgeConnected(environmentId)) {
|
||||
return new Response(JSON.stringify({ error: 'Edge agent not connected' }), {
|
||||
@@ -115,7 +115,7 @@ async function handleEdgeLogsStream(containerId: string, tail: string, environme
|
||||
// Ignore - default to demux mode
|
||||
}
|
||||
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true&tail=${tail}×tamps=true`;
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true×tamps=true&tail=${tail}${since ? `&since=${since}` : ''}${until ? `&until=${until}` : ''}`;
|
||||
|
||||
let controllerClosed = false;
|
||||
let cancelStream: (() => void) | null = null;
|
||||
@@ -262,6 +262,8 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
|
||||
const containerId = params.id;
|
||||
const tail = url.searchParams.get('tail') || '100';
|
||||
const since = url.searchParams.get('since') || '';
|
||||
const until = url.searchParams.get('until') || '';
|
||||
const envId = url.searchParams.get('env');
|
||||
const envIdNum = envId ? parseInt(envId) : undefined;
|
||||
|
||||
@@ -277,7 +279,7 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
|
||||
// Handle Hawser Edge mode separately
|
||||
if (config.type === 'hawser-edge') {
|
||||
return handleEdgeLogsStream(containerId, tail, config.environmentId!);
|
||||
return handleEdgeLogsStream(containerId, tail, config.environmentId!, since, until);
|
||||
}
|
||||
|
||||
// First, check if container has TTY enabled and get container name
|
||||
@@ -311,7 +313,7 @@ export const GET: RequestHandler = async ({ params, url, cookies }) => {
|
||||
}
|
||||
|
||||
// Build the logs URL with follow=true for streaming
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true&tail=${tail}×tamps=true`;
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true×tamps=true&tail=${tail}${since ? `&since=${since}` : ''}${until ? `&until=${until}` : ''}`;
|
||||
|
||||
let controllerClosed = false;
|
||||
let abortController: AbortController | null = new AbortController();
|
||||
|
||||
@@ -13,6 +13,7 @@ import type { RequestHandler } from './$types';
|
||||
import v8 from 'node:v8';
|
||||
import os from 'node:os';
|
||||
import { getRssStats, dumpHeapSnapshot, listHeapSnapshots } from '$lib/server/rss-tracker';
|
||||
import { formatBytes } from '$lib/utils/format';
|
||||
|
||||
// Track startup time and initial RSS for growth rate calculation
|
||||
const startupTime = Date.now();
|
||||
@@ -99,16 +100,6 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
});
|
||||
};
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 B';
|
||||
const sign = bytes < 0 ? '-' : '';
|
||||
const abs = Math.abs(bytes);
|
||||
if (abs < 1024) return `${sign}${abs} B`;
|
||||
if (abs < 1024 * 1024) return `${sign}${(abs / 1024).toFixed(1)} KB`;
|
||||
if (abs < 1024 * 1024 * 1024) return `${sign}${(abs / (1024 * 1024)).toFixed(1)} MB`;
|
||||
return `${sign}${(abs / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
||||
}
|
||||
|
||||
function formatUptime(ms: number): string {
|
||||
const seconds = Math.floor(ms / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
|
||||
@@ -138,6 +138,9 @@ export const DELETE: RequestHandler = async (event) => {
|
||||
|
||||
try {
|
||||
const id = parseInt(params.id);
|
||||
if (isNaN(id) || id <= 0) {
|
||||
return json({ error: 'Invalid environment ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Get environment name before deletion for audit log
|
||||
const env = await getEnvironment(id);
|
||||
@@ -145,6 +148,11 @@ export const DELETE: RequestHandler = async (event) => {
|
||||
return json({ error: 'Environment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Safety: never delete directories if env name is empty/whitespace
|
||||
if (!env.name?.trim()) {
|
||||
return json({ error: 'Cannot delete environment with empty name' }, { status: 500 });
|
||||
}
|
||||
|
||||
// Close Edge connection if this is a Hawser Edge environment
|
||||
// This rejects any pending requests and closes the WebSocket
|
||||
closeEdgeConnection(id);
|
||||
@@ -186,10 +194,11 @@ export const DELETE: RequestHandler = async (event) => {
|
||||
unregisterSchedule(id, 'image_prune');
|
||||
|
||||
// Clean up stack directory for this environment
|
||||
// Safety: only delete subdirectory named after the env, never the parent
|
||||
try {
|
||||
const stacksDir = getStacksDir();
|
||||
const envStackDir = join(stacksDir, env.name);
|
||||
if (existsSync(envStackDir)) {
|
||||
if (envStackDir !== stacksDir && envStackDir.startsWith(stacksDir) && existsSync(envStackDir)) {
|
||||
rmSync(envStackDir, { recursive: true, force: true });
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -200,7 +209,7 @@ export const DELETE: RequestHandler = async (event) => {
|
||||
try {
|
||||
const gitReposDir = getGitReposDir();
|
||||
const envGitDir = join(gitReposDir, env.name);
|
||||
if (existsSync(envGitDir)) {
|
||||
if (envGitDir !== gitReposDir && envGitDir.startsWith(gitReposDir) && existsSync(envGitDir)) {
|
||||
rmSync(envGitDir, { recursive: true, force: true });
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { json, type RequestHandler } from '@sveltejs/kit';
|
||||
import { scanImage, type ScanProgress, type ScanResult } from '$lib/server/scanner';
|
||||
import { saveVulnerabilityScan, getLatestScanForImage } from '$lib/server/db';
|
||||
import { inspectImage } from '$lib/server/docker';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { createJobResponse } from '$lib/server/sse';
|
||||
|
||||
@@ -97,8 +98,16 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Note: getLatestScanForImage signature is (imageId, scanner, environmentId)
|
||||
const result = await getLatestScanForImage(imageName, scanner, envId);
|
||||
// Resolve tag to SHA256 ID for reliable cache lookup
|
||||
let imageId = imageName;
|
||||
try {
|
||||
const info = await inspectImage(imageName, envId) as any;
|
||||
if (info.Id) imageId = info.Id;
|
||||
} catch {
|
||||
// Fall back to name if inspect fails
|
||||
}
|
||||
|
||||
const result = await getLatestScanForImage(imageId, scanner, envId);
|
||||
if (!result) {
|
||||
return json({ found: false });
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ interface EdgeContainerLogSource {
|
||||
/**
|
||||
* Handle merged logs streaming for Hawser Edge connections
|
||||
*/
|
||||
async function handleEdgeMergedLogs(containerIds: string[], tail: string, environmentId: number): Promise<Response> {
|
||||
async function handleEdgeMergedLogs(containerIds: string[], tail: string, environmentId: number, since?: string, until?: string): Promise<Response> {
|
||||
// Check if edge agent is connected
|
||||
if (!isEdgeConnected(environmentId)) {
|
||||
return new Response(JSON.stringify({ error: 'Edge agent not connected' }), {
|
||||
@@ -197,7 +197,7 @@ async function handleEdgeMergedLogs(containerIds: string[], tail: string, enviro
|
||||
};
|
||||
|
||||
// Start log stream for this container via Edge
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true&tail=${tail}×tamps=true`;
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true×tamps=true&tail=${tail}${since ? `&since=${since}` : ''}${until ? `&until=${until}` : ''}`;
|
||||
|
||||
const { cancel } = sendEdgeStreamRequest(
|
||||
environmentId,
|
||||
@@ -362,6 +362,8 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
// Parse container IDs from comma-separated list
|
||||
const containerIds = url.searchParams.get('containers')?.split(',').filter(Boolean) || [];
|
||||
const tail = url.searchParams.get('tail') || '100';
|
||||
const since = url.searchParams.get('since') || '';
|
||||
const until = url.searchParams.get('until') || '';
|
||||
const envId = url.searchParams.get('env');
|
||||
const envIdNum = envId ? parseInt(envId) : undefined;
|
||||
|
||||
@@ -386,7 +388,7 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
|
||||
// Handle Hawser Edge mode separately
|
||||
if (config.type === 'hawser-edge') {
|
||||
return handleEdgeMergedLogs(containerIds, tail, config.environmentId!);
|
||||
return handleEdgeMergedLogs(containerIds, tail, config.environmentId!, since, until);
|
||||
}
|
||||
|
||||
let controllerClosed = false;
|
||||
@@ -449,7 +451,7 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
const hasTty = info.Config?.Tty ?? false;
|
||||
|
||||
// Start log stream for this container
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true&tail=${tail}×tamps=true`;
|
||||
const logsPath = `/containers/${containerId}/logs?stdout=true&stderr=true&follow=true×tamps=true&tail=${tail}${since ? `&since=${since}` : ''}${until ? `&until=${until}` : ''}`;
|
||||
let logsResponse: Response;
|
||||
|
||||
if (config.type === 'socket') {
|
||||
|
||||
+1
-6
@@ -18,12 +18,7 @@ function parseEnvFile(content: string): Record<string, string> {
|
||||
const eqIndex = trimmed.indexOf('=');
|
||||
if (eqIndex > 0) {
|
||||
const key = trimmed.substring(0, eqIndex).trim();
|
||||
let value = trimmed.substring(eqIndex + 1);
|
||||
// Remove surrounding quotes if present
|
||||
if ((value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
const value = trimmed.substring(eqIndex + 1);
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user