From 2b5e505a1099a167b96da23d88923f618e9df591 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Wed, 27 Nov 2024 21:58:28 +0000 Subject: [PATCH] Add support for new SMTP config schema --- shared/studio/tabs/auth/authAdmin.module.scss | 135 ++++ shared/studio/tabs/auth/providers.tsx | 28 +- shared/studio/tabs/auth/smtp.tsx | 642 +++++++++++++----- shared/studio/tabs/auth/state/index.tsx | 314 +++++++-- 4 files changed, 882 insertions(+), 237 deletions(-) diff --git a/shared/studio/tabs/auth/authAdmin.module.scss b/shared/studio/tabs/auth/authAdmin.module.scss index f6d0525a..2fe9dc13 100644 --- a/shared/studio/tabs/auth/authAdmin.module.scss +++ b/shared/studio/tabs/auth/authAdmin.module.scss @@ -475,6 +475,141 @@ resize: vertical; } +.newDraftSMTPProvider { + display: flex; + flex-direction: column; + width: 100%; + + .configGrid { + margin-top: 0; + } + + .buttons { + display: flex; + gap: 16px; + justify-content: flex-end; + margin-top: 24px; + flex-direction: row; + } +} + +.emailProvidersUpdating { + opacity: 0.7; + pointer-events: none; +} + +.emailProviderCard { + .details { + display: flex; + flex-direction: column; + color: var(--main_text_color); + line-height: 22px; + + .name { + font-weight: 500; + } + + .senderhost { + color: var(--tertiary_text_color); + + span { + font-size: 11px; + margin: 0 6px; + opacity: 0.5; + } + } + } + + .selectCurrentProvider { + width: 32px; + height: 32px; + stroke-width: 1px; + stroke: var(--Grey75); + cursor: pointer; + flex-shrink: 0; + + &.selected { + fill: #a565cd; + stroke: #9c56b4 !important; + pointer-events: none; + } + + @include darkTheme { + stroke: var(--Grey40); + } + } + + .updatingSpinner { + color: var(--Grey50); + width: 32px; + flex-shrink: 0; + } + + .buttons { + display: flex; + gap: 16px; + justify-content: flex-end; + flex-direction: row; + padding: 4px 16px 16px 16px; + } +} + +.expandedEmailProviderConfig { + margin: -2px 12px 12px 12px; + background: var(--Grey97); + border-radius: 8px; + border: 1px solid var(--Grey93); + + @include darkTheme { + background: var(--Grey16); + border-color: var(--Grey25); + } + + .configGrid { + margin-top: 20px; + margin-bottom: 12px; + + @include isMobile { + margin: 16px; + } + } +} + +.emailProviderWarning { + position: relative; + padding: 16px 20px; + padding-left: 48px; + background: #f7e9c8; + border-radius: 8px; + border: 1px solid #c1a970; + color: #7b6226; + font-weight: 450; + line-height: 20px; + margin-top: 16px; + + & > svg { + position: absolute; + left: 16px; + top: 14px; + } + + & > span { + font-weight: 500; + } + + .link { + font-weight: inherit; + cursor: pointer; + text-decoration: underline; + } + + @include darkTheme { + background: #453d2c; + border: 1px solid #72623b; + color: #d1bd8f; + } +} + .docsNote { display: flex; align-items: flex-start; diff --git a/shared/studio/tabs/auth/providers.tsx b/shared/studio/tabs/auth/providers.tsx index 4b756dab..d2ab3d84 100644 --- a/shared/studio/tabs/auth/providers.tsx +++ b/shared/studio/tabs/auth/providers.tsx @@ -28,6 +28,7 @@ import { Select, SelectItem, TextInput, + WarningIcon, } from "@edgedb/common/newui"; import styles from "./authAdmin.module.scss"; @@ -50,11 +51,28 @@ export const ProvidersTab = observer(function ProvidersTab() { {state.providers ? ( <> {state.providers.length ? ( -
- {state.providers.map((provider) => ( - - ))} -
+ <> + {state.noEmailProviderWarning ? ( +
+ + Warning: You have enabled auth providers + requiring email, but no SMTP provider is configured. +
+ state.setSelectedTab("smtp")} + > + Enable an SMTP provider + +
+ ) : null} + +
+ {state.providers.map((provider) => ( + + ))} +
+ ) : null} {state.draftProviderConfig ? ( diff --git a/shared/studio/tabs/auth/smtp.tsx b/shared/studio/tabs/auth/smtp.tsx index 68a7c4e8..dae88a1c 100644 --- a/shared/studio/tabs/auth/smtp.tsx +++ b/shared/studio/tabs/auth/smtp.tsx @@ -1,253 +1,521 @@ import {observer} from "mobx-react-lite"; import {useTabState} from "../../state"; -import {AuthAdminState, smtpSecurity, SMTPSecurity} from "./state"; +import { + AuthAdminState, + DraftSMTPConfig, + EmailProviderConfig, + smtpSecurity, + SMTPSecurity, +} from "./state"; import cn from "@edgedb/common/utils/classNames"; -import {Checkbox, FieldHeader, Select, TextInput} from "@edgedb/common/newui"; +import { + Button, + Checkbox, + ChevronDownIcon, + ConfirmButton, + FieldHeader, + Select, + TextInput, + WarningIcon, +} from "@edgedb/common/newui"; +import {LoadingSkeleton} from "@edgedb/common/newui/loadingSkeleton"; import styles from "./authAdmin.module.scss"; import {InputSkeleton, StickyFormControls} from "./shared"; +import Spinner from "@edgedb/common/ui/spinner"; +import {useState} from "react"; export const SMTPConfigTab = observer(function SMTPConfigTab() { const state = useTabState(AuthAdminState); - const loaded = state.smtpConfig != null; + let content: JSX.Element | null = null; + if (state.newSMTPSchema) { + const newDraftState = state.draftSMTPConfigs.get(""); - const smtp = state.draftSMTPConfig; + content = state.emailProviders ? ( + <> + {state.emailProviders.length ? ( + <> + {state.noEmailProviderWarning ? ( +
+ + Warning: None of the configured SMTP providers + have been selected. Select a provider below to enable email + sending (eg. email verification, password reset emails, etc.) + in the auth extension. +
+ ) : null} - const security = smtp.getConfigValue("security") as unknown as SMTPSecurity; +
+ {state.emailProviders.map((provider) => ( + + ))} +
+ + ) : null} + + {newDraftState ? ( +
+ +
+ ) : ( +
+ +
+ )} + + ) : ( +
+ + +
+ ); + } else { + const draftState = state.draftSMTPConfigs.get(""); + + content = draftState ? ( + <> + + + + + ) : null; + } return (

SMTP Configuration

-
-
-
- -
+ {content} +
+ ); +}); -
- {loaded ? ( - smtp.setConfigValue("sender", e.target.value)} +const EmailProviderCard = observer(function EmailProviderCard({ + state, + provider, +}: { + state: AuthAdminState; + provider: EmailProviderConfig; +}) { + const [updating, setUpdating] = useState(false); + + const isSelectedProvider = state.currentEmailProvider === provider.name; + const draftState = state.draftSMTPConfigs.get(provider.name); + + return ( +
+
+ {updating ? ( + + ) : ( + { + setUpdating(true); + state + .setCurrentEmailProvider(provider.name) + .finally(() => setUpdating(false)); + }} + > + + {isSelectedProvider ? ( + - ) : ( - - )} + ) : null} + + )} +
+
{provider.name}
+ {provider._typename === "cfg::SMTPProviderConfig" ? ( +
+ {provider.sender} {provider.host || "localhost"}: + {provider.port || + (provider.security === "PlainText" + ? "25" + : provider.security === "TLS" + ? "465" + : provider.security === "STARTTLS" + ? "587" + : "587/25")} +
+ ) : null} +
+
+ draftState + ? draftState.toggleExpanded() + : state.addDraftSMTPProvider(provider) + } + > + +
+
+ + {draftState?.expanded ? ( + <> +
+
-
- "From" address of system emails sent for e.g. password reset, etc. + +
+ state.removeEmailProvider(provider.name)} + > + Remove + + + {draftState.formChanged ? ( + + ) : null} +
-
+ + ) : null} +
+ ); +}); + +const NewDraftSMTPProviderForm = observer(function NewDraftSMTPProviderForm({ + state, + draftState, +}: { + state: AuthAdminState; + draftState: DraftSMTPConfig; +}) { + return ( +
+ + +
+ + +
+
+ ); +}); +const SMTPConfigForm = observer(function SMTPConfigForm({ + hasName, + newProvider, + loaded, + smtp, +}: { + hasName?: boolean; + newProvider?: boolean; + loaded: boolean; + smtp: DraftSMTPConfig; +}) { + const security = smtp.getConfigValue("security") as unknown as SMTPSecurity; + + return ( +
+ {hasName ? (
- +
{loaded ? ( smtp.setConfigValue("host", e.target.value)} - placeholder="localhost" + value={smtp.getConfigValue("name")} + onChange={(e) => smtp.setConfigValue("name", e.target.value)} + error={smtp.nameError} /> ) : ( )}
- Host of SMTP server to use for sending emails. If not set, - "localhost" will be used. + The name of the email provider. Must be unique.
+ ) : null} -
-
- -
+
+
+ +
-
- {loaded ? ( - smtp.setConfigValue("port", e.target.value)} - placeholder={ - security === "STARTTLSOrPlainText" - ? "587 or 25" - : security === "TLS" - ? "465" - : security === "STARTTLS" - ? "587" - : "25" - } - error={smtp.portError} - size={10} - /> - ) : ( - - )} -
-
- Port of SMTP server to use for sending emails. If not set, common - defaults will be used depending on security: 465 for TLS, 587 for - STARTTLS, 25 otherwise. -
+
+ {loaded ? ( + smtp.setConfigValue("sender", e.target.value)} + error={smtp.senderError} + /> + ) : ( + + )} +
+
+ "From" address of system emails sent for e.g. password reset, etc.
+
-
-
- -
+
+
+ +
-
- {loaded ? ( - - smtp.setConfigValue("username", e.target.value) - } - /> - ) : ( - - )} -
-
- Username to login as after connected to SMTP server. -
+
+ {loaded ? ( + smtp.setConfigValue("host", e.target.value)} + placeholder="localhost" + /> + ) : ( + + )} +
+
+ Host of SMTP server to use for sending emails. If not set, + "localhost" will be used.
+
-
-
- -
+
+
+ +
-
- {loaded ? ( - - smtp.setConfigValue("password", e.target.value) - } - /> - ) : ( - - )} -
-
- Password for login after connected to SMTP server. Note: will - replace the currently configured SMTP password (if set). -
+
+ {loaded ? ( + smtp.setConfigValue("port", e.target.value)} + placeholder={ + security === "STARTTLSOrPlainText" + ? "587 or 25" + : security === "TLS" + ? "465" + : security === "STARTTLS" + ? "587" + : "25" + } + error={smtp.portError} + size={10} + /> + ) : ( + + )} +
+
+ Port of SMTP server to use for sending emails. If not set, common + defaults will be used depending on security: 465 for TLS, 587 for + STARTTLS, 25 otherwise.
+
-
-
- -
+
+
+ +
-
- {loaded ? ( - - className={styles.securitySelect} - selectedItemId={smtp.getConfigValue("security") as any} - onChange={(item) => smtp.setConfigValue("security", item.id)} - items={smtpSecurity.map((val) => ({id: val, label: val}))} - /> - ) : ( - - )} -
-
- Security mode of the connection to SMTP server. By default, - initiate a STARTTLS upgrade if supported by the server, or fallback - to PlainText. -
+
+ {loaded ? ( + smtp.setConfigValue("username", e.target.value)} + /> + ) : ( + + )}
+
+ Username to login as after connected to SMTP server. +
+
-
-
- -
+
+
+ +
-
- {loaded ? ( - - smtp.setConfigValue("validate_certs", checked) - } - /> - ) : ( - - )} -
-
- Determines if SMTP server certificates are validated. -
+
+ {loaded ? ( + smtp.setConfigValue("password", e.target.value)} + /> + ) : ( + + )} +
+
+ Password for login after connected to SMTP server.{" "} + {!newProvider + ? "Note: will replace the currently configured SMTP password." + : null}
+
-
-
- -
+
+
+ +
-
- {loaded ? ( - - smtp.setConfigValue( - "timeout_per_email", - e.target.value.toUpperCase() - ) - } - error={smtp.timeoutPerEmailError} - /> - ) : ( - - )} -
-
- Maximum time in seconds to send an email, including retry attempts. -
+
+ {loaded ? ( + + className={styles.securitySelect} + selectedItemId={smtp.getConfigValue("security") as any} + onChange={(item) => smtp.setConfigValue("security", item.id)} + items={smtpSecurity.map((val) => ({id: val, label: val}))} + /> + ) : ( + + )} +
+
+ Security mode of the connection to SMTP server. By default, initiate + a STARTTLS upgrade if supported by the server, or fallback to + PlainText.
+
-
-
- -
+
+
+ +
-
- {loaded ? ( - - smtp.setConfigValue( - "timeout_per_attempt", - e.target.value.toUpperCase() - ) - } - error={smtp.timeoutPerAttemptError} - /> - ) : ( - - )} -
-
- Maximum time in seconds for each SMTP request. -
+
+ {loaded ? ( + + smtp.setConfigValue("validate_certs", checked) + } + /> + ) : ( + + )} +
+
+ Determines if SMTP server certificates are validated.
- +
+
+ +
+ +
+ {loaded ? ( + + smtp.setConfigValue( + "timeout_per_email", + e.target.value.toUpperCase() + ) + } + error={smtp.timeoutPerEmailError} + /> + ) : ( + + )} +
+
+ Maximum time in seconds to send an email, including retry attempts. +
+
+ +
+
+ +
+ +
+ {loaded ? ( + + smtp.setConfigValue( + "timeout_per_attempt", + e.target.value.toUpperCase() + ) + } + error={smtp.timeoutPerAttemptError} + /> + ) : ( + + )} +
+
+ Maximum time in seconds for each SMTP request. +
+
); }); diff --git a/shared/studio/tabs/auth/state/index.tsx b/shared/studio/tabs/auth/state/index.tsx index fc7ba4c8..6a87138d 100644 --- a/shared/studio/tabs/auth/state/index.tsx +++ b/shared/studio/tabs/auth/state/index.tsx @@ -6,6 +6,7 @@ import { Model, model, modelAction, + objectMap, prop, } from "mobx-keystone"; import {parsers} from "../../../components/dataEditor/parsers"; @@ -88,6 +89,8 @@ export const smtpSecurity = [ export type SMTPSecurity = (typeof smtpSecurity)[number]; export interface SMTPConfigData { + _typename: "cfg::SMTPProviderConfig"; + name: string; sender: string; host: string; port: string; @@ -99,6 +102,8 @@ export interface SMTPConfigData { timeout_per_attempt: string; } +export type EmailProviderConfig = SMTPConfigData; + export const webhookEvents = [ "IdentityCreated", "IdentityAuthenticated", @@ -191,7 +196,7 @@ export class AuthAdminState extends Model({ draftProviderConfig: prop(null), draftWebhookConfig: prop(null), draftUIConfig: prop(null), - draftSMTPConfig: prop(() => new DraftSMTPConfig({})), + draftSMTPConfigs: prop(() => objectMap()), }) { @computed get extEnabled() { @@ -208,6 +213,14 @@ export class AuthAdminState extends Model({ ?.properties["app_name"] != null ); } + @computed + get newSMTPSchema() { + return ( + dbCtx + .get(this)! + .schemaData?.objectsByName.get("cfg::EmailProviderConfig") != null + ); + } @computed get hasWebhooksSchema() { @@ -314,6 +327,86 @@ export class AuthAdminState extends Model({ this.draftUIConfig = null; } + @modelAction + addDraftSMTPProvider(provider?: EmailProviderConfig) { + if (provider) { + this.draftSMTPConfigs.set( + provider.name, + new DraftSMTPConfig({currentConfig: provider}) + ); + } else { + this.draftSMTPConfigs.set( + "", + new DraftSMTPConfig({ + currentConfig: { + security: "STARTTLSOrPlainText", + validate_certs: true, + timeout_per_email: "60", + timeout_per_attempt: "15", + }, + }) + ); + } + } + @modelAction + cancelDraftSMTPProvider(name?: string) { + this.draftSMTPConfigs.delete(name ?? ""); + } + + @observable + updatingEmailProviders = false; + + @action + async removeEmailProvider(name: string) { + this.updatingEmailProviders = true; + + const conn = connCtx.get(this)!; + + try { + await conn.execute( + `configure current branch reset cfg::EmailProviderConfig filter .name = ${JSON.stringify( + name + )}` + ); + await this.refreshConfig(); + } finally { + runInAction(() => (this.updatingEmailProviders = false)); + } + } + + @action + async setCurrentEmailProvider(name: string) { + this.updatingEmailProviders = true; + + const conn = connCtx.get(this)!; + + try { + await conn.execute( + `configure current branch set current_email_provider_name := ${JSON.stringify( + name + )}` + ); + await this.refreshConfig(); + } finally { + runInAction(() => (this.updatingEmailProviders = false)); + } + } + + @computed + get noEmailProviderWarning() { + return ( + this.providers?.some( + (provider) => + provider._typename === "ext::auth::MagicLinkProviderConfig" || + provider._typename === "ext::auth::EmailPasswordProviderConfig" || + provider._typename === "ext::auth::WebAuthnProviderConfig" + ) && + !this.emailProviders?.some( + (provider) => provider.name === this.currentEmailProvider + ) + ); + } + onAttachedToRootStore() {} @observable.ref @@ -326,7 +419,10 @@ export class AuthAdminState extends Model({ uiConfig: AuthUIConfigData | false | null = null; @observable.ref - smtpConfig: SMTPConfigData | null = null; + emailProviders: EmailProviderConfig[] | null = null; + + @observable + currentEmailProvider: string | null = null; @observable.ref webhooks: WebhookConfigData[] | null = null; @@ -338,7 +434,14 @@ export class AuthAdminState extends Model({ async refreshConfig() { const conn = connCtx.get(this)!; - const {newAppAuthSchema} = this; + const {newAppAuthSchema, newSMTPSchema} = this; + + if (!newSMTPSchema && !this.draftSMTPConfigs.has("")) { + this.draftSMTPConfigs.set( + "", + new DraftSMTPConfig({currentConfig: null}) + ); + } const appConfigQuery = ` app_name, @@ -410,7 +513,20 @@ export class AuthAdminState extends Model({ : "" } }), - smtp := assert_single(cfg::Config.extensions[is SMTPConfig] { + emailProviders := ${ + newSMTPSchema + ? `cfg::Config.email_providers { + _typename := .__type__.name, + name, + [is cfg::SMTPProviderConfig].sender, + [is cfg::SMTPProviderConfig].host, + [is cfg::SMTPProviderConfig].port, + [is cfg::SMTPProviderConfig].username, + [is cfg::SMTPProviderConfig].security, + timeout_per_email_seconds := duration_get([is cfg::SMTPProviderConfig].timeout_per_email, 'totalseconds'), + timeout_per_attempt_seconds := duration_get([is cfg::SMTPProviderConfig].timeout_per_attempt, 'totalseconds'), + }` + : `{ assert_single(cfg::Config.extensions[is SMTPConfig] { sender, host, port, @@ -419,7 +535,13 @@ export class AuthAdminState extends Model({ validate_certs, timeout_per_email_seconds := duration_get(.timeout_per_email, 'totalseconds'), timeout_per_attempt_seconds := duration_get(.timeout_per_attempt, 'totalseconds'), - }) + }) }` + }, + currentEmailProvider := ${ + newSMTPSchema + ? `assert_single(cfg::Config.current_email_provider_name)` + : `{}` + } }`, undefined, {ignoreSessionConfig: true} @@ -427,7 +549,7 @@ export class AuthAdminState extends Model({ if (result === null) return; - const {auth, smtp} = result[0]; + const {auth, emailProviders, currentEmailProvider} = result[0]; runInAction(() => { this.configData = { @@ -459,12 +581,29 @@ export class AuthAdminState extends Model({ } else { this._removeDraftUIConfig(); } - this.smtpConfig = { - ...smtp, - port: smtp.port?.toString(), - timeout_per_email: smtp.timeout_per_email_seconds, - timeout_per_attempt: smtp.timeout_per_attempt_seconds, - }; + this.emailProviders = emailProviders.map((provider: any) => ({ + ...provider, + port: provider.port?.toString(), + timeout_per_email: provider.timeout_per_email_seconds, + timeout_per_attempt: provider.timeout_per_attempt_seconds, + })); + this.currentEmailProvider = currentEmailProvider; + if (newSMTPSchema) { + for (const [draftName] of this.draftSMTPConfigs) { + if ( + !this.emailProviders?.some( + (provider) => provider.name === draftName + ) + ) { + this.draftSMTPConfigs.delete(draftName); + } + } + } else { + this.draftSMTPConfigs + .get("")! + .setCurrentConfig(this.emailProviders![0]); + } + this.webhooks = auth.webhooks ?? []; }); } @@ -829,6 +968,8 @@ export class DraftUIConfig extends Model({ @model("AuthAdmin/DraftSMTPConfig") export class DraftSMTPConfig extends Model({ + currentConfig: prop | null>().withSetter(), + _name: prop(null), _sender: prop(null), _host: prop(null), _port: prop(null), @@ -841,12 +982,18 @@ export class DraftSMTPConfig }) implements AbstractDraftConfig { - getConfigValue(name: Exclude) { - return ( - this[`_${name}`] ?? - (getParent(this)?.smtpConfig || null)?.[name] ?? - "" - ); + @observable + expanded = true; + + @action + toggleExpanded() { + this.expanded = !this.expanded; + } + + getConfigValue( + name: Exclude + ) { + return this[`_${name}`] ?? this.currentConfig?.[name] ?? ""; } @modelAction @@ -857,6 +1004,42 @@ export class DraftSMTPConfig (this as any)[`_${name}`] = val; } + @computed + get parentState() { + return findParent( + this, + (parent) => parent instanceof AuthAdminState + )!; + } + + @computed + get nameError() { + if (!this.parentState.newSMTPSchema) return null; + const val = this.getConfigValue("name").trim(); + if (val == "") { + return "Name is required"; + } + if ( + val !== this.currentConfig?.name && + this.parentState.emailProviders?.some( + (provider) => provider.name === val + ) + ) { + return "Name already exists"; + } + return null; + } + + @computed + get senderError() { + if (!this.parentState.newSMTPSchema) return null; + const val = this.getConfigValue("sender").trim(); + if (val == "") { + return "Sender is required"; + } + return null; + } + @computed get portError() { const val = this.getConfigValue("port").trim(); @@ -903,6 +1086,8 @@ export class DraftSMTPConfig @computed get formError() { return ( + !!this.nameError || + !!this.senderError || !!this.portError || !!this.timeoutPerEmailError || !!this.timeoutPerAttemptError @@ -912,6 +1097,7 @@ export class DraftSMTPConfig @computed get formChanged() { return ( + this._name != null || this._sender != null || this._host != null || this._port != null || @@ -926,6 +1112,7 @@ export class DraftSMTPConfig @modelAction clearForm() { + this._name = null; this._sender = null; this._host = null; this._port = null; @@ -948,41 +1135,78 @@ export class DraftSMTPConfig if (this.formError || !this.formChanged) return; const conn = connCtx.get(this)!; - const state = getParent(this)!; + const state = this.parentState; + + const {newSMTPSchema} = state; this.updating = true; this.error = null; - const query = ( - [ - {name: "sender", cast: null}, - {name: "host", cast: null}, - {name: "port", cast: "int32"}, - {name: "username", cast: null}, - {name: "password", cast: null}, - {name: "security", cast: "ext::auth::SMTPSecurity"}, - {name: "validate_certs", cast: null}, - {name: "timeout_per_email", cast: "std::duration"}, - {name: "timeout_per_attempt", cast: "std::duration"}, - ] as const - ) - .map(({name, cast}) => { - const val = this[`_${name}`]; - if (val == null) return null; - if (typeof val === "string" && val.trim() === "") { - return `configure current database reset ext::auth::SMTPConfig::${name};`; - } - return `configure current database set ext::auth::SMTPConfig::${name} := ${ - cast ? `<${cast}>` : "" - }${JSON.stringify(val)};`; - }) - .filter((s) => s != null) - .join("\n"); + const fields = [ + ...(newSMTPSchema ? ([{name: "name", cast: null}] as const) : []), + {name: "sender", cast: null}, + {name: "host", cast: null}, + {name: "port", cast: "int32"}, + {name: "username", cast: null}, + {name: "password", cast: null}, + { + name: "security", + cast: newSMTPSchema ? "cfg::SMTPSecurity" : "ext::auth::SMTPSecurity", + }, + {name: "validate_certs", cast: null}, + {name: "timeout_per_email", cast: "std::duration"}, + {name: "timeout_per_attempt", cast: "std::duration"}, + ] as const; + + const query = newSMTPSchema + ? ` + ${ + this.currentConfig?.name + ? `configure current branch reset cfg::SMTPProviderConfig filter .name = ${JSON.stringify( + this.currentConfig.name + )};` + : "" + } + configure current branch insert cfg::SMTPProviderConfig { + ${fields + .map(({name, cast}) => { + let val = this[`_${name}`] ?? this.currentConfig?.[name]; + if (typeof val === "string") { + val = val.trim(); + } + if (val == null || val == "") { + return null; + } + return `${name} := ${cast ? `<${cast}>` : ""}${JSON.stringify( + val + )},`; + }) + .filter((s) => s != null) + .join("\n")} + } + ` + : fields + .map(({name, cast}) => { + const val = this[`_${name}`]; + if (val == null) return null; + if (typeof val === "string" && val.trim() === "") { + return `configure current database reset ext::auth::SMTPConfig::${name};`; + } + return `configure current database set ext::auth::SMTPConfig::${name} := ${ + cast ? `<${cast}>` : "" + }${JSON.stringify(val)};`; + }) + .filter((s) => s != null) + .join("\n"); try { await conn.execute(query); await state.refreshConfig(); - this.clearForm(); + if (newSMTPSchema) { + state.cancelDraftSMTPProvider(); + } else { + this.clearForm(); + } } catch (e) { console.log(e); runInAction(