Skip to content

feat: IaC hints, YAML editor for worker configs, and EE badges#7956

Merged
rubenfiszel merged 4 commits intomainfrom
feat/crd-config-hints
Feb 15, 2026
Merged

feat: IaC hints, YAML editor for worker configs, and EE badges#7956
rubenfiszel merged 4 commits intomainfrom
feat/crd-config-hints

Conversation

@rubenfiszel
Copy link
Contributor

@rubenfiszel rubenfiszel commented Feb 15, 2026

Summary

Improves the IaC/YAML experience for worker group configuration and adds clarity to EE-gated fields in the worker config drawer.

Changes

  • Instance Settings: Move IaC hint from form view to YAML mode only, with generic "manage settings as code" text
  • Workers YAML editor: New drawer to edit all worker group configs as YAML, accessible from the "New group config" dropdown and from within each worker config drawer's action bar
    • Inline diff review step before saving (same pattern as Instance Settings)
    • Sorts default and native groups first
    • Strips cache_clear and empty fields (priority_tags: {}, env_vars_allowlist: [], etc.)
  • EE badges: Added EE badges to disabled fields in the worker config drawer (Workers assignment, High-priority tags, Alerts, Env vars, Autoscaling, Python deps, Periodic script)
  • Init script available in CE: Removed EE gate from init_bash in both frontend and backend (windmill-api-configs), promoted Init Script and Periodic Script to top-level sections, added contextual info about runs page visibility and script editor testing
  • Removed: Per-group "Copy config" button (redundant with YAML editor)

Test plan

  • Instance Settings: form view has no IaC hint → toggle YAML mode → hint appears above editor
  • Workers page: click "New group config" dropdown → "Edit all configs as YAML" opens drawer (works without EE)
  • YAML editor: edit configs → "Review & Save" shows inline diff → "Save" applies changes
  • Worker config drawer: EE badges visible on gated fields when not enterprise
  • Worker config drawer: "YAML editor" button in action bar opens YAML drawer
  • Init script editable and saveable without enterprise license
  • Periodic script still gated behind EE

Generated with Claude Code

Add subtle text notes with doc links to help K8s users discover that
instance settings and worker group configs can be managed via the
WindmillInstance CRD.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 15, 2026

Deploying windmill with  Cloudflare Pages  Cloudflare Pages

Latest commit: ee3bd85
Status: ✅  Deploy successful!
Preview URL: https://faf4a4bb.windmill.pages.dev
Branch Preview URL: https://feat-crd-config-hints.windmill.pages.dev

View logs

- Move IaC hint from form view to YAML mode in Instance Settings
- Add YAML editor drawer for worker group configs with diff review
- Add EE-only badges to gated fields in worker config drawer
- Make init_bash available in CE (frontend + backend gate)
- Promote Init/Periodic scripts to top-level sections
- Add "YAML editor" button to worker config drawer action bar
- Strip cache_clear and empty fields from YAML output
- Sort default/native groups first in YAML editor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rubenfiszel rubenfiszel changed the title feat: add CRD configuration hints to instance settings and workers pages feat: IaC hints, YAML editor for worker configs, and EE badges Feb 15, 2026
@rubenfiszel rubenfiszel marked this pull request as ready for review February 15, 2026 15:43
@claude
Copy link
Contributor

claude bot commented Feb 15, 2026

Claude finished @rubenfiszel's task in 1m 56s —— View job


PR Review: IaC hints, YAML editor for worker configs, and EE badges

  • Read and analyze all changed files
  • Review backend changes (Rust)
  • Review frontend changes (Svelte)
  • Check for code duplication
  • Review YAML save logic for safety
  • Post inline comments for specific issues
  • Post final review summary

Summary

This PR makes several good UX improvements: the YAML editor for bulk worker config management is a useful addition, EE badges add clarity for CE users, and promoting init script to CE is a reasonable change. The code is generally clean and follows existing patterns.

Issues Found

Medium — Duplicated config-cleaning logic (inline comment)
The worker config cleanup logic (stripping cache_clear, empty arrays/objects) is duplicated between openYamlDrawer() and the "Copy groups config as YAML" dropdown handler. Should be extracted to a shared helper.

Medium — name: undefined leaks into config object (inline comment)
{ ...c, name: undefined } keeps the name key with value undefined in the object, which may serialize as null. Destructuring is cleaner: const { name: _, ...rest } = c.

