{% extends "admin/_layout.html" %}
{% from "_macros.html" import icon %}
{% set section = "updates" %}

{% block title %}Updates - {{ brand.name }}{% endblock %}
{% block heading %}Updates{% endblock %}

{% block admin_content %}
<p class="admin-lede">
  This host follows the
  <strong>{{ "stable" if state.channel == "stable" else "edge" }}</strong>
  channel{% if state.channel == "stable" %} — the newest release tag{% else %} —
  the tip of <code>{{ state.branch }}</code>, which is whatever was pushed most
  recently and may be half finished{% endif %}.
  Checking reaches the remote; opening this page does not.
</p>

{% if saved %}
<div class="alert alert--success">{{ icon("check", "icon--sm") }} <span>{{ saved }}</span></div>
{% endif %}
{% if state.error %}
<div class="alert alert--error">{{ icon("warning", "icon--sm") }} <span>{{ state.error }}</span></div>
{% endif %}

<section class="card">
  <h2 class="card__title">Running</h2>
  <dl class="mode-list">
    <div class="mode-list__row">
      <dt><strong>Version</strong></dt>
      <dd>
        {#
          `git describe`, not the version string: "1.0.0" exactly at a tag and
          "1.0.0-7-gd4f56d" seven commits past one. The second is the honest
          answer for an edge instance, where the version string alone would claim
          to be a release it is not.
        #}
        <strong>{{ state.running or state.version }}</strong>
        {% if state.running and state.running != state.version %}
        <span class="faint text-xs">(reports {{ state.version }})</span>
        {% endif %}
      </dd>
    </div>
    {% if state.head %}
    <div class="mode-list__row">
      <dt><strong>Commit</strong></dt>
      <dd>
        <code>{{ state.head.short }}</code> {{ state.head.subject }}
        {% if state.dirty %}<span class="badge">uncommitted changes</span>{% endif %}
      </dd>
    </div>
    {% endif %}
    {% if state.checkout %}
    <div class="mode-list__row">
      <dt><strong>Checkout</strong></dt>
      <dd><code>{{ state.checkout }}</code></dd>
    </div>
    {% endif %}
  </dl>

  {% if state.version_mismatch %}
  <div class="alert alert--warning">
    {{ icon("warning", "icon--sm") }}
    <span>
      This is tagged <strong>{{ state.version_mismatch }}</strong> but reports
      version <strong>{{ state.version }}</strong>. A tag cut before the version
      bump names a release nobody can identify afterwards.
    </span>
  </div>
  {% endif %}

  {% if not state.is_git %}
  <p class="field__hint">
    This was not installed from a git checkout — a container image, or a wheel —
    so there is nothing here to compare or update. Pull a new image instead.
  </p>
  {% else %}
  <form method="post" action="/admin/updates/check" class="btn-row">
    <button class="btn" type="submit">
      {{ icon("globe", "icon--sm") }} Check the remote
    </button>
  </form>
  {% endif %}
</section>

{% if state.is_git %}
<section class="card">
  <h2 class="card__title">
    Available
    {% if state.no_releases %}<span class="badge">nothing released yet</span>
    {% elif state.up_to_date %}<span class="badge badge--leaf">up to date</span>
    {% elif state.behind %}<span class="badge">{{ state.behind | length }} commit{{ '' if state.behind|length == 1 else 's' }} behind</span>
    {% endif %}
  </h2>

  {% if state.no_releases %}
  <p class="field__hint">
    Nothing on <code>{{ state.branch }}</code> has been tagged as a release yet,
    so the stable channel has nothing to offer. Switch this host to
    <code>edge</code> in <code>lembas.env</code> to follow the branch instead.
  </p>

  {% elif not state.available %}
  <p class="field__hint">
    Nothing known about the remote yet. Check it above.
  </p>

  {% elif state.up_to_date %}
  <p class="field__hint">
    <strong>{{ state.available.label }}</strong> is what is running.
  </p>

  {% else %}
  <p class="card__lede">
    <strong>{{ state.available.label }}</strong>
    <span class="faint">— {{ state.available.subject }}</span>
  </p>

  {#
    Release notes, out of the annotated tag itself. Escaped and preformatted,
    never through services/markdown.py: this comes from a tag object rather than
    from a template, and markdown is the one path allowed to emit HTML.
  #}
  {% if state.available.notes %}
  <pre class="tool-result__text">{{ state.available.notes }}</pre>
  {% endif %}

  {#
    The commits between, not just a count. "3 behind" is a number somebody has to
    go and look up; the subjects are what decides whether this is worth
    restarting for right now.
  #}
  {% if state.behind %}
  <details>
    <summary class="text-sm">
      {{ state.behind | length }} commit{{ '' if state.behind|length == 1 else 's' }} between
    </summary>
    <ul class="model-list">
      {% for commit in state.behind %}
      <li class="model-list__item">
        <div style="min-width: 0">
          <code class="text-xs">{{ commit.short }}</code> {{ commit.subject }}
        </div>
      </li>
      {% endfor %}
    </ul>
  </details>
  {% endif %}

  {% if state.dirty %}
  <div class="alert alert--warning">
    {{ icon("warning", "icon--sm") }}
    <span>
      This checkout has uncommitted changes, and updating discards them.
      Nothing here is meant to be edited in place, so this usually means somebody
      was debugging on the box.
    </span>
  </div>
  {% endif %}
  {% endif %}
</section>

{#
  Its own card, and rendered whether or not there is anything to apply. It used
  to live inside the "there is an update" branch, so an administrator could not
  find out that the helper was missing until the day they needed it -- which is
  the worst moment to discover a thing has to be installed from a shell.
#}
<section class="card">
  <h2 class="card__title">Applying an update</h2>

  {% if state.requested %}
  <div class="alert">
    {{ icon("clock", "icon--sm") }}
    <span>
      An update has been requested and is waiting for the helper to pick it up.
      The service restarts when it does.
    </span>
  </div>
  <form method="post" action="/admin/updates/cancel" class="btn-row">
    <button class="btn btn--sm" type="submit">Withdraw the request</button>
  </form>

  {% elif state.helper %}
  {% if state.channel_mismatch %}
  {#
    The two declarations have drifted. Worth its own alert rather than a note:
    the button would deploy something other than what this page has been naming
    all the way down, and nothing else anywhere would say so.
  #}
  <div class="alert alert--error">
    {{ icon("warning", "icon--sm") }}
    <span>
      This page follows <strong>{{ state.channel }}</strong> but the update
      helper on this host was installed for
      <strong>{{ state.helper_channel }}</strong> — so the button would deploy
      the second, not the first. They are set in two places
      (<code>lembas.env</code> and the systemd unit); re-run the installer with
      <code>INSTALL_UPDATE_HELPER=1</code> and the channel you want, which
      writes both.
    </span>
  </div>
  {% endif %}
  <p class="card__lede">
    This host has the update helper installed, so the button below writes a
    request that a systemd unit picks up and runs as root. It always deploys the
    <strong>{{ state.channel }}</strong> channel — the request carries no ref and
    no channel, so pressing it is never “deploy something else”.
  </p>
  <form method="post" action="/admin/updates/apply" class="btn-row"
        data-confirm="Update and restart? Replies being written are saved; open terminals are cut off.">
    <button class="btn btn--primary" type="submit"
            {{ 'disabled' if state.up_to_date or state.no_releases }}>
      {{ icon("sparkle", "icon--sm") }}
      {% if state.no_releases %}Nothing released
      {% elif state.up_to_date %}Nothing to apply
      {% else %}Update to {{ state.available.label }} and restart{% endif %}
    </button>
  </form>
  <p class="field__hint">
    Every restart ends every open terminal session — a command still running on
    the far side is cut off. A reply being written is saved with whatever it has.
  </p>

  {% else %}
  <div class="alert">
    {{ icon("shield", "icon--sm") }}
    <span>
      The update helper is <strong>not installed on this host</strong>, so there
      is no button. That is the honest default: the service runs as an
      unprivileged account and cannot restart itself, and a web application that
      <em>can</em> is one whose worst day is much worse. Run this instead:
      <code>{{ command }}</code>
    </span>
  </div>
  <p class="field__hint">
    To install it, re-run the installer with
    <code>INSTALL_UPDATE_HELPER=1</code>. Doing so means anybody who can
    administer this web interface can deploy the <code>{{ state.channel }}</code>
    channel — which is the point, and worth deciding on purpose rather than
    arriving at.
  </p>
  {% endif %}
</section>
{% endif %}
{% endblock %}
