Compare commits
9 Commits
rf/pythonI
...
alp/update
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9d194894d | ||
|
|
503930ff56 | ||
|
|
3529625774 | ||
|
|
045307ab41 | ||
|
|
e437764f5e | ||
|
|
6b3881bd00 | ||
|
|
410fb25309 | ||
|
|
804fd3468c | ||
|
|
e27a97dfbb |
@@ -6937,6 +6937,43 @@ paths:
|
||||
- resume
|
||||
- cancel
|
||||
|
||||
/w/{workspace}/jobs/slack_approval/{id}/{resume_id}:
|
||||
get:
|
||||
summary: get interactive slack approval payload given the job_id, resume_id and a nonce to resume a flow
|
||||
operationId: getSlackApprovalPayload
|
||||
tags:
|
||||
- job
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
- $ref: "#/components/parameters/JobId"
|
||||
- name: resume_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- name: approver
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
- name: message
|
||||
in: query
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Blocks array for posting interactive slack approval
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
blocks:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- blocks
|
||||
|
||||
/w/{workspace}/jobs_u/resume/{id}/{resume_id}/{signature}:
|
||||
get:
|
||||
summary: resume a job for a suspended flow
|
||||
|
||||
@@ -14,6 +14,7 @@ use serde_json::value::RawValue;
|
||||
use sqlx::Pool;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::str::FromStr;
|
||||
#[cfg(feature = "prometheus")]
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::io::AsyncReadExt;
|
||||
@@ -2316,7 +2317,7 @@ fn create_signature(
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Serialize)]
|
||||
#[derive(Serialize, Debug)]
|
||||
pub struct ResumeUrls {
|
||||
approvalPage: String,
|
||||
cancel: String,
|
||||
@@ -5507,3 +5508,553 @@ async fn delete_completed_job<'a>(
|
||||
let response = Json(cj).into_response();
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
use axum::extract::Form;
|
||||
use regex::Regex;
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SlackFormData {
|
||||
payload: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Payload {
|
||||
actions: Vec<Action>,
|
||||
state: State,
|
||||
response_url: Option<String>,
|
||||
message: Message,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Message {
|
||||
blocks: Option<Vec<Value>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Action {
|
||||
value: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct State {
|
||||
values: HashMap<String, HashMap<String, ValueInput>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
enum ValueInput {
|
||||
PlainTextInput { value: Option<Value> },
|
||||
Datepicker { selected_date: Option<String> },
|
||||
Timepicker { selected_time: Option<String> },
|
||||
StaticSelect { selected_option: Option<SelectedOption> },
|
||||
RadioButtons { selected_option: Option<SelectedOption> },
|
||||
Checkboxes { selected_options: Option<Vec<SelectedOption>> },
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct SelectedOption {
|
||||
value: String,
|
||||
}
|
||||
|
||||
pub async fn slack_app_callback_handler(
|
||||
authed: Option<ApiAuthed>,
|
||||
Extension(db): Extension<DB>,
|
||||
Form(form_data): Form<SlackFormData>,
|
||||
) -> error::Result<StatusCode> {
|
||||
tracing::debug!("Form data: {:?}", form_data);
|
||||
let payload: Payload = serde_json::from_str(&form_data.payload)?;
|
||||
|
||||
let action_value = payload.actions[0].value.clone();
|
||||
let response_url = payload.response_url;
|
||||
|
||||
let re = Regex::new(r"/api/w/(?P<w_id>[^/]+)/jobs_u/(?P<action>resume|cancel)/(?P<job_id>[^/]+)/(?P<resume_id>[^/]+)/(?P<secret>[a-fA-F0-9]+)(?:\?approver=(?P<approver>[^&]+))?").unwrap();
|
||||
|
||||
if let Some(captures) = re.captures(&action_value) {
|
||||
let w_id = captures.name("w_id").map_or("", |m| m.as_str());
|
||||
let action = captures.name("action").map_or("", |m| m.as_str());
|
||||
let job_id = captures.name("job_id").map_or("", |m| m.as_str());
|
||||
let resume_id = captures.name("resume_id").map_or("", |m| m.as_str());
|
||||
let secret = captures.name("secret").map_or("", |m| m.as_str());
|
||||
let approver =
|
||||
QueryApprover { approver: captures.name("approver").map(|m| m.as_str().to_string()) };
|
||||
|
||||
tracing::debug!("Request: {:?}", &form_data.payload.clone());
|
||||
|
||||
let state_values: HashMap<String, serde_json::Value> = payload
|
||||
.state
|
||||
.values
|
||||
.iter()
|
||||
.flat_map(|(_, inputs)| {
|
||||
inputs.iter().filter_map(|(action_id, input)| {
|
||||
if action_id.ends_with("_date") {
|
||||
let base_key = action_id.strip_suffix("_date").unwrap();
|
||||
let time_key = format!("{}_time", base_key);
|
||||
|
||||
// Check for Datepicker and Timepicker inputs specifically
|
||||
if let ValueInput::Datepicker { selected_date: Some(date) } = input {
|
||||
let matching_time = payload.state.values.values().flat_map(|inputs| {
|
||||
inputs.get(&time_key).and_then(|time_input| {
|
||||
if let ValueInput::Timepicker { selected_time: Some(time) } = time_input {
|
||||
Some(time)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}).next();
|
||||
|
||||
if let Some(time) = matching_time {
|
||||
return Some((
|
||||
base_key.to_string(),
|
||||
serde_json::json!(format!("{}T{}:00.000Z", date, time)),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process non-datetime inputs, including plain text or other types with `_date`
|
||||
match input {
|
||||
ValueInput::PlainTextInput { value } => {
|
||||
value.as_ref().map(|v| (action_id.clone(), v.clone().into()))
|
||||
}
|
||||
ValueInput::StaticSelect { selected_option } => selected_option
|
||||
.as_ref()
|
||||
.map(|so| (action_id.clone(), serde_json::json!(so.value))),
|
||||
ValueInput::RadioButtons { selected_option } => selected_option
|
||||
.as_ref()
|
||||
.map(|so| (action_id.clone(), serde_json::json!(so.value))),
|
||||
ValueInput::Checkboxes { selected_options } => {
|
||||
selected_options.as_ref().map(|so| {
|
||||
(
|
||||
action_id.clone(),
|
||||
serde_json::json!(so
|
||||
.iter()
|
||||
.map(|option| option.value.clone())
|
||||
.collect::<Vec<_>>()),
|
||||
)
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
let state_json = serde_json::to_value(state_values)
|
||||
.unwrap_or_else(|_| serde_json::json!({}));
|
||||
|
||||
tracing::debug!("W ID: {}", w_id);
|
||||
tracing::debug!("Action: {}", action);
|
||||
tracing::debug!("Job ID: {}", job_id);
|
||||
tracing::debug!("Resume ID: {}", resume_id);
|
||||
tracing::debug!("Secret: {}", secret);
|
||||
tracing::debug!("Approver: {:?}", approver.approver);
|
||||
tracing::debug!("State JSON: {:?}", state_json);
|
||||
|
||||
let res = resume_suspended_job_internal(
|
||||
Some(state_json),
|
||||
db,
|
||||
w_id.to_string(),
|
||||
Uuid::from_str(job_id).unwrap_or_default(),
|
||||
resume_id.parse::<u32>().unwrap_or_default(),
|
||||
approver,
|
||||
secret.to_string(),
|
||||
authed,
|
||||
action == "resume",
|
||||
)
|
||||
.await;
|
||||
|
||||
tracing::debug!("Res: {:?}", res);
|
||||
|
||||
if let Some(url) = response_url {
|
||||
let message = if action == "resume" {
|
||||
"\n\n*Workflow has been resumed!*"
|
||||
} else {
|
||||
"\n\n*Workflow has been canceled!*"
|
||||
};
|
||||
let _ = post_slack_response(&url, message).await;
|
||||
}
|
||||
} else {
|
||||
tracing::error!("Resume URL does not match the pattern.");
|
||||
}
|
||||
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct QueryMessage {
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn request_slack_approval(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path((w_id, job_id, resume_id)): Path<(String, Uuid, u32)>,
|
||||
Query(approver): Query<QueryApprover>,
|
||||
Query(message): Query<QueryMessage>,
|
||||
) -> windmill_common::error::JsonResult<serde_json::Value> {
|
||||
|
||||
let res = get_resume_urls(
|
||||
authed,
|
||||
axum::Extension(db.clone()),
|
||||
Path((w_id, job_id, resume_id)),
|
||||
axum::extract::Query(approver),
|
||||
)
|
||||
.await;
|
||||
|
||||
let schema: Option<ResumeFormRow> = sqlx::query_as!(
|
||||
ResumeFormRow,
|
||||
"SELECT
|
||||
module.value->'suspend'->'resume_form' AS resume_form,
|
||||
(module.value->'suspend'->>'hide_cancel')::boolean AS hide_cancel
|
||||
FROM
|
||||
job
|
||||
LEFT JOIN
|
||||
queue ON job.id = queue.parent_job
|
||||
LEFT JOIN
|
||||
jsonb_array_elements(job.raw_flow->'modules') AS module
|
||||
ON module->>'id' = queue.flow_step_id
|
||||
WHERE
|
||||
queue.id = $1",
|
||||
job_id
|
||||
)
|
||||
.fetch_optional(&db)
|
||||
.await?;
|
||||
|
||||
tracing::debug!("schema: {:?}", schema);
|
||||
tracing::debug!("job_id: {:?}", job_id);
|
||||
|
||||
let message_str = message.message.as_deref().unwrap_or("*A workflow has been suspended and is waiting for approval:*\n");
|
||||
|
||||
if let Some(resume_schema) = schema {
|
||||
let hide_cancel = resume_schema.hide_cancel.unwrap_or(false);
|
||||
|
||||
let schema_obj = match resume_schema.resume_form {
|
||||
Some(schema) => schema,
|
||||
None => {
|
||||
tracing::debug!("No suspend form found!");
|
||||
return transform_schemas(message_str, None, &res.unwrap().0, None, hide_cancel)
|
||||
.await
|
||||
.map(Json);
|
||||
}
|
||||
};
|
||||
|
||||
let inner_schema = schema_obj
|
||||
.get("schema")
|
||||
.ok_or_else(|| Error::BadRequest("Schema object is missing the 'schema' field!".to_string()))?;
|
||||
|
||||
let order_value = inner_schema
|
||||
.get("order")
|
||||
.ok_or_else(|| Error::BadRequest("Schema does not contain order field!".to_string()))?;
|
||||
|
||||
let order: Vec<String> = serde_json::from_value(order_value.clone())
|
||||
.map_err(|e| {
|
||||
tracing::error!("Failed to deserialize order: {:?}", e);
|
||||
Error::BadRequest("Failed to deserialize order!".to_string())
|
||||
})?;
|
||||
|
||||
let properties_value = inner_schema
|
||||
.get("properties")
|
||||
.ok_or_else(|| Error::BadRequest("Schema does not contain properties field!".to_string()))?;
|
||||
let properties: HashMap<String, ResumeFormField> = serde_json::from_value(properties_value.clone())
|
||||
.map_err(|e| {
|
||||
tracing::error!("Deserialization failed: {:?}", e);
|
||||
Error::BadRequest("Failed to deserialize properties!".to_string())
|
||||
})?;
|
||||
|
||||
let blocks = transform_schemas(message_str, Some(&properties), &res.unwrap().0, Some(&order), hide_cancel)
|
||||
.await?;
|
||||
Ok(Json(blocks))
|
||||
} else {
|
||||
Err(Error::BadRequest(
|
||||
"Could not generate interactive Slack message!".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn post_slack_response(
|
||||
response_url: &str,
|
||||
message: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut final_blocks = vec![serde_json::json!({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": message
|
||||
}
|
||||
})];
|
||||
|
||||
let payload = serde_json::json!({
|
||||
"replace_original": "true",
|
||||
"text": message,
|
||||
"blocks": final_blocks
|
||||
});
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let response = client
|
||||
.post(response_url)
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&payload)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
if response.status().is_success() {
|
||||
tracing::debug!("Slack response to approval sent successfully!");
|
||||
} else {
|
||||
tracing::error!(
|
||||
"Slack response to approval failed. Status: {}",
|
||||
response.status()
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ResumeSchema {
|
||||
pub schema: Schema,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ResumeFormRow {
|
||||
pub resume_form: Option<serde_json::Value>,
|
||||
pub hide_cancel: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Schema {
|
||||
pub order: Vec<String>,
|
||||
pub required: Vec<String>,
|
||||
pub properties: HashMap<String, ResumeFormField>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ResumeFormField {
|
||||
#[serde(rename = "type")]
|
||||
pub r#type: String,
|
||||
pub format: Option<String>,
|
||||
pub default: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub title: Option<String>,
|
||||
#[serde(rename = "enum")]
|
||||
pub r#enum: Option<Vec<String>>,
|
||||
#[serde(rename = "enumLabels")]
|
||||
pub enum_labels: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
async fn transform_schemas(
|
||||
text: &str,
|
||||
properties: Option<&HashMap<String, ResumeFormField>>,
|
||||
urls: &ResumeUrls,
|
||||
order: Option<&Vec<String>>,
|
||||
hide_cancel: bool,
|
||||
) -> Result<serde_json::Value, Error> {
|
||||
tracing::debug!("{:?}", urls);
|
||||
|
||||
let mut blocks = vec![serde_json::json!({
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": format!("{}\n<{}|Flow suspension details>", text, urls.approvalPage),
|
||||
}
|
||||
})];
|
||||
|
||||
if let Some(properties) = properties {
|
||||
if let Some(order) = order {
|
||||
for key in order {
|
||||
if let Some(schema) = properties.get(key) {
|
||||
let input_block = create_input_block(key, schema);
|
||||
match input_block {
|
||||
serde_json::Value::Array(arr) => blocks.extend(arr),
|
||||
_ => blocks.push(input_block),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (key, schema) in properties {
|
||||
let input_block = create_input_block(key, schema);
|
||||
match input_block {
|
||||
serde_json::Value::Array(arr) => blocks.extend(arr),
|
||||
_ => blocks.push(input_block),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
blocks.push(create_action_buttons(urls, hide_cancel));
|
||||
|
||||
Ok(serde_json::Value::Array(blocks))
|
||||
}
|
||||
|
||||
fn create_input_block(key: &str, schema: &ResumeFormField) -> serde_json::Value {
|
||||
let placeholder = schema
|
||||
.description
|
||||
.as_deref()
|
||||
.filter(|desc| !desc.is_empty())
|
||||
.unwrap_or("Select an option");
|
||||
|
||||
// Handle date-time format
|
||||
if schema.r#type == "string" && schema.format.as_deref() == Some("date-time") {
|
||||
let now = chrono::Local::now();
|
||||
let current_date = now.format("%Y-%m-%d").to_string();
|
||||
let current_time = now.format("%H:%M").to_string();
|
||||
|
||||
let (default_date, default_time) = if let Some(default) = &schema.default {
|
||||
if let Ok(parsed_date) = chrono::DateTime::parse_from_rfc3339(default) {
|
||||
(
|
||||
parsed_date.format("%Y-%m-%d").to_string(),
|
||||
parsed_date.format("%H:%M").to_string(),
|
||||
)
|
||||
} else {
|
||||
(current_date.clone(), current_time.clone())
|
||||
}
|
||||
} else {
|
||||
(current_date.clone(), current_time.clone())
|
||||
};
|
||||
|
||||
return serde_json::json!([
|
||||
{
|
||||
"type": "input",
|
||||
"element": {
|
||||
"type": "datepicker",
|
||||
"initial_date": &default_date,
|
||||
"placeholder": {
|
||||
"type": "plain_text",
|
||||
"text": "Select a date",
|
||||
"emoji": true
|
||||
},
|
||||
"action_id": format!("{}_date", key)
|
||||
},
|
||||
"label": {
|
||||
"type": "plain_text",
|
||||
"text": schema.title.as_deref().unwrap_or(key),
|
||||
"emoji": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"element": {
|
||||
"type": "timepicker",
|
||||
"initial_time": &default_time,
|
||||
"placeholder": {
|
||||
"type": "plain_text",
|
||||
"text": "Select time",
|
||||
"emoji": true
|
||||
},
|
||||
"action_id": format!("{}_time", key)
|
||||
},
|
||||
"label": {
|
||||
"type": "plain_text",
|
||||
"text": " ",
|
||||
"emoji": true
|
||||
}
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
// Handle enum type
|
||||
if let Some(enums) = &schema.r#enum {
|
||||
|
||||
let initial_option = schema.default.as_ref().and_then(|default_value| {
|
||||
enums.iter().find(|enum_value| enum_value == &default_value).map(|enum_value| {
|
||||
serde_json::json!({
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": schema.enum_labels.as_ref()
|
||||
.and_then(|labels| labels.get(enum_value))
|
||||
.unwrap_or(enum_value),
|
||||
"emoji": true
|
||||
},
|
||||
"value": enum_value
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
let mut element = serde_json::json!({
|
||||
"type": "static_select",
|
||||
"placeholder": {
|
||||
"type": "plain_text",
|
||||
"text": placeholder,
|
||||
"emoji": true,
|
||||
},
|
||||
"options": enums.iter().map(|enum_value| {
|
||||
serde_json::json!({
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": schema.enum_labels.as_ref()
|
||||
.and_then(|labels| labels.get(enum_value))
|
||||
.unwrap_or(enum_value),
|
||||
"emoji": true
|
||||
},
|
||||
"value": enum_value
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"action_id": key
|
||||
});
|
||||
|
||||
if let Some(option) = initial_option {
|
||||
element["initial_option"] = option;
|
||||
}
|
||||
|
||||
serde_json::json!({
|
||||
"type": "input",
|
||||
"element": element,
|
||||
"label": {
|
||||
"type": "plain_text",
|
||||
"text": schema.title.as_deref().unwrap_or(key),
|
||||
"emoji": true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Handle other types
|
||||
serde_json::json!({
|
||||
"type": "input",
|
||||
"element": {
|
||||
"type": "plain_text_input",
|
||||
"action_id": key,
|
||||
"initial_value": schema.default.as_deref().unwrap_or("")
|
||||
},
|
||||
"label": {
|
||||
"type": "plain_text",
|
||||
"text": schema.title.as_deref().unwrap_or(key),
|
||||
"emoji": true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn create_action_buttons(urls: &ResumeUrls, hide_cancel: bool) -> serde_json::Value {
|
||||
let mut elements = vec![
|
||||
serde_json::json!({
|
||||
"type": "button",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "Continue"
|
||||
},
|
||||
"style": "primary",
|
||||
"action_id": "resume_action",
|
||||
"value": urls.resume
|
||||
})
|
||||
];
|
||||
|
||||
if !hide_cancel {
|
||||
elements.push(serde_json::json!({
|
||||
"type": "button",
|
||||
"text": {
|
||||
"type": "plain_text",
|
||||
"text": "Abort"
|
||||
},
|
||||
"style": "danger",
|
||||
"action_id": "cancel_action",
|
||||
"value": urls.cancel
|
||||
}));
|
||||
}
|
||||
|
||||
serde_json::json!({
|
||||
"type": "actions",
|
||||
"elements": elements
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::{
|
||||
use anyhow::Context;
|
||||
use argon2::Argon2;
|
||||
use axum::extract::DefaultBodyLimit;
|
||||
use axum::{middleware::from_extractor, routing::get, Extension, Router};
|
||||
use axum::{middleware::from_extractor, routing::get, routing::post, Extension, Router};
|
||||
use db::DB;
|
||||
use http::HeaderValue;
|
||||
use reqwest::Client;
|
||||
@@ -367,6 +367,8 @@ pub async fn run_server(
|
||||
"/w/:workspace_id/jobs_u",
|
||||
jobs::workspace_unauthed_service().layer(cors.clone()),
|
||||
)
|
||||
.route("/slack", post(jobs::slack_app_callback_handler))
|
||||
.route("/w/:workspace_id/jobs/slack_approval/:job_id/:resume_id", get(jobs::request_slack_approval))
|
||||
.nest(
|
||||
"/w/:workspace_id/resources_u",
|
||||
resources::public_service().layer(cors.clone()),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"gitSync": "hub/9087/sync-script-to-git-repo-windmill",
|
||||
"gitSync_0": "hub/9087/sync-script-to-git-repo-windmill",
|
||||
"gitSync": "hub/9987/sync-script-to-git-repo-windmill",
|
||||
"gitSyncTest": "hub/9073/git-repo-test-read-write-windmill",
|
||||
"slackErrorHandler": "hub/9206/workspace-or-schedule-error-handler-slack",
|
||||
"slackErrorHandler_0": "hub/9079/workspace-or-schedule-error-handler-slack",
|
||||
|
||||
@@ -6,7 +6,13 @@ cp ../backend/windmill-api/openapi.yaml openapi/openapi.yaml
|
||||
|
||||
npx @redocly/openapi-cli@latest bundle openapi/openapi.yaml > openapi-bundled.yaml
|
||||
|
||||
sed -z 's/FlowModuleValue:/FlowModuleValue2:/' openapi-bundled.yaml > openapi-decycled.yaml
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# sed -z is not supported on macOS, use perl instead
|
||||
perl -0777 -pe 's/FlowModuleValue:/FlowModuleValue2:/g' openapi-bundled.yaml > openapi-decycled.yaml
|
||||
else
|
||||
sed -z 's/FlowModuleValue:/FlowModuleValue2:/' openapi-bundled.yaml > openapi-decycled.yaml
|
||||
fi
|
||||
|
||||
echo " FlowModuleValue: {}" >> openapi-decycled.yaml
|
||||
npx @redocly/openapi-cli@latest bundle openapi-decycled.yaml --ext json -d > openapi-deref.json
|
||||
|
||||
@@ -20,9 +26,19 @@ rm -rf openapi/
|
||||
rm openapi*
|
||||
|
||||
cp LICENSE windmill-api/
|
||||
sed -i '5 i license = "Apache-2.0"' windmill-api/pyproject.toml
|
||||
|
||||
sed -i 's/authors = \[\]/authors = \["Ruben Fiszel <ruben@windmill.dev>"\]/g' windmill-api/pyproject.toml
|
||||
# Check if running on macOS
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS version
|
||||
sed -i '' '5 i\
|
||||
license = "Apache-2.0"' windmill-api/pyproject.toml
|
||||
sed -i '' 's/authors = \[\]/\nauthors = \["Ruben Fiszel <ruben@windmill.dev>"\]/g' windmill-api/pyproject.toml
|
||||
else
|
||||
# Linux version
|
||||
sed -i '5 i license = "Apache-2.0"' windmill-api/pyproject.toml
|
||||
|
||||
sed -i 's/authors = \[\]/authors = \["Ruben Fiszel <ruben@windmill.dev>"\]/g' windmill-api/pyproject.toml
|
||||
fi
|
||||
|
||||
echo "# Autogenerated Windmill OpenApi Client" >> windmill-api/README.md.tmp
|
||||
echo "This is the raw autogenerated api client. You are most likely more interested \
|
||||
@@ -33,8 +49,7 @@ user friendly experience. We use \
|
||||
|
||||
echo "" >> windmill-api/README.md.tmp
|
||||
|
||||
|
||||
head -n -13 windmill-api/README.md >> windmill-api/README.md.tmp
|
||||
tail -r windmill-api/README.md | tail -n +14 | tail -r >> windmill-api/README.md.tmp
|
||||
mv windmill-api/README.md.tmp windmill-api/README.md
|
||||
|
||||
cd windmill-api && poetry build
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#! /usr/bin/env nu
|
||||
|
||||
let cache = "/tmp/windmill/cache/pip/"
|
||||
let cache = "/tmp/windmill/cache/python_311/"
|
||||
|
||||
# Clean cache
|
||||
def "main clean" [] {
|
||||
@@ -42,7 +42,7 @@ def main [
|
||||
rm -rf ($cache ++ wmill*/wmill/*)
|
||||
|
||||
# Copy files from local ./dist to every wm-client version in cache
|
||||
ls /tmp/windmill/cache/pip/wmill* | each {
|
||||
ls /tmp/windmill/cache/python_311/wmill* | each {
|
||||
|i|
|
||||
|
||||
let path = $i | get name;
|
||||
|
||||
@@ -17,6 +17,7 @@ include = ["wmill/py.typed"]
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.7"
|
||||
httpx = ">=0.24"
|
||||
slack-sdk = "^3.33.5"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=1.0.2", "poetry-dynamic-versioning"]
|
||||
|
||||
@@ -16,6 +16,7 @@ import httpx
|
||||
|
||||
from .s3_reader import S3BufferedReader, bytes_generator
|
||||
from .s3_types import Boto3ConnectionSettings, DuckDbConnectionSettings, PolarsConnectionSettings, S3Object
|
||||
from slack_sdk import WebClient
|
||||
|
||||
_client: "Windmill | None" = None
|
||||
|
||||
@@ -623,6 +624,48 @@ class Windmill:
|
||||
params={"approver": approver},
|
||||
).json()
|
||||
|
||||
def request_interactive_slack_approval(
|
||||
self,
|
||||
slack_token: str,
|
||||
channel: str,
|
||||
message: str = None,
|
||||
approver: str = None,
|
||||
) -> None:
|
||||
"""
|
||||
Request interactive Slack approval
|
||||
:param slack_token: Slack token
|
||||
:param channel: Slack channel
|
||||
:param message: Message to send to Slack
|
||||
:param approver: Approver name
|
||||
"""
|
||||
web = WebClient(slack_token)
|
||||
nonce = random.randint(0, 4294967295)
|
||||
workspace = self.workspace
|
||||
flow_job_id = os.environ.get("WM_FLOW_JOB_ID")
|
||||
|
||||
if not flow_job_id:
|
||||
raise Exception(
|
||||
"You can't use 'request_interactive_slack_approval' function in a standalone script or flow step preview. Please use it in a flow or a flow preview."
|
||||
)
|
||||
|
||||
# Only include non-empty parameters
|
||||
params = {}
|
||||
if message:
|
||||
params["message"] = message
|
||||
if approver:
|
||||
params["approver"] = approver
|
||||
|
||||
blocks = self.get(
|
||||
f"/w/{workspace}/jobs/slack_approval/{os.environ.get('WM_JOB_ID', 'NO_JOB_ID')}/{nonce}",
|
||||
params=params,
|
||||
).json()
|
||||
|
||||
web.chat_postMessage(
|
||||
channel=channel,
|
||||
text=message,
|
||||
blocks=blocks,
|
||||
)
|
||||
|
||||
def username_to_email(self, username: str) -> str:
|
||||
"""
|
||||
Get email from workspace username
|
||||
@@ -972,6 +1015,19 @@ def get_state_path() -> str:
|
||||
def get_resume_urls(approver: str = None) -> dict:
|
||||
return _client.get_resume_urls(approver)
|
||||
|
||||
@init_global_client
|
||||
def request_interactive_slack_approval(
|
||||
slack_token: str,
|
||||
channel: str,
|
||||
message: str = None,
|
||||
approver: str = None,
|
||||
) -> dict:
|
||||
return _client.request_interactive_slack_approval(
|
||||
slack_token,
|
||||
channel,
|
||||
message,
|
||||
approver,
|
||||
)
|
||||
|
||||
@init_global_client
|
||||
def cancel_running() -> dict:
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# Generate windmill-client bundle
|
||||
|
||||
```bash
|
||||
./node_modules/.bin/esbuild src/index.ts --b
|
||||
undle --outfile=windmill.js --format=esm
|
||||
./node_modules/.bin/esbuild src/index.ts --bundle --outfile=windmill.js --format=esm --platform=node
|
||||
```
|
||||
|
||||
# Generate d.ts bundle
|
||||
|
||||
@@ -14,5 +14,5 @@ cp "${script_dirpath}/s3Types.ts" "${script_dirpath}/src/"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval} from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
|
||||
|
||||
@@ -22,10 +22,15 @@ const baseUrl = getEnv("BASE_INTERNAL_URL") ?? getEnv("BASE_URL") ?? "http://loc
|
||||
const baseUrlApi = (baseUrl ?? '') + "/api";
|
||||
|
||||
EOF
|
||||
sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' src/core/OpenAPI.ts
|
||||
sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' src/core/OpenAPI.ts
|
||||
sed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" src/core/OpenAPI.ts
|
||||
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
sed -i '' 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' src/core/OpenAPI.ts
|
||||
sed -i '' 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' src/core/OpenAPI.ts
|
||||
sed -i '' "s/BASE: '\/api'/BASE: baseUrlApi/g" src/core/OpenAPI.ts
|
||||
else
|
||||
sed -i 's/WITH_CREDENTIALS: false/WITH_CREDENTIALS: true/g' "${script_dirpath}/src/core/OpenAPI.ts"
|
||||
sed -i 's/TOKEN: undefined/TOKEN: getEnv("WM_TOKEN")/g' "${script_dirpath}/src/core/OpenAPI.ts"
|
||||
sed -i "s/BASE: '\/api'/BASE: baseUrlApi/g" "${script_dirpath}/src/core/OpenAPI.ts"
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -34,4 +39,4 @@ cp "${script_dirpath}/s3Types.ts" "${script_dirpath}/src/"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export type { S3Object, DenoS3LightClientSettings } from "./s3Types";' >> "${script_dirpath}/src/index.ts"
|
||||
echo "" >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
echo 'export { type Base64, setClient, getVariable, setVariable, getResource, setResource, getResumeUrls, setState, setProgress, getProgress, getState, getIdToken, denoS3LightClientSettings, loadS3FileStream, loadS3File, writeS3File, task, runScript, runScriptAsync, runFlow, runFlowAsync, waitJob, getRootJobId, setFlowUserState, getFlowUserState, usernameToEmail, requestInteractiveSlackApproval } from "./client";' >> "${script_dirpath}/src/index.ts"
|
||||
|
||||
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.usernameToEmail = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.getIdToken = exports.getResumeEndpoints = exports.getResumeUrls = exports.writeS3File = exports.loadS3FileStream = exports.loadS3File = exports.denoS3LightClientSettings = exports.databaseUrlFromResource = exports.setVariable = exports.getVariable = exports.getState = exports.getInternalState = exports.getFlowUserState = exports.setFlowUserState = exports.setState = exports.setInternalState = exports.setResource = exports.getStatePath = exports.resolveDefaultResource = exports.runScriptAsync = exports.task = exports.getResultMaybe = exports.getResult = exports.waitJob = exports.runScript = exports.getRootJobId = exports.getResource = exports.getWorkspace = exports.setClient = exports.SHARED_FOLDER = exports.WorkspaceService = exports.UserService = exports.SettingsService = exports.ScheduleService = exports.ScriptService = exports.VariableService = exports.ResourceService = exports.JobService = exports.GroupService = exports.GranularAclService = exports.FlowService = exports.AuditService = exports.AdminService = void 0;
|
||||
exports.requestInteractiveSlackApproval = exports.usernameToEmail = exports.uint8ArrayToBase64 = exports.base64ToUint8Array = exports.getIdToken = exports.getResumeEndpoints = exports.getResumeUrls = exports.writeS3File = exports.loadS3FileStream = exports.loadS3File = exports.denoS3LightClientSettings = exports.databaseUrlFromResource = exports.setVariable = exports.getVariable = exports.getState = exports.getInternalState = exports.getFlowUserState = exports.setFlowUserState = exports.setState = exports.setInternalState = exports.setResource = exports.getStatePath = exports.resolveDefaultResource = exports.runScriptAsync = exports.task = exports.getResultMaybe = exports.getResult = exports.waitJob = exports.runScript = exports.getRootJobId = exports.getResource = exports.getWorkspace = exports.setClient = exports.SHARED_FOLDER = exports.WorkspaceService = exports.UserService = exports.SettingsService = exports.ScheduleService = exports.ScriptService = exports.VariableService = exports.ResourceService = exports.JobService = exports.GroupService = exports.GranularAclService = exports.FlowService = exports.AuditService = exports.AdminService = void 0;
|
||||
const index_1 = require("./index");
|
||||
const index_2 = require("./index");
|
||||
var index_3 = require("./index");
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { OpenAPI } from "./index";
|
||||
// import type { DenoS3LightClientSettings } from "./index";
|
||||
import { DenoS3LightClientSettings, type S3Object } from "./s3Types";
|
||||
import { WebClient, Block, KnownBlock } from "@slack/web-api";
|
||||
|
||||
export {
|
||||
AdminService,
|
||||
@@ -395,15 +396,15 @@ export async function setState(state: any): Promise<void> {
|
||||
*/
|
||||
export async function setProgress(percent: number, jobId?: any): Promise<void> {
|
||||
const workspace = getWorkspace();
|
||||
let flowId = getEnv("WM_FLOW_JOB_ID");
|
||||
let flowId = getEnv("WM_FLOW_JOB_ID");
|
||||
|
||||
// If jobId specified we need to find if there is a parent/flow
|
||||
if (jobId) {
|
||||
const job = await JobService.getJob({
|
||||
id: jobId ?? "NO_JOB_ID",
|
||||
workspace,
|
||||
noLogs: true
|
||||
});
|
||||
noLogs: true,
|
||||
});
|
||||
|
||||
// Could be actual flowId or undefined
|
||||
flowId = job.parent_job;
|
||||
@@ -415,22 +416,22 @@ export async function setProgress(percent: number, jobId?: any): Promise<void> {
|
||||
requestBody: {
|
||||
// In case user inputs float, it should be converted to int
|
||||
percent: Math.floor(percent),
|
||||
flow_job_id: (flowId == "") ? undefined : flowId,
|
||||
}
|
||||
flow_job_id: flowId == "" ? undefined : flowId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the progress
|
||||
* @param jobId? Job to get progress from
|
||||
* @returns Optional clamped between 0 and 100 progress value
|
||||
* @returns Optional clamped between 0 and 100 progress value
|
||||
*/
|
||||
export async function getProgress(jobId?: any): Promise<number | null> {
|
||||
// TODO: Delete or set to 100 completed job metrics
|
||||
return await MetricsService.getJobProgress({
|
||||
id: jobId ?? getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
|
||||
workspace: getWorkspace(),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -846,3 +847,51 @@ export async function usernameToEmail(username: string): Promise<string> {
|
||||
const workspace = getWorkspace();
|
||||
return await UserService.usernameToEmail({ username, workspace });
|
||||
}
|
||||
|
||||
interface SlackApprovalOptions {
|
||||
slackToken: string;
|
||||
channel: string;
|
||||
message?: string;
|
||||
approver?: string;
|
||||
slackBlocks?: (Block | KnownBlock)[];
|
||||
}
|
||||
|
||||
export async function requestInteractiveSlackApproval({
|
||||
slackToken,
|
||||
channel,
|
||||
message,
|
||||
approver,
|
||||
}: SlackApprovalOptions): Promise<void> {
|
||||
const web = new WebClient(slackToken);
|
||||
const nonce = Math.floor(Math.random() * 4294967295);
|
||||
const workspace = getWorkspace();
|
||||
const flowJobId = getEnv("WM_FLOW_JOB_ID");
|
||||
|
||||
if (!flowJobId) {
|
||||
throw new Error(
|
||||
"You can't use this function in a standalon script or flow step preview. Please us it in a flow or a flow preview."
|
||||
);
|
||||
}
|
||||
|
||||
// Only include non-empty parameters
|
||||
const params: { approver?: string; message?: string } = {};
|
||||
if (message) {
|
||||
params.message = message;
|
||||
}
|
||||
if (approver) {
|
||||
params.approver = approver;
|
||||
}
|
||||
|
||||
const blocks = await JobService.getSlackApprovalPayload({
|
||||
workspace,
|
||||
resumeId: nonce,
|
||||
...params,
|
||||
id: getEnv("WM_JOB_ID") ?? "NO_JOB_ID",
|
||||
});
|
||||
|
||||
await web.chat.postMessage({
|
||||
channel,
|
||||
text: message,
|
||||
blocks: blocks as unknown as (Block | KnownBlock)[],
|
||||
});
|
||||
}
|
||||
|
||||
270
typescript-client/package-lock.json
generated
270
typescript-client/package-lock.json
generated
@@ -1,13 +1,31 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"version": "1.335.0",
|
||||
"name": "alex-windmill-client-alex",
|
||||
"version": "1.435.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "windmill-client",
|
||||
"version": "1.335.0",
|
||||
"name": "alex-windmill-client-alex",
|
||||
"version": "1.435.2",
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@slack/web-api": "^7.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.4.10",
|
||||
"dts-bundle-generator": "^9.5.1",
|
||||
"esbuild": "^0.21.1",
|
||||
"typescript": "^5.4.5"
|
||||
}
|
||||
},
|
||||
"../typescript-client/": {
|
||||
"name": "alex-windmill-client-alex",
|
||||
"version": "1.435.2",
|
||||
"extraneous": true,
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@slack/web-api": "^7.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.4.10",
|
||||
"dts-bundle-generator": "^9.5.1",
|
||||
@@ -383,11 +401,63 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/logger": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/logger/-/logger-4.0.0.tgz",
|
||||
"integrity": "sha512-Wz7QYfPAlG/DR+DfABddUZeNgoeY7d1J39OCR2jR+v7VBsB8ezulDK5szTnDDPDwLH5IWhLvXIHlCFZV7MSKgA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": ">=18.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18",
|
||||
"npm": ">= 8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/types": {
|
||||
"version": "2.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/types/-/types-2.14.0.tgz",
|
||||
"integrity": "sha512-n0EGm7ENQRxlXbgKSrQZL69grzg1gHLAVd+GlRVQJ1NSORo0FrApR7wql/gaKdu2n4TO83Sq/AmeUOqD60aXUA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12.13.0",
|
||||
"npm": ">= 6.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@slack/web-api": {
|
||||
"version": "7.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@slack/web-api/-/web-api-7.8.0.tgz",
|
||||
"integrity": "sha512-d4SdG+6UmGdzWw38a4sN3lF/nTEzsDxhzU13wm10ejOpPehtmRoqBKnPztQUfFiWbNvSb4czkWYJD4kt+5+Fuw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@slack/logger": "^4.0.0",
|
||||
"@slack/types": "^2.9.0",
|
||||
"@types/node": ">=18.0.0",
|
||||
"@types/retry": "0.12.0",
|
||||
"axios": "^1.7.8",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"form-data": "^4.0.0",
|
||||
"is-electron": "2.2.2",
|
||||
"is-stream": "^2",
|
||||
"p-queue": "^6",
|
||||
"p-retry": "^4",
|
||||
"retry": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18",
|
||||
"npm": ">= 8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.10.tgz",
|
||||
"integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vwzFiiy8Rn6E0MtA13/Cxxgpan/N6UeNYR9oUu6kuJWxu6zCk98trcDp8CBhbtaeuq9SykCmXkFr2lWLoPcvLg=="
|
||||
},
|
||||
"node_modules/@types/retry": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
|
||||
"integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
@@ -413,6 +483,23 @@
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
@@ -445,6 +532,27 @@
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dts-bundle-generator": {
|
||||
"version": "9.5.1",
|
||||
"resolved": "https://registry.npmjs.org/dts-bundle-generator/-/dts-bundle-generator-9.5.1.tgz",
|
||||
@@ -514,6 +622,46 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -523,6 +671,12 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/is-electron": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz",
|
||||
"integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
@@ -532,6 +686,101 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/p-finally": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||
"integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/p-queue": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz",
|
||||
"integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.4",
|
||||
"p-timeout": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-queue/node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/p-retry": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz",
|
||||
"integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/retry": "0.12.0",
|
||||
"retry": "^0.13.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-timeout": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz",
|
||||
"integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-finally": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
@@ -541,6 +790,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/retry": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
|
||||
"integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "windmill-client",
|
||||
"name": "alex-windmill-client-alex",
|
||||
"description": "Windmill SDK client for browsers and Node.js",
|
||||
"version": "1.437.1",
|
||||
"author": "Ruben Fiszel",
|
||||
@@ -18,5 +18,8 @@
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"package.json"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"@slack/web-api": "^7.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user