Medium — No protection against deleting well-known groups (inline comment)
If a user accidentally removes default or native from the YAML, saveYamlConfig will delete them without warning.

Low — No loading indicator during YAML save (inline comment)
Sequential API calls (delete + update for each group) run without any visual feedback.

Low — Missing restart warning for periodic script (inline comment)
The old code showed a "Worker restart required" alert covering both init and periodic script changes. After splitting them into separate sections, the periodic script section lost its restart warning.

Nit — Inconsistent event handler syntax (inline comment)
onClick prop vs on:click event directive used on sibling buttons in the YAML drawer actions.

Backend Review

The Rust change (windmill-api-configs/src/lib.rs) is clean — adding init_bash to the CE allowlist follows the existing pattern and the comment is updated correctly. No issues here.

What Looks Good

  • The YAML editor drawer with diff review step follows the same pattern as Instance Settings — consistent UX
  • Sorting default/native groups first in YAML output is a nice touch
  • EE badge additions are consistent across all gated fields
  • Promoting init script to CE makes sense — it's a common operational need
  • The openYamlDrawer function properly strips cache_clear and empty fields for clean YAML output

Testing Instructions

Navigate to the Workers page (/#workers). Click the "New group config" button's dropdown and select "Edit all configs as YAML" — this should open a drawer with a YAML editor showing all worker group configurations (with default and native sorted first, and empty fields stripped). Edit the YAML, then click "Review & Save" to see an inline diff, and "Save" to apply. Also open any individual worker config drawer and verify the "YAML editor" button in its action bar opens the same YAML drawer. Check that EE badges appear on disabled fields (Workers assignment, High-priority tags, Alerts, Env vars, Autoscaling, Python deps, Periodic script) when running without an enterprise license. Then navigate to Instance Settings (/#superadmin-settings), toggle the YAML mode, and verify the IaC hint ("Use this YAML to manage instance settings as code") appears above the editor (not in form view). Finally, open a worker config drawer and confirm the Init script section is editable and saveable without an enterprise license, while Periodic script remains gated behind EE.

@@ -129,10 +129,11 @@ async fn update_config(

#[cfg(not(feature = "enterprise"))]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When constructing the CE config, you allow the 'init_bash' field similar to 'worker_tags' and 'cache_clear'. Consider adding additional validation (e.g. type or length checks) for 'init_bash' if needed to prevent potential misuse.

let yamlConfigOriginal = $state('')
let yamlDiffMode = $state(false)

function openYamlDrawer() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the openYamlDrawer function, worker groups are sorted with priority for 'default' and 'native'. The sorting logic is clear; however, consider encapsulating this logic in a helper for reuse and ensure that string comparisons are case‐consistent if needed.

const oldNames = new Set(Object.keys(workerGroups ?? {}))
const newNames = new Set(newGroups.keys())

// Delete removed groups
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the saveYamlConfig function, each removal and update of a worker group configuration issues separate API calls. For scalability, consider batching these requests to reduce latency when many groups are modified.

{#if nconfig !== undefined}
<div class="mt-8"></div>
<Label label="Alerts" tooltip="Alert is sent to the configured critical error channels">
<Label label="Alerts" tooltip="Alert is sent to the configured critical error channels" eeOnly={!hasEnterpriseFeatures}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typographical note: The tooltip text reads "Alert is sent to the configured critical error channels", which is inconsistent with the label "Alerts". Consider using a plural form (e.g., "Alerts are sent to the configured critical error channels") for clarity.


<div class="mt-8"></div>
<Section label="Python dependencies overrides" collapsable={true} class="flex flex-col gap-y-6">
<Section label="Python dependencies overrides" collapsable={true} class="flex flex-col gap-y-6" eeOnly={!hasEnterpriseFeatures}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the attribute 'collapsable' might be a typographical error. Typically, it is spelled 'collapsible'. Please verify if this was intended or if it should be corrected.

on:click={() => { yamlDiffMode = false }}
>Back to editor</Button>
<Button
unifiedSize="md"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typographical inconsistency: In this hunk, the event handler is specified as onClick (with a capital C) whereas elsewhere on:click is used. Consider using on:click to maintain consistency with the Svelte convention.

Comment on lines 788 to 799
const workersConfig = Object.entries(workerGroups).map(
([name, { cache_clear, ...config }]) => {
const cleaned: Record<string, any> = { name }
for (const [k, v] of Object.entries(config)) {
if (v == null) continue
if (Array.isArray(v) && v.length === 0) continue
if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue
cleaned[k] = v
}
return cleaned
}
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated config-cleaning logic. This inline cleanup block (lines 788–799) duplicates the same logic in openYamlDrawer() (lines 393–411). If the cleaning rules ever change (e.g. stripping more fields), they'd need to be updated in two places. Consider extracting a shared helper like cleanWorkerConfig(name, config).

Suggested change
const workersConfig = Object.entries(workerGroups).map(
([name, { cache_clear, ...config }]) => {
const cleaned: Record<string, any> = { name }
for (const [k, v] of Object.entries(config)) {
if (v == null) continue
if (Array.isArray(v) && v.length === 0) continue
if (typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0) continue
cleaned[k] = v
}
return cleaned
}
)
const workersConfig = Object.entries(workerGroups).map(
([name, config]) => cleanWorkerGroupConfig(name, config)
)

(where cleanWorkerGroupConfig is a shared function also used by openYamlDrawer).

Comment on lines +438 to +451
// Delete removed groups
for (const name of oldNames) {
if (!newNames.has(name)) {
await ConfigService.deleteConfig({ name: 'worker__' + name })
}
}

// Create or update groups
for (const [name, config] of newGroups) {
await ConfigService.updateConfig({
name: 'worker__' + name,
requestBody: config
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No loading state during sequential API calls. The save function fires sequential deleteConfig and updateConfig calls without showing a loading indicator. If there are many groups this could take a while with no feedback. Consider adding a loading state (let yamlSaving = $state(false)) and disabling the Save button while saving.

Also, the operations are not atomic — if one updateConfig call fails midway, you'll have a partially applied state. This may be acceptable for an admin-level feature, but worth noting.

sendUserToast('Each entry must have a "name" field', true)
return
}
newGroups.set(c.name, { ...c, name: undefined })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name property leaks into the config object. Setting name: undefined via spread keeps the name key in the object (with value undefined), which may serialize as "name": null depending on JSON handling. A cleaner approach:

Suggested change
newGroups.set(c.name, { ...c, name: undefined })
const { name: _, ...rest } = c
newGroups.set(c.name, rest)

Comment on lines +438 to +443
// Delete removed groups
for (const name of oldNames) {
if (!newNames.has(name)) {
await ConfigService.deleteConfig({ name: 'worker__' + name })
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential accidental deletion of protected groups. If a user accidentally removes default or native from the YAML, this will delete those groups. Consider adding a confirmation dialog before deleting groups, or at minimum warning when well-known groups (default, native) would be deleted.

variant="default"
on:click={() => { yamlDiffMode = false }}
>Back to editor</Button>
<Button
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Inconsistent event handler syntax — this uses onClick (prop) while the buttons above use on:click (event). Both work, but mixing them in the same component is a bit inconsistent.

Comment on lines +832 to 838
{#if (nconfig.init_bash ?? '') !== (config?.init_bash ?? '')}
<div class="mb-2">
<Alert size="xs" type="info" title="Worker restart required">
Workers will get killed upon detecting any changes in this section (scripts or
interval). It is assumed they are in an environment where the supervisor will restart
them.
Workers will get killed upon detecting changes to the init script. It is assumed they
are in an environment where the supervisor will restart them.
</Alert>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Worker restart required" alert previously covered both init and periodic script changes. Now that they're split into separate sections, this alert only appears for init script. The periodic script section (below) no longer shows a restart warning at all when its content changes. Was this intentional? If periodic script changes also trigger worker restarts, a similar alert should be added there.

rubenfiszel and others added 2 commits February 15, 2026 15:49
- Extract serializeWorkerGroupsAsYaml() shared helper (dedup)
- Fix name:undefined leak using proper destructuring
- Protect default/native groups from accidental deletion
- Add loading indicator during YAML save
- Add restart warning for periodic script changes
- Use consistent on:click event syntax in YAML drawer actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rubenfiszel rubenfiszel merged commit 8b8e33e into main Feb 15, 2026
3 of 4 checks passed
@rubenfiszel rubenfiszel deleted the feat/crd-config-hints branch February 15, 2026 15:53
@github-actions github-actions bot locked and limited conversation to collaborators Feb 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant