{#if loadingError} {:else if !loaded}

Loading collection…

{:else}
Apply collection

{mode === 'exact' ? 'Match the entire fleet across both games. Excess lobbies and maps outside this collection will be removed.' : 'Create missing lobbies. All existing servers and their settings stay as they are.'}

{#if preview} {noChanges ? 'Collection targets met' : `${preview.servers.length} to create · ${preview.removals.length} to remove`} {#if mode === 'exact' && preview.retainedServers.length}

{preview.retainedServers.length} existing or queued servers kept.

{/if}
{#each keyCoverage as pool}= pool.required} class:warning={pool.available < pool.required} >{pool.game.toUpperCase()} keys: {pool.unassigned ? 'waiting for lobby availability' : `${pool.available} of ${pool.required} required available`}{/each} {#if preview.servers.length}{hostStatus.filesReady ? 'Game files ready' : 'Game files preparing'}{/if}
{#if preview.servers.length}

{collectionCapacitySummary(preview.availability)}

{/if} {#if noChanges}

{mode === 'exact' ? 'Your fleet already matches this collection.' : 'Your fleet meets or exceeds every target.'} Existing servers keep their current settings.

{/if} {:else}

{checking ? 'Checking fleet changes…' : 'Resolve the issues below to check this collection.'}

{/if}
{#if launchError}{/if} {#if view.changeNotice}

{view.changeNotice}

{/if} {#if hostStatus.error} {:else if !hostStatus.loading && hostStatus.missingGames.length}

Upload {hostStatus.missingGames .map((game) => game.toUpperCase()) .join(' and ')} game files before launching.

Manage game files ↗
{:else if !hostStatus.loading && !imageValid}

Enter a valid runtime image in Advanced settings.

{:else if view.error}{/if} {#if missingKeys.length}
{#each missingKeys as pool}{@const shortfall = pool.required - pool.unassigned - pool.available}

{pool.game.toUpperCase()} needs {shortfall} more unused key{shortfall === 1 ? '' : 's'}. Manage keys ↗

{/each} {#if preview?.removals.length}

Keys assigned to removals stay in use until cleanup finishes. Complete those removals first to reuse their keys.

{/if}
{/if} {#each preview?.blockers ?? [] as blocker}{/each} {#if !checking && !unconfirmedLaunch && (view.error || hostStatus.error || missingKeys.length || preview?.blockers.length)}{/if} {#if preview}
{#each mapGroups as group} {#if mode === 'exact'}{/if}{#each group.rows as row}{#if mode === 'exact'}{/if}{/each}
{group.label}
MapTargetExisting / queuedCreateRemove
{row.mapName}{row.target}{row.existing} 0}>{row.create} 0} >{row.remove}
{/each}

Stopped and unhealthy servers also count toward targets. See Server details for health.{#if mode === 'exact'} Existing totals include the listed removals.{/if}

{#if preview.removals.length}

Destroy {preview.removals.length} server{preview.removals.length === 1 ? '' : 's'}

Containers, files, and registrations will be removed. Occupied or unreachable servers wait for a fresh empty-player check; unstarted creations are cancelled.

    {#each preview.removals as server}
  • {serverTitle(server.name)}{server.name}{server.reason}
  • {/each}
{/if}
Server details
{#if preview.servers.length}

Launch order

Hosts and ports are assigned when provisioning starts.

    {#each preview.servers as server}
  1. {server.name ? serverTitle(server.name) : preview.entries[server.entryIndex].recipe .mapName}{server.name ?? 'No available lobby'}
  2. {/each}
{/if} {#if preview.entries.some((entry) => entry.existing)}

Existing and queued servers

    {#each preview.entries as entry}{#each entry.coverage ?? [] as server}
  • {serverTitle(server.name)} ↗{server.health === 'online' ? 'Healthy' : server.health}{#if mode === 'exact'} · {preview.retainedServers.includes(server.name) ? 'Keep' : 'Remove'}{/if}
  • {/each}{/each}
{/if} {#if !preview.servers.length && !preview.entries.some((entry) => entry.existing)}

No new or matching servers.

{/if}
{#if preview.availability && preview.servers.length}
Capacity details
CPU available
{preview.availability.cpuCoresAvailable === null ? 'Unknown' : `${preview.availability.cpuCoresAvailable.toFixed(1)} cores`}
Memory available
{preview.availability.memoryBytesAvailable === null ? 'Unknown' : `${(preview.availability.memoryBytesAvailable / 1024 ** 3).toFixed(1)} GiB`}
Ports available
{preview.availability.portsAvailable ?? 'Unknown'}
Startup slots
{preview.availability.startupSlots ?? 'Unknown'}{#if preview.availability.startupLimit !== null} / {preview.availability.startupLimit}{/if}
{#each preview.warnings ?? [] as warning}

{warning}

{/each}

{preview.availability.updatedAt ? `Updated ${$timeFormat.dateTime(preview.availability.updatedAt)}.` : 'Waiting for host reports.'} Capacity is checked for each server when it starts.

View capacity and history ↗
{/if} {/if} {#if open}{/if} {#if preview}
Setup details

New servers use the current production setup when provisioning starts. Existing servers keep their files and configuration.

Runtime image
{selection.image}
{#each games as game}{@const files = hostStatus.gameFiles?.[game]} {#if files?.uploadedAt}
{game.toUpperCase()} game files uploaded
{$timeFormat.dateTime(files.uploadedAt)}
{/if} {/each}
{#each preview.entries as entry}
{entry.recipe.mapName} · {entry.recipe.game.toUpperCase()}

{entry.recipe.configSource}

{entry.recipe.files.length} managed files

    {#each entry.recipe.files as file}
  • {file.name}{launchFileVersionLabel(file)} · {file.kind}{file.path}
  • {/each}
{/each}
{/if} {/if}
{#snippet actions()}

{footerStatus}

{#if noChanges} {:else}{/if}
{/snippet}