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
This commit is contained in:
wendrul
2024-08-05 13:27:13 +02:00
committed by GitHub
parent 326745bab5
commit af0e901954
25 changed files with 809 additions and 128 deletions
@@ -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
]
},
@@ -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"
}
@@ -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
]
},
@@ -5,7 +5,7 @@
"columns": [
{
"ordinal": 0,
"name": "?column?",
"name": "bool",
"type_info": "Bool"
}
],
@@ -18,8 +18,8 @@
"Left": []
},
"nullable": [
true,
false
false,
true
]
},
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
@@ -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"
}
@@ -0,0 +1,2 @@
-- Add down migration script here
ALTER TABLE workspace_settings DROP COLUMN deploy_ui;
@@ -0,0 +1,2 @@
-- Add up migration script here
ALTER TABLE workspace_settings ADD COLUMN IF NOT EXISTS deploy_ui JSONB;
+47
View File
@@ -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:
+73 -3
View File
@@ -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<String>,
pub error_handler_extra_args: Option<serde_json::Value>,
pub error_handler_muted_on_cancel: Option<bool>,
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
pub large_file_storage: Option<serde_json::Value>, // effectively: DatasetsStorage
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
pub deploy_ui: Option<serde_json::Value>, // effectively: WorkspaceDeploymentUISettings
pub default_app: Option<String>,
pub automatic_billing: bool,
pub default_scripts: Option<serde_json::Value>,
@@ -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<WorkspaceDeploymentUISettings>,
}
#[cfg(not(feature = "enterprise"))]
async fn edit_deploy_ui_config(
_authed: ApiAuthed,
Extension(_db): Extension<DB>,
Path(_w_id): Path<String>,
Json(_new_config): Json<EditDeployUIConfig>,
) -> Result<String> {
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<DB>,
Path(w_id): Path<String>,
ApiAuthed { is_admin, username, .. }: ApiAuthed,
Json(new_config): Json<EditDeployUIConfig>,
) -> Result<String> {
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::<WorkspaceDeploymentUISettings>(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<String>,
@@ -7,6 +7,12 @@ pub struct WorkspaceGitSyncSettings {
pub repositories: Vec<GitRepositorySettings>,
}
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct WorkspaceDeploymentUISettings {
pub include_path: Vec<String>,
pub include_type: Vec<ObjectType>,
}
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(rename_all(serialize = "lowercase", deserialize = "lowercase"))]
pub enum ObjectType {
+171 -32
View File
@@ -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",
+1
View File
@@ -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",
@@ -1,13 +1,88 @@
<script lang="ts">
import { WorkspaceService } from '$lib/gen'
import { usersWorkspaceStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, usersWorkspaceStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { fade } from 'svelte/transition'
import Tooltip from './Tooltip.svelte'
import { Plus, X } from 'lucide-svelte'
import { Button } from './common'
import Toggle from './Toggle.svelte'
import { emptyString } from '$lib/utils'
export let workspaceToDeployTo: string | undefined
$: deployableWorkspaces = $usersWorkspaceStore?.workspaces
.map((w) => w.id)
.filter((w) => w != $workspaceStore)
type DeployUITypeMap = {
scripts: boolean
flows: boolean
apps: boolean
resources: boolean
variables: boolean
secrets: boolean
}
type DeployUIType = 'script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret'
const all_ok: DeployUITypeMap = {
scripts: true,
flows: true,
apps: true,
resources: true,
variables: true,
secrets: true
}
export let workspaceToDeployTo: string | undefined
export let deployUiSettings: {
include_path: string[]
include_type: DeployUITypeMap
} = {
include_path: [],
include_type: all_ok
}
function deployUITypeMapToArray(
typesMap: DeployUITypeMap,
expectedValue: boolean
): DeployUIType[] {
let result: DeployUIType[] = []
if (typesMap.scripts == expectedValue) {
result.push('script')
}
if (typesMap.flows == expectedValue) {
result.push('flow')
}
if (typesMap.apps == expectedValue) {
result.push('app')
}
if (typesMap.resources == expectedValue) {
result.push('resource')
}
if (typesMap.variables == expectedValue) {
result.push('variable')
}
if (typesMap.secrets == expectedValue) {
result.push('secret')
}
return result
}
async function editWindmillDeploymentUISettings() {
let include_path = deployUiSettings.include_path.filter((elmt) => !emptyString(elmt))
let include_type = deployUITypeMapToArray(deployUiSettings.include_type, true)
await WorkspaceService.editWorkspaceDeployUiSettings({
workspace: $workspaceStore!,
requestBody: {
deploy_ui_settings: {
include_path: include_path,
include_type: include_type
}
}
})
sendUserToast('Workspace Deployment UI settings updated')
}
</script>
<h3 class="mt-8">Workspace to link to</h3>
@@ -36,3 +111,104 @@
{/each}
</select>
</div>
<h3 class="mt-6 mb-3">Deployable items</h3>
<div class="flex flex-wrap gap-20">
<div class="max-w-md w-full">
{#if Array.isArray(deployUiSettings?.include_path)}
<h4 class="flex gap-2 mb-4"
>Filter on path<Tooltip>
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.
</Tooltip></h4
>
{#each deployUiSettings.include_path ?? [] as regexpPath, idx}
<div class="flex mt-1 items-center">
<input type="text" bind:value={regexpPath} id="arg-input-array" />
<button
transition:fade|local={{ duration: 100 }}
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
aria-label="Clear"
on:click={() => {
deployUiSettings.include_path.splice(idx, 1)
deployUiSettings.include_path = [...deployUiSettings.include_path]
}}
>
<X size={14} />
</button>
</div>
{/each}
{/if}
<div class="flex mt-2">
<Button
variant="border"
color="light"
size="xs"
btnClasses="mt-1"
on:click={() => {
deployUiSettings.include_path = [...deployUiSettings.include_path, '']
}}
id="deploy-ui-add-path-filter"
startIcon={{ icon: Plus }}
>
Add filter
</Button>
</div>
</div>
<div class="max-w-md w-full">
<h4 class="flex gap-2 mb-4"
>Filter on type<Tooltip>
You can filter which types of item can be deployed to the production workspace. By default
everything is deployable.
</Tooltip></h4
>
<div class="flex flex-col gap-2 mt-1">
<Toggle
bind:checked={deployUiSettings.include_type.scripts}
options={{ right: 'Scripts' }}
/>
<Toggle
bind:checked={deployUiSettings.include_type.flows}
options={{ right: 'Flows' }}
/>
<Toggle
bind:checked={deployUiSettings.include_type.apps}
options={{ right: 'Apps' }}
/>
<Toggle
bind:checked={deployUiSettings.include_type.resources}
options={{ right: 'Resources' }}
/>
<div class="flex gap-3">
<Toggle
bind:checked={deployUiSettings.include_type.variables}
on:change={(ev) => {
if (!ev.detail) {
deployUiSettings.include_type.secrets = false
}
}}
options={{ right: 'Variables ' }}
/>
<span>-</span>
<Toggle
disabled={!deployUiSettings.include_type.variables}
bind:checked={deployUiSettings.include_type.secrets}
options={{ left: 'Include secrets' }}
/>
</div>
</div>
</div>
</div>
{#if $enterpriseLicense}
<div class="flex mt-5 mb-5 gap-1">
<Button
color="blue"
disabled={workspaceToDeployTo == undefined}
on:click={() => {
editWindmillDeploymentUISettings()
}}>Save Deployment UI settings</Button
>
</div>
{/if}
@@ -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,
@@ -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,
@@ -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,
@@ -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,
@@ -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()
</script>
{#if item.type == 'script'}
@@ -42,6 +58,7 @@
{depth}
bind:menuOpen
{showCode}
{deployUiSettings}
/>
{:else if item.type == 'flow'}
<FlowRow
@@ -59,6 +76,7 @@
{deploymentDrawer}
{depth}
bind:menuOpen
{deployUiSettings}
/>
{:else if item.type == 'app'}
<AppRow
@@ -72,6 +90,7 @@
{deploymentDrawer}
{depth}
bind:menuOpen
{deployUiSettings}
/>
{:else if item.type == 'raw_app'}
<RawAppRow
@@ -85,6 +104,7 @@
{deploymentDrawer}
{depth}
bind:menuOpen
{deployUiSettings}
/>
{/if}
+40 -4
View File
@@ -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<T>(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']
}
@@ -1,7 +1,14 @@
<script lang="ts">
import { page } from '$app/stores'
import { FlowService, JobService, type Flow, type FlowModule } from '$lib/gen'
import { canWrite, defaultIfEmptyString, emptyString } from '$lib/utils'
import {
FlowService,
JobService,
WorkspaceService,
type Flow,
type FlowModule,
type WorkspaceDeployUISettings
} from '$lib/gen'
import { ALL_DEPLOYABLE, canWrite, defaultIfEmptyString, emptyString, isDeployable } from '$lib/utils'
import DetailPageLayout from '$lib/components/details/DetailPageLayout.svelte'
import { goto } from '$lib/navigation'
@@ -10,7 +17,7 @@
import MoveDrawer from '$lib/components/MoveDrawer.svelte'
import RunForm from '$lib/components/RunForm.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
import SavedInputs from '$lib/components/SavedInputs.svelte'
@@ -206,7 +213,22 @@
$: mainButtons = getMainButtons(flow, args)
function getMenuItems(flow: Flow | undefined) {
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()
function getMenuItems(
flow: Flow | undefined,
deployUiSettings: WorkspaceDeployUISettings | undefined
) {
if (!flow || $userStore?.operator) return []
const menuItems: any = []
@@ -232,11 +254,13 @@
}
})
menuItems.push({
label: 'Deploy to staging/prod',
onclick: () => deploymentDrawer.openDrawer(flow?.path ?? '', 'flow'),
Icon: Server
})
if (isDeployable('flow', flow?.path ?? '', deployUiSettings)) {
menuItems.push({
label: 'Deploy to staging/prod',
onclick: () => deploymentDrawer.openDrawer(flow?.path ?? '', 'flow'),
Icon: Server
})
}
if (can_write) {
menuItems.push({
@@ -318,7 +342,7 @@
<svelte:fragment slot="header">
<DetailPageHeader
{mainButtons}
menuItems={getMenuItems(flow)}
menuItems={getMenuItems(flow, deployUiSettings)}
title={defaultIfEmptyString(flow.summary, flow.path)}
bind:errorHandlerMuted={flow.ws_error_handler_muted}
scriptOrFlowPath={flow.path}
@@ -27,11 +27,11 @@
import Row from '$lib/components/table/Row.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import type { ResourceType } from '$lib/gen'
import { OauthService, ResourceService, type ListableResource } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import type { ResourceType, WorkspaceDeployUISettings } from '$lib/gen'
import { OauthService, ResourceService, WorkspaceService, type ListableResource } from '$lib/gen'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { canWrite, classNames, emptySchema, removeMarkdown, truncate } from '$lib/utils'
import { ALL_DEPLOYABLE, canWrite, classNames, emptySchema, isDeployable, removeMarkdown, truncate } from '$lib/utils'
import { convert } from '@redocly/json-to-json-schema'
import {
Braces,
@@ -324,6 +324,18 @@
types: false
}
}
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()
</script>
<ConfirmationModal
@@ -748,13 +760,17 @@
resourceEditor?.initEdit?.(path)
}
},
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'resource')
}
},
...(isDeployable('resource', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'resource')
}
}
]
: []),
{
displayName: 'Delete',
disabled: !canWrite,
@@ -1,11 +1,20 @@
<script lang="ts">
import { page } from '$app/stores'
import { base } from '$lib/base'
import { JobService, ScriptService, type Script } from '$lib/gen'
import { defaultIfEmptyString, emptyString, canWrite, truncateHash } from '$lib/utils'
import { JobService, ScriptService, WorkspaceService, type Script, type WorkspaceDeployUISettings } from '$lib/gen'
import {
defaultIfEmptyString,
emptyString,
canWrite,
truncateHash,
isDeployable,
ALL_DEPLOYABLE
} from '$lib/utils'
import Tooltip from '$lib/components/Tooltip.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import { hubBaseUrlStore, userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, hubBaseUrlStore, userStore, workspaceStore } from '$lib/stores'
import SchemaViewer from '$lib/components/SchemaViewer.svelte'
import { onDestroy } from 'svelte'
import HighlightCode from '$lib/components/HighlightCode.svelte'
@@ -318,7 +327,22 @@
}
$: mainButtons = getMainButtons(script, args, topHash, can_write)
function getMenuItems(script: Script | undefined) {
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()
function getMenuItems(
script: Script | undefined,
deployUiSettings: WorkspaceDeployUISettings | undefined
) {
if (!script || $userStore?.operator) return []
const menuItems: any = []
@@ -347,13 +371,15 @@
}
})
menuItems.push({
label: 'Deploy to staging/prod',
Icon: Server,
onclick: () => {
deploymentDrawer.openDrawer(script?.path ?? '', 'script')
}
})
if (isDeployable('script', script?.path ?? '', deployUiSettings)) {
menuItems.push({
label: 'Deploy to staging/prod',
Icon: Server,
onclick: () => {
deploymentDrawer.openDrawer(script?.path ?? '', 'script')
}
})
}
if (SCRIPT_VIEW_SHOW_PUBLISH_TO_HUB) {
menuItems.push({
@@ -471,7 +497,7 @@
<svelte:fragment slot="header">
<DetailPageHeader
{mainButtons}
menuItems={getMenuItems(script)}
menuItems={getMenuItems(script, deployUiSettings)}
title={defaultIfEmptyString(script.summary, script.path)}
bind:errorHandlerMuted={script.ws_error_handler_muted}
errorHandlerKind="script"
@@ -18,11 +18,11 @@
import TableSimple from '$lib/components/TableSimple.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import VariableEditor from '$lib/components/VariableEditor.svelte'
import type { ContextualVariable, ListableVariable } from '$lib/gen'
import type { ContextualVariable, ListableVariable, WorkspaceDeployUISettings } from '$lib/gen'
import { OauthService, VariableService, WorkspaceService } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import { canWrite, isOwner, truncate } from '$lib/utils'
import { ALL_DEPLOYABLE, canWrite, isDeployable, isOwner, truncate } from '$lib/utils'
import {
Plus,
FileUp,
@@ -78,6 +78,18 @@
})
}
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()
async function loadContextualVariables(): Promise<void> {
contextualVariables = await VariableService.listContextualVariables({
workspace: $workspaceStore!
@@ -347,13 +359,17 @@
},
disabled: !owner
},
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'variable')
}
},
...(isDeployable(is_secret ? 'secret' : 'variable', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'variable')
}
}
]
: []),
{
displayName: owner ? 'Share' : 'See Permissions',
action: () => {
@@ -482,6 +482,22 @@
}
gitSyncTestJobs = []
}
if (settings.deploy_ui != undefined && settings.deploy_ui != null) {
deployUiSettings = {
include_path:
settings.deploy_ui.include_path?.length ?? 0 > 0
? settings.deploy_ui.include_path ?? []
: [],
include_type: {
scripts: (settings.deploy_ui.include_type?.indexOf('script') ?? -1) >= 0,
flows: (settings.deploy_ui.include_type?.indexOf('flow') ?? -1) >= 0,
apps: (settings.deploy_ui.include_type?.indexOf('app') ?? -1) >= 0,
resources: (settings.deploy_ui.include_type?.indexOf('resource') ?? -1) >= 0,
variables: (settings.deploy_ui.include_type?.indexOf('variable') ?? -1) >= 0,
secrets: (settings.deploy_ui.include_type?.indexOf('secret') ?? -1) >= 0,
}
}
}
// check openai_client_credentials_oauth
usingOpenaiClientCredentialsOauth = await ResourceService.existsResourceType({
@@ -490,6 +506,18 @@
})
}
let deployUiSettings: {
include_path: string[]
include_type: {
scripts: boolean
flows: boolean
apps: boolean
resources: boolean
variables: boolean
secrets: boolean
}
}
$: {
if ($workspaceStore) {
loadSettings()
@@ -688,7 +716,7 @@
</div>
</div>
{#if $enterpriseLicense}
<DeployToSetting bind:workspaceToDeployTo />
<DeployToSetting bind:workspaceToDeployTo bind:deployUiSettings />
{:else}
<div class="my-2"
><Alert type="error" title="Enterprise license required"