From 6edf9b9946d613b599cb91688c4986044caaba8d Mon Sep 17 00:00:00 2001
From: Ruben Fiszel
Date: Sat, 7 Jan 2023 09:39:50 +0100
Subject: [PATCH] feat: add archive/unarchive/delete workspace
---
...30107075643_use_internal_url_sync.down.sql | 1 +
...0230107075643_use_internal_url_sync.up.sql | 16 ++
backend/sqlx-data.json | 192 ++++++++++++++++++
backend/windmill-api/openapi.yaml | 36 +++-
backend/windmill-api/src/workspaces.rs | 124 ++++++++++-
cli/workspace.ts | 2 +-
.../user/(user)/workspaces/+page.svelte | 17 ++
.../(logged)/workspace_settings/+page.svelte | 50 +++--
8 files changed, 414 insertions(+), 24 deletions(-)
create mode 100644 backend/migrations/20230107075643_use_internal_url_sync.down.sql
create mode 100644 backend/migrations/20230107075643_use_internal_url_sync.up.sql
diff --git a/backend/migrations/20230107075643_use_internal_url_sync.down.sql b/backend/migrations/20230107075643_use_internal_url_sync.down.sql
new file mode 100644
index 0000000000..d2f607c5b8
--- /dev/null
+++ b/backend/migrations/20230107075643_use_internal_url_sync.down.sql
@@ -0,0 +1 @@
+-- Add down migration script here
diff --git a/backend/migrations/20230107075643_use_internal_url_sync.up.sql b/backend/migrations/20230107075643_use_internal_url_sync.up.sql
new file mode 100644
index 0000000000..76132f55cf
--- /dev/null
+++ b/backend/migrations/20230107075643_use_internal_url_sync.up.sql
@@ -0,0 +1,16 @@
+-- Add up migration script here
+-- Add up migration script here
+UPDATE script SET content = 'import wmill from "https://deno.land/x/wmill@v1.57.0/main.ts";
+export async function main() {
+ await run(
+ "workspace", "add", "__automation", "admins", Deno.env.get("BASE_INTERNAL_URL") + "/", "--token", Deno.env.get("WM_TOKEN"));
+
+ await run("hub", "pull");
+}
+
+async function run(...cmd: string[]) {
+ console.log("Running \"" + cmd.join('' '') + "\"");
+ await wmill.parse(cmd);
+}', summary = 'Synchronize Hub Resource types with admins workspace',
+description = 'Basic administrative script to sync latest resource types from hub to share to every workspace. Recommended to run at least once. On a schedule by default.'
+WHERE hash = -28028598712388162 AND workspace_id = 'admins';
\ No newline at end of file
diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json
index dd54e84820..793e407a4e 100644
--- a/backend/sqlx-data.json
+++ b/backend/sqlx-data.json
@@ -163,6 +163,18 @@
},
"query": "SELECT EXISTS(SELECT 1 FROM resource WHERE path = $1 AND workspace_id = $2)"
},
+ "07b984faa14193f529ae589608289a9ffb0f3d4a0e48054393e1bb8b70b4e5e0": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM flow WHERE workspace_id = $1"
+ },
"097a576938eac385ddc2f16a00ddc69c3ca54f5a66923291730980eeeea1f8c1": {
"describe": {
"columns": [],
@@ -304,6 +316,18 @@
},
"query": "SELECT EXISTS (SELECT 1 FROM schedule WHERE path = $1 AND workspace_id = $2 AND path != script_path)"
},
+ "0dc502078eeb874262568c44078d492ac6c22b97dbf2ab0a1fd00eb3156f1745": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM script WHERE workspace_id = $1"
+ },
"0dd3fe3ddf9cb72760687d2ee0950afdcce2d54721bfe8dba008b15e4b581956": {
"describe": {
"columns": [],
@@ -317,6 +341,18 @@
},
"query": "DELETE FROM account WHERE id = $1 AND workspace_id = $2"
},
+ "0e21323c27b282ab79da7bb3418e0730ada0f3ad9b4bc38941c8ccc39685a53d": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM workspace_settings WHERE workspace_id = $1"
+ },
"0f6af40e79a3f44ce84bd127a8af9216c82e71acf8bf4df12b078020c90d7d9d": {
"describe": {
"columns": [
@@ -760,6 +796,18 @@
},
"query": "INSERT INTO queue\n (workspace_id, id, running, parent_job, created_by, permissioned_as, scheduled_for, \n script_hash, script_path, raw_code, raw_lock, args, job_kind, schedule_path, raw_flow, flow_status, is_flow_step, language, started_at, same_worker, pre_run_error, email, visible_to_owner)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, CASE WHEN $3 THEN now() END, $19, $20, $21, $22) RETURNING id"
},
+ "1eaf8d677d520c7f2f303a731de6b6d939918e41ad0d1c748d80db3fd33cb9d3": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM app WHERE workspace_id = $1"
+ },
"1f040850c2a82bc09789226b167c43fd4935cfbb4951760a4d527665b70a5ac7": {
"describe": {
"columns": [
@@ -822,6 +870,18 @@
},
"query": "UPDATE folder SET owners = array_remove(owners, $1) WHERE name = $2 AND workspace_id = $3 RETURNING name"
},
+ "20597f46f2793b30f926bbd18e41f4860b7a7839879b932916ea20f2d98f43f9": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM resource WHERE workspace_id = $1"
+ },
"20e8a8343adc4a3a28a1c4908a13047cddafc4480cf028d3455fd17347cace73": {
"describe": {
"columns": [
@@ -1284,6 +1344,30 @@
},
"query": "SELECT workspace_id, name, display_name, owners, extra_perms FROM folder WHERE name = $1 AND workspace_id = $2"
},
+ "39f1b99319f48acb95fd4247f70baaf7a38062aa8b4f70fac10215edbbd2d41c": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM schedule WHERE workspace_id = $1"
+ },
+ "3cd8dd59567bcd1d2b6babfbb995be97cd20a0f299a7fd8fa163dc3af388603d": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM folder WHERE workspace_id = $1"
+ },
"3d363466d79075df3f74f946eff43ca89faefca3bcdf2c533425ca3868b0369a": {
"describe": {
"columns": [
@@ -2204,6 +2288,18 @@
},
"query": "INSERT INTO favorite (workspace_id, usr, path, favorite_kind) VALUES ($1, $2, $3, $4) ON CONFLICT DO NOTHING"
},
+ "67411b46ce51caf84c049dbb52d0e51c9889e73c99ff16ba12a02782bbaa53c4": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "UPDATE workspace SET deleted = false WHERE id = $1"
+ },
"6c63bbcb45d3f51eccaea52ec862700e1f1c2426d823abd951e1eea4fd9b85aa": {
"describe": {
"columns": [],
@@ -2375,6 +2471,18 @@
},
"query": "UPDATE flow SET path = $1, summary = $2, description = $3, value = $4, edited_by = $5, edited_at = now(), schema = $6::text::json, dependency_job = NULL WHERE path = $7 AND workspace_id = $8"
},
+ "73d3ed17fd0723ba75722f394904f6ee306b59aaf8ecfcf56484d38541343f06": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM group_ WHERE workspace_id = $1"
+ },
"765c18d77412cbb4474f4074d583b9b44681f3b9f58754662ac07a3a3470a3c5": {
"describe": {
"columns": [
@@ -2810,6 +2918,18 @@
},
"query": "SELECT * from resource_type WHERE (workspace_id = $1 OR workspace_id = 'starter' OR workspace_id = 'admins') ORDER BY name"
},
+ "8974baa530cf20156d9931123f0e046dd626e7763b54c4d0089a33bb417906f8": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM usr_to_group WHERE workspace_id = $1"
+ },
"8a80333c2fbf7b50fed305882de6e4ffda985d5c648cd617add6c9e6a9c03f34": {
"describe": {
"columns": [],
@@ -3632,6 +3752,18 @@
},
"query": "SELECT EXISTS (SELECT 1 FROM queue WHERE workspace_id = $1 AND schedule_path = $2 AND scheduled_for = $3)"
},
+ "a2b78a33c8122a4657e95d0da73e1de64c096a41ce7b993b267284fae5ea53fb": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM workspace_key WHERE workspace_id = $1"
+ },
"a34066d4a1578a13b2e322e6936ae80a0239a79148f3edce65f51a93910a1a4b": {
"describe": {
"columns": [
@@ -3742,6 +3874,18 @@
},
"query": "\n SELECT usage.usage FROM usage \n WHERE is_workspace = false \n AND month_ = EXTRACT(YEAR FROM current_date) * 12 + EXTRACT(MONTH FROM current_date)\n AND id = $1"
},
+ "a8a9a0197c51ec1614a7a5f2c24e621eadeca9a3500e9b8ab34b3b2d5037ca3c": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM variable WHERE workspace_id = $1"
+ },
"a98b2d68f023f46ab91167d3147416df672c2aed2ba5ab70e98a9da5fa47255a": {
"describe": {
"columns": [],
@@ -4530,6 +4674,18 @@
},
"query": "SELECT username from usr WHERE workspace_id = $1"
},
+ "c6f5c08cb7c641b398e77424fb79c26d4218aea34ff9fe05beac685504333053": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM usr WHERE workspace_id = $1"
+ },
"c732dcb4df5877560a3e75e0032179140cbe442f0b80ce1172d80903dd8a14fa": {
"describe": {
"columns": [],
@@ -4739,6 +4895,18 @@
},
"query": "SELECT app.id, app.path, app.summary, app.versions, app.policy,\n app.extra_perms, app_version.value, \n app_version.created_at, app_version.created_by from app, app_version \n WHERE app.path = $1 AND app.workspace_id = $2 AND app_version.id = app.versions[array_upper(app.versions, 1)]"
},
+ "d0bcb407ce7442511add6572b100783fb6d517a23bb9a32febf38b8c47bda656": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM workspace WHERE id = $1"
+ },
"d12d151995da2e8357307dd99e3f7b59192832a566d56b56097d15f713430705": {
"describe": {
"columns": [
@@ -5156,6 +5324,18 @@
},
"query": "SELECT hash FROM script WHERE parent_hashes[1] = $1 AND workspace_id = $2"
},
+ "e533518bd272a0643529f101b1d1dcbd6287494cc87b90487d917bc56bfcf1c1": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM workspace_invite WHERE workspace_id = $1"
+ },
"e587299612077e6de39b5a501aa8a736cae4a21f11853f8ae66a610b4e65f49e": {
"describe": {
"columns": [
@@ -5299,6 +5479,18 @@
},
"query": "INSERT INTO schedule (workspace_id, path, schedule, offset_, edited_by, script_path, is_flow, args, enabled, email) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *"
},
+ "e6f85cdbe681ace495fde31e67339dc58460b6914440473fe94de7a7bc292af4": {
+ "describe": {
+ "columns": [],
+ "nullable": [],
+ "parameters": {
+ "Left": [
+ "Text"
+ ]
+ }
+ },
+ "query": "DELETE FROM completed_job WHERE workspace_id = $1"
+ },
"e838b15953b567af2a2da6a4e3a04add5ec2cacabd93171a97c5fc978a64f8aa": {
"describe": {
"columns": [],
diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml
index 95ed3ea7ff..db9b385630 100644
--- a/backend/windmill-api/openapi.yaml
+++ b/backend/windmill-api/openapi.yaml
@@ -704,9 +704,41 @@ paths:
schema:
type: string
- /w/{workspace}/workspaces/delete:
+ /w/{workspace}/workspaces/archive:
+ post:
+ summary: archive workspace
+ operationId: archiveWorkspace
+ tags:
+ - workspace
+ parameters:
+ - $ref: "#/components/parameters/WorkspaceId"
+ responses:
+ "200":
+ description: status
+ content:
+ text/plain:
+ schema:
+ type: string
+
+ /workspaces/unarchive/{workspace}:
+ post:
+ summary: unarchive workspace
+ operationId: unarchiveWorkspace
+ tags:
+ - workspace
+ parameters:
+ - $ref: "#/components/parameters/WorkspaceId"
+ responses:
+ "200":
+ description: status
+ content:
+ text/plain:
+ schema:
+ type: string
+
+ /workspaces/delete/{workspace}:
delete:
- summary: delete workspace
+ summary: delete workspace (require super admin)
operationId: deleteWorkspace
tags:
- workspace
diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs
index 65c704f3c5..e0008cd756 100644
--- a/backend/windmill-api/src/workspaces.rs
+++ b/backend/windmill-api/src/workspaces.rs
@@ -41,7 +41,7 @@ pub fn workspaced_service() -> Router {
Router::new()
.route("/list_pending_invites", get(list_pending_invites))
.route("/update", post(edit_workspace))
- .route("/delete", delete(delete_workspace))
+ .route("/archive", post(archive_workspace))
.route("/invite_user", post(invite_user))
.route("/delete_invite", post(delete_invite))
.route("/get_settings", get(get_settings))
@@ -50,7 +50,6 @@ pub fn workspaced_service() -> Router {
.route("/tarball", get(tarball_workspace))
.route("/premium_info", get(premium_info))
}
-
pub fn global_service() -> Router {
Router::new()
.route("/list_as_superadmin", get(list_workspaces_as_super_admin))
@@ -60,6 +59,8 @@ pub fn global_service() -> Router {
.route("/exists", post(exists_workspace))
.route("/exists_username", post(exists_username))
.route("/allowed_domain_auto_invite", get(is_allowed_auto_domain))
+ .route("/unarchive/:workspace", post(unarchive_workspace))
+ .route("/delete/:workspace", delete(delete_workspace))
}
#[derive(FromRow, Serialize)]
@@ -538,10 +539,62 @@ async fn edit_workspace(
Ok(format!("Updated workspace {}", &w_id))
}
-async fn delete_workspace(
+async fn archive_workspace(
Extension(db): Extension,
Path(w_id): Path,
Authed { is_admin, username, email, .. }: Authed,
+) -> Result {
+ require_admin(is_admin, &username)?;
+ let mut tx = db.begin().await?;
+ sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ audit_log(
+ &mut tx,
+ &username,
+ "workspaces.archive",
+ ActionKind::Update,
+ &w_id,
+ Some(&email),
+ None,
+ )
+ .await?;
+ tx.commit().await?;
+
+ Ok(format!("Archived workspace {}", &w_id))
+}
+
+async fn unarchive_workspace(
+ Extension(db): Extension,
+ Path(w_id): Path,
+ Authed { is_admin, username, email, .. }: Authed,
+) -> Result {
+ require_admin(is_admin, &username)?;
+ let mut tx = db.begin().await?;
+ sqlx::query!("UPDATE workspace SET deleted = false WHERE id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ audit_log(
+ &mut tx,
+ &username,
+ "workspaces.unarchive",
+ ActionKind::Update,
+ &w_id,
+ Some(&email),
+ None,
+ )
+ .await?;
+ tx.commit().await?;
+
+ Ok(format!("Unarchived workspace {}", &w_id))
+}
+
+async fn delete_workspace(
+ Extension(db): Extension,
+ Path(w_id): Path,
+ Authed { username, email, .. }: Authed,
) -> Result {
let w_id = match w_id.as_str() {
"starter" => Err(Error::BadRequest(
@@ -552,9 +605,68 @@ async fn delete_workspace(
)),
_ => Ok(w_id),
}?;
- require_admin(is_admin, &username)?;
let mut tx = db.begin().await?;
- sqlx::query!("UPDATE workspace SET deleted = true WHERE id = $1", &w_id)
+ require_super_admin(&mut tx, &email).await?;
+
+ sqlx::query!("DELETE FROM script WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+ sqlx::query!("DELETE FROM flow WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+ sqlx::query!("DELETE FROM app WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+ sqlx::query!("DELETE FROM variable WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+ sqlx::query!("DELETE FROM resource WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM schedule WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM completed_job WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM usr WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!(
+ "DELETE FROM workspace_invite WHERE workspace_id = $1",
+ &w_id
+ )
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM usr_to_group WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM group_ WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM folder WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM workspace_key WHERE workspace_id = $1", &w_id)
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!(
+ "DELETE FROM workspace_settings WHERE workspace_id = $1",
+ &w_id
+ )
+ .execute(&mut tx)
+ .await?;
+
+ sqlx::query!("DELETE FROM workspace WHERE id = $1", &w_id)
.execute(&mut tx)
.await?;
@@ -562,7 +674,7 @@ async fn delete_workspace(
&mut tx,
&username,
"workspaces.delete",
- ActionKind::Update,
+ ActionKind::Delete,
&w_id,
Some(&email),
None,
diff --git a/cli/workspace.ts b/cli/workspace.ts
index f4f26aaa42..73cf168a97 100644
--- a/cli/workspace.ts
+++ b/cli/workspace.ts
@@ -192,7 +192,7 @@ export async function add(
}
if (opts.create) {
- setClient(token, remote.substring(0, remote.length - 1));
+ setClient(token, remote.endsWith('/') ? remote.substring(0, remote.length - 1) : remote);
if (
!await WorkspaceService.existsWorkspace({
diff --git a/frontend/src/routes/(root)/(logged)/user/(user)/workspaces/+page.svelte b/frontend/src/routes/(root)/(logged)/user/(user)/workspaces/+page.svelte
index 1dba750990..379236be06 100644
--- a/frontend/src/routes/(root)/(logged)/user/(user)/workspaces/+page.svelte
+++ b/frontend/src/routes/(root)/(logged)/user/(user)/workspaces/+page.svelte
@@ -110,7 +110,24 @@
}}
>{workspace.id} - {workspace.name} as
{workspace.username}
+ {#if workspace['deleted']}
+ (archived)
+ {/if}
+ {#if $superadmin && workspace['deleted']}
+
+ {/if}
{/each}
{:else}
diff --git a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
index 1ad206196f..c58f4cf6d2 100644
--- a/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
+++ b/frontend/src/routes/(root)/(logged)/workspace_settings/+page.svelte
@@ -444,21 +444,41 @@
more information.
{/if}
-
+
+
+
+ {#if $superadmin}
+
+ {/if}
+
{:else}