diff --git a/backend/.sqlx/query-722a3096f03d25ef94292d53801d41037de4bc69dd434232029c731cbbcbc22f.json b/backend/.sqlx/query-722a3096f03d25ef94292d53801d41037de4bc69dd434232029c731cbbcbc22f.json deleted file mode 100644 index 2e4dee2a30..0000000000 --- a/backend/.sqlx/query-722a3096f03d25ef94292d53801d41037de4bc69dd434232029c731cbbcbc22f.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "INSERT INTO windmill_migrations (name) VALUES ('bypassrls_1-2')", - "describe": { - "columns": [], - "parameters": { - "Left": [] - }, - "nullable": [] - }, - "hash": "722a3096f03d25ef94292d53801d41037de4bc69dd434232029c731cbbcbc22f" -} diff --git a/backend/.sqlx/query-eb1f916f9beea3eea83ce359f5305d0cfb0d6cdba9cc56c6139f57e46345f843.json b/backend/.sqlx/query-eb1f916f9beea3eea83ce359f5305d0cfb0d6cdba9cc56c6139f57e46345f843.json deleted file mode 100644 index ecd83ae2e3..0000000000 --- a/backend/.sqlx/query-eb1f916f9beea3eea83ce359f5305d0cfb0d6cdba9cc56c6139f57e46345f843.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'bypassrls_1-2')", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "exists", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [] - }, - "nullable": [ - null - ] - }, - "hash": "eb1f916f9beea3eea83ce359f5305d0cfb0d6cdba9cc56c6139f57e46345f843" -} diff --git a/backend/custom_migrations/bypassrls_1.sql b/backend/custom_migrations/bypassrls_1.sql deleted file mode 100644 index 1918b26b0b..0000000000 --- a/backend/custom_migrations/bypassrls_1.sql +++ /dev/null @@ -1,14 +0,0 @@ -CREATE POLICY admin_policy ON account TO windmill_admin USING (true); -CREATE POLICY admin_policy ON app TO windmill_admin USING (true); -CREATE POLICY admin_policy ON audit TO windmill_admin USING (true); -CREATE POLICY admin_policy ON capture TO windmill_admin USING (true); -CREATE POLICY admin_policy ON completed_job TO windmill_admin USING (true); -CREATE POLICY admin_policy ON flow TO windmill_admin USING (true); -CREATE POLICY admin_policy ON folder TO windmill_admin USING (true); -CREATE POLICY admin_policy ON queue TO windmill_admin USING (true); -CREATE POLICY admin_policy ON raw_app TO windmill_admin USING (true); -CREATE POLICY admin_policy ON resource TO windmill_admin USING (true); -CREATE POLICY admin_policy ON schedule TO windmill_admin USING (true); -CREATE POLICY admin_policy ON script TO windmill_admin USING (true); -CREATE POLICY admin_policy ON usr_to_group TO windmill_admin USING (true); -CREATE POLICY admin_policy ON variable TO windmill_admin USING (true); \ No newline at end of file diff --git a/backend/migrations/20241006144414_admin_policy.down.sql b/backend/migrations/20241006144414_admin_policy.down.sql new file mode 100644 index 0000000000..d2f607c5b8 --- /dev/null +++ b/backend/migrations/20241006144414_admin_policy.down.sql @@ -0,0 +1 @@ +-- Add down migration script here diff --git a/backend/migrations/20241006144414_admin_policy.up.sql b/backend/migrations/20241006144414_admin_policy.up.sql new file mode 100644 index 0000000000..e6a3f3807e --- /dev/null +++ b/backend/migrations/20241006144414_admin_policy.up.sql @@ -0,0 +1,24 @@ +-- Add up migration script here +DO +$$ +DECLARE + tbl_name text; + policy_exists boolean; + tbl_names text[] := ARRAY['account', 'app', 'audit', 'capture', 'completed_job', 'flow', 'folder', 'http_trigger', 'queue', 'raw_app', 'resource', 'schedule', 'script', 'usr_to_group', 'variable']; +BEGIN + FOR tbl_name IN SELECT unnest(tbl_names) + LOOP + SELECT EXISTS ( + SELECT 1 + FROM pg_policies + WHERE schemaname = 'public' + AND tablename = tbl_name + AND policyname = 'admin_policy' + ) INTO policy_exists; + + IF NOT policy_exists THEN + EXECUTE format('CREATE POLICY admin_policy ON %I TO windmill_admin USING (true);', tbl_name); + END IF; + END LOOP; +END; +$$; \ No newline at end of file diff --git a/backend/windmill-api/src/db.rs b/backend/windmill-api/src/db.rs index d6e6ed1a1d..c44b8c18e6 100644 --- a/backend/windmill-api/src/db.rs +++ b/backend/windmill-api/src/db.rs @@ -199,11 +199,6 @@ pub async fn migrate(db: &DB) -> Result<(), Error> { Err(err) => Err(err), }?; - #[cfg(feature = "enterprise")] - if let Err(e) = windmill_migrations(&mut custom_migrator, db).await { - tracing::error!("Could not apply windmill custom migrations: {e:#}") - } - Ok(()) } @@ -497,33 +492,6 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> { Ok(()) } -#[cfg(feature = "enterprise")] -async fn windmill_migrations(migrator: &mut CustomMigrator, db: &DB) -> Result<(), Error> { - if std::env::var("MIGRATION_NO_BYPASSRLS").is_ok() { - migrator.lock().await?; - let has_done_migration = sqlx::query_scalar!( - "SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = 'bypassrls_1-2')", - ) - .fetch_one(db) - .await? - .unwrap_or(false); - - if !has_done_migration { - let query = include_str!("../../custom_migrations/bypassrls_1.sql"); - tracing::info!("Applying bypassrls_1.sql"); - let mut tx: sqlx::Transaction<'_, Postgres> = db.begin().await?; - tx.execute(query).await?; - tracing::info!("Applied bypassrls_1.sql"); - sqlx::query!("INSERT INTO windmill_migrations (name) VALUES ('bypassrls_1-2')") - .execute(&mut *tx) - .await?; - tx.commit().await?; - } - migrator.unlock().await?; - } - Ok(()) -} - #[derive(Clone, Debug)] pub struct ApiAuthed { pub email: String, diff --git a/backend/windmill-worker/src/bash_executor.rs b/backend/windmill-worker/src/bash_executor.rs index 1b1fc65f17..128eb7d25f 100644 --- a/backend/windmill-worker/src/bash_executor.rs +++ b/backend/windmill-worker/src/bash_executor.rs @@ -318,15 +318,33 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", POWERSHELL_CACHE_DIR ); + // NOTE: powershell error handling / termination is quite tricky compared to bash + // here we're trying to catch terminating errors and propagate the exit code + // to the caller such that the job will be marked as failed. It's up to the user + // to catch specific errors in their script not caught by the below as there is no + // generic set -eu as in bash + let strict_termination_start = "$ErrorActionPreference = 'Stop'\n\ + Set-StrictMode -Version Latest\n\ + try {\n"; + + let strict_termination_end = "\n\ + } catch {\n\ + Write-Output \"An error occurred:\n\"\ + Write-Output $_ + exit 1\n\ + }\n"; + // make sure param() is first let param_match = windmill_parser_bash::RE_POWERSHELL_PARAM.find(&content); let content: String = if let Some(param_match) = param_match { let param_match = param_match.as_str(); format!( - "{}\n{}\n{}", + "{}\n{}\n{}\n{}\n{}", param_match, profile, - content.replace(param_match, "") + strict_termination_start, + content.replace(param_match, ""), + strict_termination_end ) } else { format!("{}\n{}", profile, content) @@ -351,7 +369,8 @@ $env:PSModulePath = \"{};$PSModulePathBackup\"", $pipe = New-TemporaryFile\n\ & \"{}\" -File ./main.ps1 @args 2>&1 | Tee-Object -FilePath $pipe\n\ Get-Content -Path $pipe | Select-Object -Last 1 | Set-Content -Path './result2.out'\n\ - Remove-Item $pipe\n", + Remove-Item $pipe\n\ + exit $LASTEXITCODE\n", POWERSHELL_PATH.as_str() ), )?; diff --git a/cli/instance.ts b/cli/instance.ts index 6ad60ad113..30459d9ef6 100644 --- a/cli/instance.ts +++ b/cli/instance.ts @@ -25,8 +25,8 @@ import { import { add as workspaceSetup, addWorkspace, - allWorkspaces, removeWorkspace, + setActiveWorkspace, } from "./workspace.ts"; import { pushInstanceSettings, @@ -35,8 +35,9 @@ import { pushInstanceConfigs, type SimplifiedSettings, } from "./settings.ts"; -import { sleep, deepEqual } from "./utils.ts"; +import { deepEqual } from "./utils.ts"; import { GlobalOptions } from "./types.ts"; +import { getActiveWorkspace } from "./workspace.ts"; export interface Instance { remote: string; @@ -293,22 +294,18 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { log.info("No instance-level changes to apply"); } - sleep(1000); - if (opts.includeWorkspaces) { log.info("\nPulling all workspaces"); + const rootDir = Deno.cwd(); + const localWorkspaces = await getLocalWorkspaces(rootDir, instance.prefix); + + const previousActiveWorkspace = await getActiveWorkspace(undefined); const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({ page: 1, perPage: 1000, }); - let localWorkspaces = await allWorkspaces(); - localWorkspaces = localWorkspaces.filter((w) => - w.name.startsWith(instance.prefix + "_") - ); - const rootDir = Deno.cwd(); for (const remoteWorkspace of remoteWorkspaces) { log.info("\nPulling workspace " + remoteWorkspace.id); - sleep(1000); const workspaceName = instance.prefix + "_" + remoteWorkspace.id; await Deno.mkdir(path.join(rootDir, workspaceName), { recursive: true, @@ -341,7 +338,7 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { } const localWorkspacesToDelete = localWorkspaces.filter( - (w) => !remoteWorkspaces.find((r) => r.id === w.workspaceId) + (w) => !remoteWorkspaces.find((r) => r.id === w.id) ); if (localWorkspacesToDelete.length > 0) { @@ -350,20 +347,23 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) { (await Confirm.prompt({ message: "Do you want to delete the local copy of workspaces that don't exist anymore on the instance?\n" + - localWorkspacesToDelete.map((w) => w.workspaceId).join(", "), + localWorkspacesToDelete.map((w) => w).join(", "), default: true, })); if (confirmDelete) { for (const workspace of localWorkspacesToDelete) { - await removeWorkspace(workspace.name, false, {}); - await Deno.remove(path.join(rootDir, workspace.name), { + await removeWorkspace(workspace.id, false, {}); + await Deno.remove(path.join(rootDir, workspace.dir), { recursive: true, }); } } } + if (previousActiveWorkspace) { + await setActiveWorkspace(previousActiveWorkspace?.name); + } log.info(colors.green.underline.bold("All workspaces pulled")); } } @@ -421,10 +421,10 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { log.info("No instance-level changes to apply"); } - sleep(1000); - if (opts.includeWorkspaces) { instances = await allInstances(); + const rootDir = Deno.cwd(); + const localPrefix = (await Select.prompt({ message: "What is the prefix of the local workspaces you want to sync?", options: [ @@ -440,18 +440,18 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { page: 1, perPage: 1000, }); - let localWorkspaces = await allWorkspaces(); - localWorkspaces = localWorkspaces.filter((w) => - w.name.startsWith(localPrefix + "_") - ); - log.info("\nPushing all workspaces"); - const rootDir = Deno.cwd(); + const previousActiveWorkspace = await getActiveWorkspace(undefined); + + const localWorkspaces = await getLocalWorkspaces(rootDir, localPrefix); + + log.info( + `\nPushing all workspaces: ${localWorkspaces.map((x) => x.id).join(", ")}` + ); for (const localWorkspace of localWorkspaces) { - log.info("\nPushing workspace " + localWorkspace.workspaceId); - sleep(1000); + log.info("\nPushing workspace " + localWorkspace.id); try { - await Deno.chdir(path.join(rootDir, localWorkspace.name)); + await Deno.chdir(path.join(rootDir, localWorkspace.dir)); } catch (_) { throw new Error( "Workspace folder not found, are you in the right directory?" @@ -471,8 +471,8 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { createWorkspaceName: workspaceSettings.name, createUsername: undefined, }, - localWorkspace.name, - localWorkspace.workspaceId, + localWorkspace.dir, + localWorkspace.id, instance.remote ); } catch (_) { @@ -482,7 +482,7 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { continue; } await push({ - workspace: localWorkspace.name, + workspace: localWorkspace.dir, token: undefined, baseUrl: undefined, includeGroups: true, @@ -495,7 +495,7 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { } const workspacesToDelete = remoteWorkspaces.filter( - (w) => !localWorkspaces.find((l) => l.workspaceId === w.id) + (w) => !localWorkspaces.find((l) => l.id === w.id) ); if (workspacesToDelete.length > 0) { const confirmDelete = @@ -514,10 +514,28 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) { } } } + if (previousActiveWorkspace) { + await setActiveWorkspace(previousActiveWorkspace?.name); + } log.info(colors.green.underline.bold("All workspaces pushed")); } } +async function getLocalWorkspaces(rootDir: string, localPrefix: string) { + const localWorkspaces: { dir: string; id: string }[] = []; + + for await (const dir of Deno.readDir(rootDir)) { + const dirName = dir.name; + if (dirName.startsWith(localPrefix + "_")) { + localWorkspaces.push({ + dir: dirName, + id: dirName.substring(localPrefix.length + 1), + }); + } + } + return localWorkspaces; +} + async function switchI(opts: {}, instanceName: string) { const all = await allInstances(); if (all.findIndex((x) => x.name === instanceName) === -1) { diff --git a/cli/workspace.ts b/cli/workspace.ts index face504183..4338e21cfe 100644 --- a/cli/workspace.ts +++ b/cli/workspace.ts @@ -34,10 +34,10 @@ export async function allWorkspaces(): Promise { } async function getActiveWorkspaceName( - opts: GlobalOptions + opts: GlobalOptions | undefined ): Promise { - if (opts.workspace) { - return opts.workspace; + if (opts?.workspace) { + return opts?.workspace; } try { return await Deno.readTextFile((await getRootStore()) + "/activeWorkspace"); @@ -47,7 +47,7 @@ async function getActiveWorkspaceName( } export async function getActiveWorkspace( - opts: GlobalOptions + opts: GlobalOptions | undefined ): Promise { const name = await getActiveWorkspaceName(opts); if (!name) { @@ -115,7 +115,12 @@ async function switchC(opts: GlobalOptions, workspaceName: string) { return; } - return await Deno.writeTextFile( + await setActiveWorkspace(workspaceName); + return; +} + +export async function setActiveWorkspace(workspaceName: string) { + await Deno.writeTextFile( (await getRootStore()) + "/activeWorkspace", workspaceName ); @@ -241,10 +246,8 @@ export async function add( }, opts ); - await Deno.writeTextFile( - (await getRootStore()) + "/activeWorkspace", - workspaceName - ); + await setActiveWorkspace(workspaceName); + log.info( colors.green.underline( `Added workspace ${workspaceName} for ${workspaceId} on ${remote}!` diff --git a/frontend/package.json b/frontend/package.json index 4fc20b556a..02a438827a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -300,6 +300,14 @@ "types": "./package/utils.d.ts", "default": "./package/utils.js" }, + "./icons/store": { + "types": "./package/icons/store.d.ts", + "default": "./package/icons/store.js" + }, + "./script_helpers": { + "types": "./package/script_helpers.d.ts", + "default": "./package/script_helpers.js" + }, "./infer": { "types": "./package/infer.d.ts", "default": "./package/infer.js" @@ -341,6 +349,11 @@ "types": "./package/components/DropdownV2.svelte.d.ts", "svelte": "./package/components/DropdownV2.svelte", "default": "./package/components/DropdownV2.svelte" + }, + "./components/flows/FlowHistoryInner.svelte": { + "types": "./package/components/flows/FlowHistoryInner.svelte.d.ts", + "svelte": "./package/components/flows/FlowHistoryInner.svelte", + "default": "./package/components/flows/FlowHistoryInner.svelte" } }, "files": [ @@ -426,6 +439,9 @@ "components/EditableSchemaWrapper.svelte": [ "./package/components/schema/EditableSchemaWrapper.svelte.d.ts" ], + "components/flows/FlowHistoryInner.svelte": [ + "./package/components/flows/FlowHistoryInner.svelte.d.ts" + ], "utils": [ "./package/utils.d.ts" ], @@ -461,6 +477,12 @@ ], "components/DropdownV2.svelte": [ "./package/components/DropdownV2.svelte.d.ts" + ], + "script_helpers": [ + "./package/script_helpers.d.ts" + ], + "icons/store": [ + "./package/icons/store.d.ts" ] } }, diff --git a/frontend/src/lib/assets/app.css b/frontend/src/lib/assets/app.css index 99bc17ae8e..6a386c559d 100644 --- a/frontend/src/lib/assets/app.css +++ b/frontend/src/lib/assets/app.css @@ -145,3 +145,23 @@ svelte-virtual-list-contents > * + * { rgba(255, 69, 58, 0.2) 20px ); } + +.bg-draggedover { + background-image: repeating-linear-gradient( + -45deg, + rgba(0, 0, 128, 0.2), + rgba(0, 0, 192, 0.2) 10px, + rgba(0, 0, 128, 0.2) 10px, + rgba(0, 0, 192, 0.2) 20px + ); +} + +.bg-draggedover-dark { + background-image: repeating-linear-gradient( + -45deg, + rgba(0, 0, 128, 0.6), + rgba(0, 0, 192, 0.6) 10px, + rgba(0, 0, 128, 0.6) 10px, + rgba(0, 0, 192, 0.6) 20px + ); +} diff --git a/frontend/src/routes/(root)/embed_connect/+page.svelte b/frontend/src/lib/components/AppConnectLightweightResourcePicker.svelte similarity index 77% rename from frontend/src/routes/(root)/embed_connect/+page.svelte rename to frontend/src/lib/components/AppConnectLightweightResourcePicker.svelte index 87ae396e55..4e59ea7858 100644 --- a/frontend/src/routes/(root)/embed_connect/+page.svelte +++ b/frontend/src/lib/components/AppConnectLightweightResourcePicker.svelte @@ -1,5 +1,4 @@ + +
    + {#if schema == undefined} +
  • No inputs
  • + {:else} + {#each Object.entries(schema.properties ?? {}) as [inp, v]} +
  • + {v?.default != undefined && v?.default != '' + ? 'default: ' + JSON.stringify(v?.default) + : ''} +
  • + {/each} + {/if} +
diff --git a/frontend/src/lib/components/FlowMetadata.svelte b/frontend/src/lib/components/FlowMetadata.svelte index 46e0a6fda9..3ab5d3d416 100644 --- a/frontend/src/lib/components/FlowMetadata.svelte +++ b/frontend/src/lib/components/FlowMetadata.svelte @@ -26,7 +26,7 @@ {#if job['success'] != undefined} Received job: {displayDate(job.created_at ?? '')} {:else} - Received job + Received job {/if} {job?.created_at} @@ -35,7 +35,7 @@
- Started + Started {job?.started_at}
diff --git a/frontend/src/lib/components/FlowViewer.svelte b/frontend/src/lib/components/FlowViewer.svelte index 66d37704bf..cc1350b75a 100644 --- a/frontend/src/lib/components/FlowViewer.svelte +++ b/frontend/src/lib/components/FlowViewer.svelte @@ -2,11 +2,11 @@ import type { FlowValue } from '$lib/gen' import { Tab, Tabs, TabContent } from './common' import SchemaViewer from './SchemaViewer.svelte' - import FieldHeader from './FieldHeader.svelte' import FlowGraphViewer from './FlowGraphViewer.svelte' import HighlightTheme from './HighlightTheme.svelte' import FlowViewerInner from './FlowViewerInner.svelte' + import FlowInputViewer from './FlowInputViewer.svelte' export let flow: { summary: string @@ -27,10 +27,6 @@ if (initialOpen) { open[initialOpen] = true } - - function toAny(x: unknown): any { - return x as any - } @@ -54,23 +50,7 @@ Flow Input

{#if flow.schema && flow.schema.properties && Object.keys(flow.schema.properties).length > 0 && flow.schema} -
    - {#each Object.entries(flow.schema.properties) as [inp, v]} -
  • - {toAny(v)?.default != undefined - ? 'default: ' + JSON.stringify(toAny(v)?.default) - : ''} -
  • - {/each} -
+ {:else}
No inputs
{/if} diff --git a/frontend/src/lib/components/LightweightResourcePicker.svelte b/frontend/src/lib/components/LightweightResourcePicker.svelte index c5b34f9af9..e0c250f32f 100644 --- a/frontend/src/lib/components/LightweightResourcePicker.svelte +++ b/frontend/src/lib/components/LightweightResourcePicker.svelte @@ -1,6 +1,5 @@ @@ -89,12 +71,15 @@ {#if expressOAuthSetup} {#if open} {#key refreshCount} -