295 lines
13 KiB
HTML
Raw Normal View History

2026-03-13 00:18:14 +00:00
{% extends "base.html" %}
{% block title %}Projects — Ontoref{% endblock title %}
{% block head %}
<script>
(function(){
var last = "";
try { last = localStorage.getItem("ontoref-last-project") || ""; } catch(_) {}
if (!last) return;
document.addEventListener("DOMContentLoaded", function() {
var banner = document.getElementById("resume-banner");
var lbl = document.getElementById("resume-label");
if (!banner || !lbl) return;
lbl.textContent = last;
banner.href = "/ui/" + encodeURIComponent(last) + "/";
banner.classList.remove("hidden");
});
})();
</script>
{% endblock head %}
{% block content %}
<!-- Resume last project -->
<a id="resume-banner" href="#" class="hidden mb-4 flex items-center gap-2 px-4 py-2.5 rounded-lg bg-primary/10 border border-primary/20 hover:bg-primary/20 transition-colors text-sm font-medium">
<svg class="w-4 h-4 text-primary flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
Resume <span id="resume-label" class="font-mono text-primary"></span>
<span class="ml-auto text-xs text-base-content/40">last project</span>
</a>
<div class="mb-6 flex items-center justify-between">
<div>
<h1 class="text-2xl font-bold">Projects</h1>
<p class="text-base-content/50 text-sm mt-1">{{ projects | length }} project{% if projects | length != 1 %}s{% endif %} registered</p>
</div>
<a href="/ui/manage" class="btn btn-sm btn-ghost gap-1.5">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
Manage
</a>
</div>
{% if projects %}
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
{% for p in projects %}
<div class="card bg-base-200 border border-base-content/10">
<!-- Card header: always visible -->
<div class="card-body gap-0 py-4 px-5">
<!-- Title row -->
<div class="flex items-start justify-between gap-2 mb-1">
<div class="flex items-center gap-2 min-w-0">
<a href="/ui/{{ p.slug }}/"
class="card-title text-base font-mono link link-hover text-primary">{{ p.slug }}</a>
{% if p.auth %}
<span class="badge badge-warning badge-xs flex-shrink-0">protected</span>
{% else %}
<span class="badge badge-neutral badge-xs flex-shrink-0">open</span>
{% endif %}
{% if p.default_mode %}
<span class="badge badge-ghost badge-xs flex-shrink-0 font-mono">{{ p.default_mode }}</span>
{% endif %}
{% if p.repo_kind %}
<span class="badge badge-outline badge-xs flex-shrink-0 text-base-content/40">{{ p.repo_kind }}</span>
{% endif %}
</div>
<!-- Quick-access shortcut icons -->
<div class="flex items-center gap-0.5 flex-shrink-0">
<a href="/ui/{{ p.slug }}/search" title="Search"
class="btn btn-ghost btn-xs btn-circle">
<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="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z"/>
</svg>
</a>
<a href="/ui/{{ p.slug }}/backlog" title="Backlog"
class="btn btn-ghost btn-xs btn-circle">
<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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-3 7h3m-3 4h3m-6-4h.01M9 16h.01"/>
</svg>
</a>
<a href="/ui/{{ p.slug }}/" title="Open dashboard"
class="btn btn-ghost btn-xs btn-circle">
<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="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/>
</svg>
</a>
</div>
</div>
{% if p.description %}
<p class="text-sm text-base-content/70 leading-snug mb-1.5">{{ p.description }}</p>
{% endif %}
<p class="text-xs font-mono text-base-content/35 truncate mb-2" title="{{ p.root }}">{{ p.root }}</p>
<!-- Git remotes -->
{% if p.repos %}
<div class="flex flex-wrap gap-1 mb-2">
{% for r in p.repos %}
<a href="{{ r.url }}" target="_blank" rel="noopener"
class="badge badge-xs badge-ghost font-mono gap-1 border border-base-content/10 hover:border-primary/40 hover:text-primary transition-colors"
title="{{ r.url }}">
<svg class="w-2.5 h-2.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/>
</svg>
{{ r.name }}
</a>
{% endfor %}
</div>
{% endif %}
<!-- Showcase + Generated links -->
{% if p.showcase or p.generated %}
<div class="flex flex-wrap gap-1.5 mb-2">
{% for s in p.showcase %}
<a href="{{ s.url }}" target="_blank" rel="noopener"
class="btn btn-xs btn-ghost gap-1 border border-base-content/10">
{% if s.id == "branding" %}🎨{% elif s.id == "web" %}🌐{% elif s.id == "presentation" %}📊{% endif %}
{{ s.label }}
</a>
{% endfor %}
{% for g in p.generated %}
<a href="{{ g.url }}" target="_blank" rel="noopener"
class="btn btn-xs btn-ghost gap-1 border border-base-content/10 opacity-70">
📄 {{ g.label }}
</a>
{% endfor %}
</div>
{% endif %}
<!-- Summary badges -->
<div class="flex flex-wrap gap-1.5 mb-3">
{% if p.session_count > 0 %}
<span class="badge badge-sm badge-success gap-1">
<span class="w-1.5 h-1.5 rounded-full bg-current inline-block"></span>
{{ p.session_count }} session{% if p.session_count != 1 %}s{% endif %}
</span>
{% else %}
<span class="badge badge-sm badge-ghost">no sessions</span>
{% endif %}
{% if p.notif_count > 0 %}
<span class="badge badge-sm badge-warning">{{ p.notif_count }} notif</span>
{% endif %}
{% if p.backlog_open > 0 %}
<span class="badge badge-sm badge-info">{{ p.backlog_open }} open</span>
{% endif %}
{% if p.layers %}
<span class="badge badge-sm badge-ghost">{{ p.layers | length }} layers</span>
{% endif %}
{% if p.op_modes %}
<span class="badge badge-sm badge-ghost">{{ p.op_modes | length }} modes</span>
{% endif %}
</div>
<!-- Accordion panels -->
<div class="space-y-1">
{% if p.layers or p.op_modes %}
<details class="collapse collapse-arrow bg-base-300/40 rounded-lg">
<summary class="collapse-title text-xs font-semibold py-2 min-h-0 px-3 cursor-pointer">
Features & Layers
</summary>
<div class="collapse-content px-3 pb-3">
{% if p.layers %}
<p class="text-xs font-medium text-base-content/40 uppercase tracking-wider mb-1.5">Layers</p>
<div class="space-y-1 mb-3">
{% for l in p.layers %}
<div class="flex gap-2">
<span class="badge badge-xs badge-ghost font-mono flex-shrink-0 mt-0.5">{{ l.id }}</span>
<span class="text-xs text-base-content/70">{{ l.description }}</span>
</div>
{% endfor %}
</div>
{% endif %}
{% if p.op_modes %}
<p class="text-xs font-medium text-base-content/40 uppercase tracking-wider mb-1.5">Operational Modes</p>
<div class="space-y-1">
{% for m in p.op_modes %}
<div class="flex gap-2">
<span class="badge badge-xs badge-primary font-mono flex-shrink-0 mt-0.5">{{ m.id }}</span>
<span class="text-xs text-base-content/70">{{ m.description }}</span>
</div>
{% endfor %}
</div>
{% endif %}
</div>
</details>
{% endif %}
{% if p.backlog_items %}
<details class="collapse collapse-arrow bg-base-300/40 rounded-lg">
<summary class="collapse-title text-xs font-semibold py-2 min-h-0 px-3 cursor-pointer">
Backlog
<span class="badge badge-xs badge-info ml-1">{{ p.backlog_open }} open</span>
</summary>
<div class="collapse-content px-3 pb-3">
<div class="space-y-1.5">
{% for it in p.backlog_items %}
<div class="flex items-start gap-1.5">
<span class="badge badge-xs font-mono flex-shrink-0 mt-0.5
{% if it.status == 'Open' %}badge-info
{% elif it.status == 'Done' %}badge-success
{% else %}badge-ghost{% endif %}">
{{ it.status }}
</span>
<span class="badge badge-xs flex-shrink-0 mt-0.5
{% if it.priority == 'Critical' %}badge-error
{% elif it.priority == 'High' %}badge-warning
{% else %}badge-ghost{% endif %}">
{{ it.priority }}
</span>
<span class="text-xs text-base-content/80 leading-tight">{{ it.title }}</span>
</div>
{% endfor %}
</div>
<a href="/ui/{{ p.slug }}/backlog" class="btn btn-xs btn-ghost mt-2 w-full">
Manage backlog →
</a>
</div>
</details>
{% endif %}
{% if p.sessions %}
<details class="collapse collapse-arrow bg-base-300/40 rounded-lg">
<summary class="collapse-title text-xs font-semibold py-2 min-h-0 px-3 cursor-pointer">
Sessions
<span class="badge badge-xs badge-success ml-1">{{ p.session_count }}</span>
</summary>
<div class="collapse-content px-3 pb-3">
<div class="space-y-1">
{% for s in p.sessions %}
<div class="flex items-center gap-1.5 text-xs">
<span class="badge badge-xs badge-ghost font-mono">{{ s.actor_type }}</span>
<span class="text-base-content/60">{{ s.hostname }}</span>
<span class="text-base-content/30 ml-auto">{{ s.last_seen_ago }}s ago</span>
</div>
{% endfor %}
</div>
</div>
</details>
{% endif %}
{% if p.notifications %}
<details class="collapse collapse-arrow bg-base-300/40 rounded-lg">
<summary class="collapse-title text-xs font-semibold py-2 min-h-0 px-3 cursor-pointer">
Notifications
<span class="badge badge-xs badge-warning ml-1">{{ p.notif_count }}</span>
</summary>
<div class="collapse-content px-3 pb-3">
<div class="space-y-1">
{% for n in p.notifications %}
<div class="flex items-start gap-1.5 text-xs">
<span class="badge badge-xs badge-ghost flex-shrink-0">{{ n.event }}</span>
<span class="text-base-content/60 truncate">
{% if n.files %}
{{ n.files | first }}
{% set fc = n.files | length %}
{% if fc > 1 %} +{{ fc - 1 }}{% endif %}
{% endif %}
</span>
<span class="text-base-content/30 ml-auto flex-shrink-0">{{ n.age_secs }}s</span>
</div>
{% endfor %}
</div>
</div>
</details>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<div class="flex flex-col items-center justify-center py-16 text-base-content/40">
<svg class="w-12 h-12 mb-3 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
</svg>
<p class="text-sm">No projects registered.</p>
<a href="/ui/manage" class="btn btn-sm btn-ghost mt-3">Add a project</a>
</div>
{% endif %}
{% endblock content %}