@burdenoff/microfe-vibecontrols 2026.527.16 → 2026.528.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/GitOpsProviderPicker.js +29 -0
- package/dist/components/gitops/GitOpsProviderPicker.js.map +1 -0
- package/dist/components/gitops/GitOpsSetup.js +148 -102
- package/dist/components/gitops/GitOpsSetup.js.map +1 -1
- package/dist/components/gitops/VibeGitOpsTab.js +231 -223
- package/dist/components/gitops/VibeGitOpsTab.js.map +1 -1
- package/dist/components/gitops/types.js +65 -0
- package/dist/components/gitops/types.js.map +1 -0
- package/dist/components/security/SecurityEmptyState.js +2 -32
- package/dist/components/security/SecurityScanList.js +308 -0
- package/dist/components/security/SecurityScanList.js.map +1 -0
- package/dist/components/security/SecurityTabPanel.js +44 -97
- package/dist/components/security/SecurityTabPanel.js.map +1 -1
- package/dist/components/security/agentClient.js +78 -0
- package/dist/components/security/agentClient.js.map +1 -0
- package/dist/components/security/hooks.js +33 -0
- package/dist/components/security/hooks.js.map +1 -0
- package/dist/components/security/index.js +4 -6
- package/dist/components/security/types.js +3 -5
- package/dist/components/security/types.js.map +1 -1
- package/dist/components/settings/SettingsForm.js +30 -32
- package/dist/components/settings/SettingsForm.js.map +1 -1
- package/dist/components/settings/WorkspaceDefaultsSection.js +113 -118
- package/dist/components/settings/WorkspaceDefaultsSection.js.map +1 -1
- package/dist/components/shared/ConfirmDialog.js +51 -108
- package/dist/components/shared/ConfirmDialog.js.map +1 -1
- package/dist/components/shared/EmptyState.js +9 -102
- package/dist/components/shared/EmptyState.js.map +1 -1
- package/dist/components/shared/PageHeader.js +16 -47
- package/dist/components/shared/PageHeader.js.map +1 -1
- package/dist/components/tunnels/TunnelCreateWizard.js +17 -14
- package/dist/components/tunnels/TunnelCreateWizard.js.map +1 -1
- package/dist/constants/pluginCatalog.js +139 -4
- package/dist/constants/pluginCatalog.js.map +1 -1
- package/dist/hooks/useGitops.js +135 -0
- package/dist/hooks/useGitops.js.map +1 -0
- package/dist/pages/GettingStartedPage.js +6 -6
- package/dist/pages/PluginStandalonePage.js +12 -12
- package/dist/pages/VibeDetailsPage.js +572 -566
- package/dist/pages/VibeDetailsPage.js.map +1 -1
- package/dist/pages/VibesPage.js +304 -291
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/services/gitopsApi.js +128 -0
- package/dist/services/gitopsApi.js.map +1 -0
- package/package.json +2 -2
- package/dist/components/security/SecurityEmptyState.js.map +0 -1
- package/dist/components/security/SecurityEvidenceList.js +0 -42
- package/dist/components/security/SecurityEvidenceList.js.map +0 -1
- package/dist/components/security/SecurityFindingsTable.js +0 -201
- package/dist/components/security/SecurityFindingsTable.js.map +0 -1
- package/dist/components/security/SecurityHeaderStrip.js +0 -67
- package/dist/components/security/SecurityHeaderStrip.js.map +0 -1
- package/dist/components/security/SecurityLifecycleStepper.js +0 -66
- package/dist/components/security/SecurityLifecycleStepper.js.map +0 -1
- package/dist/components/security/SecurityProviderDrawer.js +0 -187
- package/dist/components/security/SecurityProviderDrawer.js.map +0 -1
- package/dist/components/security/gql.js +0 -128
- package/dist/components/security/gql.js.map +0 -1
|
@@ -1,65 +1,132 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useVibeControls as a } from "../../providers/VibeControlsProvider.js";
|
|
1
|
+
import { useVibeControls as e } from "../../providers/VibeControlsProvider.js";
|
|
3
2
|
import "../../constants/pluginCatalog.js";
|
|
4
|
-
import { PluginGate as
|
|
3
|
+
import { PluginGate as t } from "../shared/PluginGate.js";
|
|
5
4
|
import "../shared/index.js";
|
|
6
|
-
import { appendIframeTokenParam as
|
|
7
|
-
import { RepoStatsTab as
|
|
8
|
-
import { CITab as
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
5
|
+
import { appendIframeTokenParam as n, mintIframeToken as r, scheduleIframeTokenRefresh as i } from "../../services/iframeToken.js";
|
|
6
|
+
import { RepoStatsTab as a } from "./RepoStatsTab.js";
|
|
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 c } from "../../hooks/useGitops.js";
|
|
9
|
+
import { GitOpsSetup as se } from "./GitOpsSetup.js";
|
|
10
|
+
import { GitOpsProviderPicker as ce } from "./GitOpsProviderPicker.js";
|
|
11
|
+
import { composeCiStatus as le, composeRepoStats as ue } from "./types.js";
|
|
12
|
+
import { useCallback as de, useEffect as l, useMemo as u, useState as d } from "react";
|
|
13
|
+
import { BarChart3 as f, GitBranch as p, GitMerge as m, Play as h, RefreshCw as g } from "lucide-react";
|
|
14
|
+
import { useQueryClient as fe } from "@tanstack/react-query";
|
|
15
|
+
import { motion as pe } from "framer-motion";
|
|
16
|
+
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
14
17
|
//#region src/components/gitops/VibeGitOpsTab.tsx
|
|
15
|
-
var
|
|
16
|
-
let [
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}),
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
18
|
+
var b = ({ vibeId: b, agentId: C, profile: w, tunnelUrl: T, apiKey: E, tr: D }) => {
|
|
19
|
+
let [O, me] = d("repo-stats"), [k, A] = d(!1), [j, M] = d(null), [N, he] = d(null), { workspaceId: P } = e(), F = fe(), I = u(() => ({
|
|
20
|
+
tunnelUrl: T,
|
|
21
|
+
apiKey: E,
|
|
22
|
+
profile: w,
|
|
23
|
+
workspaceId: P ?? "",
|
|
24
|
+
agentId: C
|
|
25
|
+
}), [
|
|
26
|
+
T,
|
|
27
|
+
E,
|
|
28
|
+
w,
|
|
29
|
+
P,
|
|
30
|
+
C
|
|
31
|
+
]), L = ie(I), R = u(() => L.data?.filter((e) => e.registered && e.authenticated) ?? [], [L.data]), z = R.length > 0;
|
|
32
|
+
l(() => {
|
|
33
|
+
if (!j && R.length > 0) {
|
|
34
|
+
let e = R[0];
|
|
35
|
+
e && M(e.name);
|
|
36
|
+
}
|
|
37
|
+
}, [R, j]);
|
|
38
|
+
let B = s(I, j, { limit: 30 });
|
|
39
|
+
l(() => {
|
|
40
|
+
if (!N && B.data?.items.length) {
|
|
41
|
+
let e = B.data.items[0];
|
|
42
|
+
e && he(e.fqn);
|
|
43
|
+
}
|
|
44
|
+
}, [B.data, N]);
|
|
45
|
+
let V = s(I, j, { limit: 1 });
|
|
46
|
+
s.length;
|
|
47
|
+
let H = ae(I, j, N, {
|
|
48
|
+
state: "open",
|
|
49
|
+
limit: 100
|
|
50
|
+
}), U = te(I, j, N, {
|
|
51
|
+
state: "open",
|
|
52
|
+
limit: 100
|
|
53
|
+
}), W = ne(I, j, N), G = ee(I, j, N, 20), K = c(I, j, N), q = u(() => N ? V.data?.items.find((e) => e.fqn === N) ?? B.data?.items.find((e) => e.fqn === N) ?? null : V.data?.items[0] ?? null, [
|
|
54
|
+
V.data,
|
|
55
|
+
B.data,
|
|
56
|
+
N
|
|
57
|
+
]), ge = u(() => ue({
|
|
58
|
+
repo: q,
|
|
59
|
+
pulls: H.data,
|
|
60
|
+
issues: U.data,
|
|
61
|
+
languages: W.data,
|
|
62
|
+
contributors: G.data,
|
|
63
|
+
securityAlerts: K.data
|
|
64
|
+
}), [
|
|
65
|
+
q,
|
|
66
|
+
H.data,
|
|
67
|
+
U.data,
|
|
68
|
+
W.data,
|
|
69
|
+
G.data,
|
|
70
|
+
K.data
|
|
71
|
+
]), J = oe(I, j, N, { limit: 30 }), Y = re(I, j, N), _e = u(() => le({
|
|
72
|
+
pipelines: void 0,
|
|
73
|
+
runs: J.data,
|
|
74
|
+
analytics: Y.data
|
|
75
|
+
}), [J.data, Y.data]), ve = H.isLoading || U.isLoading || W.isLoading, ye = J.isLoading || Y.isLoading, be = (H.error || U.error || W.error)?.message, xe = (J.error || Y.error)?.message, Se = de(() => {
|
|
76
|
+
F.invalidateQueries({ queryKey: [
|
|
77
|
+
"gitops",
|
|
78
|
+
P ?? "",
|
|
79
|
+
C,
|
|
80
|
+
w
|
|
81
|
+
] });
|
|
82
|
+
}, [
|
|
83
|
+
F,
|
|
84
|
+
P,
|
|
85
|
+
C,
|
|
86
|
+
w
|
|
87
|
+
]), X = H.isFetching || J.isFetching, Ce = [
|
|
46
88
|
{
|
|
47
89
|
id: "repo-stats",
|
|
48
|
-
label:
|
|
49
|
-
icon:
|
|
90
|
+
label: D("gitops.repoStats", "Repo Stats"),
|
|
91
|
+
icon: f
|
|
50
92
|
},
|
|
51
93
|
{
|
|
52
94
|
id: "ci",
|
|
53
|
-
label:
|
|
54
|
-
icon:
|
|
95
|
+
label: D("gitops.ci", "CI Pipelines"),
|
|
96
|
+
icon: h
|
|
55
97
|
},
|
|
56
98
|
{
|
|
57
99
|
id: "git-ui",
|
|
58
|
-
label:
|
|
59
|
-
icon:
|
|
100
|
+
label: D("gitops.gitUI", "Git UI"),
|
|
101
|
+
icon: p
|
|
60
102
|
}
|
|
61
|
-
];
|
|
62
|
-
return
|
|
103
|
+
], [Z, Q] = d(null), [$, we] = d(null);
|
|
104
|
+
return l(() => {
|
|
105
|
+
let e = !1, t = null;
|
|
106
|
+
return (async () => {
|
|
107
|
+
if (!C || !T || O !== "git-ui") {
|
|
108
|
+
Q(null);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
let a = await r(C, w, "/ungit");
|
|
113
|
+
if (e) return;
|
|
114
|
+
let o = `${T.replace(/\/$/, "")}/api/plugins/ungit/ui`;
|
|
115
|
+
Q(n(o, a.token)), t = i(C, w, "/ungit", a, (e) => {
|
|
116
|
+
Q(n(o, e.token));
|
|
117
|
+
});
|
|
118
|
+
} catch (t) {
|
|
119
|
+
e || we(t instanceof Error ? t.message : "Failed to fetch iframe token");
|
|
120
|
+
}
|
|
121
|
+
})(), () => {
|
|
122
|
+
e = !0, t?.cancel();
|
|
123
|
+
};
|
|
124
|
+
}, [
|
|
125
|
+
C,
|
|
126
|
+
w,
|
|
127
|
+
T,
|
|
128
|
+
O
|
|
129
|
+
]), /* @__PURE__ */ y(pe.div, {
|
|
63
130
|
initial: {
|
|
64
131
|
opacity: 0,
|
|
65
132
|
y: 8
|
|
@@ -74,213 +141,154 @@ var T = ({ vibeId: s, agentId: c, profile: l, tunnelUrl: p, apiKey: m, tr: T })
|
|
|
74
141
|
},
|
|
75
142
|
transition: { duration: .2 },
|
|
76
143
|
className: "space-y-4",
|
|
77
|
-
|
|
144
|
+
"data-vibe-id": b,
|
|
145
|
+
children: [/* @__PURE__ */ y("div", {
|
|
78
146
|
className: "flex items-center justify-between",
|
|
79
|
-
children: [/* @__PURE__ */
|
|
147
|
+
children: [/* @__PURE__ */ y("div", {
|
|
80
148
|
className: "flex items-center gap-2",
|
|
81
|
-
children: [/* @__PURE__ */
|
|
149
|
+
children: [/* @__PURE__ */ v(m, { className: "size-5 text-text-secondary" }), /* @__PURE__ */ v("h3", {
|
|
82
150
|
className: "font-semibold text-text-primary",
|
|
83
|
-
children:
|
|
151
|
+
children: D("vibeDetails.gitopsTitle", "GitOps")
|
|
84
152
|
})]
|
|
85
|
-
}), /* @__PURE__ */
|
|
153
|
+
}), /* @__PURE__ */ y("div", {
|
|
86
154
|
className: "flex items-center gap-2",
|
|
87
|
-
children: [
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
children: [
|
|
156
|
+
z && /* @__PURE__ */ v(ce, {
|
|
157
|
+
providers: R.map((e) => e.name),
|
|
158
|
+
selected: j,
|
|
159
|
+
onChange: M
|
|
160
|
+
}),
|
|
161
|
+
z && (O === "repo-stats" || O === "ci") && /* @__PURE__ */ y("button", {
|
|
162
|
+
type: "button",
|
|
163
|
+
onClick: Se,
|
|
164
|
+
disabled: X,
|
|
165
|
+
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
|
+
"data-testid": "gitops-refresh",
|
|
167
|
+
children: [/* @__PURE__ */ v(g, { className: `size-3 ${X ? "animate-spin" : ""}` }), D("gitops.refresh", "Refresh")]
|
|
168
|
+
}),
|
|
169
|
+
!k && /* @__PURE__ */ v("button", {
|
|
170
|
+
type: "button",
|
|
171
|
+
onClick: () => A(!0),
|
|
172
|
+
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",
|
|
173
|
+
"data-testid": "gitops-add-provider",
|
|
174
|
+
children: D(z ? "gitops.addAnotherProvider" : "gitops.addProvider", "Add Provider")
|
|
175
|
+
})
|
|
176
|
+
]
|
|
99
177
|
})]
|
|
100
|
-
}), /* @__PURE__ */
|
|
178
|
+
}), /* @__PURE__ */ y("div", {
|
|
101
179
|
className: "flex flex-col gap-4 lg:flex-row lg:gap-6",
|
|
102
|
-
children: [/* @__PURE__ */
|
|
180
|
+
children: [/* @__PURE__ */ v("nav", {
|
|
103
181
|
className: "w-full flex-shrink-0 rounded-lg border border-border-default bg-bg-surface p-2 lg:w-56",
|
|
104
182
|
role: "tablist",
|
|
105
|
-
"aria-label":
|
|
106
|
-
children:
|
|
107
|
-
let t = e.icon, n =
|
|
108
|
-
return /* @__PURE__ */
|
|
183
|
+
"aria-label": D("gitops.navigation", "GitOps navigation"),
|
|
184
|
+
children: Ce.map((e) => {
|
|
185
|
+
let t = e.icon, n = O === e.id;
|
|
186
|
+
return /* @__PURE__ */ y("button", {
|
|
109
187
|
type: "button",
|
|
110
188
|
role: "tab",
|
|
111
189
|
"aria-selected": n,
|
|
112
|
-
onClick: () =>
|
|
190
|
+
onClick: () => me(e.id),
|
|
113
191
|
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"}`,
|
|
114
|
-
children: [/* @__PURE__ */
|
|
192
|
+
children: [/* @__PURE__ */ v(t, { className: "size-4 shrink-0" }), /* @__PURE__ */ v("span", {
|
|
115
193
|
className: "truncate",
|
|
116
194
|
children: e.label
|
|
117
195
|
})]
|
|
118
196
|
}, e.id);
|
|
119
197
|
})
|
|
120
|
-
}), /* @__PURE__ */
|
|
198
|
+
}), /* @__PURE__ */ y("div", {
|
|
121
199
|
className: "min-w-0 flex-1 space-y-4",
|
|
122
200
|
children: [
|
|
123
|
-
k && /* @__PURE__ */
|
|
124
|
-
|
|
125
|
-
agents: c ? [{
|
|
126
|
-
id: c,
|
|
127
|
-
name: "Active Agent"
|
|
128
|
-
}] : [],
|
|
201
|
+
k && /* @__PURE__ */ v(se, {
|
|
202
|
+
ctx: I,
|
|
129
203
|
onCreated: () => {
|
|
130
|
-
A(!1),
|
|
131
|
-
}
|
|
204
|
+
A(!1), L.refetch();
|
|
205
|
+
},
|
|
206
|
+
onCancel: () => A(!1)
|
|
132
207
|
}),
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}) : /* @__PURE__ */
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
className: "text-sm text-text-secondary",
|
|
149
|
-
children: T("gitops.noProvidersVibe", "No GitOps provider configured for this vibe. Add a provider to see repository stats.")
|
|
150
|
-
}),
|
|
151
|
-
/* @__PURE__ */ C("button", {
|
|
152
|
-
type: "button",
|
|
153
|
-
onClick: () => A(!0),
|
|
154
|
-
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",
|
|
155
|
-
children: T("gitops.addProvider", "Add Provider")
|
|
156
|
-
})
|
|
157
|
-
]
|
|
208
|
+
O === "repo-stats" && !k && /* @__PURE__ */ v(_, { children: z ? !T || !E ? /* @__PURE__ */ v(S, {
|
|
209
|
+
tr: D,
|
|
210
|
+
message: "gitops.noTunnel",
|
|
211
|
+
fallback: "Waiting for agent tunnel. Make sure the agent is running and a tunnel is active."
|
|
212
|
+
}) : /* @__PURE__ */ v(a, {
|
|
213
|
+
stats: ge,
|
|
214
|
+
loading: ve || L.isLoading,
|
|
215
|
+
error: be
|
|
216
|
+
}) : /* @__PURE__ */ v(x, {
|
|
217
|
+
tr: D,
|
|
218
|
+
icon: m,
|
|
219
|
+
message: "gitops.noProvidersVibe",
|
|
220
|
+
fallback: "No GitOps provider configured. Add a PAT to see repository stats.",
|
|
221
|
+
ctaLabel: D("gitops.addProvider", "Add Provider"),
|
|
222
|
+
onCta: () => A(!0)
|
|
158
223
|
}) }),
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}) : /* @__PURE__ */
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
})]
|
|
224
|
+
O === "ci" && !k && /* @__PURE__ */ v(_, { children: z ? !T || !E ? /* @__PURE__ */ v(S, {
|
|
225
|
+
tr: D,
|
|
226
|
+
message: "gitops.noTunnelCi",
|
|
227
|
+
fallback: "Waiting for agent tunnel."
|
|
228
|
+
}) : /* @__PURE__ */ v(o, {
|
|
229
|
+
status: _e,
|
|
230
|
+
loading: ye || L.isLoading,
|
|
231
|
+
error: xe
|
|
232
|
+
}) : /* @__PURE__ */ v(x, {
|
|
233
|
+
tr: D,
|
|
234
|
+
icon: h,
|
|
235
|
+
message: "gitops.noProvidersCi",
|
|
236
|
+
fallback: "No GitOps provider configured. Add a PAT to see CI pipeline status.",
|
|
237
|
+
ctaLabel: D("gitops.addProvider", "Add Provider"),
|
|
238
|
+
onCta: () => A(!0)
|
|
175
239
|
}) }),
|
|
176
|
-
|
|
177
|
-
agentId:
|
|
240
|
+
O === "git-ui" && /* @__PURE__ */ v(t, {
|
|
241
|
+
agentId: C || null,
|
|
178
242
|
required: ["@vibecontrols/vibe-plugin-tool-git"],
|
|
179
243
|
mode: "all",
|
|
180
|
-
eyebrow:
|
|
181
|
-
title:
|
|
182
|
-
description:
|
|
183
|
-
children: /* @__PURE__ */
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
244
|
+
eyebrow: D("gitops.ungitEyebrow", "Ungit plugin required"),
|
|
245
|
+
title: D("gitops.ungitTitle", "Install the visual git client"),
|
|
246
|
+
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__ */ v("div", {
|
|
248
|
+
className: "rounded-lg border border-border-default bg-status-error-bg-subtle p-4",
|
|
249
|
+
children: /* @__PURE__ */ v("p", {
|
|
250
|
+
className: "text-sm text-status-error-text",
|
|
251
|
+
children: $
|
|
252
|
+
})
|
|
253
|
+
}) : Z ? /* @__PURE__ */ v("iframe", {
|
|
254
|
+
src: Z,
|
|
255
|
+
title: "Ungit",
|
|
256
|
+
className: "w-full min-h-[600px] rounded-lg border border-border-default"
|
|
257
|
+
}) : /* @__PURE__ */ v("div", {
|
|
258
|
+
className: "rounded-lg border border-border-default bg-bg-sunken p-4",
|
|
259
|
+
children: /* @__PURE__ */ v("p", {
|
|
260
|
+
className: "text-sm text-text-secondary",
|
|
261
|
+
children: D("gitops.ungitLoading", "Loading Ungit…")
|
|
262
|
+
})
|
|
189
263
|
})
|
|
190
264
|
})
|
|
191
265
|
]
|
|
192
266
|
})]
|
|
193
267
|
})]
|
|
194
268
|
}, "gitops");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
let n = !1, r = null, i = (a) => {
|
|
201
|
-
n || (g(a), r = l(e, t, "/ungit", a, i, (e) => y(e.message)));
|
|
202
|
-
};
|
|
203
|
-
return c(e, t, "/ungit").then((a) => {
|
|
204
|
-
n || (g(a), r = l(e, t, "/ungit", a, i, (e) => y(e.message)));
|
|
205
|
-
}).catch((e) => {
|
|
206
|
-
n || y(e instanceof Error ? e.message : String(e));
|
|
207
|
-
}), () => {
|
|
208
|
-
n = !0, r?.cancel();
|
|
209
|
-
};
|
|
210
|
-
}, [
|
|
211
|
-
e,
|
|
212
|
-
t,
|
|
213
|
-
a
|
|
214
|
-
]);
|
|
215
|
-
let b = p(() => {
|
|
216
|
-
if (!n || !r) return;
|
|
217
|
-
let e = { "Content-Type": "application/json" };
|
|
218
|
-
e["x-agent-api-key"] = r, fetch(`${n}/api/profiles/${encodeURIComponent(t)}/ungit/status`, { headers: e }).then((e) => e.ok ? e.json() : null).then((e) => {
|
|
219
|
-
e && o(!!e.running);
|
|
220
|
-
}).catch(() => o(!1));
|
|
221
|
-
}, [
|
|
222
|
-
n,
|
|
223
|
-
r,
|
|
224
|
-
t
|
|
225
|
-
]);
|
|
226
|
-
return m(() => {
|
|
227
|
-
b();
|
|
228
|
-
}, [b]), n ? /* @__PURE__ */ w("div", {
|
|
229
|
-
className: "rounded-xl border border-border-default overflow-hidden",
|
|
230
|
-
children: [/* @__PURE__ */ w("div", {
|
|
231
|
-
className: "p-3 bg-bg-sunken border-b border-border-default flex items-center justify-between gap-3",
|
|
232
|
-
children: [
|
|
233
|
-
/* @__PURE__ */ C("p", {
|
|
234
|
-
className: "text-xs text-text-secondary",
|
|
235
|
-
children: i("gitops.ungitNote", "Visual git client powered by Ungit plugin on the agent.")
|
|
236
|
-
}),
|
|
237
|
-
!a && !u && /* @__PURE__ */ C("button", {
|
|
238
|
-
type: "button",
|
|
239
|
-
onClick: () => {
|
|
240
|
-
if (!r) return;
|
|
241
|
-
d(!0);
|
|
242
|
-
let e = { "Content-Type": "application/json" };
|
|
243
|
-
e["x-agent-api-key"] = r, fetch(`${n}/api/profiles/${encodeURIComponent(t)}/ungit/start`, {
|
|
244
|
-
method: "POST",
|
|
245
|
-
headers: e,
|
|
246
|
-
body: "{}"
|
|
247
|
-
}).then((e) => e.json()).then(() => {
|
|
248
|
-
o(!0), d(!1);
|
|
249
|
-
}).catch(() => d(!1));
|
|
250
|
-
},
|
|
251
|
-
className: "px-3 py-1 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors",
|
|
252
|
-
children: i("gitops.startUngit", "Start Ungit")
|
|
253
|
-
}),
|
|
254
|
-
u && /* @__PURE__ */ C("span", {
|
|
255
|
-
className: "text-xs text-text-secondary animate-pulse",
|
|
256
|
-
children: i("gitops.startingUngit", "Starting...")
|
|
257
|
-
})
|
|
258
|
-
]
|
|
259
|
-
}), a ? f ? /* @__PURE__ */ C("iframe", {
|
|
260
|
-
src: s(`${n}/ungit/`, f.token),
|
|
261
|
-
title: "Git UI (Ungit)",
|
|
262
|
-
className: "w-full",
|
|
263
|
-
style: { height: "700px" },
|
|
264
|
-
sandbox: "allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation-by-user-activation allow-modals"
|
|
265
|
-
}) : /* @__PURE__ */ C("div", {
|
|
266
|
-
className: "p-8 text-center text-sm text-text-secondary",
|
|
267
|
-
children: v ? i("gitops.iframeTokenError", `Iframe token error: ${v}`) : i("gitops.mintingIframeToken", "Authorizing iframe…")
|
|
268
|
-
}) : /* @__PURE__ */ w("div", {
|
|
269
|
-
className: "p-8 text-center",
|
|
270
|
-
children: [/* @__PURE__ */ C(_, { className: "size-8 text-text-secondary mx-auto mb-3" }), /* @__PURE__ */ C("p", {
|
|
271
|
-
className: "text-sm text-text-secondary",
|
|
272
|
-
children: i("gitops.ungitNotRunning", "Ungit is installed but not running. Click \"Start Ungit\" to launch the visual git client.")
|
|
273
|
-
})]
|
|
274
|
-
})]
|
|
275
|
-
}) : /* @__PURE__ */ C("div", {
|
|
276
|
-
className: "rounded-lg border border-border-default bg-bg-sunken p-4",
|
|
277
|
-
children: /* @__PURE__ */ C("p", {
|
|
269
|
+
}, x = ({ tr: e, icon: t, message: n, fallback: r, ctaLabel: i, onCta: a }) => /* @__PURE__ */ y("div", {
|
|
270
|
+
className: "rounded-lg border border-border-default bg-bg-surface p-6 text-center",
|
|
271
|
+
children: [
|
|
272
|
+
/* @__PURE__ */ v(t, { className: "size-8 text-text-secondary mx-auto mb-3" }),
|
|
273
|
+
/* @__PURE__ */ v("p", {
|
|
278
274
|
className: "text-sm text-text-secondary",
|
|
279
|
-
children:
|
|
275
|
+
children: e(n, r)
|
|
276
|
+
}),
|
|
277
|
+
i && a && /* @__PURE__ */ v("button", {
|
|
278
|
+
type: "button",
|
|
279
|
+
onClick: a,
|
|
280
|
+
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
|
+
children: i
|
|
280
282
|
})
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
+
]
|
|
284
|
+
}), S = ({ tr: e, message: t, fallback: n }) => /* @__PURE__ */ v("div", {
|
|
285
|
+
className: "rounded-lg border border-border-default bg-bg-sunken p-4",
|
|
286
|
+
children: /* @__PURE__ */ v("p", {
|
|
287
|
+
className: "text-sm text-text-secondary",
|
|
288
|
+
children: e(t, n)
|
|
289
|
+
})
|
|
290
|
+
});
|
|
283
291
|
//#endregion
|
|
284
|
-
export {
|
|
292
|
+
export { b as VibeGitOpsTab };
|
|
285
293
|
|
|
286
294
|
//# 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 * Shows sub-tabs:\n * 1. Repo Stats — provider-based repo info via GraphQL\n * 2. CI Pipelines — provider-based CI status via GraphQL\n * 3. Git UI — Ungit iframe (only when the ungit plugin is installed on the agent with hasUI)\n */\nimport { useCallback, useEffect, useState, type FC } from 'react';\nimport { motion } from 'framer-motion';\nimport { GitMerge, BarChart3, Play, GitBranch, RefreshCw } from 'lucide-react';\nimport {\n useListGitopsProvidersQuery,\n useGetRepoStatsQuery,\n useGetCiStatusQuery,\n useRefreshRepoStatsMutation,\n useRefreshCiStatusMutation,\n} from '../../generated/wspace-operations';\nimport { RepoStatsTab } from './RepoStatsTab';\nimport { CITab } from './CITab';\nimport { GitOpsSetup } from './GitOpsSetup';\nimport { PluginGate } from '@/components/shared';\nimport { GIT_PACKAGE } from '@/constants/pluginCatalog';\nimport { useVibeControls } from '@/providers/VibeControlsProvider';\nimport {\n mintIframeToken,\n scheduleIframeTokenRefresh,\n appendIframeTokenParam,\n type IframeTokenAutoRefreshHandle,\n type IframeTokenResponse,\n} from '@/services/iframeToken';\n\ninterface VibeGitOpsTabProps {\n vibeId: string;\n agentId: string;\n /** Canonical agent profile name — required for the agent → profile → plugins/data URL hierarchy. */\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 { workspaceId } = useVibeControls();\n\n // Fetch providers for this vibe\n const {\n data: providersData,\n loading: providersLoading,\n refetch: refetchProviders,\n } = useListGitopsProvidersQuery({\n variables: { filter: { vibeId }, pagination: { limit: 50 } },\n fetchPolicy: 'cache-and-network',\n });\n\n const providers = providersData?.listGitopsProviders?.items ?? [];\n const activeProvider = providers.find((p: { isActive: boolean }) => p.isActive);\n const selectedProviderId = activeProvider?.id || providers[0]?.id || '';\n\n // Fetch repo stats\n const {\n data: repoStatsData,\n loading: repoStatsLoading,\n error: repoStatsError,\n } = useGetRepoStatsQuery({\n variables: { providerId: selectedProviderId, agentId },\n skip: !selectedProviderId || !agentId || subTab !== 'repo-stats',\n fetchPolicy: 'cache-and-network',\n });\n\n // Fetch CI status\n const {\n data: ciStatusData,\n loading: ciStatusLoading,\n error: ciStatusError,\n } = useGetCiStatusQuery({\n variables: { providerId: selectedProviderId, agentId, limit: 30 },\n skip: !selectedProviderId || !agentId || subTab !== 'ci',\n fetchPolicy: 'cache-and-network',\n });\n\n // Refresh mutations\n const [refreshRepoStats, { loading: refreshingStats }] = useRefreshRepoStatsMutation();\n const [refreshCIStatus, { loading: refreshingCI }] = useRefreshCiStatusMutation();\n\n const handleRefresh = async () => {\n if (!selectedProviderId || !agentId) return;\n if (subTab === 'repo-stats') {\n await refreshRepoStats({\n variables: { providerId: selectedProviderId, agentId },\n });\n } else if (subTab === 'ci') {\n await refreshCIStatus({\n variables: { providerId: selectedProviderId, agentId },\n });\n }\n };\n\n const isRefreshing = refreshingStats || refreshingCI;\n const hasProviders = providers.length > 0;\n\n // Git UI sub-tab is always visible; the body is PluginGate-wrapped so the\n // user is prompted to install the ungit plugin on demand.\n const subTabs: { id: GitOpsSubTab; label: string; icon: typeof BarChart3 }[] = [\n {\n id: 'repo-stats',\n label: tr('gitops.repoStats', 'Repo Stats'),\n icon: BarChart3,\n },\n { id: 'ci', label: tr('gitops.ci', 'CI Pipelines'), icon: Play },\n {\n id: 'git-ui' as GitOpsSubTab,\n label: tr('gitops.gitUI', 'Git UI'),\n icon: GitBranch,\n },\n ];\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 >\n {/* Header */}\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 && agentId && (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 >\n <RefreshCw className={`size-3 ${isRefreshing ? 'animate-spin' : ''}`} />\n {tr('gitops.refresh', 'Refresh')}\n </button>\n )}\n {!hasProviders && !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 >\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 {/* Setup form */}\n {showSetup && (\n <GitOpsSetup\n workspaceId={workspaceId || ''}\n agents={agentId ? [{ id: agentId, name: 'Active Agent' }] : []}\n onCreated={() => {\n setShowSetup(false);\n void refetchProviders();\n }}\n />\n )}\n\n {/* Repo Stats */}\n {subTab === 'repo-stats' && !showSetup && (\n <>\n {!hasProviders ? (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6 text-center\">\n <GitMerge className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gitops.noProvidersVibe',\n 'No GitOps provider configured for this vibe. Add a provider to see repository stats.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={() => setShowSetup(true)}\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 {tr('gitops.addProvider', 'Add Provider')}\n </button>\n </div>\n ) : !agentId ? (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gitops.noAgent',\n 'An active agent with CLI access is required to fetch repository data. Ensure the agent has the GitHub CLI (gh) installed and authenticated.'\n )}\n </p>\n </div>\n ) : (\n <RepoStatsTab\n stats={repoStatsData?.getRepoStats ?? null}\n loading={repoStatsLoading || providersLoading}\n error={repoStatsError?.message}\n />\n )}\n </>\n )}\n\n {/* CI Pipelines */}\n {subTab === 'ci' && !showSetup && (\n <>\n {!hasProviders ? (\n <div className=\"rounded-lg border border-border-default bg-bg-surface p-6 text-center\">\n <Play className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gitops.noProvidersCi',\n 'No GitOps provider configured. Add a provider to see CI pipeline status.'\n )}\n </p>\n </div>\n ) : !agentId ? (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gitops.noAgentCi',\n 'An active agent with CLI access is required to fetch CI status.'\n )}\n </p>\n </div>\n ) : (\n <CITab\n status={ciStatusData?.getCIStatus ?? null}\n loading={ciStatusLoading || providersLoading}\n error={ciStatusError?.message}\n />\n )}\n </>\n )}\n\n {/* Git UI (Ungit iframe) — gated on the vibe-plugin-tool-git plugin */}\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 <UngitRuntimePanel\n agentId={agentId}\n profile={profile}\n tunnelUrl={tunnelUrl}\n apiKey={apiKey}\n tr={tr}\n />\n </PluginGate>\n )}\n </div>\n </div>\n </motion.div>\n );\n};\n\nfunction UngitRuntimePanel({\n agentId,\n profile,\n tunnelUrl,\n apiKey,\n tr,\n}: {\n agentId: string;\n profile: string;\n tunnelUrl: string | null;\n apiKey: string | null;\n tr: (key: string, fallback: string) => string;\n}) {\n const [ungitRunning, setUngitRunning] = useState(false);\n const [ungitStarting, setUngitStarting] = useState(false);\n // Iframe-token state — replaces the previous ?apiKey= URL param.\n // Token TTL is enforced server-side at <= 600s; we re-mint ~60s before expiry.\n const [iframeToken, setIframeToken] = useState<IframeTokenResponse | null>(null);\n const [tokenError, setTokenError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!agentId || !ungitRunning) return;\n let cancelled = false;\n let handle: IframeTokenAutoRefreshHandle | null = null;\n\n const onRefresh = (next: IframeTokenResponse) => {\n if (cancelled) return;\n setIframeToken(next);\n handle = scheduleIframeTokenRefresh(agentId, profile, '/ungit', next, onRefresh, (err) =>\n setTokenError(err.message)\n );\n };\n\n mintIframeToken(agentId, profile, '/ungit')\n .then((tok) => {\n if (cancelled) return;\n setIframeToken(tok);\n handle = scheduleIframeTokenRefresh(agentId, profile, '/ungit', tok, onRefresh, (err) =>\n setTokenError(err.message)\n );\n })\n .catch((err: unknown) => {\n if (cancelled) return;\n setTokenError(err instanceof Error ? err.message : String(err));\n });\n\n return () => {\n cancelled = true;\n handle?.cancel();\n };\n }, [agentId, profile, ungitRunning]);\n\n const refreshUngitStatus = useCallback(() => {\n if (!tunnelUrl || !apiKey) return;\n const headers: Record<string, string> = { 'Content-Type': 'application/json' };\n headers['x-agent-api-key'] = apiKey;\n fetch(`${tunnelUrl}/api/profiles/${encodeURIComponent(profile)}/ungit/status`, { headers })\n .then((r) => (r.ok ? r.json() : null))\n .then((data) => {\n if (data) setUngitRunning(!!data.running);\n })\n .catch(() => setUngitRunning(false));\n }, [tunnelUrl, apiKey, profile]);\n\n useEffect(() => {\n refreshUngitStatus();\n }, [refreshUngitStatus]);\n\n if (!tunnelUrl) {\n return (\n <div className=\"rounded-lg border border-border-default bg-bg-sunken p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gitops.ungitNoTunnel',\n 'Agent tunnel URL is not available. Confirm the agent is running and try again.'\n )}\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"rounded-xl border border-border-default overflow-hidden\">\n <div className=\"p-3 bg-bg-sunken border-b border-border-default flex items-center justify-between gap-3\">\n <p className=\"text-xs text-text-secondary\">\n {tr('gitops.ungitNote', 'Visual git client powered by Ungit plugin on the agent.')}\n </p>\n {!ungitRunning && !ungitStarting && (\n <button\n type=\"button\"\n onClick={() => {\n if (!apiKey) return;\n setUngitStarting(true);\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n headers['x-agent-api-key'] = apiKey;\n fetch(`${tunnelUrl}/api/profiles/${encodeURIComponent(profile)}/ungit/start`, {\n method: 'POST',\n headers,\n body: '{}',\n })\n .then((r) => r.json())\n .then(() => {\n setUngitRunning(true);\n setUngitStarting(false);\n })\n .catch(() => setUngitStarting(false));\n }}\n className=\"px-3 py-1 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('gitops.startUngit', 'Start Ungit')}\n </button>\n )}\n {ungitStarting && (\n <span className=\"text-xs text-text-secondary animate-pulse\">\n {tr('gitops.startingUngit', 'Starting...')}\n </span>\n )}\n </div>\n {ungitRunning ? (\n iframeToken ? (\n <iframe\n src={appendIframeTokenParam(`${tunnelUrl}/ungit/`, iframeToken.token)}\n title=\"Git UI (Ungit)\"\n className=\"w-full\"\n style={{ height: '700px' }}\n sandbox=\"allow-scripts allow-same-origin allow-forms allow-popups allow-top-navigation-by-user-activation allow-modals\"\n />\n ) : (\n <div className=\"p-8 text-center text-sm text-text-secondary\">\n {tokenError\n ? tr('gitops.iframeTokenError', `Iframe token error: ${tokenError}`)\n : tr('gitops.mintingIframeToken', 'Authorizing iframe…')}\n </div>\n )\n ) : (\n <div className=\"p-8 text-center\">\n <GitBranch className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'gitops.ungitNotRunning',\n 'Ungit is installed but not running. Click \"Start Ungit\" to launch the visual git client.'\n )}\n </p>\n </div>\n )}\n </div>\n );\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 appendIframeTokenParam,\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 const initial: IframeTokenResponse = await mintIframeToken(agentId, profile, '/ungit');\n if (cancelled) return;\n const baseUrl = `${tunnelUrl.replace(/\\/$/, '')}/api/plugins/ungit/ui`;\n setUngitIframeUrl(appendIframeTokenParam(baseUrl, initial.token));\n handle = scheduleIframeTokenRefresh(\n agentId,\n profile,\n '/ungit',\n initial,\n (next: IframeTokenResponse) => {\n setUngitIframeUrl(appendIframeTokenParam(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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"}
|