diff --git a/server/services.go b/server/services.go index 69a9cc5..80903f8 100644 --- a/server/services.go +++ b/server/services.go @@ -30,7 +30,21 @@ func (s *server) services(ctx context.Context) []*homelabv1.Service { if s.mockSvcs != nil { return s.mockSvcs } - return append(s.systemdServices(ctx), s.dockerServices(ctx)...) + svcs := append(s.systemdServices(ctx), s.dockerServices(ctx)...) + // живые (active/partial) наверх, остальное (inactive, not-found, failed, error) вниз; внутри групп — по имени + rank := func(svc *homelabv1.Service) int { + if svc.Error == "" && (svc.State == "active" || svc.State == "partial") { + return 0 + } + return 1 + } + sort.SliceStable(svcs, func(i, j int) bool { + if ri, rj := rank(svcs[i]), rank(svcs[j]); ri != rj { + return ri < rj + } + return svcs[i].Name < svcs[j].Name + }) + return svcs } // logProbeChange logs poll-path probe failures once per change: the poll repeats every 3s per client, diff --git a/web/src/components/ServiceCard.tsx b/web/src/components/ServiceCard.tsx index f6e4ed0..1c0aa7b 100644 --- a/web/src/components/ServiceCard.tsx +++ b/web/src/components/ServiceCard.tsx @@ -1,4 +1,4 @@ -import { useState } from "preact/hooks"; +import { useState, useRef } from "preact/hooks"; import { createClient } from "@connectrpc/connect"; import { createConnectTransport } from "@connectrpc/connect-web"; import { Container, Server, X } from "lucide-preact"; @@ -63,7 +63,10 @@ function Controls({ class="btn btn-xs btn-outline" disabled={busy || disabled} title={title} - onClick={() => onAct(op)} + onClick={(e) => { + e.stopPropagation(); // кнопка не должна открывать info-модалку карточки + onAct(op); + }} > {label} @@ -94,13 +97,19 @@ export function ServiceCard({ const [info, setInfo] = useState(null); const [infoBusy, setInfoBusy] = useState(false); + const infoOpen = useRef(false); + const closeInfo = () => { + infoOpen.current = false; + setInfo(null); + }; + const act = async (op: ServiceOp) => { setBusy(true); setError(""); try { await client.serviceAction({ name: s.name, kind: s.kind, op }); refresh(); // не ждём 3s поллинга - if (info !== null) { + if (infoOpen.current) { const r = await client.serviceInfo({ name: s.name, kind: s.kind }); setInfo(r.output); } @@ -114,6 +123,7 @@ export function ServiceCard({ const openInfo = async () => { setInfoBusy(true); setError(""); + infoOpen.current = true; try { setInfo( (await client.serviceInfo({ name: s.name, kind: s.kind })).output, @@ -160,7 +170,7 @@ export function ServiceCard({ -