forked from wrenn/wrenn
Polish admin capsule pages and improve shared components
- Admin list: remove redundant Open button, normalize with dashboard patterns (sorting, search highlight, auto-refresh, animations) - Admin detail: breadcrumb header, status bar, visibility polling - FilesTab: add treeOnly prop, compact mode uses 2/7 tree + 5/7 preview split, expand tree to full width when no file selected, improve copy - MetricsPanel: hide Live badge in compact layout (redundant with status) - DestroyDialog: accept destroyFn prop for admin capsule deletion
This commit is contained in:
@ -1,13 +1,15 @@
|
||||
<script lang="ts">
|
||||
import { destroyCapsule } from '$lib/api/capsules';
|
||||
import type { ApiResult } from '$lib/api/client';
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
capsuleId: string;
|
||||
onclose: () => void;
|
||||
ondestroyed?: () => void;
|
||||
destroyFn?: (id: string) => Promise<ApiResult<void>>;
|
||||
};
|
||||
let { open, capsuleId, onclose, ondestroyed }: Props = $props();
|
||||
let { open, capsuleId, onclose, ondestroyed, destroyFn }: Props = $props();
|
||||
|
||||
let destroying = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
@ -15,7 +17,8 @@
|
||||
async function handleDestroy() {
|
||||
destroying = true;
|
||||
error = null;
|
||||
const result = await destroyCapsule(capsuleId);
|
||||
const destroy = destroyFn ?? destroyCapsule;
|
||||
const result = await destroy(capsuleId);
|
||||
if (result.ok) {
|
||||
error = null;
|
||||
ondestroyed?.();
|
||||
|
||||
@ -453,8 +453,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-ui font-medium text-[var(--color-text-secondary)]">File browser unavailable</span>
|
||||
<span class="text-meta text-[var(--color-text-muted)]">Start the capsule to browse its filesystem</span>
|
||||
<span class="text-ui font-medium text-[var(--color-text-secondary)]">Capsule not running</span>
|
||||
<span class="text-meta text-[var(--color-text-muted)]">Start or resume the capsule to browse files</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -462,7 +462,7 @@
|
||||
<div class="flex flex-1 min-h-0">
|
||||
|
||||
<!-- Left panel: File tree -->
|
||||
<div class="flex shrink-0 flex-col bg-[var(--color-bg-2)] {(treeOnly || (compact && !selectedFile)) ? 'flex-1' : 'w-[380px] border-r border-[var(--color-border)]'}"
|
||||
<div class="flex shrink-0 flex-col bg-[var(--color-bg-2)] {(treeOnly || (compact && !selectedFile)) ? 'flex-1' : compact ? 'w-[28.57%] border-r border-[var(--color-border)]' : 'w-[380px] border-r border-[var(--color-border)]'}"
|
||||
>
|
||||
|
||||
<!-- Path input -->
|
||||
@ -482,7 +482,7 @@
|
||||
onfocus={() => (pathInputFocused = true)}
|
||||
onblur={() => (pathInputFocused = false)}
|
||||
onkeydown={handleKeydown}
|
||||
placeholder="Enter path..."
|
||||
placeholder="/home/user or ~/file.txt"
|
||||
spellcheck="false"
|
||||
autocomplete="off"
|
||||
class="flex-1 bg-transparent font-mono text-meta text-[var(--color-text-primary)] outline-none placeholder:text-[var(--color-text-muted)]"
|
||||
@ -569,7 +569,7 @@
|
||||
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-meta text-[var(--color-text-muted)]">Nothing here yet</span>
|
||||
<span class="text-meta text-[var(--color-text-muted)]">Empty directory</span>
|
||||
</div>
|
||||
{:else}
|
||||
{#each sortedEntries as entry, idx (entry.path)}
|
||||
@ -669,8 +669,8 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="text-ui text-[var(--color-text-secondary)]">No file selected</span>
|
||||
<span class="text-meta text-[var(--color-text-muted)]">Choose a file from the tree, or enter a path directly</span>
|
||||
<span class="text-ui text-[var(--color-text-secondary)]">Select a file to preview</span>
|
||||
<span class="text-meta text-[var(--color-text-muted)]">Click a file in the tree or type a path above</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -704,7 +704,7 @@
|
||||
<button
|
||||
onclick={handleDownload}
|
||||
disabled={downloading || !isDownloadable}
|
||||
title={isDownloadable ? undefined : 'Only regular files can be downloaded'}
|
||||
title={isDownloadable ? 'Download file' : 'Not a regular file — download unavailable'}
|
||||
class="flex items-center gap-1.5 rounded-[var(--radius-button)] border border-[var(--color-border)] bg-[var(--color-bg-3)] px-2.5 py-1 text-badge font-semibold uppercase tracking-[0.05em] text-[var(--color-text-secondary)] transition-colors hover:bg-[var(--color-bg-4)] hover:text-[var(--color-text-primary)] disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{#if downloading}
|
||||
@ -752,13 +752,13 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="text-ui font-medium text-[var(--color-text-primary)]">Special file</span>
|
||||
<span class="text-ui font-medium text-[var(--color-text-primary)]">Not a regular file</span>
|
||||
<span class="text-meta text-[var(--color-text-tertiary)]">
|
||||
<code class="rounded bg-[var(--color-bg-4)] px-1.5 py-0.5 font-mono text-[var(--color-text-secondary)]">{selectedFile.name}</code>
|
||||
is a device, socket, or pipe
|
||||
</span>
|
||||
<span class="mt-1 text-meta text-[var(--color-text-muted)]">
|
||||
Special files can't be previewed or downloaded.
|
||||
These file types can't be read or downloaded.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -781,7 +781,7 @@
|
||||
</span>
|
||||
{/if}
|
||||
<span class="mt-1 text-meta text-[var(--color-text-muted)]">
|
||||
Symlinks can't be downloaded directly. Navigate to the target file instead.
|
||||
Open the target path to view or download its contents.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@ -806,14 +806,14 @@
|
||||
</div>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
{#if isFileTooLarge(selectedFile.size)}
|
||||
<span class="text-ui font-medium text-[var(--color-text-primary)]">Too large to preview</span>
|
||||
<span class="text-ui font-medium text-[var(--color-text-primary)]">File too large to preview</span>
|
||||
<span class="text-meta text-[var(--color-text-tertiary)]">
|
||||
{formatFileSize(selectedFile.size)} — preview limit is 10 MB
|
||||
{formatFileSize(selectedFile.size)} exceeds the 10 MB preview limit
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-ui font-medium text-[var(--color-text-primary)]">Binary file</span>
|
||||
<span class="text-meta text-[var(--color-text-tertiary)]">
|
||||
Can't display as text — download to view
|
||||
This file type can't be displayed as text
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@ -262,11 +262,15 @@
|
||||
<div class="flex flex-1 flex-col min-h-0">
|
||||
<!-- Controls row -->
|
||||
<div class="flex shrink-0 items-center justify-between {layout === 'full' ? 'px-0 pb-5' : 'border-b border-[var(--color-border)] bg-[var(--color-bg-1)] px-5 py-2'}">
|
||||
{#if !metricsLoading}
|
||||
<span class="flex items-center gap-1.5 rounded-[3px] border border-[var(--color-accent)]/25 bg-[var(--color-accent-glow-mid)] px-2 py-1 text-badge font-semibold uppercase tracking-[0.05em] text-[var(--color-accent-mid)]">
|
||||
<span class="h-[5px] w-[5px] rounded-full bg-[var(--color-accent)]" style="animation: wrenn-glow 2.5s ease-in-out infinite"></span>
|
||||
Live
|
||||
</span>
|
||||
{#if layout === 'full'}
|
||||
{#if !metricsLoading}
|
||||
<span class="flex items-center gap-1.5 rounded-[3px] border border-[var(--color-accent)]/25 bg-[var(--color-accent-glow-mid)] px-2 py-1 text-badge font-semibold uppercase tracking-[0.05em] text-[var(--color-accent-mid)]">
|
||||
<span class="h-[5px] w-[5px] rounded-full bg-[var(--color-accent)]" style="animation: wrenn-glow 2.5s ease-in-out infinite"></span>
|
||||
Live
|
||||
</span>
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div></div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user