@@ -44,7 +44,7 @@ pub async fn schedule_stats(db: &DB, instance_name: String, http_client: &reqwes
|
||||
tracing::info!("Sending stats");
|
||||
let result = send_stats(&instance_name, &http_client, &db).await;
|
||||
if result.is_err() {
|
||||
tracing::error!("Error sending stats: {}", result.err().unwrap());
|
||||
tracing::info!("Error sending stats: {}", result.err().unwrap());
|
||||
} else {
|
||||
tracing::info!("Stats sent");
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
storage: 'config'
|
||||
}
|
||||
],
|
||||
'SSO/OAuth': [],
|
||||
Telemetry: [
|
||||
{
|
||||
label: 'Disable telemetry',
|
||||
@@ -267,8 +268,6 @@
|
||||
{#each Object.keys(settings) as category}
|
||||
<Tab value={category}>{category}</Tab>
|
||||
{/each}
|
||||
<Tab value="oauth">SSO/OAuth</Tab>
|
||||
|
||||
<svelte:fragment slot="content">
|
||||
<div class="pt-4" />
|
||||
{#each Object.keys(settings) as category}
|
||||
@@ -278,110 +277,217 @@
|
||||
>Setting SMTP unlock sending emails upon adding new users to the workspace or the
|
||||
instance.</div
|
||||
>
|
||||
{:else if category == 'Telemetry'}
|
||||
<div class="text-secondary pb-4 text-xs">
|
||||
Telemetry helps Windmill build a better product for all
|
||||
</div>
|
||||
{/if}
|
||||
<div>
|
||||
<div class="flex-col flex gap-2 pb-4">
|
||||
{#each settings[category] as setting}
|
||||
{#if !setting.cloudonly || isCloudHosted()}
|
||||
{#if setting.ee_only != undefined && !$enterpriseLicense}
|
||||
<div class="flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap">
|
||||
<AlertTriangle size={16} />
|
||||
EE only <Tooltip>{setting.ee_only}</Tooltip>
|
||||
{#if category == 'SSO/OAuth'}
|
||||
<div>
|
||||
<h4 class="pb-4">SSO</h4>
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert type="warning" title="Limited to 50 SSO users">
|
||||
Without EE, the number of SSO users is limited to 50. SCIM/SAML is available on EE
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<div class="py-1" />
|
||||
<Alert type="info" title="Test on a separate tab">
|
||||
The recommended workflow is to to save your oauth setting and test them directly on
|
||||
the login or resource page
|
||||
</Alert>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<OAuthSetting name="google" bind:value={oauths['google']} />
|
||||
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
|
||||
<OktaSetting bind:value={oauths['okta']} />
|
||||
<OAuthSetting name="github" bind:value={oauths['github']} />
|
||||
<OAuthSetting name="gitlab" bind:value={oauths['gitlab']} />
|
||||
<OAuthSetting name="jumpcloud" bind:value={oauths['jumpcloud']} />
|
||||
<KeycloakSetting bind:value={oauths['keycloak']} />
|
||||
</div>
|
||||
<h4 class="py-4">OAuth</h4>
|
||||
<Alert type="info" title="Require a corresponding resource type">
|
||||
After setting an oauth client, make sure that there is a corresponding resource type
|
||||
with the same name with a "token" field in the admins workspace.
|
||||
</Alert>
|
||||
<div class="py-1" />
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1" />
|
||||
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if !['google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'keycloak', 'slack'].includes(k)}
|
||||
{#if oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2 border rounded">
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Id</span>
|
||||
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Secret</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Client Secret"
|
||||
bind:value={oauths[k]['secret']}
|
||||
/>
|
||||
</label>
|
||||
{#if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
<span class="text-secondary text-xs">{setting.description}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<div class="flex gap-2">
|
||||
<select name="oauth_name" id="oauth_name" bind:value={oauth_name}>
|
||||
<option value="custom">Fully Custom (require ee)</option>
|
||||
{#each windmillBuiltins as name}
|
||||
<option value={name}>{capitalize(name)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if oauth_name == 'custom'}
|
||||
<input type="text" placeholder="client_id" bind:value={resourceName} />
|
||||
{:else}
|
||||
<input type="text" value={oauth_name} disabled />
|
||||
{/if}
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: faPlus }}
|
||||
disabled={(oauth_name == 'custom' && resourceName == '') ||
|
||||
(oauth_name == 'custom' && !$enterpriseLicense)}
|
||||
on:click={() => {
|
||||
let name = oauth_name == 'custom' ? resourceName : oauth_name
|
||||
oauths[name] = { id: '', secret: '' }
|
||||
resourceName = ''
|
||||
}}
|
||||
>
|
||||
Add OAuth client {oauth_name == 'custom' && !$enterpriseLicense
|
||||
? '(require ee)'
|
||||
: ''}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div>
|
||||
<div class="flex-col flex gap-2 pb-4">
|
||||
{#each settings[category] as setting}
|
||||
{#if !setting.cloudonly || isCloudHosted()}
|
||||
{#if setting.ee_only != undefined && !$enterpriseLicense}
|
||||
<div
|
||||
class="flex text-xs items-center gap-1 text-yellow-500 whitespace-nowrap"
|
||||
>
|
||||
<AlertTriangle size={16} />
|
||||
EE only <Tooltip>{setting.ee_only}</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
{#if setting.tooltip}
|
||||
<Tooltip>{setting.tooltip}</Tooltip>
|
||||
{/if}
|
||||
{#if values}
|
||||
{@const hasError = setting.isValid && !setting.isValid(values[setting.key])}
|
||||
{#if setting.fieldType == 'text'}
|
||||
<input
|
||||
disabled={setting.ee_only != undefined && !$enterpriseLicense}
|
||||
type="text"
|
||||
placeholder={setting.placeholder}
|
||||
class={hasError
|
||||
? 'border !border-red-700 !border-opacity-30 !focus:border-red-700 !focus:border-opacity-30 !bg-red-100'
|
||||
: ''}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'textarea'}
|
||||
<textarea
|
||||
rows="2"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'license_key'}
|
||||
<div class="flex justify-between gap-2">
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
<span class="text-secondary text-xs">{setting.description}</span>
|
||||
{/if}
|
||||
{#if setting.tooltip}
|
||||
<Tooltip>{setting.tooltip}</Tooltip>
|
||||
{/if}
|
||||
{#if values}
|
||||
{@const hasError = setting.isValid && !setting.isValid(values[setting.key])}
|
||||
{#if setting.fieldType == 'text'}
|
||||
<input
|
||||
disabled={setting.ee_only != undefined && !$enterpriseLicense}
|
||||
type="text"
|
||||
placeholder={setting.placeholder}
|
||||
class={hasError
|
||||
? 'border !border-red-700 !border-opacity-30 !focus:border-red-700 !focus:border-opacity-30 !bg-red-100'
|
||||
: ''}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'textarea'}
|
||||
<textarea
|
||||
rows="2"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
<Button
|
||||
variant={values[setting.key] ? 'contained' : 'border'}
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
await SettingService.testLicenseKey({
|
||||
requestBody: { license_key: values[setting.key] }
|
||||
})
|
||||
sendUserToast('Valid key')
|
||||
}}>Test Key</Button
|
||||
>
|
||||
</div>
|
||||
{#if values[setting.key]?.length > 0}
|
||||
{#if parseDate(values[setting.key])}
|
||||
<span class="text-tertiary text-2xs"
|
||||
>License key expires on {parseDate(values[setting.key])}</span
|
||||
{:else if setting.fieldType == 'license_key'}
|
||||
<div class="flex justify-between gap-2">
|
||||
<textarea
|
||||
rows="2"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
<Button
|
||||
variant={values[setting.key] ? 'contained' : 'border'}
|
||||
size="xs"
|
||||
on:click={async () => {
|
||||
await SettingService.testLicenseKey({
|
||||
requestBody: { license_key: values[setting.key] }
|
||||
})
|
||||
sendUserToast('Valid key')
|
||||
}}>Test Key</Button
|
||||
>
|
||||
</div>
|
||||
{#if values[setting.key]?.length > 0}
|
||||
{#if parseDate(values[setting.key])}
|
||||
<span class="text-tertiary text-2xs"
|
||||
>License key expires on {parseDate(values[setting.key])}</span
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
{:else if setting.fieldType == 'email'}
|
||||
<input
|
||||
type="email"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'number'}
|
||||
<input
|
||||
type="number"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'password'}
|
||||
<input
|
||||
type="password"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'boolean'}
|
||||
<div>
|
||||
<Toggle bind:checked={values[setting.key]} />
|
||||
</div>
|
||||
{:else if setting.fieldType == 'seconds'}
|
||||
<div>
|
||||
<SecondsInput bind:seconds={values[setting.key]} />
|
||||
</div>
|
||||
{/if}
|
||||
{:else if setting.fieldType == 'email'}
|
||||
<input
|
||||
type="email"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'number'}
|
||||
<input
|
||||
type="number"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'password'}
|
||||
<input
|
||||
type="password"
|
||||
placeholder={setting.placeholder}
|
||||
bind:value={values[setting.key]}
|
||||
/>
|
||||
{:else if setting.fieldType == 'boolean'}
|
||||
<div>
|
||||
<Toggle bind:checked={values[setting.key]} />
|
||||
</div>
|
||||
{:else if setting.fieldType == 'seconds'}
|
||||
<div>
|
||||
<SecondsInput bind:seconds={values[setting.key]} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if hasError}
|
||||
<span class="text-red-500 text-xs">
|
||||
Base url must start with http:// or https:// and must not end with a
|
||||
trailing slash.
|
||||
</span>
|
||||
{#if hasError}
|
||||
<span class="text-red-500 text-xs">
|
||||
Base url must start with http:// or https:// and must not end with a
|
||||
trailing slash.
|
||||
</span>
|
||||
{/if}
|
||||
{:else}
|
||||
<input disabled placeholder="Loading..." />
|
||||
{/if}
|
||||
{:else}
|
||||
<input disabled placeholder="Loading..." />
|
||||
{/if}
|
||||
</label>
|
||||
{/if}
|
||||
{/each}
|
||||
</label>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{#if category == 'SMTP'}
|
||||
<div class="flex gap-4"
|
||||
><input type="email" bind:value={to} placeholder="contact@windmill.dev" />
|
||||
@@ -408,104 +514,6 @@
|
||||
{/if}
|
||||
</TabContent>
|
||||
{/each}
|
||||
<TabContent value={'oauth'}>
|
||||
<div>
|
||||
<h4 class="pb-4">SSO</h4>
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert type="warning" title="Limited to 50 SSO users">
|
||||
Without EE, the number of SSO users is limited to 50. SCIM/SAML is available on EE
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<div class="py-1" />
|
||||
<Alert type="info" title="Test on a separate tab">
|
||||
The recommended workflow is to to save your oauth setting and test them directly on the
|
||||
login or resource page
|
||||
</Alert>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<OAuthSetting name="google" bind:value={oauths['google']} />
|
||||
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
|
||||
<OktaSetting bind:value={oauths['okta']} />
|
||||
<OAuthSetting name="github" bind:value={oauths['github']} />
|
||||
<OAuthSetting name="gitlab" bind:value={oauths['gitlab']} />
|
||||
<OAuthSetting name="jumpcloud" bind:value={oauths['jumpcloud']} />
|
||||
<KeycloakSetting bind:value={oauths['keycloak']} />
|
||||
</div>
|
||||
<h4 class="py-4">OAuth</h4>
|
||||
<Alert type="info" title="Require a corresponding resource type">
|
||||
After setting an oauth client, make sure that there is a corresponding resource type
|
||||
with the same name with a "token" field in the admins workspace.
|
||||
</Alert>
|
||||
<div class="py-1" />
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1" />
|
||||
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if !['google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'keycloak', 'slack'].includes(k)}
|
||||
{#if oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="p-2 border rounded">
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Id</span>
|
||||
<input type="text" placeholder="Client Id" bind:value={oauths[k]['id']} />
|
||||
</label>
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">Client Secret</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Client Secret"
|
||||
bind:value={oauths[k]['secret']}
|
||||
/>
|
||||
</label>
|
||||
{#if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<div class="flex gap-2">
|
||||
<select name="oauth_name" id="oauth_name" bind:value={oauth_name}>
|
||||
<option value="custom">Fully Custom (require ee)</option>
|
||||
{#each windmillBuiltins as name}
|
||||
<option value={name}>{capitalize(name)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if oauth_name == 'custom'}
|
||||
<input type="text" placeholder="client_id" bind:value={resourceName} />
|
||||
{:else}
|
||||
<input type="text" value={oauth_name} disabled />
|
||||
{/if}
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: faPlus }}
|
||||
disabled={(oauth_name == 'custom' && resourceName == '') ||
|
||||
(oauth_name == 'custom' && !$enterpriseLicense)}
|
||||
on:click={() => {
|
||||
let name = oauth_name == 'custom' ? resourceName : oauth_name
|
||||
oauths[name] = { id: '', secret: '' }
|
||||
resourceName = ''
|
||||
}}
|
||||
>
|
||||
Add OAuth client {oauth_name == 'custom' && !$enterpriseLicense ? '(require ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user