This commit is contained in:
jarek committed 2026-03-02 07:59:58 +01:00
1 parent 4b430340db
commit 9c451aedf9
120 files changed
+7777 -6046

No files matched your search

+34 -40
View File
@@ -7,7 +7,6 @@
import {
db,
rawClient,
isPostgres,
isSqlite,
eq,
@@ -200,6 +199,10 @@ export async function deleteEnvironment(id: number): Promise<boolean> {
const env = await getEnvironment(id);
if (!env) return false;
// Clean up in-memory metrics
const { clearEnvironmentMetrics } = await import('./metrics-store.js');
clearEnvironmentMetrics(id);
// Clean up related records that don't have cascade delete defined
try {
await db.delete(hostMetrics).where(eq(hostMetrics.environmentId, id));
@@ -576,45 +579,28 @@ export async function saveHostMetric(
memoryPercent: number,
memoryUsed: number,
memoryTotal: number,
environmentId?: number
environmentId?: number,
_skipEnvCheck = false
): Promise<void> {
// Verify environment exists before inserting (avoids FK violations on deleted envs)
if (environmentId) {
const env = await getEnvironment(environmentId);
if (!env) return;
}
await db.insert(hostMetrics).values({
environmentId: environmentId || null,
cpuPercent,
memoryPercent,
memoryUsed,
memoryTotal
});
// Cleanup old metrics (keep last 24 hours)
const cutoff24h = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
await db.delete(hostMetrics).where(sql`timestamp < ${cutoff24h}`);
// Delegated to in-memory ring buffer (no DB writes)
if (!environmentId) return;
const { pushMetric } = await import('./metrics-store.js');
pushMetric(environmentId, cpuPercent, memoryPercent, memoryUsed, memoryTotal);
}
export async function getHostMetrics(limit = 60, environmentId?: number): Promise<HostMetric[]> {
if (environmentId) {
return db.select().from(hostMetrics)
.where(eq(hostMetrics.environmentId, environmentId))
.orderBy(desc(hostMetrics.timestamp))
.limit(limit);
const { getMetricsHistory } = await import('./metrics-store.js');
// getMetricsHistory returns oldest-first, but callers expect newest-first
return getMetricsHistory(environmentId, limit).reverse();
}
return db.select().from(hostMetrics)
.orderBy(desc(hostMetrics.timestamp))
.limit(limit);
const { getAllMetrics } = await import('./metrics-store.js');
return getAllMetrics(limit);
}
export async function getLatestHostMetrics(environmentId: number): Promise<HostMetric | null> {
const results = await db.select().from(hostMetrics)
.where(eq(hostMetrics.environmentId, environmentId))
.orderBy(desc(hostMetrics.timestamp))
.limit(1);
return results[0] ?? null;
const { getLatestMetric } = await import('./metrics-store.js');
return getLatestMetric(environmentId);
}
// =============================================================================
@@ -3269,20 +3255,23 @@ export interface ContainerEventResult {
offset: number;
}
export async function logContainerEvent(data: ContainerEventCreateData): Promise<ContainerEventData> {
// Timestamp is already an ISO-8601 string from event-subprocess
// Both SQLite and PostgreSQL schemas use mode: 'string' so we pass it directly
const result = await db.insert(containerEvents).values({
export async function logContainerEvent(
data: ContainerEventCreateData
): Promise<ContainerEventData> {
const attrs = data.actorAttributes ? JSON.stringify(data.actorAttributes) : null;
const [inserted] = await db.insert(containerEvents).values({
environmentId: data.environmentId ?? null,
containerId: data.containerId,
containerName: data.containerName ?? null,
image: data.image ?? null,
action: data.action,
actorAttributes: data.actorAttributes ? JSON.stringify(data.actorAttributes) : null,
actorAttributes: attrs,
timestamp: data.timestamp
}).returning();
}).returning({ id: containerEvents.id });
return getContainerEvent(result[0].id) as Promise<ContainerEventData>;
const event = await getContainerEvent(inserted.id);
return event!;
}
export async function getContainerEvent(id: number): Promise<ContainerEventData | undefined> {
@@ -4502,11 +4491,16 @@ export async function setStackEnvVars(
));
}
// Insert new vars
// Insert new vars (deduplicate by key - last entry wins)
if (variables.length > 0) {
const seen = new Map<string, { key: string; value: string; isSecret?: boolean }>();
for (const v of variables) {
seen.set(v.key, v);
}
const deduped = Array.from(seen.values());
const now = new Date().toISOString();
await db.insert(stackEnvironmentVariables).values(
variables.map(v => ({
deduped.map(v => ({
stackName,
environmentId,
key: v.key,