mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
fix: migrate smtp instance settings to global settings (#4416)
* all * all * all * fix * update ee private * all
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "bool",
|
||||
"name": "?column?",
|
||||
"type_info": "Bool"
|
||||
}
|
||||
],
|
||||
|
||||
+4
-4
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "SELECT config FROM config WHERE name = 'server'",
|
||||
"query": "SELECT value FROM global_settings WHERE name = 'smtp_settings'",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "config",
|
||||
"name": "value",
|
||||
"type_info": "Jsonb"
|
||||
}
|
||||
],
|
||||
@@ -13,8 +13,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "8ca76fcadc659d7069b97930cdde2d5200f3cef9dedb83b76c8a6a433d2a4de3"
|
||||
"hash": "83ec97f6aad154e0e06ee05a3647dab8f89b1b2d7a569c7eac8c4169e37b9f8b"
|
||||
}
|
||||
+2
-2
@@ -18,8 +18,8 @@
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "b3dbdfb50ee8118bdaed3164b210cb549a34b96554ae1872355b90304f5dcb76"
|
||||
|
||||
Generated
+29
-60
@@ -286,7 +286,7 @@ dependencies = [
|
||||
"arrow-data",
|
||||
"arrow-schema",
|
||||
"chrono",
|
||||
"chrono-tz 0.9.0",
|
||||
"chrono-tz",
|
||||
"half",
|
||||
"hashbrown 0.14.5",
|
||||
"num",
|
||||
@@ -1083,7 +1083,7 @@ dependencies = [
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -1667,18 +1667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93698b29de5e97ad0ae26447b344c482a7284c737d9ddc5f9e52b74a336671bb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build 0.3.0",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build 0.4.0",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
@@ -1693,16 +1682,6 @@ dependencies = [
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.3.0"
|
||||
@@ -2682,7 +2661,7 @@ dependencies = [
|
||||
"tokio-rustls 0.26.0",
|
||||
"tokio-socks",
|
||||
"tokio-util",
|
||||
"tower 0.4.13",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
]
|
||||
@@ -2795,7 +2774,7 @@ dependencies = [
|
||||
"rustls-webpki 0.102.8",
|
||||
"serde",
|
||||
"tokio",
|
||||
"webpki-roots 0.26.6",
|
||||
"webpki-roots 0.26.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4306,7 +4285,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"socket2 0.5.7",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
@@ -4892,7 +4871,7 @@ dependencies = [
|
||||
"smtp-proto",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"webpki-roots 0.26.6",
|
||||
"webpki-roots 0.26.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -8051,7 +8030,7 @@ dependencies = [
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.10.0",
|
||||
"webpki-roots 0.26.6",
|
||||
"webpki-roots 0.26.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9500,16 +9479,6 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||
dependencies = [
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-cookies"
|
||||
version = "0.10.0"
|
||||
@@ -9924,9 +9893,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id"
|
||||
version = "0.3.5"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
|
||||
checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-id-start"
|
||||
@@ -9972,15 +9941,15 @@ checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
|
||||
|
||||
[[package]]
|
||||
name = "unicode_categories"
|
||||
@@ -10044,7 +10013,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki-roots 0.26.6",
|
||||
"webpki-roots 0.26.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -10340,9 +10309,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.6"
|
||||
version = "0.26.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958"
|
||||
checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
@@ -10480,7 +10449,7 @@ dependencies = [
|
||||
"candle-nn",
|
||||
"candle-transformers",
|
||||
"chrono",
|
||||
"chrono-tz 0.10.0",
|
||||
"chrono-tz",
|
||||
"cookie 0.17.0",
|
||||
"crc",
|
||||
"cron",
|
||||
@@ -10492,7 +10461,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"http 1.1.0",
|
||||
"hyper 1.4.1",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
"magic-crypt",
|
||||
@@ -10527,7 +10496,7 @@ dependencies = [
|
||||
"tokio-native-tls",
|
||||
"tokio-tar",
|
||||
"tokio-util",
|
||||
"tower 0.5.1",
|
||||
"tower",
|
||||
"tower-cookies",
|
||||
"tower-http",
|
||||
"tracing",
|
||||
@@ -10597,7 +10566,7 @@ dependencies = [
|
||||
"hmac",
|
||||
"hyper 1.4.1",
|
||||
"indexmap 2.5.0",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"magic-crypt",
|
||||
"mail-send",
|
||||
@@ -10684,7 +10653,7 @@ version = "1.397.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"gosyn",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"regex",
|
||||
"windmill-parser",
|
||||
@@ -10707,7 +10676,7 @@ name = "windmill-parser-php"
|
||||
version = "1.397.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"php-parser-rs",
|
||||
"serde_json",
|
||||
"windmill-parser",
|
||||
@@ -10718,7 +10687,7 @@ name = "windmill-parser-py"
|
||||
version = "1.397.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"rustpython-parser",
|
||||
"serde_json",
|
||||
"windmill-parser",
|
||||
@@ -10730,7 +10699,7 @@ version = "1.397.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-recursion",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"phf",
|
||||
"regex",
|
||||
@@ -10748,7 +10717,7 @@ version = "1.397.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"convert_case 0.6.0",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"pulldown-cmark",
|
||||
"quote",
|
||||
@@ -10829,12 +10798,12 @@ dependencies = [
|
||||
"axum",
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"chrono-tz 0.10.0",
|
||||
"chrono-tz",
|
||||
"cron",
|
||||
"futures-core",
|
||||
"hex",
|
||||
"hmac",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"lazy_static",
|
||||
"prometheus",
|
||||
"regex",
|
||||
@@ -10890,7 +10859,7 @@ dependencies = [
|
||||
"gcp_auth",
|
||||
"git-version",
|
||||
"hex",
|
||||
"itertools 0.13.0",
|
||||
"itertools 0.10.5",
|
||||
"jsonwebtoken",
|
||||
"lazy_static",
|
||||
"mappable-rc",
|
||||
|
||||
@@ -1 +1 @@
|
||||
f8621356df7f9b9250913cf5cdbda35d0703f311
|
||||
e093d51a219ce4ff0562a02db24ec402554a1f05
|
||||
@@ -0,0 +1 @@
|
||||
-- Add down migration script here
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Add up migration script here
|
||||
INSERT INTO global_settings (name, value)
|
||||
SELECT 'smtp_settings', config
|
||||
FROM config
|
||||
WHERE name = 'server'
|
||||
ON CONFLICT (name) DO UPDATE SET value = excluded.value;
|
||||
|
||||
DELETE FROM config WHERE name = 'server';
|
||||
+13
-6
@@ -8,7 +8,10 @@
|
||||
|
||||
use anyhow::Context;
|
||||
use git_version::git_version;
|
||||
use monitor::{send_current_log_file_to_object_store, send_logs_to_object_store};
|
||||
use monitor::{
|
||||
reload_timeout_wait_result_setting, send_current_log_file_to_object_store,
|
||||
send_logs_to_object_store,
|
||||
};
|
||||
use rand::Rng;
|
||||
use sqlx::{postgres::PgListener, Pool, Postgres};
|
||||
use std::{
|
||||
@@ -35,7 +38,7 @@ use windmill_common::{
|
||||
JWT_SECRET_SETTING, KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING,
|
||||
OAUTH_SETTING, PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, SMTP_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
scripts::ScriptLang,
|
||||
stats_ee::schedule_stats,
|
||||
@@ -74,7 +77,7 @@ use crate::monitor::{
|
||||
reload_critical_error_channels_setting, reload_extra_pip_index_url_setting,
|
||||
reload_hub_base_url_setting, reload_job_default_timeout_setting, reload_jwt_secret_setting,
|
||||
reload_license_key, reload_npm_config_registry_setting, reload_pip_index_url_setting,
|
||||
reload_retention_period_setting, reload_scim_token_setting, reload_server_config,
|
||||
reload_retention_period_setting, reload_scim_token_setting, reload_smtp_config,
|
||||
reload_worker_config,
|
||||
};
|
||||
|
||||
@@ -563,9 +566,7 @@ Windmill Community Edition {GIT_VERSION}
|
||||
"notify_config_change" => {
|
||||
match n.payload() {
|
||||
"server" if server_mode => {
|
||||
tracing::info!("Server config change detected: {}", n.payload());
|
||||
|
||||
reload_server_config(&db).await;
|
||||
tracing::error!("Server config change detected but server config is obsolete: {}", n.payload());
|
||||
},
|
||||
a@ _ if worker_mode && a == format!("worker__{}", *WORKER_GROUP) => {
|
||||
tracing::info!("Worker config change detected: {}", n.payload());
|
||||
@@ -609,6 +610,12 @@ Windmill Community Edition {GIT_VERSION}
|
||||
tracing::error!("Error loading default tag per workspace workspaces: {e:#}");
|
||||
}
|
||||
}
|
||||
SMTP_SETTING => {
|
||||
reload_smtp_config(&db).await;
|
||||
},
|
||||
TIMEOUT_WAIT_RESULT_SETTING => {
|
||||
reload_timeout_wait_result_setting(&db).await
|
||||
},
|
||||
RETENTION_PERIOD_SECS_SETTING => {
|
||||
reload_retention_period_setting(&db).await
|
||||
},
|
||||
|
||||
+22
-11
@@ -22,6 +22,7 @@ use tokio::{
|
||||
#[cfg(feature = "embedding")]
|
||||
use windmill_api::embeddings::update_embeddings_db;
|
||||
use windmill_api::{
|
||||
jobs::TIMEOUT_WAIT_RESULT,
|
||||
oauth2_ee::{build_oauth_clients, OAuthClient},
|
||||
DEFAULT_BODY_LIMIT, IS_SECURE, OAUTH_CLIENTS, REQUEST_SIZE_LIMIT, SAML_METADATA, SCIM_TOKEN,
|
||||
};
|
||||
@@ -40,17 +41,17 @@ use windmill_common::{
|
||||
KEEP_JOB_DIR_SETTING, LICENSE_KEY_SETTING, NPM_CONFIG_REGISTRY_SETTING, OAUTH_SETTING,
|
||||
PIP_INDEX_URL_SETTING, REQUEST_SIZE_LIMIT_SETTING,
|
||||
REQUIRE_PREEXISTING_USER_FOR_OAUTH_SETTING, RETENTION_PERIOD_SECS_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING,
|
||||
SAML_METADATA_SETTING, SCIM_TOKEN_SETTING, TIMEOUT_WAIT_RESULT_SETTING,
|
||||
},
|
||||
jobs::QueuedJob,
|
||||
oauth2::REQUIRE_PREEXISTING_USER_FOR_OAUTH,
|
||||
server::load_server_config,
|
||||
server::load_smtp_config,
|
||||
tracing_init::JSON_FMT,
|
||||
users::truncate_token,
|
||||
utils::{now_from_db, rd_string, report_critical_error, Mode},
|
||||
worker::{
|
||||
load_worker_config, make_pull_query, make_suspended_pull_query, reload_custom_tags_setting,
|
||||
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SERVER_CONFIG, WORKER_CONFIG,
|
||||
DEFAULT_TAGS_PER_WORKSPACE, DEFAULT_TAGS_WORKSPACES, SMTP_CONFIG, WORKER_CONFIG,
|
||||
WORKER_GROUP,
|
||||
},
|
||||
BASE_URL, CRITICAL_ERROR_CHANNELS, DB, DEFAULT_HUB_BASE_URL, HUB_BASE_URL, JOB_RETENTION_SECS,
|
||||
@@ -172,8 +173,9 @@ pub async fn initial_load(
|
||||
reload_s3_cache_setting(&db).await;
|
||||
}
|
||||
|
||||
reload_smtp_config(&db).await;
|
||||
|
||||
if server_mode {
|
||||
reload_server_config(&db).await;
|
||||
reload_retention_period_setting(&db).await;
|
||||
reload_request_size(&db).await;
|
||||
reload_saml_metadata_setting(&db).await;
|
||||
@@ -698,6 +700,15 @@ pub async fn reload_scim_token_setting(db: &DB) {
|
||||
.await;
|
||||
}
|
||||
|
||||
pub async fn reload_timeout_wait_result_setting(db: &DB) {
|
||||
reload_option_setting_with_tracing(
|
||||
db,
|
||||
TIMEOUT_WAIT_RESULT_SETTING,
|
||||
"TIMEOUT_WAIT_RESULT",
|
||||
TIMEOUT_WAIT_RESULT.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
pub async fn reload_saml_metadata_setting(db: &DB) {
|
||||
reload_option_setting_with_tracing(
|
||||
db,
|
||||
@@ -1129,14 +1140,14 @@ pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reload_server_config(db: &Pool<Postgres>) {
|
||||
let config = load_server_config(&db).await;
|
||||
if let Err(e) = config {
|
||||
tracing::error!("Error reloading server config: {:?}", e)
|
||||
pub async fn reload_smtp_config(db: &Pool<Postgres>) {
|
||||
let smtp_config = load_smtp_config(&db).await;
|
||||
if let Err(e) = smtp_config {
|
||||
tracing::error!("Error reloading smtp config: {:?}", e)
|
||||
} else {
|
||||
let mut wc = SERVER_CONFIG.write().await;
|
||||
tracing::info!("Reloading server config...");
|
||||
*wc = config.unwrap()
|
||||
let mut wc = SMTP_CONFIG.write().await;
|
||||
tracing::info!("Reloading smtp config...");
|
||||
*wc = smtp_config.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -742,6 +742,35 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/settings/test_critical_channels:
|
||||
post:
|
||||
summary: test critical channels
|
||||
operationId: testCriticalChannels
|
||||
tags:
|
||||
- setting
|
||||
requestBody:
|
||||
description: test critical channel payload
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
slack_channel:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
|
||||
/settings/test_license_key:
|
||||
post:
|
||||
summary: test license key
|
||||
|
||||
@@ -58,7 +58,7 @@ use tower_http::cors::{Any, CorsLayer};
|
||||
use urlencoding::encode;
|
||||
use windmill_audit::audit_ee::{audit_log, AuditAuthor};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE, SERVER_CONFIG};
|
||||
use windmill_common::worker::{to_raw_value, CUSTOM_TAGS_PER_WORKSPACE};
|
||||
use windmill_common::{
|
||||
db::UserDB,
|
||||
error::{self, to_anyhow, Error},
|
||||
@@ -3165,6 +3165,17 @@ impl Drop for Guard {
|
||||
}
|
||||
}
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref TIMEOUT_WAIT_RESULT: Arc<RwLock<Option<u64>>> = Arc::new(RwLock::new(
|
||||
std::env::var("TIMEOUT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok())
|
||||
));
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct WindmillCompositeResult {
|
||||
windmill_status_code: Option<u16>,
|
||||
@@ -3179,7 +3190,7 @@ async fn run_wait_result(
|
||||
username: &str,
|
||||
) -> error::Result<Response> {
|
||||
let mut result = None;
|
||||
let timeout = SERVER_CONFIG.read().await.timeout_wait_result.clone();
|
||||
let timeout = TIMEOUT_WAIT_RESULT.read().await.clone().unwrap_or(600);
|
||||
let timeout_ms = if timeout <= 0 {
|
||||
2000
|
||||
} else {
|
||||
|
||||
@@ -26,6 +26,7 @@ use axum::extract::Query;
|
||||
|
||||
use serde::Deserialize;
|
||||
use windmill_common::{
|
||||
ee::{send_critical_alert, CriticalAlertKind, CriticalErrorChannel},
|
||||
error::{self, JsonResult, Result},
|
||||
global_settings::{
|
||||
AUTOMATE_USERNAME_CREATION_SETTING, EMAIL_DOMAIN_SETTING, ENV_SETTINGS,
|
||||
@@ -55,7 +56,8 @@ pub fn global_service() -> Router {
|
||||
get(get_latest_key_renewal_attempt),
|
||||
)
|
||||
.route("/renew_license_key", post(renew_license_key))
|
||||
.route("/customer_portal", post(create_customer_portal_session));
|
||||
.route("/customer_portal", post(create_customer_portal_session))
|
||||
.route("/test_critical_channels", post(test_critical_channels));
|
||||
|
||||
#[cfg(feature = "parquet")]
|
||||
{
|
||||
@@ -387,3 +389,20 @@ pub async fn create_customer_portal_session(
|
||||
|
||||
return Ok(url);
|
||||
}
|
||||
|
||||
pub async fn test_critical_channels(
|
||||
Extension(db): Extension<DB>,
|
||||
authed: ApiAuthed,
|
||||
Json(test_critical_channels): Json<Vec<CriticalErrorChannel>>,
|
||||
) -> Result<String> {
|
||||
require_super_admin(&db, &authed.email).await?;
|
||||
|
||||
send_critical_alert(
|
||||
"Test critical error".to_string(),
|
||||
&db,
|
||||
CriticalAlertKind::CriticalError,
|
||||
Some(test_critical_channels),
|
||||
)
|
||||
.await;
|
||||
Ok("Sent test critical error".to_string())
|
||||
}
|
||||
|
||||
@@ -36,7 +36,13 @@ pub enum CriticalAlertKind {
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn send_critical_alert(_error_message: String, _db: &DB, _kind: CriticalAlertKind) {}
|
||||
pub async fn send_critical_alert(
|
||||
_error_message: String,
|
||||
_db: &DB,
|
||||
_kind: CriticalAlertKind,
|
||||
channels: Option<Vec<CriticalErrorChannel>>,
|
||||
) {
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
pub async fn schedule_key_renewal(_http_client: &reqwest::Client, _db: &crate::db::DB) -> () {
|
||||
|
||||
@@ -14,6 +14,8 @@ pub const EXTRA_PIP_INDEX_URL_SETTING: &str = "pip_extra_index_url";
|
||||
pub const PIP_INDEX_URL_SETTING: &str = "pip_index_url";
|
||||
pub const SCIM_TOKEN_SETTING: &str = "scim_token";
|
||||
pub const SAML_METADATA_SETTING: &str = "saml_metadata";
|
||||
pub const SMTP_SETTING: &str = "smtp_settings";
|
||||
pub const TIMEOUT_WAIT_RESULT_SETTING: &str = "timeout_wait_result";
|
||||
|
||||
pub const UNIQUE_ID_SETTING: &str = "uid";
|
||||
pub const DISABLE_STATS_SETTING: &str = "disable_stats";
|
||||
|
||||
@@ -13,22 +13,20 @@ pub struct Smtp {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq)]
|
||||
pub struct ServerConfigOpt {
|
||||
pub struct SmtpConfigOpt {
|
||||
pub smtp_host: Option<String>,
|
||||
pub smtp_username: Option<String>,
|
||||
pub smtp_password: Option<String>,
|
||||
pub smtp_port: Option<u16>,
|
||||
pub smtp_from: Option<String>,
|
||||
pub smtp_tls_implicit: Option<bool>,
|
||||
pub timeout_wait_result: Option<u64>,
|
||||
}
|
||||
|
||||
pub async fn load_server_config(db: &DB) -> error::Result<ServerConfig> {
|
||||
let config: ServerConfigOpt =
|
||||
sqlx::query_scalar!("SELECT config FROM config WHERE name = 'server'",)
|
||||
pub async fn load_smtp_config(db: &DB) -> error::Result<Option<Smtp>> {
|
||||
let config: SmtpConfigOpt =
|
||||
sqlx::query_scalar!("SELECT value FROM global_settings WHERE name = 'smtp_settings'",)
|
||||
.fetch_optional(db)
|
||||
.await?
|
||||
.flatten()
|
||||
.map(|x| serde_json::from_value(x).ok())
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
@@ -77,20 +75,10 @@ pub async fn load_server_config(db: &DB) -> error::Result<ServerConfig> {
|
||||
tracing::warn!("SMTP not configured");
|
||||
}
|
||||
|
||||
Ok(ServerConfig {
|
||||
smtp,
|
||||
timeout_wait_result: config
|
||||
.timeout_wait_result
|
||||
.ok_or(
|
||||
std::env::var("TIMEOUT_WAIT_RESULT")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok()),
|
||||
)
|
||||
.unwrap_or(600),
|
||||
})
|
||||
Ok(smtp)
|
||||
}
|
||||
|
||||
impl Default for ServerConfigOpt {
|
||||
impl Default for SmtpConfigOpt {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
smtp_from: None,
|
||||
@@ -99,13 +87,6 @@ impl Default for ServerConfigOpt {
|
||||
smtp_port: None,
|
||||
smtp_tls_implicit: None,
|
||||
smtp_username: None,
|
||||
timeout_wait_result: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
pub struct ServerConfig {
|
||||
pub smtp: Option<Smtp>,
|
||||
pub timeout_wait_result: u64,
|
||||
}
|
||||
|
||||
@@ -288,7 +288,7 @@ pub async fn report_critical_error(error_message: String, _db: DB) -> () {
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
send_critical_alert(error_message, &_db, CriticalAlertKind::CriticalError).await;
|
||||
send_critical_alert(error_message, &_db, CriticalAlertKind::CriticalError, None).await;
|
||||
}
|
||||
|
||||
pub async fn report_recovered_critical_error(message: String, _db: DB) -> () {
|
||||
@@ -304,5 +304,11 @@ pub async fn report_recovered_critical_error(message: String, _db: DB) -> () {
|
||||
tracing::error!("Failed to save critical error to database: {}", err);
|
||||
}
|
||||
#[cfg(feature = "enterprise")]
|
||||
send_critical_alert(message, &_db, CriticalAlertKind::RecoveredCriticalError).await;
|
||||
send_critical_alert(
|
||||
message,
|
||||
&_db,
|
||||
CriticalAlertKind::RecoveredCriticalError,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::{
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{error, global_settings::CUSTOM_TAGS_SETTING, server::ServerConfig, DB};
|
||||
use crate::{error, global_settings::CUSTOM_TAGS_SETTING, server::Smtp, DB};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref WORKER_GROUP: String = std::env::var("WORKER_GROUP").unwrap_or_else(|_| "default".to_string());
|
||||
@@ -66,8 +66,7 @@ lazy_static::lazy_static! {
|
||||
pub static ref WORKER_SUSPENDED_PULL_QUERY: Arc<RwLock<String>> = Arc::new(RwLock::new("".to_string()));
|
||||
|
||||
|
||||
pub static ref SERVER_CONFIG: Arc<RwLock<ServerConfig>> = Arc::new(RwLock::new(ServerConfig { smtp: Default::default(), timeout_wait_result: 20 }));
|
||||
|
||||
pub static ref SMTP_CONFIG: Arc<RwLock<Option<Smtp>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
|
||||
pub static ref CLOUD_HOSTED: bool = std::env::var("CLOUD_HOSTED").is_ok();
|
||||
@@ -195,7 +194,11 @@ fn normalize_path(path: &Path) -> PathBuf {
|
||||
}
|
||||
ret
|
||||
}
|
||||
pub fn write_file_at_user_defined_location(job_dir: &str, user_defined_path: &str, content: &str) -> error::Result<File> {
|
||||
pub fn write_file_at_user_defined_location(
|
||||
job_dir: &str,
|
||||
user_defined_path: &str,
|
||||
content: &str,
|
||||
) -> error::Result<File> {
|
||||
let job_dir = Path::new(job_dir);
|
||||
let user_path = PathBuf::from(user_defined_path);
|
||||
|
||||
@@ -210,7 +213,8 @@ pub fn write_file_at_user_defined_location(job_dir: &str, user_defined_path: &st
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::PermissionDenied,
|
||||
"Path is outside the allowed job directory.",
|
||||
).into());
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
if let Some(parent_dir) = full_path.parent() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { settings, settingsKeys, type SettingStorage } from './instanceSettings'
|
||||
import { Button, Skeleton, Tab, TabContent, Tabs } from '$lib/components/common'
|
||||
import { ConfigService, SettingService, SettingsService } from '$lib/gen'
|
||||
import { SettingService, SettingsService } from '$lib/gen'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import SecondsInput from '$lib/components/common/seconds/SecondsInput.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
@@ -54,7 +54,6 @@
|
||||
attempted_at: string
|
||||
} | null = null
|
||||
|
||||
let serverConfig = {}
|
||||
let initialValues: Record<string, any> = {}
|
||||
let loading = true
|
||||
|
||||
@@ -71,17 +70,10 @@
|
||||
|
||||
async function loadSettings() {
|
||||
loading = true
|
||||
try {
|
||||
serverConfig = (await ConfigService.getConfig({ name: 'server' })) ?? {}
|
||||
} catch (e) {
|
||||
console.log("Sever config not found, assuming it's first setup")
|
||||
}
|
||||
|
||||
async function getValue(key: string, storage: SettingStorage) {
|
||||
if (storage == 'setting') {
|
||||
return SettingService.getGlobal({ key })
|
||||
} else if (storage == 'config') {
|
||||
return serverConfig[key]
|
||||
}
|
||||
}
|
||||
initialOauths = (await SettingService.getGlobal({ key: 'oauths' })) ?? {}
|
||||
@@ -110,6 +102,9 @@
|
||||
if (values['base_url'] == undefined) {
|
||||
values['base_url'] = 'http://localhost'
|
||||
}
|
||||
if (values['smtp_connect'] == undefined) {
|
||||
values['smtp_connect'] = {}
|
||||
}
|
||||
loading = false
|
||||
|
||||
latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt()
|
||||
@@ -118,18 +113,6 @@
|
||||
export async function saveSettings() {
|
||||
if (values) {
|
||||
const allSettings = Object.values(settings).flatMap((x) => Object.entries(x))
|
||||
const newServerConfig = Object.fromEntries(
|
||||
allSettings
|
||||
.filter((x) => x[1].storage == 'config' && values?.[x[1].key] && values?.[x[1].key] != '')
|
||||
.map((x) => [x[1].key, values?.[x[1].key]])
|
||||
)
|
||||
if (!deepEqual(newServerConfig, serverConfig)) {
|
||||
await ConfigService.updateConfig({
|
||||
name: 'server',
|
||||
requestBody: newServerConfig
|
||||
})
|
||||
serverConfig = JSON.parse(JSON.stringify(newServerConfig))
|
||||
}
|
||||
let licenseKeySet = false
|
||||
await Promise.all(
|
||||
allSettings
|
||||
@@ -693,79 +676,102 @@
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'critical_error_channels'}
|
||||
<div class="w-full">
|
||||
<div class="flex max-w-md mt-1 gap-2 w-full items-center">
|
||||
<select disabled>
|
||||
<option>Tracing</option>
|
||||
</select>
|
||||
<input disabled />
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover"
|
||||
aria-label="Clear"
|
||||
disabled
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
<div class="w-full flex gap-x-16 flex-wrap">
|
||||
<div class="w-full max-w-lg">
|
||||
<div class="flex w-full max-w-lg mt-1 gap-2 w-full items-center">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Logs (critical errors are always logged)"
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if $enterpriseLicense && Array.isArray(values[setting.key])}
|
||||
{#each values[setting.key] ?? [] as v, i}
|
||||
<div class="flex w-full max-w-lg mt-1 gap-2 w-full items-center">
|
||||
<select
|
||||
class="w-20"
|
||||
on:change={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
values[setting.key][i] = {
|
||||
[e.target['value']]: ''
|
||||
}
|
||||
}
|
||||
}}
|
||||
value={v && 'slack_channel' in v ? 'slack_channel' : 'email'}
|
||||
>
|
||||
<option value="email">Email</option>
|
||||
<option value="slack_channel">Slack</option>
|
||||
</select>
|
||||
{#if v && 'slack_channel' in v}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Slack channel"
|
||||
on:input={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
values[setting.key][i] = {
|
||||
slack_channel: e.target['value']
|
||||
}
|
||||
}
|
||||
}}
|
||||
value={v?.slack_channel ?? ''}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
on:input={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
values[setting.key][i] = {
|
||||
email: e.target['value']
|
||||
}
|
||||
}
|
||||
}}
|
||||
value={v?.email ?? ''}
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
values[setting.key] = values[setting.key].filter(
|
||||
(_, index) => index !== i
|
||||
)
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{#if $enterpriseLicense && Array.isArray(values[setting.key])}
|
||||
{#each values[setting.key] ?? [] as v, i}
|
||||
<div class="flex max-w-md mt-1 gap-2 w-full items-center">
|
||||
<select
|
||||
on:change={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
values[setting.key][i] = {
|
||||
[e.target['value']]: ''
|
||||
}
|
||||
}
|
||||
}}
|
||||
value={v && 'slack_channel' in v ? 'slack_channel' : 'email'}
|
||||
>
|
||||
<option value="email">Email</option>
|
||||
<option value="slack_channel">Slack</option>
|
||||
</select>
|
||||
{#if v && 'slack_channel' in v}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Slack channel"
|
||||
on:input={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
values[setting.key][i] = {
|
||||
slack_channel: e.target['value']
|
||||
}
|
||||
}
|
||||
}}
|
||||
value={v?.slack_channel ?? ''}
|
||||
/>
|
||||
{:else}
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
on:input={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
values[setting.key][i] = {
|
||||
email: e.target['value']
|
||||
}
|
||||
}
|
||||
}}
|
||||
value={v?.email ?? ''}
|
||||
/>
|
||||
{/if}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
values[setting.key] = values[setting.key].filter(
|
||||
(_, index) => index !== i
|
||||
<div
|
||||
><div class="flex mt-1">
|
||||
<Button
|
||||
size="xs"
|
||||
variant="contained"
|
||||
on:click={async () => {
|
||||
try {
|
||||
await SettingService.testCriticalChannels({
|
||||
requestBody: values[setting.key]
|
||||
})
|
||||
sendUserToast(
|
||||
'Test message sent successfully to critical channels',
|
||||
false
|
||||
)
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
} catch (error) {
|
||||
sendUserToast(
|
||||
'Failed to send test message: ' + error.message,
|
||||
true
|
||||
)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Test Critical Channels
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mt-2 gap-20 items-baseline">
|
||||
<Button
|
||||
@@ -818,6 +824,65 @@
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if setting.fieldType == 'smtp_connect'}
|
||||
<div class="flex flex-col gap-4 mt-4">
|
||||
{#if values[setting.key]}
|
||||
<div>
|
||||
<label for="smtp_host" class="block text-sm font-medium">Host</label>
|
||||
<input
|
||||
type="text"
|
||||
id="smtp_host"
|
||||
placeholder="smtp.gmail.com"
|
||||
bind:value={values[setting.key].smtp_host}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="smtp_port" class="block text-sm font-medium">Port</label>
|
||||
<input
|
||||
type="number"
|
||||
id="smtp_port"
|
||||
placeholder="587"
|
||||
bind:value={values[setting.key].smtp_port}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="smtp_username" class="block text-sm font-medium"
|
||||
>Username</label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="smtp_username"
|
||||
placeholder="ruben@windmill.dev"
|
||||
bind:value={values[setting.key].smtp_username}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label for="smtp_password" class="block text-sm font-medium"
|
||||
>Password</label
|
||||
>
|
||||
<Password bind:password={values[setting.key].smtp_password} />
|
||||
</div>
|
||||
<div>
|
||||
<label for="smtp_from" class="block text-sm font-medium"
|
||||
>From Address</label
|
||||
>
|
||||
<input
|
||||
type="email"
|
||||
id="smtp_from"
|
||||
placeholder="noreply@windmill.dev"
|
||||
bind:value={values[setting.key].smtp_from}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Toggle
|
||||
id="smtp_tls_implicit"
|
||||
bind:checked={values[setting.key].smtp_tls_implicit}
|
||||
options={{ right: 'Implicit TLS' }}
|
||||
label="Implicit TLS"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if setting.fieldType == 'object_store_config'}
|
||||
<ObjectStoreConfigSettings bind:bucket_config={values[setting.key]} />
|
||||
<div class="mb-6" />
|
||||
@@ -863,21 +928,22 @@
|
||||
</div>
|
||||
</div>
|
||||
{#if category == 'SMTP'}
|
||||
{@const smtp = values['smtp_settings']}
|
||||
<div class="flex gap-4"
|
||||
><input type="email" bind:value={to} placeholder="contact@windmill.dev" />
|
||||
<Button
|
||||
disabled={to == ''}
|
||||
disabled={to == '' || !smtp}
|
||||
on:click={async () => {
|
||||
await SettingService.testSmtp({
|
||||
requestBody: {
|
||||
to,
|
||||
smtp: {
|
||||
host: values['smtp_host'],
|
||||
username: values['smtp_username'],
|
||||
password: values['smtp_password'],
|
||||
port: values['smtp_port'],
|
||||
from: values['smtp_from'],
|
||||
tls_implicit: values['smtp_tls_implicit']
|
||||
host: smtp['smtp_host'],
|
||||
username: smtp['smtp_username'],
|
||||
password: smtp['smtp_password'],
|
||||
port: smtp['smtp_port'],
|
||||
from: smtp['smtp_from'],
|
||||
tls_implicit: smtp['smtp_tls_implicit']
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
/>
|
||||
</div>
|
||||
{#if bucket_config}
|
||||
<div class="flex gap-2">
|
||||
<div class="flex gap-2 py-1">
|
||||
<Button
|
||||
spacingSize="sm"
|
||||
size="xs"
|
||||
@@ -123,81 +123,83 @@
|
||||
<Tab size="sm" value="S3">S3</Tab>
|
||||
<Tab size="sm" value="Azure">Azure Blob</Tab>
|
||||
</Tabs>
|
||||
{#if bucket_config.type === 'S3'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Bucket</span>
|
||||
<input type="text" placeholder="bucket-name" bind:value={bucket_config.bucket} />
|
||||
</label>
|
||||
<div class="flex flex-col gap-2 mt-2 p-2 border rounded-md">
|
||||
{#if bucket_config.type === 'S3'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Bucket</span>
|
||||
<input type="text" placeholder="bucket-name" bind:value={bucket_config.bucket} />
|
||||
</label>
|
||||
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Region</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>If left empty, will be derived automatically from $AWS_REGION</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.region} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Access key ID</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>If left empty, will be derived automatically from $AWS_ACCESS_KEY_ID, pod or ec2 profile</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.access_key} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Secret key</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>If left empty, will be derived automatically from $AWS_SECRET_KEY, pod or ec2 profile</span
|
||||
>
|
||||
<input type="password" autocomplete="new-password" bind:value={bucket_config.secret_key} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Endpoint</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>Only needed for non AWS S3 providers like R2 or MinIo</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.endpoint} />
|
||||
</label>
|
||||
<div class="block pb-2">
|
||||
<span class="text-tertiary text-2xs">Disable if using https only policy</span>
|
||||
<div>
|
||||
<Toggle bind:checked={bucket_config.allow_http} options={{ right: 'Allow http' }} />
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Region</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>If left empty, will be derived automatically from $AWS_REGION</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.region} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Access key ID</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>If left empty, will be derived automatically from $AWS_ACCESS_KEY_ID, pod or ec2 profile</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.access_key} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Secret key</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>If left empty, will be derived automatically from $AWS_SECRET_KEY, pod or ec2 profile</span
|
||||
>
|
||||
<input type="password" autocomplete="new-password" bind:value={bucket_config.secret_key} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Endpoint</span>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>Only needed for non AWS S3 providers like R2 or MinIo</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.endpoint} />
|
||||
</label>
|
||||
<div class="block pb-2">
|
||||
<span class="text-tertiary text-2xs">Disable if using https only policy</span>
|
||||
<div>
|
||||
<Toggle bind:checked={bucket_config.allow_http} options={{ right: 'Allow http' }} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else if bucket_config.type === 'Azure'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Account name</span>
|
||||
<input type="text" placeholder="account-name" bind:value={bucket_config.accountName} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Container name</span>
|
||||
<input type="text" placeholder="container-name" bind:value={bucket_config.containerName} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Access key</span>
|
||||
<input type="password" autocomplete="new-password" bind:value={bucket_config.accessKey} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm"
|
||||
>Tenant ID <span class="text-2xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.tenantId} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm"
|
||||
>Client ID <span class="text-2xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.clientId} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm"
|
||||
>Endpoint <span class="text-2xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>Only needed for non Azure Blob providers like Azurite</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.endpoint} />
|
||||
</label>
|
||||
{:else}
|
||||
<div>Unknown bucket type {bucket_config['type']}</div>
|
||||
{/if}
|
||||
{:else if bucket_config.type === 'Azure'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Account name</span>
|
||||
<input type="text" placeholder="account-name" bind:value={bucket_config.accountName} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Container name</span>
|
||||
<input type="text" placeholder="container-name" bind:value={bucket_config.containerName} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Access key</span>
|
||||
<input type="password" autocomplete="new-password" bind:value={bucket_config.accessKey} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm"
|
||||
>Tenant ID <span class="text-2xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.tenantId} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm"
|
||||
>Client ID <span class="text-2xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.clientId} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm"
|
||||
>Endpoint <span class="text-2xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<span class="text-tertiary text-2xs"
|
||||
>Only needed for non Azure Blob providers like Azurite</span
|
||||
>
|
||||
<input type="text" bind:value={bucket_config.endpoint} />
|
||||
</label>
|
||||
{:else}
|
||||
<div>Unknown bucket type {bucket_config['type']}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
// @ts-nocheck
|
||||
import { onMount } from 'svelte'
|
||||
export let password: string
|
||||
export let password: string | undefined
|
||||
export let placeholder = '******'
|
||||
export let disabled = false
|
||||
export let required = false
|
||||
|
||||
@@ -19,13 +19,14 @@ export interface Setting {
|
||||
| 'object_store_config'
|
||||
| 'critical_error_channels'
|
||||
| 'slack_connect'
|
||||
| 'smtp_connect'
|
||||
storage: SettingStorage
|
||||
isValid?: (value: any) => boolean
|
||||
error?: string
|
||||
defaultValue?: () => any
|
||||
}
|
||||
|
||||
export type SettingStorage = 'setting' | 'config'
|
||||
export type SettingStorage = 'setting'
|
||||
|
||||
export const settings: Record<string, Setting[]> = {
|
||||
Core: [
|
||||
@@ -76,7 +77,7 @@ export const settings: Record<string, Setting[]> = {
|
||||
cloudonly: true,
|
||||
fieldType: 'seconds',
|
||||
placeholder: '60',
|
||||
storage: 'config'
|
||||
storage: 'setting'
|
||||
},
|
||||
{
|
||||
label: 'License key',
|
||||
@@ -188,45 +189,12 @@ export const settings: Record<string, Setting[]> = {
|
||||
}
|
||||
],
|
||||
SMTP: [
|
||||
{
|
||||
label: 'Host',
|
||||
key: 'smtp_host',
|
||||
fieldType: 'text',
|
||||
placeholder: 'smtp.gmail.com',
|
||||
storage: 'config'
|
||||
},
|
||||
{
|
||||
label: 'Port',
|
||||
key: 'smtp_port',
|
||||
fieldType: 'number',
|
||||
placeholder: '587',
|
||||
storage: 'config'
|
||||
},
|
||||
{
|
||||
label: 'Username',
|
||||
key: 'smtp_username',
|
||||
fieldType: 'text',
|
||||
placeholder: 'ruben@windmill.dev',
|
||||
storage: 'config'
|
||||
},
|
||||
{
|
||||
label: 'Password',
|
||||
key: 'smtp_password',
|
||||
fieldType: 'password',
|
||||
storage: 'config'
|
||||
},
|
||||
{
|
||||
label: 'From Address',
|
||||
key: 'smtp_from',
|
||||
placeholder: 'noreply@windmill.dev',
|
||||
fieldType: 'email',
|
||||
storage: 'config'
|
||||
},
|
||||
{
|
||||
label: 'Implicit TLS',
|
||||
key: 'smtp_tls_implicit',
|
||||
fieldType: 'boolean',
|
||||
storage: 'config'
|
||||
{
|
||||
label: 'SMTP',
|
||||
key: 'smtp_settings',
|
||||
fieldType: 'smtp_connect',
|
||||
storage: 'setting',
|
||||
ee_only: '',
|
||||
}
|
||||
],
|
||||
Slack: [
|
||||
|
||||
Reference in New Issue
Block a user