@burdenoff/microfe-adaptercloud 2026.625.2 → 2026.628.1
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 +65 -121
- package/dist/adaptercloud/AdapterCloudRoot.js.map +1 -1
- package/dist/adaptercloud/AdapterCloudRoutes.js +70 -182
- package/dist/adaptercloud/AdapterCloudRoutes.js.map +1 -1
- package/dist/adaptercloud/components/DataState.js +42 -0
- package/dist/adaptercloud/components/DataState.js.map +1 -0
- package/dist/adaptercloud/components/SeverityBadge.js +12 -0
- package/dist/adaptercloud/components/SeverityBadge.js.map +1 -0
- package/dist/adaptercloud/components/StatusBadge.js +43 -44
- package/dist/adaptercloud/components/StatusBadge.js.map +1 -1
- package/dist/adaptercloud/context/AdapterCloudContext.js +22 -30
- package/dist/adaptercloud/context/AdapterCloudContext.js.map +1 -1
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js +397 -194
- package/dist/adaptercloud/hooks/useAdapterCloudApi.js.map +1 -1
- package/dist/adaptercloud/index.js +1 -0
- package/dist/adaptercloud/pages/OverviewPage.js +334 -0
- package/dist/adaptercloud/pages/OverviewPage.js.map +1 -0
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js +187 -0
- package/dist/adaptercloud/pages/adapters/AdapterDetailPage.js.map +1 -0
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js +278 -0
- package/dist/adaptercloud/pages/adapters/AdapterFormPage.js.map +1 -0
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js +172 -0
- package/dist/adaptercloud/pages/adapters/AdaptersListPage.js.map +1 -0
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js +247 -0
- package/dist/adaptercloud/pages/connections/ConnectionDetailPage.js.map +1 -0
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js +271 -0
- package/dist/adaptercloud/pages/connections/ConnectionFormPage.js.map +1 -0
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js +167 -0
- package/dist/adaptercloud/pages/connections/ConnectionsListPage.js.map +1 -0
- package/dist/adaptercloud/pages/costs/CostsPage.js +169 -0
- package/dist/adaptercloud/pages/costs/CostsPage.js.map +1 -0
- package/dist/adaptercloud/pages/drift/DriftListPage.js +132 -0
- package/dist/adaptercloud/pages/drift/DriftListPage.js.map +1 -0
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js +171 -0
- package/dist/adaptercloud/pages/policies/PoliciesListPage.js.map +1 -0
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js +185 -0
- package/dist/adaptercloud/pages/policies/PolicyDetailPage.js.map +1 -0
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js +215 -0
- package/dist/adaptercloud/pages/policies/PolicyFormPage.js.map +1 -0
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js +207 -0
- package/dist/adaptercloud/pages/resources/ResourceDetailPage.js.map +1 -0
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js +182 -0
- package/dist/adaptercloud/pages/resources/ResourcesListPage.js.map +1 -0
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js +196 -0
- package/dist/adaptercloud/pages/sync-runs/SyncRunsListPage.js.map +1 -0
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js +258 -0
- package/dist/adaptercloud/pages/violations/ViolationsListPage.js.map +1 -0
- package/dist/adaptercloud/store/adaptercloudStore.js +1 -7
- package/dist/adaptercloud/store/adaptercloudStore.js.map +1 -1
- package/dist/adaptercloud/utils/domain.js +105 -0
- package/dist/adaptercloud/utils/domain.js.map +1 -0
- package/dist/adaptercloud/utils/formatters.js +1 -12
- package/dist/adaptercloud/utils/formatters.js.map +1 -1
- package/dist/index.js +5 -4
- package/package.json +1 -1
- package/dist/adaptercloud/components/InvestmentCard.js +0 -98
- package/dist/adaptercloud/components/InvestmentCard.js.map +0 -1
- package/dist/adaptercloud/components/RatingDisplay.js +0 -35
- package/dist/adaptercloud/components/RatingDisplay.js.map +0 -1
- package/dist/adaptercloud/mocks/data.js +0 -1517
- package/dist/adaptercloud/mocks/data.js.map +0 -1
- package/dist/adaptercloud/mocks/users.js +0 -34
- package/dist/adaptercloud/mocks/users.js.map +0 -1
- package/dist/adaptercloud/pages/HomePage.js +0 -934
- package/dist/adaptercloud/pages/HomePage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/AdminAnalyticsPage.js +0 -653
- package/dist/adaptercloud/pages/admin/AdminAnalyticsPage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/AdminDashboard.js +0 -665
- package/dist/adaptercloud/pages/admin/AdminDashboard.js.map +0 -1
- package/dist/adaptercloud/pages/admin/AdminFinancialPage.js +0 -426
- package/dist/adaptercloud/pages/admin/AdminFinancialPage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/AdminOrdersPage.js +0 -665
- package/dist/adaptercloud/pages/admin/AdminOrdersPage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/AdminProfilePage.js +0 -261
- package/dist/adaptercloud/pages/admin/AdminProfilePage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/AdminSettingsPage.js +0 -663
- package/dist/adaptercloud/pages/admin/AdminSettingsPage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/CartManagementPage.js +0 -874
- package/dist/adaptercloud/pages/admin/CartManagementPage.js.map +0 -1
- package/dist/adaptercloud/pages/admin/UserManagementPage.js +0 -898
- package/dist/adaptercloud/pages/admin/UserManagementPage.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/AnalyticsDashboard.js +0 -840
- package/dist/adaptercloud/pages/cart-owner/AnalyticsDashboard.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/BusinessSettingsPage.js +0 -714
- package/dist/adaptercloud/pages/cart-owner/BusinessSettingsPage.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/CartOwnerDashboard.js +0 -788
- package/dist/adaptercloud/pages/cart-owner/CartOwnerDashboard.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/CartOwnerProfilePage.js +0 -888
- package/dist/adaptercloud/pages/cart-owner/CartOwnerProfilePage.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/CartServiceConfigPage.js +0 -634
- package/dist/adaptercloud/pages/cart-owner/CartServiceConfigPage.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/InventoryManagementPage.js +0 -907
- package/dist/adaptercloud/pages/cart-owner/InventoryManagementPage.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/OrderManagementPage.js +0 -875
- package/dist/adaptercloud/pages/cart-owner/OrderManagementPage.js.map +0 -1
- package/dist/adaptercloud/pages/cart-owner/StaffManagementPage.js +0 -827
- package/dist/adaptercloud/pages/cart-owner/StaffManagementPage.js.map +0 -1
- package/dist/adaptercloud/pages/common/HelpSupportPage.js +0 -1329
- package/dist/adaptercloud/pages/common/HelpSupportPage.js.map +0 -1
- package/dist/adaptercloud/pages/common/NotificationCenterPage.js +0 -671
- package/dist/adaptercloud/pages/common/NotificationCenterPage.js.map +0 -1
- package/dist/adaptercloud/pages/common/SettingsPage.js +0 -982
- package/dist/adaptercloud/pages/common/SettingsPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/BookingDetailsPage.js +0 -596
- package/dist/adaptercloud/pages/customer/BookingDetailsPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/CartDetailPage.js +0 -623
- package/dist/adaptercloud/pages/customer/CartDetailPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/CartDiscoveryPage.js +0 -552
- package/dist/adaptercloud/pages/customer/CartDiscoveryPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/CheckoutPage.js +0 -534
- package/dist/adaptercloud/pages/customer/CheckoutPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/CustomerDashboard.js +0 -525
- package/dist/adaptercloud/pages/customer/CustomerDashboard.js.map +0 -1
- package/dist/adaptercloud/pages/customer/CustomerProfilePage.js +0 -623
- package/dist/adaptercloud/pages/customer/CustomerProfilePage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/DirectServiceBookingPage.js +0 -629
- package/dist/adaptercloud/pages/customer/DirectServiceBookingPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/FavoritesPage.js +0 -480
- package/dist/adaptercloud/pages/customer/FavoritesPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/OrderHistoryPage.js +0 -444
- package/dist/adaptercloud/pages/customer/OrderHistoryPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/OrderTrackingPage.js +0 -391
- package/dist/adaptercloud/pages/customer/OrderTrackingPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/PaymentMethodsPage.js +0 -504
- package/dist/adaptercloud/pages/customer/PaymentMethodsPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/ReviewsPage.js +0 -609
- package/dist/adaptercloud/pages/customer/ReviewsPage.js.map +0 -1
- package/dist/adaptercloud/pages/customer/SavedAddressesPage.js +0 -459
- package/dist/adaptercloud/pages/customer/SavedAddressesPage.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestmentDetailsPage.js +0 -851
- package/dist/adaptercloud/pages/investor/InvestmentDetailsPage.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestmentOpportunitiesPage.js +0 -942
- package/dist/adaptercloud/pages/investor/InvestmentOpportunitiesPage.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestorAnalyticsPage.js +0 -683
- package/dist/adaptercloud/pages/investor/InvestorAnalyticsPage.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestorDashboard.js +0 -452
- package/dist/adaptercloud/pages/investor/InvestorDashboard.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestorInvestmentsPage.js +0 -402
- package/dist/adaptercloud/pages/investor/InvestorInvestmentsPage.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestorPortfolioPage.js +0 -571
- package/dist/adaptercloud/pages/investor/InvestorPortfolioPage.js.map +0 -1
- package/dist/adaptercloud/pages/investor/InvestorProfilePage.js +0 -1067
- package/dist/adaptercloud/pages/investor/InvestorProfilePage.js.map +0 -1
- package/dist/adaptercloud/pages/worker/TaskDetailsPage.js +0 -526
- package/dist/adaptercloud/pages/worker/TaskDetailsPage.js.map +0 -1
- package/dist/adaptercloud/pages/worker/WorkerDashboard.js +0 -659
- package/dist/adaptercloud/pages/worker/WorkerDashboard.js.map +0 -1
- package/dist/adaptercloud/pages/worker/WorkerOrdersPage.js +0 -678
- package/dist/adaptercloud/pages/worker/WorkerOrdersPage.js.map +0 -1
- package/dist/adaptercloud/pages/worker/WorkerProfilePage.js +0 -689
- package/dist/adaptercloud/pages/worker/WorkerProfilePage.js.map +0 -1
- package/dist/adaptercloud/pages/worker/WorkerSchedulePage.js +0 -582
- package/dist/adaptercloud/pages/worker/WorkerSchedulePage.js.map +0 -1
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { PageLayout as e } from "../../components/PageLayout.js";
|
|
2
|
+
import { MetricsCard as t } from "../../components/MetricsCard.js";
|
|
3
|
+
import { formatMoney as n } from "../../utils/domain.js";
|
|
4
|
+
import { DataState as r } from "../../components/DataState.js";
|
|
5
|
+
import { useCostRecords as i, useCostSummary as a } from "../../hooks/useAdapterCloudApi.js";
|
|
6
|
+
import { formatDate as o } from "../../utils/formatters.js";
|
|
7
|
+
import { useCallback as s, useMemo as c, useState as l } from "react";
|
|
8
|
+
import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
9
|
+
import { DollarSign as d, Receipt as f, Search as p } from "lucide-react";
|
|
10
|
+
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
11
|
+
import { EmphasisPanel as g, GlassCard as _, ResponsiveTable as v } from "@burdenoff/fe-libs/ui";
|
|
12
|
+
//#region src/adaptercloud/pages/costs/CostsPage.tsx
|
|
13
|
+
function y() {
|
|
14
|
+
let { t: y } = u(), b = s((e, t) => {
|
|
15
|
+
let n = y(e);
|
|
16
|
+
return n === e ? t : n;
|
|
17
|
+
}, [y]), [x, S] = l(""), [C, w] = l(""), T = a(), E = i({ service: C || void 0 }), D = T.data?.currency ?? "USD", O = c(() => [...T.data?.lines ?? []].sort((e, t) => t.total - e.total), [T.data]), k = s((e) => {
|
|
18
|
+
e.preventDefault(), w(x.trim());
|
|
19
|
+
}, [x]), A = [
|
|
20
|
+
{
|
|
21
|
+
key: "service",
|
|
22
|
+
header: b("adaptercloud.costs.service", "Service"),
|
|
23
|
+
priority: "primary",
|
|
24
|
+
cell: (e) => /* @__PURE__ */ m("span", {
|
|
25
|
+
className: "font-medium text-text-primary",
|
|
26
|
+
children: e.service
|
|
27
|
+
})
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: "amount",
|
|
31
|
+
header: b("adaptercloud.costs.amount", "Amount"),
|
|
32
|
+
priority: "primary",
|
|
33
|
+
cell: (e) => /* @__PURE__ */ m("span", {
|
|
34
|
+
className: "tabular-nums text-text-primary",
|
|
35
|
+
children: n(e.amount, e.currency)
|
|
36
|
+
})
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "period",
|
|
40
|
+
header: b("adaptercloud.costs.period", "Period"),
|
|
41
|
+
priority: "secondary",
|
|
42
|
+
cell: (e) => /* @__PURE__ */ h("span", {
|
|
43
|
+
className: "text-sm text-text-secondary",
|
|
44
|
+
children: [
|
|
45
|
+
o(e.periodStart, {
|
|
46
|
+
hour: void 0,
|
|
47
|
+
minute: void 0
|
|
48
|
+
}),
|
|
49
|
+
" –",
|
|
50
|
+
" ",
|
|
51
|
+
o(e.periodEnd, {
|
|
52
|
+
hour: void 0,
|
|
53
|
+
minute: void 0
|
|
54
|
+
})
|
|
55
|
+
]
|
|
56
|
+
})
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "scope",
|
|
60
|
+
header: b("adaptercloud.costs.scope", "Scope"),
|
|
61
|
+
priority: "hidden-on-mobile",
|
|
62
|
+
cell: (e) => /* @__PURE__ */ m("span", {
|
|
63
|
+
className: "text-xs text-text-secondary",
|
|
64
|
+
children: e.resourceId ? b("adaptercloud.costs.resourceScope", "Resource") : e.connectionId ? b("adaptercloud.costs.connectionScope", "Connection") : b("adaptercloud.costs.workspaceScope", "Workspace")
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
return /* @__PURE__ */ h(e, {
|
|
69
|
+
title: b("adaptercloud.costs.title", "FinOps"),
|
|
70
|
+
description: b("adaptercloud.costs.subtitle", "Spend attributed to the same resources you operate — cost and topology share one model."),
|
|
71
|
+
children: [/* @__PURE__ */ h(r, {
|
|
72
|
+
isLoading: T.isLoading,
|
|
73
|
+
isError: T.isError,
|
|
74
|
+
error: T.error,
|
|
75
|
+
onRetry: () => void T.refetch(),
|
|
76
|
+
children: [/* @__PURE__ */ h(g, {
|
|
77
|
+
className: "mb-8",
|
|
78
|
+
children: [/* @__PURE__ */ m("div", {
|
|
79
|
+
className: "mb-4 flex items-center justify-between",
|
|
80
|
+
children: /* @__PURE__ */ m("h2", {
|
|
81
|
+
className: "text-lg font-semibold text-text-primary",
|
|
82
|
+
children: b("adaptercloud.costs.summary", "Cost Summary")
|
|
83
|
+
})
|
|
84
|
+
}), /* @__PURE__ */ h("div", {
|
|
85
|
+
className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
|
|
86
|
+
children: [/* @__PURE__ */ m(t, {
|
|
87
|
+
featured: !0,
|
|
88
|
+
icon: /* @__PURE__ */ m(d, { className: "h-5 w-5" }),
|
|
89
|
+
value: T.data ? n(T.data.total, D) : "—",
|
|
90
|
+
label: b("adaptercloud.costs.totalSpend", "Total Spend")
|
|
91
|
+
}), O.slice(0, 3).map((e) => /* @__PURE__ */ m(t, {
|
|
92
|
+
icon: /* @__PURE__ */ m(f, { className: "h-5 w-5" }),
|
|
93
|
+
value: n(e.total, D),
|
|
94
|
+
label: e.service
|
|
95
|
+
}, e.service))]
|
|
96
|
+
})]
|
|
97
|
+
}), O.length > 0 && /* @__PURE__ */ h("section", {
|
|
98
|
+
className: "mb-8",
|
|
99
|
+
children: [/* @__PURE__ */ m("h2", {
|
|
100
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
101
|
+
children: b("adaptercloud.costs.byService", "Spend by Service")
|
|
102
|
+
}), /* @__PURE__ */ m(_, {
|
|
103
|
+
className: "p-5",
|
|
104
|
+
children: /* @__PURE__ */ m("ul", {
|
|
105
|
+
className: "space-y-3",
|
|
106
|
+
children: O.map((e) => {
|
|
107
|
+
let t = T.data?.total ? Math.round(e.total / T.data.total * 100) : 0;
|
|
108
|
+
return /* @__PURE__ */ h("li", { children: [/* @__PURE__ */ h("div", {
|
|
109
|
+
className: "mb-1 flex items-center justify-between text-sm",
|
|
110
|
+
children: [/* @__PURE__ */ m("span", {
|
|
111
|
+
className: "truncate text-text-primary",
|
|
112
|
+
children: e.service
|
|
113
|
+
}), /* @__PURE__ */ h("span", {
|
|
114
|
+
className: "tabular-nums text-text-secondary",
|
|
115
|
+
children: [
|
|
116
|
+
n(e.total, D),
|
|
117
|
+
" · ",
|
|
118
|
+
t,
|
|
119
|
+
"%"
|
|
120
|
+
]
|
|
121
|
+
})]
|
|
122
|
+
}), /* @__PURE__ */ m("div", {
|
|
123
|
+
className: "h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
124
|
+
children: /* @__PURE__ */ m("div", {
|
|
125
|
+
className: "h-full rounded-full bg-action-primary-bg",
|
|
126
|
+
style: { width: `${Math.max(t, 2)}%` }
|
|
127
|
+
})
|
|
128
|
+
})] }, e.service);
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})]
|
|
132
|
+
})]
|
|
133
|
+
}), /* @__PURE__ */ h("section", { children: [/* @__PURE__ */ h("div", {
|
|
134
|
+
className: "mb-4 flex flex-wrap items-center justify-between gap-3",
|
|
135
|
+
children: [/* @__PURE__ */ m("h2", {
|
|
136
|
+
className: "text-lg font-semibold text-text-primary",
|
|
137
|
+
children: b("adaptercloud.costs.records", "Cost Records")
|
|
138
|
+
}), /* @__PURE__ */ h("form", {
|
|
139
|
+
onSubmit: k,
|
|
140
|
+
className: "relative",
|
|
141
|
+
children: [/* @__PURE__ */ m(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ m("input", {
|
|
142
|
+
type: "text",
|
|
143
|
+
value: x,
|
|
144
|
+
onChange: (e) => S(e.target.value),
|
|
145
|
+
placeholder: b("adaptercloud.costs.filterService", "Filter by service…"),
|
|
146
|
+
className: "w-56 rounded-lg border border-border-subtle bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
|
|
147
|
+
})]
|
|
148
|
+
})]
|
|
149
|
+
}), /* @__PURE__ */ m(r, {
|
|
150
|
+
isLoading: E.isLoading,
|
|
151
|
+
isError: E.isError,
|
|
152
|
+
isEmpty: (E.data ?? []).length === 0,
|
|
153
|
+
error: E.error,
|
|
154
|
+
onRetry: () => void E.refetch(),
|
|
155
|
+
emptyTitle: b("adaptercloud.costs.emptyTitle", "No cost records"),
|
|
156
|
+
emptyDescription: b("adaptercloud.costs.emptyDescription", "Run a cost-ingest sync against a connection to populate the ledger."),
|
|
157
|
+
emptyIcon: /* @__PURE__ */ m(f, { className: "h-10 w-10" }),
|
|
158
|
+
children: /* @__PURE__ */ m(v, {
|
|
159
|
+
data: E.data ?? [],
|
|
160
|
+
columns: A,
|
|
161
|
+
rowKey: (e) => e.id
|
|
162
|
+
})
|
|
163
|
+
})] })]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
//#endregion
|
|
167
|
+
export { y as CostsPage };
|
|
168
|
+
|
|
169
|
+
//# sourceMappingURL=CostsPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CostsPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/costs/CostsPage.tsx"],"sourcesContent":["/**\n * Costs (FinOps) Page\n * @module adaptercloud/pages/costs\n *\n * Workspace FinOps: the cost summary by service (`adaCostSummary`) plus the raw\n * cost-record ledger (`adaListCostRecords`), optionally filtered by service.\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { DollarSign, Search, Receipt } from 'lucide-react';\nimport {\n EmphasisPanel,\n GlassCard,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { DataState } from '../../components/DataState';\nimport { useCostSummary, useCostRecords } from '../../hooks/useAdapterCloudApi';\nimport { formatMoney } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaCostRecord } from '../../types';\n\nexport function CostsPage() {\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 [serviceInput, setServiceInput] = useState('');\n const [appliedService, setAppliedService] = useState('');\n\n const summary = useCostSummary();\n const records = useCostRecords({ service: appliedService || undefined });\n\n const currency = summary.data?.currency ?? 'USD';\n\n const sortedLines = useMemo(\n () => [...(summary.data?.lines ?? [])].sort((a, b) => b.total - a.total),\n [summary.data]\n );\n\n const onSearch = useCallback(\n (e: React.FormEvent) => {\n e.preventDefault();\n setAppliedService(serviceInput.trim());\n },\n [serviceInput]\n );\n\n const recordColumns: ResponsiveTableColumn<AdaCostRecord>[] = [\n {\n key: 'service',\n header: t('adaptercloud.costs.service', 'Service'),\n priority: 'primary',\n cell: (r) => <span className=\"font-medium text-text-primary\">{r.service}</span>,\n },\n {\n key: 'amount',\n header: t('adaptercloud.costs.amount', 'Amount'),\n priority: 'primary',\n cell: (r) => (\n <span className=\"tabular-nums text-text-primary\">{formatMoney(r.amount, r.currency)}</span>\n ),\n },\n {\n key: 'period',\n header: t('adaptercloud.costs.period', 'Period'),\n priority: 'secondary',\n cell: (r) => (\n <span className=\"text-sm text-text-secondary\">\n {formatDate(r.periodStart, { hour: undefined, minute: undefined })} –{' '}\n {formatDate(r.periodEnd, { hour: undefined, minute: undefined })}\n </span>\n ),\n },\n {\n key: 'scope',\n header: t('adaptercloud.costs.scope', 'Scope'),\n priority: 'hidden-on-mobile',\n cell: (r) => (\n <span className=\"text-xs text-text-secondary\">\n {r.resourceId\n ? t('adaptercloud.costs.resourceScope', 'Resource')\n : r.connectionId\n ? t('adaptercloud.costs.connectionScope', 'Connection')\n : t('adaptercloud.costs.workspaceScope', 'Workspace')}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('adaptercloud.costs.title', 'FinOps')}\n description={t(\n 'adaptercloud.costs.subtitle',\n 'Spend attributed to the same resources you operate — cost and topology share one model.'\n )}\n >\n <DataState\n isLoading={summary.isLoading}\n isError={summary.isError}\n error={summary.error}\n onRetry={() => void summary.refetch()}\n >\n {/* Total + by-service */}\n <EmphasisPanel className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.summary', 'Cost Summary')}\n </h2>\n </div>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n featured\n icon={<DollarSign className=\"h-5 w-5\" />}\n value={summary.data ? formatMoney(summary.data.total, currency) : '—'}\n label={t('adaptercloud.costs.totalSpend', 'Total Spend')}\n />\n {sortedLines.slice(0, 3).map((line) => (\n <MetricsCard\n key={line.service}\n icon={<Receipt className=\"h-5 w-5\" />}\n value={formatMoney(line.total, currency)}\n label={line.service}\n />\n ))}\n </div>\n </EmphasisPanel>\n\n {/* Full by-service breakdown */}\n {sortedLines.length > 0 && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.byService', 'Spend by Service')}\n </h2>\n <GlassCard className=\"p-5\">\n <ul className=\"space-y-3\">\n {sortedLines.map((line) => {\n const pct = summary.data?.total\n ? Math.round((line.total / summary.data.total) * 100)\n : 0;\n return (\n <li key={line.service}>\n <div className=\"mb-1 flex items-center justify-between text-sm\">\n <span className=\"truncate text-text-primary\">{line.service}</span>\n <span className=\"tabular-nums text-text-secondary\">\n {formatMoney(line.total, currency)} · {pct}%\n </span>\n </div>\n <div className=\"h-1.5 w-full overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-full rounded-full bg-action-primary-bg\"\n style={{ width: `${Math.max(pct, 2)}%` }}\n />\n </div>\n </li>\n );\n })}\n </ul>\n </GlassCard>\n </section>\n )}\n </DataState>\n\n {/* Cost records ledger */}\n <section>\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {t('adaptercloud.costs.records', 'Cost Records')}\n </h2>\n <form onSubmit={onSearch} className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <input\n type=\"text\"\n value={serviceInput}\n onChange={(e) => setServiceInput(e.target.value)}\n placeholder={t('adaptercloud.costs.filterService', 'Filter by service…')}\n className=\"w-56 rounded-lg border border-border-subtle bg-bg-surface py-2 pl-9 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </form>\n </div>\n\n <DataState\n isLoading={records.isLoading}\n isError={records.isError}\n isEmpty={(records.data ?? []).length === 0}\n error={records.error}\n onRetry={() => void records.refetch()}\n emptyTitle={t('adaptercloud.costs.emptyTitle', 'No cost records')}\n emptyDescription={t(\n 'adaptercloud.costs.emptyDescription',\n 'Run a cost-ingest sync against a connection to populate the ledger.'\n )}\n emptyIcon={<Receipt className=\"h-10 w-10\" />}\n >\n <ResponsiveTable\n data={records.data ?? []}\n columns={recordColumns}\n rowKey={(r) => r.id}\n />\n </DataState>\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { useAdapterCloud as e } from "../../context/AdapterCloudContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { driftStatusLabels as n, severityRank as r } from "../../utils/domain.js";
|
|
4
|
+
import { StatusBadge as i } from "../../components/StatusBadge.js";
|
|
5
|
+
import { SeverityBadge as a } from "../../components/SeverityBadge.js";
|
|
6
|
+
import { DataState as o } from "../../components/DataState.js";
|
|
7
|
+
import { useDriftRecords as s, useRemediateDrift as c } from "../../hooks/useAdapterCloudApi.js";
|
|
8
|
+
import { formatDate as l } from "../../utils/formatters.js";
|
|
9
|
+
import { useCallback as u, useState as d } from "react";
|
|
10
|
+
import { useI18n as f } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { GitCompareArrows as p, Loader2 as m, Wrench as h } from "lucide-react";
|
|
12
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
//#region src/adaptercloud/pages/drift/DriftListPage.tsx
|
|
15
|
+
function y() {
|
|
16
|
+
let { navigateTo: y } = e(), { t: b } = f(), x = u((e, t) => {
|
|
17
|
+
let n = b(e);
|
|
18
|
+
return n === e ? t : n;
|
|
19
|
+
}, [b]), [S, C] = d("DETECTED"), [w, T] = d(null), E = s({ status: S || void 0 }), D = c(), O = [...E.data ?? []].sort((e, t) => r[t.severity] - r[e.severity]), k = u((e) => {
|
|
20
|
+
T(null), D.mutate(e, { onError: (e) => T(e instanceof Error ? e.message : "Failed to remediate drift.") });
|
|
21
|
+
}, [D]);
|
|
22
|
+
return /* @__PURE__ */ _(t, {
|
|
23
|
+
title: x("adaptercloud.drift.title", "Drift"),
|
|
24
|
+
description: x("adaptercloud.drift.subtitle", "Divergences between observed and desired state, surfaced during reconciliation."),
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ g("div", {
|
|
27
|
+
className: "mb-6 flex flex-wrap gap-3",
|
|
28
|
+
children: /* @__PURE__ */ _("select", {
|
|
29
|
+
value: S,
|
|
30
|
+
onChange: (e) => C(e.target.value),
|
|
31
|
+
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)]",
|
|
32
|
+
children: [/* @__PURE__ */ g("option", {
|
|
33
|
+
value: "",
|
|
34
|
+
children: x("adaptercloud.drift.allStatuses", "All statuses")
|
|
35
|
+
}), Object.keys(n).map((e) => /* @__PURE__ */ g("option", {
|
|
36
|
+
value: e,
|
|
37
|
+
children: n[e]
|
|
38
|
+
}, e))]
|
|
39
|
+
})
|
|
40
|
+
}),
|
|
41
|
+
w && /* @__PURE__ */ g("p", {
|
|
42
|
+
className: "mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text",
|
|
43
|
+
children: w
|
|
44
|
+
}),
|
|
45
|
+
/* @__PURE__ */ g(o, {
|
|
46
|
+
isLoading: E.isLoading,
|
|
47
|
+
isError: E.isError,
|
|
48
|
+
isEmpty: O.length === 0,
|
|
49
|
+
error: E.error,
|
|
50
|
+
onRetry: () => void E.refetch(),
|
|
51
|
+
emptyTitle: x("adaptercloud.drift.emptyTitle", "No drift"),
|
|
52
|
+
emptyDescription: x("adaptercloud.drift.emptyDescription", "Nothing matches this filter — observed state matches desired state."),
|
|
53
|
+
emptyIcon: /* @__PURE__ */ g(p, { className: "h-10 w-10" }),
|
|
54
|
+
children: /* @__PURE__ */ g("div", {
|
|
55
|
+
className: "space-y-3",
|
|
56
|
+
children: O.map((e) => {
|
|
57
|
+
let t = e.status === "DETECTED" || e.status === "ACKNOWLEDGED";
|
|
58
|
+
return /* @__PURE__ */ g(v, {
|
|
59
|
+
className: "p-5",
|
|
60
|
+
children: /* @__PURE__ */ _("div", {
|
|
61
|
+
className: "flex flex-wrap items-start justify-between gap-3",
|
|
62
|
+
children: [/* @__PURE__ */ _("div", {
|
|
63
|
+
className: "min-w-0 flex-1",
|
|
64
|
+
children: [
|
|
65
|
+
/* @__PURE__ */ _("div", {
|
|
66
|
+
className: "mb-1.5 flex flex-wrap items-center gap-2",
|
|
67
|
+
children: [/* @__PURE__ */ g(a, { severity: e.severity }), /* @__PURE__ */ g(i, {
|
|
68
|
+
status: e.status,
|
|
69
|
+
label: n[e.status]
|
|
70
|
+
})]
|
|
71
|
+
}),
|
|
72
|
+
/* @__PURE__ */ g("p", {
|
|
73
|
+
className: "break-all font-mono text-sm font-medium text-text-primary",
|
|
74
|
+
children: e.attributePath
|
|
75
|
+
}),
|
|
76
|
+
/* @__PURE__ */ _("div", {
|
|
77
|
+
className: "mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2",
|
|
78
|
+
children: [/* @__PURE__ */ _("div", {
|
|
79
|
+
className: "rounded-lg bg-bg-sunken p-3",
|
|
80
|
+
children: [/* @__PURE__ */ g("p", {
|
|
81
|
+
className: "mb-1 text-xs font-semibold text-text-secondary",
|
|
82
|
+
children: x("adaptercloud.drift.expected", "Expected")
|
|
83
|
+
}), /* @__PURE__ */ g("pre", {
|
|
84
|
+
className: "overflow-auto text-xs text-text-primary",
|
|
85
|
+
children: JSON.stringify(e.expectedValue, null, 2)
|
|
86
|
+
})]
|
|
87
|
+
}), /* @__PURE__ */ _("div", {
|
|
88
|
+
className: "rounded-lg bg-bg-sunken p-3",
|
|
89
|
+
children: [/* @__PURE__ */ g("p", {
|
|
90
|
+
className: "mb-1 text-xs font-semibold text-text-secondary",
|
|
91
|
+
children: x("adaptercloud.drift.observed", "Observed")
|
|
92
|
+
}), /* @__PURE__ */ g("pre", {
|
|
93
|
+
className: "overflow-auto text-xs text-text-primary",
|
|
94
|
+
children: JSON.stringify(e.observedValue, null, 2)
|
|
95
|
+
})]
|
|
96
|
+
})]
|
|
97
|
+
}),
|
|
98
|
+
/* @__PURE__ */ _("p", {
|
|
99
|
+
className: "mt-2 text-xs text-text-secondary",
|
|
100
|
+
children: [
|
|
101
|
+
x("adaptercloud.drift.detected", "Detected"),
|
|
102
|
+
" ",
|
|
103
|
+
l(e.detectedAt),
|
|
104
|
+
" · ",
|
|
105
|
+
/* @__PURE__ */ g("button", {
|
|
106
|
+
type: "button",
|
|
107
|
+
onClick: () => y(`/resources/${e.resourceId}`),
|
|
108
|
+
className: "hover:underline",
|
|
109
|
+
children: x("adaptercloud.drift.viewResource", "View resource")
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
})
|
|
113
|
+
]
|
|
114
|
+
}), t && /* @__PURE__ */ _("button", {
|
|
115
|
+
type: "button",
|
|
116
|
+
disabled: D.isPending,
|
|
117
|
+
onClick: () => k(e.id),
|
|
118
|
+
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-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50",
|
|
119
|
+
children: [D.isPending ? /* @__PURE__ */ g(m, { className: "h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ g(h, { className: "h-3.5 w-3.5" }), x("adaptercloud.drift.remediate", "Remediate")]
|
|
120
|
+
})]
|
|
121
|
+
})
|
|
122
|
+
}, e.id);
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
130
|
+
export { y as DriftListPage };
|
|
131
|
+
|
|
132
|
+
//# sourceMappingURL=DriftListPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DriftListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/drift/DriftListPage.tsx"],"sourcesContent":["/**\n * Drift Board Page\n * @module adaptercloud/pages/drift\n *\n * Divergences between observed and desired state (`adaListDriftRecords`).\n * Filters by status; each detected/acknowledged record can be remediated via\n * `adaRemediateDrift`.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { GitCompareArrows, Wrench, Loader2 } from 'lucide-react';\nimport { GlassCard } 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 { useDriftRecords, useRemediateDrift } from '../../hooks/useAdapterCloudApi';\nimport { driftStatusLabels, severityRank } from '../../utils/domain';\nimport { formatDate } from '../../utils/formatters';\nimport type { AdaDriftStatus } from '../../types';\n\nexport function DriftListPage() {\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<AdaDriftStatus | ''>('DETECTED');\n const [actionError, setActionError] = useState<string | null>(null);\n\n const query = useDriftRecords({ status: status || undefined });\n const remediate = useRemediateDrift();\n\n const items = [...(query.data ?? [])].sort(\n (a, b) => severityRank[b.severity] - severityRank[a.severity]\n );\n\n const handleRemediate = useCallback(\n (id: string) => {\n setActionError(null);\n remediate.mutate(id, {\n onError: (err) =>\n setActionError(err instanceof Error ? err.message : 'Failed to remediate drift.'),\n });\n },\n [remediate]\n );\n\n return (\n <PageLayout\n title={t('adaptercloud.drift.title', 'Drift')}\n description={t(\n 'adaptercloud.drift.subtitle',\n 'Divergences between observed and desired state, surfaced during reconciliation.'\n )}\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={status}\n onChange={(e) => setStatus(e.target.value as AdaDriftStatus | '')}\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.drift.allStatuses', 'All statuses')}</option>\n {(Object.keys(driftStatusLabels) as AdaDriftStatus[]).map((s) => (\n <option key={s} value={s}>\n {driftStatusLabels[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.drift.emptyTitle', 'No drift')}\n emptyDescription={t(\n 'adaptercloud.drift.emptyDescription',\n 'Nothing matches this filter — observed state matches desired state.'\n )}\n emptyIcon={<GitCompareArrows className=\"h-10 w-10\" />}\n >\n <div className=\"space-y-3\">\n {items.map((d) => {\n const canRemediate = d.status === 'DETECTED' || d.status === 'ACKNOWLEDGED';\n return (\n <GlassCard key={d.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={d.severity} />\n <StatusBadge status={d.status} label={driftStatusLabels[d.status]} />\n </div>\n <p className=\"break-all font-mono text-sm font-medium text-text-primary\">\n {d.attributePath}\n </p>\n <div className=\"mt-3 grid grid-cols-1 gap-3 sm:grid-cols-2\">\n <div className=\"rounded-lg bg-bg-sunken p-3\">\n <p className=\"mb-1 text-xs font-semibold text-text-secondary\">\n {t('adaptercloud.drift.expected', 'Expected')}\n </p>\n <pre className=\"overflow-auto text-xs text-text-primary\">\n {JSON.stringify(d.expectedValue, null, 2)}\n </pre>\n </div>\n <div className=\"rounded-lg bg-bg-sunken p-3\">\n <p className=\"mb-1 text-xs font-semibold text-text-secondary\">\n {t('adaptercloud.drift.observed', 'Observed')}\n </p>\n <pre className=\"overflow-auto text-xs text-text-primary\">\n {JSON.stringify(d.observedValue, null, 2)}\n </pre>\n </div>\n </div>\n <p className=\"mt-2 text-xs text-text-secondary\">\n {t('adaptercloud.drift.detected', 'Detected')} {formatDate(d.detectedAt)}\n {' · '}\n <button\n type=\"button\"\n onClick={() => navigateTo(`/resources/${d.resourceId}`)}\n className=\"hover:underline\"\n >\n {t('adaptercloud.drift.viewResource', 'View resource')}\n </button>\n </p>\n </div>\n\n {canRemediate && (\n <button\n type=\"button\"\n disabled={remediate.isPending}\n onClick={() => handleRemediate(d.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-success-text transition-colors hover:bg-status-success-bg-subtle disabled:opacity-50\"\n >\n {remediate.isPending ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Wrench className=\"h-3.5 w-3.5\" />\n )}\n {t('adaptercloud.drift.remediate', 'Remediate')}\n </button>\n )}\n </div>\n </GlassCard>\n );\n })}\n </div>\n </DataState>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { useAdapterCloud as e } from "../../context/AdapterCloudContext.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { PageLayout as n } from "../../components/PageLayout.js";
|
|
4
|
+
import { policyEnforcementLabels as r, policyKindLabels as i } from "../../utils/domain.js";
|
|
5
|
+
import { SeverityBadge as a } from "../../components/SeverityBadge.js";
|
|
6
|
+
import { DataState as o } from "../../components/DataState.js";
|
|
7
|
+
import { usePolicies as s } from "../../hooks/useAdapterCloudApi.js";
|
|
8
|
+
import { formatRelativeTime as c } from "../../utils/formatters.js";
|
|
9
|
+
import { useCallback as l, useState as u } from "react";
|
|
10
|
+
import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { ChevronLeft as f, ChevronRight as p, Plus as m, ScrollText as h } from "lucide-react";
|
|
12
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
13
|
+
import { GlassCard as v } from "@burdenoff/fe-libs/ui";
|
|
14
|
+
//#region src/adaptercloud/pages/policies/PoliciesListPage.tsx
|
|
15
|
+
var y = 20;
|
|
16
|
+
function b() {
|
|
17
|
+
let { navigateTo: b } = e(), { t: x } = d(), S = l((e, t) => {
|
|
18
|
+
let n = x(e);
|
|
19
|
+
return n === e ? t : n;
|
|
20
|
+
}, [x]), [C, w] = u(""), [T, E] = u("all"), [D, O] = u(0), k = s({
|
|
21
|
+
kind: C || void 0,
|
|
22
|
+
isActive: T === "all" ? void 0 : T === "active",
|
|
23
|
+
pagination: {
|
|
24
|
+
limit: y,
|
|
25
|
+
offset: D
|
|
26
|
+
}
|
|
27
|
+
}), A = k.data?.items ?? [], j = k.data?.pageInfo.hasMore ?? !1;
|
|
28
|
+
return /* @__PURE__ */ _(n, {
|
|
29
|
+
title: S("adaptercloud.policies.title", "Policies"),
|
|
30
|
+
description: S("adaptercloud.policies.subtitle", "Governance rules that evaluate the inventory — guardrail, tagging, security, cost, compliance and naming."),
|
|
31
|
+
actions: /* @__PURE__ */ _("button", {
|
|
32
|
+
type: "button",
|
|
33
|
+
onClick: () => b("/policies/new"),
|
|
34
|
+
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",
|
|
35
|
+
children: [/* @__PURE__ */ g(m, { className: "h-4 w-4" }), S("adaptercloud.policies.new", "New policy")]
|
|
36
|
+
}),
|
|
37
|
+
children: [/* @__PURE__ */ _("div", {
|
|
38
|
+
className: "mb-6 flex flex-wrap gap-3",
|
|
39
|
+
children: [/* @__PURE__ */ _("select", {
|
|
40
|
+
value: C,
|
|
41
|
+
onChange: (e) => {
|
|
42
|
+
w(e.target.value), O(0);
|
|
43
|
+
},
|
|
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)]",
|
|
45
|
+
children: [/* @__PURE__ */ g("option", {
|
|
46
|
+
value: "",
|
|
47
|
+
children: S("adaptercloud.policies.allKinds", "All kinds")
|
|
48
|
+
}), Object.keys(i).map((e) => /* @__PURE__ */ g("option", {
|
|
49
|
+
value: e,
|
|
50
|
+
children: i[e]
|
|
51
|
+
}, e))]
|
|
52
|
+
}), /* @__PURE__ */ _("select", {
|
|
53
|
+
value: T,
|
|
54
|
+
onChange: (e) => {
|
|
55
|
+
E(e.target.value), O(0);
|
|
56
|
+
},
|
|
57
|
+
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)]",
|
|
58
|
+
children: [
|
|
59
|
+
/* @__PURE__ */ g("option", {
|
|
60
|
+
value: "all",
|
|
61
|
+
children: S("adaptercloud.policies.allStates", "All")
|
|
62
|
+
}),
|
|
63
|
+
/* @__PURE__ */ g("option", {
|
|
64
|
+
value: "active",
|
|
65
|
+
children: S("adaptercloud.policies.active", "Active")
|
|
66
|
+
}),
|
|
67
|
+
/* @__PURE__ */ g("option", {
|
|
68
|
+
value: "inactive",
|
|
69
|
+
children: S("adaptercloud.policies.inactive", "Inactive")
|
|
70
|
+
})
|
|
71
|
+
]
|
|
72
|
+
})]
|
|
73
|
+
}), /* @__PURE__ */ _(o, {
|
|
74
|
+
isLoading: k.isLoading,
|
|
75
|
+
isError: k.isError,
|
|
76
|
+
isEmpty: A.length === 0,
|
|
77
|
+
error: k.error,
|
|
78
|
+
onRetry: () => void k.refetch(),
|
|
79
|
+
emptyTitle: S("adaptercloud.policies.emptyTitle", "No policies yet"),
|
|
80
|
+
emptyDescription: S("adaptercloud.policies.emptyDescription", "Author your first governance policy to put compliance into the system."),
|
|
81
|
+
emptyIcon: /* @__PURE__ */ g(h, { className: "h-10 w-10" }),
|
|
82
|
+
emptyAction: /* @__PURE__ */ g("button", {
|
|
83
|
+
type: "button",
|
|
84
|
+
onClick: () => b("/policies/new"),
|
|
85
|
+
className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:opacity-90",
|
|
86
|
+
children: S("adaptercloud.policies.new", "New policy")
|
|
87
|
+
}),
|
|
88
|
+
children: [/* @__PURE__ */ g("div", {
|
|
89
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
90
|
+
children: A.map((e) => /* @__PURE__ */ _(v, {
|
|
91
|
+
role: "button",
|
|
92
|
+
tabIndex: 0,
|
|
93
|
+
onClick: () => b(`/policies/${e.id}`),
|
|
94
|
+
onKeyDown: (t) => {
|
|
95
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), b(`/policies/${e.id}`));
|
|
96
|
+
},
|
|
97
|
+
className: "cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
98
|
+
children: [
|
|
99
|
+
/* @__PURE__ */ _("div", {
|
|
100
|
+
className: "flex items-start justify-between gap-2",
|
|
101
|
+
children: [/* @__PURE__ */ _("div", {
|
|
102
|
+
className: "min-w-0",
|
|
103
|
+
children: [/* @__PURE__ */ g("h3", {
|
|
104
|
+
className: "truncate text-base font-semibold text-text-primary",
|
|
105
|
+
children: e.name
|
|
106
|
+
}), /* @__PURE__ */ g("p", {
|
|
107
|
+
className: "mt-0.5 text-xs text-text-secondary",
|
|
108
|
+
children: i[e.kind]
|
|
109
|
+
})]
|
|
110
|
+
}), /* @__PURE__ */ g(a, { severity: e.severity })]
|
|
111
|
+
}),
|
|
112
|
+
e.description && /* @__PURE__ */ g("p", {
|
|
113
|
+
className: "mt-3 line-clamp-2 text-sm text-text-secondary",
|
|
114
|
+
children: e.description
|
|
115
|
+
}),
|
|
116
|
+
/* @__PURE__ */ _("div", {
|
|
117
|
+
className: "mt-4 flex items-center gap-2",
|
|
118
|
+
children: [/* @__PURE__ */ g("span", {
|
|
119
|
+
className: "inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary",
|
|
120
|
+
children: r[e.enforcement]
|
|
121
|
+
}), /* @__PURE__ */ _("span", {
|
|
122
|
+
className: t("inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium", e.isActive ? "bg-status-success-bg-subtle text-status-success-text" : "bg-bg-sunken text-text-secondary"),
|
|
123
|
+
children: [/* @__PURE__ */ g("span", { className: t("h-1.5 w-1.5 rounded-full", e.isActive ? "bg-status-success-text" : "bg-text-secondary") }), e.isActive ? S("adaptercloud.policies.active", "Active") : S("adaptercloud.policies.inactive", "Inactive")]
|
|
124
|
+
})]
|
|
125
|
+
}),
|
|
126
|
+
/* @__PURE__ */ _("p", {
|
|
127
|
+
className: "mt-4 text-xs text-text-secondary",
|
|
128
|
+
children: [
|
|
129
|
+
S("adaptercloud.common.updated", "Updated"),
|
|
130
|
+
" ",
|
|
131
|
+
c(e.updatedAt)
|
|
132
|
+
]
|
|
133
|
+
})
|
|
134
|
+
]
|
|
135
|
+
}, e.id))
|
|
136
|
+
}), (D > 0 || j) && /* @__PURE__ */ _("div", {
|
|
137
|
+
className: "mt-6 flex items-center justify-between",
|
|
138
|
+
children: [
|
|
139
|
+
/* @__PURE__ */ _("button", {
|
|
140
|
+
type: "button",
|
|
141
|
+
disabled: D === 0,
|
|
142
|
+
onClick: () => O((e) => Math.max(0, e - y)),
|
|
143
|
+
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",
|
|
144
|
+
children: [/* @__PURE__ */ g(f, { className: "h-4 w-4" }), S("adaptercloud.common.prev", "Previous")]
|
|
145
|
+
}),
|
|
146
|
+
/* @__PURE__ */ _("span", {
|
|
147
|
+
className: "text-sm text-text-secondary",
|
|
148
|
+
children: [
|
|
149
|
+
S("adaptercloud.common.showing", "Showing"),
|
|
150
|
+
" ",
|
|
151
|
+
D + 1,
|
|
152
|
+
"–",
|
|
153
|
+
D + A.length
|
|
154
|
+
]
|
|
155
|
+
}),
|
|
156
|
+
/* @__PURE__ */ _("button", {
|
|
157
|
+
type: "button",
|
|
158
|
+
disabled: !j,
|
|
159
|
+
onClick: () => O((e) => e + y),
|
|
160
|
+
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",
|
|
161
|
+
children: [S("adaptercloud.common.next", "Next"), /* @__PURE__ */ g(p, { className: "h-4 w-4" })]
|
|
162
|
+
})
|
|
163
|
+
]
|
|
164
|
+
})]
|
|
165
|
+
})]
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
export { b as PoliciesListPage };
|
|
170
|
+
|
|
171
|
+
//# sourceMappingURL=PoliciesListPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PoliciesListPage.js","names":[],"sources":["../../../../src/adaptercloud/pages/policies/PoliciesListPage.tsx"],"sourcesContent":["/**\n * Policies List Page\n * @module adaptercloud/pages/policies\n *\n * Governance rules evaluated against the inventory (`adaListPolicies`). Filters\n * by kind and active flag; offset pagination.\n */\n\nimport { useState, useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ScrollText, Plus, ChevronLeft, ChevronRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { SeverityBadge } from '../../components/SeverityBadge';\nimport { DataState } from '../../components/DataState';\nimport { useAdapterCloud } from '../../context/AdapterCloudContext';\nimport { usePolicies } from '../../hooks/useAdapterCloudApi';\nimport { policyKindLabels, policyEnforcementLabels } from '../../utils/domain';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { AdaPolicyKind } from '../../types';\n\nconst PAGE_SIZE = 20;\n\ntype ActiveFilter = 'all' | 'active' | 'inactive';\n\nexport function PoliciesListPage() {\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 [kind, setKind] = useState<AdaPolicyKind | ''>('');\n const [active, setActive] = useState<ActiveFilter>('all');\n const [offset, setOffset] = useState(0);\n\n const query = usePolicies({\n kind: kind || undefined,\n isActive: active === 'all' ? undefined : active === 'active',\n pagination: { limit: PAGE_SIZE, offset },\n });\n\n const items = query.data?.items ?? [];\n const hasMore = query.data?.pageInfo.hasMore ?? false;\n\n return (\n <PageLayout\n title={t('adaptercloud.policies.title', 'Policies')}\n description={t(\n 'adaptercloud.policies.subtitle',\n 'Governance rules that evaluate the inventory — guardrail, tagging, security, cost, compliance and naming.'\n )}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies/new')}\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\"\n >\n <Plus className=\"h-4 w-4\" />\n {t('adaptercloud.policies.new', 'New policy')}\n </button>\n }\n >\n <div className=\"mb-6 flex flex-wrap gap-3\">\n <select\n value={kind}\n onChange={(e) => {\n setKind(e.target.value as AdaPolicyKind | '');\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.policies.allKinds', 'All kinds')}</option>\n {(Object.keys(policyKindLabels) as AdaPolicyKind[]).map((k) => (\n <option key={k} value={k}>\n {policyKindLabels[k]}\n </option>\n ))}\n </select>\n <select\n value={active}\n onChange={(e) => {\n setActive(e.target.value as ActiveFilter);\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=\"all\">{t('adaptercloud.policies.allStates', 'All')}</option>\n <option value=\"active\">{t('adaptercloud.policies.active', 'Active')}</option>\n <option value=\"inactive\">{t('adaptercloud.policies.inactive', 'Inactive')}</option>\n </select>\n </div>\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.policies.emptyTitle', 'No policies yet')}\n emptyDescription={t(\n 'adaptercloud.policies.emptyDescription',\n 'Author your first governance policy to put compliance into the system.'\n )}\n emptyIcon={<ScrollText className=\"h-10 w-10\" />}\n emptyAction={\n <button\n type=\"button\"\n onClick={() => navigateTo('/policies/new')}\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.policies.new', 'New policy')}\n </button>\n }\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {items.map((policy) => (\n <GlassCard\n key={policy.id}\n role=\"button\"\n tabIndex={0}\n onClick={() => navigateTo(`/policies/${policy.id}`)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigateTo(`/policies/${policy.id}`);\n }\n }}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <h3 className=\"truncate text-base font-semibold text-text-primary\">\n {policy.name}\n </h3>\n <p className=\"mt-0.5 text-xs text-text-secondary\">\n {policyKindLabels[policy.kind]}\n </p>\n </div>\n <SeverityBadge severity={policy.severity} />\n </div>\n\n {policy.description && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{policy.description}</p>\n )}\n\n <div className=\"mt-4 flex items-center gap-2\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\">\n {policyEnforcementLabels[policy.enforcement]}\n </span>\n <span\n className={cn(\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-xs font-medium',\n policy.isActive\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n )}\n >\n <span\n className={cn(\n 'h-1.5 w-1.5 rounded-full',\n policy.isActive ? 'bg-status-success-text' : 'bg-text-secondary'\n )}\n />\n {policy.isActive\n ? t('adaptercloud.policies.active', 'Active')\n : t('adaptercloud.policies.inactive', 'Inactive')}\n </span>\n </div>\n\n <p className=\"mt-4 text-xs text-text-secondary\">\n {t('adaptercloud.common.updated', 'Updated')} {formatRelativeTime(policy.updatedAt)}\n </p>\n </GlassCard>\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":"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"}
|