left4me/l4d2web/templates/blueprint_detail.html
mwiegand 985df970f8
feat(l4d2-web): per-overlay server.cfg aliases — expose checkbox + auto-exec
Each linked overlay gets a checkbox on the blueprint detail page that opts
its server.cfg in as exec server_overlay_<id>. The web app builds the
spec with {path, alias} per overlay and prepends exec server_overlay_<id>
lines to the blueprint config in lowest-overlay-first order. The host
stages those copies in the overlayfs upper layer before mounting (avoids
copy-up writes against a sandbox-uid file). A live preview block above the
Config textarea shows what gets auto-executed.

Schema:
- alembic 0007: BlueprintOverlay.expose_server_cfg BOOLEAN

Spec contract:
- l4d2host OverlayRef(path, alias?). load_spec accepts both bare-string
  and {path, alias} entries.

Side effects folded in (same file in l4d2_facade):
- start_server auto-initializes; the manual Initialize step is no longer
  needed before Start.
- initialize_server no longer runs blueprint builders — builds happen on
  overlay save, not on every server Start.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 01:26:31 +02:00

80 lines
4.1 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends "base.html" %}
{% block title %}Blueprint {{ blueprint.name }} | left4me{% endblock %}
{% block content %}
<section class="panel">
<div class="page-heading">
<h1>Blueprint: {{ blueprint.name }}</h1>
</div>
<form method="post" action="/blueprints/{{ blueprint.id }}" class="stack">
<input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}">
<label><span class="section-title">Name</span><input name="name" value="{{ blueprint.name }}" required></label>
<label><span class="section-title">Arguments</span><textarea name="arguments" rows="2" spellcheck="false">{{ arguments | join('\n') }}</textarea></label>
<span class="section-title">Overlays</span>
<div class="overlay-picker">
<ol class="overlay-picker-list" data-overlay-list>
{% for overlay in selected_overlays %}
<li class="overlay-picker-row" draggable="true" data-overlay-id="{{ overlay.id }}" data-overlay-name="{{ overlay.name }}">
<span class="overlay-picker-handle" aria-hidden="true">⋮⋮</span>
<span class="overlay-picker-name">{{ overlay.name }}</span>
<label class="overlay-picker-expose" title="Auto-load this overlay's server.cfg before your blueprint config">
<input type="checkbox" name="expose_server_cfg_ids" value="{{ overlay.id }}"
{% if overlay_expose_state.get(overlay.id) %}checked{% endif %}>
exec <code>server.cfg</code>
</label>
<button type="button" class="overlay-picker-remove" data-action="remove" aria-label="Remove {{ overlay.name }}">×</button>
<input type="hidden" name="overlay_ids" value="{{ overlay.id }}">
</li>
{% endfor %}
</ol>
<p class="overlay-picker-empty muted" data-overlay-empty {% if selected_overlays %}hidden{% endif %}>No overlays selected. Pick one below to add.</p>
<div class="overlay-picker-add">
<select data-overlay-add aria-label="Add overlay">
<option value="">Add overlay…</option>
{% for overlay in available_overlays %}
<option value="{{ overlay.id }}" data-overlay-name="{{ overlay.name }}">{{ overlay.name }}</option>
{% endfor %}
</select>
</div>
</div>
{% set exposed = [] %}
{# Source `exec` is last-wins. First overlay in the list = topmost =
highest precedence, so its exec runs LAST. Iterate the picker list in
reverse to render the preview in actual execution order. #}
{% for overlay in selected_overlays | reverse %}{% if overlay_expose_state.get(overlay.id) %}{{ exposed.append(overlay) or '' }}{% endif %}{% endfor %}
<label><span class="section-title">Config</span>
<div class="config-shell">
{% if exposed %}
<pre class="config-preview" aria-label="Auto-loaded overlay configs">{% for o in exposed %}exec {{ o.name }}.cfg
{% endfor %}</pre>
{% endif %}
<textarea name="config" rows="8" spellcheck="false">{{ config_lines | join('\n') }}</textarea>
</div>
</label>
<button type="submit">Save blueprint</button>
</form>
</section>
<div class="page-footer-actions">
<button type="button" class="danger-outline" data-modal-open="delete-blueprint-modal">Delete blueprint</button>
</div>
<dialog id="delete-blueprint-modal" class="modal" aria-labelledby="delete-blueprint-title">
<div class="modal-header">
<h2 id="delete-blueprint-title">Delete blueprint "{{ blueprint.name }}"?</h2>
<button type="button" class="modal-close" data-modal-close aria-label="Close">&times;</button>
</div>
<div class="modal-body">
<p>This cannot be undone. Blueprints in use by a server cannot be deleted.</p>
</div>
<div class="modal-footer">
<button type="button" class="button-secondary" data-modal-close>Cancel</button>
<form method="post" action="/blueprints/{{ blueprint.id }}/delete" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ session.get('csrf_token', '') }}">
<button class="danger" type="submit">Delete</button>
</form>
</div>
</dialog>
<script src="{{ url_for('static', filename='js/blueprint-overlay-picker.js') }}" defer></script>
{% endblock %}