mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-06 16:02:19 +00:00
more svelte 5 migrations
This commit is contained in:
@@ -6,7 +6,11 @@
|
||||
import type { DurationStatus } from './graph'
|
||||
import type { Writable } from 'svelte/store'
|
||||
|
||||
export let states: Writable<Record<string, DurationStatus>> | undefined
|
||||
interface Props {
|
||||
states: Writable<Record<string, DurationStatus>> | undefined
|
||||
}
|
||||
|
||||
let { states }: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col">
|
||||
|
||||
@@ -5,17 +5,21 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { onMount } from 'svelte'
|
||||
|
||||
export let resourceType: string | undefined = undefined
|
||||
export let workspace: string
|
||||
export let express = false
|
||||
interface Props {
|
||||
resourceType?: string | undefined
|
||||
workspace: string
|
||||
express?: boolean
|
||||
}
|
||||
|
||||
let step = 1
|
||||
let disabled = false
|
||||
let manual = true
|
||||
let { resourceType = $bindable(undefined), workspace, express = false }: Props = $props()
|
||||
|
||||
let appConnect: AppConnectInner | undefined = undefined
|
||||
let step = $state(1)
|
||||
let disabled = $state(false)
|
||||
let manual = $state(true)
|
||||
|
||||
let darkMode: boolean = false
|
||||
let appConnect: AppConnectInner | undefined = $state(undefined)
|
||||
|
||||
let darkMode: boolean = $state(false)
|
||||
|
||||
if (workspace) {
|
||||
$workspaceStore = workspace
|
||||
|
||||
@@ -9,17 +9,25 @@
|
||||
import EditableSchemaDrawer from './schema/EditableSchemaDrawer.svelte'
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
|
||||
export let canEditResourceType: boolean = false
|
||||
export let originalType: string | undefined = undefined
|
||||
export let itemsType:
|
||||
| {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined
|
||||
interface Props {
|
||||
canEditResourceType?: boolean
|
||||
originalType?: string | undefined
|
||||
itemsType:
|
||||
| {
|
||||
type?: 'string' | 'number' | 'bytes' | 'object' | 'resource'
|
||||
contentEncoding?: 'base64'
|
||||
enum?: string[]
|
||||
resourceType?: string
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined
|
||||
}
|
||||
|
||||
let {
|
||||
canEditResourceType = false,
|
||||
originalType = undefined,
|
||||
itemsType = $bindable()
|
||||
}: Props = $props()
|
||||
|
||||
let selected:
|
||||
| 'string'
|
||||
@@ -29,7 +37,7 @@
|
||||
| 'enum'
|
||||
| 'resource'
|
||||
| 's3object'
|
||||
| undefined =
|
||||
| undefined = $state(
|
||||
itemsType?.type != 'string'
|
||||
? itemsType?.type == 'object' && itemsType?.resourceType == 's3object'
|
||||
? 's3object'
|
||||
@@ -39,12 +47,13 @@
|
||||
: itemsType?.contentEncoding == 'base64'
|
||||
? 'bytes'
|
||||
: 'string'
|
||||
)
|
||||
|
||||
let schema = {
|
||||
let schema = $state({
|
||||
properties: itemsType?.properties || {},
|
||||
order: Object.keys(itemsType?.properties || {}),
|
||||
required: Object.values(itemsType?.properties || {}).map((p) => p.required)
|
||||
}
|
||||
})
|
||||
|
||||
function updateItemsType() {
|
||||
itemsType = {
|
||||
@@ -59,7 +68,7 @@
|
||||
<Label label="Items type">
|
||||
<select
|
||||
bind:value={selected}
|
||||
on:change={() => {
|
||||
onchange={() => {
|
||||
if (selected == 'enum') {
|
||||
itemsType = { type: 'string', enum: [] }
|
||||
} else if (selected == 'string') {
|
||||
@@ -127,7 +136,7 @@
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
if (itemsType && itemsType.enum) {
|
||||
const enumValue = itemsType.enum[index]
|
||||
itemsType.enum = itemsType.enum.filter((el) => el !== enumValue)
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import AssignableTagsInner from './AssignableTagsInner.svelte'
|
||||
|
||||
export let placement: 'bottom-end' | 'top-end' = 'bottom-end'
|
||||
export let color: 'nord' | 'dark' = 'dark'
|
||||
export let disabled = false
|
||||
export let showWorkspaceRestriction = false
|
||||
interface Props {
|
||||
placement?: 'bottom-end' | 'top-end'
|
||||
color?: 'nord' | 'dark'
|
||||
disabled?: boolean
|
||||
showWorkspaceRestriction?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
placement = 'bottom-end',
|
||||
color = 'dark',
|
||||
disabled = false,
|
||||
showWorkspaceRestriction = false
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
<Popover
|
||||
@@ -18,7 +27,7 @@
|
||||
closeButton
|
||||
usePointerDownOutside
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
<Button {color} size="xs" nonCaptureEvent={true} {disabled}>
|
||||
<div class="flex flex-row gap-1 items-center"
|
||||
><Pen size={14} /> Custom tags <Tooltip light
|
||||
@@ -29,8 +38,8 @@
|
||||
></div
|
||||
>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
<AssignableTagsInner {showWorkspaceRestriction} on:refresh />
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { preventDefault, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
import { Button } from './common'
|
||||
import { ExternalLink, Loader2, X } from 'lucide-svelte'
|
||||
import { SettingService, WorkerService } from '$lib/gen'
|
||||
@@ -9,9 +11,13 @@
|
||||
import { base } from '$lib/base'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
|
||||
export let showWorkspaceRestriction = false
|
||||
let newTag: string = ''
|
||||
let customTags: string[] | undefined = undefined
|
||||
interface Props {
|
||||
showWorkspaceRestriction?: boolean
|
||||
}
|
||||
|
||||
let { showWorkspaceRestriction = false }: Props = $props()
|
||||
let newTag: string = $state('')
|
||||
let customTags: string[] | undefined = $state(undefined)
|
||||
|
||||
async function loadCustomTags() {
|
||||
try {
|
||||
@@ -40,15 +46,17 @@
|
||||
<button
|
||||
class="z-10 rounded-full p-1 duration-200 hover:bg-gray-200"
|
||||
aria-label="Remove item"
|
||||
on:click|preventDefault|stopPropagation={async () => {
|
||||
await SettingService.setGlobal({
|
||||
key: CUSTOM_TAGS_SETTING,
|
||||
requestBody: { value: customTags?.filter((x) => x != customTag) }
|
||||
onclick={stopPropagation(
|
||||
preventDefault(async () => {
|
||||
await SettingService.setGlobal({
|
||||
key: CUSTOM_TAGS_SETTING,
|
||||
requestBody: { value: customTags?.filter((x) => x != customTag) }
|
||||
})
|
||||
dispatch('refresh')
|
||||
loadCustomTags()
|
||||
sendUserToast('Tag removed')
|
||||
})
|
||||
dispatch('refresh')
|
||||
loadCustomTags()
|
||||
sendUserToast('Tag removed')
|
||||
}}
|
||||
)}
|
||||
>
|
||||
<X size={12} />
|
||||
</button><NoWorkerWithTagWarning tag={customTag} />
|
||||
@@ -74,7 +82,9 @@
|
||||
}}
|
||||
disabled={newTag.trim() == '' || !$superadmin}
|
||||
>
|
||||
Add {#if !$superadmin} <span class="text-2xs text-tertiary">superadmin only</span> {/if}
|
||||
Add {#if !$superadmin}
|
||||
<span class="text-2xs text-tertiary">superadmin only</span>
|
||||
{/if}
|
||||
</Button>
|
||||
<span class="text-sm text-primary"
|
||||
>Configure <a href="{base}/workers" target="_blank" class="inline-flex gap-1 items-baseline"
|
||||
|
||||
@@ -18,9 +18,31 @@
|
||||
import { ExternalLink, Plus } from 'lucide-svelte'
|
||||
import AzureOauthSettings from './AzureOauthSettings.svelte'
|
||||
|
||||
export let snowflakeAccountIdentifier = ''
|
||||
export let oauths: Record<string, any> = {}
|
||||
export let requirePreexistingUserForOauth: boolean = false
|
||||
interface Props {
|
||||
snowflakeAccountIdentifier?: string
|
||||
oauths?: Record<string, any>
|
||||
requirePreexistingUserForOauth?: boolean
|
||||
scim?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
snowflakeAccountIdentifier = $bindable(),
|
||||
oauths = $bindable(),
|
||||
requirePreexistingUserForOauth = $bindable(),
|
||||
scim
|
||||
}: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
if (snowflakeAccountIdentifier == undefined) {
|
||||
snowflakeAccountIdentifier = ''
|
||||
}
|
||||
if (oauths == undefined) {
|
||||
oauths = {}
|
||||
}
|
||||
if (requirePreexistingUserForOauth == undefined) {
|
||||
requirePreexistingUserForOauth = false
|
||||
}
|
||||
})
|
||||
|
||||
const windmillBuiltins = [
|
||||
'azure_oauth',
|
||||
@@ -45,12 +67,12 @@
|
||||
'xero'
|
||||
]
|
||||
|
||||
let oauth_name = undefined
|
||||
let oauth_name = $state(undefined)
|
||||
|
||||
let clientName = ''
|
||||
let resourceName = ''
|
||||
let clientName = $state('')
|
||||
let resourceName = $state('')
|
||||
|
||||
let tab: 'sso' | 'oauth' | 'scim' = 'sso'
|
||||
let tab: 'sso' | 'oauth' | 'scim' = $state('sso')
|
||||
</script>
|
||||
|
||||
<div>
|
||||
@@ -62,60 +84,147 @@
|
||||
</div>
|
||||
|
||||
<div class="mb-6">
|
||||
{#if tab === 'sso'}
|
||||
{#if !$enterpriseLicense || $enterpriseLicense.endsWith('_pro')}
|
||||
<Alert type="warning" title="Limited to 10 SSO users">
|
||||
Without EE, the number of SSO users is limited to 10. SCIM/SAML is available on EE
|
||||
</Alert>
|
||||
{/if}
|
||||
{#if oauths}
|
||||
{#if tab === 'sso'}
|
||||
{#if !$enterpriseLicense || $enterpriseLicense.endsWith('_pro')}
|
||||
<Alert type="warning" title="Limited to 10 SSO users">
|
||||
Without EE, the number of SSO users is limited to 10. SCIM/SAML is available on EE
|
||||
</Alert>
|
||||
{/if}
|
||||
|
||||
<div class="py-1"></div>
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When at least one of the below options is set, users will be able to login to Windmill
|
||||
via their third-party account.
|
||||
<br /> To test SSO, the recommended workflow is to to save the settings and try to login
|
||||
in an incognito window.
|
||||
<a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#sso">Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 py-4">
|
||||
<OAuthSetting name="google" bind:value={oauths['google']} />
|
||||
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
|
||||
<OktaSetting bind:value={oauths['okta']} />
|
||||
<Auth0Setting bind:value={oauths['auth0']} />
|
||||
<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']} />
|
||||
<AuthentikSetting bind:value={oauths['authentik']} />
|
||||
<AutheliaSetting bind:value={oauths['authelia']} />
|
||||
<KanidmSetting bind:value={oauths['kanidm']} />
|
||||
<ZitadelSetting bind:value={oauths['zitadel']} />
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if !['authelia', 'authentik', 'google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'auth0', 'keycloak', 'slack', 'kanidm', 'zitadel'].includes(k) && 'login_config' in oauths[k]}
|
||||
{#if oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
if (oauths) {
|
||||
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">Custom Name</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Custom Name"
|
||||
bind:value={oauths[k]['display_name']}
|
||||
/>
|
||||
</label>
|
||||
<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'}
|
||||
<CustomSso bind:login_config={oauths[k]['login_config']} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<input type="text" placeholder="client_id" bind:value={clientName} />
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={clientName == ''}
|
||||
on:click={() => {
|
||||
if (oauths) {
|
||||
oauths[clientName] = { id: '', secret: '', login_config: {} }
|
||||
}
|
||||
clientName = ''
|
||||
}}
|
||||
>
|
||||
Add custom SSO client {!$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<Toggle
|
||||
options={{
|
||||
right: 'Require users to have been added manually to Windmill to sign in through OAuth'
|
||||
}}
|
||||
bind:checked={requirePreexistingUserForOauth}
|
||||
/>
|
||||
</div>
|
||||
{:else if tab === 'oauth'}
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When one of the below options is set, you will be able to create a specific resource
|
||||
containing a token automatically generated by the third-party provider.
|
||||
<br />
|
||||
To test it after setting an oauth client, go to the Resources menu and create a new one of
|
||||
the type of your oauth client (i.e. a 'github' resource if you set Github OAuth).
|
||||
<br /><a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#oauth"
|
||||
>Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="teams" eeOnly={true} bind:value={oauths['teams']} />
|
||||
<div class="py-1"></div>
|
||||
|
||||
<div class="py-1"></div>
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When at least one of the below options is set, users will be able to login to Windmill via
|
||||
their third-party account.
|
||||
<br /> To test SSO, the recommended workflow is to to save the settings and try to login in
|
||||
an incognito window.
|
||||
<a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#sso">Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="flex flex-col gap-3 py-4">
|
||||
<OAuthSetting name="google" bind:value={oauths['google']} />
|
||||
<OAuthSetting name="microsoft" bind:value={oauths['microsoft']} />
|
||||
<OktaSetting bind:value={oauths['okta']} />
|
||||
<Auth0Setting bind:value={oauths['auth0']} />
|
||||
<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']} />
|
||||
<AuthentikSetting bind:value={oauths['authentik']} />
|
||||
<AutheliaSetting bind:value={oauths['authelia']} />
|
||||
<KanidmSetting bind:value={oauths['kanidm']} />
|
||||
<ZitadelSetting bind:value={oauths['zitadel']} />
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if !['authelia', 'authentik', 'google', 'microsoft', 'github', 'gitlab', 'jumpcloud', 'okta', 'auth0', 'keycloak', 'slack', 'kanidm', 'zitadel'].includes(k) && 'login_config' in oauths[k]}
|
||||
{#if oauths[k]}
|
||||
{#if oauths[k] && !('login_config' in oauths[k])}
|
||||
{#if !['slack', 'teams'].includes(k) && oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="text-md font-medium text-primary">{k}</label>
|
||||
<CloseButton
|
||||
on:close={() => {
|
||||
delete oauths[k]
|
||||
oauths = { ...oauths }
|
||||
if (oauths) {
|
||||
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">Custom Name</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Custom Name"
|
||||
bind:value={oauths[k]['display_name']}
|
||||
/>
|
||||
</label>
|
||||
<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']} />
|
||||
@@ -124,141 +233,68 @@
|
||||
<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'}
|
||||
<CustomSso bind:login_config={oauths[k]['login_config']} />
|
||||
{#if k === 'azure_oauth'}
|
||||
<AzureOauthSettings bind:connect_config={oauths[k]['connect_config']} />
|
||||
{:else if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
{#if k == 'snowflake_oauth'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm flex gap-2 items-center"
|
||||
><a
|
||||
href="https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier"
|
||||
target="_blank">Snowflake Account Identifier</a
|
||||
><ExternalLink size={12} /></span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="<orgname>-<account_name>"
|
||||
required={true}
|
||||
bind:value={snowflakeAccountIdentifier}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<input type="text" placeholder="client_id" bind:value={clientName} />
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={clientName == ''}
|
||||
on:click={() => {
|
||||
oauths[clientName] = { id: '', secret: '', login_config: {} }
|
||||
clientName = ''
|
||||
}}
|
||||
>
|
||||
Add custom SSO client {!$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex gap-2 py-4">
|
||||
<Toggle
|
||||
options={{
|
||||
right: 'Require users to have been added manually to Windmill to sign in through OAuth'
|
||||
}}
|
||||
bind:checked={requirePreexistingUserForOauth}
|
||||
/>
|
||||
</div>
|
||||
{:else if tab === 'oauth'}
|
||||
<div class="mb-2">
|
||||
<span class="text-primary text-sm"
|
||||
>When one of the below options is set, you will be able to create a specific resource
|
||||
containing a token automatically generated by the third-party provider.
|
||||
<br />
|
||||
To test it after setting an oauth client, go to the Resources menu and create a new one of the
|
||||
type of your oauth client (i.e. a 'github' resource if you set Github OAuth).
|
||||
<br /><a target="_blank" href="https://www.windmill.dev/docs/misc/setup_oauth#oauth"
|
||||
>Learn more</a
|
||||
></span
|
||||
>
|
||||
</div>
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="slack" bind:value={oauths['slack']} />
|
||||
<div class="py-1"></div>
|
||||
<OAuthSetting login={false} name="teams" eeOnly={true} bind:value={oauths['teams']} />
|
||||
<div class="py-1"></div>
|
||||
|
||||
{#each Object.keys(oauths) as k}
|
||||
{#if oauths[k] && !('login_config' in oauths[k])}
|
||||
{#if !['slack', 'teams'].includes(k) && oauths[k]}
|
||||
<div class="flex flex-col gap-2 pb-4">
|
||||
<div class="flex flex-row items-center gap-2">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<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 k === 'azure_oauth'}
|
||||
<AzureOauthSettings bind:connect_config={oauths[k]['connect_config']} />
|
||||
{:else if !windmillBuiltins.includes(k) && k != 'slack'}
|
||||
<CustomOauth bind:connect_config={oauths[k]['connect_config']} />
|
||||
{/if}
|
||||
{#if k == 'snowflake_oauth'}
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm flex gap-2 items-center"
|
||||
><a
|
||||
href="https://docs.snowflake.com/en/user-guide/admin-account-identifier#using-an-account-name-as-an-identifier"
|
||||
target="_blank">Snowflake Account Identifier</a
|
||||
><ExternalLink size={12} /></span
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="<orgname>-<account_name>"
|
||||
required={true}
|
||||
bind:value={snowflakeAccountIdentifier}
|
||||
/>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<select name="oauth_name" id="oauth_name" bind:value={oauth_name}>
|
||||
<option value={undefined}>Select an OAuth client</option>
|
||||
<option value="custom">Fully Custom (requires 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}
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<div class="flex gap-2">
|
||||
<select name="oauth_name" id="oauth_name" bind:value={oauth_name}>
|
||||
<option value={undefined}>Select an OAuth client</option>
|
||||
<option value="custom">Fully Custom (requires 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: Plus }}
|
||||
disabled={!oauth_name ||
|
||||
(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 ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if tab == 'scim'}
|
||||
<slot name="scim" />
|
||||
<Button
|
||||
variant="border"
|
||||
color="blue"
|
||||
hover="yo"
|
||||
size="sm"
|
||||
endIcon={{ icon: Plus }}
|
||||
disabled={!oauth_name ||
|
||||
(oauth_name == 'custom' && resourceName == '') ||
|
||||
(oauth_name == 'custom' && !$enterpriseLicense)}
|
||||
on:click={() => {
|
||||
if (oauths) {
|
||||
let name = oauth_name == 'custom' ? resourceName : oauth_name
|
||||
oauths[name ?? ''] = { id: '', secret: '' }
|
||||
}
|
||||
resourceName = ''
|
||||
}}
|
||||
>
|
||||
Add OAuth client {oauth_name == 'custom' && !$enterpriseLicense ? '(requires ee)' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
{:else if tab == 'scim'}
|
||||
{@render scim?.()}
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -41,10 +41,14 @@
|
||||
import TeamSelector from './TeamSelector.svelte'
|
||||
import ChannelSelector from './ChannelSelector.svelte'
|
||||
|
||||
export let setting: Setting
|
||||
export let version: string
|
||||
export let values: Writable<Record<string, any>>
|
||||
export let loading = true
|
||||
interface Props {
|
||||
setting: Setting
|
||||
version: string
|
||||
values: Writable<Record<string, any>>
|
||||
loading?: boolean
|
||||
}
|
||||
|
||||
let { setting, version, values, loading = true }: Props = $props()
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
if (
|
||||
@@ -57,9 +61,9 @@
|
||||
let latestKeyRenewalAttempt: {
|
||||
result: string
|
||||
attempted_at: string
|
||||
} | null
|
||||
} | null = $state(null)
|
||||
|
||||
let isFetching = false
|
||||
let isFetching = $state(false)
|
||||
|
||||
function showSetting(setting: string, values: Record<string, any>) {
|
||||
if (setting == 'dev_instance') {
|
||||
@@ -70,11 +74,11 @@
|
||||
return true
|
||||
}
|
||||
|
||||
let licenseKeyChanged = false
|
||||
let renewing = false
|
||||
let opening = false
|
||||
let licenseKeyChanged = $state(false)
|
||||
let renewing = $state(false)
|
||||
let opening = $state(false)
|
||||
|
||||
let to: string = ''
|
||||
let to: string = $state('')
|
||||
|
||||
async function reloadKeyrenewalAttemptInfo() {
|
||||
latestKeyRenewalAttempt = await SettingService.getLatestKeyRenewalAttempt()
|
||||
@@ -135,9 +139,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
let pythonAvailableVersions: ListAvailablePythonVersionsResponse = []
|
||||
let pythonAvailableVersions: ListAvailablePythonVersionsResponse = $state([])
|
||||
|
||||
let isPyFetching = false
|
||||
let isPyFetching = $state(false)
|
||||
async function fetch_available_python_versions() {
|
||||
if (isPyFetching) return
|
||||
isPyFetching = true
|
||||
@@ -209,7 +213,7 @@
|
||||
{/if}
|
||||
{#if setting.fieldType == 'select'}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
@@ -233,7 +237,7 @@
|
||||
</div>
|
||||
{:else if setting.fieldType == 'select_python'}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
@@ -254,7 +258,7 @@
|
||||
/>
|
||||
{/each}
|
||||
<PopoverMelt closeButton={!isPyFetching}>
|
||||
<svelte:fragment slot="trigger">
|
||||
{#snippet trigger()}
|
||||
{#if setting.select_items?.some((e) => e.label == $values[setting.key] || e.value == $values[setting.key])}
|
||||
<Button
|
||||
variant="border"
|
||||
@@ -270,8 +274,8 @@
|
||||
nonCaptureEvent={true}>Custom | {$values[setting.key]}</Button
|
||||
>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="content">
|
||||
{/snippet}
|
||||
{#snippet content()}
|
||||
{#if isPyFetching}
|
||||
<div class="p-4">
|
||||
<LoadingIcon />
|
||||
@@ -289,13 +293,13 @@
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</PopoverMelt>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<label class="block pb-2">
|
||||
<span class="text-primary font-semibold text-sm">{setting.label}</span>
|
||||
{#if setting.description}
|
||||
@@ -444,27 +448,29 @@
|
||||
on {attemptedAt}
|
||||
</span>
|
||||
</div>
|
||||
<div slot="text">
|
||||
{#if latestKeyRenewalAttempt.result === 'success'}
|
||||
<span class="text-green-300">
|
||||
Latest key renewal succeeded on {attemptedAt}
|
||||
</span>
|
||||
{:else if isTrial}
|
||||
<span class="text-yellow-300">
|
||||
License key cannot be renewed during trial ({attemptedAt})
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-red-300">
|
||||
Latest key renewal failed on {attemptedAt}: {latestKeyRenewalAttempt.result.replace(
|
||||
'error: ',
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
{/if}
|
||||
<br />
|
||||
As long as invoices are paid and usage corresponds to the subscription, the key is
|
||||
renewed daily with a validity of 35 days (grace period).
|
||||
</div>
|
||||
{#snippet text()}
|
||||
<div>
|
||||
{#if latestKeyRenewalAttempt?.result === 'success'}
|
||||
<span class="text-green-300">
|
||||
Latest key renewal succeeded on {attemptedAt}
|
||||
</span>
|
||||
{:else if isTrial}
|
||||
<span class="text-yellow-300">
|
||||
License key cannot be renewed during trial ({attemptedAt})
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-red-300">
|
||||
Latest key renewal failed on {attemptedAt}: {latestKeyRenewalAttempt?.result.replace(
|
||||
'error: ',
|
||||
''
|
||||
)}
|
||||
</span>
|
||||
{/if}
|
||||
<br />
|
||||
As long as invoices are paid and usage corresponds to the subscription, the key
|
||||
is renewed daily with a validity of 35 days (grace period).
|
||||
</div>
|
||||
{/snippet}
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -520,7 +526,7 @@
|
||||
<div class="flex w-full max-w-lg mt-1 gap-2 items-center">
|
||||
<select
|
||||
class="max-w-24"
|
||||
on:change={(e) => {
|
||||
onchange={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
$values[setting.key][i] = {
|
||||
[e.target['value']]: ''
|
||||
@@ -545,7 +551,7 @@
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Slack channel"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
$values[setting.key][i] = {
|
||||
slack_channel: e.target['value']
|
||||
@@ -603,7 +609,7 @@
|
||||
/>
|
||||
{/if}
|
||||
<div>
|
||||
<button on:click={fetchTeams} class="flex items-center gap-1 mt-2">
|
||||
<button onclick={fetchTeams} class="flex items-center gap-1 mt-2">
|
||||
<RefreshCcw size={16} class={isFetching ? 'animate-spin' : ''} />
|
||||
</button>
|
||||
</div>
|
||||
@@ -612,7 +618,7 @@
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email address"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target?.['value']) {
|
||||
$values[setting.key][i] = {
|
||||
email: e.target['value']
|
||||
@@ -626,7 +632,7 @@
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover"
|
||||
aria-label="Clear"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
$values[setting.key] = $values[setting.key].filter(
|
||||
(_, index) => index !== i
|
||||
)
|
||||
@@ -723,7 +729,7 @@
|
||||
type="number"
|
||||
id="writer_memory_budget"
|
||||
placeholder="300"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
if (e.target.valueAsNumber) {
|
||||
$values[setting.key].writer_memory_budget =
|
||||
@@ -781,7 +787,7 @@
|
||||
type="number"
|
||||
id="max_indexed_job_log_size"
|
||||
placeholder="1024"
|
||||
on:input={(e) => {
|
||||
oninput={(e) => {
|
||||
if (e.target instanceof HTMLInputElement) {
|
||||
if (e.target.valueAsNumber) {
|
||||
$values[setting.key].max_indexed_job_log_size =
|
||||
|
||||
@@ -16,20 +16,29 @@
|
||||
import InstanceSetting from './InstanceSetting.svelte'
|
||||
import { writable, type Writable } from 'svelte/store'
|
||||
|
||||
export let tab: string = 'Core'
|
||||
export let hideTabs: boolean = false
|
||||
export let hideSave: boolean = false
|
||||
export let closeDrawer: (() => void) | undefined = () => {}
|
||||
interface Props {
|
||||
tab?: string
|
||||
hideTabs?: boolean
|
||||
hideSave?: boolean
|
||||
closeDrawer?: (() => void) | undefined
|
||||
}
|
||||
|
||||
let {
|
||||
tab = $bindable('Core'),
|
||||
hideTabs = false,
|
||||
hideSave = false,
|
||||
closeDrawer = () => {}
|
||||
}: Props = $props()
|
||||
|
||||
let values: Writable<Record<string, any>> = writable({})
|
||||
let initialOauths: Record<string, any> = {}
|
||||
let initialRequirePreexistingUserForOauth: boolean = false
|
||||
let requirePreexistingUserForOauth: boolean = false
|
||||
let requirePreexistingUserForOauth: boolean = $state(false)
|
||||
|
||||
let initialValues: Record<string, any> = {}
|
||||
let snowflakeAccountIdentifier = ''
|
||||
let version: string = ''
|
||||
let loading = true
|
||||
let snowflakeAccountIdentifier = $state('')
|
||||
let version: string = $state('')
|
||||
let loading = $state(true)
|
||||
|
||||
loadSettings()
|
||||
loadVersion()
|
||||
@@ -39,7 +48,7 @@
|
||||
async function loadVersion() {
|
||||
version = await SettingsService.backendVersion()
|
||||
}
|
||||
let oauths: Record<string, any> = {}
|
||||
let oauths: Record<string, any> = $state({})
|
||||
|
||||
async function loadSettings() {
|
||||
loading = true
|
||||
@@ -240,7 +249,7 @@
|
||||
</script>
|
||||
|
||||
<div class="pb-8">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_label_has_associated_control -->
|
||||
<Tabs {hideTabs} bind:selected={tab}>
|
||||
{#each settingsKeys as category}
|
||||
<Tab value={category}>{category}</Tab>
|
||||
@@ -332,7 +341,7 @@
|
||||
bind:snowflakeAccountIdentifier
|
||||
bind:requirePreexistingUserForOauth
|
||||
>
|
||||
<svelte:fragment slot="scim">
|
||||
{#snippet scim()}
|
||||
<div class="flex-col flex gap-2 pb-4">
|
||||
{#each scimSamlSetting as setting}
|
||||
<InstanceSetting
|
||||
@@ -344,7 +353,7 @@
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</AuthSettings>
|
||||
{/if}
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user