Merge branch 'main' into python-uv

This commit is contained in:
pyranota
2024-10-31 08:53:45 +00:00
committed by GitHub
30 changed files with 560 additions and 443 deletions
+2 -1
View File
@@ -461,6 +461,8 @@ jobs:
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,enable=true,priority=100,prefix=,suffix=,format=short
type=ref,event=branch
type=ref,event=pr
- name: Login to registry
uses: docker/login-action@v3
@@ -478,7 +480,6 @@ jobs:
file: "./docker/DockerfileNsjail"
tags: |
${{ steps.meta-ee-public.outputs.tags }}
ghcr.io/windmill-labs/windmill-ee-nsjail:main
labels: |
${{ steps.meta-ee-public.outputs.labels }}
org.opencontainers.image.licenses=Windmill-Enterprise-License
+7
View File
@@ -1,5 +1,12 @@
# Changelog
## [1.414.2](https://github.com/windmill-labs/windmill/compare/v1.414.1...v1.414.2) (2024-10-29)
### Bug Fixes
* **cli:** improve instance sync for CI/CD + --folder-per-instance ([212579a](https://github.com/windmill-labs/windmill/commit/212579a514d070355fe0d9e0215593bacfa05e1f))
## [1.414.1](https://github.com/windmill-labs/windmill/compare/v1.414.0...v1.414.1) (2024-10-29)
+24 -24
View File
@@ -10395,7 +10395,7 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windmill"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"axum",
@@ -10437,7 +10437,7 @@ dependencies = [
[[package]]
name = "windmill-api"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"argon2",
@@ -10522,7 +10522,7 @@ dependencies = [
[[package]]
name = "windmill-api-client"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"base64 0.21.7",
"chrono",
@@ -10540,7 +10540,7 @@ dependencies = [
[[package]]
name = "windmill-audit"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"chrono",
"serde",
@@ -10553,7 +10553,7 @@ dependencies = [
[[package]]
name = "windmill-autoscaling"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"rsmq_async",
@@ -10568,7 +10568,7 @@ dependencies = [
[[package]]
name = "windmill-common"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"async-stream",
@@ -10614,7 +10614,7 @@ dependencies = [
[[package]]
name = "windmill-git-sync"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"regex",
"rsmq_async",
@@ -10629,7 +10629,7 @@ dependencies = [
[[package]]
name = "windmill-indexer"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"bytes",
@@ -10651,7 +10651,7 @@ dependencies = [
[[package]]
name = "windmill-macros"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"itertools 0.13.0",
"lazy_static",
@@ -10663,7 +10663,7 @@ dependencies = [
[[package]]
name = "windmill-parser"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"convert_case 0.6.0",
"serde",
@@ -10672,7 +10672,7 @@ dependencies = [
[[package]]
name = "windmill-parser-bash"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10684,7 +10684,7 @@ dependencies = [
[[package]]
name = "windmill-parser-go"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"gosyn",
@@ -10696,7 +10696,7 @@ dependencies = [
[[package]]
name = "windmill-parser-graphql"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10708,7 +10708,7 @@ dependencies = [
[[package]]
name = "windmill-parser-php"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10719,7 +10719,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"itertools 0.13.0",
@@ -10730,7 +10730,7 @@ dependencies = [
[[package]]
name = "windmill-parser-py-imports"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -10748,7 +10748,7 @@ dependencies = [
[[package]]
name = "windmill-parser-rust"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"convert_case 0.6.0",
@@ -10765,7 +10765,7 @@ dependencies = [
[[package]]
name = "windmill-parser-sql"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10777,7 +10777,7 @@ dependencies = [
[[package]]
name = "windmill-parser-ts"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"lazy_static",
@@ -10795,7 +10795,7 @@ dependencies = [
[[package]]
name = "windmill-parser-wasm"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"getrandom 0.2.15",
@@ -10816,7 +10816,7 @@ dependencies = [
[[package]]
name = "windmill-parser-yaml"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"serde_json",
@@ -10826,7 +10826,7 @@ dependencies = [
[[package]]
name = "windmill-queue"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"async-recursion",
@@ -10859,7 +10859,7 @@ dependencies = [
[[package]]
name = "windmill-sql-datatype-parser-wasm"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"wasm-bindgen",
"wasm-bindgen-test",
@@ -10869,7 +10869,7 @@ dependencies = [
[[package]]
name = "windmill-worker"
version = "1.414.1"
version = "1.414.2"
dependencies = [
"anyhow",
"async-recursion",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "windmill"
version = "1.414.1"
version = "1.414.2"
authors.workspace = true
edition.workspace = true
@@ -29,7 +29,7 @@ members = [
]
[workspace.package]
version = "1.414.1"
version = "1.414.2"
authors = ["Ruben Fiszel <ruben@windmill.dev>"]
edition = "2021"
@@ -61,6 +61,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma
"opensearchpy" => "opensearch-py",
"lokalise" => "python-lokalise-api",
"msgraph" => "msgraph-sdk",
"pythonjsonlogger" => "python-json-logger",
};
fn replace_import(x: String) -> String {
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.414.1
version: 1.414.2
title: Windmill API
contact:
+157 -102
View File
@@ -18,12 +18,8 @@ use serde_json::value::RawValue;
use serde_json::Map;
use serde_json::Value;
use tokio::sync::Mutex;
use tokio_postgres::types::IsNull;
use tokio_postgres::Client;
use tokio_postgres::{
types::{to_sql_checked, ToSql},
NoTls, Row,
};
use tokio_postgres::{types::ToSql, NoTls, Row};
use tokio_postgres::{
types::{FromSql, Type},
Column,
@@ -41,7 +37,7 @@ use windmill_queue::CanceledBy;
use crate::common::{build_args_values, sizeof_val, OccupancyMetrics};
use crate::handle_child::run_future_with_polling_update_job_poller;
use crate::{AuthedClientBackgroundTask, MAX_RESULT_SIZE};
use bytes::{Buf, BytesMut};
use bytes::Buf;
use lazy_static::lazy_static;
use urlencoding::encode;
@@ -98,6 +94,11 @@ fn do_postgresql_inner<'a>(
let mut res: Vec<serde_json::Value> = vec![];
let query_params = query_params
.iter()
.map(|p| &**p as &(dyn ToSql + Sync))
.collect_vec();
if skip_collect {
client
.execute_raw(&query, query_params)
@@ -414,133 +415,176 @@ pub async fn do_postgresql(
return Ok(raw_result);
}
#[derive(Debug)]
enum PgType {
String(String),
Bool(bool),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
U32(u32),
F32(f32),
F64(f64),
Uuid(Uuid),
Decimal(Decimal),
Date(chrono::NaiveDate),
Time(chrono::NaiveTime),
Timestamp(chrono::NaiveDateTime),
None(Option<bool>),
Array(Vec<PgType>),
Json(serde_json::Value),
Bytea(Vec<u8>),
fn map_as_single_type<T>(
vec: &Vec<Value>,
f: impl Fn(&Value) -> Option<T>,
) -> anyhow::Result<Vec<Option<T>>> {
vec.into_iter()
.map(|v| {
// allow nulls in arrays
if matches!(v, Value::Null) {
Some(None)
} else {
f(v).map(Some)
}
})
.collect::<Option<Vec<Option<T>>>>()
.ok_or_else(|| anyhow::anyhow!("Mixed types in array"))
}
impl ToSql for PgType {
fn to_sql(
&self,
ty: &Type,
out: &mut BytesMut,
) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
match *self {
PgType::String(ref val) => val.to_sql(ty, out),
PgType::Bool(ref val) => val.to_sql(ty, out),
PgType::I8(ref val) => val.to_sql(ty, out),
PgType::I16(ref val) => val.to_sql(ty, out),
PgType::I32(ref val) => val.to_sql(ty, out),
PgType::I64(ref val) => val.to_sql(ty, out),
PgType::U32(ref val) => val.to_sql(ty, out),
PgType::F32(ref val) => val.to_sql(ty, out),
PgType::F64(ref val) => val.to_sql(ty, out),
PgType::Uuid(ref val) => val.to_sql(ty, out),
PgType::Decimal(ref val) => val.to_sql(ty, out),
PgType::Date(ref val) => val.to_sql(ty, out),
PgType::Time(ref val) => val.to_sql(ty, out),
PgType::Timestamp(ref val) => val.to_sql(ty, out),
PgType::None(ref val) => val.to_sql(ty, out),
PgType::Array(ref val) => val.to_sql(ty, out),
PgType::Json(ref val) => val.to_sql(ty, out),
PgType::Bytea(ref val) => val.to_sql(ty, out),
fn convert_vec_val(
vec: &Vec<Value>,
arg_t: &String,
) -> windmill_common::error::Result<Box<dyn ToSql + Sync + Send>> {
match arg_t.as_str() {
"bool" | "boolean" => Ok(Box::new(map_as_single_type(vec, |v| v.as_bool())?)),
"char" | "character" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_i64().map(|x| x as i8)
})?)),
"smallint" | "smallserial" | "int2" | "serial2" => {
Ok(Box::new(map_as_single_type(vec, |v| {
v.as_i64().map(|x| x as i16)
})?))
}
"int" | "integer" | "int4" | "serial" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_i64().map(|x| x as i32)
})?)),
"numeric" | "decimal" => Ok(Box::new(map_as_single_type(vec, |v| {
if v.is_i64() {
Decimal::from_i64(v.as_i64().unwrap())
} else if v.is_f64() {
Decimal::from_f64(v.as_f64().unwrap())
} else {
None
}
})?)),
"oid" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_u64().map(|x| x as u32)
})?)),
"bigint" | "bigserial" | "int8" | "serial8" => {
Ok(Box::new(map_as_single_type(vec, |v| {
v.as_u64().map(|x| x as i64)
})?))
}
"real" | "float4" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_f64().map(|x| x as f32)
})?)),
"double" | "float8" => Ok(Box::new(map_as_single_type(vec, |v| v.as_f64())?)),
"uuid" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| Uuid::parse_str(x).ok()).flatten()
})?)),
"date" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| {
chrono::NaiveDate::parse_from_str(x, "%Y-%m-%dT%H:%M:%S.%3fZ").unwrap_or_default()
})
})?)),
"time" | "timetz" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| {
chrono::NaiveTime::parse_from_str(x, "%Y-%m-%dT%H:%M:%S.%3fZ").unwrap_or_default()
})
})?)),
"timestamp" | "timestamptz" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| {
chrono::NaiveDateTime::parse_from_str(x, "%Y-%m-%dT%H:%M:%S.%3fZ")
.unwrap_or_default()
})
})?)),
"jsonb" | "json" => Ok(Box::new(vec.clone().into_iter().map(Some).collect_vec())),
"bytea" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| {
engine::general_purpose::STANDARD
.decode(x)
.unwrap_or(vec![])
})
})?)),
"text" | "varchar" => Ok(Box::new(map_as_single_type(vec, |v| {
v.as_str().map(|x| x.to_string())
})?)),
_ => Err(anyhow::anyhow!("Unsupported JSON array type"))?,
}
fn accepts(_: &Type) -> bool {
true
}
to_sql_checked!();
}
fn convert_val(value: &Value, arg_t: &String, typ: &Typ) -> windmill_common::error::Result<PgType> {
fn convert_val(
value: &Value,
arg_t: &String,
typ: &Typ,
) -> windmill_common::error::Result<Box<dyn ToSql + Sync + Send>> {
match value {
Value::Array(vec) if arg_t.ends_with("[]") => {
let arg_t = arg_t.trim_end_matches("[]").to_string();
let mut result = vec![];
for val in vec {
result.push(convert_val(val, &arg_t, typ)?);
}
Ok(PgType::Array(result))
convert_vec_val(vec, &arg_t)
}
Value::Null => Ok(PgType::None(None::<bool>)),
Value::Bool(b) => Ok(PgType::Bool(b.clone())),
Value::Number(n) if matches!(typ, Typ::Str(_)) => Ok(PgType::String(n.to_string())),
Value::Number(n) if n.is_i64() && arg_t == "char" => {
Ok(PgType::I8(n.as_i64().unwrap() as i8))
}
Value::Number(n) if n.is_i64() && (arg_t == "smallint" || arg_t == "smallserial") => {
Ok(PgType::I16(n.as_i64().unwrap() as i16))
Value::Null => Ok(Box::new(None::<bool>)),
Value::Bool(b) => Ok(Box::new(b.clone())),
Value::Number(n) if matches!(typ, Typ::Str(_)) => Ok(Box::new(n.to_string())),
Value::Number(n) if arg_t == "char" && n.is_i64() => {
Ok(Box::new(n.as_i64().unwrap() as i8))
}
Value::Number(n)
if n.is_i64()
&& (arg_t == "int"
|| arg_t == "integer"
|| arg_t == "int4"
|| arg_t == "serial") =>
if (arg_t == "smallint"
|| arg_t == "smallserial"
|| arg_t == "int2"
|| arg_t == "serial2")
&& n.is_i64() =>
{
Ok(PgType::I32(n.as_i64().unwrap() as i32))
Ok(Box::new(n.as_i64().unwrap() as i16))
}
Value::Number(n) if n.is_i64() && (arg_t == "numeric" || arg_t == "decimal") => Ok(
PgType::Decimal(Decimal::from_i64(n.as_i64().unwrap()).unwrap()),
Value::Number(n)
if (arg_t == "int" || arg_t == "integer" || arg_t == "int4" || arg_t == "serial")
&& n.is_i64() =>
{
Ok(Box::new(n.as_i64().unwrap() as i32))
}
Value::Number(n) if (arg_t == "real" || arg_t == "float4") && n.as_f64().is_some() => {
Ok(Box::new(n.as_f64().unwrap() as f32))
}
Value::Number(n) if (arg_t == "double" || arg_t == "float8") && n.as_f64().is_some() => {
Ok(Box::new(n.as_f64().unwrap()))
}
Value::Number(n) if (arg_t == "numeric" || arg_t == "decimal") && n.is_i64() => Ok(
Box::new(Decimal::from_i64(n.as_i64().unwrap()).unwrap_or_default()),
),
Value::Number(n) if n.is_i64() => Ok(PgType::I64(n.as_i64().unwrap())),
Value::Number(n) if n.is_u64() && arg_t == "oid" => {
Ok(PgType::U32(n.as_u64().unwrap() as u32))
}
Value::Number(n) if n.is_u64() && (arg_t == "bigint" || arg_t == "bigserial") => {
Ok(PgType::I64(n.as_u64().unwrap() as i64))
}
Value::Number(n) if n.is_f64() && arg_t == "real" => {
Ok(PgType::F32(n.as_f64().unwrap() as f32))
}
Value::Number(n) if n.is_f64() && arg_t == "double" => Ok(PgType::F64(n.as_f64().unwrap())),
Value::Number(n) if n.is_f64() && (arg_t == "numeric" || arg_t == "decimal") => Ok(
PgType::Decimal(Decimal::from_f64(n.as_f64().unwrap()).unwrap()),
Value::Number(n) if (arg_t == "numeric" || arg_t == "decimal") && n.is_f64() => Ok(
Box::new(Decimal::from_f64(n.as_f64().unwrap()).unwrap_or_default()),
),
Value::Number(n) => Ok(PgType::F64(n.as_f64().unwrap())),
Value::String(s) if arg_t == "uuid" => Ok(PgType::Uuid(Uuid::parse_str(s)?)),
Value::Number(n) if arg_t == "oid" && n.is_u64() => {
Ok(Box::new(n.as_u64().unwrap() as u32))
}
Value::Number(n)
if (arg_t == "bigint"
|| arg_t == "bigserial"
|| arg_t == "int8"
|| arg_t == "serial8")
&& n.is_u64() =>
{
Ok(Box::new(n.as_u64().unwrap() as i64))
}
Value::Number(n) if n.is_i64() => Ok(Box::new(n.as_i64().unwrap())),
Value::Number(n) => Ok(Box::new(n.as_f64().unwrap())),
Value::String(s) if arg_t == "uuid" => Ok(Box::new(Uuid::parse_str(s)?)),
Value::String(s) if arg_t == "date" => {
let date =
chrono::NaiveDate::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%3fZ").unwrap_or_default();
Ok(PgType::Date(date))
Ok(Box::new(date))
}
Value::String(s) if arg_t == "time" || arg_t == "timetz" => {
let time =
chrono::NaiveTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%3fZ").unwrap_or_default();
Ok(PgType::Time(time))
Ok(Box::new(time))
}
Value::String(s) if arg_t == "timestamp" || arg_t == "timestamptz" => {
let datetime = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S.%3fZ")
.unwrap_or_default();
Ok(PgType::Timestamp(datetime))
Ok(Box::new(datetime))
}
Value::String(s) if arg_t == "bytea" => {
let bytes = engine::general_purpose::STANDARD
.decode(s)
.unwrap_or(vec![]);
Ok(PgType::Bytea(bytes))
Ok(Box::new(bytes))
}
Value::Object(_) => Ok(PgType::Json(value.clone())),
Value::String(s) => Ok(PgType::String(s.clone())),
Value::Object(_) => Ok(Box::new(value.clone())),
Value::String(s) => Ok(Box::new(s.clone())),
_ => Err(Error::ExecutionErr(format!(
"Unsupported type in query: {:?} and signature {arg_t:?}",
value
@@ -624,6 +668,9 @@ pub fn pg_cell_to_json_value(
Type::TS_VECTOR => get_basic(row, column, column_i, |a: StringCollector| {
Ok(JSONValue::String(a.0))
})?,
Type::OID => get_basic(row, column, column_i, |a: u32| {
Ok(JSONValue::Number(serde_json::Number::from(a)))
})?,
// array types
Type::BOOL_ARRAY => get_array(row, column, column_i, |a: bool| Ok(JSONValue::Bool(a)))?,
Type::BIT_ARRAY => get_array(row, column, column_i, |a: bit_vec::BitVec| match a.len() {
@@ -655,6 +702,10 @@ pub fn pg_cell_to_json_value(
Type::FLOAT8_ARRAY => {
get_array(row, column, column_i, |a: f64| Ok(f64_to_json_number(a)?))?
}
Type::NUMERIC_ARRAY => get_array(row, column, column_i, |a: Decimal| {
Ok(serde_json::to_value(a)
.map_err(|_| anyhow::anyhow!("Cannot convert decimal to json"))?)
})?,
// these types require a custom StringCollector struct as an intermediary (see struct at bottom)
Type::TS_VECTOR_ARRAY => get_array(row, column, column_i, |a: StringCollector| {
Ok(JSONValue::String(a.0))
@@ -766,7 +817,7 @@ fn get_array<'a, T: FromSql<'a>>(
val_to_json_val: impl Fn(T) -> Result<JSONValue, Error>,
) -> Result<JSONValue, Error> {
let raw_val_array = row
.try_get::<_, Option<Vec<T>>>(column_i)
.try_get::<_, Option<Vec<Option<T>>>>(column_i)
.with_context(|| {
format!(
"conversion issue for array at column_name `{}`",
@@ -777,7 +828,11 @@ fn get_array<'a, T: FromSql<'a>>(
Some(val_array) => {
let mut result = vec![];
for val in val_array {
result.push(val_to_json_val(val)?);
result.push(
val.map(|v| val_to_json_val(v))
.transpose()?
.unwrap_or(Value::Null),
);
}
JSONValue::Array(result)
}
+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.414.1";
export const VERSION = "v1.414.2";
export async function login(email: string, password: string): Promise<string> {
return await windmill.UserService.login({
+55 -19
View File
@@ -181,6 +181,7 @@ export type InstanceSyncOptions = {
instance?: string;
baseUrl?: string;
token?: string;
folderPerInstance?: boolean;
yes?: boolean;
};
@@ -297,7 +298,11 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
if (opts.includeWorkspaces) {
log.info("\nPulling all workspaces");
const rootDir = Deno.cwd();
const localWorkspaces = await getLocalWorkspaces(rootDir, instance.prefix);
const localWorkspaces = await getLocalWorkspaces(
rootDir,
instance.prefix,
opts.folderPerInstance
);
const previousActiveWorkspace = await getActiveWorkspace(undefined);
const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({
@@ -306,7 +311,9 @@ async function instancePull(opts: GlobalOptions & InstanceSyncOptions) {
});
for (const remoteWorkspace of remoteWorkspaces) {
log.info("\nPulling workspace " + remoteWorkspace.id);
const workspaceName = instance.prefix + "_" + remoteWorkspace.id;
const workspaceName = opts?.folderPerInstance
? instance.prefix + "/" + remoteWorkspace.id
: instance.prefix + "_" + remoteWorkspace.id;
await Deno.mkdir(path.join(rootDir, workspaceName), {
recursive: true,
});
@@ -425,16 +432,21 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
instances = await allInstances();
const rootDir = Deno.cwd();
const localPrefix = (await Select.prompt({
message: "What is the prefix of the local workspaces you want to sync?",
options: [
...instances.map((i) => ({
name: `${i.prefix} (${i.name} - ${i.remote})`,
value: i.prefix,
})),
],
default: instance.prefix as unknown,
})) as unknown as string;
let localPrefix;
if (opts.baseUrl && opts.token) {
localPrefix = "custom";
} else {
localPrefix = (await Select.prompt({
message: "What is the prefix of the local workspaces you want to sync?",
options: [
...instances.map((i) => ({
name: `${i.prefix} (${i.name} - ${i.remote})`,
value: i.prefix,
})),
],
default: instance.prefix as unknown,
})) as unknown as string;
}
const remoteWorkspaces = await wmill.listWorkspacesAsSuperAdmin({
page: 1,
@@ -443,7 +455,11 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
const previousActiveWorkspace = await getActiveWorkspace(undefined);
const localWorkspaces = await getLocalWorkspaces(rootDir, localPrefix);
const localWorkspaces = await getLocalWorkspaces(
rootDir,
localPrefix,
opts.folderPerInstance
);
log.info(
`\nPushing all workspaces: ${localWorkspaces.map((x) => x.id).join(", ")}`
@@ -521,17 +537,35 @@ async function instancePush(opts: GlobalOptions & InstanceSyncOptions) {
}
}
async function getLocalWorkspaces(rootDir: string, localPrefix: string) {
async function getLocalWorkspaces(
rootDir: string,
localPrefix: string,
folderPerInstance?: boolean
) {
const localWorkspaces: { dir: string; id: string }[] = [];
for await (const dir of Deno.readDir(rootDir)) {
const dirName = dir.name;
if (dirName.startsWith(localPrefix + "_")) {
if (!(await Deno.stat(localPrefix).catch(() => null))) {
await Deno.mkdir(localPrefix);
}
if (folderPerInstance) {
for await (const dir of Deno.readDir(rootDir + "/" + localPrefix)) {
const dirName = dir.name;
localWorkspaces.push({
dir: dirName,
id: dirName.substring(localPrefix.length + 1),
dir: localPrefix + "/" + dirName,
id: dirName,
});
}
log.info(localWorkspaces);
} else {
for await (const dir of Deno.readDir(rootDir)) {
const dirName = dir.name;
if (dirName.startsWith(localPrefix + "_")) {
localWorkspaces.push({
dir: dirName,
id: dirName.substring(localPrefix.length + 1),
});
}
}
}
return localWorkspaces;
}
@@ -650,6 +684,7 @@ const command = new Command()
.option("--skip-configs", "Skip pulling configs (worker groups and SMTP)")
.option("--skip-groups", "Skip pulling instance groups")
.option("--include-workspaces", "Also pull workspaces")
.option("--folder-per-instance", "Create a folder per instance")
.option(
"--instance <instance:string>",
"Name of the instance to push to, override the active instance"
@@ -665,6 +700,7 @@ const command = new Command()
.option("--skip-configs", "Skip pushing configs (worker groups and SMTP)")
.option("--skip-groups", "Skip pushing instance groups")
.option("--include-workspaces", "Also push workspaces")
.option("--folder-per-instance", "Create a folder per instance")
.option(
"--instance <instance:string>",
"Name of the instance to push to, override the active instance"
-1
View File
@@ -1,4 +1,3 @@
import { log } from "./deps.ts";
import crypto from "node:crypto";
// Helper function to convert strings to Uint8Array (binary)
+1 -1
View File
@@ -60,7 +60,7 @@ export {
// }
// });
export const VERSION = "1.414.1";
export const VERSION = "1.414.2";
const command = new Command()
.name("wmill")
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "windmill-components",
"version": "1.414.1",
"version": "1.414.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "windmill-components",
"version": "1.414.1",
"version": "1.414.2",
"license": "AGPL-3.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "windmill-components",
"version": "1.414.1",
"version": "1.414.2",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@@ -6,7 +6,7 @@
import AppConnectInner from './AppConnectInner.svelte'
import DarkModeObserver from './DarkModeObserver.svelte'
let expressOAuthSetup = false
export let expressOAuthSetup = false
let drawer: Drawer
let resourceType = ''
@@ -18,8 +18,7 @@
let appConnectInner: AppConnectInner | undefined = undefined
let rtToLoad: string | undefined = ''
export async function open(rt?: string, express?: boolean) {
expressOAuthSetup = express ?? false
export async function open(rt?: string) {
rtToLoad = rt
drawer.openDrawer?.()
}
@@ -27,7 +26,7 @@
$: appConnectInner && onRtToLoadChange(rtToLoad)
function onRtToLoadChange(rtToLoad: string | undefined) {
appConnectInner?.open(rtToLoad, expressOAuthSetup)
appConnectInner?.open(rtToLoad)
}
const dispatch = createEventDispatcher()
@@ -60,6 +59,7 @@
bind:manual
on:close={drawer?.closeDrawer}
on:refresh
express={expressOAuthSetup}
/>
<div slot="actions" class="flex gap-1">
{#if step > 1}
+247 -248
View File
@@ -31,6 +31,7 @@
export let isGoogleSignin = false
export let disabled = false
export let manual = true
export let express = false
let isValid = true
@@ -81,10 +82,7 @@
let pathError = ''
let expressOAuthSetup = false
export async function open(rt?: string, express?: boolean) {
expressOAuthSetup = express ?? false
export async function open(rt?: string) {
if (!rt) {
loadResourceTypes()
}
@@ -95,12 +93,12 @@
valueToken = undefined
await loadConnects()
manual = !connects?.includes(resourceType)
if (manual && expressOAuthSetup) {
if (manual && express) {
dispatch('error', 'Express OAuth setup is not available for non OAuth resource types')
return
}
if (rt) {
if (!manual && expressOAuthSetup) {
if (!manual && express) {
await getScopesAndParams()
step = 2
}
@@ -171,7 +169,6 @@
}
function popupListener(event) {
console.log('popupListener', event.data, event.origin, window.location.origin)
let data = event.data
if (event.origin == null || event.origin !== window.location.origin) {
return
@@ -187,7 +184,7 @@
value = data.res.access_token!
valueToken = data.res
step = 4
if (expressOAuthSetup) {
if (express) {
path = `u/${$userStore?.username}/${resourceType}_${new Date().getTime()}`
next()
}
@@ -329,252 +326,254 @@
let editScopes = false
</script>
<SearchItems
{filter}
items={connects
? connects
.sort((a, b) => a.localeCompare(b))
.map((key) => ({
key
}))
: undefined}
bind:filteredItems={filteredConnects}
f={(x) => x.key}
/>
<SearchItems
{filter}
items={connectsManual?.sort((a, b) => a[0].localeCompare(b[0]))}
bind:filteredItems={filteredConnectsManual}
f={(x) => x[0]}
/>
{#if step == 1}
<div class="w-12/12 pb-2 flex flex-row my-1 gap-1">
<input
type="text"
placeholder="Search resource type"
bind:value={filter}
class="text-2xl grow"
/>
</div>
<h2 class="mb-4">OAuth APIs</h2>
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnects}
{#each filteredConnects as { key }}
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = false
resourceType = key
next()
}}
>
<IconedResourceType name={key} after={true} width="20px" height="20px" />
</Button>
{/each}
{:else}
{#each new Array(3) as _}
<Skeleton layout={[[2]]} />
{/each}
{/if}
</div>
{#if connects && connects.length == 0}
<div class="text-secondary text-sm w-full"
>No OAuth APIs has been setup on the instance. To add oauth APIs, first sync the resource
types with the hub, then add oauth configuration. See <a
href="https://www.windmill.dev/docs/misc/setup_oauth">documentation</a
>
</div>
{/if}
<h2 class="mt-8 mb-4">Others</h2>
{#if connectsManual && connectsManual?.length < 10}
<div class="text-secondary p-2">
Resource Types have not been synced with the hub. Go to the admins workspace to sync them (and
add a schedule to do daily):
<p class="mt-4"
>1. Go to the "admins" workspaces:
<img src="{base}/sync_resource_types.png" alt="sync resource types" class="mt-2" />
</p>
<p class="mt-4">
2: Run the synchronization script:
<img src="{base}/sync_resource_types2.png" alt="sync resource types" class="mt-2" />
</p>
</div>
{/if}
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnectsManual}
{#each filteredConnectsManual as [key, _]}
{#if nativeLanguagesCategory.includes(key)}
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = true
resourceType = key
next()
}}
>
<IconedResourceType name={key} after={true} width="20px" height="20px" />
</Button>
{/if}
{/each}
{/if}
</div>
<div class="mt-8 mb-4" />
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnectsManual}
{#each filteredConnectsManual as [key, _]}
{#if !nativeLanguagesCategory.includes(key)}
<!-- Exclude specific items -->
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = true
resourceType = key
next()
}}
>
<IconedResourceType name={key} after={true} width="20px" height="20px" />
</Button>
{/if}
{/each}
{:else}
{#each new Array(9) as _}
<Skeleton layout={[[2]]} />
{/each}
{/if}
</div>
{:else if step == 2 && manual}
<Path
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder={resourceType}
kind="resource"
{#if !express}
<SearchItems
{filter}
items={connects
? connects
.sort((a, b) => a.localeCompare(b))
.map((key) => ({
key
}))
: undefined}
bind:filteredItems={filteredConnects}
f={(x) => x.key}
/>
{#if apiTokenApps[resourceType]}
<h2 class="mt-4 mb-2">Instructions</h2>
<div class="pl-10">
<ol class="list-decimal">
{#each apiTokenApps[resourceType].instructions as step}
<li>
{@html step}
</li>
{/each}
</ol>
</div>
{#if apiTokenApps[resourceType].img}
<div class="mt-4 w-full overflow-hidden">
<img class="m-auto max-h-60" alt="connect" src={base + apiTokenApps[resourceType].img} />
</div>
{/if}
{:else if !emptyString(resourceTypeInfo?.description)}
<h4 class="mt-8 mb-2">{resourceTypeInfo?.name} description</h4>
<div class="text-sm">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
{/if}
{#if resourceType == 'postgresql' || resourceType == 'mysql' || resourceType == 'mongodb'}
<WhitelistIp />
{/if}
<h4 class="mt-8 inline-flex items-center gap-4"
>Resource description <Required required={false} />
<div class="flex gap-1 items-center">
<Toggle size="xs" bind:checked={renderDescription} />
<Pen size={14} />
</div>
</h4>
{#if renderDescription}
<div>
<div class="flex flex-row-reverse text-2xs text-tertiary -mt-1">GH Markdown</div>
<textarea use:autosize bind:value={description} placeholder={'Resource description'} />
</div>
{:else if description == undefined || description == ''}
<div class="text-sm text-tertiary">No description provided</div>
{:else}
<div class="mt-2" />
<GfmMarkdown md={description} />
{/if}
<div class="mt-12">
{#key resourceTypeInfo}
<ApiConnectForm
bind:linkedSecret
{linkedSecretCandidates}
{resourceType}
{resourceTypeInfo}
bind:args
bind:isValid
<SearchItems
{filter}
items={connectsManual?.sort((a, b) => a[0].localeCompare(b[0]))}
bind:filteredItems={filteredConnectsManual}
f={(x) => x[0]}
/>
{#if step == 1}
<div class="w-12/12 pb-2 flex flex-row my-1 gap-1">
<input
type="text"
placeholder="Search resource type"
bind:value={filter}
class="text-2xl grow"
/>
{/key}
</div>
{:else if step == 2 && !manual}
{#if manual == false && resourceType != ''}
<h1 class="mb-4">{resourceType}</h1>
<div class="my-4 text-secondary"
>Click connect to create a resource backed by an oauth connection, whose token is fetched from
the external services and refreshed automatically if needed before expiration (using its
refresh token)</div
>
<h4 class="mb-2">Description</h4>
<div class="text-sm mb-8">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
<h3 class="mb-4 flex gap-4"
>Scopes <button
on:click={() => {
editScopes = !editScopes
}}><Pen size={14} /></button
></h3
>
{#if editScopes}
<OauthScopes bind:scopes />
{:else}
<div class="flex flex-col gap-1">
{#each scopes as scope}
<div class="py-0.5 pl-2 text-xs">- {scope}</div>
<h2 class="mb-4">OAuth APIs</h2>
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnects}
{#each filteredConnects as { key }}
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = false
resourceType = key
next()
}}
>
<IconedResourceType name={key} after={true} width="20px" height="20px" />
</Button>
{/each}
{:else}
{#each new Array(3) as _}
<Skeleton layout={[[2]]} />
{/each}
{/if}
</div>
{#if connects && connects.length == 0}
<div class="text-secondary text-sm w-full"
>No OAuth APIs has been setup on the instance. To add oauth APIs, first sync the resource
types with the hub, then add oauth configuration. See <a
href="https://www.windmill.dev/docs/misc/setup_oauth">documentation</a
>
</div>
{/if}
{/if}
{:else if step == 3 && !manual}
Finish connection in popup window
{:else}
<Path
initialPath=""
namePlaceholder={resourceType}
bind:error={pathError}
bind:path
kind="resource"
/>
{#if apiTokenApps[resourceType] || !manual}
<ul class="mt-10">
<li>
1. A secret variable containing the {apiTokenApps[resourceType]?.linkedSecret ?? 'token'}
<span class="font-bold">{truncateRev(value, 5, '*****')}</span>
will be stored a
<span class="font-mono whitespace-nowrap">{path}</span>.
</li>
<li class="mt-4">
2. The resource containing that token will be stored at the same path <span
class="font-mono whitespace-nowrap">{path}</span
>. The Variable and Resource will be "linked together", they will be deleted and renamed
together.
</li></ul
>
<h2 class="mt-8 mb-4">Others</h2>
{#if connectsManual && connectsManual?.length < 10}
<div class="text-secondary p-2">
Resource Types have not been synced with the hub. Go to the admins workspace to sync them
(and add a schedule to do daily):
<p class="mt-4"
>1. Go to the "admins" workspaces:
<img src="{base}/sync_resource_types.png" alt="sync resource types" class="mt-2" />
</p>
<p class="mt-4">
2: Run the synchronization script:
<img src="{base}/sync_resource_types2.png" alt="sync resource types" class="mt-2" />
</p>
</div>
{/if}
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnectsManual}
{#each filteredConnectsManual as [key, _]}
{#if nativeLanguagesCategory.includes(key)}
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = true
resourceType = key
next()
}}
>
<IconedResourceType name={key} after={true} width="20px" height="20px" />
</Button>
{/if}
{/each}
{/if}
</div>
<div class="mt-8 mb-4" />
<div class="grid sm:grid-cols-2 md:grid-cols-3 gap-x-2 gap-y-1 items-center mb-2">
{#if filteredConnectsManual}
{#each filteredConnectsManual as [key, _]}
{#if !nativeLanguagesCategory.includes(key)}
<!-- Exclude specific items -->
<Button
size="sm"
variant="border"
color={key === resourceType ? 'blue' : 'light'}
btnClasses={key === resourceType ? '!border-2' : 'm-[1px]'}
on:click={() => {
manual = true
resourceType = key
next()
}}
>
<IconedResourceType name={key} after={true} width="20px" height="20px" />
</Button>
{/if}
{/each}
{:else}
{#each new Array(9) as _}
<Skeleton layout={[[2]]} />
{/each}
{/if}
</div>
{:else if step == 2 && manual}
<Path
bind:error={pathError}
bind:path
initialPath=""
namePlaceholder={resourceType}
kind="resource"
/>
{#if apiTokenApps[resourceType]}
<h2 class="mt-4 mb-2">Instructions</h2>
<div class="pl-10">
<ol class="list-decimal">
{#each apiTokenApps[resourceType].instructions as step}
<li>
{@html step}
</li>
{/each}
</ol>
</div>
{#if apiTokenApps[resourceType].img}
<div class="mt-4 w-full overflow-hidden">
<img class="m-auto max-h-60" alt="connect" src={base + apiTokenApps[resourceType].img} />
</div>
{/if}
{:else if !emptyString(resourceTypeInfo?.description)}
<h4 class="mt-8 mb-2">{resourceTypeInfo?.name} description</h4>
<div class="text-sm">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
{/if}
{#if resourceType == 'postgresql' || resourceType == 'mysql' || resourceType == 'mongodb'}
<WhitelistIp />
{/if}
<h4 class="mt-8 inline-flex items-center gap-4"
>Resource description <Required required={false} />
<div class="flex gap-1 items-center">
<Toggle size="xs" bind:checked={renderDescription} />
<Pen size={14} />
</div>
</h4>
{#if renderDescription}
<div>
<div class="flex flex-row-reverse text-2xs text-tertiary -mt-1">GH Markdown</div>
<textarea use:autosize bind:value={description} placeholder={'Resource description'} />
</div>
{:else if description == undefined || description == ''}
<div class="text-sm text-tertiary">No description provided</div>
{:else}
<div class="mt-2" />
<GfmMarkdown md={description} />
{/if}
<div class="mt-12">
{#key resourceTypeInfo}
<ApiConnectForm
bind:linkedSecret
{linkedSecretCandidates}
{resourceType}
{resourceTypeInfo}
bind:args
bind:isValid
/>
{/key}
</div>
{:else if step == 2 && !manual}
{#if manual == false && resourceType != ''}
<h1 class="mb-4">{resourceType}</h1>
<div class="my-4 text-secondary"
>Click connect to create a resource backed by an oauth connection, whose token is fetched
from the external services and refreshed automatically if needed before expiration (using
its refresh token)</div
>
<h4 class="mb-2">Description</h4>
<div class="text-sm mb-8">
<Markdown md={urlize(resourceTypeInfo?.description ?? '', 'md')} />
</div>
<h3 class="mb-4 flex gap-4"
>Scopes <button
on:click={() => {
editScopes = !editScopes
}}><Pen size={14} /></button
></h3
>
{#if editScopes}
<OauthScopes bind:scopes />
{:else}
<div class="flex flex-col gap-1">
{#each scopes as scope}
<div class="py-0.5 pl-2 text-xs">- {scope}</div>
{/each}
</div>
{/if}
{/if}
{:else if step == 3 && !manual && !express}
Finish connection in popup window
{:else}
<Path
initialPath=""
namePlaceholder={resourceType}
bind:error={pathError}
bind:path
kind="resource"
/>
{#if apiTokenApps[resourceType] || !manual}
<ul class="mt-10">
<li>
1. A secret variable containing the {apiTokenApps[resourceType]?.linkedSecret ?? 'token'}
<span class="font-bold">{truncateRev(value, 5, '*****')}</span>
will be stored a
<span class="font-mono whitespace-nowrap">{path}</span>.
</li>
<li class="mt-4">
2. The resource containing that token will be stored at the same path <span
class="font-mono whitespace-nowrap">{path}</span
>. The Variable and Resource will be "linked together", they will be deleted and renamed
together.
</li></ul
>
{/if}
{/if}
{/if}
@@ -23,7 +23,7 @@
onMount(async () => {
if (resourceType) {
appConnect?.open(resourceType, express)
appConnect?.open(resourceType)
}
})
</script>
@@ -31,24 +31,27 @@
<DarkModeObserver bind:darkMode />
<div>
<div class="flex flex-row-reverse w-full">
<div class="flex gap-2">
{#if step > 2}
<Button variant="border" on:click={appConnect?.back}>Back</Button>
{/if}
<Button {disabled} on:click={appConnect?.next}>
{#if step == 2 && !manual}
Connect
{:else if step == 1}
Next
{:else}
Save
{#if !express}
<div class="flex flex-row-reverse w-full pb-2">
<div class="flex gap-2">
{#if step > 2}
<Button variant="border" on:click={appConnect?.back}>Back</Button>
{/if}
</Button>
<Button {disabled} on:click={appConnect?.next}>
{#if step == 2 && !manual}
Connect
{:else if step == 1}
Next
{:else}
Save
{/if}
</Button>
</div>
</div>
</div>
{/if}
<AppConnectInner
{express}
bind:this={appConnect}
bind:step
bind:resourceType
@@ -50,6 +50,11 @@
nc.push({ value: value ?? initialValue!, label: value ?? initialValue! })
}
collection = nc
if (expressOAuthSetup && nc.length > 0) {
value = nc[0].value
valueSelect = nc[0]
}
}
$: $workspaceStore && loadResources(resourceType)
@@ -71,13 +76,15 @@
{#if expressOAuthSetup}
{#if open}
{#key refreshCount}
{#await import('./AppConnectLightweightResourcePicker.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
{#await import('./AppConnectLightweightResourcePicker.svelte') then Module}
<Module.default
workspace={appViewerContext?.workspace ?? $workspaceStore}
{resourceType}
express={true}
on:error={(e) => {
sendUserToast(e.detail, true)
open = false
}}
/>
{/await}
{/key}
@@ -40,7 +40,7 @@
let collection = valueSelect ? [valueSelect] : []
export async function askNewResource() {
appConnect?.open?.(resourceType, expressOAuthSetup)
appConnect?.open?.(resourceType)
}
let loading = true
@@ -101,6 +101,7 @@
}
}}
bind:this={appConnect}
{expressOAuthSetup}
/>
<ResourceEditorDrawer
@@ -178,7 +179,7 @@
color="light"
variant="border"
size="sm"
on:click={() => appConnect?.open?.(resourceType, expressOAuthSetup)}
on:click={() => appConnect?.open?.(resourceType)}
startIcon={{ icon: Plus }}
iconOnly={collection?.length > 0}
>{#if collection?.length == 0}
@@ -279,7 +279,11 @@
// Handle through confirmation modal
confirmCallback = async () => {
open = false
await editScript(stay, actual_parent_hash, deployMsg)
if (actual_parent_hash) {
await editScript(stay, actual_parent_hash, deployMsg)
} else {
sendUserToast('Could not fetch latest version of the script', true)
}
}
// Open confirmation modal
open = true
@@ -250,7 +250,6 @@
<ToggleButtonGroup
selected={super_admin}
on:selected={async (e) => {
console.log('BAR')
if (email == $userStore?.email) {
sendUserToast('You cannot demote yourself', true)
listUsers(activeOnly)
+6 -2
View File
@@ -65,8 +65,8 @@ export const POSTGRES_TYPES = [
'BIGSERIAL[]',
'REAL',
'REAL[]',
'DOUBLE PRECISION',
'DOUBLE PRECISION[]',
'FLOAT8',
'FLOAT8[]',
'NUMERIC',
'NUMERIC[]',
'DECIMAL',
@@ -77,8 +77,12 @@ export const POSTGRES_TYPES = [
'DATE[]',
'TIME',
'TIME[]',
'TIMETZ',
'TIMETZ[]',
'TIMESTAMP',
'TIMESTAMP[]',
'TIMESTAMPTZ',
'TIMESTAMPTZ[]',
'JSON',
'JSON[]',
'JSONB',
+3 -2
View File
@@ -1,10 +1,11 @@
{
"gitSync": "hub/9087/sync-script-to-git-repo-windmill",
"gitSyncTest": "hub/9073/git-repo-test-read-write-windmill",
"slackErrorHandler": "hub/9079/workspace-or-schedule-error-handler-slack",
"slackErrorHandler": "hub/9206/workspace-or-schedule-error-handler-slack",
"slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack",
"slackRecoveryHandler": "hub/9080/slack/schedule-recovery-handler-slack",
"slackSuccessHandler": "hub/9072/slack/schedule-success-handler-slack",
"slackReport": "hub/9084/slack",
"discordReport": "hub/9085/discord",
"smtpReport": "hub/9086/smtp"
}
}
+2 -2
View File
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
wmill = ">=1.414.1"
wmill_pg = ">=1.414.1"
wmill = ">=1.414.2"
wmill_pg = ">=1.414.2"
sendgrid = "*"
mysql-connector-python = "*"
pymongo = "*"
+1 -1
View File
@@ -1,7 +1,7 @@
openapi: "3.0.3"
info:
version: 1.414.1
version: 1.414.2
title: OpenFlow Spec
contact:
name: Ruben Fiszel
@@ -12,7 +12,7 @@
RootModule = 'WindmillClient.psm1'
# Version number of this module.
ModuleVersion = '1.414.1'
ModuleVersion = '1.414.2'
# Supported PSEditions
# CompatiblePSEditions = @()
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "wmill"
version = "1.414.1"
version = "1.414.2"
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.414.1"
version = "1.414.2"
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.414.1",
"version": "1.414.2",
"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.414.1",
"version": "1.414.2",
"author": "Ruben Fiszel",
"license": "Apache 2.0",
"devDependencies": {
+1 -1
View File
@@ -1 +1 @@
1.414.1
1.414.2