Skip to content

Commit e5697c6

Browse files
authored
Allow the operator to set the default kairos-init (#368)
* Allow the operator to set the default kairos-init and let the user override it Signed-off-by: Dimitris Karakasilis <[email protected]> * Add "Show advanced options" toggle switch Signed-off-by: Dimitris Karakasilis <[email protected]> * Fix roundness of border of the last accordion body and header Signed-off-by: Dimitris Karakasilis <[email protected]> * Fix black icons not showing good on dark background Fixes kairos-io/kairos#3471 Signed-off-by: Dimitris Karakasilis <[email protected]> * Test new functionality Signed-off-by: Dimitris Karakasilis <[email protected]> * Update resetForm with the new field (although it's not used anywhere) Signed-off-by: Dimitris Karakasilis <[email protected]> --------- Signed-off-by: Dimitris Karakasilis <[email protected]>
1 parent f363262 commit e5697c6

File tree

17 files changed

+558
-20
lines changed

17 files changed

+558
-20
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ dev: build-js build-go ## Build JS assets, compile Go binary, and run Docker ima
3434
-v $(PWD)/builds:/builds \
3535
-v /dev:/dev \
3636
-v /var/run/docker.sock:/var/run/docker.sock \
37-
quay.io/kairos/auroraboot web --builds-dir /builds --create-worker $(ARGS)
37+
quay.io/kairos/auroraboot web --builds-dir /builds --create-worker $(ARGS) --default-kairos-init-version v0.4.9
3838

3939
# Build JavaScript assets (only if needed)
4040
build-js: internal/web/app/package.json ## Build JavaScript and CSS assets

e2e/web/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ The test suite covers the following functionality:
3232
- Responsive design testing
3333
- Keyboard accessibility
3434

35+
### Advanced Options Feature (`advanced-options.cy.js`)
36+
- Advanced options checkbox functionality
37+
- Kairos-init version field visibility toggling
38+
- Form submission with hidden/visible advanced fields
39+
- Config API integration for default values
40+
- User interaction testing (label clicking, keyboard navigation)
41+
- State persistence across tab switches
42+
- Error handling for config API failures
43+
- Accessibility and user experience validation
44+
3545
## Running Tests
3646

3747
### Prerequisites
@@ -63,6 +73,9 @@ npm run test:logs
6373
# Test original form functionality
6474
npm run test:original
6575

76+
# Test advanced options functionality
77+
npm run test:advanced
78+
6679
# Run headless
6780
npm run test:headless
6881
```

e2e/web/cypress/e2e/advanced-options.cy.js

Lines changed: 396 additions & 0 deletions
Large diffs are not rendered by default.

e2e/web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"test:headless": "cypress run --headless",
66
"test:builds": "cypress run --spec 'cypress/e2e/builds-tab.cy.js'",
77
"test:logs": "cypress run --spec 'cypress/e2e/build-logs.cy.js'",
8-
"test:original": "cypress run --spec 'cypress/e2e/scpec.cy.js'"
8+
"test:original": "cypress run --spec 'cypress/e2e/scpec.cy.js'",
9+
"test:advanced": "cypress run --spec 'cypress/e2e/advanced-options.cy.js'"
910
},
1011
"dependencies": {
1112
"cypress": "^14.0.0"

internal/cmd/web.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ var WebCMD = cli.Command{
3535
Usage: "Start a local worker in a goroutine",
3636
Value: false,
3737
},
38+
&cli.StringFlag{
39+
Name: "default-kairos-init-version",
40+
Usage: "Default kairos-init version to prefill in the form (e.g., v0.6.0). If not specified, the form will start empty and use 'latest' when submitted empty.",
41+
Value: "",
42+
},
3843
},
3944
Action: func(c *cli.Context) error {
4045
os.MkdirAll(c.String("artifact-dir"), os.ModePerm)
@@ -61,10 +66,11 @@ var WebCMD = cli.Command{
6166
}
6267

6368
return web.App(web.AppConfig{
64-
EnableLogger: true,
65-
ListenAddr: c.String("address"),
66-
OutDir: c.String("artifact-dir"),
67-
BuildsDir: c.String("builds-dir"),
69+
EnableLogger: true,
70+
ListenAddr: c.String("address"),
71+
OutDir: c.String("artifact-dir"),
72+
BuildsDir: c.String("builds-dir"),
73+
DefaultKairosInitVersion: c.String("default-kairos-init-version"),
6874
})
6975
},
7076
}

internal/web/api_handlers.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ type BuildWithUUID struct {
3939
jobstorage.BuildJob
4040
}
4141

42+
// ConfigResponse represents the application configuration
43+
type ConfigResponse struct {
44+
DefaultKairosInitVersion string `json:"default_kairos_init_version"`
45+
}
46+
47+
// @Summary Get application configuration
48+
// @Description Returns the application configuration including default values for the form
49+
// @Tags config
50+
// @Accept json
51+
// @Produce json
52+
// @Success 200 {object} ConfigResponse
53+
// @Router /config [get]
54+
func HandleGetConfig(c echo.Context) error {
55+
return c.JSON(http.StatusOK, ConfigResponse{
56+
DefaultKairosInitVersion: appConfig.DefaultKairosInitVersion,
57+
})
58+
}
59+
4260
// @Summary List all build jobs
4361
// @Description Returns a paginated list of all build jobs, optionally filtered by status
4462
// @Tags builds

internal/web/app.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import (
2020
var staticFiles embed.FS
2121

2222
var mu sync.Mutex
23+
var appConfig AppConfig
2324

2425
type AppConfig struct {
25-
EnableLogger bool
26-
ListenAddr string
27-
OutDir string
28-
BuildsDir string
26+
EnableLogger bool
27+
ListenAddr string
28+
OutDir string
29+
BuildsDir string
30+
DefaultKairosInitVersion string
2931
}
3032

3133
func getFileSystem(useOS bool) http.FileSystem {
@@ -43,6 +45,7 @@ func getFileSystem(useOS bool) http.FileSystem {
4345

4446
func App(config AppConfig) error {
4547
jobstorage.BuildsDir = config.BuildsDir
48+
appConfig = config
4649
e := echo.New()
4750

4851
if config.EnableLogger {
@@ -62,6 +65,7 @@ func App(config AppConfig) error {
6265

6366
// API routes
6467
api := e.Group("/api/v1")
68+
api.GET("/config", HandleGetConfig)
6569
api.GET("/builds", HandleListBuilds)
6670
api.POST("/builds", HandleQueueBuild)
6771
api.POST("/builds/bind", HandleBindBuildJob)
@@ -153,6 +157,7 @@ func buildHandler(c echo.Context) error {
153157
KubernetesVersion: kubernetesVersion,
154158
Image: image,
155159
Version: c.FormValue("version"),
160+
KairosInitVersion: c.FormValue("kairos_init_version"),
156161
Artifacts: artifacts,
157162
CloudConfig: c.FormValue("cloud_config"),
158163
},

internal/web/app/accordion-view.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,13 @@ export function createAccordionView() {
1414

1515
// Accordion-specific state
1616
openSections: ['base-image'], // Start with base-image open
17+
showAdvancedOptions: false, // Controls visibility of advanced options
1718

1819
// Initialize watchers for form data changes
1920
init() {
21+
// Load configuration from server
22+
this.loadConfig();
23+
2024
// Watch for variant changes
2125
this.$watch('formData.variant', (newValue, oldValue) => {
2226
this.handleVariantChange();
@@ -28,6 +32,23 @@ export function createAccordionView() {
2832
});
2933
},
3034

35+
// Load application configuration
36+
async loadConfig() {
37+
try {
38+
const response = await fetch('/api/v1/config');
39+
if (response.ok) {
40+
const config = await response.json();
41+
// Set default kairos-init version if provided by operator
42+
if (config.default_kairos_init_version) {
43+
this.formData.kairos_init_version = config.default_kairos_init_version;
44+
}
45+
}
46+
} catch (error) {
47+
console.warn('Failed to load config:', error);
48+
// Silently fail - form will work with defaults
49+
}
50+
},
51+
3152
// Section definitions - data-driven approach
3253
sections: [
3354
{
@@ -184,6 +205,25 @@ export function createAccordionView() {
184205
visible: true,
185206
getSelectedLabel: 'getArtifactsLabel',
186207
getSelectedIcons: 'getSelectedArtifactIcons' // Multiple icons for artifacts
208+
},
209+
{
210+
id: 'kairos-init-version',
211+
title: 'Kairos Init Version',
212+
type: 'text-input',
213+
formField: 'kairos_init_version',
214+
placeholder: 'latest',
215+
visible: true,
216+
advanced: true, // This marks it as an advanced option
217+
description: 'Specify the version of kairos-init to use for building the image. Leave empty to use the latest version.',
218+
infoPopover: {
219+
title: 'Kairos Init Version',
220+
content: 'This controls which features and bug fixes are included in the build process. Different versions of kairos-init may support different base images or have various performance improvements.',
221+
link: {
222+
url: 'https://github.com/kairos-io/kairos-init/releases',
223+
text: 'View available versions'
224+
}
225+
},
226+
getSelectedLabel: 'getKairosInitVersionLabel'
187227
}
188228
],
189229

@@ -209,6 +249,40 @@ export function createAccordionView() {
209249
return this[section.dataKey] || [];
210250
},
211251

252+
// Check if a section should be visible (considering advanced options)
253+
isSectionVisible(section) {
254+
// If it's not visible in general, don't show it
255+
if (!section.visible) {
256+
return false;
257+
}
258+
// If it's an advanced option, only show it when advanced options are enabled
259+
if (section.advanced && !this.showAdvancedOptions) {
260+
return false;
261+
}
262+
return true;
263+
},
264+
265+
// Get visible sections for styling calculations
266+
getVisibleSections() {
267+
return this.sections.filter(s => this.isSectionVisible(s));
268+
},
269+
270+
// Get index of a section among visible sections
271+
getVisibleSectionIndex(section) {
272+
return this.getVisibleSections().indexOf(section);
273+
},
274+
275+
// Check if section is first visible
276+
isFirstVisibleSection(section) {
277+
return this.getVisibleSectionIndex(section) === 0;
278+
},
279+
280+
// Check if section is last visible
281+
isLastVisibleSection(section) {
282+
const visibleSections = this.getVisibleSections();
283+
return this.getVisibleSectionIndex(section) === visibleSections.length - 1;
284+
},
285+
212286

213287

214288
getSelectedValue(section) {
Lines changed: 1 addition & 1 deletion
Loading

internal/web/app/assets/img/arm.svg

Lines changed: 1 addition & 1 deletion
Loading

0 commit comments

Comments
 (0)