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}
|
{:else if capsule}
|
||||||
<div class="flex flex-1 flex-col min-h-0">
|
<div class="flex flex-1 flex-col min-h-0">
|
||||||
|
|
||||||
<!-- Action buttons -->
|
<!-- Tabs + lifecycle actions -->
|
||||||
<div class="flex items-center justify-end gap-2 px-7 pt-5">
|
<div class="mt-5 flex items-center border-b border-[var(--color-border)] px-7">
|
||||||
{#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">
|
|
||||||
<button
|
<button
|
||||||
onclick={() => setTab('metrics')}
|
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
|
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>
|
</svg>
|
||||||
Terminal
|
Terminal
|
||||||
</button>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Tab content -->
|
<!-- Tab content -->
|
||||||
|
|||||||
@ -162,10 +162,10 @@
|
|||||||
<div class="flex flex-1 flex-col overflow-hidden">
|
<div class="flex flex-1 flex-col overflow-hidden">
|
||||||
<main class="flex-1 overflow-y-auto bg-[var(--color-bg-0)]">
|
<main class="flex-1 overflow-y-auto bg-[var(--color-bg-0)]">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="px-7 pt-8">
|
<div class="px-8 pt-8">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between">
|
||||||
<div>
|
<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
|
Templates
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mt-2 text-ui text-[var(--color-text-secondary)]">
|
<p class="mt-2 text-ui text-[var(--color-text-secondary)]">
|
||||||
@ -231,15 +231,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="skeleton h-4 w-20 rounded-sm"></div>
|
<div class="skeleton h-4 w-20 rounded-sm"></div>
|
||||||
</div>
|
</div>
|
||||||
<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">
|
||||||
<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="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.05em] text-[var(--color-text-muted)]">Name</div>
|
<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.05em] text-[var(--color-text-muted)]">Type</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.05em] text-[var(--color-text-muted)]">vCPUs</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.05em] text-[var(--color-text-muted)]">Memory</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.05em] text-[var(--color-text-muted)]">Size</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.05em] text-[var(--color-text-muted)]">Created</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.05em] text-[var(--color-text-muted)]">Actions</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>
|
</div>
|
||||||
{#each Array(4) as _, i}
|
{#each Array(4) as _, i}
|
||||||
<div
|
<div
|
||||||
@ -294,30 +294,28 @@
|
|||||||
|
|
||||||
{#if filteredSnapshots.length === 0}
|
{#if filteredSnapshots.length === 0}
|
||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
<div class="flex flex-col items-center justify-center py-[72px]">
|
<div class="flex flex-col items-center justify-center py-28 text-center">
|
||||||
<div class="relative mb-5">
|
<div class="relative mb-7">
|
||||||
<!-- Radial glow behind icon -->
|
<div class="absolute -inset-3 rounded-2xl bg-[var(--color-accent-glow)] blur-xl"></div>
|
||||||
<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
|
<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)]"
|
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"
|
||||||
style="animation: iconFloat 4s ease-in-out infinite"
|
|
||||||
>
|
>
|
||||||
<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" />
|
<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" />
|
<polyline points="3.27 6.96 12 12.01 20.73 6.96" /><line x1="12" y1="22.08" x2="12" y2="12" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</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)}
|
{emptyHeading(typeFilter)}
|
||||||
</p>
|
</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)}
|
{emptyDescription(typeFilter)}
|
||||||
</p>
|
</p>
|
||||||
{#if typeFilter === 'all' || typeFilter === 'snapshot'}
|
{#if typeFilter === 'all' || typeFilter === 'snapshot'}
|
||||||
<a
|
<a
|
||||||
href="/dashboard/capsules"
|
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
|
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">
|
<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>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Table -->
|
<!-- 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 -->
|
<!-- 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="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.05em] text-[var(--color-text-muted)]">Name</div>
|
<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.05em] text-[var(--color-text-muted)]">Type</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.05em] text-[var(--color-text-muted)]">vCPUs</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.05em] text-[var(--color-text-muted)]">Memory</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.05em] text-[var(--color-text-muted)]">Size</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.05em] text-[var(--color-text-muted)]">Created</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.05em] text-[var(--color-text-muted)]">Actions</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>
|
</div>
|
||||||
|
|
||||||
<!-- Rows -->
|
<!-- Rows -->
|
||||||
@ -366,17 +364,14 @@
|
|||||||
<!-- Type badge -->
|
<!-- Type badge -->
|
||||||
<div class="px-5 py-4">
|
<div class="px-5 py-4">
|
||||||
{#if isSnapshot}
|
{#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-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
|
<span class="h-1.5 w-1.5 rounded-full bg-[var(--color-accent)]"></span>
|
||||||
class="inline-block h-[5px] w-[5px] shrink-0 rounded-full bg-[var(--color-accent)]"
|
snapshot
|
||||||
style="box-shadow: 0 0 6px rgba(94,140,88,0.5); animation: wrenn-glow 1.8s ease-in-out infinite"
|
|
||||||
></span>
|
|
||||||
Snapshot
|
|
||||||
</span>
|
</span>
|
||||||
{:else}
|
{: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-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="inline-block h-[5px] w-[5px] shrink-0 rounded-full bg-[var(--color-blue)]"></span>
|
<span class="h-1.5 w-1.5 rounded-full bg-[var(--color-blue)]"></span>
|
||||||
Image
|
image
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@ -475,7 +470,7 @@
|
|||||||
</main>
|
</main>
|
||||||
|
|
||||||
<!-- Status bar -->
|
<!-- 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">
|
<div class="flex items-center gap-1.5">
|
||||||
<span
|
<span
|
||||||
class="inline-flex h-[5px] w-[5px] rounded-full bg-[var(--color-accent)]"
|
class="inline-flex h-[5px] w-[5px] rounded-full bg-[var(--color-accent)]"
|
||||||
@ -526,12 +521,16 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<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"
|
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.2s ease both"
|
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>
|
<!-- Danger accent edge -->
|
||||||
<p class="mt-2 text-ui text-[var(--color-text-tertiary)]">
|
<div class="h-[2px] rounded-t-[var(--radius-card)] bg-gradient-to-r from-transparent via-[var(--color-red)] to-transparent"></div>
|
||||||
Permanently delete <span class="font-mono font-medium text-[var(--color-text-secondary)]">{deleteTarget.name}</span>.
|
|
||||||
|
<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.
|
Running capsules won't be affected, but you won't be able to launch new ones from it.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -548,7 +547,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if deleteError}
|
{#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}
|
{deleteError}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -564,7 +563,7 @@
|
|||||||
<button
|
<button
|
||||||
onclick={handleDelete}
|
onclick={handleDelete}
|
||||||
disabled={deleting}
|
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}
|
{#if deleting}
|
||||||
<svg class="animate-spin" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="animate-spin" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
@ -576,6 +575,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -591,11 +591,15 @@
|
|||||||
></div>
|
></div>
|
||||||
|
|
||||||
<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"
|
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.2s ease both"
|
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>
|
<!-- Top accent edge -->
|
||||||
<p class="mt-1 text-ui text-[var(--color-text-tertiary)]">
|
<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.
|
Configure resources and launch a new capsule from this snapshot.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@ -612,12 +616,9 @@
|
|||||||
</label>
|
</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">
|
<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'}
|
{#if launchTarget.type === 'snapshot'}
|
||||||
<span
|
<span class="h-1.5 w-1.5 shrink-0 rounded-full bg-[var(--color-accent)]"></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>
|
|
||||||
{:else}
|
{: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}
|
{/if}
|
||||||
<span class="flex-1 font-mono text-ui text-[var(--color-text-bright)]">{launchTarget.name}</span>
|
<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)]">
|
<span class="text-label text-[var(--color-text-muted)]">
|
||||||
@ -694,7 +695,7 @@
|
|||||||
<button
|
<button
|
||||||
onclick={handleLaunch}
|
onclick={handleLaunch}
|
||||||
disabled={launching}
|
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}
|
{#if launching}
|
||||||
<svg class="animate-spin" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg class="animate-spin" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
@ -706,6 +707,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -715,17 +717,17 @@
|
|||||||
.skeleton {
|
.skeleton {
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
90deg,
|
90deg,
|
||||||
var(--color-bg-4) 0%,
|
var(--color-bg-3) 25%,
|
||||||
var(--color-bg-5) 50%,
|
var(--color-bg-4) 50%,
|
||||||
var(--color-bg-4) 100%
|
var(--color-bg-3) 75%
|
||||||
);
|
);
|
||||||
background-size: 200% 100%;
|
background-size: 200% 100%;
|
||||||
animation: shimmer 1.6s ease-in-out infinite;
|
animation: shimmer 1.4s ease infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shimmer {
|
@keyframes shimmer {
|
||||||
0% { background-position: 200% center; }
|
0% { background-position: -200% 0; }
|
||||||
100% { background-position: -200% center; }
|
100% { background-position: 200% 0; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Left accent stripe — slides in on hover, color-keyed to snapshot type */
|
/* Left accent stripe — slides in on hover, color-keyed to snapshot type */
|
||||||
@ -745,4 +747,9 @@
|
|||||||
.snapshot-row.type-image:hover {
|
.snapshot-row.type-image:hover {
|
||||||
background: rgba(90, 159, 212, 0.04);
|
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>
|
</style>
|
||||||
|
|||||||
@ -17,8 +17,9 @@ mkdir -p /sys/fs/cgroup
|
|||||||
mount -t cgroup2 cgroup2 /sys/fs/cgroup 2>/dev/null || true
|
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
|
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
|
hostname sandbox
|
||||||
|
echo "127.0.0.1 sandbox" >> /etc/hosts
|
||||||
|
|
||||||
# Configure networking if the kernel ip= boot arg did not already set it up.
|
# 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
|
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.
|
// 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
|
templateDefaultUser := bctx.User
|
||||||
templateDefaultEnv := bctx.EnvVars
|
templateDefaultEnv := filterBuildEnv(bctx.EnvVars)
|
||||||
|
|
||||||
// Phase 3: Post-build (as root) — cleanup.
|
// Phase 3: Post-build (as root) — cleanup.
|
||||||
bctx.User = "root"
|
bctx.User = "root"
|
||||||
@ -739,3 +741,27 @@ func (s *BuildService) uploadAndExtractArchive(
|
|||||||
|
|
||||||
return nil
|
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