Files
client/templates/pages/releases.html.jinja
2026-03-07 22:53:48 +01:00

153 lines
12 KiB
Django/Jinja

{% extends "base.html.jinja" %}
{% block content %}
<section class="max-w-5xl mx-auto px-4 py-12">
<div class="flex items-center justify-between mb-8">
<h1 class="text-2xl font-bold">Continuous deployment</h1>
</div>
{% if timeline | length > 0 %}
<swim-lanes>
{# ── Environment swim lanes ──────────────────────────────── #}
{% for lane in lanes %}
<div data-lane="{{ lane.name }}"></div>
{% endfor %}
{# ── Release timeline ─────────────────────────────────────── #}
<div data-swimlane-timeline class="flex-1 space-y-3 min-w-0 ml-2">
{% for item in timeline %}
{% if item.kind == "release" %}
{# ── Visible release card ──────────────────────────────── #}
{% set release = item.release %}
<div data-release data-envs="{{ release.dest_envs }}" class="border border-gray-200 rounded-lg overflow-hidden">
{# Release header #}
<div class="px-4 py-3 flex items-center gap-3 flex-wrap">
<div class="flex items-center gap-2 min-w-0 flex-1">
<span class="inline-block w-6 h-6 rounded-full bg-gray-200 shrink-0" data-avatar></span>
<a href="/orgs/{{ org_name }}/projects/{{ release.project_name }}/releases/{{ release.slug }}" class="font-medium text-gray-900 hover:text-black truncate">
{{ release.title }}
</a>
</div>
<div class="flex items-center gap-4 text-xs text-gray-500 shrink-0 flex-wrap">
{% if release.branch %}
<span class="flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A2 2 0 013 12V7a4 4 0 014-4z"/></svg>
{{ release.branch }}
</span>
{% endif %}
{% if release.commit_sha %}
<span class="font-mono">{{ release.commit_sha[:7] }}</span>
{% endif %}
<span title="{{ release.created_at | datetime }}">{{ release.created_at | timeago }}</span>
{% if release.source_user %}
<span class="flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
{{ release.source_user }}
</span>
{% endif %}
<span class="text-gray-400">
<a href="/orgs/{{ org_name }}/projects/{{ release.project_name }}" class="hover:text-gray-700">{{ release.project_name }}</a>
</span>
</div>
</div>
{# Deployment steps (collapsed by default) #}
<details class="border-t border-gray-100 group">
<summary class="px-4 py-2 flex items-center gap-2 text-sm cursor-pointer list-none hover:bg-gray-50">
<svg class="w-4 h-4 text-green-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<span class="text-gray-600 text-sm">Deployed to</span>
{% for dest in release.destinations %}
<span class="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full {% if 'prod' in dest.environment and 'preprod' not in dest.environment %}bg-pink-100 text-pink-800{% elif 'preprod' in dest.environment or 'pre-prod' in dest.environment %}bg-orange-100 text-orange-800{% elif 'stag' in dest.environment %}bg-yellow-100 text-yellow-800{% elif 'dev' in dest.environment %}bg-violet-100 text-violet-800{% else %}bg-gray-100 text-gray-700{% endif %}">
{{ dest.environment }}
<span class="w-1.5 h-1.5 rounded-full {% if 'prod' in dest.environment and 'preprod' not in dest.environment %}bg-pink-500{% elif 'preprod' in dest.environment or 'pre-prod' in dest.environment %}bg-orange-500{% elif 'stag' in dest.environment %}bg-yellow-500{% elif 'dev' in dest.environment %}bg-violet-500{% else %}bg-gray-400{% endif %}"></span>
</span>
{% endfor %}
<svg class="w-3 h-3 text-gray-400 shrink-0 ml-auto transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
</summary>
{% for dest in release.destinations %}
<div class="px-4 py-2 flex items-center gap-3 text-sm {% if not loop.last %}border-b border-gray-50{% endif %} border-t border-gray-50">
<svg class="w-4 h-4 text-green-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
<span class="text-gray-600">Deployed to</span>
<span class="inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full {% if 'prod' in dest.environment and 'preprod' not in dest.environment %}bg-pink-100 text-pink-800{% elif 'preprod' in dest.environment or 'pre-prod' in dest.environment %}bg-orange-100 text-orange-800{% elif 'stag' in dest.environment %}bg-yellow-100 text-yellow-800{% elif 'dev' in dest.environment %}bg-violet-100 text-violet-800{% else %}bg-gray-100 text-gray-700{% endif %}">
{{ dest.environment }}
<span class="w-1.5 h-1.5 rounded-full {% if 'prod' in dest.environment and 'preprod' not in dest.environment %}bg-pink-500{% elif 'preprod' in dest.environment or 'pre-prod' in dest.environment %}bg-orange-500{% elif 'stag' in dest.environment %}bg-yellow-500{% elif 'dev' in dest.environment %}bg-violet-500{% else %}bg-gray-400{% endif %}"></span>
</span>
<span class="text-gray-400 text-xs">{{ dest.name }}</span>
{% if dest.type_name %}
<span class="text-gray-400 text-xs">({{ dest.type_name }}{% if dest.type_version %} v{{ dest.type_version }}{% endif %})</span>
{% endif %}
</div>
{% endfor %}
</details>
</div>
{% elif item.kind == "hidden" %}
{# ── Hidden commits group ──────────────────────────────── #}
<details class="group">
<summary class="flex items-center gap-2 py-2 px-1 text-sm text-gray-400 cursor-pointer hover:text-gray-600 list-none">
<svg class="w-3 h-3 transition-transform group-open:rotate-90" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>
{{ item.count }} hidden commit{{ "s" if item.count != 1 }}
<span class="text-gray-300">&middot;</span>
<span class="group-open:hidden">Show commit{{ "s" if item.count != 1 }}</span>
<span class="hidden group-open:inline">Hide commit{{ "s" if item.count != 1 }}</span>
</summary>
<div class="space-y-3 mt-1">
{% for release in item.releases %}
<div data-release data-envs="" class="border border-gray-200 rounded-lg overflow-hidden opacity-75">
<div class="px-4 py-3 flex items-center gap-3 flex-wrap">
<div class="flex items-center gap-2 min-w-0 flex-1">
<span class="inline-block w-6 h-6 rounded-full bg-gray-200 shrink-0" data-avatar></span>
<a href="/orgs/{{ org_name }}/projects/{{ release.project_name }}/releases/{{ release.slug }}" class="font-medium text-gray-900 hover:text-black truncate">
{{ release.title }}
</a>
</div>
<div class="flex items-center gap-4 text-xs text-gray-500 shrink-0 flex-wrap">
{% if release.commit_sha %}
<span class="font-mono">{{ release.commit_sha[:7] }}</span>
{% endif %}
<span title="{{ release.created_at | datetime }}">{{ release.created_at | timeago }}</span>
{% if release.source_user %}
<span class="flex items-center gap-1">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/></svg>
{{ release.source_user }}
</span>
{% endif %}
<span class="text-gray-400">
<a href="/orgs/{{ org_name }}/projects/{{ release.project_name }}" class="hover:text-gray-700">{{ release.project_name }}</a>
</span>
</div>
</div>
</div>
{% endfor %}
</div>
</details>
{% endif %}
{% endfor %}
</div>
</swim-lanes>
{% else %}
{# ── Empty state ──────────────────────────────────────────── #}
<div class="border border-gray-200 rounded-lg p-12 text-center">
<div class="max-w-md mx-auto">
<div class="w-12 h-12 mx-auto mb-4 rounded-full bg-gray-100 flex items-center justify-center">
<svg class="w-6 h-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/></svg>
</div>
<p class="font-medium text-gray-900 mb-1">No releases yet</p>
<p class="text-sm text-gray-500 mb-6">Releases appear when you deploy with Forest CLI.</p>
<div class="bg-gray-50 rounded-lg p-4 text-left">
<p class="text-xs font-medium text-gray-700 mb-2">Get started with the CLI:</p>
<pre class="text-xs text-gray-600 overflow-x-auto"><code>forest release create \
--org {{ org_name }} \
--project my-project \
--dest staging:my-service</code></pre>
</div>
</div>
</div>
{% endif %}
</section>
<script src="/static/js/swim-lanes.js"></script>
{% endblock %}