diff --git a/backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json b/backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json new file mode 100644 index 0000000000..b56784fd64 --- /dev/null +++ b/backend/.sqlx/query-08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO variable\n (workspace_id, path, value, is_secret, account, is_oauth, expires_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ON CONFLICT (workspace_id, path) DO UPDATE SET value = $3, expires_at = $7", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Varchar", + "Varchar", + "Bool", + "Int4", + "Bool", + "Timestamptz" + ] + }, + "nullable": [] + }, + "hash": "08dd2ea6b17a52bce352d6443d7d009cfc9da0d3b2bd1f40d422b550779e5324" +} diff --git a/backend/.sqlx/query-44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b.json b/backend/.sqlx/query-44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b.json new file mode 100644 index 0000000000..18c572b1fb --- /dev/null +++ b/backend/.sqlx/query-44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b.json @@ -0,0 +1,34 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT value, is_secret, expires_at FROM variable WHERE path = $1", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value", + "type_info": "Varchar" + }, + { + "ordinal": 1, + "name": "is_secret", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "expires_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false, + false, + true + ] + }, + "hash": "44a12919fb154055f1142cc078ef131f8a0c9cdb37cfba6283a6718480b02a4b" +} diff --git a/backend/.sqlx/query-84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf.json b/backend/.sqlx/query-84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf.json new file mode 100644 index 0000000000..e4473b360c --- /dev/null +++ b/backend/.sqlx/query-84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf.json @@ -0,0 +1,20 @@ +{ + "db_name": "PostgreSQL", + "query": "SELECT value FROM global_settings WHERE name = 'teams'", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "value", + "type_info": "Jsonb" + } + ], + "parameters": { + "Left": [] + }, + "nullable": [ + false + ] + }, + "hash": "84d048ea323758842dc564c700b524d1a5b196a7b77afcb02f46b84b22088bbf" +} diff --git a/backend/.sqlx/query-9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14.json b/backend/.sqlx/query-9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14.json new file mode 100644 index 0000000000..a2f8a647c5 --- /dev/null +++ b/backend/.sqlx/query-9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "INSERT INTO global_settings (name, value)\n VALUES ('teams', $1)\n ON CONFLICT (name) DO UPDATE SET value = $1", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "9c72b2962d0919353cfe5af710e857d432dff44e343b8f0610208d42ff5afd14" +} diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 2f8cf983a0..9f385d12b5 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -ddcfdfc18a9833a5fc4e62ad62a265ef1e06a0aa \ No newline at end of file +0c89b8974ff6e1c9eda2134f09d4a03f18b57c15 diff --git a/backend/migrations/20250115223046_teams_global_settings.down.sql b/backend/migrations/20250115223046_teams_global_settings.down.sql new file mode 100644 index 0000000000..93b9a533ba --- /dev/null +++ b/backend/migrations/20250115223046_teams_global_settings.down.sql @@ -0,0 +1 @@ +DELETE FROM global_settings WHERE name = 'teams'; \ No newline at end of file diff --git a/backend/migrations/20250115223046_teams_global_settings.up.sql b/backend/migrations/20250115223046_teams_global_settings.up.sql new file mode 100644 index 0000000000..b21532515b --- /dev/null +++ b/backend/migrations/20250115223046_teams_global_settings.up.sql @@ -0,0 +1 @@ +INSERT INTO global_settings (name, value) VALUES ('teams', '{}'); \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 2428c57fd4..c262d7d8ab 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -40,7 +40,7 @@ use windmill_common::{ NUGET_CONFIG_SETTING, OAUTH_SETTING, OTEL_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, SMTP_SETTING, - TIMEOUT_WAIT_RESULT_SETTING, + TIMEOUT_WAIT_RESULT_SETTING, TEAMS_SETTING }, scripts::ScriptLang, stats_ee::schedule_stats, @@ -734,6 +734,9 @@ Windmill Community Edition {GIT_VERSION} SMTP_SETTING => { reload_smtp_config(&db).await; }, + TEAMS_SETTING => { + tracing::info!("Teams setting changed."); + }, INDEXER_SETTING => { reload_indexer_config(&db).await; }, diff --git a/backend/src/monitor.rs b/backend/src/monitor.rs index 0aeb29df56..59a625fc2a 100644 --- a/backend/src/monitor.rs +++ b/backend/src/monitor.rs @@ -1479,7 +1479,7 @@ pub async fn reload_base_url_setting(db: &DB) -> error::Result<()> { #[cfg(feature = "oauth2")] { let mut l = windmill_api::OAUTH_CLIENTS.write().await; - *l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths) + *l = windmill_api::oauth2_ee::build_oauth_clients(&base_url, oauths, db).await .map_err(|e| tracing::error!("Error building oauth clients (is the oauth.json mounted and in correct format? Use '{}' as minimal oauth.json): {}", "{}", e)) .unwrap(); } diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index bc81195219..dd7fc1877c 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -3234,6 +3234,22 @@ paths: type: array items: type: string + + /teams/sync: + post: + operationId: syncTeams + summary: synchronize Microsoft Teams information (teams/channels) + tags: + - teams + responses: + '200': + description: Teams information successfully synchronized + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/TeamInfo' /w/{workspace}/resources/create: post: @@ -14012,3 +14028,49 @@ components: format: date-time required: - trigger_kind + + TeamInfo: + type: object + required: + - team_id + - team_name + - channels + properties: + team_id: + type: string + description: The unique identifier of the Microsoft Teams team + example: "19:abc123def456@thread.tacv2" + team_name: + type: string + description: The display name of the Microsoft Teams team + example: "Engineering Team" + channels: + type: array + description: List of channels within the team + items: + $ref: '#/components/schemas/ChannelInfo' + + ChannelInfo: + type: object + required: + - channel_id + - channel_name + - tenant_id + - service_url + properties: + channel_id: + type: string + description: The unique identifier of the channel + example: "19:channel123@thread.tacv2" + channel_name: + type: string + description: The display name of the channel + example: "General" + tenant_id: + type: string + description: The Microsoft Teams tenant identifier + example: "12345678-1234-1234-1234-123456789012" + service_url: + type: string + description: The service URL for the channel + example: "https://smba.trafficmanager.net/amer/12345678-1234-1234-1234-123456789012/" diff --git a/backend/windmill-api/src/ai.rs b/backend/windmill-api/src/ai.rs index 32c1f4e4dc..234a1fe73a 100644 --- a/backend/windmill-api/src/ai.rs +++ b/backend/windmill-api/src/ai.rs @@ -1,7 +1,5 @@ -use crate::{ - db::{ApiAuthed, DB}, - variables::decrypt, -}; +use crate::db::{ApiAuthed, DB}; +use windmill_common::variables::decrypt; use anthropic::AnthropicCache; use axum::{ body::Bytes, diff --git a/backend/windmill-api/src/apps.rs b/backend/windmill-api/src/apps.rs index a8cf4a5bde..f2e340394e 100644 --- a/backend/windmill-api/src/apps.rs +++ b/backend/windmill-api/src/apps.rs @@ -13,10 +13,10 @@ use crate::{ resources::get_resource_value_interpolated_internal, users::{require_owner_of_path, OptAuthed}, utils::WithStarredInfoQuery, - variables::encrypt, webhook_util::{WebhookMessage, WebhookShared}, HTTP_CLIENT, }; +use windmill_common::variables::encrypt; #[cfg(feature = "parquet")] use crate::{ job_helpers_ee::{ diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index 08b6df7c10..4734b3a3ab 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -96,6 +96,8 @@ mod scripts; mod service_logs; mod settings; mod slack_approvals; +#[cfg(feature = "enterprise")] +mod teams_ee; #[cfg(feature = "smtp")] mod smtp_server_ee; mod static_assets; @@ -435,6 +437,17 @@ pub async fn run_server( jobs::workspace_unauthed_service().layer(cors.clone()), ) .route("/slack", post(slack_approvals::slack_app_callback_handler)) + .nest("/teams", { + #[cfg(feature = "enterprise")] + { + teams_ee::teams_service() + } + + #[cfg(not(feature = "enterprise"))] + { + Router::new() + } + }) .route( "/w/:workspace_id/jobs/slack_approval/:job_id", get(slack_approvals::request_slack_approval), diff --git a/backend/windmill-api/src/oauth2_ee.rs b/backend/windmill-api/src/oauth2_ee.rs index 4648873431..49d2155cf3 100644 --- a/backend/windmill-api/src/oauth2_ee.rs +++ b/backend/windmill-api/src/oauth2_ee.rs @@ -80,9 +80,10 @@ pub struct AllClients { } #[cfg(feature = "oauth2")] -pub fn build_oauth_clients( +pub async fn build_oauth_clients( _base_url: &str, _oauths_from_config: Option>, + _db: &DB, ) -> anyhow::Result { // Implementation is not open source return Ok(AllClients { diff --git a/backend/windmill-api/src/teams_ee.rs b/backend/windmill-api/src/teams_ee.rs new file mode 100644 index 0000000000..2933d4214c --- /dev/null +++ b/backend/windmill-api/src/teams_ee.rs @@ -0,0 +1,5 @@ +use axum::Router; + +pub fn teams_service() -> Router { + Router::new() +} \ No newline at end of file diff --git a/backend/windmill-api/src/variables.rs b/backend/windmill-api/src/variables.rs index 3c51694b9e..aaf3b1dd49 100644 --- a/backend/windmill-api/src/variables.rs +++ b/backend/windmill-api/src/variables.rs @@ -31,7 +31,7 @@ use windmill_common::{ }; use lazy_static::lazy_static; -use magic_crypt::{MagicCrypt256, MagicCryptError, MagicCryptTrait}; +use windmill_common::variables::{decrypt, encrypt}; use serde::Deserialize; use sqlx::{Postgres, Transaction}; use windmill_git_sync::{handle_deployment_metadata, DeployedObject}; @@ -676,17 +676,3 @@ pub async fn get_value_internal<'c>( Ok(r) } - -pub fn encrypt(mc: &MagicCrypt256, value: &str) -> String { - mc.encrypt_str_to_base64(value) -} - -pub fn decrypt(mc: &MagicCrypt256, value: String) -> Result { - mc.decrypt_base64_to_string(value).map_err(|e| match e { - MagicCryptError::DecryptError(_) => Error::InternalErr( - "Could not decrypt value. The value may have been encrypted with a different key." - .to_string(), - ), - _ => Error::InternalErr(e.to_string()), - }) -} diff --git a/backend/windmill-api/src/workspaces.rs b/backend/windmill-api/src/workspaces.rs index 5c8ed2fd87..144d58f3ad 100644 --- a/backend/windmill-api/src/workspaces.rs +++ b/backend/windmill-api/src/workspaces.rs @@ -52,7 +52,7 @@ use windmill_git_sync::handle_deployment_metadata; #[cfg(feature = "enterprise")] use windmill_common::utils::require_admin_or_devops; -use crate::variables::{decrypt, encrypt}; +use windmill_common::variables::{decrypt, encrypt}; use hyper::StatusCode; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Postgres, Transaction}; diff --git a/backend/windmill-api/src/workspaces_export.rs b/backend/windmill-api/src/workspaces_export.rs index 578e170c1c..70fea1ea5a 100644 --- a/backend/windmill-api/src/workspaces_export.rs +++ b/backend/windmill-api/src/workspaces_export.rs @@ -36,7 +36,7 @@ use windmill_common::{ variables::ExportableListableVariable, }; -use crate::variables::decrypt; +use windmill_common::variables::decrypt; use hyper::header; use serde::{Deserialize, Serialize}; use serde_json::Value; diff --git a/backend/windmill-common/src/global_settings.rs b/backend/windmill-common/src/global_settings.rs index 9e5c308e65..01ca59752f 100644 --- a/backend/windmill-common/src/global_settings.rs +++ b/backend/windmill-common/src/global_settings.rs @@ -17,6 +17,7 @@ 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 TEAMS_SETTING: &str = "teams"; pub const INDEXER_SETTING: &str = "indexer_settings"; pub const TIMEOUT_WAIT_RESULT_SETTING: &str = "timeout_wait_result"; @@ -94,3 +95,21 @@ pub const ENV_SETTINGS: [&str; 54] = [ "OTEL_TRACING", "OTEL_LOGS", ]; + +use crate::error; +use sqlx::Pool; +use sqlx::postgres::Postgres; + +pub async fn load_value_from_global_settings( + db: &Pool, + setting_name: &str, +) -> error::Result> { + let r = sqlx::query!( + "SELECT value FROM global_settings WHERE name = $1", + setting_name + ) + .fetch_optional(db) + .await? + .map(|x| x.value); + Ok(r) +} \ No newline at end of file diff --git a/backend/windmill-common/src/lib.rs b/backend/windmill-common/src/lib.rs index 9fcd14111f..4c33fae7b0 100644 --- a/backend/windmill-common/src/lib.rs +++ b/backend/windmill-common/src/lib.rs @@ -36,6 +36,7 @@ pub mod job_s3_helpers_ee; pub mod jobs; pub mod more_serde; pub mod oauth2; +pub mod teams_ee; pub mod otel_ee; pub mod queue; pub mod s3_helpers; diff --git a/backend/windmill-common/src/oauth2.rs b/backend/windmill-common/src/oauth2.rs index 46cf727b86..ed78514cdf 100644 --- a/backend/windmill-common/src/oauth2.rs +++ b/backend/windmill-common/src/oauth2.rs @@ -18,6 +18,9 @@ pub const WORKSPACE_SLACK_BOT_TOKEN_PATH: &str = "f/slack_bot/bot_token"; pub const GLOBAL_SLACK_BOT_TOKEN_PATH: &str = "f/slack_bot/global_bot_token"; +pub const GLOBAL_TEAMS_BOT_TOKEN_PATH: &str = "f/teams_bot/global_bot_token"; + +pub const GLOBAL_TEAMS_API_TOKEN_PATH: &str = "f/teams_bot/global_api_token"; lazy_static::lazy_static! { pub static ref REQUIRE_PREEXISTING_USER_FOR_OAUTH: AtomicBool = AtomicBool::new(std::env::var("REQUIRE_PREEXISTING_USER_FOR_OAUTH") diff --git a/backend/windmill-common/src/teams_ee.rs b/backend/windmill-common/src/teams_ee.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/backend/windmill-common/src/variables.rs b/backend/windmill-common/src/variables.rs index 26cbca7f08..7cb96d1927 100644 --- a/backend/windmill-common/src/variables.rs +++ b/backend/windmill-common/src/variables.rs @@ -9,6 +9,7 @@ use chrono::{SecondsFormat, Utc}; use magic_crypt::{MagicCrypt256, MagicCryptError, MagicCryptTrait}; use serde::{Deserialize, Serialize}; +use crate::error; use crate::{worker::WORKER_GROUP, BASE_URL, DB}; @@ -157,6 +158,20 @@ pub async fn decrypt_value_with_mc( })?) } +pub fn encrypt(mc: &MagicCrypt256, value: &str) -> String { + mc.encrypt_str_to_base64(value) +} + +pub fn decrypt(mc: &MagicCrypt256, value: String) -> error::Result { + mc.decrypt_base64_to_string(value).map_err(|e| match e { + MagicCryptError::DecryptError(_) => error::Error::InternalErr( + "Could not decrypt value. The value may have been encrypted with a different key." + .to_string(), + ), + _ => error::Error::InternalErr(e.to_string()), + }) +} + pub const WM_SCHEDULED_FOR: &str = "WM_SCHEDULED_FOR"; pub async fn get_reserved_variables( diff --git a/frontend/src/lib/components/AuthSettings.svelte b/frontend/src/lib/components/AuthSettings.svelte index 7966197ef7..195e760114 100644 --- a/frontend/src/lib/components/AuthSettings.svelte +++ b/frontend/src/lib/components/AuthSettings.svelte @@ -38,6 +38,7 @@ 'visma', 'spotify', 'snowflake_oauth', + 'teams', 'xero' ] @@ -169,10 +170,12 @@
+ +
{#each Object.keys(oauths) as k} {#if oauths[k] && !('login_config' in oauths[k])} - {#if !['slack'].includes(k) && oauths[k]} + {#if !['slack', 'teams'].includes(k) && oauths[k]}
diff --git a/frontend/src/lib/components/IconedResourceType.svelte b/frontend/src/lib/components/IconedResourceType.svelte index 85d8d4c061..ea539681a3 100644 --- a/frontend/src/lib/components/IconedResourceType.svelte +++ b/frontend/src/lib/components/IconedResourceType.svelte @@ -9,7 +9,10 @@ export let center = false export let isSelected = false export let formatExtension = undefined - $: iconComponent = APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]] + + $: iconComponent = name === "teams" + ? APP_TO_ICON_COMPONENT.ms_teams_webhook + : APP_TO_ICON_COMPONENT[name] || APP_TO_ICON_COMPONENT[name.split('_')[0]]
diff --git a/frontend/src/lib/components/InstanceSetting.svelte b/frontend/src/lib/components/InstanceSetting.svelte index 5f909b6e5a..a7e41f5876 100644 --- a/frontend/src/lib/components/InstanceSetting.svelte +++ b/frontend/src/lib/components/InstanceSetting.svelte @@ -8,6 +8,7 @@ BadgeX, Info, Plus, + RefreshCcw, Slack, X } from 'lucide-svelte' @@ -16,7 +17,7 @@ import ObjectStoreConfigSettings from './ObjectStoreConfigSettings.svelte' import { sendUserToast } from '$lib/toast' import ConfirmButton from './ConfirmButton.svelte' - import { IndexSearchService, SettingService } from '$lib/gen' + import { IndexSearchService, SettingService, TeamsService } from '$lib/gen' import { Button, SecondsInput, Skeleton } from './common' import Password from './Password.svelte' import { classNames } from '$lib/utils' @@ -40,6 +41,8 @@ attempted_at: string } | null + let isFetching = false + function showSetting(setting: string, values: Record) { if (setting == 'dev_instance') { if (values['license_key'] == undefined) { @@ -111,6 +114,49 @@ valid: false } } + + async function fetchTeams() { + if (isFetching) return + isFetching = true + try { + $values['teams'] = await TeamsService.syncTeams() + } catch (error) { + console.error('Error fetching teams:', error) + } finally { + isFetching = false + } + } + + function handleTeamChange(event: Event, i: number) { + const teamId = (event.target as HTMLSelectElement).value + const team = $values['teams'].find((team) => team.team_id === teamId) || null + $values['critical_error_channels'][i] = { + teams_channel: { + team_id: team?.team_id, + team_name: team?.team_name, + channel_id: team?.channels[0]?.channel_id, + channel_name: team?.channels[0]?.channel_name + } + } + } + + function handleChannelChange(event: Event, setting: Setting, i: number) { + const channelId = (event.target as HTMLSelectElement).value + const team = $values['teams'].find( + (team) => team.team_id === $values['critical_error_channels'][i]?.teams_channel?.team_id + ) + const channel = team?.channels.find((channel) => channel.channel_id === channelId) || null + if (channelId) { + $values['critical_error_channels'][i] = { + teams_channel: { + team_id: team?.team_id, + team_name: team?.team_name, + channel_id: channel?.channel_id, + channel_name: channel?.channel_name + } + } + } + } {#if (!setting.cloudonly || isCloudHosted()) && showSetting(setting.key, $values) && !(setting.hiddenIfNull && $values[setting.key] == null)} @@ -339,7 +385,7 @@ {#each $values[setting.key] ?? [] as v, i}
{#if v && 'slack_channel' in v} + {:else if v && 'teams_channel' in v} +
+ + {#if $values['critical_error_channels'][i]?.teams_channel?.team_id} + + {/if} +
+ +
+
{:else} { + if (el.teams_channel) { + const team = teams.find((team) => team.team_name === el.teams_channel.team_name) || null + return { + teams_channel: { + team_id: team?.team_id, + team_name: team?.team_name, + channel_id: team?.channels.find( + (channel) => channel.channel_id === el.teams_channel.channel_id + )?.channel_id, + channel_name: team?.channels.find( + (channel) => channel.channel_id === el.teams_channel.channel_id + )?.channel_name + } + } + } + return el + }) + } + $values = nvalues loading = false diff --git a/frontend/src/lib/components/OAuthSetting.svelte b/frontend/src/lib/components/OAuthSetting.svelte index ddeca32788..38533d690b 100644 --- a/frontend/src/lib/components/OAuthSetting.svelte +++ b/frontend/src/lib/components/OAuthSetting.svelte @@ -12,7 +12,7 @@ $: enabled = value != undefined let tenant: string = '' - $: name == 'microsoft' && changeTenantId(tenant) + $: (name == 'microsoft' || name == 'teams') && changeTenantId(tenant) onMount(() => { try { @@ -24,6 +24,9 @@ ) { tenant = value['login_config']['auth_url'].split('/')[3] } + if (name === 'teams' && value?.tenant) { + tenant = value.tenant + } } catch (e) { console.error('Could not set tenantId', e) } @@ -32,13 +35,20 @@ function changeTenantId(tenant: string) { if (value && tenant) { if (tenant != '') { - value = { - ...value, - login_config: { - auth_url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`, - token_url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`, - userinfo_url: `https://graph.microsoft.com/oidc/userinfo`, - scopes: ['openid', 'profile', 'email'] + if (name === "teams") { + value = { + ...value, + tenant + } + } else { + value = { + ...value, + login_config: { + auth_url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/authorize`, + token_url: `https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`, + userinfo_url: `https://graph.microsoft.com/oidc/userinfo`, + scopes: ['openid', 'profile', 'email'] + } } } } else { @@ -67,7 +77,7 @@ > {#if enabled}
- {#if name != 'slack'} + {#if name != 'slack' && name != 'teams'} - {#if name == 'microsoft'} + {#if name == 'microsoft' || name == 'teams'}
+ {:else if name == 'teams'} + +
+ Follow this guide on Windmill Docs to create a new Microsoft Teams App. Then paste Client ID, Tenant ID, and Client Secret here. +
+
{/if}
{/if} diff --git a/frontend/src/lib/components/instanceSettings.ts b/frontend/src/lib/components/instanceSettings.ts index 462d8ca8b0..a0d7982c81 100644 --- a/frontend/src/lib/components/instanceSettings.ts +++ b/frontend/src/lib/components/instanceSettings.ts @@ -271,7 +271,7 @@ export const settings: Record = { { label: 'Critical alert channels', description: - 'Channels to send critical alerts to. SMTP and Slack must be configured below. Learn more', + 'Channels to send critical alerts to. SMTP, Slack or Microsoft Teams must be configured below. Learn more', key: 'critical_error_channels', fieldType: 'critical_error_channels', storage: 'setting',