sorting+modal fix
Deploy / deploy (push) Successful in 8s

This commit is contained in:
2026-08-31 23:09:35 +05:00
parent 28702bd029
commit b714c00b27
2 changed files with 30 additions and 6 deletions
+15 -1
View File
@@ -30,7 +30,21 @@ func (s *server) services(ctx context.Context) []*homelabv1.Service {
if s.mockSvcs != nil { if s.mockSvcs != nil {
return s.mockSvcs 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, // logProbeChange logs poll-path probe failures once per change: the poll repeats every 3s per client,
+15 -5
View File
@@ -1,4 +1,4 @@
import { useState } from "preact/hooks"; import { useState, useRef } from "preact/hooks";
import { createClient } from "@connectrpc/connect"; import { createClient } from "@connectrpc/connect";
import { createConnectTransport } from "@connectrpc/connect-web"; import { createConnectTransport } from "@connectrpc/connect-web";
import { Container, Server, X } from "lucide-preact"; import { Container, Server, X } from "lucide-preact";
@@ -63,7 +63,10 @@ function Controls({
class="btn btn-xs btn-outline" class="btn btn-xs btn-outline"
disabled={busy || disabled} disabled={busy || disabled}
title={title} title={title}
onClick={() => onAct(op)} onClick={(e) => {
e.stopPropagation(); // кнопка не должна открывать info-модалку карточки
onAct(op);
}}
> >
{label} {label}
</button> </button>
@@ -94,13 +97,19 @@ export function ServiceCard({
const [info, setInfo] = useState<string | null>(null); const [info, setInfo] = useState<string | null>(null);
const [infoBusy, setInfoBusy] = useState(false); const [infoBusy, setInfoBusy] = useState(false);
const infoOpen = useRef(false);
const closeInfo = () => {
infoOpen.current = false;
setInfo(null);
};
const act = async (op: ServiceOp) => { const act = async (op: ServiceOp) => {
setBusy(true); setBusy(true);
setError(""); setError("");
try { try {
await client.serviceAction({ name: s.name, kind: s.kind, op }); await client.serviceAction({ name: s.name, kind: s.kind, op });
refresh(); // не ждём 3s поллинга refresh(); // не ждём 3s поллинга
if (info !== null) { if (infoOpen.current) {
const r = await client.serviceInfo({ name: s.name, kind: s.kind }); const r = await client.serviceInfo({ name: s.name, kind: s.kind });
setInfo(r.output); setInfo(r.output);
} }
@@ -114,6 +123,7 @@ export function ServiceCard({
const openInfo = async () => { const openInfo = async () => {
setInfoBusy(true); setInfoBusy(true);
setError(""); setError("");
infoOpen.current = true;
try { try {
setInfo( setInfo(
(await client.serviceInfo({ name: s.name, kind: s.kind })).output, (await client.serviceInfo({ name: s.name, kind: s.kind })).output,
@@ -160,7 +170,7 @@ export function ServiceCard({
<div class="modal-box max-w-2xl"> <div class="modal-box max-w-2xl">
<button <button
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
onClick={() => setInfo(null)} onClick={closeInfo}
> >
<X size={16} /> <X size={16} />
</button> </button>
@@ -174,7 +184,7 @@ export function ServiceCard({
{infoBusy ? "…" : info} {infoBusy ? "…" : info}
</pre> </pre>
</div> </div>
<div class="modal-backdrop" onClick={() => setInfo(null)} /> <div class="modal-backdrop" onClick={closeInfo} />
</div> </div>
)} )}
</div> </div>