+15
-1
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user