From bd7b832394758f1aa676936e1aee7fd33d7dad34 Mon Sep 17 00:00:00 2001 From: jarek Date: Sat, 3 Jan 2026 14:56:20 +0100 Subject: [PATCH] 1.0.6 --- Dockerfile | 14 +- src/lib/components/StackEnvVarsPanel.svelte | 134 +++++++++++-------- src/lib/components/data-grid/DataGrid.svelte | 10 +- src/lib/data/changelog.json | 10 ++ src/routes/activity/+page.svelte | 12 +- src/routes/images/+page.svelte | 21 ++- src/routes/stacks/+page.svelte | 12 +- src/routes/stacks/GitStackModal.svelte | 117 ++++++++++++++-- src/routes/stacks/StackModal.svelte | 69 +++------- 9 files changed, 265 insertions(+), 134 deletions(-) diff --git a/Dockerfile b/Dockerfile index edd2e4a..902b76f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,6 +6,9 @@ # - Full transparency (no dependency on pre-built Chainguard images) # - Reproducible builds from open-source Wolfi packages # - Minimal attack surface with only required packages +# +# Bun is copied from the official oven/bun image (app-builder stage) to ensure +# compatibility with all x86_64 CPUs (including those without AVX2 like Celeron). # ============================================================================= # ----------------------------------------------------------------------------- @@ -15,13 +18,14 @@ # to build our custom Wolfi OS from scratch using open-source packages. FROM alpine:3.21 AS os-builder +ARG TARGETARCH + WORKDIR /work # Install apko tool (latest stable release) # apko is the tool Chainguard uses to build their images - we use it directly ARG APKO_VERSION=0.30.34 -ARG TARGETARCH -RUN apk add --no-cache curl \ +RUN apk add --no-cache curl unzip \ && ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") \ && curl -sL "https://github.com/chainguard-dev/apko/releases/download/v${APKO_VERSION}/apko_${APKO_VERSION}_linux_${ARCH}.tar.gz" \ | tar -xz --strip-components=1 -C /usr/local/bin \ @@ -29,6 +33,7 @@ RUN apk add --no-cache curl \ # Generate apko.yaml for current target architecture only # We build single-arch to avoid multi-arch layer confusion in extraction +# Note: Bun is NOT included here - it's copied from app-builder stage for CPU compatibility RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64") \ && printf '%s\n' \ "contents:" \ @@ -41,7 +46,6 @@ RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64") " - ca-certificates" \ " - busybox" \ " - tzdata" \ - " - bun" \ " - docker-cli" \ " - docker-compose" \ " - sqlite" \ @@ -99,6 +103,10 @@ FROM scratch # Install our custom-built Wolfi OS (now we have /bin/sh!) COPY --from=os-builder /work/rootfs/ / +# Copy Bun from official image - ensures compatibility with all x86_64 CPUs (no AVX2 requirement) +# Wolfi's bun package requires AVX2 which breaks on Celeron/Atom CPUs +COPY --from=app-builder /usr/local/bin/bun /usr/bin/bun + WORKDIR /app # Set up environment variables diff --git a/src/lib/components/StackEnvVarsPanel.svelte b/src/lib/components/StackEnvVarsPanel.svelte index 715d063..30391b5 100644 --- a/src/lib/components/StackEnvVarsPanel.svelte +++ b/src/lib/components/StackEnvVarsPanel.svelte @@ -287,46 +287,68 @@
-
-
- Environment variables - {#if infoText} - - - - - - -

{infoText}

-
-
-
- {/if} - -
- - -
+ +
+
+ Environment variables + {#if infoText} + + + + + + +

{infoText}

+
+
+
+ {/if} + +
+ +
+ + {#if validation} +
+ {#if validation.missing.length > 0} + + {validation.missing.length} missing + + {/if} + {#if validation.required.length > 0} + + {validation.required.length - validation.missing.length} defined + + {/if} + {#if validation.optional.length > 0} + + {validation.optional.length} optional + + {/if} +
+ {/if} +
+ {#if !readonly} -
+
{#if viewMode === 'form'} {/if} -
- confirmClearOpen = o} - > - {#snippet children({ open })} - - {/snippet} - -
+ confirmClearOpen = o} + > + {#snippet children({ open })} + + {/snippet} +
(); let rowStateCacheDataRef: T[] | null = null; + let rowStateCacheExpandedRef: Set | null = null; + let rowStateCacheSelectedRef: Set | null = null; - // Clear row state cache when data reference changes + // Clear row state cache when data or selection/expansion state changes $effect(() => { - if (data !== rowStateCacheDataRef) { + if (data !== rowStateCacheDataRef || + expandedKeys !== rowStateCacheExpandedRef || + selectedKeys !== rowStateCacheSelectedRef) { rowStateCache = new WeakMap(); rowStateCacheDataRef = data; + rowStateCacheExpandedRef = expandedKeys; + rowStateCacheSelectedRef = selectedKeys; } }); diff --git a/src/lib/data/changelog.json b/src/lib/data/changelog.json index 7954abb..94fbdf7 100644 --- a/src/lib/data/changelog.json +++ b/src/lib/data/changelog.json @@ -1,4 +1,14 @@ [ + { + "version": "1.0.6", + "date": "2026-01-03", + "changes": [ + { "type": "fix", "text": "Legacy CPU support (Celeron, Atom) - Bun binary now copied from official image instead of Wolfi package" }, + { "type": "fix", "text": "Stack modal layouts improved with resizable split panels" }, + { "type": "fix", "text": "Missing column headers in images overview" } + ], + "imageTag": "fnsys/dockhand:v1.0.6" + }, { "version": "1.0.5", "date": "2026-01-01", diff --git a/src/routes/activity/+page.svelte b/src/routes/activity/+page.svelte index 008d4e2..c859190 100644 --- a/src/routes/activity/+page.svelte +++ b/src/routes/activity/+page.svelte @@ -307,7 +307,9 @@ total = data.total; if (append) { - events = [...events, ...data.events]; + // Use push() for O(k) append instead of spread for O(n) copy + events.push(...data.events); + events = events; // Trigger Svelte reactivity hasMore = events.length < total; // Update eventIds Set with new events for (const evt of data.events) { @@ -517,12 +519,16 @@ // Add to beginning of events (prepend new events) - use Set for fast duplicate check if (!eventIds.has(newEvent.id)) { eventIds.add(newEvent.id); - events = [newEvent, ...events]; + // Use unshift() for in-place mutation instead of spread for O(n) copy + events.unshift(newEvent); + events = events; // Trigger Svelte reactivity total = total + 1; // Add container to list if not already there if (newEvent.containerName && !containers.includes(newEvent.containerName)) { - containers = [...containers, newEvent.containerName].sort(); + containers.push(newEvent.containerName); + containers.sort(); + containers = containers; // Trigger Svelte reactivity } } } catch { diff --git a/src/routes/images/+page.svelte b/src/routes/images/+page.svelte index fd428c6..279a33c 100644 --- a/src/routes/images/+page.svelte +++ b/src/routes/images/+page.svelte @@ -7,7 +7,7 @@ import { Label } from '$lib/components/ui/label'; import * as Dialog from '$lib/components/ui/dialog'; import * as Select from '$lib/components/ui/select'; - import { Trash2, Upload, RefreshCw, Play, Search, Layers, Server, ShieldCheck, CheckSquare, Square, Tag, Check, XCircle, Icon, AlertTriangle, X, Images, Copy, Download, ChevronRight, ChevronDown, Loader2 } from 'lucide-svelte'; + import { Trash2, Upload, RefreshCw, Play, Search, Layers, Server, ShieldCheck, CheckSquare, Square, Tag, Check, XCircle, Icon, AlertTriangle, X, Images, Copy, Download, ChevronRight, ChevronDown, Loader2, ArrowUp, ArrowDown, ArrowUpDown } from 'lucide-svelte'; import { broom, whale } from '@lucide/lab'; import ConfirmPopover from '$lib/components/ConfirmPopover.svelte'; import BatchOperationModal from '$lib/components/BatchOperationModal.svelte'; @@ -720,6 +720,25 @@ {/if} + {:else if column.sortable} + + {:else if column.id !== 'expand' && column.id !== 'actions'} + {column.label} {/if} {/snippet} {#snippet cell(column, group, rowState)} diff --git a/src/routes/stacks/+page.svelte b/src/routes/stacks/+page.svelte index fe95bbe..5f32637 100644 --- a/src/routes/stacks/+page.svelte +++ b/src/routes/stacks/+page.svelte @@ -1270,10 +1270,7 @@ sortDirection = state.direction; }} onRowClick={(stack, e) => { - const hasContainers = stack.containers && stack.containers.length > 0; - if (hasContainers) { - toggleExpand(stack.name); - } + toggleExpand(stack.name); }} rowClass={(stack) => { const isExp = expandedStacks.has(stack.name); @@ -1904,6 +1901,13 @@ {/each}
+ {:else} +
+
+ + No containers +
+
{/if} {/snippet} diff --git a/src/routes/stacks/GitStackModal.svelte b/src/routes/stacks/GitStackModal.svelte index af6fa47..3d53581 100644 --- a/src/routes/stacks/GitStackModal.svelte +++ b/src/routes/stacks/GitStackModal.svelte @@ -1,17 +1,25 @@ { if (isOpen) focusFirstInput(); }}> - - - - - {gitStack ? 'Edit git stack' : 'Deploy from Git'} - - - {gitStack ? 'Update git stack settings' : 'Deploy a compose stack from a Git repository'} - + + +
+
+
+ +
+
+ + {gitStack ? 'Edit git stack' : 'Deploy from Git'} + + + {gitStack ? 'Update git stack settings' : 'Deploy a compose stack from a Git repository'} + +
+
+ + + +
-
+
-
+
+
{#if !gitStack}
@@ -774,10 +849,24 @@ {#if formError}

{formError}

{/if} +
+
+ + + -
+
- + {#if gitStack}
-
- - Environment variables - - - - - -
-

These variables will be written to a .env file in the stack directory.

-
-
-
- - {#if envValidation} -
- {#if envValidation.missing.length > 0} - - {envValidation.missing.length} missing - - {/if} - {#if envValidation.required.length > 0} - - {envValidation.required.length - envValidation.missing.length} required - - {/if} - {#if envValidation.optional.length > 0} - - {envValidation.optional.length} optional - - {/if} - {#if envValidation.unused.length > 0} - - {envValidation.unused.length} unused - - {/if} -
- {/if} -
-
- { markDirty(); debouncedValidate(); }} - theme={editorTheme} - /> -
+ { markDirty(); debouncedValidate(); }} + theme={editorTheme} + infoText="These variables will be written to a .env file in the stack directory." + />
{:else if activeTab === 'graph'}