services-panel.html

125 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
<!-- Services Panel -->
<div class="bg-white rounded-xl border border-slate-200/60">
    <div class="flex items-center justify-between px-4 pt-3 pb-1">
        <h3 class="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Services</h3>
        <button class="w-6 h-6 inline-flex items-center justify-center rounded text-slate-300 hover:text-indigo-600 hover:bg-indigo-50 transition-colors" onclick="document.getElementById('deploy-modal').showModal()" title="Deploy">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" /></svg>
        </button>
    </div>

    {{if dashboard.AllServices}}
    <div class="px-1 pb-1">
        {{range $svc := dashboard.AllServices}}
        <div class="group flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-slate-50/80 transition-colors">
            {{if eq $svc.Status "running"}}
            <span class="w-2 h-2 rounded-full bg-emerald-500 shrink-0"></span>
            {{else if eq $svc.Status "building"}}
            <span class="w-2 h-2 rounded-full bg-amber-400 animate-pulse shrink-0"></span>
            {{else if eq $svc.Status "failed"}}
            <span class="w-2 h-2 rounded-full bg-red-500 shrink-0"></span>
            {{else}}
            <span class="w-2 h-2 rounded-full bg-slate-300 shrink-0"></span>
            {{end}}
            <a href="/services/{{$svc.ID}}" class="text-[13px] font-medium text-slate-600 truncate flex-1 hover:text-indigo-600 transition-colors">{{$svc.Name}}</a>
            <span class="text-[10px] font-mono text-slate-300 hidden sm:inline">svc-{{$svc.Slug}}</span>
            <span class="text-[10px] font-mono text-slate-400 shrink-0">:{{$svc.Port}}</span>
            <div class="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity shrink-0">
                {{if eq $svc.Status "running"}}
                <button class="w-6 h-6 inline-flex items-center justify-center rounded text-slate-300 hover:text-slate-500 hover:bg-slate-100" hx-post="/services/{{$svc.ID}}/restart" hx-target="body" title="Restart">
                    <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" /></svg>
                </button>
                <button class="w-6 h-6 inline-flex items-center justify-center rounded text-slate-200 hover:text-red-400 hover:bg-red-50" hx-post="/services/{{$svc.ID}}/stop" hx-target="body" title="Stop">
                    <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><rect x="6" y="6" width="12" height="12" rx="1" /></svg>
                </button>
                {{else if eq $svc.Status "stopped"}}
                <button class="w-6 h-6 inline-flex items-center justify-center rounded text-emerald-300 hover:text-emerald-500 hover:bg-emerald-50" hx-post="/services/{{$svc.ID}}/start" hx-target="body" title="Start">
                    <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3" /></svg>
                </button>
                {{end}}
            </div>
        </div>
        {{end}}
    </div>
    {{else}}
    <p class="text-[13px] text-slate-400 text-center py-4 px-4">No services deployed</p>
    {{end}}

    <!-- Infrastructure (collapsible) -->
    {{if dashboard.InfraContainers}}
    <div class="border-t border-slate-100 mx-4"></div>
    <details class="px-4 py-2 pb-3">
        <summary class="flex items-center gap-2 cursor-pointer select-none py-1 group">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3 text-slate-300 transition-transform [details[open]>&]:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
            <span class="text-[10px] font-semibold uppercase tracking-wider text-slate-300 group-hover:text-slate-400 transition-colors">Infrastructure</span>
            <span class="text-[10px] font-mono text-slate-300 bg-slate-50 px-1.5 py-0.5 rounded">{{len dashboard.InfraContainers}}</span>
        </summary>
        <div class="mt-1 ml-5 space-y-0.5">
            {{range $ct := dashboard.InfraContainers}}
            <div class="flex items-center gap-2.5 py-0.5 text-[11px]">
                {{if eq (index $ct "Status") "running"}}
                <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 shrink-0"></span>
                {{else}}
                <span class="w-1.5 h-1.5 rounded-full bg-slate-300 shrink-0"></span>
                {{end}}
                <span class="text-slate-500 font-mono truncate">{{index $ct "Name"}}</span>
                <span class="text-slate-300 font-mono truncate hidden sm:inline ml-auto text-[10px]">{{index $ct "Image"}}</span>
            </div>
            {{end}}
        </div>
    </details>
    {{end}}

    <!-- Routes (collapsible) -->
    {{if dashboard.Routes}}
    <div class="border-t border-slate-100 mx-4"></div>
    <details class="px-4 py-2 pb-3">
        <summary class="flex items-center gap-2 cursor-pointer select-none py-1 group">
            <svg xmlns="http://www.w3.org/2000/svg" class="w-3 h-3 text-slate-300 transition-transform [details[open]>&]:rotate-90" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" /></svg>
            <span class="text-[10px] font-semibold uppercase tracking-wider text-slate-300 group-hover:text-slate-400 transition-colors">Routes</span>
            <span class="text-[10px] font-mono text-slate-300 bg-slate-50 px-1.5 py-0.5 rounded">{{len dashboard.Routes}}</span>
        </summary>
        <div class="mt-1 ml-5 space-y-0.5">
            {{range $route := dashboard.Routes}}
            <div class="flex items-center gap-2.5 py-0.5 text-[11px]">
                {{if $route.Active}}
                <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 shrink-0"></span>
                {{else}}
                <span class="w-1.5 h-1.5 rounded-full bg-slate-300 shrink-0"></span>
                {{end}}
                <span class="text-slate-500 font-mono truncate flex-1">{{$route.Host}}</span>
                <span class="text-slate-300 mx-1">&rarr;</span>
                <span class="text-slate-400 font-mono shrink-0">{{$route.Target}}:{{$route.Port}}</span>
                {{if $route.System}}
                <span class="text-[9px] text-slate-300 border border-slate-200 rounded px-1">sys</span>
                {{end}}
            </div>
            {{end}}
        </div>
    </details>
    {{end}}
</div>

<!-- Deploy Modal -->
<dialog id="deploy-modal" class="modal modal-bottom sm:modal-middle">
    <div class="modal-box bg-white border border-slate-200 shadow-xl max-w-md p-5">
        <h3 class="text-base font-bold text-slate-800 mb-0.5">Deploy App</h3>
        <p class="text-[11px] text-slate-400 mb-4">Build and deploy from a repository</p>
        <form method="dialog"><button class="btn btn-sm btn-circle btn-ghost absolute right-3 top-3 text-slate-300">x</button></form>
        <form hx-post="/services/deploy" hx-target="body">
            <div class="form-control mb-4">
                <label class="label"><span class="label-text text-slate-500 text-xs">Repository</span></label>
                <select name="repo_id" class="select select-bordered w-full" required>
                    <option value="">Select a repository...</option>
                    {{range $repo := dashboard.AvailableRepos}}
                    <option value="{{$repo.ID}}">{{$repo.Name}}</option>
                    {{end}}
                </select>
            </div>
            <div class="flex justify-end gap-2">
                <button type="button" class="btn btn-ghost btn-sm" onclick="document.getElementById('deploy-modal').close()">Cancel</button>
                <button type="submit" class="btn btn-primary btn-sm">Deploy</button>
            </div>
        </form>
    </div>
    <form method="dialog" class="modal-backdrop bg-black/10"><button>close</button></form>
</dialog>