From af0e90195402a2d78e297e87969f0f9d3aeaaca0 Mon Sep 17 00:00:00 2001 From: wendrul <53628737+wendrul@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:27:13 +0200 Subject: [PATCH] feat: deployment UI filter deployable items (#4183) * Add column to workspace settings table * Add endpoint to change the deploy UI settings * Make frontend page to edit UI settings * Prepare sqlx * Add deployment restrictions to frontend * Fix function name * Change dependency to minimatch, CE compatibility * Remove default include_path * Remove picomatch types (old dep) * Use empty list instead of globstar as default * All deployable if config is null --- ...c61296a3ff7489ae12f52a19f9543173ac597.json | 6 + ...76be8aaa74ec3d32596c76a2041769478e61e.json | 14 ++ ...dcc40f463cbc52d94ed9315cf9a547d4c89f2.json | 6 + ...7f5033b9c9afc344d9c3e385ba20a3ad2197a.json | 2 +- ...0cb549a34b96554ae1872355b90304f5dcb76.json | 4 +- ...f47a3b108bbf61c9f4afd364ed869deac75a5.json | 15 ++ .../20240804220655_deploy_ui_config.down.sql | 2 + .../20240804220655_deploy_ui_config.up.sql | 2 + backend/windmill-api/openapi.yaml | 47 ++++ backend/windmill-api/src/workspaces.rs | 76 ++++++- backend/windmill-common/src/workspaces.rs | 6 + frontend/package-lock.json | 203 +++++++++++++++--- frontend/package.json | 1 + .../src/lib/components/DeployToSetting.svelte | 180 +++++++++++++++- .../lib/components/common/table/AppRow.svelte | 30 ++- .../components/common/table/FlowRow.svelte | 27 ++- .../components/common/table/RawAppRow.svelte | 22 +- .../components/common/table/ScriptRow.svelte | 32 ++- frontend/src/lib/components/home/Item.svelte | 20 ++ frontend/src/lib/utils.ts | 44 +++- .../(logged)/flows/get/[...path]/+page.svelte | 44 +++- .../(root)/(logged)/resources/+page.svelte | 38 +++- .../scripts/get/[...hash]/+page.svelte | 50 +++-- .../(root)/(logged)/variables/+page.svelte | 36 +++- .../(logged)/workspace_settings/+page.svelte | 30 ++- 25 files changed, 809 insertions(+), 128 deletions(-) create mode 100644 backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json create mode 100644 backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json create mode 100644 backend/migrations/20240804220655_deploy_ui_config.down.sql create mode 100644 backend/migrations/20240804220655_deploy_ui_config.up.sql diff --git a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json b/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json index 93d5dbac1e..16a701fdff 100644 --- a/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json +++ b/backend/.sqlx/query-1730f39fd1793d45fbb41b21389c61296a3ff7489ae12f52a19f9543173ac597.json @@ -112,6 +112,11 @@ "ordinal": 21, "name": "default_scripts", "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "deploy_ui", + "type_info": "Jsonb" } ], "parameters": { @@ -141,6 +146,7 @@ true, true, false, + true, true ] }, diff --git a/backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json b/backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json new file mode 100644 index 0000000000..64d01d5058 --- /dev/null +++ b/backend/.sqlx/query-26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "26b3e1f531909a2d841cfed49bb76be8aaa74ec3d32596c76a2041769478e61e" +} diff --git a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json index 0e29ef4745..5f4f137cd9 100644 --- a/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json +++ b/backend/.sqlx/query-55cb03040bc2a8c53dd7fbb42bbdcc40f463cbc52d94ed9315cf9a547d4c89f2.json @@ -112,6 +112,11 @@ "ordinal": 21, "name": "default_scripts", "type_info": "Jsonb" + }, + { + "ordinal": 22, + "name": "deploy_ui", + "type_info": "Jsonb" } ], "parameters": { @@ -141,6 +146,7 @@ true, true, false, + true, true ] }, diff --git a/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json b/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json index 75b8108281..1fa370e682 100644 --- a/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json +++ b/backend/.sqlx/query-661f472ff3860983322162420457f5033b9c9afc344d9c3e385ba20a3ad2197a.json @@ -5,7 +5,7 @@ "columns": [ { "ordinal": 0, - "name": "?column?", + "name": "bool", "type_info": "Bool" } ], diff --git a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json index 99269c9851..54e94cfb8f 100644 --- a/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json +++ b/backend/.sqlx/query-b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76.json @@ -18,8 +18,8 @@ "Left": [] }, "nullable": [ - true, - false + false, + true ] }, "hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76" diff --git a/backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json b/backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json new file mode 100644 index 0000000000..1b49e9e986 --- /dev/null +++ b/backend/.sqlx/query-c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb", + "Text" + ] + }, + "nullable": [] + }, + "hash": "c3364a5cd3fb7d43a6a6f484009f47a3b108bbf61c9f4afd364ed869deac75a5" +} diff --git a/backend/migrations/20240804220655_deploy_ui_config.down.sql b/backend/migrations/20240804220655_deploy_ui_config.down.sql new file mode 100644 index 0000000000..92c6564ae1 --- /dev/null +++ b/backend/migrations/20240804220655_deploy_ui_config.down.sql @@ -0,0 +1,2 @@ +-- Add down migration script here +ALTER TABLE workspace_settings DROP COLUMN deploy_ui; diff --git a/backend/migrations/20240804220655_deploy_ui_config.up.sql b/backend/migrations/20240804220655_deploy_ui_config.up.sql new file mode 100644 index 0000000000..6c59270932 --- /dev/null +++ b/backend/migrations/20240804220655_deploy_ui_config.up.sql @@ -0,0 +1,2 @@ +-- Add up migration script here +ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS deploy_ui JSONB; diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 49a5a5c4c9..bad9db1281 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -1507,6 +1507,8 @@ paths: $ref: "#/components/schemas/LargeFileStorage" git_sync: $ref: "#/components/schemas/WorkspaceGitSyncSettings" + deploy_ui: + $ref: "#/components/schemas/WorkspaceDeployUISettings" default_app: type: string default_scripts: @@ -1896,6 +1898,32 @@ paths: application/json: schema: {} + /w/{workspace}/workspaces/edit_deploy_ui_config: + post: + summary: edit workspace deploy ui settings + operationId: editWorkspaceDeployUISettings + tags: + - workspace + parameters: + - $ref: "#/components/parameters/WorkspaceId" + requestBody: + description: Workspace deploy UI settings + required: true + content: + application/json: + schema: + type: object + properties: + deploy_ui_settings: + $ref: "#/components/schemas/WorkspaceDeployUISettings" + + responses: + "200": + description: status + content: + application/json: + schema: {} + /w/{workspace}/workspaces/edit_default_app: post: summary: edit default app for workspace @@ -11078,6 +11106,25 @@ components: items: $ref: "#/components/schemas/GitRepositorySettings" + WorkspaceDeployUISettings: + type: object + properties: + include_path: + type: array + items: + type: string + include_type: + type: array + items: + type: string + enum: + - script + - flow + - app + - resource + - variable + - secret + WorkspaceDefaultScripts: type: object properties: diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index b3ae17a1c7..08509680cb 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -42,7 +42,7 @@ use windmill_common::schedule::Schedule; use windmill_common::users::username_to_permissioned_as; use windmill_common::variables::build_crypt; use windmill_common::worker::{to_raw_value, CLOUD_HOSTED}; -use windmill_common::workspaces::WorkspaceGitSyncSettings; +use windmill_common::workspaces::{WorkspaceDeploymentUISettings, WorkspaceGitSyncSettings}; use windmill_common::{ error::{to_anyhow, Error, JsonResult, Result}, flows::Flow, @@ -97,6 +97,7 @@ pub fn workspaced_service() -> Router { post(edit_large_file_storage_config), ) .route("/edit_git_sync_config", post(edit_git_sync_config)) + .route("/edit_deploy_ui_config", post(edit_deploy_ui_config)) .route("/edit_default_app", post(edit_default_app)) .route("/default_app", get(get_default_app)) .route( @@ -167,8 +168,9 @@ pub struct WorkspaceSettings { pub error_handler: Option, pub error_handler_extra_args: Option, pub error_handler_muted_on_cancel: Option, - pub large_file_storage: Option, // effectively: DatasetsStorage - pub git_sync: Option, // effectively: WorkspaceGitSyncSettings + pub large_file_storage: Option, // effectively: DatasetsStorage + pub git_sync: Option, // effectively: WorkspaceGitSyncSettings + pub deploy_ui: Option, // effectively: WorkspaceDeploymentUISettings pub default_app: Option, pub automatic_billing: bool, pub default_scripts: Option, @@ -1052,6 +1054,74 @@ async fn edit_git_sync_config( Ok(format!("Edit git sync config for workspace {}", &w_id)) } +#[derive(Deserialize)] +struct EditDeployUIConfig { + deploy_ui_settings: Option, +} + +#[cfg(not(feature = "enterprise"))] +async fn edit_deploy_ui_config( + _authed: ApiAuthed, + Extension(_db): Extension, + Path(_w_id): Path, + Json(_new_config): Json, +) -> Result { + return Err(Error::BadRequest( + "Deployment UI is only available on Windmill Enterprise Edition".to_string(), + )); +} + + +#[cfg(feature = "enterprise")] +async fn edit_deploy_ui_config( + authed: ApiAuthed, + Extension(db): Extension, + Path(w_id): Path, + ApiAuthed { is_admin, username, .. }: ApiAuthed, + Json(new_config): Json, +) -> Result { + require_admin(is_admin, &username)?; + + let mut tx = db.begin().await?; + + let args_for_audit = format!("{:?}", new_config.deploy_ui_settings); + audit_log( + &mut *tx, + &authed, + "workspaces.edit_deploy_ui_config", + ActionKind::Update, + &w_id, + Some(&authed.email), + Some([("deployment_ui_settings", args_for_audit.as_str())].into()), + ) + .await?; + + if let Some(deploy_ui_settings) = new_config.deploy_ui_settings { + let serialized_config = serde_json::to_value::(deploy_ui_settings) + .map_err(|err| Error::InternalErr(err.to_string()))?; + + sqlx::query!( + "UPDATE workspace_settings SET deploy_ui = $1 WHERE workspace_id = $2", + serialized_config, + &w_id + ) + .execute(&mut *tx) + .await?; + } else { + sqlx::query!( + "UPDATE workspace_settings SET deploy_ui = NULL WHERE workspace_id = $1", + &w_id, + ) + .execute(&mut *tx) + .await?; + } + tx.commit().await?; + + Ok(format!("Edit deployment UI config for workspace {}", &w_id)) +} + + + #[derive(Deserialize)] pub struct EditDefaultApp { pub default_app_path: Option, diff --git a/backend/windmill-common/src/workspaces.rs b/backend/windmill-common/src/workspaces.rs index 756b920bf4..d674285479 100644 --- a/backend/windmill-common/src/workspaces.rs +++ b/backend/windmill-common/src/workspaces.rs @@ -7,6 +7,12 @@ pub struct WorkspaceGitSyncSettings { pub repositories: Vec, } +#[derive(Serialize, Deserialize, Debug, Default)] +pub struct WorkspaceDeploymentUISettings { + pub include_path: Vec, + pub include_type: Vec, +} + #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))] pub enum ObjectType { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 381cdfb06b..ece4e299d1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -34,6 +34,7 @@ "hash-sum": "^2.0.0", "highlight.js": "^11.8.0", "lucide-svelte": "^0.293.0", + "minimatch": "^10.0.1", "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.83.5 <1.84.0", "monaco-graphql": "^1.5.1", "monaco-languageclient": "~7.0.1", @@ -935,6 +936,28 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/js": { "version": "8.54.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", @@ -1026,6 +1049,28 @@ "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1289,6 +1334,18 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.10.0.tgz", @@ -2386,6 +2443,18 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -2560,13 +2629,11 @@ "dev": true }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -4061,6 +4128,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/eslint/node_modules/eslint-scope": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", @@ -4098,6 +4175,18 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/esm-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", @@ -4607,6 +4696,28 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -6535,6 +6646,18 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -6597,15 +6720,17 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -7349,18 +7474,6 @@ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/picomatch-browser": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/picomatch-browser/-/picomatch-browser-2.2.6.tgz", @@ -8298,6 +8411,18 @@ "node": ">=8.10.0" } }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/redent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-4.0.0.tgz", @@ -9104,6 +9229,16 @@ "node": ">=8" } }, + "node_modules/sucrase/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -9133,6 +9268,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10159,14 +10306,6 @@ "vscode": "^1.82.0" } }, - "node_modules/vscode-languageclient/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, "node_modules/vscode-languageclient/node_modules/minimatch": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 99e707e464..541318d95f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -114,6 +114,7 @@ "hash-sum": "^2.0.0", "highlight.js": "^11.8.0", "lucide-svelte": "^0.293.0", + "minimatch": "^10.0.1", "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.83.5 <1.84.0", "monaco-graphql": "^1.5.1", "monaco-languageclient": "~7.0.1", diff --git a/frontend/src/lib/components/DeployToSetting.svelte b/frontend/src/lib/components/DeployToSetting.svelte index 3fe5081023..462ed6a67c 100644 --- a/frontend/src/lib/components/DeployToSetting.svelte +++ b/frontend/src/lib/components/DeployToSetting.svelte @@ -1,13 +1,88 @@

Workspace to link to

@@ -36,3 +111,104 @@ {/each} +

Deployable items

+
+
+ {#if Array.isArray(deployUiSettings?.include_path)} +

Filter on path + Only scripts, flows and apps with their path matching one of those filters will be allowed + to be deployed in the deploy UI. The filters allow '*'' and '**' characters, with '*'' + matching any character allowed in paths until the next slash (/) and '**' matching + anything including slashes. +

+ {#each deployUiSettings.include_path ?? [] as regexpPath, idx} +
+ + +
+ {/each} + {/if} +
+ +
+
+ +
+

Filter on type + You can filter which types of item can be deployed to the production workspace. By default + everything is deployable. +

+
+ + + + +
+ { + if (!ev.detail) { + deployUiSettings.include_type.secrets = false + } + }} + options={{ right: 'Variables ' }} + /> + - + +
+
+
+
+{#if $enterpriseLicense} +
+ +
+{/if} diff --git a/frontend/src/lib/components/common/table/AppRow.svelte b/frontend/src/lib/components/common/table/AppRow.svelte index f52b7fdae6..93afe04815 100644 --- a/frontend/src/lib/components/common/table/AppRow.svelte +++ b/frontend/src/lib/components/common/table/AppRow.svelte @@ -4,7 +4,12 @@ import type MoveDrawer from '$lib/components/MoveDrawer.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' import type ShareModal from '$lib/components/ShareModal.svelte' - import { AppService, DraftService, type ListableApp } from '$lib/gen' + import { + AppService, + DraftService, + type ListableApp, + type WorkspaceDeployUISettings + } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import { createEventDispatcher } from 'svelte' import Button from '../button/Button.svelte' @@ -28,7 +33,7 @@ import { goto as gotoUrl } from '$app/navigation' import { page } from '$app/stores' import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte' - import { DELETE, copyToClipboard } from '$lib/utils' + import { DELETE, copyToClipboard, isDeployable } from '$lib/utils' import AppDeploymentHistory from '$lib/components/apps/editor/AppDeploymentHistory.svelte' import AppJsonEditor from '$lib/components/apps/editor/AppJsonEditor.svelte' @@ -41,6 +46,7 @@ export let deleteConfirmedCallback: (() => void) | undefined export let depth: number = 0 export let menuOpen: boolean = false + export let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined const dispatch = createEventDispatcher() @@ -163,14 +169,18 @@ disabled: !canWrite, hide: $userStore?.operator }, - { - displayName: 'Deploy to staging/prod', - icon: Globe, - action: () => { - deploymentDrawer.openDrawer(path, 'app') - }, - hide: $userStore?.operator - }, + ...(isDeployable('app', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to staging/prod', + icon: Globe, + action: () => { + deploymentDrawer.openDrawer(path, 'app') + }, + hide: $userStore?.operator + } + ] + : []), { displayName: $userStore?.operator ? 'View JSON' : 'View/Edit JSON', icon: FileJson, diff --git a/frontend/src/lib/components/common/table/FlowRow.svelte b/frontend/src/lib/components/common/table/FlowRow.svelte index c667b8bcff..ab73ba060e 100644 --- a/frontend/src/lib/components/common/table/FlowRow.svelte +++ b/frontend/src/lib/components/common/table/FlowRow.svelte @@ -6,7 +6,7 @@ import ScheduleEditor from '$lib/components/ScheduleEditor.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' import type ShareModal from '$lib/components/ShareModal.svelte' - import { FlowService, type Flow, DraftService } from '$lib/gen' + import { FlowService, type Flow, DraftService, type WorkspaceDeployUISettings } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import { createEventDispatcher } from 'svelte' import Badge from '../badge/Badge.svelte' @@ -14,7 +14,7 @@ import Row from './Row.svelte' import DraftBadge from '$lib/components/DraftBadge.svelte' import { sendUserToast } from '$lib/toast' - import { DELETE, copyToClipboard, isOwner } from '$lib/utils' + import { DELETE, copyToClipboard, isDeployable, isOwner } from '$lib/utils' import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte' import { Pen, @@ -42,6 +42,7 @@ export let errorHandlerMuted: boolean export let depth: number = 0 export let menuOpen: boolean = false + export let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined const dispatch = createEventDispatcher() @@ -190,15 +191,19 @@ copyToClipboard(path) } }, - { - displayName: 'Deploy to staging/prod', - icon: Globe, - action: () => { - deploymentDrawer.openDrawer(path, 'flow') - }, - disabled: archived, - hide: $userStore?.operator - }, + ...(isDeployable('flow', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to staging/prod', + icon: Globe, + action: () => { + deploymentDrawer.openDrawer(path, 'flow') + }, + disabled: archived, + hide: $userStore?.operator + } + ] + : []), { displayName: 'Deployments', icon: HistoryIcon, diff --git a/frontend/src/lib/components/common/table/RawAppRow.svelte b/frontend/src/lib/components/common/table/RawAppRow.svelte index a7751c36eb..615de50e64 100644 --- a/frontend/src/lib/components/common/table/RawAppRow.svelte +++ b/frontend/src/lib/components/common/table/RawAppRow.svelte @@ -4,7 +4,7 @@ import type MoveDrawer from '$lib/components/MoveDrawer.svelte' import SharedBadge from '$lib/components/SharedBadge.svelte' import type ShareModal from '$lib/components/ShareModal.svelte' - import { RawAppService, type ListableRawApp } from '$lib/gen' + import { RawAppService, type ListableRawApp, type WorkspaceDeployUISettings } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import { createEventDispatcher } from 'svelte' import Button from '../button/Button.svelte' @@ -15,6 +15,7 @@ import { goto } from '$lib/navigation' import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte' import { FileUp, Globe, Pen, Share, Trash } from 'lucide-svelte' + import { isDeployable } from '$lib/utils' export let app: ListableRawApp & { canWrite: boolean } export let marked: string | undefined @@ -25,6 +26,7 @@ export let deploymentDrawer: DeployWorkspaceDrawer export let depth: number = 0 export let menuOpen: boolean = false + export let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined let updateAppDrawer: Drawer @@ -98,13 +100,17 @@ }, disabled: !canWrite }, - { - displayName: 'Deploy to prod/staging', - icon: Globe, - action: () => { - deploymentDrawer.openDrawer(path, 'raw_app') - } - }, + ...(isDeployable('app', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to prod/staging', + icon: Globe, + action: () => { + deploymentDrawer.openDrawer(path, 'raw_app') + } + } + ] + : []), { displayName: canWrite ? 'Share' : 'See Permissions', icon: Share, diff --git a/frontend/src/lib/components/common/table/ScriptRow.svelte b/frontend/src/lib/components/common/table/ScriptRow.svelte index 450836af77..12acd2774c 100644 --- a/frontend/src/lib/components/common/table/ScriptRow.svelte +++ b/frontend/src/lib/components/common/table/ScriptRow.svelte @@ -7,7 +7,12 @@ import SharedBadge from '$lib/components/SharedBadge.svelte' import type ShareModal from '$lib/components/ShareModal.svelte' - import { ScriptService, type Script, DraftService } from '$lib/gen' + import { + ScriptService, + type Script, + DraftService, + type WorkspaceDeployUISettings + } from '$lib/gen' import { userStore, workspaceStore } from '$lib/stores' import { createEventDispatcher } from 'svelte' @@ -16,7 +21,7 @@ import Row from './Row.svelte' import DraftBadge from '$lib/components/DraftBadge.svelte' import { sendUserToast } from '$lib/toast' - import { copyToClipboard, DELETE, isOwner } from '$lib/utils' + import { copyToClipboard, DELETE, isDeployable, isOwner } from '$lib/utils' import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte' import { LanguageIcon } from '../languageIcons' import { @@ -49,6 +54,7 @@ export let showCode: (path: string, summary: string) => void export let depth: number = 0 export let menuOpen: boolean = false + export let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined const dispatch = createEventDispatcher() @@ -211,15 +217,19 @@ disabled: !owner || script.archived, hide: $userStore?.operator }, - { - displayName: 'Deploy to staging/prod', - icon: FileUp, - action: () => { - deploymentDrawer.openDrawer(script.path, 'script') - }, - disabled: script.archived, - hide: $userStore?.operator - }, + ...(isDeployable('script', script.path, deployUiSettings) + ? [ + { + displayName: 'Deploy to staging/prod', + icon: FileUp, + action: () => { + deploymentDrawer.openDrawer(script.path, 'script') + }, + disabled: script.archived, + hide: $userStore?.operator + } + ] + : []), { displayName: 'View runs', icon: List, diff --git a/frontend/src/lib/components/home/Item.svelte b/frontend/src/lib/components/home/Item.svelte index 91ef63179d..2e7b3c6967 100644 --- a/frontend/src/lib/components/home/Item.svelte +++ b/frontend/src/lib/components/home/Item.svelte @@ -10,6 +10,9 @@ import ShareModal from '../ShareModal.svelte' import { createEventDispatcher } from 'svelte' import { ArrowBigUp } from 'lucide-svelte' + import { enterpriseLicense, workspaceStore } from '$lib/stores' + import { WorkspaceService, type WorkspaceDeployUISettings } from '$lib/gen' + import { ALL_DEPLOYABLE } from '$lib/utils' export let item export let depth: number = 0 @@ -23,6 +26,19 @@ let menuOpen: boolean = false export let showCode: (path: string, summary: string) => void + + let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined + + async function getDeployUiSettings() { + if (!$enterpriseLicense) { + deployUiSettings = ALL_DEPLOYABLE + return + } + let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! }) + deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE + } + getDeployUiSettings() + {#if item.type == 'script'} @@ -42,6 +58,7 @@ {depth} bind:menuOpen {showCode} + {deployUiSettings} /> {:else if item.type == 'flow'} {:else if item.type == 'app'} {:else if item.type == 'raw_app'} {/if} diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index 554301c203..876d60bce2 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -11,9 +11,10 @@ import { deepEqual } from 'fast-equals' import YAML from 'yaml' import type { UserExt } from './stores' import { sendUserToast } from './toast' -import type { Script } from './gen' +import type { Script, WorkspaceDeployUISettings } from './gen' import type { EnumType, SchemaProperty } from './common' import type { Schema } from './common' +import { minimatch } from 'minimatch' export { sendUserToast } export function validateUsername(username: string): string { @@ -494,7 +495,7 @@ export function isObject(obj: any) { export function debounce(func: (...args: any[]) => any, wait: number) { let timeout: any - return function (...args: any[]) { + return function(...args: any[]) { // @ts-ignore const context = this clearTimeout(timeout) @@ -504,7 +505,7 @@ export function debounce(func: (...args: any[]) => any, wait: number) { export function throttle(func: (...args: any[]) => T, wait: number) { let timeout: any - return function (...args: any[]) { + return function(...args: any[]) { if (!timeout) { timeout = setTimeout(() => { timeout = null @@ -720,7 +721,7 @@ export async function tryEvery({ try { await tryCode() break - } catch (err) {} + } catch (err) { } i++ } if (i >= times) { @@ -937,3 +938,38 @@ export function getSchemaFromProperties(properties: { [name: string]: SchemaProp order: Object.keys(properties).filter((k) => k !== 'label') } } + +type DeployUIType = 'script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret' + +export function isDeployable( + type: DeployUIType, + path: string, + deployUiSettings: WorkspaceDeployUISettings | undefined +) { + if (deployUiSettings == undefined) { + return false + } + + if ( + deployUiSettings.include_type != undefined && + !deployUiSettings.include_type.includes(type) + ) { + return false + } + + if ( + deployUiSettings.include_path != undefined && + deployUiSettings.include_path.length != 0 && + deployUiSettings.include_path.every((x) => !minimatch(path, x)) + ) { + return false + } + + return true +} + +export const ALL_DEPLOYABLE: WorkspaceDeployUISettings = { + include_path: [], + include_type: ['script', 'flow', 'app', 'resource', 'variable', 'secret'] +} + diff --git a/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte b/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte index 95fad52d5e..76973ddeac 100644 --- a/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/flows/get/[...path]/+page.svelte @@ -1,7 +1,14 @@ { - deploymentDrawer.openDrawer(path, 'resource') - } - }, + ...(isDeployable('resource', path, deployUiSettings) + ? [ + { + displayName: 'Deploy to prod/staging', + icon: FileUp, + action: () => { + deploymentDrawer.openDrawer(path, 'resource') + } + } + ] + : []), { displayName: 'Delete', disabled: !canWrite, diff --git a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte index 87a6009419..bdf1ffca5a 100644 --- a/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte +++ b/frontend/src/routes/(root)/(logged)/scripts/get/[...hash]/+page.svelte @@ -1,11 +1,20 @@