Files
electric-cherry/src/lib/ui/components/LaunchRecipe.svelte
T

158 lines
4.3 KiB
Svelte

<script lang="ts">
import RelativeTime from '$ui/components/RelativeTime.svelte';
import { launchFileVersionLabel } from '$lib/artifact-version.js';
import type { LaunchRecipeSummary } from '$lib/deployment-plan.js';
let { recipe, historical = false }: { recipe: LaunchRecipeSummary; historical?: boolean } =
$props();
</script>
<div class="recipe">
<div class="recipe-heading">
<div>
{#if !historical}<span class="eyebrow">Current promoted setup</span>{/if}
<h3>{recipe.mapName}</h3>
</div>
<span class="game">{recipe.game === 't6' ? 'Black Ops II' : 'Black Ops I'}</span>
</div>
<dl>
<div>
<dt>Configuration</dt>
<dd>{recipe.configSource}</dd>
</div>
<div>
<dt>Container image</dt>
<dd class="mono">{recipe.image}</dd>
</div>
</dl>
{#if !historical}<p>
Each server uses the latest promoted scripts, plugins, and configuration when
provisioning begins, including promotions made after this preview or while queued.
</p>{/if}
<details>
<summary
>{recipe.files.length} managed file{recipe.files.length === 1 ? '' : 's'}
{historical ? 'recorded for this deployment' : 'in the current setup'}</summary
>
<ul>
{#each recipe.files as file}<li>
<div>
<strong>{file.name}</strong><span
>{launchFileVersionLabel(file)} · {file.kind}
· {file.size.toLocaleString()} bytes</span
>
</div>
<code>{file.path}</code><small title={file.sha256}
>SHA-256 {file.sha256.slice(0, 12)}</small
>
</li>{/each}
</ul>
{#if !historical}<p>
Previewed <RelativeTime value={recipe.capturedAt} />. This preview shows the
promoted setup at the time above. The deployment history records the files selected
when provisioning begins.
</p>{/if}
<small class="fingerprint" title={recipe.fingerprint}
>Recipe {recipe.fingerprint.slice(0, 16)}</small
>
</details>
</div>
<style>
.recipe {
display: grid;
gap: 16px;
min-width: 0;
}
.recipe-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.eyebrow {
display: block;
font-size: var(--text-xs);
text-transform: uppercase;
letter-spacing: 0.12em;
color: var(--text-secondary);
margin-bottom: 6px;
}
h3 {
margin: 0;
font-size: var(--text-xl);
letter-spacing: -0.03em;
}
.game {
border: 1px solid var(--border-strong);
border-radius: 99px;
padding: 5px 10px;
font-size: var(--text-xs);
white-space: nowrap;
}
dl {
display: grid;
gap: 12px;
margin: 0;
}
dt {
color: var(--text-secondary);
font-size: var(--text-xs);
}
dd {
margin: 4px 0 0;
font-size: var(--text-sm);
overflow-wrap: anywhere;
line-height: 1.5;
}
.mono,
code,
.fingerprint {
font-family: var(--font-mono);
}
details {
border-top: 1px solid var(--border-subtle);
padding-top: 14px;
}
summary {
cursor: pointer;
font-size: var(--text-sm);
}
ul {
list-style: none;
padding: 0;
margin: 12px 0;
}
li {
display: grid;
gap: 5px;
padding: 12px 0;
border-bottom: 1px solid var(--border-subtle);
overflow-wrap: anywhere;
}
li > div {
display: flex;
justify-content: space-between;
gap: 8px;
flex-wrap: wrap;
}
strong {
font-size: var(--text-sm);
font-weight: 550;
}
span,
code,
small {
font-size: var(--text-xs);
}
li span,
small {
color: var(--text-secondary);
}
p {
color: var(--text-secondary);
font-size: var(--text-xs);
line-height: 1.6;
}
</style>