@burdenoff/microfe-adaptercloud 2026.706.2 → 2026.706.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adaptercloud/AdapterCloudRoot.js +52 -44
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
- package/dist/adaptercloud/AdapterCloudRoutes.js +18 -14
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/ConfirmDialog.js.map +1 -1
- package/dist/adaptercloud/components/viz/BarChart.js +32 -0
- package/dist/adaptercloud/components/viz/BarChart.js.map +1 -0
- package/dist/adaptercloud/components/viz/Sparkline.js +50 -0
- package/dist/adaptercloud/components/viz/Sparkline.js.map +1 -0
- package/dist/adaptercloud/components/viz/StackedBar.js +1 -0
- package/dist/adaptercloud/components/viz/TopologyGraph.js +96 -0
- package/dist/adaptercloud/components/viz/TopologyGraph.js.map +1 -0
- package/dist/adaptercloud/components/viz/index.js +4 -0
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +22 -4
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +176 -123
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +261 -179
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +158 -94
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +160 -111
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +163 -84
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +177 -96
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +145 -131
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +10 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +226 -165
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/topology/TopologyPage.js +397 -0
- package/dist/adaptercloud/pages/topology/TopologyPage.js.map +1 -0
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +184 -131
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,218 +1,279 @@
|
|
|
1
1
|
import { useAdapterCloud as e } from "../../context/AdapterCloudContext.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { syncKindLabels as r, syncStatusLabels as i } from "../../utils/domain.js";
|
|
5
|
+
import { StatusBadge as a } from "../../components/StatusBadge.js";
|
|
6
|
+
import { DataState as o } from "../../components/DataState.js";
|
|
7
|
+
import { useCancelSyncRun as s, useSyncRuns as c } from "../../hooks/useAdapterCloudApi.js";
|
|
8
|
+
import { formatDate as l } from "../../utils/formatters.js";
|
|
8
9
|
import { ConfirmDialog as u } from "../../components/ConfirmDialog.js";
|
|
9
10
|
import { useCallback as d, useState as f } from "react";
|
|
10
11
|
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
-
import { Activity as m,
|
|
12
|
-
import { jsx as
|
|
13
|
-
import {
|
|
12
|
+
import { Activity as m, Boxes as h, ChevronLeft as g, ChevronRight as _, Cloud as v, Cpu as y, Database as b, HardDrive as x, Loader2 as S, Network as C, Puzzle as w, Radio as T, Server as E, XCircle as D } from "lucide-react";
|
|
13
|
+
import { jsx as O, jsxs as k } from "react/jsx-runtime";
|
|
14
|
+
import { ResponsiveTable as A } from "@burdenoff/fe-libs/ui";
|
|
14
15
|
//#region src/adaptercloud/pages/sync-runs/SyncRunsListPage.tsx
|
|
15
|
-
var
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
var j = 20, M = {
|
|
17
|
+
PUBLIC_CLOUD: v,
|
|
18
|
+
PRIVATE_CLOUD: v,
|
|
19
|
+
SAAS: v,
|
|
20
|
+
ON_PREM: x,
|
|
21
|
+
EDGE: T,
|
|
22
|
+
IOT: y,
|
|
23
|
+
KUBERNETES: h,
|
|
24
|
+
NETWORK: C,
|
|
25
|
+
DATA_SYSTEM: b,
|
|
26
|
+
CUSTOM: w
|
|
27
|
+
}, N = {
|
|
28
|
+
DISCOVERY: "bg-accent-blue-subtle text-accent-blue",
|
|
29
|
+
RECONCILE: "bg-status-warning-bg-subtle text-status-warning-text",
|
|
30
|
+
COST_INGEST: "bg-accent-purple-subtle text-accent-purple",
|
|
31
|
+
FULL: "bg-bg-sunken text-text-secondary"
|
|
32
|
+
};
|
|
33
|
+
function P({ kind: e }) {
|
|
34
|
+
return /* @__PURE__ */ O("span", {
|
|
35
|
+
className: t("inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium", N[e]),
|
|
36
|
+
children: r[e]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function F(e, t) {
|
|
40
|
+
if (!e || !t) return null;
|
|
41
|
+
let n = new Date(t).getTime() - new Date(e).getTime();
|
|
42
|
+
if (!Number.isFinite(n) || n < 0) return null;
|
|
43
|
+
let r = Math.floor(n / 1e3), i = Math.floor(r / 3600), a = Math.floor(r % 3600 / 60), o = r % 60;
|
|
44
|
+
return i > 0 ? `${i}h ${a}m ${o}s` : a > 0 ? `${a}m ${o}s` : `${o}s`;
|
|
45
|
+
}
|
|
46
|
+
function I() {
|
|
47
|
+
let { navigateTo: t } = e(), { t: r } = p(), h = d((e, t) => {
|
|
48
|
+
let n = r(e);
|
|
19
49
|
return n === e ? t : n;
|
|
20
|
-
}, [
|
|
21
|
-
status:
|
|
50
|
+
}, [r]), [v, y] = f(""), [b, x] = f(0), [C, w] = f(null), [T, N] = f(null), I = c({
|
|
51
|
+
status: v || void 0,
|
|
22
52
|
pagination: {
|
|
23
|
-
limit:
|
|
24
|
-
offset:
|
|
53
|
+
limit: j,
|
|
54
|
+
offset: b
|
|
25
55
|
}
|
|
26
|
-
}),
|
|
27
|
-
|
|
28
|
-
onSuccess: () =>
|
|
29
|
-
onError: (e) =>
|
|
56
|
+
}), L = s(), R = I.data?.items ?? [], z = I.data?.pageInfo.hasMore ?? !1, B = L.isPending ? L.variables : void 0, V = d((e) => {
|
|
57
|
+
w(null), L.mutate(e, {
|
|
58
|
+
onSuccess: () => N(null),
|
|
59
|
+
onError: (e) => w(e instanceof Error ? e.message : "Failed to cancel run.")
|
|
30
60
|
});
|
|
31
|
-
}, [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
}, [L]), H = (e, t) => e.status === "QUEUED" || e.status === "RUNNING" ? /* @__PURE__ */ O("span", {
|
|
62
|
+
className: "text-text-tertiary",
|
|
63
|
+
children: "—"
|
|
64
|
+
}) : /* @__PURE__ */ O("span", {
|
|
65
|
+
className: "tabular-nums text-text-primary",
|
|
66
|
+
children: t
|
|
67
|
+
}), U = [
|
|
68
|
+
{
|
|
69
|
+
key: "kind",
|
|
70
|
+
header: h("adaptercloud.syncRuns.kind", "Kind"),
|
|
71
|
+
priority: "primary",
|
|
72
|
+
cell: (e) => /* @__PURE__ */ O(P, { kind: e.kind })
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
key: "connection",
|
|
76
|
+
header: h("adaptercloud.syncRuns.connection", "Connection"),
|
|
77
|
+
priority: "secondary",
|
|
78
|
+
cell: (e) => {
|
|
79
|
+
let t = e.connection?.adapter?.kind;
|
|
80
|
+
return /* @__PURE__ */ k("div", {
|
|
81
|
+
className: "flex min-w-0 items-center gap-2",
|
|
82
|
+
children: [/* @__PURE__ */ O("span", {
|
|
83
|
+
className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-bg-sunken text-text-secondary",
|
|
84
|
+
children: /* @__PURE__ */ O(t ? M[t] : E, { className: "h-3.5 w-3.5" })
|
|
85
|
+
}), e.connection?.name ? /* @__PURE__ */ O("span", {
|
|
86
|
+
className: "truncate text-sm text-text-primary",
|
|
87
|
+
children: e.connection.name
|
|
88
|
+
}) : /* @__PURE__ */ O("span", {
|
|
89
|
+
className: "text-sm text-text-tertiary",
|
|
90
|
+
children: "—"
|
|
91
|
+
})]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: "status",
|
|
97
|
+
header: h("adaptercloud.syncRuns.status", "Status"),
|
|
98
|
+
priority: "secondary",
|
|
99
|
+
cell: (e) => /* @__PURE__ */ k("div", {
|
|
100
|
+
className: "flex flex-col gap-1",
|
|
101
|
+
children: [/* @__PURE__ */ O(a, {
|
|
102
|
+
status: e.status,
|
|
103
|
+
label: i[e.status]
|
|
104
|
+
}), e.error && /* @__PURE__ */ O("span", {
|
|
105
|
+
className: "max-w-xs truncate text-xs text-status-error-text",
|
|
106
|
+
title: e.error,
|
|
107
|
+
children: e.error
|
|
108
|
+
})]
|
|
109
|
+
})
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
key: "discovered",
|
|
113
|
+
header: h("adaptercloud.syncRuns.discovered", "Discovered"),
|
|
114
|
+
priority: "secondary",
|
|
115
|
+
headerClassName: "text-right",
|
|
116
|
+
cellClassName: "text-right",
|
|
117
|
+
cell: (e) => H(e, e.discoveredCount)
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: "reconciled",
|
|
121
|
+
header: h("adaptercloud.syncRuns.reconciled", "Reconciled"),
|
|
122
|
+
priority: "secondary",
|
|
123
|
+
headerClassName: "text-right",
|
|
124
|
+
cellClassName: "text-right",
|
|
125
|
+
cell: (e) => H(e, e.reconciledCount)
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: "drift",
|
|
129
|
+
header: h("adaptercloud.syncRuns.drift", "Drift"),
|
|
130
|
+
priority: "secondary",
|
|
131
|
+
headerClassName: "text-right",
|
|
132
|
+
cellClassName: "text-right",
|
|
133
|
+
cell: (e) => H(e, e.driftCount)
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: "violations",
|
|
137
|
+
header: h("adaptercloud.syncRuns.violations", "Violations"),
|
|
138
|
+
priority: "secondary",
|
|
139
|
+
headerClassName: "text-right",
|
|
140
|
+
cellClassName: "text-right",
|
|
141
|
+
cell: (e) => H(e, e.violationCount)
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
key: "started",
|
|
145
|
+
header: h("adaptercloud.syncRuns.started", "Started"),
|
|
146
|
+
priority: "secondary",
|
|
147
|
+
cell: (e) => e.startedAt ? /* @__PURE__ */ O("span", {
|
|
148
|
+
className: "whitespace-nowrap text-xs text-text-secondary",
|
|
149
|
+
children: l(e.startedAt)
|
|
150
|
+
}) : /* @__PURE__ */ O("span", {
|
|
151
|
+
className: "text-xs text-text-tertiary",
|
|
152
|
+
children: "—"
|
|
153
|
+
})
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
key: "duration",
|
|
157
|
+
header: h("adaptercloud.syncRuns.duration", "Duration"),
|
|
158
|
+
priority: "secondary",
|
|
159
|
+
headerClassName: "text-right",
|
|
160
|
+
cellClassName: "text-right",
|
|
161
|
+
cell: (e) => {
|
|
162
|
+
let t = F(e.startedAt, e.finishedAt);
|
|
163
|
+
return t ? /* @__PURE__ */ O("span", {
|
|
164
|
+
className: "whitespace-nowrap text-xs tabular-nums text-text-secondary",
|
|
165
|
+
children: t
|
|
166
|
+
}) : /* @__PURE__ */ O("span", {
|
|
167
|
+
className: "text-xs text-text-tertiary",
|
|
168
|
+
children: "—"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
];
|
|
173
|
+
return /* @__PURE__ */ k(n, {
|
|
174
|
+
title: h("adaptercloud.syncRuns.title", "Sync Runs"),
|
|
175
|
+
description: h("adaptercloud.syncRuns.subtitle", "Discovery, reconciliation and cost-ingest executions that keep the picture live."),
|
|
35
176
|
children: [
|
|
36
|
-
/* @__PURE__ */
|
|
177
|
+
/* @__PURE__ */ O("div", {
|
|
37
178
|
className: "mb-6 flex flex-wrap gap-3",
|
|
38
|
-
children: /* @__PURE__ */
|
|
39
|
-
value:
|
|
179
|
+
children: /* @__PURE__ */ k("select", {
|
|
180
|
+
value: v,
|
|
40
181
|
onChange: (e) => {
|
|
41
|
-
|
|
182
|
+
y(e.target.value), x(0);
|
|
42
183
|
},
|
|
43
|
-
"aria-label":
|
|
184
|
+
"aria-label": h("adaptercloud.syncRuns.filterByStatus", "Filter by status"),
|
|
44
185
|
className: "rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]",
|
|
45
|
-
children: [/* @__PURE__ */
|
|
186
|
+
children: [/* @__PURE__ */ O("option", {
|
|
46
187
|
value: "",
|
|
47
|
-
children:
|
|
48
|
-
}), Object.keys(
|
|
188
|
+
children: h("adaptercloud.syncRuns.allStatuses", "All statuses")
|
|
189
|
+
}), Object.keys(i).map((e) => /* @__PURE__ */ O("option", {
|
|
49
190
|
value: e,
|
|
50
|
-
children:
|
|
191
|
+
children: i[e]
|
|
51
192
|
}, e))]
|
|
52
193
|
})
|
|
53
194
|
}),
|
|
54
|
-
|
|
195
|
+
C && /* @__PURE__ */ O("p", {
|
|
55
196
|
role: "alert",
|
|
56
197
|
"aria-live": "polite",
|
|
57
198
|
className: "mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
58
|
-
children:
|
|
199
|
+
children: C
|
|
59
200
|
}),
|
|
60
|
-
/* @__PURE__ */
|
|
61
|
-
isLoading:
|
|
62
|
-
isError:
|
|
63
|
-
isEmpty:
|
|
64
|
-
error:
|
|
65
|
-
onRetry: () => void
|
|
66
|
-
emptyTitle:
|
|
67
|
-
emptyDescription:
|
|
68
|
-
emptyIcon: /* @__PURE__ */
|
|
69
|
-
emptyAction: /* @__PURE__ */
|
|
201
|
+
/* @__PURE__ */ k(o, {
|
|
202
|
+
isLoading: I.isLoading,
|
|
203
|
+
isError: I.isError,
|
|
204
|
+
isEmpty: R.length === 0,
|
|
205
|
+
error: I.error,
|
|
206
|
+
onRetry: () => void I.refetch(),
|
|
207
|
+
emptyTitle: h("adaptercloud.syncRuns.emptyTitle", "No sync runs yet"),
|
|
208
|
+
emptyDescription: h("adaptercloud.syncRuns.emptyDescription", "Start a sync run from a connection to keep the inventory current."),
|
|
209
|
+
emptyIcon: /* @__PURE__ */ O(m, { className: "h-10 w-10" }),
|
|
210
|
+
emptyAction: /* @__PURE__ */ O("button", {
|
|
70
211
|
type: "button",
|
|
71
|
-
onClick: () =>
|
|
212
|
+
onClick: () => t("/connections"),
|
|
72
213
|
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
73
|
-
children:
|
|
214
|
+
children: h("adaptercloud.syncRuns.goToConnections", "Go to connections")
|
|
74
215
|
}),
|
|
75
|
-
children: [/* @__PURE__ */
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
children: n[e.kind]
|
|
91
|
-
}), /* @__PURE__ */ C(i, { status: e.status })]
|
|
92
|
-
}),
|
|
93
|
-
/* @__PURE__ */ w("div", {
|
|
94
|
-
className: "flex flex-wrap items-center gap-4 text-xs text-text-secondary",
|
|
95
|
-
children: [
|
|
96
|
-
/* @__PURE__ */ w("span", {
|
|
97
|
-
className: "flex items-center gap-1",
|
|
98
|
-
children: [
|
|
99
|
-
/* @__PURE__ */ C(b, { className: "h-3.5 w-3.5" }),
|
|
100
|
-
" ",
|
|
101
|
-
e.discoveredCount,
|
|
102
|
-
" ",
|
|
103
|
-
k("adaptercloud.syncRuns.discovered", "discovered")
|
|
104
|
-
]
|
|
105
|
-
}),
|
|
106
|
-
/* @__PURE__ */ w("span", {
|
|
107
|
-
className: "flex items-center gap-1",
|
|
108
|
-
children: [
|
|
109
|
-
/* @__PURE__ */ C(y, { className: "h-3.5 w-3.5" }),
|
|
110
|
-
" ",
|
|
111
|
-
e.reconciledCount,
|
|
112
|
-
" ",
|
|
113
|
-
k("adaptercloud.syncRuns.reconciled", "reconciled")
|
|
114
|
-
]
|
|
115
|
-
}),
|
|
116
|
-
/* @__PURE__ */ w("span", {
|
|
117
|
-
className: "flex items-center gap-1",
|
|
118
|
-
children: [
|
|
119
|
-
/* @__PURE__ */ C(_, { className: "h-3.5 w-3.5" }),
|
|
120
|
-
" ",
|
|
121
|
-
e.driftCount,
|
|
122
|
-
" ",
|
|
123
|
-
k("adaptercloud.syncRuns.drift", "drift")
|
|
124
|
-
]
|
|
125
|
-
}),
|
|
126
|
-
/* @__PURE__ */ w("span", {
|
|
127
|
-
className: "flex items-center gap-1",
|
|
128
|
-
children: [
|
|
129
|
-
/* @__PURE__ */ C(x, { className: "h-3.5 w-3.5" }),
|
|
130
|
-
" ",
|
|
131
|
-
e.violationCount,
|
|
132
|
-
" ",
|
|
133
|
-
k("adaptercloud.syncRuns.violations", "violations")
|
|
134
|
-
]
|
|
135
|
-
})
|
|
136
|
-
]
|
|
137
|
-
}),
|
|
138
|
-
/* @__PURE__ */ w("p", {
|
|
139
|
-
className: "mt-2 text-xs text-text-secondary",
|
|
140
|
-
children: [
|
|
141
|
-
e.startedAt ? `${k("adaptercloud.syncRuns.started", "Started")} ${c(e.startedAt)}` : `${k("adaptercloud.syncRuns.created", "Created")} ${l(e.createdAt)}`,
|
|
142
|
-
e.finishedAt && ` · ${k("adaptercloud.syncRuns.finished", "Finished")} ${c(e.finishedAt)}`,
|
|
143
|
-
" · ",
|
|
144
|
-
/* @__PURE__ */ C("button", {
|
|
145
|
-
type: "button",
|
|
146
|
-
onClick: () => D(`/connections/${e.connectionId}`),
|
|
147
|
-
className: "hover:underline",
|
|
148
|
-
children: k("adaptercloud.syncRuns.viewConnection", "View connection")
|
|
149
|
-
})
|
|
150
|
-
]
|
|
151
|
-
}),
|
|
152
|
-
e.error && /* @__PURE__ */ C("p", {
|
|
153
|
-
className: "mt-2 rounded-lg bg-status-error-bg-subtle px-3 py-1.5 text-xs text-status-error-text",
|
|
154
|
-
children: e.error
|
|
155
|
-
})
|
|
156
|
-
]
|
|
157
|
-
}), t && /* @__PURE__ */ w("button", {
|
|
158
|
-
type: "button",
|
|
159
|
-
disabled: H === e.id,
|
|
160
|
-
onClick: () => {
|
|
161
|
-
F(null), L(e);
|
|
162
|
-
},
|
|
163
|
-
className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50",
|
|
164
|
-
children: [H === e.id ? /* @__PURE__ */ C(v, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ C(S, { className: "h-3.5 w-3.5" }), k("adaptercloud.syncRuns.cancel", "Cancel")]
|
|
165
|
-
})]
|
|
166
|
-
})
|
|
167
|
-
}, e.id);
|
|
168
|
-
})
|
|
169
|
-
}), (M > 0 || V) && /* @__PURE__ */ w("div", {
|
|
216
|
+
children: [/* @__PURE__ */ O(A, {
|
|
217
|
+
data: R,
|
|
218
|
+
columns: U,
|
|
219
|
+
rowKey: (e) => e.id,
|
|
220
|
+
onRowClick: (e) => t(`/connections/${e.connectionId}`),
|
|
221
|
+
rowActions: (e) => e.status === "QUEUED" || e.status === "RUNNING" ? /* @__PURE__ */ k("button", {
|
|
222
|
+
type: "button",
|
|
223
|
+
disabled: B === e.id,
|
|
224
|
+
onClick: () => {
|
|
225
|
+
w(null), N(e);
|
|
226
|
+
},
|
|
227
|
+
className: "inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50",
|
|
228
|
+
children: [B === e.id ? /* @__PURE__ */ O(S, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ O(D, { className: "h-3.5 w-3.5" }), h("adaptercloud.syncRuns.cancel", "Cancel")]
|
|
229
|
+
}) : null
|
|
230
|
+
}), (b > 0 || z) && /* @__PURE__ */ k("div", {
|
|
170
231
|
className: "mt-6 flex items-center justify-between",
|
|
171
232
|
children: [
|
|
172
|
-
/* @__PURE__ */
|
|
233
|
+
/* @__PURE__ */ k("button", {
|
|
173
234
|
type: "button",
|
|
174
|
-
disabled:
|
|
175
|
-
onClick: () =>
|
|
235
|
+
disabled: b === 0,
|
|
236
|
+
onClick: () => x((e) => Math.max(0, e - j)),
|
|
176
237
|
className: "inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
|
|
177
|
-
children: [/* @__PURE__ */
|
|
238
|
+
children: [/* @__PURE__ */ O(g, { className: "h-4 w-4" }), h("adaptercloud.common.prev", "Previous")]
|
|
178
239
|
}),
|
|
179
|
-
/* @__PURE__ */
|
|
240
|
+
/* @__PURE__ */ k("span", {
|
|
180
241
|
className: "text-sm text-text-secondary",
|
|
181
242
|
children: [
|
|
182
|
-
|
|
243
|
+
h("adaptercloud.common.showing", "Showing"),
|
|
183
244
|
" ",
|
|
184
|
-
|
|
245
|
+
b + 1,
|
|
185
246
|
"–",
|
|
186
|
-
|
|
247
|
+
b + R.length
|
|
187
248
|
]
|
|
188
249
|
}),
|
|
189
|
-
/* @__PURE__ */
|
|
250
|
+
/* @__PURE__ */ k("button", {
|
|
190
251
|
type: "button",
|
|
191
|
-
disabled: !
|
|
192
|
-
onClick: () =>
|
|
252
|
+
disabled: !z,
|
|
253
|
+
onClick: () => x((e) => e + j),
|
|
193
254
|
className: "inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50",
|
|
194
|
-
children: [
|
|
255
|
+
children: [h("adaptercloud.common.next", "Next"), /* @__PURE__ */ O(_, { className: "h-4 w-4" })]
|
|
195
256
|
})
|
|
196
257
|
]
|
|
197
258
|
})]
|
|
198
259
|
}),
|
|
199
|
-
/* @__PURE__ */
|
|
200
|
-
open: !!
|
|
260
|
+
/* @__PURE__ */ O(u, {
|
|
261
|
+
open: !!T,
|
|
201
262
|
onOpenChange: (e) => {
|
|
202
|
-
e ||
|
|
263
|
+
e || N(null);
|
|
203
264
|
},
|
|
204
|
-
title:
|
|
205
|
-
description:
|
|
206
|
-
confirmLabel:
|
|
207
|
-
cancelLabel:
|
|
208
|
-
onConfirm: () =>
|
|
209
|
-
isPending: !!
|
|
265
|
+
title: h("adaptercloud.syncRuns.cancelTitle", "Cancel this sync run?"),
|
|
266
|
+
description: h("adaptercloud.syncRuns.cancelPrompt", "The run will stop and transition to Cancelled. Any work already done is kept, but it will not finish."),
|
|
267
|
+
confirmLabel: h("adaptercloud.syncRuns.confirmCancel", "Cancel run"),
|
|
268
|
+
cancelLabel: h("adaptercloud.syncRuns.keepRunning", "Keep running"),
|
|
269
|
+
onConfirm: () => T && V(T.id),
|
|
270
|
+
isPending: !!T && B === T?.id,
|
|
210
271
|
tone: "danger"
|
|
211
272
|
})
|
|
212
273
|
]
|
|
213
274
|
});
|
|
214
275
|
}
|
|
215
276
|
//#endregion
|
|
216
|
-
export {
|
|
277
|
+
export { I as SyncRunsListPage };
|
|
217
278
|
|
|
218
279
|
//# sourceMappingURL=SyncRunsListPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SyncRunsListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/sync-runs/SyncRunsListPage.tsx"],"sourcesContent":["/**\n * Sync Runs History Page\n * @module adaptercloud/pages/sync-runs\n *\n * The operations log (`adaListSyncRuns`) — every discovery / reconcile /\n * cost-ingest / full execution with its lifecycle and counters. Filters by\n * status; queued/running runs can be cancelled via `adaCancelSyncRun`.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Activity,\n ChevronLeft,\n ChevronRight,\n XCircle,\n Loader2,\n Server,\n GitCompareArrows,\n ShieldAlert,\n RotateCcw,\n} from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataState } from '../../components/DataState';\nimport { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useSyncRuns, useCancelSyncRun } from '../../hooks/useAdapterCloudApi';\nimport { syncKindLabels, syncStatusLabels } from '../../utils/domain';\nimport { formatDate, formatRelativeTime } from '../../utils/formatters';\nimport type { AdaSyncRun, AdaSyncStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\nexport function SyncRunsListPage() {\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const [status, setStatus] = useState<AdaSyncStatus | ''>('');\n const [offset, setOffset] = useState(0);\n const [actionError, setActionError] = useState<string | null>(null);\n const [confirmTarget, setConfirmTarget] = useState<AdaSyncRun | null>(null);\n\n const query = useSyncRuns({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n const cancel = useCancelSyncRun();\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n // Per-id pending so cancelling one run only disables/spins that row's button.\n const pendingId = cancel.isPending ? cancel.variables : undefined;\n\n const handleCancel = useCallback(\n (id: string) => {\n setActionError(null);\n cancel.mutate(id, {\n onSuccess: () => setConfirmTarget(null),\n onError: (err) =>\n setActionError(err instanceof Error ? err.message : 'Failed to cancel run.'),\n });\n },\n [cancel]\n );\n\n return (\n <PageLayout\n title={t('adaptercloud.syncRuns.title', 'Sync Runs')}\n description={t(\n 'adaptercloud.syncRuns.subtitle',\n 'Discovery, reconciliation and cost-ingest executions that keep the picture live.'\n )}\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as AdaSyncStatus | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.syncRuns.filterByStatus', 'Filter by status')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.syncRuns.allStatuses', 'All statuses')}</option>\n {(Object.keys(syncStatusLabels) as AdaSyncStatus[]).map((s) => (\n <option key={s} value={s}>\n {syncStatusLabels[s]}\n </option>\n ))}\n </select>\n </div>\n\n {actionError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {actionError}\n </p>\n )}\n\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={items.length === 0}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.syncRuns.emptyTitle', 'No sync runs yet')}\n emptyDescription={t(\n 'adaptercloud.syncRuns.emptyDescription',\n 'Start a sync run from a connection to keep the inventory current.'\n )}\n emptyIcon={<Activity className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.syncRuns.goToConnections', 'Go to connections')}\n </button>\n }\n >\n <div className=\"space-y-3\">\n {items.map((run) => {\n const cancellable = run.status === 'QUEUED' || run.status === 'RUNNING';\n return (\n <GlassCard key={run.id} className=\"p-5\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"mb-1.5 flex flex-wrap items-center gap-2\">\n <span className=\"text-sm font-semibold text-text-primary\">\n {syncKindLabels[run.kind]}\n </span>\n <StatusBadge status={run.status} />\n </div>\n <div className=\"flex flex-wrap items-center gap-4 text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Server className=\"h-3.5 w-3.5\" /> {run.discoveredCount}{' '}\n {t('adaptercloud.syncRuns.discovered', 'discovered')}\n </span>\n <span className=\"flex items-center gap-1\">\n <RotateCcw className=\"h-3.5 w-3.5\" /> {run.reconciledCount}{' '}\n {t('adaptercloud.syncRuns.reconciled', 'reconciled')}\n </span>\n <span className=\"flex items-center gap-1\">\n <GitCompareArrows className=\"h-3.5 w-3.5\" /> {run.driftCount}{' '}\n {t('adaptercloud.syncRuns.drift', 'drift')}\n </span>\n <span className=\"flex items-center gap-1\">\n <ShieldAlert className=\"h-3.5 w-3.5\" /> {run.violationCount}{' '}\n {t('adaptercloud.syncRuns.violations', 'violations')}\n </span>\n </div>\n <p className=\"mt-2 text-xs text-text-secondary\">\n {run.startedAt\n ? `${t('adaptercloud.syncRuns.started', 'Started')} ${formatDate(run.startedAt)}`\n : `${t('adaptercloud.syncRuns.created', 'Created')} ${formatRelativeTime(run.createdAt)}`}\n {run.finishedAt &&\n ` · ${t('adaptercloud.syncRuns.finished', 'Finished')} ${formatDate(run.finishedAt)}`}\n {' · '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/connections/${run.connectionId}`)}\n className=\"hover:underline\"\n >\n {t('adaptercloud.syncRuns.viewConnection', 'View connection')}\n </button>\n </p>\n {run.error && (\n <p className=\"mt-2 rounded-lg bg-status-error-bg-subtle px-3 py-1.5 text-xs text-status-error-text\">\n {run.error}\n </p>\n )}\n </div>\n\n {cancellable && (\n <button\n type=\"button\"\n disabled={pendingId === run.id}\n onClick={() => {\n setActionError(null);\n setConfirmTarget(run);\n }}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {pendingId === run.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <XCircle className=\"h-3.5 w-3.5\" />\n )}\n {t('adaptercloud.syncRuns.cancel', 'Cancel')}\n </button>\n )}\n </div>\n </GlassCard>\n );\n })}\n </div>\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.prev', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {t('adaptercloud.common.next', 'Next')}\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={Boolean(confirmTarget)}\n onOpenChange={(open) => {\n if (!open) setConfirmTarget(null);\n }}\n title={t('adaptercloud.syncRuns.cancelTitle', 'Cancel this sync run?')}\n description={t(\n 'adaptercloud.syncRuns.cancelPrompt',\n 'The run will stop and transition to Cancelled. Any work already done is kept, but it will not finish.'\n )}\n confirmLabel={t('adaptercloud.syncRuns.confirmCancel', 'Cancel run')}\n cancelLabel={t('adaptercloud.syncRuns.keepRunning', 'Keep running')}\n onConfirm={() => confirmTarget && handleCancel(confirmTarget.id)}\n isPending={Boolean(confirmTarget) && pendingId === confirmTarget?.id}\n tone=\"danger\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SyncRunsListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/sync-runs/SyncRunsListPage.tsx"],"sourcesContent":["/**\n * Sync Runs History Page\n * @module adaptercloud/pages/sync-runs\n *\n * The operations log (`adaListSyncRuns`) — every discovery / reconcile /\n * cost-ingest / full execution with its lifecycle and counters. On md+ this\n * renders a dense operations table (Kind · Connection · Status · counters ·\n * Started · Duration) so runs compare at a glance; below 768px it reflows to a\n * card per run (via fe-libs `ResponsiveTable`). Filters by status; queued/running\n * runs can be cancelled via `adaCancelSyncRun`.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Activity,\n ChevronLeft,\n ChevronRight,\n XCircle,\n Loader2,\n Server,\n Cloud,\n Radio,\n Cpu,\n Boxes,\n Network,\n Database,\n HardDrive,\n Puzzle,\n type LucideIcon,\n} from 'lucide-react';\nimport { ResponsiveTable, type ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { DataState } from '../../components/DataState';\nimport { ConfirmDialog } from '../../components/ConfirmDialog';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useSyncRuns, useCancelSyncRun } from '../../hooks/useAdapterCloudApi';\nimport { syncKindLabels, syncStatusLabels } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { AdaAdapterKind, AdaSyncKind, AdaSyncRun, AdaSyncStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\n/**\n * Per-adapter-kind glyph — stands in for the vendor logos (aws / gcp / azure /\n * snowflake) in the mock. Keyed off the run's bound connection's adapter kind.\n */\nconst adapterKindIcon: Record<AdaAdapterKind, LucideIcon> = {\n PUBLIC_CLOUD: Cloud,\n PRIVATE_CLOUD: Cloud,\n SAAS: Cloud,\n ON_PREM: HardDrive,\n EDGE: Radio,\n IOT: Cpu,\n KUBERNETES: Boxes,\n NETWORK: Network,\n DATA_SYSTEM: Database,\n CUSTOM: Puzzle,\n};\n\n/** Colored kind pill (semantic tokens only), mirroring the mock's kind badges. */\nconst kindBadgeClass: Record<AdaSyncKind, string> = {\n DISCOVERY: 'bg-accent-blue-subtle text-accent-blue',\n RECONCILE: 'bg-status-warning-bg-subtle text-status-warning-text',\n COST_INGEST: 'bg-accent-purple-subtle text-accent-purple',\n FULL: 'bg-bg-sunken text-text-secondary',\n};\n\nfunction KindBadge({ kind }: { kind: AdaSyncKind }) {\n return (\n <span\n className={cn(\n 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium',\n kindBadgeClass[kind]\n )}\n >\n {syncKindLabels[kind]}\n </span>\n );\n}\n\n/** Elapsed wall-clock between start and finish, e.g. \"3m 12s\". Null if unfinished. */\nfunction formatDuration(startedAt: string | null, finishedAt: string | null): string | null {\n if (!startedAt || !finishedAt) return null;\n const ms = new Date(finishedAt).getTime() - new Date(startedAt).getTime();\n if (!Number.isFinite(ms) || ms < 0) return null;\n const totalSeconds = Math.floor(ms / 1000);\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n if (hours > 0) return `${hours}h ${minutes}m ${seconds}s`;\n if (minutes > 0) return `${minutes}m ${seconds}s`;\n return `${seconds}s`;\n}\n\nexport function SyncRunsListPage() {\n const { navigateTo } = useAdapterCloud();\n const { t: translate } = useI18n();\n const t = useCallback(\n (key: string, fallback: string): string => {\n const value = translate(key);\n return value === key ? fallback : value;\n },\n [translate]\n );\n\n const [status, setStatus] = useState<AdaSyncStatus | ''>('');\n const [offset, setOffset] = useState(0);\n const [actionError, setActionError] = useState<string | null>(null);\n const [confirmTarget, setConfirmTarget] = useState<AdaSyncRun | null>(null);\n\n const query = useSyncRuns({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n const cancel = useCancelSyncRun();\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n // Per-id pending so cancelling one run only disables/spins that row's button.\n const pendingId = cancel.isPending ? cancel.variables : undefined;\n\n const handleCancel = useCallback(\n (id: string) => {\n setActionError(null);\n cancel.mutate(id, {\n onSuccess: () => setConfirmTarget(null),\n onError: (err) =>\n setActionError(err instanceof Error ? err.message : 'Failed to cancel run.'),\n });\n },\n [cancel]\n );\n\n // Counters are meaningless until a run has produced results — dim them to an\n // em-dash while queued/running, matching the mock.\n const counterCell = (run: AdaSyncRun, value: number) =>\n run.status === 'QUEUED' || run.status === 'RUNNING' ? (\n <span className=\"text-text-tertiary\">—</span>\n ) : (\n <span className=\"tabular-nums text-text-primary\">{value}</span>\n );\n\n const columns: ResponsiveTableColumn<AdaSyncRun>[] = [\n {\n key: 'kind',\n header: t('adaptercloud.syncRuns.kind', 'Kind'),\n priority: 'primary',\n cell: (run) => <KindBadge kind={run.kind} />,\n },\n {\n key: 'connection',\n header: t('adaptercloud.syncRuns.connection', 'Connection'),\n priority: 'secondary',\n cell: (run) => {\n const kind = run.connection?.adapter?.kind;\n const Icon = kind ? adapterKindIcon[kind] : Server;\n return (\n <div className=\"flex min-w-0 items-center gap-2\">\n <span className=\"flex h-6 w-6 shrink-0 items-center justify-center rounded-md bg-bg-sunken text-text-secondary\">\n <Icon className=\"h-3.5 w-3.5\" />\n </span>\n {run.connection?.name ? (\n <span className=\"truncate text-sm text-text-primary\">{run.connection.name}</span>\n ) : (\n <span className=\"text-sm text-text-tertiary\">—</span>\n )}\n </div>\n );\n },\n },\n {\n key: 'status',\n header: t('adaptercloud.syncRuns.status', 'Status'),\n priority: 'secondary',\n cell: (run) => (\n <div className=\"flex flex-col gap-1\">\n <StatusBadge status={run.status} label={syncStatusLabels[run.status]} />\n {run.error && (\n <span className=\"max-w-xs truncate text-xs text-status-error-text\" title={run.error}>\n {run.error}\n </span>\n )}\n </div>\n ),\n },\n {\n key: 'discovered',\n header: t('adaptercloud.syncRuns.discovered', 'Discovered'),\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (run) => counterCell(run, run.discoveredCount),\n },\n {\n key: 'reconciled',\n header: t('adaptercloud.syncRuns.reconciled', 'Reconciled'),\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (run) => counterCell(run, run.reconciledCount),\n },\n {\n key: 'drift',\n header: t('adaptercloud.syncRuns.drift', 'Drift'),\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (run) => counterCell(run, run.driftCount),\n },\n {\n key: 'violations',\n header: t('adaptercloud.syncRuns.violations', 'Violations'),\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (run) => counterCell(run, run.violationCount),\n },\n {\n key: 'started',\n header: t('adaptercloud.syncRuns.started', 'Started'),\n priority: 'secondary',\n cell: (run) =>\n run.startedAt ? (\n <span className=\"whitespace-nowrap text-xs text-text-secondary\">\n {formatDate(run.startedAt)}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'duration',\n header: t('adaptercloud.syncRuns.duration', 'Duration'),\n priority: 'secondary',\n headerClassName: 'text-right',\n cellClassName: 'text-right',\n cell: (run) => {\n const duration = formatDuration(run.startedAt, run.finishedAt);\n return duration ? (\n <span className=\"whitespace-nowrap text-xs tabular-nums text-text-secondary\">\n {duration}\n </span>\n ) : (\n <span className=\"text-xs text-text-tertiary\">—</span>\n );\n },\n },\n ];\n\n return (\n <PageLayout\n title={t('adaptercloud.syncRuns.title', 'Sync Runs')}\n description={t(\n 'adaptercloud.syncRuns.subtitle',\n 'Discovery, reconciliation and cost-ingest executions that keep the picture live.'\n )}\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as AdaSyncStatus | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.syncRuns.filterByStatus', 'Filter by status')}\n className=\"rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n >\n <option value=\"\">{t('adaptercloud.syncRuns.allStatuses', 'All statuses')}</option>\n {(Object.keys(syncStatusLabels) as AdaSyncStatus[]).map((s) => (\n <option key={s} value={s}>\n {syncStatusLabels[s]}\n </option>\n ))}\n </select>\n </div>\n\n {actionError && (\n <p\n role=\"alert\"\n aria-live=\"polite\"\n className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n {actionError}\n </p>\n )}\n\n <DataState\n isLoading={query.isLoading}\n isError={query.isError}\n isEmpty={items.length === 0}\n error={query.error}\n onRetry={() => void query.refetch()}\n emptyTitle={t('adaptercloud.syncRuns.emptyTitle', 'No sync runs yet')}\n emptyDescription={t(\n 'adaptercloud.syncRuns.emptyDescription',\n 'Start a sync run from a connection to keep the inventory current.'\n )}\n emptyIcon={<Activity className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/connections')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90\"\n >\n {t('adaptercloud.syncRuns.goToConnections', 'Go to connections')}\n </button>\n }\n >\n <ResponsiveTable\n data={items}\n columns={columns}\n rowKey={(run) => run.id}\n onRowClick={(run) => navigateTo(`/connections/${run.connectionId}`)}\n rowActions={(run) => {\n const cancellable = run.status === 'QUEUED' || run.status === 'RUNNING';\n if (!cancellable) return null;\n return (\n <button\n type=\"button\"\n disabled={pendingId === run.id}\n onClick={() => {\n setActionError(null);\n setConfirmTarget(run);\n }}\n className=\"inline-flex shrink-0 items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-50\"\n >\n {pendingId === run.id ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <XCircle className=\"h-3.5 w-3.5\" />\n )}\n {t('adaptercloud.syncRuns.cancel', 'Cancel')}\n </button>\n );\n }}\n />\n\n {(offset > 0 || hasMore) && (\n <div className=\"mt-6 flex items-center justify-between\">\n <button\n type=\"button\"\n disabled={offset === 0}\n onClick={() => setOffset((o) => Math.max(0, o - PAGE_SIZE))}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n {t('adaptercloud.common.prev', 'Previous')}\n </button>\n <span className=\"text-sm text-text-secondary\">\n {t('adaptercloud.common.showing', 'Showing')} {offset + 1}–{offset + items.length}\n </span>\n <button\n type=\"button\"\n disabled={!hasMore}\n onClick={() => setOffset((o) => o + PAGE_SIZE)}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-subtle px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {t('adaptercloud.common.next', 'Next')}\n <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n )}\n </DataState>\n\n <ConfirmDialog\n open={Boolean(confirmTarget)}\n onOpenChange={(open) => {\n if (!open) setConfirmTarget(null);\n }}\n title={t('adaptercloud.syncRuns.cancelTitle', 'Cancel this sync run?')}\n description={t(\n 'adaptercloud.syncRuns.cancelPrompt',\n 'The run will stop and transition to Cancelled. Any work already done is kept, but it will not finish.'\n )}\n confirmLabel={t('adaptercloud.syncRuns.confirmCancel', 'Cancel run')}\n cancelLabel={t('adaptercloud.syncRuns.keepRunning', 'Keep running')}\n onConfirm={() => confirmTarget && handleCancel(confirmTarget.id)}\n isPending={Boolean(confirmTarget) && pendingId === confirmTarget?.id}\n tone=\"danger\"\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|