optional stacks_dir flat layout for local stacks
This commit is contained in:
5 files changed
+92
-403
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { join } from 'path';
|
||||
import { getStackDir, isHawserConnection } from '$lib/server/stacks';
|
||||
import { getStackDir } from '$lib/server/stacks';
|
||||
import { getEnvironment } from '$lib/server/db';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
@@ -17,15 +17,6 @@ import type { RequestHandler } from './$types';
|
||||
* resp-200: {stackDir:string!, composePath:string!, envPath:string!, source:string!}
|
||||
* resp-200-example: {"stackDir":"/data/stacks/prod/web","composePath":"/data/stacks/prod/web/compose.yaml","envPath":"/data/stacks/prod/web/.env","source":"default"}
|
||||
* resp-400: Stack name is required
|
||||
*
|
||||
* Query params:
|
||||
* - name: Stack name (required)
|
||||
* - env: Environment ID (optional)
|
||||
* - location: Custom base location path (optional)
|
||||
*
|
||||
* If location is provided, path will be: {location}/{envName}/{stackName}/
|
||||
* Otherwise uses Dockhand's default via getStackDir (flat STACKS_DIR/<stackName>/
|
||||
* for local envs when STACKS_DIR is set, else $DATA_DIR/stacks/<envName>/<stackName>/).
|
||||
*/
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
const stackName = url.searchParams.get('name');
|
||||
@@ -38,7 +29,6 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
}
|
||||
|
||||
let stackDir: string;
|
||||
let source = 'default';
|
||||
|
||||
if (location) {
|
||||
// Custom location: {location}/{envName}/{stackName}/
|
||||
@@ -54,23 +44,13 @@ export const GET: RequestHandler = async ({ url }) => {
|
||||
}
|
||||
} else {
|
||||
// Dockhand default location
|
||||
if (envIdNum) {
|
||||
const env = await getEnvironment(envIdNum);
|
||||
if (env && isHawserConnection(env) && env.hawserStacksDir) {
|
||||
stackDir = join(env.hawserStacksDir, stackName);
|
||||
source = 'hawser';
|
||||
} else {
|
||||
stackDir = await getStackDir(stackName, envIdNum);
|
||||
}
|
||||
} else {
|
||||
stackDir = await getStackDir(stackName, envIdNum);
|
||||
}
|
||||
stackDir = await getStackDir(stackName, envIdNum);
|
||||
}
|
||||
|
||||
return json({
|
||||
stackDir,
|
||||
composePath: `${stackDir}/compose.yaml`,
|
||||
envPath: `${stackDir}/.env`,
|
||||
source
|
||||
source: 'default'
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,8 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { getStackSources, getEnvironment } from '$lib/server/db';
|
||||
import { countStackEnvVars, resolveStackSourceDisplayPathsForEnv, buildStackPathHintsMap } from '$lib/server/stacks';
|
||||
import { getStackSources } from '$lib/server/db';
|
||||
import { countStackEnvVars } from '$lib/server/stacks';
|
||||
import { authorize } from '$lib/server/authorize';
|
||||
import { listContainers } from '$lib/server/docker';
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
@@ -26,19 +25,7 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
try {
|
||||
const sources = await getStackSources(envIdNum);
|
||||
|
||||
const envIds = [...new Set(sources.map((s) => s.environmentId).filter((id): id is number => id != null))];
|
||||
const envs = await Promise.all(envIds.map((id) => getEnvironment(id)));
|
||||
const envMap = new Map(envs.filter((e) => e !== undefined).map((e) => [e.id, e]));
|
||||
|
||||
const hintEnvIds = [...new Set(sources.map((s) => s.environmentId ?? null))];
|
||||
const hintMaps = await Promise.all(
|
||||
hintEnvIds.map(async (id) => {
|
||||
const containers = await listContainers(true, id).catch(() => []);
|
||||
return { id, map: buildStackPathHintsMap(containers) };
|
||||
})
|
||||
);
|
||||
const hintsByEnv = new Map(hintMaps.map((h) => [String(h.id ?? 'null'), h.map]));
|
||||
|
||||
// Convert to a map for easier lookup in the frontend
|
||||
const sourceMap: Record<
|
||||
string,
|
||||
{
|
||||
@@ -50,6 +37,11 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
envVarCount?: number;
|
||||
}
|
||||
> = {};
|
||||
// Count env vars server-side (one local read per stack) so the list badge does
|
||||
// not need a /env fetch per stack. GET /env resolves its env param as
|
||||
// `envId ? parseInt : null`, so pass the SAME null-when-absent value (not
|
||||
// undefined - getStackEnvVars scopes differently for null vs undefined) so the
|
||||
// count matches that endpoint's variable list exactly.
|
||||
const countEnvId = envIdNum ?? null;
|
||||
const counts = await Promise.all(
|
||||
sources.map((s) =>
|
||||
@@ -58,21 +50,16 @@ export const GET: RequestHandler = async ({ url, cookies }) => {
|
||||
: Promise.resolve(0)
|
||||
)
|
||||
);
|
||||
for (const [i, source] of sources.entries()) {
|
||||
const resolved = await resolveStackSourceDisplayPathsForEnv(
|
||||
source,
|
||||
source.environmentId != null ? envMap.get(source.environmentId) ?? null : null,
|
||||
hintsByEnv.get(String(source.environmentId ?? 'null'))?.get(source.stackName) ?? null
|
||||
);
|
||||
sources.forEach((source, i) => {
|
||||
sourceMap[source.stackName] = {
|
||||
sourceType: source.sourceType,
|
||||
composePath: resolved.composePath,
|
||||
composePath: source.composePath,
|
||||
repository: source.repository,
|
||||
secretProviderId: source.secretProviderId,
|
||||
icon: source.icon ?? null,
|
||||
envVarCount: counts[i],
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return json(sourceMap);
|
||||
} catch (error) {
|
||||
|
||||
@@ -706,20 +706,18 @@
|
||||
<p>
|
||||
The environment
|
||||
<code class="text-xs bg-muted px-1 py-0.5 rounded">{deleteEnvTarget.name}</code>
|
||||
and the following directories will be permanently removed from the Dockhand host
|
||||
(managed local stacks under <code class="text-xs">STACKS_DIR</code>, when configured, are removed per stack name):
|
||||
and the following directories will be permanently removed from the Dockhand host:
|
||||
</p>
|
||||
<div class="space-y-1 text-xs font-mono bg-muted/40 rounded-md p-3 border overflow-x-auto">
|
||||
<div class="flex items-center gap-2 whitespace-nowrap">
|
||||
<Trash2 class="w-3.5 h-3.5 shrink-0 text-destructive" />
|
||||
<code class="whitespace-nowrap">$DATA_DIR/stacks/{deleteEnvTarget.name}/</code>
|
||||
<span class="text-muted-foreground shrink-0">(staging)</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 whitespace-nowrap">
|
||||
<Trash2 class="w-3.5 h-3.5 shrink-0 text-destructive" />
|
||||
<code class="whitespace-nowrap">$DATA_DIR/git-repos/{deleteEnvTarget.name}/</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
Shared git repository clones under <code class="text-xs">$DATA_DIR/git-repos/<repoName>/</code>
|
||||
are not removed when an environment is deleted.
|
||||
</p>
|
||||
{#if deleteCountsLoading}
|
||||
<p class="flex items-center gap-2">
|
||||
<RefreshCw class="w-3.5 h-3.5 animate-spin" />
|
||||
|
||||
Reference in new issue
Block a user