forked from wrenn/wrenn
Fix runtime env leaking into templates, add hostname to /etc/hosts
- Filter out user-specific env vars (HOME, USER, LOGNAME, SHELL, etc.) from template default_env so they don't override envd's per-user resolution. Fixes bash sourcing /root/.bashrc as wrenn-user. - Keep WRENN_SANDBOX (legitimate runtime flag), only filter per-sandbox IDs (WRENN_SANDBOX_ID, WRENN_TEMPLATE_ID). - Add "127.0.0.1 sandbox" to /etc/hosts in wrenn-init.sh so sudo can resolve the hostname. Fixes "unable to resolve host sandbox" error. - Move capsule lifecycle buttons (Pause/Resume/Snapshot/Destroy) to the same row as Stats/Files/Terminal tabs. - Show vCPU/Memory for all template types with Required/Recommended tooltips on the user templates page.
This commit is contained in:
@ -478,60 +478,8 @@
|
||||
{:else if capsule}
|
||||
<div class="flex flex-1 flex-col min-h-0">
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="flex items-center justify-end gap-2 px-7 pt-5">
|
||||
{#if capsule.status === 'running'}
|
||||
<button
|
||||
onclick={handlePause}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] border border-[var(--color-amber)]/30 bg-[var(--color-amber)]/8 px-3.5 py-2 text-ui font-medium text-[var(--color-amber)] transition-all duration-150 hover:bg-[var(--color-amber)]/15 hover:border-[var(--color-amber)]/50 disabled:opacity-50"
|
||||
>
|
||||
{#if actionLoading === 'pause'}
|
||||
<svg class="animate-spin" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Pausing...
|
||||
{:else}
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16" /><rect x="14" y="4" width="4" height="16" /></svg>
|
||||
Pause
|
||||
{/if}
|
||||
</button>
|
||||
{:else if capsule.status === 'paused'}
|
||||
<button
|
||||
onclick={handleResume}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] border border-[var(--color-accent)]/30 bg-[var(--color-accent)]/8 px-3.5 py-2 text-ui font-medium text-[var(--color-accent-bright)] transition-all duration-150 hover:bg-[var(--color-accent)]/15 hover:border-[var(--color-accent)]/50 disabled:opacity-50"
|
||||
>
|
||||
{#if actionLoading === 'resume'}
|
||||
<svg class="animate-spin" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Resuming...
|
||||
{:else}
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3" /></svg>
|
||||
Resume
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
onclick={() => { showSnapshot = true; }}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] border border-[var(--color-border)] bg-[var(--color-bg-3)] px-3.5 py-2 text-ui font-medium text-[var(--color-text-secondary)] transition-all duration-150 hover:bg-[var(--color-bg-4)] hover:text-[var(--color-text-primary)] disabled:opacity-50"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 4h-5L7 7H2v13a2 2 0 002 2h16a2 2 0 002-2V7h-5l-2.5-3z" /><circle cx="12" cy="15" r="3" /></svg>
|
||||
Snapshot
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if capsule.status === 'running' || capsule.status === 'paused'}
|
||||
<button
|
||||
onclick={() => { showDestroy = true; }}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/8 px-3.5 py-2 text-ui font-medium text-[var(--color-red)] transition-all duration-150 hover:bg-[var(--color-red)]/15 hover:border-[var(--color-red)]/50 disabled:opacity-50"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" /></svg>
|
||||
Destroy
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Tabs (matches Templates page pattern) -->
|
||||
<div class="mt-5 flex gap-0 border-b border-[var(--color-border)] px-7">
|
||||
<!-- Tabs + lifecycle actions -->
|
||||
<div class="mt-5 flex items-center border-b border-[var(--color-border)] px-7">
|
||||
<button
|
||||
onclick={() => setTab('metrics')}
|
||||
class="flex items-center gap-2 border-b-2 px-4 py-2.5 text-ui font-medium transition-colors duration-150
|
||||
@ -570,6 +518,58 @@
|
||||
</svg>
|
||||
Terminal
|
||||
</button>
|
||||
|
||||
<!-- Lifecycle actions (right-aligned) -->
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
{#if capsule.status === 'running'}
|
||||
<button
|
||||
onclick={handlePause}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-amber)]/30 bg-[var(--color-amber)]/8 px-3 py-1.5 text-meta font-medium text-[var(--color-amber)] transition-all duration-150 hover:bg-[var(--color-amber)]/15 hover:border-[var(--color-amber)]/50 disabled:opacity-50"
|
||||
>
|
||||
{#if actionLoading === 'pause'}
|
||||
<svg class="animate-spin" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Pausing...
|
||||
{:else}
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16" /><rect x="14" y="4" width="4" height="16" /></svg>
|
||||
Pause
|
||||
{/if}
|
||||
</button>
|
||||
{:else if capsule.status === 'paused'}
|
||||
<button
|
||||
onclick={handleResume}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-accent)]/30 bg-[var(--color-accent)]/8 px-3 py-1.5 text-meta font-medium text-[var(--color-accent-bright)] transition-all duration-150 hover:bg-[var(--color-accent)]/15 hover:border-[var(--color-accent)]/50 disabled:opacity-50"
|
||||
>
|
||||
{#if actionLoading === 'resume'}
|
||||
<svg class="animate-spin" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>
|
||||
Resuming...
|
||||
{:else}
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3" /></svg>
|
||||
Resume
|
||||
{/if}
|
||||
</button>
|
||||
<button
|
||||
onclick={() => { showSnapshot = true; }}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-border)] bg-[var(--color-bg-3)] px-3 py-1.5 text-meta font-medium text-[var(--color-text-secondary)] transition-all duration-150 hover:bg-[var(--color-bg-4)] hover:text-[var(--color-text-primary)] disabled:opacity-50"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M14.5 4h-5L7 7H2v13a2 2 0 002 2h16a2 2 0 002-2V7h-5l-2.5-3z" /><circle cx="12" cy="15" r="3" /></svg>
|
||||
Snapshot
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
{#if capsule.status === 'running' || capsule.status === 'paused'}
|
||||
<button
|
||||
onclick={() => { showDestroy = true; }}
|
||||
disabled={actionLoading !== null}
|
||||
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/8 px-3 py-1.5 text-meta font-medium text-[var(--color-red)] transition-all duration-150 hover:bg-[var(--color-red)]/15 hover:border-[var(--color-red)]/50 disabled:opacity-50"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6" /><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" /></svg>
|
||||
Destroy
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab content -->
|
||||
|
||||
@ -162,10 +162,10 @@
|
||||
<div class="flex flex-1 flex-col overflow-hidden">
|
||||
<main class="flex-1 overflow-y-auto bg-[var(--color-bg-0)]">
|
||||
<!-- Header -->
|
||||
<div class="px-7 pt-8">
|
||||
<div class="px-8 pt-8">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h1 class="font-serif text-page tracking-[-0.02em] text-[var(--color-text-bright)]">
|
||||
<h1 class="font-serif text-page tracking-[-0.03em] text-[var(--color-text-bright)]">
|
||||
Templates
|
||||
</h1>
|
||||
<p class="mt-2 text-ui text-[var(--color-text-secondary)]">
|
||||
@ -231,15 +231,15 @@
|
||||
</div>
|
||||
<div class="skeleton h-4 w-20 rounded-sm"></div>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-[var(--radius-card)] border border-[var(--color-border)]">
|
||||
<div class="grid border-b border-[var(--color-border)] bg-[var(--color-bg-3)]" style="grid-template-columns: 2fr 1fr 0.7fr 0.9fr 0.8fr 1.3fr 140px">
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Name</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Type</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">vCPUs</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Memory</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Size</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Created</div>
|
||||
<div class="px-5 py-3 text-right text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Actions</div>
|
||||
<div class="overflow-hidden rounded-[var(--radius-card)] border border-[var(--color-border)] shadow-sm">
|
||||
<div class="grid border-b border-[var(--color-border)] bg-[var(--color-bg-0)]/40" style="grid-template-columns: 2fr 1fr 0.7fr 0.9fr 0.8fr 1.3fr 140px">
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Name</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Type</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">vCPUs</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Memory</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Size</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Created</div>
|
||||
<div class="px-5 py-3 text-right text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Actions</div>
|
||||
</div>
|
||||
{#each Array(4) as _, i}
|
||||
<div
|
||||
@ -294,30 +294,28 @@
|
||||
|
||||
{#if filteredSnapshots.length === 0}
|
||||
<!-- Empty state -->
|
||||
<div class="flex flex-col items-center justify-center py-[72px]">
|
||||
<div class="relative mb-5">
|
||||
<!-- Radial glow behind icon -->
|
||||
<div class="absolute inset-0 -m-4 rounded-full" style="background: radial-gradient(circle, rgba(94,140,88,0.08) 0%, transparent 70%)"></div>
|
||||
<div class="flex flex-col items-center justify-center py-28 text-center">
|
||||
<div class="relative mb-7">
|
||||
<div class="absolute -inset-3 rounded-2xl bg-[var(--color-accent-glow)] blur-xl"></div>
|
||||
<div
|
||||
class="relative flex h-14 w-14 items-center justify-center rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-3)]"
|
||||
style="animation: iconFloat 4s ease-in-out infinite"
|
||||
class="empty-icon-float relative flex h-18 w-18 items-center justify-center rounded-xl border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] shadow-card"
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="var(--color-text-secondary)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" class="text-[var(--color-accent-mid)]">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96" /><line x1="12" y1="22.08" x2="12" y2="12" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<p class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">
|
||||
<p class="font-serif text-heading leading-snug text-[var(--color-text-secondary)]">
|
||||
{emptyHeading(typeFilter)}
|
||||
</p>
|
||||
<p class="mt-1.5 max-w-[340px] text-center text-ui text-[var(--color-text-tertiary)]">
|
||||
<p class="mt-2 max-w-[320px] text-ui text-[var(--color-text-muted)]">
|
||||
{emptyDescription(typeFilter)}
|
||||
</p>
|
||||
{#if typeFilter === 'all' || typeFilter === 'snapshot'}
|
||||
<a
|
||||
href="/dashboard/capsules"
|
||||
class="mt-6 flex items-center gap-2 rounded-[var(--radius-button)] border border-[var(--color-border-mid)] bg-[var(--color-bg-3)] px-4 py-2 text-ui font-medium text-[var(--color-text-secondary)] transition-all duration-150 hover:border-[var(--color-border-mid)] hover:bg-[var(--color-bg-4)] hover:text-[var(--color-text-primary)] active:scale-95"
|
||||
class="mt-6 flex items-center gap-2 rounded-[var(--radius-button)] border border-[var(--color-accent)]/30 bg-[var(--color-accent)]/10 px-4 py-2 text-ui font-medium text-[var(--color-accent-bright)] transition-all duration-200 hover:bg-[var(--color-accent)]/20 hover:border-[var(--color-accent)]/50"
|
||||
>
|
||||
Go to Capsules
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
@ -329,16 +327,16 @@
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Table -->
|
||||
<div class="overflow-hidden rounded-[var(--radius-card)] border border-[var(--color-border)]">
|
||||
<div class="overflow-hidden rounded-[var(--radius-card)] border border-[var(--color-border)] shadow-sm">
|
||||
<!-- Header -->
|
||||
<div class="grid border-b border-[var(--color-border)] bg-[var(--color-bg-3)]" style="grid-template-columns: 2fr 1fr 0.7fr 0.9fr 0.8fr 1.3fr 140px">
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Name</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Type</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">vCPUs</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Memory</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Size</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Created</div>
|
||||
<div class="px-5 py-3 text-right text-label font-semibold uppercase tracking-[0.05em] text-[var(--color-text-muted)]">Actions</div>
|
||||
<div class="grid border-b border-[var(--color-border)] bg-[var(--color-bg-0)]/40" style="grid-template-columns: 2fr 1fr 0.7fr 0.9fr 0.8fr 1.3fr 140px">
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Name</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Type</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">vCPUs</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Memory</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Size</div>
|
||||
<div class="px-5 py-3 text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Created</div>
|
||||
<div class="px-5 py-3 text-right text-label font-semibold uppercase tracking-[0.06em] text-[var(--color-text-tertiary)]">Actions</div>
|
||||
</div>
|
||||
|
||||
<!-- Rows -->
|
||||
@ -366,17 +364,14 @@
|
||||
<!-- Type badge -->
|
||||
<div class="px-5 py-4">
|
||||
{#if isSnapshot}
|
||||
<span class="inline-flex items-center gap-1.5 rounded-[3px] border border-[var(--color-accent)]/25 bg-[var(--color-accent)]/10 px-2.5 py-1 text-badge font-semibold uppercase tracking-[0.04em] text-[var(--color-accent-mid)]">
|
||||
<span
|
||||
class="inline-block h-[5px] w-[5px] shrink-0 rounded-full bg-[var(--color-accent)]"
|
||||
style="box-shadow: 0 0 6px rgba(94,140,88,0.5); animation: wrenn-glow 1.8s ease-in-out infinite"
|
||||
></span>
|
||||
Snapshot
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full border border-[var(--color-accent)]/25 bg-[var(--color-accent)]/8 px-2.5 py-0.5 text-label font-medium text-[var(--color-accent-bright)]">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-[var(--color-accent)]"></span>
|
||||
snapshot
|
||||
</span>
|
||||
{:else}
|
||||
<span class="inline-flex items-center gap-1.5 rounded-[3px] border border-[var(--color-blue)]/25 bg-[var(--color-blue)]/10 px-2.5 py-1 text-badge font-semibold uppercase tracking-[0.04em] text-[var(--color-blue)]">
|
||||
<span class="inline-block h-[5px] w-[5px] shrink-0 rounded-full bg-[var(--color-blue)]"></span>
|
||||
Image
|
||||
<span class="inline-flex items-center gap-1.5 rounded-full border border-[var(--color-blue)]/25 bg-[var(--color-blue)]/8 px-2.5 py-0.5 text-label font-medium text-[var(--color-blue)]">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-[var(--color-blue)]"></span>
|
||||
image
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
@ -475,7 +470,7 @@
|
||||
</main>
|
||||
|
||||
<!-- Status bar -->
|
||||
<footer class="flex h-7 shrink-0 items-center justify-end border-t border-[var(--color-border)] bg-[var(--color-bg-1)] px-7">
|
||||
<footer class="flex h-7 shrink-0 items-center justify-end border-t border-[var(--color-border)] bg-[var(--color-bg-1)] px-8">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span
|
||||
class="inline-flex h-[5px] w-[5px] rounded-full bg-[var(--color-accent)]"
|
||||
@ -526,12 +521,16 @@
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="relative w-full max-w-[380px] rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] p-6"
|
||||
style="animation: fadeUp 0.2s ease both"
|
||||
class="relative w-full max-w-[420px] rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] shadow-dialog"
|
||||
style="animation: fadeUp 0.18s cubic-bezier(0.25,1,0.5,1) both"
|
||||
>
|
||||
<h2 class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">Delete snapshot</h2>
|
||||
<p class="mt-2 text-ui text-[var(--color-text-tertiary)]">
|
||||
Permanently delete <span class="font-mono font-medium text-[var(--color-text-secondary)]">{deleteTarget.name}</span>.
|
||||
<!-- Danger accent edge -->
|
||||
<div class="h-[2px] rounded-t-[var(--radius-card)] bg-gradient-to-r from-transparent via-[var(--color-red)] to-transparent"></div>
|
||||
|
||||
<div class="p-6">
|
||||
<h2 class="font-serif text-heading leading-tight tracking-[-0.02em] text-[var(--color-text-bright)]">Delete snapshot</h2>
|
||||
<p class="mt-1.5 text-ui text-[var(--color-text-tertiary)]">
|
||||
Permanently delete <code class="rounded bg-[var(--color-bg-4)] px-1.5 py-0.5 font-mono text-[0.8rem] text-[var(--color-text-primary)]">{deleteTarget.name}</code>.
|
||||
Running capsules won't be affected, but you won't be able to launch new ones from it.
|
||||
</p>
|
||||
|
||||
@ -548,7 +547,7 @@
|
||||
{/if}
|
||||
|
||||
{#if deleteError}
|
||||
<div class="mt-4 rounded-[var(--radius-input)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/5 px-3 py-2 text-meta text-[var(--color-red)]">
|
||||
<div class="mt-3 rounded-[var(--radius-input)] border border-[var(--color-red)]/30 bg-[var(--color-red)]/5 px-3 py-2 text-meta text-[var(--color-red)]">
|
||||
{deleteError}
|
||||
</div>
|
||||
{/if}
|
||||
@ -564,7 +563,7 @@
|
||||
<button
|
||||
onclick={handleDelete}
|
||||
disabled={deleting}
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] bg-[var(--color-red)] px-5 py-2 text-ui font-semibold text-white transition-all duration-150 hover:brightness-115 hover:-translate-y-px active:translate-y-0 active:scale-95 disabled:opacity-50 disabled:hover:translate-y-0"
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] bg-[var(--color-red)] px-5 py-2.5 text-ui font-semibold text-white transition-all duration-200 hover:shadow-[0_0_16px_rgba(207,129,114,0.25)] hover:brightness-110 disabled:opacity-50"
|
||||
>
|
||||
{#if deleting}
|
||||
<svg class="animate-spin" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@ -578,6 +577,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Launch Dialog -->
|
||||
@ -591,11 +591,15 @@
|
||||
></div>
|
||||
|
||||
<div
|
||||
class="relative w-full max-w-[420px] rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] p-6"
|
||||
style="animation: fadeUp 0.2s ease both"
|
||||
class="relative w-full max-w-[420px] rounded-[var(--radius-card)] border border-[var(--color-border-mid)] bg-[var(--color-bg-2)] shadow-dialog"
|
||||
style="animation: fadeUp 0.18s cubic-bezier(0.25,1,0.5,1) both"
|
||||
>
|
||||
<h2 class="font-serif text-heading tracking-[-0.02em] text-[var(--color-text-bright)]">Launch Capsule</h2>
|
||||
<p class="mt-1 text-ui text-[var(--color-text-tertiary)]">
|
||||
<!-- Top accent edge -->
|
||||
<div class="h-[2px] rounded-t-[var(--radius-card)] bg-gradient-to-r from-transparent via-[var(--color-accent)] to-transparent"></div>
|
||||
|
||||
<div class="p-6">
|
||||
<h2 class="font-serif text-heading leading-tight tracking-[-0.02em] text-[var(--color-text-bright)]">Launch Capsule</h2>
|
||||
<p class="mt-1.5 text-ui text-[var(--color-text-tertiary)]">
|
||||
Configure resources and launch a new capsule from this snapshot.
|
||||
</p>
|
||||
|
||||
@ -612,12 +616,9 @@
|
||||
</label>
|
||||
<div class="flex items-center gap-2 rounded-[var(--radius-input)] border border-[var(--color-border)] bg-[var(--color-bg-0)] px-3 py-2">
|
||||
{#if launchTarget.type === 'snapshot'}
|
||||
<span
|
||||
class="inline-block h-[6px] w-[6px] shrink-0 rounded-full bg-[var(--color-accent)]"
|
||||
style="box-shadow: 0 0 6px rgba(94,140,88,0.5); animation: wrenn-glow 1.8s ease-in-out infinite"
|
||||
></span>
|
||||
<span class="h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--color-accent)]"></span>
|
||||
{:else}
|
||||
<span class="inline-block h-[6px] w-[6px] shrink-0 rounded-full bg-[var(--color-blue)]"></span>
|
||||
<span class="h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--color-blue)]"></span>
|
||||
{/if}
|
||||
<span class="flex-1 font-mono text-ui text-[var(--color-text-bright)]">{launchTarget.name}</span>
|
||||
<span class="text-label text-[var(--color-text-muted)]">
|
||||
@ -694,7 +695,7 @@
|
||||
<button
|
||||
onclick={handleLaunch}
|
||||
disabled={launching}
|
||||
class="flex items-center gap-2 rounded-[var(--radius-button)] bg-[var(--color-accent)] px-5 py-2 text-ui font-semibold text-white transition-all duration-150 hover:brightness-115 hover:-translate-y-px active:translate-y-0 active:scale-95 disabled:opacity-50 disabled:hover:translate-y-0"
|
||||
class="group flex items-center gap-2 rounded-[var(--radius-button)] bg-[var(--color-accent)] px-5 py-2.5 text-ui font-semibold text-white transition-all duration-200 hover:shadow-[0_0_20px_var(--color-accent-glow-mid)] hover:brightness-115 hover:-translate-y-px active:translate-y-0 disabled:opacity-50 disabled:hover:translate-y-0 disabled:hover:shadow-none"
|
||||
>
|
||||
{#if launching}
|
||||
<svg class="animate-spin" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@ -708,6 +709,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
@ -715,17 +717,17 @@
|
||||
.skeleton {
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
var(--color-bg-4) 0%,
|
||||
var(--color-bg-5) 50%,
|
||||
var(--color-bg-4) 100%
|
||||
var(--color-bg-3) 25%,
|
||||
var(--color-bg-4) 50%,
|
||||
var(--color-bg-3) 75%
|
||||
);
|
||||
background-size: 200% 100%;
|
||||
animation: shimmer 1.6s ease-in-out infinite;
|
||||
animation: shimmer 1.4s ease infinite;
|
||||
}
|
||||
|
||||
@keyframes shimmer {
|
||||
0% { background-position: 200% center; }
|
||||
100% { background-position: -200% center; }
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
|
||||
/* Left accent stripe — slides in on hover, color-keyed to snapshot type */
|
||||
@ -745,4 +747,9 @@
|
||||
.snapshot-row.type-image:hover {
|
||||
background: rgba(90, 159, 212, 0.04);
|
||||
}
|
||||
|
||||
/* Empty state icon float — matches admin pattern */
|
||||
.empty-icon-float {
|
||||
animation: iconFloat 3s ease-in-out infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -17,8 +17,9 @@ mkdir -p /sys/fs/cgroup
|
||||
mount -t cgroup2 cgroup2 /sys/fs/cgroup 2>/dev/null || true
|
||||
echo "+cpu +memory +io" > /sys/fs/cgroup/cgroup.subtree_control 2>/dev/null || true
|
||||
|
||||
# Set hostname
|
||||
# Set hostname and make it resolvable (sudo requires this).
|
||||
hostname sandbox
|
||||
echo "127.0.0.1 sandbox" >> /etc/hosts
|
||||
|
||||
# Configure networking if the kernel ip= boot arg did not already set it up.
|
||||
if ! ip addr show eth0 2>/dev/null | grep -q "169.254.0.21"; then
|
||||
|
||||
@ -420,8 +420,10 @@ func (s *BuildService) executeBuild(ctx context.Context, buildIDStr string) {
|
||||
}
|
||||
|
||||
// Capture the final user and env vars as template defaults.
|
||||
// Filter out user-specific and runtime vars that should be resolved at
|
||||
// sandbox creation time, not baked in from the build environment.
|
||||
templateDefaultUser := bctx.User
|
||||
templateDefaultEnv := bctx.EnvVars
|
||||
templateDefaultEnv := filterBuildEnv(bctx.EnvVars)
|
||||
|
||||
// Phase 3: Post-build (as root) — cleanup.
|
||||
bctx.User = "root"
|
||||
@ -739,3 +741,27 @@ func (s *BuildService) uploadAndExtractArchive(
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runtimeEnvVars lists env vars that are user- or session-specific and should
|
||||
// not be persisted into template defaults. These are resolved at runtime by
|
||||
// envd based on the actual user and sandbox context.
|
||||
var runtimeEnvVars = map[string]bool{
|
||||
"HOME": true, "USER": true, "LOGNAME": true, "SHELL": true,
|
||||
"PWD": true, "OLDPWD": true, "HOSTNAME": true, "TERM": true,
|
||||
"SHLVL": true, "_": true,
|
||||
// Per-sandbox identifiers set by envd at boot via MMDS.
|
||||
"WRENN_SANDBOX_ID": true, "WRENN_TEMPLATE_ID": true,
|
||||
}
|
||||
|
||||
// filterBuildEnv returns a copy of envVars with runtime/user-specific
|
||||
// variables removed so they don't override envd's per-user resolution.
|
||||
func filterBuildEnv(envVars map[string]string) map[string]string {
|
||||
filtered := make(map[string]string, len(envVars))
|
||||
for k, v := range envVars {
|
||||
if runtimeEnvVars[k] {
|
||||
continue
|
||||
}
|
||||
filtered[k] = v
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user