@@ -144,6 +144,97 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── Policy evaluations (approval, soak, branch) ──────────── #}
|
||||
{% if policy_evaluations | length > 0 %}
|
||||
<div class="mb-8">
|
||||
<h2 class="text-sm font-semibold text-gray-900 mb-3">Policy Requirements</h2>
|
||||
<div class="border border-gray-200 rounded-lg divide-y divide-gray-100">
|
||||
{% for eval in policy_evaluations %}
|
||||
<div class="px-4 py-3">
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
{% if eval.passed %}
|
||||
<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>
|
||||
{% else %}
|
||||
<svg class="w-4 h-4 text-amber-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
{% endif %}
|
||||
|
||||
{% if eval.policy_type == "approval" %}
|
||||
<span class="bg-emerald-100 text-emerald-700 text-xs font-medium px-1.5 py-0.5 rounded">Approval</span>
|
||||
{% elif eval.policy_type == "soak_time" %}
|
||||
<span class="bg-indigo-100 text-indigo-700 text-xs font-medium px-1.5 py-0.5 rounded">Soak Time</span>
|
||||
{% elif eval.policy_type == "branch_restriction" %}
|
||||
<span class="bg-orange-100 text-orange-700 text-xs font-medium px-1.5 py-0.5 rounded">Branch</span>
|
||||
{% endif %}
|
||||
|
||||
<span class="text-gray-600">{{ eval.policy_name }}</span>
|
||||
<span class="text-xs text-gray-400 ml-auto">{{ eval.reason }}</span>
|
||||
</div>
|
||||
|
||||
{# ── Approval UI ──────────────────────────────── #}
|
||||
{% if eval.policy_type == "approval" and eval.approval_state %}
|
||||
<div class="mt-3 ml-7">
|
||||
<div class="flex items-center gap-2 text-xs text-gray-500 mb-2">
|
||||
<span class="font-medium">{{ eval.approval_state.current_approvals }}/{{ eval.approval_state.required_approvals }} approvals</span>
|
||||
</div>
|
||||
|
||||
{% if eval.approval_state.decisions | length > 0 %}
|
||||
<div class="space-y-1 mb-3">
|
||||
{% for d in eval.approval_state.decisions %}
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
{% if d.decision == "approved" %}
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-green-500"></span>
|
||||
<span class="text-green-700 font-medium">{{ d.username }}</span>
|
||||
<span class="text-gray-400">approved</span>
|
||||
{% else %}
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-red-500"></span>
|
||||
<span class="text-red-700 font-medium">{{ d.username }}</span>
|
||||
<span class="text-gray-400">rejected</span>
|
||||
{% endif %}
|
||||
{% if d.comment %}<span class="text-gray-400">— {{ d.comment }}</span>{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not eval.passed %}
|
||||
{% if is_release_author and not is_admin %}
|
||||
<p class="text-xs text-gray-500 italic">You cannot approve your own release.</p>
|
||||
{% else %}
|
||||
<div class="flex items-center gap-2">
|
||||
{% if not is_release_author %}
|
||||
<form method="post" action="/orgs/{{ org_name }}/projects/{{ project_name }}/releases/{{ artifact.slug }}/approve" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="release_intent_id" value="{{ release_intent_id }}">
|
||||
<input type="hidden" name="target_environment" value="{{ eval.target_environment }}">
|
||||
<button type="submit" class="text-xs px-3 py-1.5 rounded-md bg-green-600 text-white hover:bg-green-700 transition-colors">Approve</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if is_release_author and is_admin %}
|
||||
<form method="post" action="/orgs/{{ org_name }}/projects/{{ project_name }}/releases/{{ artifact.slug }}/approve" class="inline" onsubmit="return confirm('You are the release author. This is an admin bypass — are you sure?')">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="release_intent_id" value="{{ release_intent_id }}">
|
||||
<input type="hidden" name="target_environment" value="{{ eval.target_environment }}">
|
||||
<input type="hidden" name="force_bypass" value="true">
|
||||
<button type="submit" class="text-xs px-3 py-1.5 rounded-md bg-red-600 text-white hover:bg-red-700 transition-colors">Bypass (Admin)</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
<form method="post" action="/orgs/{{ org_name }}/projects/{{ project_name }}/releases/{{ artifact.slug }}/reject" class="inline">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token }}">
|
||||
<input type="hidden" name="release_intent_id" value="{{ release_intent_id }}">
|
||||
<input type="hidden" name="target_environment" value="{{ eval.target_environment }}">
|
||||
<button type="submit" class="text-xs px-3 py-1.5 rounded-md border border-red-300 text-red-600 hover:bg-red-50 transition-colors">Reject</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{# ── Destinations with status ──────────────────────────────── #}
|
||||
{% if destinations | length > 0 or configured_destinations | length > 0 %}
|
||||
<div class="mb-8">
|
||||
|
||||
Reference in New Issue
Block a user