Skip to content

Commit f1e9c61

Browse files
authored
chore: refactor flux control vars to come from the API (#317)
1 parent 53826dc commit f1e9c61

File tree

8 files changed

+263
-102
lines changed

8 files changed

+263
-102
lines changed

cli/pkg/config/config.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,29 @@ type Config struct {
2121
// Kubernetes settings
2222
KubeConfigPath string
2323
InsecureSkipTLSVerify bool
24+
25+
// FluxCD controller settings (used for logs and controller discovery)
26+
FluxCD FluxCDConfig
27+
}
28+
29+
// FluxCDConfig holds configuration for FluxCD controllers and namespace.
30+
// These values can be customized via environment variables:
31+
// - FLUXCD_NAMESPACE
32+
// - FLUXCD_HELM_CONTROLLER_NAME
33+
// - FLUXCD_HELM_CONTROLLER_LABEL_KEY
34+
// - FLUXCD_HELM_CONTROLLER_LABEL_VALUE
35+
// - FLUXCD_KUSTOMIZE_CONTROLLER_NAME
36+
// - FLUXCD_KUSTOMIZE_CONTROLLER_LABEL_KEY
37+
// - FLUXCD_KUSTOMIZE_CONTROLLER_LABEL_VALUE
38+
type FluxCDConfig struct {
39+
Namespace string
40+
41+
HelmControllerDeploymentName string
42+
HelmControllerLabelKey string
43+
HelmControllerLabelValue string
44+
KustomizeControllerDeploymentName string
45+
KustomizeControllerLabelKey string
46+
KustomizeControllerLabelValue string
2447
}
2548

2649
// New returns a new configuration with sensible defaults
@@ -31,6 +54,15 @@ func New() *Config {
3154
StaticFilesDirectory: "./web/static",
3255
KubeConfigPath: defaultKubeConfigPath(),
3356
InsecureSkipTLSVerify: false,
57+
FluxCD: FluxCDConfig{
58+
Namespace: "flux-system",
59+
HelmControllerDeploymentName: "helm-controller",
60+
HelmControllerLabelKey: "app.kubernetes.io/component",
61+
HelmControllerLabelValue: "helm-controller",
62+
KustomizeControllerDeploymentName: "kustomize-controller",
63+
KustomizeControllerLabelKey: "app.kubernetes.io/component",
64+
KustomizeControllerLabelValue: "kustomize-controller",
65+
},
3466
}
3567
}
3668

@@ -64,6 +96,29 @@ func (c *Config) Parse() {
6496
if env := os.Getenv("KUBECONFIG_INSECURE_SKIP_TLS_VERIFY"); env == "true" {
6597
c.InsecureSkipTLSVerify = true
6698
}
99+
100+
// FluxCD configuration from environment variables (override defaults when set)
101+
if env := os.Getenv("FLUXCD_NAMESPACE"); env != "" {
102+
c.FluxCD.Namespace = env
103+
}
104+
if env := os.Getenv("FLUXCD_HELM_CONTROLLER_NAME"); env != "" {
105+
c.FluxCD.HelmControllerDeploymentName = env
106+
}
107+
if env := os.Getenv("FLUXCD_HELM_CONTROLLER_LABEL_KEY"); env != "" {
108+
c.FluxCD.HelmControllerLabelKey = env
109+
}
110+
if env := os.Getenv("FLUXCD_HELM_CONTROLLER_LABEL_VALUE"); env != "" {
111+
c.FluxCD.HelmControllerLabelValue = env
112+
}
113+
if env := os.Getenv("FLUXCD_KUSTOMIZE_CONTROLLER_NAME"); env != "" {
114+
c.FluxCD.KustomizeControllerDeploymentName = env
115+
}
116+
if env := os.Getenv("FLUXCD_KUSTOMIZE_CONTROLLER_LABEL_KEY"); env != "" {
117+
c.FluxCD.KustomizeControllerLabelKey = env
118+
}
119+
if env := os.Getenv("FLUXCD_KUSTOMIZE_CONTROLLER_LABEL_VALUE"); env != "" {
120+
c.FluxCD.KustomizeControllerLabelValue = env
121+
}
67122
}
68123

69124
// defaultKubeConfigPath returns the default path to the kubeconfig file

cli/pkg/server/server.go

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ type SystemView struct {
8080
Filters []SystemViewFilter `json:"filters"`
8181
}
8282

