@burdenoff/microfe-adaptercloud 2026.706.1 → 2026.706.2
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/AdapterCloudRoutes.js +5 -1
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/ConfirmDialog.js +27 -0
- package/dist/adaptercloud/components/ConfirmDialog.js.map +1 -0
- package/dist/adaptercloud/components/DataState.js +1 -1
- package/dist/adaptercloud/components/DataState.js.map +1 -1
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +50 -31
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/pages/OverviewPage.js +159 -156
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +102 -89
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +2 -0
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +2 -0
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +141 -124
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js +2 -0
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +1 -0
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/costs/CostsPage.js +112 -87
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -1
- package/dist/adaptercloud/pages/drift/DriftListPage.js +81 -59
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +2 -0
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +109 -91
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js +211 -184
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +153 -111
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -1
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +111 -83
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -1
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +108 -86
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -1
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +11 -3
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,192 +5,214 @@ import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
|
5
5
|
import { DataState as a } from "../../components/DataState.js";
|
|
6
6
|
import { useCancelSyncRun as o, useSyncRuns as s } from "../../hooks/useAdapterCloudApi.js";
|
|
7
7
|
import { formatDate as c, formatRelativeTime as l } from "../../utils/formatters.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
8
|
+
import { ConfirmDialog as u } from "../../components/ConfirmDialog.js";
|
|
9
|
+
import { useCallback as d, useState as f } from "react";
|
|
10
|
+
import { useI18n as p } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { Activity as m, ChevronLeft as h, ChevronRight as g, GitCompareArrows as _, Loader2 as v, RotateCcw as y, Server as b, ShieldAlert as x, XCircle as S } from "lucide-react";
|
|
12
|
+
import { jsx as C, jsxs as w } from "react/jsx-runtime";
|
|
13
|
+
import { GlassCard as T } from "@burdenoff/fe-libs/ui";
|
|
13
14
|
//#region src/adaptercloud/pages/sync-runs/SyncRunsListPage.tsx
|
|
14
|
-
var
|
|
15
|
-
function
|
|
16
|
-
let { navigateTo:
|
|
17
|
-
let n =
|
|
15
|
+
var E = 20;
|
|
16
|
+
function D() {
|
|
17
|
+
let { navigateTo: D } = e(), { t: O } = p(), k = d((e, t) => {
|
|
18
|
+
let n = O(e);
|
|
18
19
|
return n === e ? t : n;
|
|
19
|
-
}, [
|
|
20
|
-
status:
|
|
20
|
+
}, [O]), [A, j] = f(""), [M, N] = f(0), [P, F] = f(null), [I, L] = f(null), R = s({
|
|
21
|
+
status: A || void 0,
|
|
21
22
|
pagination: {
|
|
22
|
-
limit:
|
|
23
|
-
offset:
|
|
23
|
+
limit: E,
|
|
24
|
+
offset: M
|
|
24
25
|
}
|
|
25
|
-
}),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
}), z = o(), B = R.data?.items ?? [], V = R.data?.pageInfo.hasMore ?? !1, H = z.isPending ? z.variables : void 0, U = d((e) => {
|
|
27
|
+
F(null), z.mutate(e, {
|
|
28
|
+
onSuccess: () => L(null),
|
|
29
|
+
onError: (e) => F(e instanceof Error ? e.message : "Failed to cancel run.")
|
|
30
|
+
});
|
|
31
|
+
}, [z]);
|
|
32
|
+
return /* @__PURE__ */ w(t, {
|
|
33
|
+
title: k("adaptercloud.syncRuns.title", "Sync Runs"),
|
|
34
|
+
description: k("adaptercloud.syncRuns.subtitle", "Discovery, reconciliation and cost-ingest executions that keep the picture live."),
|
|
31
35
|
children: [
|
|
32
|
-
/* @__PURE__ */
|
|
36
|
+
/* @__PURE__ */ C("div", {
|
|
33
37
|
className: "mb-6 flex flex-wrap gap-3",
|
|
34
|
-
children: /* @__PURE__ */
|
|
35
|
-
value:
|
|
38
|
+
children: /* @__PURE__ */ w("select", {
|
|
39
|
+
value: A,
|
|
36
40
|
onChange: (e) => {
|
|
37
|
-
|
|
41
|
+
j(e.target.value), N(0);
|
|
38
42
|
},
|
|
43
|
+
"aria-label": k("adaptercloud.syncRuns.filterByStatus", "Filter by status"),
|
|
39
44
|
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)]",
|
|
40
|
-
children: [/* @__PURE__ */
|
|
45
|
+
children: [/* @__PURE__ */ C("option", {
|
|
41
46
|
value: "",
|
|
42
|
-
children:
|
|
43
|
-
}), Object.keys(r).map((e) => /* @__PURE__ */
|
|
47
|
+
children: k("adaptercloud.syncRuns.allStatuses", "All statuses")
|
|
48
|
+
}), Object.keys(r).map((e) => /* @__PURE__ */ C("option", {
|
|
44
49
|
value: e,
|
|
45
50
|
children: r[e]
|
|
46
51
|
}, e))]
|
|
47
52
|
})
|
|
48
53
|
}),
|
|
49
|
-
|
|
54
|
+
P && /* @__PURE__ */ C("p", {
|
|
55
|
+
role: "alert",
|
|
56
|
+
"aria-live": "polite",
|
|
50
57
|
className: "mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
51
|
-
children:
|
|
58
|
+
children: P
|
|
52
59
|
}),
|
|
53
|
-
/* @__PURE__ */
|
|
54
|
-
isLoading:
|
|
55
|
-
isError:
|
|
56
|
-
isEmpty:
|
|
57
|
-
error:
|
|
58
|
-
onRetry: () => void
|
|
59
|
-
emptyTitle:
|
|
60
|
-
emptyDescription:
|
|
61
|
-
emptyIcon: /* @__PURE__ */
|
|
62
|
-
emptyAction: /* @__PURE__ */
|
|
60
|
+
/* @__PURE__ */ w(a, {
|
|
61
|
+
isLoading: R.isLoading,
|
|
62
|
+
isError: R.isError,
|
|
63
|
+
isEmpty: B.length === 0,
|
|
64
|
+
error: R.error,
|
|
65
|
+
onRetry: () => void R.refetch(),
|
|
66
|
+
emptyTitle: k("adaptercloud.syncRuns.emptyTitle", "No sync runs yet"),
|
|
67
|
+
emptyDescription: k("adaptercloud.syncRuns.emptyDescription", "Start a sync run from a connection to keep the inventory current."),
|
|
68
|
+
emptyIcon: /* @__PURE__ */ C(m, { className: "h-10 w-10" }),
|
|
69
|
+
emptyAction: /* @__PURE__ */ C("button", {
|
|
63
70
|
type: "button",
|
|
64
|
-
onClick: () =>
|
|
71
|
+
onClick: () => D("/connections"),
|
|
65
72
|
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
66
|
-
children:
|
|
73
|
+
children: k("adaptercloud.syncRuns.goToConnections", "Go to connections")
|
|
67
74
|
}),
|
|
68
|
-
children: [/* @__PURE__ */
|
|
75
|
+
children: [/* @__PURE__ */ C("div", {
|
|
69
76
|
className: "space-y-3",
|
|
70
|
-
children:
|
|
77
|
+
children: B.map((e) => {
|
|
71
78
|
let t = e.status === "QUEUED" || e.status === "RUNNING";
|
|
72
|
-
return /* @__PURE__ */
|
|
79
|
+
return /* @__PURE__ */ C(T, {
|
|
73
80
|
className: "p-5",
|
|
74
|
-
children: /* @__PURE__ */
|
|
81
|
+
children: /* @__PURE__ */ w("div", {
|
|
75
82
|
className: "flex flex-wrap items-start justify-between gap-3",
|
|
76
|
-
children: [/* @__PURE__ */
|
|
83
|
+
children: [/* @__PURE__ */ w("div", {
|
|
77
84
|
className: "min-w-0 flex-1",
|
|
78
85
|
children: [
|
|
79
|
-
/* @__PURE__ */
|
|
86
|
+
/* @__PURE__ */ w("div", {
|
|
80
87
|
className: "mb-1.5 flex flex-wrap items-center gap-2",
|
|
81
|
-
children: [/* @__PURE__ */
|
|
88
|
+
children: [/* @__PURE__ */ C("span", {
|
|
82
89
|
className: "text-sm font-semibold text-text-primary",
|
|
83
90
|
children: n[e.kind]
|
|
84
|
-
}), /* @__PURE__ */
|
|
91
|
+
}), /* @__PURE__ */ C(i, { status: e.status })]
|
|
85
92
|
}),
|
|
86
|
-
/* @__PURE__ */
|
|
93
|
+
/* @__PURE__ */ w("div", {
|
|
87
94
|
className: "flex flex-wrap items-center gap-4 text-xs text-text-secondary",
|
|
88
95
|
children: [
|
|
89
|
-
/* @__PURE__ */
|
|
96
|
+
/* @__PURE__ */ w("span", {
|
|
90
97
|
className: "flex items-center gap-1",
|
|
91
98
|
children: [
|
|
92
|
-
/* @__PURE__ */
|
|
99
|
+
/* @__PURE__ */ C(b, { className: "h-3.5 w-3.5" }),
|
|
93
100
|
" ",
|
|
94
101
|
e.discoveredCount,
|
|
95
102
|
" ",
|
|
96
|
-
|
|
103
|
+
k("adaptercloud.syncRuns.discovered", "discovered")
|
|
97
104
|
]
|
|
98
105
|
}),
|
|
99
|
-
/* @__PURE__ */
|
|
106
|
+
/* @__PURE__ */ w("span", {
|
|
100
107
|
className: "flex items-center gap-1",
|
|
101
108
|
children: [
|
|
102
|
-
/* @__PURE__ */
|
|
109
|
+
/* @__PURE__ */ C(y, { className: "h-3.5 w-3.5" }),
|
|
103
110
|
" ",
|
|
104
111
|
e.reconciledCount,
|
|
105
112
|
" ",
|
|
106
|
-
|
|
113
|
+
k("adaptercloud.syncRuns.reconciled", "reconciled")
|
|
107
114
|
]
|
|
108
115
|
}),
|
|
109
|
-
/* @__PURE__ */
|
|
116
|
+
/* @__PURE__ */ w("span", {
|
|
110
117
|
className: "flex items-center gap-1",
|
|
111
118
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
119
|
+
/* @__PURE__ */ C(_, { className: "h-3.5 w-3.5" }),
|
|
113
120
|
" ",
|
|
114
121
|
e.driftCount,
|
|
115
122
|
" ",
|
|
116
|
-
|
|
123
|
+
k("adaptercloud.syncRuns.drift", "drift")
|
|
117
124
|
]
|
|
118
125
|
}),
|
|
119
|
-
/* @__PURE__ */
|
|
126
|
+
/* @__PURE__ */ w("span", {
|
|
120
127
|
className: "flex items-center gap-1",
|
|
121
128
|
children: [
|
|
122
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ C(x, { className: "h-3.5 w-3.5" }),
|
|
123
130
|
" ",
|
|
124
131
|
e.violationCount,
|
|
125
132
|
" ",
|
|
126
|
-
|
|
133
|
+
k("adaptercloud.syncRuns.violations", "violations")
|
|
127
134
|
]
|
|
128
135
|
})
|
|
129
136
|
]
|
|
130
137
|
}),
|
|
131
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ w("p", {
|
|
132
139
|
className: "mt-2 text-xs text-text-secondary",
|
|
133
140
|
children: [
|
|
134
|
-
e.startedAt ? `${
|
|
135
|
-
e.finishedAt && ` · ${
|
|
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)}`,
|
|
136
143
|
" · ",
|
|
137
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ C("button", {
|
|
138
145
|
type: "button",
|
|
139
|
-
onClick: () =>
|
|
146
|
+
onClick: () => D(`/connections/${e.connectionId}`),
|
|
140
147
|
className: "hover:underline",
|
|
141
|
-
children:
|
|
148
|
+
children: k("adaptercloud.syncRuns.viewConnection", "View connection")
|
|
142
149
|
})
|
|
143
150
|
]
|
|
144
151
|
}),
|
|
145
|
-
e.error && /* @__PURE__ */
|
|
152
|
+
e.error && /* @__PURE__ */ C("p", {
|
|
146
153
|
className: "mt-2 rounded-lg bg-status-error-bg-subtle px-3 py-1.5 text-xs text-status-error-text",
|
|
147
154
|
children: e.error
|
|
148
155
|
})
|
|
149
156
|
]
|
|
150
|
-
}), t && /* @__PURE__ */
|
|
157
|
+
}), t && /* @__PURE__ */ w("button", {
|
|
151
158
|
type: "button",
|
|
152
|
-
disabled:
|
|
153
|
-
onClick: () =>
|
|
159
|
+
disabled: H === e.id,
|
|
160
|
+
onClick: () => {
|
|
161
|
+
F(null), L(e);
|
|
162
|
+
},
|
|
154
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",
|
|
155
|
-
children: [
|
|
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")]
|
|
156
165
|
})]
|
|
157
166
|
})
|
|
158
167
|
}, e.id);
|
|
159
168
|
})
|
|
160
|
-
}), (
|
|
169
|
+
}), (M > 0 || V) && /* @__PURE__ */ w("div", {
|
|
161
170
|
className: "mt-6 flex items-center justify-between",
|
|
162
171
|
children: [
|
|
163
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ w("button", {
|
|
164
173
|
type: "button",
|
|
165
|
-
disabled:
|
|
166
|
-
onClick: () =>
|
|
174
|
+
disabled: M === 0,
|
|
175
|
+
onClick: () => N((e) => Math.max(0, e - E)),
|
|
167
176
|
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",
|
|
168
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ C(h, { className: "h-4 w-4" }), k("adaptercloud.common.prev", "Previous")]
|
|
169
178
|
}),
|
|
170
|
-
/* @__PURE__ */
|
|
179
|
+
/* @__PURE__ */ w("span", {
|
|
171
180
|
className: "text-sm text-text-secondary",
|
|
172
181
|
children: [
|
|
173
|
-
|
|
182
|
+
k("adaptercloud.common.showing", "Showing"),
|
|
174
183
|
" ",
|
|
175
|
-
|
|
184
|
+
M + 1,
|
|
176
185
|
"–",
|
|
177
|
-
|
|
186
|
+
M + B.length
|
|
178
187
|
]
|
|
179
188
|
}),
|
|
180
|
-
/* @__PURE__ */
|
|
189
|
+
/* @__PURE__ */ w("button", {
|
|
181
190
|
type: "button",
|
|
182
|
-
disabled: !
|
|
183
|
-
onClick: () =>
|
|
191
|
+
disabled: !V,
|
|
192
|
+
onClick: () => N((e) => e + E),
|
|
184
193
|
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",
|
|
185
|
-
children: [
|
|
194
|
+
children: [k("adaptercloud.common.next", "Next"), /* @__PURE__ */ C(g, { className: "h-4 w-4" })]
|
|
186
195
|
})
|
|
187
196
|
]
|
|
188
197
|
})]
|
|
198
|
+
}),
|
|
199
|
+
/* @__PURE__ */ C(u, {
|
|
200
|
+
open: !!I,
|
|
201
|
+
onOpenChange: (e) => {
|
|
202
|
+
e || L(null);
|
|
203
|
+
},
|
|
204
|
+
title: k("adaptercloud.syncRuns.cancelTitle", "Cancel this sync run?"),
|
|
205
|
+
description: k("adaptercloud.syncRuns.cancelPrompt", "The run will stop and transition to Cancelled. Any work already done is kept, but it will not finish."),
|
|
206
|
+
confirmLabel: k("adaptercloud.syncRuns.confirmCancel", "Cancel run"),
|
|
207
|
+
cancelLabel: k("adaptercloud.syncRuns.keepRunning", "Keep running"),
|
|
208
|
+
onConfirm: () => I && U(I.id),
|
|
209
|
+
isPending: !!I && H === I?.id,
|
|
210
|
+
tone: "danger"
|
|
189
211
|
})
|
|
190
212
|
]
|
|
191
213
|
});
|
|
192
214
|
}
|
|
193
215
|
//#endregion
|
|
194
|
-
export {
|
|
216
|
+
export { D as SyncRunsListPage };
|
|
195
217
|
|
|
196
218
|
//# 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 { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useSyncRuns, useCancelSyncRun } from '../../hooks/useAdapterCloudApi';\nimport { syncKindLabels, syncStatusLabels } from '../../utils/domain';\nimport { formatDate, formatRelativeTime } from '../../utils/formatters';\nimport type { 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\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 const handleCancel = useCallback(\n (id: string) => {\n setActionError(null);\n cancel.mutate(id, {\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 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 className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\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={cancel.isPending}\n onClick={() => handleCancel(run.id)}\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 {cancel.isPending ? (\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 </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,IAAM,IAAY;AAElB,SAAgB,IAAmB;CACjC,IAAM,EAAE,kBAAe,GAAiB,EAClC,EAAE,GAAG,MAAc,GAAS,EAC5B,IAAI,GACP,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAU,CACZ,EAEK,CAAC,GAAQ,KAAa,EAA6B,GAAG,EACtD,CAAC,GAAQ,KAAa,EAAS,EAAE,EACjC,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAQ,EAAY;EACxB,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW;GAAQ;EACzC,CAAC,EACI,IAAS,GAAkB,EAE3B,IAAQ,EAAM,MAAM,SAAS,EAAE,EAC/B,IAAU,EAAM,MAAM,SAAS,WAAW,IAE1C,IAAe,GAClB,MAAe;AAEd,EADA,EAAe,KAAK,EACpB,EAAO,OAAO,GAAI,EAChB,UAAU,MACR,EAAe,aAAe,QAAQ,EAAI,UAAU,wBAAwB,EAC/E,CAAC;IAEJ,CAAC,EAAO,CACT;AAED,QACE,kBAAC,GAAD;EACE,OAAO,EAAE,+BAA+B,YAAY;EACpD,aAAa,EACX,kCACA,mFACD;YALH;GAOE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAU,EAAE,OAAO,MAA4B,EAC/C,EAAU,EAAE;;KAEd,WAAU;eANZ,CAQE,kBAAC,UAAD;MAAQ,OAAM;gBAAI,EAAE,qCAAqC,eAAe;MAAU,CAAA,EAChF,OAAO,KAAK,EAAiB,CAAqB,KAAK,MACvD,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAiB;MACX,EAFI,EAEJ,CACT,CACK;;IACL,CAAA;GAEL,KACC,kBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAGN,kBAAC,GAAD;IACE,WAAW,EAAM;IACjB,SAAS,EAAM;IACf,SAAS,EAAM,WAAW;IAC1B,OAAO,EAAM;IACb,eAAe,KAAK,EAAM,SAAS;IACnC,YAAY,EAAE,oCAAoC,mBAAmB;IACrE,kBAAkB,EAChB,0CACA,oEACD;IACD,WAAW,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA;IAC7C,aACE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,eAAe;KACzC,WAAU;eAET,EAAE,yCAAyC,oBAAoB;KACzD,CAAA;cAnBb,CAsBE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,MAAQ;MAClB,IAAM,IAAc,EAAI,WAAW,YAAY,EAAI,WAAW;AAC9D,aACE,kBAAC,GAAD;OAAwB,WAAU;iBAChC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAe,EAAI;YACf,CAAA,EACP,kBAAC,GAAD,EAAa,QAAQ,EAAI,QAAU,CAAA,CAC/B;;UACN,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cACE,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;;cAAE,EAAI;cAAiB;cACxD,EAAE,oCAAoC,aAAa;cAC/C;;YACP,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cACE,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA;;cAAE,EAAI;cAAiB;cAC3D,EAAE,oCAAoC,aAAa;cAC/C;;YACP,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cACE,kBAAC,GAAD,EAAkB,WAAU,eAAgB,CAAA;;cAAE,EAAI;cAAY;cAC7D,EAAE,+BAA+B,QAAQ;cACrC;;YACP,kBAAC,QAAD;aAAM,WAAU;uBAAhB;cACE,kBAAC,GAAD,EAAa,WAAU,eAAgB,CAAA;;cAAE,EAAI;cAAgB;cAC5D,EAAE,oCAAoC,aAAa;cAC/C;;YACH;;UACN,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAI,YACD,GAAG,EAAE,iCAAiC,UAAU,CAAC,GAAG,EAAW,EAAI,UAAU,KAC7E,GAAG,EAAE,iCAAiC,UAAU,CAAC,GAAG,EAAmB,EAAI,UAAU;YACxF,EAAI,cACH,MAAM,EAAE,kCAAkC,WAAW,CAAC,GAAG,EAAW,EAAI,WAAW;YACpF;YACD,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAW,gBAAgB,EAAI,eAAe;aAC7D,WAAU;uBAET,EAAE,wCAAwC,kBAAkB;aACtD,CAAA;YACP;;UACH,EAAI,SACH,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAI;WACH,CAAA;UAEF;YAEL,KACC,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,EAAO;SACjB,eAAe,EAAa,EAAI,GAAG;SACnC,WAAU;mBAJZ,CAMG,EAAO,YACN,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,GAEhD,kBAAC,GAAD,EAAS,WAAU,eAAgB,CAAA,EAEpC,EAAE,gCAAgC,SAAS,CACrC;WAEP;;OACI,EAjEI,EAAI,GAiER;OAEd;KACE,CAAA,GAEJ,IAAS,KAAK,MACd,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAW;OACrB,eAAe,GAAW,MAAM,KAAK,IAAI,GAAG,IAAI,EAAU,CAAC;OAC3D,WAAU;iBAJZ,CAME,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAClC,EAAE,4BAA4B,WAAW,CACnC;;MACT,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG,EAAE,+BAA+B,UAAU;QAAC;QAAE,IAAS;QAAE;QAAE,IAAS,EAAM;QACtE;;MACP,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,CAAC;OACX,eAAe,GAAW,MAAM,IAAI,EAAU;OAC9C,WAAU;iBAJZ,CAMG,EAAE,4BAA4B,OAAO,EACtC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAC7B;;MACL;OAEE;;GACD"}
|
|
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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"}
|
|
@@ -66,6 +66,7 @@ function A() {
|
|
|
66
66
|
onChange: (e) => {
|
|
67
67
|
L(e.target.value), z(0);
|
|
68
68
|
},
|
|
69
|
+
"aria-label": F("adaptercloud.violations.filterByStatus", "Filter by status"),
|
|
69
70
|
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)]",
|
|
70
71
|
children: [/* @__PURE__ */ x("option", {
|
|
71
72
|
value: "",
|
|
@@ -82,6 +83,8 @@ function A() {
|
|
|
82
83
|
})]
|
|
83
84
|
}),
|
|
84
85
|
W && /* @__PURE__ */ x("p", {
|
|
86
|
+
role: "alert",
|
|
87
|
+
"aria-live": "polite",
|
|
85
88
|
className: "mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
86
89
|
children: W
|
|
87
90
|
}),
|
|
@@ -113,7 +116,7 @@ function A() {
|
|
|
113
116
|
e.policy && /* @__PURE__ */ x("button", {
|
|
114
117
|
type: "button",
|
|
115
118
|
onClick: () => A(`/policies/${e.policyId}`),
|
|
116
|
-
className: "text-xs font-medium text-
|
|
119
|
+
className: "text-xs font-medium text-accent-blue hover:underline",
|
|
117
120
|
children: e.policy.name
|
|
118
121
|
})
|
|
119
122
|
]
|
|
@@ -217,19 +220,24 @@ function A() {
|
|
|
217
220
|
/* @__PURE__ */ S("div", {
|
|
218
221
|
className: "px-1 py-2",
|
|
219
222
|
children: [
|
|
220
|
-
/* @__PURE__ */ x("
|
|
221
|
-
|
|
223
|
+
/* @__PURE__ */ x("label", {
|
|
224
|
+
htmlFor: "ada-waiver-reason",
|
|
225
|
+
className: "mb-1.5 block text-sm text-text-secondary",
|
|
222
226
|
children: F("adaptercloud.violations.waivePrompt", "A waiver requires a documented reason and is recorded on the violation.")
|
|
223
227
|
}),
|
|
224
228
|
/* @__PURE__ */ x("textarea", {
|
|
229
|
+
id: "ada-waiver-reason",
|
|
225
230
|
value: H,
|
|
226
231
|
onChange: (e) => U(e.target.value),
|
|
227
232
|
rows: 4,
|
|
228
233
|
autoFocus: !0,
|
|
234
|
+
"aria-label": F("adaptercloud.violations.waiverReason", "Waiver reason"),
|
|
229
235
|
placeholder: F("adaptercloud.violations.waiveReasonPlaceholder", "Why is this acceptable?"),
|
|
230
236
|
className: "w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
231
237
|
}),
|
|
232
238
|
W && /* @__PURE__ */ x("p", {
|
|
239
|
+
role: "alert",
|
|
240
|
+
"aria-live": "polite",
|
|
233
241
|
className: "mt-2 text-sm text-status-error-text",
|
|
234
242
|
children: W
|
|
235
243
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViolationsListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/violations/ViolationsListPage.tsx"],"sourcesContent":["/**\n * Violations Queue Page\n * @module adaptercloud/pages/violations\n *\n * The governance work queue (`adaListViolations`). Filters by status (and by\n * policy/resource via URL params). Each open/acknowledged violation can be\n * transitioned via `adaResolveViolation` — acknowledge, resolve, or waive (a\n * waiver requires a reason, enforced both client-side and at the service).\n */\n\nimport { useState, useCallback } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ShieldAlert,\n ChevronLeft,\n ChevronRight,\n Check,\n CheckCheck,\n FileText,\n Loader2,\n} from 'lucide-react';\nimport {\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useViolations, useResolveViolation } from '../../hooks/useAdapterCloudApi';\nimport { violationStatusLabels, severityRank } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaPolicyViolation, AdaViolationStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\nexport function ViolationsListPage() {\n const { navigateTo } = useAdapterCloud();\n const [searchParams] = useSearchParams();\n const policyId = searchParams.get('policyId') ?? undefined;\n const resourceId = searchParams.get('resourceId') ?? undefined;\n\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<AdaViolationStatus | ''>('OPEN');\n const [offset, setOffset] = useState(0);\n const [waiveTarget, setWaiveTarget] = useState<AdaPolicyViolation | null>(null);\n const [waiverReason, setWaiverReason] = useState('');\n const [actionError, setActionError] = useState<string | null>(null);\n\n const query = useViolations({\n policyId,\n resourceId,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n const resolve = useResolveViolation();\n\n const items = [...(query.data?.items ?? [])].sort(\n (a, b) => severityRank[b.severity] - severityRank[a.severity]\n );\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const onError = useCallback(\n (err: unknown) =>\n setActionError(err instanceof Error ? err.message : 'Action failed. Please try again.'),\n []\n );\n\n const transition = useCallback(\n (id: string, newStatus: AdaViolationStatus) => {\n setActionError(null);\n resolve.mutate({ id, status: newStatus }, { onError });\n },\n [resolve, onError]\n );\n\n const confirmWaive = useCallback(() => {\n if (!waiveTarget) return;\n if (!waiverReason.trim()) {\n setActionError(t('adaptercloud.violations.reasonRequired', 'A waiver requires a reason.'));\n return;\n }\n setActionError(null);\n resolve.mutate(\n { id: waiveTarget.id, status: 'WAIVED', waiverReason: waiverReason.trim() },\n {\n onSuccess: () => {\n setWaiveTarget(null);\n setWaiverReason('');\n },\n onError,\n }\n );\n }, [waiveTarget, waiverReason, resolve, onError, t]);\n\n return (\n <PageLayout\n title={t('adaptercloud.violations.title', 'Violations')}\n description={t(\n 'adaptercloud.violations.subtitle',\n 'Work the governance queue — acknowledge, resolve, or waive policy breaches.'\n )}\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <select\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as AdaViolationStatus | '');\n setOffset(0);\n }}\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.violations.allStatuses', 'All statuses')}</option>\n {(Object.keys(violationStatusLabels) as AdaViolationStatus[]).map((s) => (\n <option key={s} value={s}>\n {violationStatusLabels[s]}\n </option>\n ))}\n </select>\n {(policyId || resourceId) && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/violations')}\n className=\"inline-flex items-center gap-1.5 rounded-full bg-accent-blue-subtle px-2.5 py-1 text-xs text-accent-blue hover:underline\"\n >\n {t('adaptercloud.violations.clearScope', 'Clear scope ×')}\n </button>\n )}\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\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.violations.emptyTitle', 'No violations')}\n emptyDescription={t(\n 'adaptercloud.violations.emptyDescription',\n 'Nothing matches this filter — your estate is clean here.'\n )}\n emptyIcon={<ShieldAlert className=\"h-10 w-10\" />}\n >\n <div className=\"space-y-3\">\n {items.map((v) => {\n const isOpen = v.status === 'OPEN' || v.status === 'ACKNOWLEDGED';\n return (\n <GlassCard key={v.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 <SeverityBadge severity={v.severity} />\n <StatusBadge status={v.status} />\n {v.policy && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/policies/${v.policyId}`)}\n className=\"text-xs font-medium text-primary hover:underline\"\n >\n {v.policy.name}\n </button>\n )}\n </div>\n <p className=\"text-sm font-medium text-text-primary\">{v.message}</p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.violations.detected', 'Detected')} {formatDate(v.detectedAt)}\n {' · '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources/${v.resourceId}`)}\n className=\"hover:underline\"\n >\n {t('adaptercloud.violations.viewResource', 'View resource')}\n </button>\n </p>\n {v.waiverReason && (\n <p className=\"mt-2 rounded-lg bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n {t('adaptercloud.violations.waiver', 'Waiver')}: {v.waiverReason}\n </p>\n )}\n </div>\n\n {isOpen && (\n <div className=\"flex shrink-0 flex-wrap gap-2\">\n {v.status === 'OPEN' && (\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={() => transition(v.id, 'ACKNOWLEDGED')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary transition-colors hover:bg-accent disabled:opacity-50\"\n >\n <Check className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.violations.acknowledge', 'Acknowledge')}\n </button>\n )}\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={() => transition(v.id, 'RESOLVED')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n <CheckCheck className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.violations.resolve', 'Resolve')}\n </button>\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={() => {\n setActionError(null);\n setWaiverReason('');\n setWaiveTarget(v);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary transition-colors hover:bg-accent disabled:opacity-50\"\n >\n <FileText className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.violations.waive', 'Waive')}\n </button>\n </div>\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 {/* Waive dialog */}\n <ResponsiveDialog\n open={Boolean(waiveTarget)}\n onOpenChange={(open) => {\n if (!open) setWaiveTarget(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {t('adaptercloud.violations.waiveTitle', 'Waive violation')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <div className=\"px-1 py-2\">\n <p className=\"mb-3 text-sm text-text-secondary\">\n {t(\n 'adaptercloud.violations.waivePrompt',\n 'A waiver requires a documented reason and is recorded on the violation.'\n )}\n </p>\n <textarea\n value={waiverReason}\n onChange={(e) => setWaiverReason(e.target.value)}\n rows={4}\n autoFocus\n placeholder={t(\n 'adaptercloud.violations.waiveReasonPlaceholder',\n 'Why is this acceptable?'\n )}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {actionError && <p className=\"mt-2 text-sm text-status-error-text\">{actionError}</p>}\n </div>\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setWaiveTarget(null)}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={confirmWaive}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {resolve.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {t('adaptercloud.violations.confirmWaive', 'Waive')}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwCA,IAAM,IAAY;AAElB,SAAgB,IAAqB;CACnC,IAAM,EAAE,kBAAe,GAAiB,EAClC,CAAC,KAAgB,GAAiB,EAClC,IAAW,EAAa,IAAI,WAAW,IAAI,KAAA,GAC3C,IAAa,EAAa,IAAI,aAAa,IAAI,KAAA,GAE/C,EAAE,GAAG,MAAc,GAAS,EAC5B,IAAI,GACP,GAAa,MAA6B;EACzC,IAAM,IAAQ,EAAU,EAAI;AAC5B,SAAO,MAAU,IAAM,IAAW;IAEpC,CAAC,EAAU,CACZ,EAEK,CAAC,GAAQ,KAAa,EAAkC,OAAO,EAC/D,CAAC,GAAQ,KAAa,EAAS,EAAE,EACjC,CAAC,GAAa,KAAkB,EAAoC,KAAK,EACzE,CAAC,GAAc,KAAmB,EAAS,GAAG,EAC9C,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,IAAQ,EAAc;EAC1B;EACA;EACA,QAAQ,KAAU,KAAA;EAClB,YAAY;GAAE,OAAO;GAAW;GAAQ;EACzC,CAAC,EACI,IAAU,GAAqB,EAE/B,IAAQ,CAAC,GAAI,EAAM,MAAM,SAAS,EAAE,CAAE,CAAC,MAC1C,GAAG,MAAM,EAAa,EAAE,YAAY,EAAa,EAAE,UACrD,EACK,IAAU,EAAM,MAAM,SAAS,WAAW,IAE1C,IAAU,GACb,MACC,EAAe,aAAe,QAAQ,EAAI,UAAU,mCAAmC,EACzF,EAAE,CACH,EAEK,IAAa,GAChB,GAAY,MAAkC;AAE7C,EADA,EAAe,KAAK,EACpB,EAAQ,OAAO;GAAE;GAAI,QAAQ;GAAW,EAAE,EAAE,YAAS,CAAC;IAExD,CAAC,GAAS,EAAQ,CACnB,EAEK,IAAe,QAAkB;AAChC,SACL;OAAI,CAAC,EAAa,MAAM,EAAE;AACxB,MAAe,EAAE,0CAA0C,8BAA8B,CAAC;AAC1F;;AAGF,GADA,EAAe,KAAK,EACpB,EAAQ,OACN;IAAE,IAAI,EAAY;IAAI,QAAQ;IAAU,cAAc,EAAa,MAAM;IAAE,EAC3E;IACE,iBAAiB;AAEf,KADA,EAAe,KAAK,EACpB,EAAgB,GAAG;;IAErB;IACD,CACF;;IACA;EAAC;EAAa;EAAc;EAAS;EAAS;EAAE,CAAC;AAEpD,QACE,kBAAC,GAAD;EACE,OAAO,EAAE,iCAAiC,aAAa;EACvD,aAAa,EACX,oCACA,8EACD;YALH;GAOE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAU,EAAE,OAAO,MAAiC,EACpD,EAAU,EAAE;;KAEd,WAAU;eANZ,CAQE,kBAAC,UAAD;MAAQ,OAAM;gBAAI,EAAE,uCAAuC,eAAe;MAAU,CAAA,EAClF,OAAO,KAAK,EAAsB,CAA0B,KAAK,MACjE,kBAAC,UAAD;MAAgB,OAAO;gBACpB,EAAsB;MAChB,EAFI,EAEJ,CACT,CACK;SACP,KAAY,MACZ,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,cAAc;KACxC,WAAU;eAET,EAAE,sCAAsC,gBAAgB;KAClD,CAAA,CAEP;;GAEL,KACC,kBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAGN,kBAAC,GAAD;IACE,WAAW,EAAM;IACjB,SAAS,EAAM;IACf,SAAS,EAAM,WAAW;IAC1B,OAAO,EAAM;IACb,eAAe,KAAK,EAAM,SAAS;IACnC,YAAY,EAAE,sCAAsC,gBAAgB;IACpE,kBAAkB,EAChB,4CACA,2DACD;IACD,WAAW,kBAAC,GAAD,EAAa,WAAU,aAAc,CAAA;cAXlD,CAaE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAM,KAAK,MAAM;MAChB,IAAM,IAAS,EAAE,WAAW,UAAU,EAAE,WAAW;AACnD,aACE,kBAAC,GAAD;OAAsB,WAAU;iBAC9B,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,GAAD,EAAe,UAAU,EAAE,UAAY,CAAA;YACvC,kBAAC,GAAD,EAAa,QAAQ,EAAE,QAAU,CAAA;YAChC,EAAE,UACD,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAW,aAAa,EAAE,WAAW;aACpD,WAAU;uBAET,EAAE,OAAO;aACH,CAAA;YAEP;;UACN,kBAAC,KAAD;WAAG,WAAU;qBAAyC,EAAE;WAAY,CAAA;UACpE,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAE,oCAAoC,WAAW;YAAC;YAAE,EAAW,EAAE,WAAW;YAC5E;YACD,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAW,cAAc,EAAE,aAAa;aACvD,WAAU;uBAET,EAAE,wCAAwC,gBAAgB;aACpD,CAAA;YACP;;UACH,EAAE,gBACD,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAE,kCAAkC,SAAS;YAAC;YAAG,EAAE;YAClD;;UAEF;YAEL,KACC,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACG,EAAE,WAAW,UACZ,kBAAC,UAAD;WACE,MAAK;WACL,UAAU,EAAQ;WAClB,eAAe,EAAW,EAAE,IAAI,eAAe;WAC/C,WAAU;qBAJZ,CAME,kBAAC,GAAD,EAAO,WAAU,eAAgB,CAAA,EAChC,EAAE,uCAAuC,cAAc,CACjD;;UAEX,kBAAC,UAAD;WACE,MAAK;WACL,UAAU,EAAQ;WAClB,eAAe,EAAW,EAAE,IAAI,WAAW;WAC3C,WAAU;qBAJZ,CAME,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA,EACrC,EAAE,mCAAmC,UAAU,CACzC;;UACT,kBAAC,UAAD;WACE,MAAK;WACL,UAAU,EAAQ;WAClB,eAAe;AAGb,YAFA,EAAe,KAAK,EACpB,EAAgB,GAAG,EACnB,EAAe,EAAE;;WAEnB,WAAU;qBARZ,CAUE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACnC,EAAE,iCAAiC,QAAQ,CACrC;;UACL;WAEJ;;OACI,EAzEI,EAAE,GAyEN;OAEd;KACE,CAAA,GAEJ,IAAS,KAAK,MACd,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,MAAW;OACrB,eAAe,GAAW,MAAM,KAAK,IAAI,GAAG,IAAI,EAAU,CAAC;OAC3D,WAAU;iBAJZ,CAME,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA,EAClC,EAAE,4BAA4B,WAAW,CACnC;;MACT,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG,EAAE,+BAA+B,UAAU;QAAC;QAAE,IAAS;QAAE;QAAE,IAAS,EAAM;QACtE;;MACP,kBAAC,UAAD;OACE,MAAK;OACL,UAAU,CAAC;OACX,eAAe,GAAW,MAAM,IAAI,EAAU;OAC9C,WAAU;iBAJZ,CAMG,EAAE,4BAA4B,OAAO,EACtC,kBAAC,GAAD,EAAc,WAAU,WAAY,CAAA,CAC7B;;MACL;OAEE;;GAGZ,kBAAC,GAAD;IACE,MAAM,EAAQ;IACd,eAAe,MAAS;AACtB,KAAK,KAAM,EAAe,KAAK;;cAGjC,kBAAC,GAAD,EAAA,UAAA;KACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACG,EAAE,sCAAsC,kBAAkB,EACrC,CAAA,EACD,CAAA;KACzB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EACC,uCACA,0EACD;QACC,CAAA;OACJ,kBAAC,YAAD;QACE,OAAO;QACP,WAAW,MAAM,EAAgB,EAAE,OAAO,MAAM;QAChD,MAAM;QACN,WAAA;QACA,aAAa,EACX,kDACA,0BACD;QACD,WAAU;QACV,CAAA;OACD,KAAe,kBAAC,KAAD;QAAG,WAAU;kBAAuC;QAAgB,CAAA;OAChF;;KACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAe,KAAK;MACnC,WAAU;gBAET,EAAE,8BAA8B,SAAS;MACnC,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,UAAU,EAAQ;MAClB,SAAS;MACT,WAAU;gBAJZ,CAMG,EAAQ,aAAa,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EACjE,EAAE,wCAAwC,QAAQ,CAC5C;QACc,EAAA,CAAA;KACD,EAAA,CAAA;IACT,CAAA;GACR"}
|
|
1
|
+
{"version":3,"file":"ViolationsListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/violations/ViolationsListPage.tsx"],"sourcesContent":["/**\n * Violations Queue Page\n * @module adaptercloud/pages/violations\n *\n * The governance work queue (`adaListViolations`). Filters by status (and by\n * policy/resource via URL params). Each open/acknowledged violation can be\n * transitioned via `adaResolveViolation` — acknowledge, resolve, or waive (a\n * waiver requires a reason, enforced both client-side and at the service).\n */\n\nimport { useState, useCallback } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ShieldAlert,\n ChevronLeft,\n ChevronRight,\n Check,\n CheckCheck,\n FileText,\n Loader2,\n} from 'lucide-react';\nimport {\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { useViolations, useResolveViolation } from '../../hooks/useAdapterCloudApi';\nimport { violationStatusLabels, severityRank } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaPolicyViolation, AdaViolationStatus } from '../../types';\n\nconst PAGE_SIZE = 20;\n\nexport function ViolationsListPage() {\n const { navigateTo } = useAdapterCloud();\n const [searchParams] = useSearchParams();\n const policyId = searchParams.get('policyId') ?? undefined;\n const resourceId = searchParams.get('resourceId') ?? undefined;\n\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<AdaViolationStatus | ''>('OPEN');\n const [offset, setOffset] = useState(0);\n const [waiveTarget, setWaiveTarget] = useState<AdaPolicyViolation | null>(null);\n const [waiverReason, setWaiverReason] = useState('');\n const [actionError, setActionError] = useState<string | null>(null);\n\n const query = useViolations({\n policyId,\n resourceId,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset },\n });\n const resolve = useResolveViolation();\n\n // Severity ordering is applied ONLY within the current server page. The queue\n // pages by offset, so this does not globally reorder across pages — a page-2\n // CRITICAL still sorts below a page-1 LOW. Kept as a within-page readability\n // aid (the service has no severity sort arg to make it global).\n const items = [...(query.data?.items ?? [])].sort(\n (a, b) => severityRank[b.severity] - severityRank[a.severity]\n );\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n const onError = useCallback(\n (err: unknown) =>\n setActionError(err instanceof Error ? err.message : 'Action failed. Please try again.'),\n []\n );\n\n const transition = useCallback(\n (id: string, newStatus: AdaViolationStatus) => {\n setActionError(null);\n resolve.mutate({ id, status: newStatus }, { onError });\n },\n [resolve, onError]\n );\n\n const confirmWaive = useCallback(() => {\n if (!waiveTarget) return;\n if (!waiverReason.trim()) {\n setActionError(t('adaptercloud.violations.reasonRequired', 'A waiver requires a reason.'));\n return;\n }\n setActionError(null);\n resolve.mutate(\n { id: waiveTarget.id, status: 'WAIVED', waiverReason: waiverReason.trim() },\n {\n onSuccess: () => {\n setWaiveTarget(null);\n setWaiverReason('');\n },\n onError,\n }\n );\n }, [waiveTarget, waiverReason, resolve, onError, t]);\n\n return (\n <PageLayout\n title={t('adaptercloud.violations.title', 'Violations')}\n description={t(\n 'adaptercloud.violations.subtitle',\n 'Work the governance queue — acknowledge, resolve, or waive policy breaches.'\n )}\n >\n <div className=\"mb-6 flex flex-wrap items-center gap-3\">\n <select\n value={status}\n onChange={(e) => {\n setStatus(e.target.value as AdaViolationStatus | '');\n setOffset(0);\n }}\n aria-label={t('adaptercloud.violations.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.violations.allStatuses', 'All statuses')}</option>\n {(Object.keys(violationStatusLabels) as AdaViolationStatus[]).map((s) => (\n <option key={s} value={s}>\n {violationStatusLabels[s]}\n </option>\n ))}\n </select>\n {(policyId || resourceId) && (\n <button\n type=\"button\"\n onClick={() => navigateTo('/violations')}\n className=\"inline-flex items-center gap-1.5 rounded-full bg-accent-blue-subtle px-2.5 py-1 text-xs text-accent-blue hover:underline\"\n >\n {t('adaptercloud.violations.clearScope', 'Clear scope ×')}\n </button>\n )}\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.violations.emptyTitle', 'No violations')}\n emptyDescription={t(\n 'adaptercloud.violations.emptyDescription',\n 'Nothing matches this filter — your estate is clean here.'\n )}\n emptyIcon={<ShieldAlert className=\"h-10 w-10\" />}\n >\n <div className=\"space-y-3\">\n {items.map((v) => {\n const isOpen = v.status === 'OPEN' || v.status === 'ACKNOWLEDGED';\n return (\n <GlassCard key={v.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 <SeverityBadge severity={v.severity} />\n <StatusBadge status={v.status} />\n {v.policy && (\n <button\n type=\"button\"\n onClick={() => navigateTo(`/policies/${v.policyId}`)}\n className=\"text-xs font-medium text-accent-blue hover:underline\"\n >\n {v.policy.name}\n </button>\n )}\n </div>\n <p className=\"text-sm font-medium text-text-primary\">{v.message}</p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {t('adaptercloud.violations.detected', 'Detected')} {formatDate(v.detectedAt)}\n {' · '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources/${v.resourceId}`)}\n className=\"hover:underline\"\n >\n {t('adaptercloud.violations.viewResource', 'View resource')}\n </button>\n </p>\n {v.waiverReason && (\n <p className=\"mt-2 rounded-lg bg-bg-sunken px-3 py-1.5 text-xs text-text-secondary\">\n {t('adaptercloud.violations.waiver', 'Waiver')}: {v.waiverReason}\n </p>\n )}\n </div>\n\n {isOpen && (\n <div className=\"flex shrink-0 flex-wrap gap-2\">\n {v.status === 'OPEN' && (\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={() => transition(v.id, 'ACKNOWLEDGED')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-primary transition-colors hover:bg-accent disabled:opacity-50\"\n >\n <Check className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.violations.acknowledge', 'Acknowledge')}\n </button>\n )}\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={() => transition(v.id, 'RESOLVED')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-status-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n <CheckCheck className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.violations.resolve', 'Resolve')}\n </button>\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={() => {\n setActionError(null);\n setWaiverReason('');\n setWaiveTarget(v);\n }}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-subtle px-3 py-1.5 text-xs font-medium text-text-secondary transition-colors hover:bg-accent disabled:opacity-50\"\n >\n <FileText className=\"h-3.5 w-3.5\" />\n {t('adaptercloud.violations.waive', 'Waive')}\n </button>\n </div>\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 {/* Waive dialog */}\n <ResponsiveDialog\n open={Boolean(waiveTarget)}\n onOpenChange={(open) => {\n if (!open) setWaiveTarget(null);\n }}\n >\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {t('adaptercloud.violations.waiveTitle', 'Waive violation')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <div className=\"px-1 py-2\">\n <label htmlFor=\"ada-waiver-reason\" className=\"mb-1.5 block text-sm text-text-secondary\">\n {t(\n 'adaptercloud.violations.waivePrompt',\n 'A waiver requires a documented reason and is recorded on the violation.'\n )}\n </label>\n <textarea\n id=\"ada-waiver-reason\"\n value={waiverReason}\n onChange={(e) => setWaiverReason(e.target.value)}\n rows={4}\n autoFocus\n aria-label={t('adaptercloud.violations.waiverReason', 'Waiver reason')}\n placeholder={t(\n 'adaptercloud.violations.waiveReasonPlaceholder',\n 'Why is this acceptable?'\n )}\n className=\"w-full rounded-lg border border-border-subtle bg-bg-surface px-3 py-2 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n {actionError && (\n <p role=\"alert\" aria-live=\"polite\" className=\"mt-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n </div>\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setWaiveTarget(null)}\n className=\"rounded-lg border border-border-subtle px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-accent\"\n >\n {t('adaptercloud.common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n disabled={resolve.isPending}\n onClick={confirmWaive}\n className=\"inline-flex items-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90 disabled:opacity-50\"\n >\n {resolve.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {t('adaptercloud.violations.confirmWaive', 'Waive')}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|