mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-23 08:03:33 +00:00
* feat: configurable workspace roles with per-workspace overrides Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: close role review gaps in pickers, declarative sync and removal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: keep resolve_role_name doc on its function Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: validate overrides on instance role changes and bill admins fully Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: allow pipeline drafts and keep role checks on one connection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs: list data pipelines in operator_can_draft Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: expect a 403 role refusal when an operator resolves jobs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: require a valid license for custom workspace roles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: freeze workspace roles when the license lapses Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin the ee companion for workspace roles Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor: broadcast role override changes from the write path, not a trigger Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: update ee-repo-ref to b5294d38db082bdd104d5358a47695390694c05d This commit updates the EE repository reference after PR #891 was merged in windmill-ee-private. Previous ee-repo-ref: 688e00c5982865b14098f530d54280e2311994c0 New ee-repo-ref: b5294d38db082bdd104d5358a47695390694c05d Automated by sync-ee-ref workflow. * feat: add workspace roles settings UI and role pickers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: show the server's reason when a settings save fails Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: default role pickers to a defined role and confirm resets of unknown capabilities Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: treat null role capabilities as unset and key role drafts by map Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: gate the frontend on workspace role capabilities Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: gate deploys, bulk actions and chat composition per role kind Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: treat a superadmin as full in deploy permission checks Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: leave page visibility out of isRestricted, as the server does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let a member discard their own draft without the authoring right Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: show custom roles as inactive without a valid license Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: keep the user store import on the agent page Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: show custom roles as frozen without a valid license Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: reorder instance roles with a drag handle Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: let an admin row that kept the operator flag hold full rights everywhere Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: treat superadmin members as admins and return role names from get user Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: move the instance role label into the expanded panel Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
230 lines
6.9 KiB
Svelte
230 lines
6.9 KiB
Svelte
<script lang="ts">
|
|
import type { Schema } from '$lib/common'
|
|
import { Button, Drawer, DrawerContent, Tab, Tabs } from '$lib/components/common'
|
|
import FlowScriptPicker from '$lib/components/flows/pickers/FlowScriptPicker.svelte'
|
|
import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte'
|
|
import Tooltip from '$lib/components/Tooltip.svelte'
|
|
import { inferArgs } from '$lib/infer'
|
|
import { initialCode } from '$lib/script_helpers'
|
|
import { emptySchema } from '$lib/utils'
|
|
import { defaultScriptLanguages, getScriptByPath, processInlineLangs } from '$lib/scripts'
|
|
|
|
import { Building, GitFork, Globe2 } from 'lucide-svelte'
|
|
import { createEventDispatcher } from 'svelte'
|
|
import { defaultCode } from '../component'
|
|
import WorkspaceScriptList from '../settingsPanel/mainInput/WorkspaceScriptList.svelte'
|
|
import RunnableSelector from '../settingsPanel/mainInput/RunnableSelector.svelte'
|
|
import { defaultScripts } from '$lib/stores'
|
|
import { useRoleCapabilities } from '$lib/operatorWriteRights'
|
|
import DefaultScripts from '$lib/components/DefaultScripts.svelte'
|
|
import type { Preview } from '$lib/gen'
|
|
import type { InlineScript } from '../../sharedTypes'
|
|
|
|
interface Props {
|
|
componentType?: string | undefined
|
|
showScriptPicker?: boolean
|
|
rawApps?: boolean
|
|
unusedInlineScripts: { name: string; inlineScript: InlineScript }[]
|
|
}
|
|
|
|
let {
|
|
componentType = undefined,
|
|
showScriptPicker = false,
|
|
rawApps = false,
|
|
unusedInlineScripts
|
|
}: Props = $props()
|
|
|
|
let tab = $state('workspacescripts')
|
|
let filter: string = $state('')
|
|
let picker: Drawer | undefined = $state(undefined)
|
|
|
|
const dispatch = createEventDispatcher()
|
|
const roleCaps = useRoleCapabilities()
|
|
const composeOnlyApps = $derived($roleCaps.apps === 'compose')
|
|
|
|
async function inferInlineScriptSchema(
|
|
language: Preview['language'],
|
|
content: string,
|
|
schema: Schema
|
|
): Promise<Schema> {
|
|
try {
|
|
await inferArgs(language, content, schema)
|
|
} catch (e) {
|
|
console.error("Couldn't infer args", e)
|
|
}
|
|
|
|
return schema
|
|
}
|
|
|
|
async function createInlineScriptByLanguage(
|
|
language: Preview['language'],
|
|
subkind: 'pgsql' | 'mysql' | 'fetch' | undefined = undefined
|
|
) {
|
|
const content =
|
|
defaultCode(componentType ?? '', (subkind || language) ?? '') ??
|
|
initialCode(language, 'script', subkind ?? 'flow')
|
|
|
|
return newInlineScript(content, language)
|
|
}
|
|
|
|
async function newInlineScript(content: string, language: Preview['language']) {
|
|
let schema: Schema = emptySchema()
|
|
|
|
schema = await inferInlineScriptSchema(language, content, schema)
|
|
const newInlineScript = {
|
|
content,
|
|
language,
|
|
schema
|
|
}
|
|
dispatch('new', newInlineScript)
|
|
}
|
|
|
|
async function pickScript(path: string) {
|
|
const script = await getScriptByPath(path)
|
|
newInlineScript(script.content, script.language)
|
|
}
|
|
|
|
async function pickHubScript(path: string) {
|
|
const script = await getScriptByPath(path)
|
|
newInlineScript(script.content, script.language)
|
|
}
|
|
|
|
let langs = $derived(
|
|
processInlineLangs(undefined, $defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
|
|
.map((l) => [defaultScriptLanguages[l], l])
|
|
.filter(
|
|
(x) =>
|
|
x[1] != 'docker' &&
|
|
($defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1]))
|
|
) as [string, Preview['language']][]
|
|
)
|
|
</script>
|
|
|
|
<Drawer bind:this={picker} size="1000px">
|
|
<DrawerContent title="Script/Flow Picker" on:close={picker.closeDrawer}>
|
|
<div>
|
|
<div class="max-w-6xl">
|
|
<Tabs bind:selected={tab}>
|
|
<Tab value="workspacescripts" label="Workspace Scripts" icon={Building} />
|
|
|
|
{#if !composeOnlyApps}
|
|
<Tab value="hubscripts" label="Hub Scripts" icon={Globe2} />
|
|
{/if}
|
|
</Tabs>
|
|
<div class="my-2"></div>
|
|
<div class="flex flex-col gap-y-16">
|
|
<div class="flex flex-col">
|
|
{#if tab == 'workspacescripts'}
|
|
<WorkspaceScriptList on:pick={(e) => pickScript(e.detail)} />
|
|
{:else if tab == 'hubscripts'}
|
|
<PickHubScript bind:filter on:pick={(e) => pickHubScript(e.detail.path)} />
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</DrawerContent>
|
|
</Drawer>
|
|
|
|
<div class="flex flex-col px-4 gap-2 text-sm" id="app-editor-empty-runnable">
|
|
<div class="mt-2 flex justify-between gap-4" id="app-editor-runnable-header">
|
|
<div class="font-bold items-baseline truncate">
|
|
{composeOnlyApps ? 'Choose a script or flow' : 'Choose a language'}
|
|
</div>
|
|
<div class="flex gap-2">
|
|
{#if showScriptPicker}
|
|
<RunnableSelector {unusedInlineScripts} {rawApps} on:pick hideCreateScript />
|
|
{/if}
|
|
{#if !composeOnlyApps}
|
|
<Button
|
|
on:click={() => picker?.openDrawer()}
|
|
size="xs"
|
|
variant="border"
|
|
color="light"
|
|
startIcon={{ icon: GitFork }}
|
|
btnClasses="truncate"
|
|
>
|
|
Fork other script
|
|
</Button>
|
|
{/if}
|
|
|
|
<Button
|
|
on:click={() => dispatch('delete')}
|
|
size="xs"
|
|
color="red"
|
|
variant="border"
|
|
btnClasses="truncate"
|
|
>
|
|
Clear
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Operators with builder rights compose runnables that already exist: the language pickers
|
|
below all author code, which the backend refuses from them. -->
|
|
<div class="flex flex-row w-full gap-8" class:hidden={composeOnlyApps}>
|
|
<div id="app-editor-backend-runnables">
|
|
<div class="mb-1 text-sm font-semibold flex gap-4">Backend <DefaultScripts /> </div>
|
|
|
|
<div class="flex flex-row flex-wrap gap-2">
|
|
{#each langs as [label, lang] (lang)}
|
|
<FlowScriptPicker
|
|
{label}
|
|
{lang}
|
|
on:click={() => {
|
|
createInlineScriptByLanguage(lang)
|
|
}}
|
|
id={`create-${lang}-script`}
|
|
/>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{#if !rawApps}
|
|
<div id="app-editor-frontend-runnables">
|
|
<div class="mb-1 text-sm font-semibold">
|
|
Frontend
|
|
<Tooltip
|
|
documentationLink="https://www.windmill.dev/docs/apps/app-runnable-panel#frontend-scripts"
|
|
>
|
|
Frontend scripts are executed in the browser and can manipulate the app context
|
|
directly.
|
|
</Tooltip>
|
|
</div>
|
|
|
|
<div>
|
|
<FlowScriptPicker
|
|
label={`JavaScript`}
|
|
lang="javascript"
|
|
on:click={() => {
|
|
const newInlineScript = {
|
|
content: `// read outputs and ctx
|
|
console.log(ctx.email)
|
|
|
|
// access a global state store
|
|
if (!state.foo) { state.foo = 0 }
|
|
state.foo += 1
|
|
|
|
// for reactivity to work, you need to assign a value and not modify it in place
|
|
// e.g: state.foo.push(1) will not work but 'state.foo = [...state.foo, 1]' will.
|
|
// you may also just reassign as next statement 'state.foo = state.foo'
|
|
|
|
// you can also navigate (goto), recompute a script (recompute), or set a tab (setTab)
|
|
// Inputs and display components support settings their value directly
|
|
// setValue('a', "Bar")
|
|
// Tables support setting their selected index (setSelectedIndex)
|
|
// all helpers can be found at https://www.windmill.dev/docs/apps/app-runnable-panel#frontend-scripts-helpers
|
|
|
|
return state.foo`,
|
|
language: 'frontend',
|
|
path: 'frontend script',
|
|
schema: undefined
|
|
}
|
|
dispatch('new', newInlineScript)
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|