@burdenoff/microfe-vibecontrols 2026.530.1 → 2026.530.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/components/gitops/VibeGitOpsTab.js +82 -86
- package/dist/components/gitops/VibeGitOpsTab.js.map +1 -1
- package/dist/components/security/SecurityScanList.js +15 -22
- package/dist/components/security/SecurityScanList.js.map +1 -1
- package/dist/components/security/SecurityTabPanel.js +1 -3
- package/dist/components/security/SecurityTabPanel.js.map +1 -1
- package/dist/components/security/agentClient.js +41 -37
- package/dist/components/security/agentClient.js.map +1 -1
- package/dist/components/security/hooks.js +11 -12
- package/dist/components/security/hooks.js.map +1 -1
- package/dist/generated/wspace-operations.js +1735 -1564
- package/dist/generated/wspace-operations.js.map +1 -1
- package/dist/generated/wspace-types.js.map +1 -1
- package/dist/hooks/useGitops.js +89 -79
- package/dist/hooks/useGitops.js.map +1 -1
- package/dist/index.js +10 -10
- package/dist/pages/VibeDetailsPage.js +570 -568
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/services/gitopsApi.js +123 -69
- package/dist/services/gitopsApi.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,41 +5,37 @@ import "../shared/index.js";
|
|
|
5
5
|
import { appendIframeTokenQuery as n, mintIframeToken as r, scheduleIframeTokenRefresh as i } from "../../services/iframeToken.js";
|
|
6
6
|
import { RepoStatsTab as a } from "./RepoStatsTab.js";
|
|
7
7
|
import { CITab as o } from "./CITab.js";
|
|
8
|
-
import { useGitopsContributors as ee, useGitopsIssues as te, useGitopsLanguages as ne, useGitopsPipelineAnalytics as re, useGitopsProviders as ie, useGitopsPullRequests as ae, useGitopsRepos as s, useGitopsRuns as oe, useGitopsSecurityAlerts as
|
|
9
|
-
import { GitOpsSetup as
|
|
10
|
-
import { GitOpsProviderPicker as
|
|
11
|
-
import { composeCiStatus as
|
|
12
|
-
import { useCallback as
|
|
13
|
-
import { BarChart3 as
|
|
14
|
-
import { useQueryClient as
|
|
15
|
-
import { motion as
|
|
16
|
-
import { Fragment as
|
|
8
|
+
import { useGitopsContributors as ee, useGitopsIssues as te, useGitopsLanguages as ne, useGitopsPipelineAnalytics as re, useGitopsProviders as ie, useGitopsPullRequests as ae, useGitopsRepos as s, useGitopsRuns as oe, useGitopsSecurityAlerts as se } from "../../hooks/useGitops.js";
|
|
9
|
+
import { GitOpsSetup as ce } from "./GitOpsSetup.js";
|
|
10
|
+
import { GitOpsProviderPicker as le } from "./GitOpsProviderPicker.js";
|
|
11
|
+
import { composeCiStatus as c, composeRepoStats as l } from "./types.js";
|
|
12
|
+
import { useCallback as u, useEffect as d, useMemo as f, useState as p } from "react";
|
|
13
|
+
import { BarChart3 as ue, GitBranch as de, GitMerge as m, Play as h, RefreshCw as g } from "lucide-react";
|
|
14
|
+
import { useQueryClient as _ } from "@tanstack/react-query";
|
|
15
|
+
import { motion as fe } from "framer-motion";
|
|
16
|
+
import { Fragment as v, jsx as y, jsxs as b } from "react/jsx-runtime";
|
|
17
17
|
//#region src/components/gitops/VibeGitOpsTab.tsx
|
|
18
|
-
var
|
|
19
|
-
let [O,
|
|
20
|
-
|
|
21
|
-
apiKey: E,
|
|
22
|
-
profile: w,
|
|
18
|
+
var x = ({ vibeId: x, agentId: w, profile: T, tunnelUrl: E, tr: D }) => {
|
|
19
|
+
let [O, pe] = p("repo-stats"), [k, A] = p(!1), [j, M] = p(null), [N, me] = p(null), { workspaceId: P } = e(), F = _(), I = f(() => ({
|
|
20
|
+
profile: T,
|
|
23
21
|
workspaceId: P ?? "",
|
|
24
|
-
agentId:
|
|
22
|
+
agentId: w
|
|
25
23
|
}), [
|
|
26
24
|
T,
|
|
27
|
-
E,
|
|
28
|
-
w,
|
|
29
25
|
P,
|
|
30
|
-
|
|
31
|
-
]), L = ie(I), R =
|
|
32
|
-
|
|
26
|
+
w
|
|
27
|
+
]), L = ie(I), R = f(() => L.data?.filter((e) => e.registered && e.authenticated) ?? [], [L.data]), z = R.length > 0;
|
|
28
|
+
d(() => {
|
|
33
29
|
if (!j && R.length > 0) {
|
|
34
30
|
let e = R[0];
|
|
35
31
|
e && M(e.name);
|
|
36
32
|
}
|
|
37
33
|
}, [R, j]);
|
|
38
34
|
let B = s(I, j, { limit: 30 });
|
|
39
|
-
|
|
35
|
+
d(() => {
|
|
40
36
|
if (!N && B.data?.items.length) {
|
|
41
37
|
let e = B.data.items[0];
|
|
42
|
-
e &&
|
|
38
|
+
e && me(e.fqn);
|
|
43
39
|
}
|
|
44
40
|
}, [B.data, N]);
|
|
45
41
|
let V = s(I, j, { limit: 1 });
|
|
@@ -50,11 +46,11 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
50
46
|
}), U = te(I, j, N, {
|
|
51
47
|
state: "open",
|
|
52
48
|
limit: 100
|
|
53
|
-
}), W = ne(I, j, N), G = ee(I, j, N, 20), K =
|
|
49
|
+
}), W = ne(I, j, N), G = ee(I, j, N, 20), K = se(I, j, N), q = f(() => N ? V.data?.items.find((e) => e.fqn === N) ?? B.data?.items.find((e) => e.fqn === N) ?? null : V.data?.items[0] ?? null, [
|
|
54
50
|
V.data,
|
|
55
51
|
B.data,
|
|
56
52
|
N
|
|
57
|
-
]),
|
|
53
|
+
]), he = f(() => l({
|
|
58
54
|
repo: q,
|
|
59
55
|
pulls: H.data,
|
|
60
56
|
issues: U.data,
|
|
@@ -68,27 +64,27 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
68
64
|
W.data,
|
|
69
65
|
G.data,
|
|
70
66
|
K.data
|
|
71
|
-
]), J = oe(I, j, N, { limit: 30 }), Y = re(I, j, N),
|
|
67
|
+
]), J = oe(I, j, N, { limit: 30 }), Y = re(I, j, N), ge = f(() => c({
|
|
72
68
|
pipelines: void 0,
|
|
73
69
|
runs: J.data,
|
|
74
70
|
analytics: Y.data
|
|
75
|
-
}), [J.data, Y.data]),
|
|
71
|
+
}), [J.data, Y.data]), _e = H.isLoading || U.isLoading || W.isLoading, ve = J.isLoading || Y.isLoading, ye = (H.error || U.error || W.error)?.message, be = (J.error || Y.error)?.message, xe = u(() => {
|
|
76
72
|
F.invalidateQueries({ queryKey: [
|
|
77
73
|
"gitops",
|
|
78
74
|
P ?? "",
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
w,
|
|
76
|
+
T
|
|
81
77
|
] });
|
|
82
78
|
}, [
|
|
83
79
|
F,
|
|
84
80
|
P,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
]), X = H.isFetching || J.isFetching,
|
|
81
|
+
w,
|
|
82
|
+
T
|
|
83
|
+
]), X = H.isFetching || J.isFetching, Se = [
|
|
88
84
|
{
|
|
89
85
|
id: "repo-stats",
|
|
90
86
|
label: D("gitops.repoStats", "Repo Stats"),
|
|
91
|
-
icon:
|
|
87
|
+
icon: ue
|
|
92
88
|
},
|
|
93
89
|
{
|
|
94
90
|
id: "ci",
|
|
@@ -98,35 +94,35 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
98
94
|
{
|
|
99
95
|
id: "git-ui",
|
|
100
96
|
label: D("gitops.gitUI", "Git UI"),
|
|
101
|
-
icon:
|
|
97
|
+
icon: de
|
|
102
98
|
}
|
|
103
|
-
], [Z, Q] =
|
|
104
|
-
return
|
|
99
|
+
], [Z, Q] = p(null), [$, Ce] = p(null);
|
|
100
|
+
return d(() => {
|
|
105
101
|
let e = !1, t = null;
|
|
106
102
|
return (async () => {
|
|
107
|
-
if (!
|
|
103
|
+
if (!w || !E || O !== "git-ui") {
|
|
108
104
|
Q(null);
|
|
109
105
|
return;
|
|
110
106
|
}
|
|
111
107
|
try {
|
|
112
|
-
let a = await r(
|
|
108
|
+
let a = await r(w, T, "/ungit");
|
|
113
109
|
if (e) return;
|
|
114
|
-
let o = `${
|
|
115
|
-
Q(n(o, a.token)), t = i(
|
|
110
|
+
let o = `${E.replace(/\/$/, "")}/ungit/`;
|
|
111
|
+
Q(n(o, a.token)), t = i(w, T, "/ungit", a, (e) => {
|
|
116
112
|
Q(n(o, e.token));
|
|
117
113
|
});
|
|
118
114
|
} catch (t) {
|
|
119
|
-
e ||
|
|
115
|
+
e || Ce(t instanceof Error ? t.message : "Failed to fetch iframe token");
|
|
120
116
|
}
|
|
121
117
|
})(), () => {
|
|
122
118
|
e = !0, t?.cancel();
|
|
123
119
|
};
|
|
124
120
|
}, [
|
|
125
|
-
C,
|
|
126
121
|
w,
|
|
127
122
|
T,
|
|
123
|
+
E,
|
|
128
124
|
O
|
|
129
|
-
]), /* @__PURE__ */
|
|
125
|
+
]), /* @__PURE__ */ b(fe.div, {
|
|
130
126
|
initial: {
|
|
131
127
|
opacity: 0,
|
|
132
128
|
y: 8
|
|
@@ -141,32 +137,32 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
141
137
|
},
|
|
142
138
|
transition: { duration: .2 },
|
|
143
139
|
className: "space-y-4",
|
|
144
|
-
"data-vibe-id":
|
|
145
|
-
children: [/* @__PURE__ */
|
|
140
|
+
"data-vibe-id": x,
|
|
141
|
+
children: [/* @__PURE__ */ b("div", {
|
|
146
142
|
className: "flex items-center justify-between",
|
|
147
|
-
children: [/* @__PURE__ */
|
|
143
|
+
children: [/* @__PURE__ */ b("div", {
|
|
148
144
|
className: "flex items-center gap-2",
|
|
149
|
-
children: [/* @__PURE__ */
|
|
145
|
+
children: [/* @__PURE__ */ y(m, { className: "size-5 text-text-secondary" }), /* @__PURE__ */ y("h3", {
|
|
150
146
|
className: "font-semibold text-text-primary",
|
|
151
147
|
children: D("vibeDetails.gitopsTitle", "GitOps")
|
|
152
148
|
})]
|
|
153
|
-
}), /* @__PURE__ */
|
|
149
|
+
}), /* @__PURE__ */ b("div", {
|
|
154
150
|
className: "flex items-center gap-2",
|
|
155
151
|
children: [
|
|
156
|
-
z && /* @__PURE__ */
|
|
152
|
+
z && /* @__PURE__ */ y(le, {
|
|
157
153
|
providers: R.map((e) => e.name),
|
|
158
154
|
selected: j,
|
|
159
155
|
onChange: M
|
|
160
156
|
}),
|
|
161
|
-
z && (O === "repo-stats" || O === "ci") && /* @__PURE__ */
|
|
157
|
+
z && (O === "repo-stats" || O === "ci") && /* @__PURE__ */ b("button", {
|
|
162
158
|
type: "button",
|
|
163
|
-
onClick:
|
|
159
|
+
onClick: xe,
|
|
164
160
|
disabled: X,
|
|
165
161
|
className: "flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-md border border-border-default text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50 transition-colors",
|
|
166
162
|
"data-testid": "gitops-refresh",
|
|
167
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ y(g, { className: `size-3 ${X ? "animate-spin" : ""}` }), D("gitops.refresh", "Refresh")]
|
|
168
164
|
}),
|
|
169
|
-
!k && /* @__PURE__ */
|
|
165
|
+
!k && /* @__PURE__ */ y("button", {
|
|
170
166
|
type: "button",
|
|
171
167
|
onClick: () => A(!0),
|
|
172
168
|
className: "flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors",
|
|
@@ -175,45 +171,45 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
175
171
|
})
|
|
176
172
|
]
|
|
177
173
|
})]
|
|
178
|
-
}), /* @__PURE__ */
|
|
174
|
+
}), /* @__PURE__ */ b("div", {
|
|
179
175
|
className: "flex flex-col gap-4 lg:flex-row lg:gap-6",
|
|
180
|
-
children: [/* @__PURE__ */
|
|
176
|
+
children: [/* @__PURE__ */ y("nav", {
|
|
181
177
|
className: "w-full flex-shrink-0 rounded-lg border border-border-default bg-bg-surface p-2 lg:w-56",
|
|
182
178
|
role: "tablist",
|
|
183
179
|
"aria-label": D("gitops.navigation", "GitOps navigation"),
|
|
184
|
-
children:
|
|
180
|
+
children: Se.map((e) => {
|
|
185
181
|
let t = e.icon, n = O === e.id;
|
|
186
|
-
return /* @__PURE__ */
|
|
182
|
+
return /* @__PURE__ */ b("button", {
|
|
187
183
|
type: "button",
|
|
188
184
|
role: "tab",
|
|
189
185
|
"aria-selected": n,
|
|
190
|
-
onClick: () =>
|
|
186
|
+
onClick: () => pe(e.id),
|
|
191
187
|
className: `flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm font-medium transition-colors ${n ? "bg-action-primary-bg text-action-primary-text shadow-sm" : "text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary"}`,
|
|
192
|
-
children: [/* @__PURE__ */
|
|
188
|
+
children: [/* @__PURE__ */ y(t, { className: "size-4 shrink-0" }), /* @__PURE__ */ y("span", {
|
|
193
189
|
className: "truncate",
|
|
194
190
|
children: e.label
|
|
195
191
|
})]
|
|
196
192
|
}, e.id);
|
|
197
193
|
})
|
|
198
|
-
}), /* @__PURE__ */
|
|
194
|
+
}), /* @__PURE__ */ b("div", {
|
|
199
195
|
className: "min-w-0 flex-1 space-y-4",
|
|
200
196
|
children: [
|
|
201
|
-
k && /* @__PURE__ */
|
|
197
|
+
k && /* @__PURE__ */ y(ce, {
|
|
202
198
|
ctx: I,
|
|
203
199
|
onCreated: () => {
|
|
204
200
|
A(!1), L.refetch();
|
|
205
201
|
},
|
|
206
202
|
onCancel: () => A(!1)
|
|
207
203
|
}),
|
|
208
|
-
O === "repo-stats" && !k && /* @__PURE__ */ v
|
|
204
|
+
O === "repo-stats" && !k && /* @__PURE__ */ y(v, { children: z ? w ? /* @__PURE__ */ y(a, {
|
|
205
|
+
stats: he,
|
|
206
|
+
loading: _e || L.isLoading,
|
|
207
|
+
error: ye
|
|
208
|
+
}) : /* @__PURE__ */ y(C, {
|
|
209
209
|
tr: D,
|
|
210
210
|
message: "gitops.noTunnel",
|
|
211
211
|
fallback: "Waiting for agent tunnel. Make sure the agent is running and a tunnel is active."
|
|
212
|
-
}) : /* @__PURE__ */
|
|
213
|
-
stats: ge,
|
|
214
|
-
loading: ve || L.isLoading,
|
|
215
|
-
error: be
|
|
216
|
-
}) : /* @__PURE__ */ v(x, {
|
|
212
|
+
}) : /* @__PURE__ */ y(S, {
|
|
217
213
|
tr: D,
|
|
218
214
|
icon: m,
|
|
219
215
|
message: "gitops.noProvidersVibe",
|
|
@@ -221,15 +217,15 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
221
217
|
ctaLabel: D("gitops.addProvider", "Add Provider"),
|
|
222
218
|
onCta: () => A(!0)
|
|
223
219
|
}) }),
|
|
224
|
-
O === "ci" && !k && /* @__PURE__ */ v
|
|
220
|
+
O === "ci" && !k && /* @__PURE__ */ y(v, { children: z ? w ? /* @__PURE__ */ y(o, {
|
|
221
|
+
status: ge,
|
|
222
|
+
loading: ve || L.isLoading,
|
|
223
|
+
error: be
|
|
224
|
+
}) : /* @__PURE__ */ y(C, {
|
|
225
225
|
tr: D,
|
|
226
226
|
message: "gitops.noTunnelCi",
|
|
227
227
|
fallback: "Waiting for agent tunnel."
|
|
228
|
-
}) : /* @__PURE__ */
|
|
229
|
-
status: _e,
|
|
230
|
-
loading: ye || L.isLoading,
|
|
231
|
-
error: xe
|
|
232
|
-
}) : /* @__PURE__ */ v(x, {
|
|
228
|
+
}) : /* @__PURE__ */ y(S, {
|
|
233
229
|
tr: D,
|
|
234
230
|
icon: h,
|
|
235
231
|
message: "gitops.noProvidersCi",
|
|
@@ -237,26 +233,26 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
237
233
|
ctaLabel: D("gitops.addProvider", "Add Provider"),
|
|
238
234
|
onCta: () => A(!0)
|
|
239
235
|
}) }),
|
|
240
|
-
O === "git-ui" && /* @__PURE__ */
|
|
241
|
-
agentId:
|
|
236
|
+
O === "git-ui" && /* @__PURE__ */ y(t, {
|
|
237
|
+
agentId: w || null,
|
|
242
238
|
required: ["@vibecontrols/vibe-plugin-tool-git"],
|
|
243
239
|
mode: "all",
|
|
244
240
|
eyebrow: D("gitops.ungitEyebrow", "Ungit plugin required"),
|
|
245
241
|
title: D("gitops.ungitTitle", "Install the visual git client"),
|
|
246
242
|
description: D("gitops.ungitDescription", "The Git UI embeds the Ungit visual git client served by your agent. Install the plugin to continue."),
|
|
247
|
-
children: $ ? /* @__PURE__ */
|
|
243
|
+
children: $ ? /* @__PURE__ */ y("div", {
|
|
248
244
|
className: "rounded-lg border border-border-default bg-status-error-bg-subtle p-4",
|
|
249
|
-
children: /* @__PURE__ */
|
|
245
|
+
children: /* @__PURE__ */ y("p", {
|
|
250
246
|
className: "text-sm text-status-error-text",
|
|
251
247
|
children: $
|
|
252
248
|
})
|
|
253
|
-
}) : Z ? /* @__PURE__ */
|
|
249
|
+
}) : Z ? /* @__PURE__ */ y("iframe", {
|
|
254
250
|
src: Z,
|
|
255
251
|
title: "Ungit",
|
|
256
252
|
className: "w-full min-h-[600px] rounded-lg border border-border-default"
|
|
257
|
-
}) : /* @__PURE__ */
|
|
253
|
+
}) : /* @__PURE__ */ y("div", {
|
|
258
254
|
className: "rounded-lg border border-border-default bg-bg-sunken p-4",
|
|
259
|
-
children: /* @__PURE__ */
|
|
255
|
+
children: /* @__PURE__ */ y("p", {
|
|
260
256
|
className: "text-sm text-text-secondary",
|
|
261
257
|
children: D("gitops.ungitLoading", "Loading Ungit…")
|
|
262
258
|
})
|
|
@@ -266,29 +262,29 @@ var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D })
|
|
|
266
262
|
})]
|
|
267
263
|
})]
|
|
268
264
|
}, "gitops");
|
|
269
|
-
},
|
|
265
|
+
}, S = ({ tr: e, icon: t, message: n, fallback: r, ctaLabel: i, onCta: a }) => /* @__PURE__ */ b("div", {
|
|
270
266
|
className: "rounded-lg border border-border-default bg-bg-surface p-6 text-center",
|
|
271
267
|
children: [
|
|
272
|
-
/* @__PURE__ */
|
|
273
|
-
/* @__PURE__ */
|
|
268
|
+
/* @__PURE__ */ y(t, { className: "size-8 text-text-secondary mx-auto mb-3" }),
|
|
269
|
+
/* @__PURE__ */ y("p", {
|
|
274
270
|
className: "text-sm text-text-secondary",
|
|
275
271
|
children: e(n, r)
|
|
276
272
|
}),
|
|
277
|
-
i && a && /* @__PURE__ */
|
|
273
|
+
i && a && /* @__PURE__ */ y("button", {
|
|
278
274
|
type: "button",
|
|
279
275
|
onClick: a,
|
|
280
276
|
className: "mt-3 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors",
|
|
281
277
|
children: i
|
|
282
278
|
})
|
|
283
279
|
]
|
|
284
|
-
}),
|
|
280
|
+
}), C = ({ tr: e, message: t, fallback: n }) => /* @__PURE__ */ y("div", {
|
|
285
281
|
className: "rounded-lg border border-border-default bg-bg-sunken p-4",
|
|
286
|
-
children: /* @__PURE__ */
|
|
282
|
+
children: /* @__PURE__ */ y("p", {
|
|
287
283
|
className: "text-sm text-text-secondary",
|
|
288
284
|
children: e(t, n)
|
|
289
285
|
})
|
|
290
286
|
});
|
|
291
287
|
//#endregion
|
|
292
|
-
export {
|
|
288
|
+
export { x as VibeGitOpsTab };
|
|
293
289
|
|
|
294
290
|
//# sourceMappingURL=VibeGitOpsTab.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VibeGitOpsTab.js","names":[],"sources":["../../../src/components/gitops/VibeGitOpsTab.tsx"],"sourcesContent":["/**\n * VibeGitOpsTab — GitOps tab content for the Vibe detail page.\n *\n * V2: All data is sourced from the agent over the tunnel via\n * `/api/profiles/<profile>/gitops/<provider>/*`. No backend GraphQL\n * involvement. The backend's old `gitopsProvider` records are deprecated.\n *\n * Sub-tabs:\n * 1. Repo Stats — repo metadata + PR + issue + security counts\n * 2. CI Pipelines — recent runs + analytics percentiles\n * 3. Git UI — Ungit iframe (when vibe-plugin-tool-git is installed)\n */\nimport { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport { motion } from 'framer-motion';\nimport { GitMerge, BarChart3, Play, GitBranch, RefreshCw } from 'lucide-react';\nimport { useQueryClient } from '@tanstack/react-query';\n\nimport { RepoStatsTab } from './RepoStatsTab';\nimport { CITab } from './CITab';\nimport { GitOpsSetup } from './GitOpsSetup';\nimport { GitOpsProviderPicker } from './GitOpsProviderPicker';\nimport { PluginGate } from '@/components/shared';\nimport { GIT_PACKAGE } from '@/constants/pluginCatalog';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport {\n useGitopsContributors,\n useGitopsIssues,\n useGitopsLanguages,\n useGitopsPipelineAnalytics,\n useGitopsProviders,\n useGitopsPullRequests,\n useGitopsRepos,\n useGitopsRuns,\n useGitopsSecurityAlerts,\n} from '@/hooks/useGitops';\nimport { composeCiStatus, composeRepoStats, type ProviderName, type RepoFqn } from './types';\nimport {\n mintIframeToken,\n scheduleIframeTokenRefresh,\n appendIframeTokenQuery,\n type IframeTokenAutoRefreshHandle,\n type IframeTokenResponse,\n} from '@/services/iframeToken';\n\ninterface VibeGitOpsTabProps {\n vibeId: string;\n agentId: string;\n /** Canonical agent profile name. */\n profile: string;\n tunnelUrl: string | null;\n apiKey: string | null;\n tr: (key: string, fallback: string) => string;\n}\n\ntype GitOpsSubTab = 'repo-stats' | 'ci' | 'git-ui';\n\nexport const VibeGitOpsTab: FC<VibeGitOpsTabProps> = ({\n vibeId,\n agentId,\n profile,\n tunnelUrl,\n apiKey,\n tr,\n}) => {\n const [subTab, setSubTab] = useState<GitOpsSubTab>('repo-stats');\n const [showSetup, setShowSetup] = useState(false);\n const [selectedProvider, setSelectedProvider] = useState<ProviderName | null>(null);\n const [selectedRepoFqn, setSelectedRepoFqn] = useState<RepoFqn | null>(null);\n const { workspaceId } = useVibeControls();\n const qc = useQueryClient();\n\n const ctx = useMemo(\n () => ({ tunnelUrl, apiKey, profile, workspaceId: workspaceId ?? '', agentId }),\n [tunnelUrl, apiKey, profile, workspaceId, agentId]\n );\n\n // Providers registered on the agent.\n const providersQ = useGitopsProviders(ctx);\n const authedProviders = useMemo(\n () => providersQ.data?.filter((p) => p.registered && p.authenticated) ?? [],\n [providersQ.data]\n );\n const hasProviders = authedProviders.length > 0;\n\n // Pick a default once data loads.\n useEffect(() => {\n if (!selectedProvider && authedProviders.length > 0) {\n const first = authedProviders[0];\n if (first) {\n setSelectedProvider(first.name as ProviderName);\n }\n }\n }, [authedProviders, selectedProvider]);\n\n // Once a provider is selected, fetch repos for it and pick the first one\n // by default. The user can switch repos via the picker.\n const reposQ = useGitopsRepos(ctx, selectedProvider, { limit: 30 });\n useEffect(() => {\n if (!selectedRepoFqn && reposQ.data?.items.length) {\n const first = reposQ.data.items[0];\n if (first) {\n setSelectedRepoFqn(first.fqn);\n }\n }\n }, [reposQ.data, selectedRepoFqn]);\n\n // Repo Stats composite — runs only when active sub-tab is repo-stats.\n const repoQ = useGitopsRepos(ctx, selectedProvider, { limit: 1 }); // metadata via list (1st item)\n const repoDetailQ = useGitopsRepos.length ? null : null; // keep TS happy; we use repoQ.items[0]\n const pullsQ = useGitopsPullRequests(ctx, selectedProvider, selectedRepoFqn, {\n state: 'open',\n limit: 100,\n });\n const issuesQ = useGitopsIssues(ctx, selectedProvider, selectedRepoFqn, {\n state: 'open',\n limit: 100,\n });\n const languagesQ = useGitopsLanguages(ctx, selectedProvider, selectedRepoFqn);\n const contributorsQ = useGitopsContributors(ctx, selectedProvider, selectedRepoFqn, 20);\n const securityQ = useGitopsSecurityAlerts(ctx, selectedProvider, selectedRepoFqn);\n\n const activeRepo = useMemo(\n () =>\n selectedRepoFqn\n ? (repoQ.data?.items.find((r) => r.fqn === selectedRepoFqn) ??\n reposQ.data?.items.find((r) => r.fqn === selectedRepoFqn) ??\n null)\n : (repoQ.data?.items[0] ?? null),\n [repoQ.data, reposQ.data, selectedRepoFqn]\n );\n\n const repoStats = useMemo(\n () =>\n composeRepoStats({\n repo: activeRepo,\n pulls: pullsQ.data,\n issues: issuesQ.data,\n languages: languagesQ.data,\n contributors: contributorsQ.data,\n securityAlerts: securityQ.data,\n }),\n [activeRepo, pullsQ.data, issuesQ.data, languagesQ.data, contributorsQ.data, securityQ.data]\n );\n\n // CI Status composite.\n const runsQ = useGitopsRuns(ctx, selectedProvider, selectedRepoFqn, { limit: 30 });\n const ciAnalyticsQ = useGitopsPipelineAnalytics(ctx, selectedProvider, selectedRepoFqn);\n const ciStatus = useMemo(\n () =>\n composeCiStatus({\n pipelines: undefined,\n runs: runsQ.data,\n analytics: ciAnalyticsQ.data,\n }),\n [runsQ.data, ciAnalyticsQ.data]\n );\n\n const repoStatsLoading = pullsQ.isLoading || issuesQ.isLoading || languagesQ.isLoading;\n const ciStatusLoading = runsQ.isLoading || ciAnalyticsQ.isLoading;\n const repoStatsError = (pullsQ.error || issuesQ.error || languagesQ.error)?.message;\n const ciStatusError = (runsQ.error || ciAnalyticsQ.error)?.message;\n\n const handleRefresh = useCallback(() => {\n void qc.invalidateQueries({ queryKey: ['gitops', workspaceId ?? '', agentId, profile] });\n }, [qc, workspaceId, agentId, profile]);\n\n const isRefreshing = pullsQ.isFetching || runsQ.isFetching;\n\n const subTabs: { id: GitOpsSubTab; label: string; icon: typeof BarChart3 }[] = [\n { id: 'repo-stats', label: tr('gitops.repoStats', 'Repo Stats'), icon: BarChart3 },\n { id: 'ci', label: tr('gitops.ci', 'CI Pipelines'), icon: Play },\n { id: 'git-ui', label: tr('gitops.gitUI', 'Git UI'), icon: GitBranch },\n ];\n\n // Ungit iframe state (unchanged from v1).\n const [ungitIframeUrl, setUngitIframeUrl] = useState<string | null>(null);\n const [ungitTokenError, setUngitTokenError] = useState<string | null>(null);\n useEffect(() => {\n let cancelled = false;\n let handle: IframeTokenAutoRefreshHandle | null = null;\n const setup = async () => {\n if (!agentId || !tunnelUrl || subTab !== 'git-ui') {\n setUngitIframeUrl(null);\n return;\n }\n try {\n // F5 residual — Ungit's reverse proxy lives at `/ungit/` (declared\n // by the external `vibe-plugin-tool-git` package's publicPaths +\n // meta.url override). The plugin's createUngitProxy validates the\n // entry-document auth via `?vt=<token>` (or `?apiKey=`), then\n // issues its own session cookie for sub-requests. Switching this\n // to a host-level hash→cookie shim (like F5 does for `/ui/<p>`)\n // would require an SDK contract change + coordinated republish of\n // the external plugin package. Until that ships, the iframe-token\n // sits in the URL once. Mitigations in place: F6 makes it single-\n // use (replayed token rejected); F7 strips the token before\n // upstream forwarding so Ungit never sees it; F10 enforces Origin\n // on non-GET/HEAD sub-requests. Residual surface: one query string\n // appears in CF logs + browser history for the initial document.\n const initial: IframeTokenResponse = await mintIframeToken(agentId, profile, '/ungit');\n if (cancelled) return;\n const baseUrl = `${tunnelUrl.replace(/\\/$/, '')}/ungit/`;\n setUngitIframeUrl(appendIframeTokenQuery(baseUrl, initial.token));\n handle = scheduleIframeTokenRefresh(\n agentId,\n profile,\n '/ungit',\n initial,\n (next: IframeTokenResponse) => {\n setUngitIframeUrl(appendIframeTokenQuery(baseUrl, next.token));\n }\n );\n } catch (err) {\n if (!cancelled) {\n setUngitTokenError(err instanceof Error ? err.message : 'Failed to fetch iframe token');\n }\n }\n };\n void setup();\n return () => {\n cancelled = true;\n handle?.cancel();\n };\n }, [agentId, profile, tunnelUrl, subTab]);\n\n return (\n <motion.div\n key=\"gitops\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-4\"\n data-vibe-id={vibeId}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <GitMerge className=\"size-5 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibeDetails.gitopsTitle', 'GitOps')}\n </h3>\n </div>\n <div className=\"flex items-center gap-2\">\n {hasProviders && (\n <GitOpsProviderPicker\n providers={authedProviders.map((p) => p.name as ProviderName)}\n selected={selectedProvider}\n onChange={setSelectedProvider}\n />\n )}\n {hasProviders && (subTab === 'repo-stats' || subTab === 'ci') && (\n <button\n type=\"button\"\n onClick={handleRefresh}\n disabled={isRefreshing}\n className=\"flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-md border border-border-default text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50 transition-colors\"\n data-testid=\"gitops-refresh\"\n >\n <RefreshCw className={`size-3 ${isRefreshing ? 'animate-spin' : ''}`} />\n {tr('gitops.refresh', 'Refresh')}\n </button>\n )}\n {!showSetup && (\n <button\n type=\"button\"\n onClick={() => setShowSetup(true)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n data-testid=\"gitops-add-provider\"\n >\n {hasProviders\n ? tr('gitops.addAnotherProvider', 'Add Provider')\n : tr('gitops.addProvider', 'Add Provider')}\n </button>\n )}\n </div>\n </div>\n\n <div className=\"flex flex-col gap-4 lg:flex-row lg:gap-6\">\n <nav\n className=\"w-full flex-shrink-0 rounded-lg border border-border-default bg-bg-surface p-2 lg:w-56\"\n role=\"tablist\"\n aria-label={tr('gitops.navigation', 'GitOps navigation')}\n >\n {subTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = subTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setSubTab(tab.id)}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm font-medium transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text shadow-sm'\n : 'text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary'\n }`}\n >\n <Icon className=\"size-4 shrink-0\" />\n <span className=\"truncate\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n\n <div className=\"min-w-0 flex-1 space-y-4\">\n {showSetup && (\n <GitOpsSetup\n ctx={ctx}\n onCreated={() => {\n setShowSetup(false);\n void providersQ.refetch();\n }}\n onCancel={() => setShowSetup(false)}\n />\n )}\n\n {subTab === 'repo-stats' && !showSetup && (\n <>\n {!hasProviders ? (\n <EmptyState\n tr={tr}\n icon={GitMerge}\n message=\"gitops.noProvidersVibe\"\n fallback=\"No GitOps provider configured. Add a PAT to see repository stats.\"\n ctaLabel={tr('gitops.addProvider', 'Add Provider')}\n onCta={() => setShowSetup(true)}\n />\n ) : !tunnelUrl || !apiKey ? (\n <Notice\n tr={tr}\n message=\"gitops.noTunnel\"\n fallback=\"Waiting for agent tunnel. Make sure the agent is running and a tunnel is active.\"\n />\n ) : (\n <RepoStatsTab\n stats={repoStats}\n loading={repoStatsLoading || providersQ.isLoading}\n error={repoStatsError}\n />\n )}\n </>\n )}\n\n {subTab === 'ci' && !showSetup && (\n <>\n {!hasProviders ? (\n <EmptyState\n tr={tr}\n icon={Play}\n message=\"gitops.noProvidersCi\"\n fallback=\"No GitOps provider configured. Add a PAT to see CI pipeline status.\"\n ctaLabel={tr('gitops.addProvider', 'Add Provider')}\n onCta={() => setShowSetup(true)}\n />\n ) : !tunnelUrl || !apiKey ? (\n <Notice tr={tr} message=\"gitops.noTunnelCi\" fallback=\"Waiting for agent tunnel.\" />\n ) : (\n <CITab\n status={ciStatus}\n loading={ciStatusLoading || providersQ.isLoading}\n error={ciStatusError}\n />\n )}\n </>\n )}\n\n {subTab === 'git-ui' && (\n <PluginGate\n agentId={agentId || null}\n required={[GIT_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('gitops.ungitEyebrow', 'Ungit plugin required')}\n title={tr('gitops.ungitTitle', 'Install the visual git client')}\n description={tr(\n 'gitops.ungitDescription',\n 'The Git UI embeds the Ungit visual git client served by your agent. Install the plugin to continue.'\n )}\n >\n {ungitTokenError ? (\n <div className=\"rounded-lg border border-border-default bg-status-error-bg-subtle p-4\">\n <p className=\"text-sm text-status-error-text\">{ungitTokenError}</p>\n </div>\n ) : ungitIframeUrl ? (\n <iframe\n src={ungitIframeUrl}\n title=\"Ungit\"\n className=\"w-full min-h-[600px] rounded-lg border border-border-default\"\n />\n ) : (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr('gitops.ungitLoading', 'Loading Ungit…')}\n </p>\n </div>\n )}\n </PluginGate>\n )}\n </div>\n </div>\n </motion.div>\n );\n};\n\ninterface EmptyStateProps {\n tr: (key: string, fallback: string) => string;\n icon: typeof BarChart3;\n message: string;\n fallback: string;\n ctaLabel?: string;\n onCta?: () => void;\n}\n\nconst EmptyState: FC<EmptyStateProps> = ({\n tr,\n icon: Icon,\n message,\n fallback,\n ctaLabel,\n onCta,\n}) => (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6 text-center\">\n <Icon className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">{tr(message, fallback)}</p>\n {ctaLabel && onCta && (\n <button\n type=\"button\"\n onClick={onCta}\n className=\"mt-3 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {ctaLabel}\n </button>\n )}\n </div>\n);\n\ninterface NoticeProps {\n tr: (key: string, fallback: string) => string;\n message: string;\n fallback: string;\n}\n\nconst Notice: FC<NoticeProps> = ({ tr, message, fallback }) => (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">{tr(message, fallback)}</p>\n </div>\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VibeGitOpsTab.js","names":[],"sources":["../../../src/components/gitops/VibeGitOpsTab.tsx"],"sourcesContent":["/**\n * VibeGitOpsTab — GitOps tab content for the Vibe detail page.\n *\n * V2: All data is sourced from the agent over the tunnel via\n * `/api/profiles/<profile>/gitops/<provider>/*`. No backend GraphQL\n * involvement. The backend's old `gitopsProvider` records are deprecated.\n *\n * Sub-tabs:\n * 1. Repo Stats — repo metadata + PR + issue + security counts\n * 2. CI Pipelines — recent runs + analytics percentiles\n * 3. Git UI — Ungit iframe (when vibe-plugin-tool-git is installed)\n */\nimport { useCallback, useEffect, useMemo, useState, type FC } from 'react';\nimport { motion } from 'framer-motion';\nimport { GitMerge, BarChart3, Play, GitBranch, RefreshCw } from 'lucide-react';\nimport { useQueryClient } from '@tanstack/react-query';\n\nimport { RepoStatsTab } from './RepoStatsTab';\nimport { CITab } from './CITab';\nimport { GitOpsSetup } from './GitOpsSetup';\nimport { GitOpsProviderPicker } from './GitOpsProviderPicker';\nimport { PluginGate } from '@/components/shared';\nimport { GIT_PACKAGE } from '@/constants/pluginCatalog';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport {\n useGitopsContributors,\n useGitopsIssues,\n useGitopsLanguages,\n useGitopsPipelineAnalytics,\n useGitopsProviders,\n useGitopsPullRequests,\n useGitopsRepos,\n useGitopsRuns,\n useGitopsSecurityAlerts,\n} from '@/hooks/useGitops';\nimport { composeCiStatus, composeRepoStats, type ProviderName, type RepoFqn } from './types';\nimport {\n mintIframeToken,\n scheduleIframeTokenRefresh,\n appendIframeTokenQuery,\n type IframeTokenAutoRefreshHandle,\n type IframeTokenResponse,\n} from '@/services/iframeToken';\n\ninterface VibeGitOpsTabProps {\n vibeId: string;\n agentId: string;\n /** Canonical agent profile name. */\n profile: string;\n /**\n * Tunnel URL — retained ONLY for the `git-ui` iframe handshake\n * (ungit subpath) which still bootstraps via a one-time iframe token.\n * GitOps REST calls no longer use this; they route through the svc\n * proxy (F4 Wave C).\n */\n tunnelUrl: string | null;\n tr: (key: string, fallback: string) => string;\n}\n\ntype GitOpsSubTab = 'repo-stats' | 'ci' | 'git-ui';\n\nexport const VibeGitOpsTab: FC<VibeGitOpsTabProps> = ({\n vibeId,\n agentId,\n profile,\n tunnelUrl,\n tr,\n}) => {\n const [subTab, setSubTab] = useState<GitOpsSubTab>('repo-stats');\n const [showSetup, setShowSetup] = useState(false);\n const [selectedProvider, setSelectedProvider] = useState<ProviderName | null>(null);\n const [selectedRepoFqn, setSelectedRepoFqn] = useState<RepoFqn | null>(null);\n const { workspaceId } = useVibeControls();\n const qc = useQueryClient();\n\n const ctx = useMemo(\n () => ({ profile, workspaceId: workspaceId ?? '', agentId }),\n [profile, workspaceId, agentId]\n );\n\n // Providers registered on the agent.\n const providersQ = useGitopsProviders(ctx);\n const authedProviders = useMemo(\n () => providersQ.data?.filter((p) => p.registered && p.authenticated) ?? [],\n [providersQ.data]\n );\n const hasProviders = authedProviders.length > 0;\n\n // Pick a default once data loads.\n useEffect(() => {\n if (!selectedProvider && authedProviders.length > 0) {\n const first = authedProviders[0];\n if (first) {\n setSelectedProvider(first.name as ProviderName);\n }\n }\n }, [authedProviders, selectedProvider]);\n\n // Once a provider is selected, fetch repos for it and pick the first one\n // by default. The user can switch repos via the picker.\n const reposQ = useGitopsRepos(ctx, selectedProvider, { limit: 30 });\n useEffect(() => {\n if (!selectedRepoFqn && reposQ.data?.items.length) {\n const first = reposQ.data.items[0];\n if (first) {\n setSelectedRepoFqn(first.fqn);\n }\n }\n }, [reposQ.data, selectedRepoFqn]);\n\n // Repo Stats composite — runs only when active sub-tab is repo-stats.\n const repoQ = useGitopsRepos(ctx, selectedProvider, { limit: 1 }); // metadata via list (1st item)\n const repoDetailQ = useGitopsRepos.length ? null : null; // keep TS happy; we use repoQ.items[0]\n const pullsQ = useGitopsPullRequests(ctx, selectedProvider, selectedRepoFqn, {\n state: 'open',\n limit: 100,\n });\n const issuesQ = useGitopsIssues(ctx, selectedProvider, selectedRepoFqn, {\n state: 'open',\n limit: 100,\n });\n const languagesQ = useGitopsLanguages(ctx, selectedProvider, selectedRepoFqn);\n const contributorsQ = useGitopsContributors(ctx, selectedProvider, selectedRepoFqn, 20);\n const securityQ = useGitopsSecurityAlerts(ctx, selectedProvider, selectedRepoFqn);\n\n const activeRepo = useMemo(\n () =>\n selectedRepoFqn\n ? (repoQ.data?.items.find((r) => r.fqn === selectedRepoFqn) ??\n reposQ.data?.items.find((r) => r.fqn === selectedRepoFqn) ??\n null)\n : (repoQ.data?.items[0] ?? null),\n [repoQ.data, reposQ.data, selectedRepoFqn]\n );\n\n const repoStats = useMemo(\n () =>\n composeRepoStats({\n repo: activeRepo,\n pulls: pullsQ.data,\n issues: issuesQ.data,\n languages: languagesQ.data,\n contributors: contributorsQ.data,\n securityAlerts: securityQ.data,\n }),\n [activeRepo, pullsQ.data, issuesQ.data, languagesQ.data, contributorsQ.data, securityQ.data]\n );\n\n // CI Status composite.\n const runsQ = useGitopsRuns(ctx, selectedProvider, selectedRepoFqn, { limit: 30 });\n const ciAnalyticsQ = useGitopsPipelineAnalytics(ctx, selectedProvider, selectedRepoFqn);\n const ciStatus = useMemo(\n () =>\n composeCiStatus({\n pipelines: undefined,\n runs: runsQ.data,\n analytics: ciAnalyticsQ.data,\n }),\n [runsQ.data, ciAnalyticsQ.data]\n );\n\n const repoStatsLoading = pullsQ.isLoading || issuesQ.isLoading || languagesQ.isLoading;\n const ciStatusLoading = runsQ.isLoading || ciAnalyticsQ.isLoading;\n const repoStatsError = (pullsQ.error || issuesQ.error || languagesQ.error)?.message;\n const ciStatusError = (runsQ.error || ciAnalyticsQ.error)?.message;\n\n const handleRefresh = useCallback(() => {\n void qc.invalidateQueries({ queryKey: ['gitops', workspaceId ?? '', agentId, profile] });\n }, [qc, workspaceId, agentId, profile]);\n\n const isRefreshing = pullsQ.isFetching || runsQ.isFetching;\n\n const subTabs: { id: GitOpsSubTab; label: string; icon: typeof BarChart3 }[] = [\n { id: 'repo-stats', label: tr('gitops.repoStats', 'Repo Stats'), icon: BarChart3 },\n { id: 'ci', label: tr('gitops.ci', 'CI Pipelines'), icon: Play },\n { id: 'git-ui', label: tr('gitops.gitUI', 'Git UI'), icon: GitBranch },\n ];\n\n // Ungit iframe state (unchanged from v1).\n const [ungitIframeUrl, setUngitIframeUrl] = useState<string | null>(null);\n const [ungitTokenError, setUngitTokenError] = useState<string | null>(null);\n useEffect(() => {\n let cancelled = false;\n let handle: IframeTokenAutoRefreshHandle | null = null;\n const setup = async () => {\n if (!agentId || !tunnelUrl || subTab !== 'git-ui') {\n setUngitIframeUrl(null);\n return;\n }\n try {\n // F5 residual — Ungit's reverse proxy lives at `/ungit/` (declared\n // by the external `vibe-plugin-tool-git` package's publicPaths +\n // meta.url override). The plugin's createUngitProxy validates the\n // entry-document auth via `?vt=<token>` (or `?apiKey=`), then\n // issues its own session cookie for sub-requests. Switching this\n // to a host-level hash→cookie shim (like F5 does for `/ui/<p>`)\n // would require an SDK contract change + coordinated republish of\n // the external plugin package. Until that ships, the iframe-token\n // sits in the URL once. Mitigations in place: F6 makes it single-\n // use (replayed token rejected); F7 strips the token before\n // upstream forwarding so Ungit never sees it; F10 enforces Origin\n // on non-GET/HEAD sub-requests. Residual surface: one query string\n // appears in CF logs + browser history for the initial document.\n const initial: IframeTokenResponse = await mintIframeToken(agentId, profile, '/ungit');\n if (cancelled) return;\n const baseUrl = `${tunnelUrl.replace(/\\/$/, '')}/ungit/`;\n setUngitIframeUrl(appendIframeTokenQuery(baseUrl, initial.token));\n handle = scheduleIframeTokenRefresh(\n agentId,\n profile,\n '/ungit',\n initial,\n (next: IframeTokenResponse) => {\n setUngitIframeUrl(appendIframeTokenQuery(baseUrl, next.token));\n }\n );\n } catch (err) {\n if (!cancelled) {\n setUngitTokenError(err instanceof Error ? err.message : 'Failed to fetch iframe token');\n }\n }\n };\n void setup();\n return () => {\n cancelled = true;\n handle?.cancel();\n };\n }, [agentId, profile, tunnelUrl, subTab]);\n\n return (\n <motion.div\n key=\"gitops\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-4\"\n data-vibe-id={vibeId}\n >\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n <GitMerge className=\"size-5 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('vibeDetails.gitopsTitle', 'GitOps')}\n </h3>\n </div>\n <div className=\"flex items-center gap-2\">\n {hasProviders && (\n <GitOpsProviderPicker\n providers={authedProviders.map((p) => p.name as ProviderName)}\n selected={selectedProvider}\n onChange={setSelectedProvider}\n />\n )}\n {hasProviders && (subTab === 'repo-stats' || subTab === 'ci') && (\n <button\n type=\"button\"\n onClick={handleRefresh}\n disabled={isRefreshing}\n className=\"flex items-center gap-1.5 px-2.5 py-1 text-xs rounded-md border border-border-default text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50 transition-colors\"\n data-testid=\"gitops-refresh\"\n >\n <RefreshCw className={`size-3 ${isRefreshing ? 'animate-spin' : ''}`} />\n {tr('gitops.refresh', 'Refresh')}\n </button>\n )}\n {!showSetup && (\n <button\n type=\"button\"\n onClick={() => setShowSetup(true)}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n data-testid=\"gitops-add-provider\"\n >\n {hasProviders\n ? tr('gitops.addAnotherProvider', 'Add Provider')\n : tr('gitops.addProvider', 'Add Provider')}\n </button>\n )}\n </div>\n </div>\n\n <div className=\"flex flex-col gap-4 lg:flex-row lg:gap-6\">\n <nav\n className=\"w-full flex-shrink-0 rounded-lg border border-border-default bg-bg-surface p-2 lg:w-56\"\n role=\"tablist\"\n aria-label={tr('gitops.navigation', 'GitOps navigation')}\n >\n {subTabs.map((tab) => {\n const Icon = tab.icon;\n const isActive = subTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => setSubTab(tab.id)}\n className={`flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm font-medium transition-colors ${\n isActive\n ? 'bg-action-primary-bg text-action-primary-text shadow-sm'\n : 'text-text-secondary hover:bg-action-ghost-bg-hover hover:text-text-primary'\n }`}\n >\n <Icon className=\"size-4 shrink-0\" />\n <span className=\"truncate\">{tab.label}</span>\n </button>\n );\n })}\n </nav>\n\n <div className=\"min-w-0 flex-1 space-y-4\">\n {showSetup && (\n <GitOpsSetup\n ctx={ctx}\n onCreated={() => {\n setShowSetup(false);\n void providersQ.refetch();\n }}\n onCancel={() => setShowSetup(false)}\n />\n )}\n\n {subTab === 'repo-stats' && !showSetup && (\n <>\n {!hasProviders ? (\n <EmptyState\n tr={tr}\n icon={GitMerge}\n message=\"gitops.noProvidersVibe\"\n fallback=\"No GitOps provider configured. Add a PAT to see repository stats.\"\n ctaLabel={tr('gitops.addProvider', 'Add Provider')}\n onCta={() => setShowSetup(true)}\n />\n ) : !agentId ? (\n <Notice\n tr={tr}\n message=\"gitops.noTunnel\"\n fallback=\"Waiting for agent tunnel. Make sure the agent is running and a tunnel is active.\"\n />\n ) : (\n <RepoStatsTab\n stats={repoStats}\n loading={repoStatsLoading || providersQ.isLoading}\n error={repoStatsError}\n />\n )}\n </>\n )}\n\n {subTab === 'ci' && !showSetup && (\n <>\n {!hasProviders ? (\n <EmptyState\n tr={tr}\n icon={Play}\n message=\"gitops.noProvidersCi\"\n fallback=\"No GitOps provider configured. Add a PAT to see CI pipeline status.\"\n ctaLabel={tr('gitops.addProvider', 'Add Provider')}\n onCta={() => setShowSetup(true)}\n />\n ) : !agentId ? (\n <Notice tr={tr} message=\"gitops.noTunnelCi\" fallback=\"Waiting for agent tunnel.\" />\n ) : (\n <CITab\n status={ciStatus}\n loading={ciStatusLoading || providersQ.isLoading}\n error={ciStatusError}\n />\n )}\n </>\n )}\n\n {subTab === 'git-ui' && (\n <PluginGate\n agentId={agentId || null}\n required={[GIT_PACKAGE]}\n mode=\"all\"\n eyebrow={tr('gitops.ungitEyebrow', 'Ungit plugin required')}\n title={tr('gitops.ungitTitle', 'Install the visual git client')}\n description={tr(\n 'gitops.ungitDescription',\n 'The Git UI embeds the Ungit visual git client served by your agent. Install the plugin to continue.'\n )}\n >\n {ungitTokenError ? (\n <div className=\"rounded-lg border border-border-default bg-status-error-bg-subtle p-4\">\n <p className=\"text-sm text-status-error-text\">{ungitTokenError}</p>\n </div>\n ) : ungitIframeUrl ? (\n <iframe\n src={ungitIframeUrl}\n title=\"Ungit\"\n className=\"w-full min-h-[600px] rounded-lg border border-border-default\"\n />\n ) : (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr('gitops.ungitLoading', 'Loading Ungit…')}\n </p>\n </div>\n )}\n </PluginGate>\n )}\n </div>\n </div>\n </motion.div>\n );\n};\n\ninterface EmptyStateProps {\n tr: (key: string, fallback: string) => string;\n icon: typeof BarChart3;\n message: string;\n fallback: string;\n ctaLabel?: string;\n onCta?: () => void;\n}\n\nconst EmptyState: FC<EmptyStateProps> = ({\n tr,\n icon: Icon,\n message,\n fallback,\n ctaLabel,\n onCta,\n}) => (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6 text-center\">\n <Icon className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">{tr(message, fallback)}</p>\n {ctaLabel && onCta && (\n <button\n type=\"button\"\n onClick={onCta}\n className=\"mt-3 px-4 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {ctaLabel}\n </button>\n )}\n </div>\n);\n\ninterface NoticeProps {\n tr: (key: string, fallback: string) => string;\n message: string;\n fallback: string;\n}\n\nconst Notice: FC<NoticeProps> = ({ tr, message, fallback }) => (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">{tr(message, fallback)}</p>\n </div>\n);\n"],"mappings":"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"}
|
|
@@ -132,19 +132,12 @@ function y({ rowKey: e }) {
|
|
|
132
132
|
]
|
|
133
133
|
}, e);
|
|
134
134
|
}
|
|
135
|
-
function b({ vibeId: r, agentId: i,
|
|
136
|
-
let { data:
|
|
135
|
+
function b({ vibeId: r, agentId: i, profile: a, limit: o = 20 }) {
|
|
136
|
+
let { data: c, isLoading: b, error: x, isFetching: S, refetch: C } = e(t(() => ({
|
|
137
137
|
agentId: i,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}), [
|
|
142
|
-
i,
|
|
143
|
-
a,
|
|
144
|
-
o,
|
|
145
|
-
c
|
|
146
|
-
]), r, b);
|
|
147
|
-
return i && a && o ? /* @__PURE__ */ d("section", {
|
|
138
|
+
profile: a
|
|
139
|
+
}), [i, a]), r, o);
|
|
140
|
+
return i ? /* @__PURE__ */ d("section", {
|
|
148
141
|
"data-testid": "security-scan-list",
|
|
149
142
|
className: "rounded-lg border border-border-default bg-bg-surface",
|
|
150
143
|
children: [
|
|
@@ -159,19 +152,19 @@ function b({ vibeId: r, agentId: i, tunnelUrl: a, apiKey: o, profile: c, limit:
|
|
|
159
152
|
className: "text-xs text-text-secondary mt-0.5",
|
|
160
153
|
children: [
|
|
161
154
|
"Read live from the agent. Showing up to ",
|
|
162
|
-
|
|
155
|
+
o,
|
|
163
156
|
" most recently active runs for this vibe."
|
|
164
157
|
]
|
|
165
158
|
})]
|
|
166
159
|
}), /* @__PURE__ */ u("button", {
|
|
167
160
|
type: "button",
|
|
168
|
-
onClick: () => void
|
|
169
|
-
disabled:
|
|
161
|
+
onClick: () => void C(),
|
|
162
|
+
disabled: S,
|
|
170
163
|
className: "px-2.5 py-1 text-xs rounded-md border border-border-default bg-bg-sunken text-text-primary hover:bg-action-primary-bg-hover disabled:opacity-50",
|
|
171
|
-
children:
|
|
164
|
+
children: S ? "Refreshing…" : "Refresh"
|
|
172
165
|
})]
|
|
173
166
|
}),
|
|
174
|
-
|
|
167
|
+
x && /* @__PURE__ */ d("div", {
|
|
175
168
|
className: "m-4 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg/10 p-3 text-sm",
|
|
176
169
|
children: [/* @__PURE__ */ u(n, { className: "size-4 text-status-error-text mt-0.5 shrink-0" }), /* @__PURE__ */ d("div", {
|
|
177
170
|
className: "text-text-primary",
|
|
@@ -180,7 +173,7 @@ function b({ vibeId: r, agentId: i, tunnelUrl: a, apiKey: o, profile: c, limit:
|
|
|
180
173
|
children: "Couldn't load scan runs"
|
|
181
174
|
}), /* @__PURE__ */ u("p", {
|
|
182
175
|
className: "mt-0.5 text-xs text-text-secondary",
|
|
183
|
-
children:
|
|
176
|
+
children: x.message
|
|
184
177
|
})]
|
|
185
178
|
})]
|
|
186
179
|
}),
|
|
@@ -221,12 +214,12 @@ function b({ vibeId: r, agentId: i, tunnelUrl: a, apiKey: o, profile: c, limit:
|
|
|
221
214
|
})
|
|
222
215
|
] })
|
|
223
216
|
}), /* @__PURE__ */ d("tbody", { children: [
|
|
224
|
-
|
|
217
|
+
b && c === void 0 && /* @__PURE__ */ d(l, { children: [
|
|
225
218
|
/* @__PURE__ */ u(y, { rowKey: 0 }),
|
|
226
219
|
/* @__PURE__ */ u(y, { rowKey: 1 }),
|
|
227
220
|
/* @__PURE__ */ u(y, { rowKey: 2 })
|
|
228
221
|
] }),
|
|
229
|
-
!
|
|
222
|
+
!b && (c?.length ?? 0) === 0 && !x && /* @__PURE__ */ u("tr", { children: /* @__PURE__ */ u("td", {
|
|
230
223
|
colSpan: 7,
|
|
231
224
|
className: "px-3 py-8",
|
|
232
225
|
children: /* @__PURE__ */ d("div", {
|
|
@@ -244,7 +237,7 @@ function b({ vibeId: r, agentId: i, tunnelUrl: a, apiKey: o, profile: c, limit:
|
|
|
244
237
|
]
|
|
245
238
|
})
|
|
246
239
|
}) }),
|
|
247
|
-
(
|
|
240
|
+
(c ?? []).map((e) => /* @__PURE__ */ d("tr", {
|
|
248
241
|
className: "border-t border-border-subtle align-middle",
|
|
249
242
|
children: [
|
|
250
243
|
/* @__PURE__ */ u("td", {
|
|
@@ -299,7 +292,7 @@ function b({ vibeId: r, agentId: i, tunnelUrl: a, apiKey: o, profile: c, limit:
|
|
|
299
292
|
}) : /* @__PURE__ */ u("section", {
|
|
300
293
|
"data-testid": "security-scan-list",
|
|
301
294
|
className: "rounded-lg border border-border-default bg-bg-surface p-4 text-sm text-text-secondary",
|
|
302
|
-
children: "Connect to a reachable agent to load scan history. Scan runs are read
|
|
295
|
+
children: "Connect to a reachable agent to load scan history. Scan runs are read from the agent via the svc GraphQL proxy — there's no backend cache."
|
|
303
296
|
});
|
|
304
297
|
}
|
|
305
298
|
//#endregion
|