mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 00:02:19 +00:00
feat: devOps role can edit worker groups (#5984)
* feat: allow devOps role to edit worker groups config - Updated backend permission checks in configs.rs to use require_devops_role() instead of require_super_admin() - Updated frontend UI in workers page to show worker group management for devOps users - Updated WorkerGroup component to allow devOps role access to all configuration features - Updated AssignableTagsInner component to allow devOps users to manage tags 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * Update configs.rs --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
@@ -21,7 +21,7 @@ use windmill_common::{
|
||||
DB,
|
||||
};
|
||||
|
||||
use crate::{db::ApiAuthed, utils::require_super_admin};
|
||||
use crate::{db::ApiAuthed, utils::{require_devops_role}};
|
||||
|
||||
pub fn global_service() -> Router {
|
||||
Router::new()
|
||||
@@ -103,7 +103,7 @@ async fn get_config(
|
||||
Path(name): Path<String>,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> error::JsonResult<Option<serde_json::Value>> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
let config = sqlx::query_as!(Config, "SELECT * FROM config WHERE name = $1", name)
|
||||
.fetch_optional(&db)
|
||||
@@ -119,7 +119,7 @@ async fn update_config(
|
||||
authed: ApiAuthed,
|
||||
Json(config): Json<serde_json::Value>,
|
||||
) -> error::Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
if name.starts_with("worker__") {
|
||||
@@ -157,7 +157,7 @@ async fn delete_config(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
) -> error::Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
@@ -232,7 +232,7 @@ async fn list_configs(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
) -> error::JsonResult<Vec<Config>> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
require_devops_role(&db, &authed.email).await?;
|
||||
let configs = sqlx::query_as!(Config, "SELECT name, config FROM config")
|
||||
.fetch_all(&db)
|
||||
.await?;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { ExternalLink, Loader2, X } from 'lucide-svelte'
|
||||
import { SettingService, WorkerService } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { superadmin } from '$lib/stores'
|
||||
import { superadmin, devopsRole } from '$lib/stores'
|
||||
import NoWorkerWithTagWarning from './runs/NoWorkerWithTagWarning.svelte'
|
||||
import { CUSTOM_TAGS_SETTING } from '$lib/consts'
|
||||
import { base } from '$lib/base'
|
||||
@@ -80,10 +80,10 @@
|
||||
loadCustomTags()
|
||||
sendUserToast('Tag added')
|
||||
}}
|
||||
disabled={newTag.trim() == '' || !$superadmin}
|
||||
disabled={newTag.trim() == '' || !($superadmin || $devopsRole)}
|
||||
>
|
||||
Add {#if !$superadmin}
|
||||
<span class="text-2xs text-tertiary">superadmin only</span>
|
||||
Add {#if !($superadmin || $devopsRole)}
|
||||
<span class="text-2xs text-tertiary">superadmin or devops only</span>
|
||||
{/if}
|
||||
</Button>
|
||||
<span class="text-sm text-primary"
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { emptyString, pluralize } from '$lib/utils'
|
||||
import { enterpriseLicense, superadmin } from '$lib/stores'
|
||||
import { enterpriseLicense, superadmin, devopsRole } from '$lib/stores'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import Editor from './Editor.svelte'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
@@ -155,7 +155,7 @@
|
||||
let vcpus_memory = $derived(computeVCpuAndMemory(workers))
|
||||
let selected = $derived(nconfig?.dedicated_worker != undefined ? 'dedicated' : 'normal')
|
||||
$effect(() => {
|
||||
$superadmin && listWorkspaces()
|
||||
($superadmin || $devopsRole) && listWorkspaces()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
<Drawer bind:this={drawer} size="800px">
|
||||
<DrawerContent
|
||||
on:close={() => drawer?.closeDrawer()}
|
||||
title={$superadmin ? `Edit worker config '${name}'` : `Worker config '${name}'`}
|
||||
title={($superadmin || $devopsRole) ? `Edit worker config '${name}'` : `Worker config '${name}'`}
|
||||
>
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert type="warning" title="Worker management UI is EE only">
|
||||
@@ -433,7 +433,7 @@
|
||||
{:else if selected == 'dedicated'}
|
||||
{#if nconfig?.dedicated_worker != undefined}
|
||||
<input
|
||||
disabled={!$superadmin}
|
||||
disabled={!($superadmin || $devopsRole)}
|
||||
placeholder="<workspace>:<script path>"
|
||||
type="text"
|
||||
onchange={() => {
|
||||
@@ -442,7 +442,7 @@
|
||||
}}
|
||||
bind:value={nconfig.dedicated_worker}
|
||||
/>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<div class="py-2"
|
||||
><Alert
|
||||
type="info"
|
||||
@@ -471,11 +471,11 @@
|
||||
<div class="flex gap-1 items-center">
|
||||
<input
|
||||
type="text"
|
||||
disabled={!$superadmin}
|
||||
disabled={!($superadmin || $devopsRole)}
|
||||
placeholder="/path/to/python3.X/site-packages"
|
||||
bind:value={nconfig.additional_python_paths![i]}
|
||||
/>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<button
|
||||
class="rounded-full bg-surface/60 hover:bg-gray-200"
|
||||
aria-label="Clear"
|
||||
@@ -497,7 +497,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<div class="flex">
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -523,12 +523,12 @@
|
||||
{#each nconfig.pip_local_dependencies as _, i}
|
||||
<div class="flex gap-1 items-center">
|
||||
<input
|
||||
disabled={!$superadmin}
|
||||
disabled={!($superadmin || $devopsRole)}
|
||||
type="text"
|
||||
placeholder="httpx"
|
||||
bind:value={nconfig.pip_local_dependencies[i]}
|
||||
/>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<button
|
||||
class="rounded-full bg-surface/60 hover:bg-gray-200"
|
||||
aria-label="Clear"
|
||||
@@ -550,7 +550,7 @@
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<div class="flex">
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -584,7 +584,7 @@
|
||||
{#each customEnvVars as envvar, i}
|
||||
<div class="flex gap-1 items-center">
|
||||
<input
|
||||
disabled={!$superadmin}
|
||||
disabled={!($superadmin || $devopsRole)}
|
||||
type="text"
|
||||
placeholder="ENV_VAR_NAME"
|
||||
bind:value={envvar.key}
|
||||
@@ -593,7 +593,7 @@
|
||||
}}
|
||||
/>
|
||||
<ToggleButtonGroup
|
||||
disabled={!$superadmin}
|
||||
disabled={!($superadmin || $devopsRole)}
|
||||
class="w-128"
|
||||
bind:selected={envvar.type}
|
||||
on:selected={(e) => {
|
||||
@@ -610,13 +610,13 @@
|
||||
</ToggleButtonGroup>
|
||||
<input
|
||||
type="text"
|
||||
disabled={!$superadmin || envvar.type === 'dynamic'}
|
||||
disabled={!($superadmin || $devopsRole) || envvar.type === 'dynamic'}
|
||||
placeholder={envvar.type === 'dynamic'
|
||||
? 'value read from worker env var'
|
||||
: 'static value'}
|
||||
bind:value={envvar.value}
|
||||
/>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<button
|
||||
class="rounded-full bg-surface/60 hover:bg-gray-200"
|
||||
aria-label="Clear"
|
||||
@@ -639,7 +639,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<div class="flex">
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -657,7 +657,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if !superadmin}
|
||||
{#if !($superadmin || $devopsRole)}
|
||||
<div class="flex flex-wrap items-center gap-1 pt-2">
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -745,7 +745,7 @@
|
||||
>
|
||||
{/if}
|
||||
<Editor
|
||||
disabled={!$superadmin}
|
||||
disabled={!($superadmin || $devopsRole)}
|
||||
class="flex flex-1 grow h-full w-full"
|
||||
automaticLayout
|
||||
scriptLang={'bash'}
|
||||
@@ -824,7 +824,7 @@
|
||||
}}
|
||||
disabled={(!dirty && nconfig?.dedicated_worker == undefined) ||
|
||||
!$enterpriseLicense ||
|
||||
!$superadmin}
|
||||
!($superadmin || $devopsRole)}
|
||||
>
|
||||
Apply changes
|
||||
</Button>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import {
|
||||
enterpriseLicense,
|
||||
superadmin,
|
||||
devopsRole,
|
||||
userStore,
|
||||
workspaceStore,
|
||||
userWorkspaces
|
||||
@@ -131,7 +132,7 @@
|
||||
loadWorkers()
|
||||
loadWorkerGroups()
|
||||
loadCustomTags()
|
||||
$: $superadmin && loadDefaultTagsPerWorkspace()
|
||||
$: ($superadmin || $devopsRole) && loadDefaultTagsPerWorkspace()
|
||||
|
||||
onDestroy(() => {
|
||||
if (intervalId) {
|
||||
@@ -271,7 +272,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<QueueMetricsDrawer bind:this={queueMetricsDrawer} />
|
||||
{/if}
|
||||
|
||||
@@ -331,7 +332,7 @@
|
||||
tooltip="The workers are the dutiful servants that execute the jobs."
|
||||
documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
|
||||
>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<div class="flex flex-row-reverse w-full pb-2 items-center gap-4">
|
||||
<div>
|
||||
<AssignableTags
|
||||
@@ -392,7 +393,7 @@
|
||||
>
|
||||
<div></div>
|
||||
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<Button
|
||||
size="sm"
|
||||
@@ -577,7 +578,7 @@
|
||||
<Cell head>Last ping</Cell>
|
||||
<Cell head>Worker start</Cell>
|
||||
<Cell head>Jobs ran</Cell>
|
||||
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
|
||||
{#if (!config || config?.dedicated_worker == undefined) && ($superadmin || $devopsRole)}
|
||||
<Cell head>Last job</Cell>
|
||||
<Cell head>Occupancy rate<br />(15s/5m/30m/ever)</Cell>
|
||||
{/if}
|
||||
@@ -585,7 +586,7 @@
|
||||
<Cell head>Limits</Cell>
|
||||
<Cell head>Version</Cell>
|
||||
<Cell head>Liveness</Cell>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<Cell head>
|
||||
Live Shell
|
||||
<Tooltip>
|
||||
@@ -604,7 +605,7 @@
|
||||
<tr class="border-t">
|
||||
<Cell
|
||||
first
|
||||
colspan={(!config || config?.dedicated_worker == undefined) && $superadmin
|
||||
colspan={(!config || config?.dedicated_worker == undefined) && ($superadmin || $devopsRole)
|
||||
? 12
|
||||
: 9}
|
||||
scope="colgroup"
|
||||
@@ -655,7 +656,7 @@
|
||||
>
|
||||
<Cell>{displayDate(started_at)}</Cell>
|
||||
<Cell>{jobs_executed}</Cell>
|
||||
{#if (!config || config?.dedicated_worker == undefined) && $superadmin}
|
||||
{#if (!config || config?.dedicated_worker == undefined) && ($superadmin || $devopsRole)}
|
||||
<Cell>
|
||||
{#if last_job_id}
|
||||
<a href={`/run/${last_job_id}?workspace=${last_job_workspace_id}`}>
|
||||
@@ -715,7 +716,7 @@
|
||||
: 'Unknown'}
|
||||
</Badge>
|
||||
</Cell>
|
||||
{#if $superadmin}
|
||||
{#if $superadmin || $devopsRole}
|
||||
<Cell>
|
||||
<Button
|
||||
size="xs"
|
||||
|
||||
Reference in New Issue
Block a user