fix: count tag-matched servers in the workflows list
This commit is contained in:
@@ -8,6 +8,7 @@ import { api, Workflow, WorkflowStep, WorkflowStepRef, SecretGroupSummary } from
|
||||
import { Button } from "@/components/ui";
|
||||
import { EditWorkflowModal } from "@/components/workflows/EditWorkflowModal";
|
||||
import { StepPickerModal } from "@/components/workflows/StepPickerModal";
|
||||
import { resolveTargets } from "@/lib/targets";
|
||||
|
||||
const inputClass = "w-full rounded-lg border border-border bg-surface-2 px-3 py-2 text-sm text-text-primary placeholder-text-secondary/50 focus:border-signal focus:outline-none focus:ring-1 focus:ring-signal";
|
||||
|
||||
@@ -125,15 +126,7 @@ export default function WorkflowBuilder() {
|
||||
|
||||
const targetTags = wf.target_tags ?? {};
|
||||
|
||||
/*
|
||||
* This is the other half of a deliberate duplication: the authority is
|
||||
* UnionTargets/MatchesTags in server/internal/services/targets.go, and this
|
||||
* only exists so the designer can answer "how many servers?" without a
|
||||
* round trip. It must stay identical in meaning — an EMPTY selector matches
|
||||
* NOTHING (a cleared field must not become a fleet-wide run), and multiple
|
||||
* tag keys AND together. Change one, change both.
|
||||
*/
|
||||
const matched = (servers ?? []).filter((s) => wf.target_server_ids.includes(s.server_id) || (Object.keys(targetTags).length > 0 && Object.entries(targetTags).every(([k, v]) => s.tags?.[k] === v)));
|
||||
const matched = resolveTargets(servers ?? [], wf.target_server_ids, targetTags);
|
||||
|
||||
const sortedSteps = [...wf.steps].sort((a, b) => a.order - b.order);
|
||||
const selectedRef = selected !== null ? sortedSteps[selected] : null;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { api, Workflow } from "@/lib/api";
|
||||
import { Button, Card } from "@/components/ui";
|
||||
import { Table, Thead, Tbody, Tr, Th, Td } from "@/components/ui";
|
||||
import { resolveTargets } from "@/lib/targets";
|
||||
|
||||
export default function WorkflowsPage() {
|
||||
const qc = useQueryClient();
|
||||
@@ -22,6 +23,11 @@ export default function WorkflowsPage() {
|
||||
queryFn: api.listWorkflows,
|
||||
});
|
||||
|
||||
// The fleet, so a tag-only workflow reports the servers it actually reaches.
|
||||
// target_server_ids.length alone reads 0 for one, which is the count of the
|
||||
// half of the selector it does not use.
|
||||
const { data: servers } = useQuery({ queryKey: ["servers"], queryFn: () => api.listServers() });
|
||||
|
||||
const { mutate: create, isPending } = useMutation({
|
||||
mutationFn: () => api.createWorkflow({ name: "Untitled workflow", target_server_ids: [], steps: [] }),
|
||||
onSuccess: (workflow) => {
|
||||
@@ -75,9 +81,25 @@ export default function WorkflowsPage() {
|
||||
<span className="font-medium text-text-primary">{w.name}</span>
|
||||
</Td>
|
||||
<Td label="Targets">
|
||||
<span className="text-text-secondary">
|
||||
{w.target_server_ids.length} server{w.target_server_ids.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
{servers ? (
|
||||
(() => {
|
||||
const matched = resolveTargets(servers, w.target_server_ids, w.target_tags ?? {});
|
||||
return (
|
||||
<span className="flex flex-wrap items-center gap-1.5">
|
||||
<span className={matched.length === 0 ? "text-danger" : "text-text-secondary"} title={matched.map((s) => s.hostname).join("\n")}>
|
||||
{matched.length} server{matched.length !== 1 ? "s" : ""}
|
||||
</span>
|
||||
{Object.entries(w.target_tags ?? {}).map(([k, v]) => (
|
||||
<span key={k} className="rounded-sm border border-border px-1.5 py-0.5 font-mono text-[10px] text-text-tertiary">
|
||||
{k}:{v}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
);
|
||||
})()
|
||||
) : (
|
||||
<span className="text-text-tertiary">…</span>
|
||||
)}
|
||||
</Td>
|
||||
<Td label="Steps">
|
||||
<span className="text-text-secondary">{w.steps.length}</span>
|
||||
|
||||
Reference in New Issue
Block a user