mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-06 00:02:13 +00:00
feat(raw apps): pick the data table role when setting up an app
This commit is contained in:
@@ -647,12 +647,14 @@ export class AIChatManager {
|
||||
scriptEditorGetLintErrors = $state<(() => ScriptLintResult) | undefined>(undefined)
|
||||
flowAiChatHelpers = $state<FlowAIChatHelpers | undefined>(undefined)
|
||||
appAiChatHelpers = $state<AppAIChatHelpers | undefined>(undefined)
|
||||
/** Datatable creation policy: enabled flag, datatable name, and optional schema */
|
||||
/** Datatable creation policy: enabled flag, datatable name, optional schema,
|
||||
* and the role the app's queries run as. */
|
||||
datatableCreationPolicy = $state<{
|
||||
enabled: boolean
|
||||
datatable: string | undefined
|
||||
schema: string | undefined
|
||||
}>({ enabled: false, datatable: undefined, schema: undefined })
|
||||
role?: string | undefined
|
||||
}>({ enabled: false, datatable: undefined, schema: undefined, role: undefined })
|
||||
pendingNewCode = $state<string | undefined>(undefined)
|
||||
apiTools = $state<Tool<any>[]>([])
|
||||
aiChatInput = $state<AIChatInput | null>(null)
|
||||
|
||||
@@ -921,9 +921,14 @@ export function prepareAppSystemMessage(customPrompt?: string): ChatCompletionSy
|
||||
const policy = aiChatManager.datatableCreationPolicy
|
||||
const datatableName = policy.datatable ?? 'main'
|
||||
const schemaPrefix = policy.schema ? `${policy.schema}.` : ''
|
||||
// Use wmill.datatable() for 'main' (default), otherwise wmill.datatable('name')
|
||||
const datatableCall =
|
||||
datatableName === 'main' ? 'wmill.datatable()' : `wmill.datatable('${datatableName}')`
|
||||
// `wmill.datatable()` for 'main' without a role — the defaults — and the name
|
||||
// and role spelled out otherwise. A role names the privileges the app's own
|
||||
// queries run with, so it has to be in the code the model writes.
|
||||
const datatableCall = policy.role
|
||||
? `wmill.datatable('${datatableName}', '${policy.role}')`
|
||||
: datatableName === 'main'
|
||||
? 'wmill.datatable()'
|
||||
: `wmill.datatable('${datatableName}')`
|
||||
|
||||
let content = `You are a helpful assistant that creates and edits apps on the Windmill platform. Apps are defined as a collection of files that contains both the frontend and the backend.
|
||||
|
||||
|
||||
@@ -878,7 +878,8 @@
|
||||
aiChatManager.datatableCreationPolicy = {
|
||||
enabled: data.datatable !== undefined,
|
||||
datatable: data.datatable,
|
||||
schema: data.schema
|
||||
schema: data.schema,
|
||||
role: data.role
|
||||
}
|
||||
|
||||
// Start auto-snapshot
|
||||
@@ -900,9 +901,14 @@
|
||||
// Read the current policy from aiChatManager
|
||||
const policy = aiChatManager.datatableCreationPolicy
|
||||
// Only update if different to avoid infinite loops
|
||||
if (data.datatable !== policy.datatable || data.schema !== policy.schema) {
|
||||
if (
|
||||
data.datatable !== policy.datatable ||
|
||||
data.schema !== policy.schema ||
|
||||
data.role !== policy.role
|
||||
) {
|
||||
data.datatable = policy.datatable
|
||||
data.schema = policy.schema
|
||||
data.role = policy.role
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -22,7 +22,9 @@
|
||||
import { type DataTableRef, type RawAppData, formatDataTableRef } from './dataTableRefUtils'
|
||||
import {
|
||||
createDatatablesResource,
|
||||
createRolesResource,
|
||||
createSchemasResource,
|
||||
rolesWorthPicking,
|
||||
toDatatableItems,
|
||||
toSchemaItems
|
||||
} from './datatableUtils.svelte'
|
||||
@@ -72,6 +74,25 @@
|
||||
() => selectedDatatable,
|
||||
() => opWs
|
||||
)
|
||||
const roles = createRolesResource(
|
||||
() => selectedDatatable,
|
||||
() => opWs
|
||||
)
|
||||
|
||||
let selectedRole = $state<string | undefined>(undefined)
|
||||
const availableRoles = $derived(roles.current.roles)
|
||||
const showRolePicker = $derived(rolesWorthPicking(availableRoles))
|
||||
|
||||
// The picked role belongs to the data table it was picked on, and the one it
|
||||
// defaults to is what the app gets without saying anything.
|
||||
$effect(() => {
|
||||
const available = roles.current.roles
|
||||
if (selectedRole === undefined || !available.includes(selectedRole)) {
|
||||
selectedRole = available.includes(roles.current.defaultRole)
|
||||
? roles.current.defaultRole
|
||||
: available[0]
|
||||
}
|
||||
})
|
||||
|
||||
const availableDatatables = $derived(datatables.current)
|
||||
const availableSchemas = $derived(schemas.current)
|
||||
@@ -148,7 +169,8 @@
|
||||
input: {
|
||||
type: 'database',
|
||||
resourceType: 'postgresql',
|
||||
resourcePath: `datatable://${selectedDatatable}`
|
||||
resourcePath: `datatable://${selectedDatatable}`,
|
||||
role: showRolePicker ? selectedRole : undefined
|
||||
}
|
||||
})
|
||||
await dbOps.onCreateSchema({ schema: newSchemaName })
|
||||
@@ -164,9 +186,10 @@
|
||||
? {
|
||||
tables: formattedTables,
|
||||
datatable: selectedDatatable,
|
||||
schema: effectiveSchema
|
||||
schema: effectiveSchema,
|
||||
role: showRolePicker ? selectedRole : undefined
|
||||
}
|
||||
: { tables: formattedTables, datatable: undefined, schema: undefined }
|
||||
: { tables: formattedTables, datatable: undefined, schema: undefined, role: undefined }
|
||||
|
||||
const policy: Policy = {
|
||||
on_behalf_of: $userStore?.username.includes('@')
|
||||
@@ -269,6 +292,23 @@
|
||||
class="w-40"
|
||||
/>
|
||||
</div>
|
||||
{#if showRolePicker}
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="text-xs text-emphasis font-semibold" for="datatable-role"
|
||||
>Role</label
|
||||
>
|
||||
<Select
|
||||
id="datatable-role"
|
||||
disablePortal
|
||||
items={availableRoles.map((r) => ({ value: r, label: r }))}
|
||||
bind:value={selectedRole}
|
||||
clearable={false}
|
||||
placeholder="Role"
|
||||
size="sm"
|
||||
class="w-40"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<span class="text-xs text-emphasis font-semibold">Schema</span>
|
||||
<div class="flex flex-row gap-1 w-full items-center">
|
||||
|
||||
@@ -16,13 +16,17 @@ export interface RawAppData {
|
||||
datatable: string | undefined
|
||||
/** The schema for table creation (if specified) */
|
||||
schema: string | undefined
|
||||
/** The data table role the app's queries and table creation run as; absent
|
||||
* means the data table's default one. */
|
||||
role?: string
|
||||
}
|
||||
|
||||
/** Default data configuration */
|
||||
export const DEFAULT_DATA: RawAppData = {
|
||||
tables: [],
|
||||
datatable: undefined,
|
||||
schema: undefined
|
||||
schema: undefined,
|
||||
role: undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,6 +2,7 @@ import { resource } from 'runed'
|
||||
import { workspaceStore, dbSchemas } from '$lib/stores'
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import { getDbSchemas } from '$lib/components/apps/components/display/dbtable/metadata'
|
||||
import { ADMIN_DATATABLE_ROLE } from '$lib/components/dbTypes'
|
||||
import { get } from 'svelte/store'
|
||||
|
||||
/**
|
||||
@@ -59,6 +60,42 @@ export function createSchemasResource(
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a resource that loads the roles the caller may use on a datatable,
|
||||
* and the one it defaults to.
|
||||
*/
|
||||
export function createRolesResource(
|
||||
getDatatable: () => string | undefined,
|
||||
getWorkspace: () => string | undefined = () => get(workspaceStore)
|
||||
) {
|
||||
return resource(
|
||||
() => [getDatatable() ?? '', getWorkspace() ?? ''] as const,
|
||||
async ([datatableName, workspace]): Promise<{ roles: string[]; defaultRole: string }> => {
|
||||
if (!datatableName || !workspace) return { roles: [], defaultRole: ADMIN_DATATABLE_ROLE }
|
||||
try {
|
||||
const res = await WorkspaceService.listUsableDatatableRoles({ workspace, datatableName })
|
||||
return {
|
||||
roles: res.enabled ? res.roles : [],
|
||||
defaultRole: res.default_role
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to load datatable roles:', e)
|
||||
return { roles: [], defaultRole: ADMIN_DATATABLE_ROLE }
|
||||
}
|
||||
},
|
||||
{ initialValue: { roles: [], defaultRole: ADMIN_DATATABLE_ROLE } }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether naming a role says anything here: a data table without permissions has
|
||||
* none to pick, and one whose single role is the implicit `admin` has no choice
|
||||
* to offer.
|
||||
*/
|
||||
export function rolesWorthPicking(roles: string[]): boolean {
|
||||
return roles.length > 1 || (roles.length === 1 && roles[0] !== ADMIN_DATATABLE_ROLE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts datatables array to Select items format
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user