mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-23 00:00:33 +00:00
3d4f4c6c38
* export_datatable_schema * Propose to fork the datatable on ws fork * dump datatable * Dockerfile * Fix import_datatable_dump * datatable schema fork works! * Option to copy both schema and data * Datatable fork behavior * nit ui * use psql instead * remove fork_datatable route * feat: add fork_pg_database and export_pg_schema routes with DB Manager UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: pluralize "schema" to "schemas" in DB Manager export/import UI Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add import mode select (schema only vs schema + data) to DB Manager import Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Select schema or schema+data when important database * fix: prepend $res: prefix to resource paths in DB Manager import/export Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * style: dynamic import button label based on selected mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nits * feat: add warning alert when schema+data import mode is selected Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nit hide on cloud hosted * refactor: remove fork_behavior from datatable settings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * refactor: split CreateWorkspace into layout wrapper and CreateWorkspaceInner Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: instantiate CreateWorkspaceInner in globalForkModal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * nit icons * Data table fork UI * feat: pass per-datatable fork behaviors from UI to backend during workspace fork Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix fork overwriting all datatables * UI nits * custom instance db refactor * custom instance db wizard btn for all in dropdown * nit * Delete custom instance database button * Disable forking for resource datatables * Big import buttons when db empty * Revert "Disable forking for resource datatables" This reverts commit9561cc8fd4. * feat: add non_diffable flag to resource table Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add resource-type datatable fork with CREATE DATABASE Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: tag forked datatables with nonDiffable and forkedFrom Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: diff datatable and ducklake settings individually on workspace merge Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: skip non_diffable resources and datatables in workspace diff Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: default datatable fork behavior to keep_original Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: make grant permissions non-fatal in instance datatable fork Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: make datatable and ducklake diffs visible in workspace comparison Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: remove datatable fork logic from workspace fork route Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: correct ahead/behind logic for datatable and ducklake diffs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "fix: correct ahead/behind logic for datatable and ducklake diffs" This reverts commit6b50884dc6. * revert: remove datatable and ducklake settings diffing logic Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add datatable clone UI with step-by-step confirmation modal Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: extract datatable fork UI into ForkDatatableSection component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit * fix: run datatable cloning before workspace fork creation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit disable fork admins * nit fix switching workspace prematurely * fix: use source workspace for forkPgDatabase calls during fork Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: update forked workspace datatable settings after fork creation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add forked_from field to DataTable and set it for instance forks Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit onFinish * fix: add forked_from to DataTableSettings OpenAPI schema Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: track datatable table DDL changes in workspace_diff Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "feat: track datatable table DDL changes in workspace_diff" This reverts commit7526dd68b9. * feat: add get_datatable_full_schema endpoint and snapshot schema on fork Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix duplicate migration key * fix: set forked_from on datatable config for both instance and resource types Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nits * feat: drop forked databases on workspace deletion with confirmation UI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: extract drop_forked_datatable_databases from delete_workspace Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: cast pg char columns to text in FK schema query Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: show dbname instead of resource type in fork deletion modal Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ui nit * refactor: extract drop_custom_instance_database into windmill-common Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add datatable schema diff section to merge UI Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * UI * feat: add review drawer with YAML diff and SQL migration runner Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: use Monaco DiffEditor for YAML diff in review drawer Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit * Revert "refactor: use Monaco DiffEditor for YAML diff in review drawer" This reverts commita86008ba4c. * Revert "feat: add review drawer with YAML diff and SQL migration runner" This reverts commit0a0deb5ddb. * feat: add review drawer with DiffEditor and SQL migration runner Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * ui nits * fix: show diff between forked_from schema and changed side Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: re-fetch target live schema after migration for correct baseline Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * revert * nit auto next * feat: add confirmation modal before deploying migration to parent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: handle missing columns/foreignKeys in schema conversion Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nits * refactor: use temp file on disk for pg_dump instead of in-memory string Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Don't replace postgres dbname * fix: add validation to drop_custom_instance_database and use source db for CREATE/DROP Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: type DataTable.forked_from as DataTableForkedFrom struct Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: simplify fork_pg_database to take source + target_dbname Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * dead code * feat: enforce schema_and_data admin-only and extract create_custom_instance_database Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: rename fork_pg_database to import_pg_database with source/target/override params Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit * refactor: remove original_dbname/original_resource from forked_from, resolve from parent Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit * fix: resolve forked dbname from fork workspace when dropping resource databases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nits * fix: always clean up global_settings even if database doesn't exist Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: check datatable resource_type from config instead of URL prefix Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: wrap PG default value expressions in braces to prevent CAST quoting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "fix: wrap PG default value expressions in braces to prevent CAST quoting" This reverts commit77f5a2c4e8. * refactor: reuse columnDefToTableEditorValuesColumn for default value handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: store raw API schema in forked_from to avoid double transformation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "fix: store raw API schema in forked_from to avoid double transformation" This reverts commite326197a20. * Revert "refactor: reuse columnDefToTableEditorValuesColumn for default value handling" This reverts commitbd8f071d9f. * fix: validate dbname with strict regex to prevent SQL injection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix default value * always validate dbname * refactor: move get_datatable_full_schema structs and logic to query_builders.rs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: split import_pg_database into create_pg_database + import_pg_database Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: extract drop_forked_datatable_databases into its own route Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: require admin when using $res: resource paths in import_pg_database Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use UserDB for $res: resource access and restrict dbname creation - resolve_pg_source_checked uses UserDB (row-level security) for $res: paths - transform_json_unchecked is now pub(crate) to prevent misuse - Non-superadmins can only create databases with wm_fork_ prefix - datatable:// remains accessible to everyone Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: refuse to drop forked databases unless name starts with wm_fork_ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: remove resolve_pg_source, use resolve_pg_source_checked everywhere Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Fix permissions * sqlx prepare * compilation nits * sqlx prepare * sqlx prepare * wrong route syntax * fix: allow workspace owner to edit datatable config for fork setup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Revert "fix: allow workspace owner to edit datatable config for fork setup" This reverts commitab683e637b. * refactor: move datatable fork setup into create_workspace_fork backend Instead of updating datatable settings from the frontend after fork creation (which required admin/owner access), pass forked_datatables info to create_workspace_fork and handle it atomically in the same transaction. Removes applyPostForkDatatableUpdates from frontend. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: snapshot schema in backend during fork instead of frontend The schema snapshot is now taken by the backend in apply_forked_datatable via snapshot_datatable_schema, which connects to the parent workspace's datatable and runs pg_get_full_schema. This removes the need for the frontend to call getDatatableFullSchema and pass the schema through. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: use get_resource_value_interpolated_internal for $res: to resolve $var: references Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit * sqlx prepare * fix: add permission check to drop_forked_datatable_databases, validate dbnames, restrict temp file perms - drop_forked_datatable_databases: same permission as delete_workspace (fork owner or super admin) - validate_dbname on target_dbname_override and ForkedDatatableInfo.new_dbname - Enforce wm_fork_ prefix on forked datatable new_dbname - DumpFile: set /tmp/windmill/ to 0700 and create files with 0600 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * nit CLI * Rename to ws_specific * sqlx prepare * nit always validate dbname * fix: include foreign keys in CREATE TABLE migration for added tables Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: detect nextval defaults and use SERIAL/BIGSERIAL types in CREATE TABLE Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update frontend/src/lib/components/DBManagerDrawer.svelte Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update backend/windmill-common/src/lib.rs Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * Update backend/windmill-common/src/lib.rs Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix: sort foreign keys by constraint name for deterministic schema output Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * sqlx prepare * rename migration to update timestamp --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
217 lines
6.0 KiB
Svelte
217 lines
6.0 KiB
Svelte
<script lang="ts">
|
|
import { dbSchemas, workspaceStore, type DBSchema } from '$lib/stores'
|
|
import { sendUserToast, sortArray } from '$lib/utils'
|
|
import { Loader2 } from 'lucide-svelte'
|
|
import { dbSupportsSchemas } from './apps/components/display/dbtable/utils'
|
|
import DbManager from './DBManager.svelte'
|
|
import {
|
|
dbSchemaOpsWithPreviewScripts,
|
|
dbTableOpsWithPreviewScripts,
|
|
getDbType,
|
|
getDucklakeSchema
|
|
} from './dbOps'
|
|
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
|
import SqlRepl from './SqlRepl.svelte'
|
|
import SimpleAgTable from './SimpleAgTable.svelte'
|
|
import { type Snippet } from 'svelte'
|
|
import type { DbInput } from './dbTypes'
|
|
import { getDbSchemas, loadAllTablesMetaData } from './apps/components/display/dbtable/metadata'
|
|
|
|
import type { SelectedTable } from './DBManager.svelte'
|
|
import { getDbFeatures } from './apps/components/display/dbtable/dbFeatures'
|
|
import { resource } from 'runed'
|
|
|
|
interface Props {
|
|
input?: DbInput
|
|
showRepl?: boolean
|
|
hasReplResult?: boolean
|
|
selectedSchemaKey?: string | undefined
|
|
selectedTableKey?: string | undefined
|
|
dbSelector?: Snippet<[]>
|
|
/** Enable multi-select mode with checkboxes in sidebar */
|
|
multiSelectMode?: boolean
|
|
/** Selected tables in multi-select mode */
|
|
selectedTables?: SelectedTable[]
|
|
/** Tables that are already added and should show as disabled */
|
|
disabledTables?: SelectedTable[]
|
|
onImport?: (mode: 'schema_and_data' | 'schema_only') => void
|
|
}
|
|
|
|
let {
|
|
input,
|
|
showRepl = true,
|
|
hasReplResult = $bindable(false),
|
|
selectedSchemaKey = $bindable(undefined),
|
|
selectedTableKey = $bindable(undefined),
|
|
dbSelector,
|
|
multiSelectMode = false,
|
|
selectedTables = $bindable([]),
|
|
disabledTables = [],
|
|
onImport
|
|
}: Props = $props()
|
|
|
|
let dbSchema: DBSchema | undefined = $derived(input && $dbSchemas[getDbSchemasPath(input)])
|
|
|
|
function getDbSchemasPath(input: DbInput): string {
|
|
switch (input.type) {
|
|
case 'database':
|
|
return input.resourcePath
|
|
case 'ducklake':
|
|
return 'ducklake://' + input.ducklake
|
|
}
|
|
}
|
|
|
|
let colDefs = resource(
|
|
() => [input],
|
|
async () => {
|
|
if (!input) return
|
|
return await loadAllTablesMetaData($workspaceStore, input)
|
|
}
|
|
)
|
|
let dbSchemasPromise = resource(
|
|
() => [input],
|
|
async () => {
|
|
if (!input) return
|
|
const dbSchemasPath = getDbSchemasPath(input)
|
|
if (input.type == 'database') {
|
|
$dbSchemas[dbSchemasPath] = await getDbSchemas(
|
|
input.resourceType,
|
|
input.resourcePath,
|
|
$workspaceStore,
|
|
(message: string) => sendUserToast(message, true)
|
|
)
|
|
} else if (input.type == 'ducklake') {
|
|
$dbSchemas[dbSchemasPath] = await getDucklakeSchema({
|
|
workspace: $workspaceStore!,
|
|
ducklake: input.ducklake
|
|
})
|
|
}
|
|
}
|
|
)
|
|
export const refresh = () => {
|
|
colDefs.refetch()
|
|
dbSchemasPromise.refetch()
|
|
}
|
|
export function isLoading() {
|
|
return colDefs.loading || dbSchemasPromise.loading
|
|
}
|
|
|
|
let replPanelSize = $state(36)
|
|
const REPL_MIN_SIZE = 1.5
|
|
|
|
let replResultData: undefined | Record<string, any>[] = $state(undefined)
|
|
|
|
// Sync replResultData state with bindable prop
|
|
$effect(() => {
|
|
hasReplResult = !!replResultData
|
|
})
|
|
|
|
// Export for parent components
|
|
export function clearReplResult() {
|
|
replResultData = undefined
|
|
}
|
|
let _dbManager: DbManager | undefined = $state()
|
|
export const dbManager = () => _dbManager
|
|
</script>
|
|
|
|
<svelte:window
|
|
onkeydown={(e) => {
|
|
if (e.key === 'Escape') {
|
|
if (replResultData) {
|
|
replResultData = undefined
|
|
}
|
|
}
|
|
}}
|
|
/>
|
|
|
|
{#if dbSchema && $workspaceStore && input}
|
|
{@const _input = input}
|
|
{@const dbType = getDbType(_input)}
|
|
<Splitpanes horizontal>
|
|
<Pane class="relative">
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
class={'absolute inset-0 z-10 p-8 ' +
|
|
(replResultData
|
|
? 'bg-surface/90'
|
|
: 'transition-colors bg-transparent pointer-events-none select-none')}
|
|
onclick={(e) => {
|
|
// Only proceed if the click is directly on this div and not on the child elements
|
|
if (e.target === e.currentTarget) {
|
|
replResultData = undefined
|
|
}
|
|
}}
|
|
>
|
|
{#if replResultData}
|
|
{#key replResultData}
|
|
<SimpleAgTable data={replResultData} class="animate-zoom-in" />
|
|
{/key}
|
|
{/if}
|
|
</div>
|
|
<DbManager
|
|
dbSupportsSchemas={input?.type == 'database' && dbSupportsSchemas(input.resourceType)}
|
|
databaseIsEmpty={!Object.values(dbSchema.schema).flatMap((s) => Object.values(s)).length}
|
|
{dbSchema}
|
|
colDefs={colDefs.current}
|
|
dbTableOpsFactory={({ colDefs, tableKey }) =>
|
|
dbTableOpsWithPreviewScripts({
|
|
colDefs,
|
|
tableKey,
|
|
input: _input,
|
|
workspace: $workspaceStore
|
|
})}
|
|
dbSchemaOps={dbSchemaOpsWithPreviewScripts({
|
|
input: _input,
|
|
workspace: $workspaceStore
|
|
})}
|
|
initialTableKey={input.specificTable}
|
|
initialSchemaKey={input.type == 'database' ? input.specificSchema : undefined}
|
|
asset={_input.type == 'ducklake'
|
|
? { kind: 'ducklake', path: _input.ducklake }
|
|
: _input.resourcePath.startsWith('datatable://')
|
|
? { kind: 'datatable', path: _input.resourcePath.substring('datatable://'.length) }
|
|
: undefined}
|
|
{dbType}
|
|
refresh={() => refresh()}
|
|
{dbSelector}
|
|
{onImport}
|
|
bind:selectedSchemaKey
|
|
bind:selectedTableKey
|
|
{multiSelectMode}
|
|
bind:selectedTables
|
|
bind:this={_dbManager}
|
|
{disabledTables}
|
|
features={getDbFeatures(input)}
|
|
/>
|
|
</Pane>
|
|
{#if showRepl}
|
|
<Pane bind:size={replPanelSize} minSize={REPL_MIN_SIZE} class="relative">
|
|
<SqlRepl
|
|
{input}
|
|
onData={(data) => {
|
|
replResultData = data
|
|
}}
|
|
placeholderTableName={sortArray(
|
|
Object.keys(
|
|
dbSchema?.schema[
|
|
'public' in dbSchema?.schema
|
|
? 'public'
|
|
: 'dbo' in dbSchema?.schema
|
|
? 'dbo'
|
|
: Object.keys(dbSchema?.schema ?? {})?.[0]
|
|
] ?? {}
|
|
)
|
|
)?.[0]}
|
|
/>
|
|
</Pane>
|
|
{/if}
|
|
</Splitpanes>
|
|
{:else}
|
|
<Splitpanes>
|
|
<Pane class="relative flex justify-center items-center">
|
|
<Loader2 class="animate-spin" size={32} />
|
|
</Pane>
|
|
</Splitpanes>
|
|
{/if}
|