diff --git a/frontend/src/lib/components/Sidebar.svelte b/frontend/src/lib/components/Sidebar.svelte index 4111dd8..f7849ee 100644 --- a/frontend/src/lib/components/Sidebar.svelte +++ b/frontend/src/lib/components/Sidebar.svelte @@ -49,7 +49,7 @@ const platformItems: NavItem[] = [ { label: 'Capsules', icon: IconMonitor, href: '/dashboard/capsules' }, - { label: 'Templates', icon: IconBox, href: '/dashboard/snapshots' }, + { label: 'Templates', icon: IconBox, href: '/dashboard/templates' }, { label: 'Metrics', icon: IconMetrics, href: '/dashboard/metrics' } ]; diff --git a/frontend/src/routes/admin/templates/+page.svelte b/frontend/src/routes/admin/templates/+page.svelte index 67c3b0a..a309658 100644 --- a/frontend/src/routes/admin/templates/+page.svelte +++ b/frontend/src/routes/admin/templates/+page.svelte @@ -270,47 +270,50 @@
-
-
+
+ +
+ +
-

+

Templates

-

+

Build and manage global templates available to all teams.

- + {#if !templatesLoading && !templatesError} -
-
- {templateCount} +
+
+ {templateCount} templates
-
- {baseCount} +
+ {baseCount} base
-
- {snapshotCount} +
+ {snapshotCount} snapshots
{#if runningBuilds > 0} -
- - - +
+ + + - {runningBuilds} + {runningBuilds} building
{/if} @@ -318,30 +321,32 @@ {/if}
- -
+ +
{#each [['templates', 'Templates', templateCount], ['builds', 'Builds', builds.length]] as [id, label, count] (id)} {/each}
-
+
{#if activeTab === 'templates'} {#if templatesLoading} {@render skeletonRows(5, ['Name', 'Type', 'Specs', 'Size', 'Created', ''])} @@ -374,20 +379,20 @@ {#snippet skeletonRows(count: number, headers: string[])} -
+
- + {#each headers as h} - + {/each} {#each Array(count) as _, i} - + {#each headers as _h, j} - {/each} @@ -399,81 +404,99 @@ {/snippet} {#snippet emptyState(type: 'templates' | 'builds')} -
-
- {#if type === 'templates'} - - {:else} - - {/if} +
+ +
+
+
+ {#if type === 'templates'} + + {:else} + + {/if} +
-

- {type === 'templates' ? 'No templates yet.' : 'No builds yet.'} +

+ {type === 'templates' ? 'No templates yet' : 'No builds yet'}

-

+

{type === 'templates' ? 'Create a template to provide pre-configured environments for all teams.' : 'Start a template build to see progress and logs here.'}

+ {#if type === 'templates'} + + {/if}
{/snippet} {#snippet templatesTable()} -
+
{h}{h}
+
- - - - - - - + + + + + + + - {#each templates as tmpl (tmpl.name)} - - + - - - - -
NameType
NameType
-
- {tmpl.name} + {#each templates as tmpl, i (tmpl.name)} +
+
+ {tmpl.name}
+ {#if tmpl.type === 'snapshot'} - + + snapshot {:else} - + + base {/if} + @@ -486,28 +509,30 @@ {/snippet} {#snippet buildsTable()} -
+
- - - - - - - - + + + + + + + + - {#each builds as build (build.id)} + {#each builds as build, i (build.id)} toggleBuildExpand(build.id)} > - - - - - - - @@ -569,7 +596,7 @@ {#if expandedBuildId === build.id}
BuildNameStatus
BuildNameStatus
-
+
+
{build.id}
- {build.name} + + {build.name} - + + {#if build.status === 'running'} - - - + + + {:else if build.status === 'success'} - + {:else if build.status === 'failed'} - + {:else} - + {/if} {build.status}
-
+
{#if build.status === 'pending' || build.status === 'running'}
-

+ +
+ +
+

Create Template

@@ -872,7 +903,7 @@

+

{/if} @@ -897,10 +929,14 @@ onkeydown={(e) => { if (e.key === 'Escape' && !deleting) deleteTarget = null; }} >
-

+ +
+ +
+

Delete Template

@@ -924,7 +960,7 @@

+

{/if} @@ -959,4 +996,59 @@ background-size: 200% 100%; animation: shimmer 1.4s ease infinite; } + + /* Stat pill — shared base */ + .stat-pill { + display: flex; + align-items: baseline; + gap: 6px; + border-radius: var(--radius-button); + border-width: 1px; + padding: 6px 12px; + transition: transform 0.15s ease, box-shadow 0.15s ease; + } + .stat-pill:hover { + transform: translateY(-1px); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); + } + + /* Table header */ + .table-header { + padding: 10px 20px; + text-align: left; + font-size: var(--text-label); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--color-text-tertiary); + } + + /* Staggered row entrance */ + .table-row-animate { + animation: fadeUp 0.25s ease both; + } + + /* Tab button */ + .tab-button { + position: relative; + padding: 14px 20px 14px 0; + font-size: var(--text-ui); + transition: color 0.15s ease; + cursor: pointer; + } + + /* Active build row — subtle left accent */ + .build-row-active { + box-shadow: inset 3px 0 0 var(--color-blue); + } + + /* Progress bar glow for running builds */ + .progress-bar-glow { + box-shadow: 0 0 8px rgba(90, 159, 212, 0.4); + } + + /* Empty state icon float */ + .empty-icon-float { + animation: iconFloat 3s ease-in-out infinite; + } diff --git a/frontend/src/routes/dashboard/snapshots/+page.svelte b/frontend/src/routes/dashboard/templates/+page.svelte similarity index 99% rename from frontend/src/routes/dashboard/snapshots/+page.svelte rename to frontend/src/routes/dashboard/templates/+page.svelte index 2fe2b37..b84a3d7 100644 --- a/frontend/src/routes/dashboard/snapshots/+page.svelte +++ b/frontend/src/routes/dashboard/templates/+page.svelte @@ -383,8 +383,8 @@
- {#if snapshot.type === 'snapshot' && snapshot.vcpus != null} - + {#if snapshot.vcpus != null && snapshot.vcpus > 0} + @@ -397,8 +397,8 @@
- {#if snapshot.type === 'snapshot' && snapshot.memory_mb != null} - + {#if snapshot.memory_mb != null && snapshot.memory_mb > 0} +