Support game-wide autoscaling rules with per-map targets
This commit is contained in:
@@ -73,8 +73,8 @@
|
||||
<form onsubmit={event => { event.preventDefault(); void submit('preview'); }} oninput={() => preview = null} onchange={() => preview = null}>
|
||||
<fieldset disabled={busy}>
|
||||
<div class="fields">
|
||||
<label>Game<select bind:value={settings.game} disabled={!!editing} onchange={() => settings.mapSlug = zombieMaps(settings.game)[0].slug}><option value="t5">Black Ops I</option><option value="t6">Black Ops II</option></select></label>
|
||||
<label>Map variant<select bind:value={settings.mapSlug} disabled={!!editing}>{#each zombieMaps(settings.game) as map}<option value={map.slug}>{map.friendly}</option>{/each}</select></label>
|
||||
<label>Game<select bind:value={settings.game} disabled={!!editing} onchange={() => { if (settings.mapSlug !== '*') settings.mapSlug = zombieMaps(settings.game)[0].slug; }}><option value="t5">Black Ops I</option><option value="t6">Black Ops II</option></select></label>
|
||||
<label>Scope<select bind:value={settings.mapSlug} disabled={!!editing}><option value="*">Every map in this game</option>{#each zombieMaps(settings.game) as map}<option value={map.slug}>{map.friendly}</option>{/each}</select></label>
|
||||
<label>Mode<select bind:value={settings.mode}><option value="off">Off</option><option value="dry-run">Dry run</option>{#if report.liveAvailable}<option value="create-only">Create only</option>{/if}{#if report.removalAvailable}<option value="create-destroy">Create and destroy</option>{/if}</select></label>
|
||||
<label>Minimum running lobbies<input type="number" min="1" max="20" required bind:value={settings.minimum} /></label>
|
||||
<label>Maximum lobbies<input type="number" min="1" max="20" required bind:value={settings.maximum} /></label>
|
||||
@@ -82,15 +82,16 @@
|
||||
<label>Empty duration in minutes<input type="number" min="1" max="1440" required bind:value={settings.emptyMinutes} /></label>
|
||||
<label>Action cooldown in minutes<input type="number" min="1" max="1440" required bind:value={settings.cooldownMinutes} /></label>
|
||||
</div>
|
||||
{#if settings.mapSlug === '*'}<p>Targets apply separately to every map, including all variants. Each map needs its own keys. Actions share one cooldown and run one at a time across the game.</p>{/if}
|
||||
<div class="actions"><Button type="submit" disabled={busy}>Preview changes</Button><Button onclick={() => editorOpen = false}>Close editor</Button></div>
|
||||
</fieldset>
|
||||
</form>
|
||||
{#if preview}
|
||||
<div class="preview" aria-live="polite">
|
||||
<h3>Preview · {modeLabel(preview.policy.mode)}</h3>
|
||||
<p>Target: {preview.decision.desired} running lobbies. {preview.snapshot.keyedNames.length} saved lobby keys. {preview.snapshot.members.length} existing or pending lobbies.</p>
|
||||
<p>Target: {preview.decision.desired} running lobbies{preview.policy.mapSlug === '*' ? ' across all maps' : ''}. {preview.snapshot.keyedNames.length} saved lobby keys. {preview.snapshot.members.length} existing or pending lobbies.</p>
|
||||
<p>{preview.decision.action === 'wait' ? 'Wait' : `${preview.decision.action === 'create' ? 'Create' : 'Remove'} ${preview.decision.server}`}: {preview.decision.reason}</p>
|
||||
{#if preview.policy.mode === 'create-only' || preview.policy.mode === 'create-destroy'}<p>Saving gives this policy exclusive control of the map's lobby count. Pause it before launching a competing collection or manual lifecycle action.</p>{/if}
|
||||
{#if preview.policy.mode === 'create-only' || preview.policy.mode === 'create-destroy'}<p>Saving gives this policy exclusive control of the selected maps' lobby counts. Pause it before launching a competing collection or manual lifecycle action.</p>{/if}
|
||||
{#if preview.snapshot.members.length}<ul>{#each preview.snapshot.members as member}<li>{member.name} · {member.state}{member.blocker ? ` · ${member.blocker}` : ''}</li>{/each}</ul>{/if}
|
||||
{#each [...preview.blockers, ...preview.snapshot.blockers] as blocker}<p>{blocker}</p>{/each}
|
||||
<Button variant="primary" disabled={busy || preview.blockers.length > 0} onclick={() => submit('save')}>Save reviewed policy</Button>
|
||||
@@ -98,13 +99,13 @@
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
{#if !report.policies.length}<p class="panel">No autoscaling policies. Add a policy to observe a map in dry run.</p>{/if}
|
||||
{#if !report.policies.length}<p class="panel">No autoscaling policies. Add a policy to observe a map or a whole game in dry run.</p>{/if}
|
||||
{#each report.policies as view (view.policy.id)}
|
||||
<section class="panel" id={view.policy.id} aria-labelledby={`title-${view.policy.id}`}>
|
||||
<div class="heading"><h2 id={`title-${view.policy.id}`}>{zombieMapBySlug(view.policy.game, view.policy.mapSlug)?.friendly ?? view.policy.mapSlug} · {view.policy.game.toUpperCase()}</h2><span>{modeLabel(view.policy.mode)}</span>{#if canOperate}<Button disabled={busy} onclick={() => edit(view.policy)}>Edit or pause</Button>{/if}</div>
|
||||
<div class="heading"><h2 id={`title-${view.policy.id}`}>{view.policy.mapSlug === '*' ? 'Every map' : zombieMapBySlug(view.policy.game, view.policy.mapSlug)?.friendly ?? view.policy.mapSlug} · {view.policy.game.toUpperCase()}</h2><span>{modeLabel(view.policy.mode)}</span>{#if canOperate}<Button disabled={busy} onclick={() => edit(view.policy)}>Edit or pause</Button>{/if}</div>
|
||||
<p class="counts">{view.decision.occupied} occupied · {view.decision.empty} empty and ready · {view.decision.pending} pending · {view.decision.unknown} unknown or stopped</p>
|
||||
{#if view.unsettled.length}<div class="banner"><strong>Work is still settling.</strong>{#each view.unsettled as operation}<p><a href={`/provisioning/${operation.id}`}>{operation.action} · {operation.state}</a> — {operation.stage}</p>{/each}{#if view.ownsMap}<p>This policy retains map ownership until its outstanding work settles.</p>{/if}</div>{/if}
|
||||
<p>Minimum {view.policy.minimum} · Maximum {view.policy.maximum} · Spare target {view.policy.spares} · {view.snapshot.keyedNames.length} saved keys</p>
|
||||
{#if view.unsettled.length}<div class="banner"><strong>Work is still settling.</strong>{#each view.unsettled as operation}<p><a href={`/provisioning/${operation.id}`}>{operation.action} · {operation.state}</a> — {operation.stage}</p>{/each}{#if view.ownsMap}<p>This policy retains ownership of its scope until its outstanding work settles.</p>{/if}</div>{/if}
|
||||
<p>{view.policy.mapSlug === '*' ? 'Per map: ' : ''}Minimum {view.policy.minimum} · Maximum {view.policy.maximum} · Spare target {view.policy.spares} · {view.snapshot.keyedNames.length} saved keys</p>
|
||||
<p><strong>{view.policy.mode === 'dry-run' ? 'Hypothetical decision: ' : 'Current decision: '}</strong>{view.decision.action}{view.decision.server ? ` ${view.decision.server}` : ''}. {view.decision.reason}</p>
|
||||
{#if view.policy.mode === 'create-only' && view.decision.action === 'destroy'}<p>Removal is advisory in Create only mode.</p>{/if}
|
||||
<p class="muted">Last evaluated: {view.lastEvaluatedAt ? $timeFormat.dateTime(new Date(view.lastEvaluatedAt).toISOString()) : 'Not evaluated yet'}{view.policy.nextActionAt > Date.now() ? ` · Next action after ${$timeFormat.dateTime(new Date(view.policy.nextActionAt).toISOString())}` : ''}</p>
|
||||
|
||||
Reference in New Issue
Block a user