feat(frontend): Merge main

This commit is contained in:
Faton Ramadani
2024-08-05 16:25:15 +02:00
45 changed files with 1190 additions and 180 deletions
+15
View File
@@ -1,5 +1,20 @@
# Changelog
## [1.375.0](https://github.com/windmill-labs/windmill/compare/v1.374.0...v1.375.0) (2024-08-05)
### Features
* deployment UI filter deployable items ([#4183](https://github.com/windmill-labs/windmill/issues/4183)) ([f2f8bbe](https://github.com/windmill-labs/windmill/commit/f2f8bbe1d4364540854fef3bf11df5bb4252c17d))
* improve indices of completed_runs for faster load ([cc111ba](https://github.com/windmill-labs/windmill/commit/cc111ba7dcd7fe6c4280d394d4eb0e723e9d8b19))
### Bug Fixes
* **frontend:** add missing truncate for branch predicates ([#4180](https://github.com/windmill-labs/windmill/issues/4180)) ([947dd21](https://github.com/windmill-labs/windmill/commit/947dd219335244728213e95a46e577428ac65464))
* **frontend:** fix style panel for compoentn without custom css ([#4182](https://github.com/windmill-labs/windmill/issues/4182)) ([43a89ee](https://github.com/windmill-labs/windmill/commit/43a89ee1ccbeeac11211277f8005d85541c71dc3))
* **frontend:** fix tutorial for apps with the new topbar ([#4186](https://github.com/windmill-labs/windmill/issues/4186)) ([704e75e](https://github.com/windmill-labs/windmill/commit/704e75e893a94e71091c840aa5608837c0617526))
## [1.374.0](https://github.com/windmill-labs/windmill/compare/v1.373.1...v1.374.0) (2024-08-04)
@@ -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"
}
+20 -20
View File
@@ -10383,7 +10383,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"axum",
@@ -10423,7 +10423,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"argon2",
@@ -10502,7 +10502,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"base64 0.21.7",
"chrono",
@@ -10520,7 +10520,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"chrono",
"serde",
@@ -10533,7 +10533,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"async-stream",
@@ -10574,7 +10574,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"regex",
"rsmq_async",
@@ -10589,7 +10589,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"bytes",
@@ -10610,7 +10610,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"serde",
"serde_json",
@@ -10618,7 +10618,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10629,7 +10629,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"gosyn",
@@ -10641,7 +10641,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10652,7 +10652,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10666,7 +10666,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10677,7 +10677,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -10694,7 +10694,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"lazy_static",
@@ -10705,7 +10705,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10724,7 +10724,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -10743,7 +10743,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"async-recursion",
@@ -10776,7 +10776,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -10786,7 +10786,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.374.0"
version = "1.375.0"
dependencies = [
"anyhow",
"async-recursion",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.374.0"
version = "1.375.0"
authors.workspace = true
edition.workspace = true
@@ -25,7 +25,7 @@ members = [
]
[workspace.package]
version = "1.374.0"
version = "1.375.0"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -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;
+48 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.374.0
version: 1.375.0
title: Windmill API
contact:
@@ -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:
+139 -6
View File
@@ -327,13 +327,13 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
.unwrap_or(false);
if !has_done_migration {
sqlx::query(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
).execute(db).await?;
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_created_at_new_2 ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, created_at DESC)"
// ).execute(db).await?;
sqlx::query(
"CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
).execute(db).await?;
// sqlx::query(
// "CREATE INDEX CONCURRENTLY IF NOT EXISTS ix_completed_job_workspace_id_started_at_new ON completed_job (workspace_id, job_kind, success, is_skipped, is_flow_step, started_at DESC)"
// ).execute(db).await?;
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at",
@@ -436,6 +436,139 @@ async fn fix_job_completed_index(db: &DB) -> Result<(), Error> {
tracing::info!("released lock for {migration_job_name}");
}
let migration_job_name = "fix_job_completed_index_4";
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
tracing::info!("Applying {migration_job_name} migration");
let mut tx = db.begin().await?;
let mut r = false;
while !r {
r = sqlx::query_scalar!("SELECT pg_try_advisory_lock(4242)")
.fetch_one(&mut *tx)
.await
.map_err(|e| {
tracing::error!("Error acquiring {migration_job_name} lock: {e:#}");
sqlx::migrate::MigrateError::Execute(e)
})?
.unwrap_or(false);
if !r {
tracing::info!("PG {migration_job_name} lock already acquired by another server or worker, retrying in 5s. (look for the advisory lock in pg_lock with granted = true)");
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
}
}
tracing::info!("acquired lock for {migration_job_name}");
let has_done_migration = sqlx::query_scalar!(
"SELECT EXISTS(SELECT name FROM windmill_migrations WHERE name = $1)",
migration_job_name
)
.fetch_one(db)
.await?
.unwrap_or(false);
if !has_done_migration {
let mut i = 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_3 ON completed_job (workspace_id, created_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_8 ON completed_job (workspace_id, created_at DESC) where job_kind in ('deploymentcallback') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_9 ON completed_job (workspace_id, created_at DESC) where job_kind in ('dependencies', 'flowdependencies', 'appdependencies') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_5 ON completed_job (workspace_id, created_at DESC) where job_kind in ('preview', 'flowpreview') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_6 ON completed_job (workspace_id, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_created_at_new_7 ON completed_job (workspace_id, success, created_at DESC) where job_kind in ('script', 'flow') AND parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_workspace_id_started_at_new_2 ON completed_job (workspace_id, started_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists root_job_index_by_path_2 ON completed_job (workspace_id, script_path, created_at desc) WHERE parent_job IS NULL")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("create index concurrently if not exists ix_completed_job_created_at ON completed_job (created_at DESC)")
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_created_at_new_2",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query(
"DROP INDEX CONCURRENTLY IF EXISTS ix_completed_job_workspace_id_started_at_new",
)
.execute(db)
.await?;
i += 1;
tracing::info!("step {i} of {migration_job_name} migration");
sqlx::query("DROP INDEX CONCURRENTLY IF EXISTS root_job_index_by_path")
.execute(db)
.await?;
sqlx::query!(
"INSERT INTO windmill_migrations (name) VALUES ($1) ON CONFLICT DO NOTHING",
migration_job_name
)
.execute(&mut *tx)
.await?;
tracing::info!("Finished applying {migration_job_name} migration");
} else {
tracing::info!("migration {migration_job_name} already done");
}
let _ = sqlx::query("SELECT pg_advisory_unlock(4242)")
.execute(&mut *tx)
.await?;
tx.commit().await?;
tracing::info!("released lock for {migration_job_name}");
}
Ok(())
}
+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 {
@@ -1321,8 +1321,6 @@ pub async fn start_worker(
tracing::info!("Starting worker {w_id};{script_path} (codebase: {codebase:?}");
if !codebase.is_some() {
let _ = write_file(job_dir, "main.ts", inner_content).await?;
} else {
let _ = write_file(job_dir, "package.json", r#"{ "type": "module" }"#).await?;
}
let common_bun_proc_envs: HashMap<String, String> =
+1 -1
View File
@@ -2,7 +2,7 @@ import { sleep } from "https://deno.land/x/sleep@v1.2.1/mod.ts";
import * as windmill from "https://deno.land/x/windmill@v1.174.0/mod.ts";
import * as api from "https://deno.land/x/windmill@v1.174.0/windmill-api/index.ts";
export const VERSION = "v1.374.0";
export const VERSION = "v1.375.0";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+1 -1
View File
@@ -34,7 +34,7 @@ addEventListener("error", (event) => {
}
});
export const VERSION = "v1.374.0";
export const VERSION = "v1.375.0";
let command: any = new Command()
.name("wmill")
+171 -32
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.374.0",
"version": "1.375.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.374.0",
"version": "1.375.0",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",
@@ -36,6 +36,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",
@@ -953,6 +954,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",
@@ -1044,6 +1067,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",
@@ -1307,6 +1352,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",
@@ -2433,6 +2490,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",
@@ -2607,12 +2676,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==",
"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": {
@@ -4108,6 +4176,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",
@@ -4145,6 +4223,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",
@@ -4652,6 +4742,26 @@
"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==",
"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==",
"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",
@@ -6577,6 +6687,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",
@@ -6638,14 +6760,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==",
"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": {
@@ -7386,18 +7511,6 @@
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
"devOptional": 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",
@@ -8335,6 +8448,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",
@@ -9138,6 +9263,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",
@@ -9167,6 +9302,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",
@@ -10179,14 +10326,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",
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.374.0",
"version": "1.375.0",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -116,6 +116,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}
@@ -366,7 +366,10 @@ export function appComponentFromType<T extends keyof typeof components>(
tabs: init.tabs,
conditions: init.conditions,
nodes: init.nodes,
customCss: deepMergeWithPriority(ccomponents[type].customCss as any, override?.customCss),
customCss: deepMergeWithPriority(
ccomponents[type].customCss as any,
override?.customCss ?? {}
),
recomputeIds: init.recomputeIds ? [] : undefined,
actionButtons: init.actionButtons ? [] : undefined,
actions: [],
@@ -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}
@@ -122,21 +122,26 @@
): Promise<Job[]> {
loadingFetch = true
try {
let scriptPathStart = folder === null || folder === '' ? undefined : `f/${folder}/`
let scriptPathExact = path === null || path === '' ? undefined : path
return JobService.listJobs({
workspace: $workspaceStore!,
createdOrStartedBefore: startedBefore,
createdOrStartedAfter: startedAfter,
createdOrStartedAfterCompletedJobs: startedAfterCompletedJobs,
schedulePath,
scriptPathExact: path === null || path === '' ? undefined : path,
scriptPathExact,
createdBy: user === null || user === '' ? undefined : user,
scriptPathStart: folder === null || folder === '' ? undefined : `f/${folder}/`,
scriptPathStart: scriptPathStart,
jobKinds,
success: success == 'success' ? true : success == 'failure' ? false : undefined,
running: success == 'running' ? true : undefined,
isSkipped: isSkipped ? undefined : false,
isFlowStep: jobKindsCat != 'all' ? false : undefined,
hasNullParent: jobKindsCat != 'all' ? false : undefined,
// isFlowStep: jobKindsCat != 'all' ? false : undefined,
hasNullParent:
scriptPathExact != undefined || scriptPathStart != undefined || jobKinds != 'all'
? true
: undefined,
label: label === null || label === '' ? undefined : label,
isNotSchedule: showSchedules == false ? true : undefined,
scheduledForBeforeNow: showFutureJobs == false ? true : undefined,
@@ -5,6 +5,11 @@ export type Changelog = {
}
const changelogs: Changelog[] = [
{
label: 'Nativets runtime supports npm packages and relative imports',
href: 'https://www.windmill.dev/changelog/native-runtime-imports',
date: '2024-07-29'
},
{
label: 'App bar as components',
href: 'https://www.windmill.dev/changelog/app-bar-components',
@@ -84,7 +84,7 @@
description:
'Click on a component to add it to your app. Here we will add a display component.',
onNextClick: () => {
if (!$selectedComponent?.includes('b')) {
if (!$selectedComponent?.includes('e')) {
addComponent('displaycomponent')
}
@@ -216,7 +216,7 @@
},
{
element: '#connect-output-a',
element: '#connect-output-d',
popover: {
title: 'Select the output',
description: ' ',
@@ -265,7 +265,7 @@
description:
'We can now type in the text input and see the result in the display component',
onNextClick: () => {
connectInlineRunnableInputToComponentOutput($app, 'b', 'x', 'a', 'result')
connectInlineRunnableInputToComponentOutput($app, 'e', 'x', 'd', 'result', 'integer')
$app = $app
+171 -1
View File
@@ -1,4 +1,5 @@
import type { FlowModule, OpenFlow } from '$lib/gen'
import { deepEqual } from 'fast-equals'
import { findGridItem } from '../apps/editor/appUtils'
import type { App } from '../apps/types'
@@ -61,8 +62,177 @@ export function isFlowTainted(flow: OpenFlow) {
)
}
const emptyApp = {
grid: [
{
'3': {
fixed: false,
x: 0,
y: 0,
fullHeight: false,
w: 6,
h: 2
},
'12': {
fixed: false,
x: 0,
y: 0,
fullHeight: false,
w: 12,
h: 2
},
data: {
type: 'containercomponent',
configuration: {},
customCss: {
container: {
class: '!p-0',
style: ''
}
},
actions: [],
numberOfSubgrids: 1,
id: 'a'
},
id: 'a'
}
],
fullscreen: false,
unusedInlineScripts: [],
hiddenInlineScripts: [],
theme: {
type: 'path',
path: 'f/app_themes/theme_0'
},
subgrids: {
'a-0': [
{
'3': {
fixed: false,
x: 0,
y: 0,
fullHeight: false,
w: 6,
h: 1
},
'12': {
fixed: false,
x: 0,
y: 0,
fullHeight: false,
w: 6,
h: 1
},
data: {
type: 'textcomponent',
configuration: {
style: {
type: 'static',
value: 'Body'
},
copyButton: {
type: 'static',
value: false
},
tooltip: {
type: 'evalv2',
value: '',
fieldType: 'text',
expr: '`Author: ${ctx.author}`',
connections: [
{
componentId: 'ctx',
id: 'author'
}
]
},
disableNoText: {
type: 'static',
value: true,
fieldType: 'boolean'
}
},
componentInput: {
type: 'templatev2',
fieldType: 'template',
eval: '${ctx.summary}',
connections: [
{
id: 'summary',
componentId: 'ctx'
}
]
},
customCss: {
text: {
class: 'text-xl font-semibold whitespace-nowrap truncate',
style: ''
},
container: {
class: '',
style: ''
}
},
actions: [],
horizontalAlignment: 'left',
verticalAlignment: 'center',
id: 'b'
},
id: 'b'
},
{
'3': {
fixed: false,
x: 0,
y: 1,
fullHeight: false,
w: 3,
h: 1
},
'12': {
fixed: false,
x: 6,
y: 0,
fullHeight: false,
w: 6,
h: 1
},
data: {
type: 'recomputeallcomponent',
configuration: {},
actions: [],
menuItems: [],
horizontalAlignment: 'right',
verticalAlignment: 'center',
id: 'c'
},
id: 'c'
}
]
},
hideLegacyTopBar: true,
norefreshbar: false
}
export function isAppTainted(app: App) {
return !(app.grid.length === 0 && app.hiddenInlineScripts?.length === 0)
if (app.hideLegacyTopBar === true) {
// An empty app should have only have a topbar and no hidden inline scripts
if (Array.isArray(app.hiddenInlineScripts) && app.hiddenInlineScripts?.length > 0) {
return true
}
// New apps have only a single component which is the topbar
if (Array.isArray(app.grid) && app.grid.length > 1) {
return true
}
// Check if the current app is different from an empty app
return !deepEqual(app, emptyApp)
} else {
// For older apps,
return !(app.grid.length === 0 && app.hiddenInlineScripts?.length === 0)
}
}
export function updateFlowModuleById(
+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"
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.374.0"
wmill_pg = ">=1.374.0"
wmill = ">=1.375.0"
wmill_pg = ">=1.375.0"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.374.0
version: 1.375.0
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.374.0'
ModuleVersion = '1.375.0'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.374.0"
version = "1.375.0"
description = "A client library for accessing Windmill server wrapping the Windmill client API"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill-pg"
version = "1.374.0"
version = "1.375.0"
description = "An extension client for the wmill client library focused on pg"
license = "Apache-2.0"
homepage = "https://windmill.dev"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@windmill/windmill",
"version": "1.374.0",
"version": "1.375.0",
"exports": "./src/index.ts",
"publish": {
"exclude": ["!src", "./s3Types.ts", "./client.ts"]
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "windmill-client",
"description": "Windmill SDK client for browsers and Node.js",
"version": "1.374.0",
"version": "1.375.0",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.374.0
1.375.0