83+
// ControllerConfig represents configuration for a FluxCD controller
84+
type ControllerConfig struct {
85+
DeploymentName string `json:"deploymentName"`
86+
LabelKey string `json:"labelKey"`
87+
LabelValue string `json:"labelValue"`
88+
}
89+
90+
// FluxCDResponse represents the FluxCD configuration in the config response
91+
type FluxCDResponse struct {
92+
Namespace string `json:"namespace"`
93+
HelmController ControllerConfig `json:"helmController"`
94+
KustomizeController ControllerConfig `json:"kustomizeController"`
95+
}
96+
97+
// ConfigResponse represents the response from the /api/config endpoint
98+
type ConfigResponse struct {
99+
SystemViews []SystemView `json:"systemViews"`
100+
FluxCD FluxCDResponse `json:"fluxcd"`
101+
}
102+
83103
// defaultSystemViews contains the built‑in system views that were previously hardcoded in ViewBar.tsx.
84104
// They are now served from the backend so they can be centrally controlled and customized.
85105
var defaultSystemViews = []SystemView{
@@ -268,8 +288,21 @@ func (s *Server) Setup() {
268288

269289
// App configuration endpoint (exposes UI options like system views)
270290
s.echo.GET("/api/config", func(c echo.Context) error {
271-
return c.JSON(http.StatusOK, map[string]interface{}{
272-
"systemViews": defaultSystemViews,
291+
return c.JSON(http.StatusOK, ConfigResponse{
292+
SystemViews: defaultSystemViews,
293+
FluxCD: FluxCDResponse{
294+
Namespace: s.config.FluxCD.Namespace,
295+
HelmController: ControllerConfig{
296+
DeploymentName: s.config.FluxCD.HelmControllerDeploymentName,
297+
LabelKey: s.config.FluxCD.HelmControllerLabelKey,
298+
LabelValue: s.config.FluxCD.HelmControllerLabelValue,
299+
},
300+
KustomizeController: ControllerConfig{
301+
DeploymentName: s.config.FluxCD.KustomizeControllerDeploymentName,
302+
LabelKey: s.config.FluxCD.KustomizeControllerLabelKey,
303+
LabelValue: s.config.FluxCD.KustomizeControllerLabelValue,
304+
},
305+
},
273306
})
274307
})
275308

src/app.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ErrorProvider } from "./store/errorStore.tsx";
1818
import { UpdateNotice } from "./components/UpdateNotice.tsx";
1919
import { applyTheme, loadInitialTheme } from "./utils/theme.ts";
2020
import { keyboardManager } from "./utils/keyboardManager.ts";
21+
import { AppConfigProvider } from "./store/appConfigStore.tsx";
2122

2223
function App() {
2324
// Initialize centralized keyboard manager
@@ -35,21 +36,23 @@ function App() {
3536

3637
return (
3738
<ErrorProvider>
38-
<ApiResourceProvider>
39-
<FilterProvider>
40-
<UpdateNotice />
41-
<HashRouter>
42-
<Route path="/" component={Dashboard} />
43-
<Route path="/kustomization/:namespace/:name" component={KustomizationDetails} />
44-
<Route path="/helmrelease/:namespace/:name" component={HelmReleaseDetails} />
45-
<Route path="/terraform/:namespace/:name" component={TerraformDetails} />
46-
<Route path="/helmclassic/:namespace/:name" component={HelmClassicReleaseDetails} />
47-
<Route path="/application/:namespace/:name" component={ApplicationDetails} />
48-
<Route path="/secret/:namespace/:name" component={SecretDetails} />
49-
<Route path="/kluctldeployment/:namespace/:name" component={KluctlDeploymentDetails} />
50-
</HashRouter>
51-
</FilterProvider>
52-
</ApiResourceProvider>
39+
<AppConfigProvider>
40+
<ApiResourceProvider>
41+
<FilterProvider>
42+
<UpdateNotice />
43+
<HashRouter>
44+
<Route path="/" component={Dashboard} />
45+
<Route path="/kustomization/:namespace/:name" component={KustomizationDetails} />
46+
<Route path="/helmrelease/:namespace/:name" component={HelmReleaseDetails} />
47+
<Route path="/terraform/:namespace/:name" component={TerraformDetails} />
48+
<Route path="/helmclassic/:namespace/:name" component={HelmClassicReleaseDetails} />
49+
<Route path="/application/:namespace/:name" component={ApplicationDetails} />
50+
<Route path="/secret/:namespace/:name" component={SecretDetails} />
51+
<Route path="/kluctldeployment/:namespace/:name" component={KluctlDeploymentDetails} />
52+
</HashRouter>
53+
</FilterProvider>
54+
</ApiResourceProvider>
55+
</AppConfigProvider>
5356
</ErrorProvider>
5457
);
5558
}

src/components/viewBar/ViewBar.tsx

Lines changed: 45 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { ActiveFilter } from "../filterBar/FilterBar.tsx";
77
import { usePaneFilterStore } from "../../store/paneFilterStore.tsx";
88
import { ShortcutPrefix, doesEventMatchShortcut, getShortcutPrefix, setShortcutPrefix, getDefaultShortcutPrefix, formatShortcutForDisplay } from "../../utils/shortcuts.ts";
99
import { keyboardManager } from "../../utils/keyboardManager.ts";
10+
import { useAppConfig } from "../../store/appConfigStore.tsx";
1011

1112
export interface View {
1213
id: string;
@@ -44,6 +45,50 @@ export function ViewBar(props: ViewBarProps) {
4445
setShortcutPrefix(viewShortcutModifier());
4546
});
4647

48+
const { appConfig } = useAppConfig();
49+
50+
let systemViewsInitialized = false;
51+
52+
// Initialize system views from global app config when available
53+
createEffect(() => {
54+
if (systemViewsInitialized) return;
55+
const cfg = appConfig();
56+
if (!cfg) return;
57+
58+
const rawViews = (cfg as any).systemViews as unknown;
59+
if (Array.isArray(rawViews)) {
60+
const parsed: View[] = rawViews
61+
.map((raw: any): View | null => {
62+
if (!raw || typeof raw !== "object") return null;
63+
const id = String((raw as any).id ?? "").trim();
64+
const label = String((raw as any).label ?? "").trim();
65+
const rawFilters = (raw as any).filters;
66+
const filters: ActiveFilter[] = Array.isArray(rawFilters)
67+
? rawFilters
68+
.filter((f: any) => f && typeof f.name === "string" && typeof f.value === "string")
69+
.map((f: any) => ({ name: f.name as string, value: f.value as string }))
70+
: [];
71+
if (!id || !label || filters.length === 0) {
72+
return null;
73+
}
74+
return {
75+
id,
76+
label,
77+
isSystem: (raw as any).isSystem !== false,
78+
filters,
79+
};
80+
})
81+
.filter((v): v is View => v !== null);
82+
83+
if (parsed.length > 0) {
84+
setSystemViews(parsed);
85+
}
86+
}
87+
88+
systemViewsInitialized = true;
89+
loadViews();
90+
});
91+
4792
// Check if current filters match any existing view
4893
const hasMatchingView = createMemo(() => {
4994
const currentFilters = paneFilterStore.activeFilters || [];
@@ -172,48 +217,6 @@ export function ViewBar(props: ViewBarProps) {
172217
// Labels using formatShortcutForDisplay now rerender via Solid's reactive signal in shortcuts.ts
173218

174219
onMount(() => {
175-
// First, try to load system views from the backend config, then initialize the full list
176-
(async () => {
177-
try {
178-
const res = await fetch("/api/config");
179-
if (res.ok) {
180-
const data = await res.json() as { systemViews?: unknown };
181-
const rawViews = (data && (data as any).systemViews) as unknown;
182-
if (Array.isArray(rawViews)) {
183-
const parsed: View[] = rawViews
184-
.map((raw: any): View | null => {
185-
if (!raw || typeof raw !== "object") return null;
186-
const id = String((raw as any).id ?? "").trim();
187-
const label = String((raw as any).label ?? "").trim();
188-
const rawFilters = (raw as any).filters;
189-
const filters: ActiveFilter[] = Array.isArray(rawFilters)
190-
? rawFilters
191-
.filter((f: any) => f && typeof f.name === "string" && typeof f.value === "string")
192-
.map((f: any) => ({ name: f.name as string, value: f.value as string }))
193-
: [];
194-
if (!id || !label || filters.length === 0) {
195-
return null;
196-
}
197-
return {
198-
id,
199-
label,
200-
isSystem: (raw as any).isSystem !== false,
201-
filters,
202-
};
203-
})
204-
.filter((v): v is View => v !== null);
205-
206-
if (parsed.length > 0) {
207-
setSystemViews(parsed);
208-
}
209-
}
210-
}
211-
} catch {
212-
// Ignore network or parsing errors; fall back to whatever systemViews already holds (possibly empty)
213-
} finally {
214-
loadViews();
215-
}
216-
})();
217220
try {
218221
const handler: EventListener = () => loadViews();
219222
// store handler on window for cleanup without using `any`

src/config/fluxcd.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)