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}
-
+ {#await import('./AppConnectLightweightResourcePicker.svelte')}
+
+ {:then Module}
+
+ {/await}
{/key}
{/if}
{:else}
@@ -105,12 +90,31 @@
tooltip="Resources represent connections to third party systems. Learn more on how to integrate external APIs."
documentationLink="https://www.windmill.dev/docs/integrations/integrations_on_windmill"
>
-
+ {:then Module}
+ {
+ sendUserToast(e.detail, true)
+ }}
+ on:refresh={(e) => {
+ value = e.detail
+ valueSelect = { value, label: value }
+ drawer?.closeDrawer?.()
+ open = false
+ }}
+ />
+ {/await}
+
+
{/if}
@@ -142,13 +146,11 @@