* improve collapsible link * do not show superadmin ws link when already in it * improve OAuth UI * sso/oauth instance settings ui * refactor instance settings alerts WIP * Indexer and Oauth to brand guidelines * refactor ws error handler page * Create a tab SMTP in the Instance Settings * Ractivity isssue fix for tabs * nit * Add smtp settings status in Error handler * Add smtp configuration status * Display teams connection status for instance alerts * nit * Add critical alerts description * nit * nit * improve ee display * nit * nit * fix typo * nit * restore vit config --------- Co-authored-by: Alexander Petric <alex@windmill.dev>
232 lines
6.7 KiB
Svelte
232 lines
6.7 KiB
Svelte
<script lang="ts">
|
|
import { Badge, Button } from '$lib/components/common'
|
|
import { Slack, Code2, Unplug, Plug } from 'lucide-svelte'
|
|
import MsTeamsIcon from '$lib/components/icons/MSTeamsIcon.svelte'
|
|
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
|
|
import { hubBaseUrlStore, workspaceStore, enterpriseLicense } from '$lib/stores'
|
|
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
|
import { WorkspaceService } from '$lib/gen'
|
|
import { sendUserToast } from '$lib/utils'
|
|
import TeamSelector from './TeamSelector.svelte'
|
|
import CollapseLink from './CollapseLink.svelte'
|
|
|
|
interface TeamItem {
|
|
team_id: string
|
|
team_name: string
|
|
}
|
|
|
|
let {
|
|
platform,
|
|
teamName,
|
|
display_name,
|
|
scriptPath = $bindable(),
|
|
initialPath = $bindable(),
|
|
itemKind = $bindable('script' as 'flow' | 'script'),
|
|
onDisconnect,
|
|
onSelect,
|
|
connectHref,
|
|
createScriptHref,
|
|
createFlowHref,
|
|
documentationLink,
|
|
onLoadSettings,
|
|
workspaceConfig,
|
|
hideConnectButton = false,
|
|
isOAuthEnabled = false,
|
|
workspaceSpecificConnection = false
|
|
}: {
|
|
platform: 'slack' | 'teams'
|
|
teamName: string | undefined
|
|
display_name: string | undefined
|
|
scriptPath: string
|
|
initialPath: string
|
|
itemKind: 'flow' | 'script'
|
|
onDisconnect: () => Promise<void>
|
|
onSelect: () => Promise<void>
|
|
connectHref: string | undefined
|
|
createScriptHref: string
|
|
createFlowHref: string
|
|
documentationLink: string
|
|
onLoadSettings: () => void
|
|
workspaceConfig?: import('svelte').Snippet
|
|
hideConnectButton?: boolean
|
|
isOAuthEnabled?: boolean
|
|
workspaceSpecificConnection?: boolean
|
|
} = $props()
|
|
|
|
let selectedTeam: TeamItem | undefined = $state(undefined)
|
|
|
|
async function connectTeams() {
|
|
if (!selectedTeam) return
|
|
|
|
try {
|
|
await WorkspaceService.connectTeams({
|
|
workspace: $workspaceStore!,
|
|
requestBody: {
|
|
team_id: selectedTeam.team_id,
|
|
team_name: selectedTeam.team_name
|
|
}
|
|
})
|
|
sendUserToast('Connected to Teams successfully')
|
|
onLoadSettings()
|
|
} catch (error) {
|
|
// Extract the actual error message from the API response
|
|
let errorMessage = 'Failed to connect to Teams'
|
|
|
|
if (typeof error?.body === 'string') {
|
|
errorMessage = error.body
|
|
} else if (error?.body?.message) {
|
|
errorMessage = error.body.message
|
|
} else if (error?.message && error.message !== 'Bad Request') {
|
|
errorMessage = error.message
|
|
}
|
|
|
|
sendUserToast(errorMessage, true)
|
|
console.error('Error connecting to Teams:', error)
|
|
}
|
|
}
|
|
|
|
const capitalizedPlatform = $derived(platform.charAt(0).toUpperCase() + platform.slice(1))
|
|
</script>
|
|
|
|
<div class="flex flex-col gap-1">
|
|
<div class="text-xs font-semibold text-emphasis">{capitalizedPlatform} connection</div>
|
|
<div class="rounded-md border p-4 flex flex-col gap-6">
|
|
{#if workspaceConfig}
|
|
{@render workspaceConfig()}
|
|
{/if}
|
|
{#if teamName || workspaceSpecificConnection}
|
|
<div class="flex flex-col gap-2 max-w-sm">
|
|
<div class="flex flex-row gap-2 items-center">
|
|
{#if display_name}
|
|
<Badge color="green">
|
|
<Plug size={14} />
|
|
Workspace connected to {capitalizedPlatform} team '{display_name}'</Badge
|
|
>
|
|
{/if}
|
|
<Button
|
|
unifiedSize="md"
|
|
startIcon={{ icon: Unplug }}
|
|
disabled={!$enterpriseLicense && platform === 'teams'}
|
|
onclick={onDisconnect}
|
|
destructive
|
|
variant="subtle"
|
|
>
|
|
Disconnect {capitalizedPlatform}
|
|
{!$enterpriseLicense && platform === 'teams' ? '(EE only)' : ''}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
{:else if !hideConnectButton}
|
|
<div class="flex flex-col gap-2">
|
|
<div class="flex flex-row gap-2 items-center">
|
|
{#if platform === 'teams'}
|
|
{#if $enterpriseLicense && isOAuthEnabled}
|
|
<TeamSelector
|
|
bind:selectedTeam
|
|
minWidth="180px"
|
|
disabled={!$enterpriseLicense}
|
|
onError={(e) => {
|
|
const errorMsg =
|
|
typeof (e as any)?.body === 'string'
|
|
? (e as any).body
|
|
: e?.message || 'Unknown error'
|
|
sendUserToast('Failed to load teams: ' + errorMsg, true)
|
|
}}
|
|
/>
|
|
{/if}
|
|
<Button
|
|
unifiedSize="md"
|
|
variant="accent"
|
|
onclick={connectTeams}
|
|
endIcon={{ icon: MsTeamsIcon }}
|
|
disabled={!selectedTeam || !$enterpriseLicense}
|
|
>
|
|
Connect to {platform.charAt(0).toUpperCase() + platform.slice(1)}
|
|
{$enterpriseLicense ? '' : '(EE only)'}
|
|
</Button>
|
|
{:else}
|
|
<Button
|
|
size="xs"
|
|
variant="accent"
|
|
href={connectHref}
|
|
startIcon={{ icon: Slack }}
|
|
disabled={!isOAuthEnabled}
|
|
>
|
|
Connect to {platform.charAt(0).toUpperCase() + platform.slice(1)}
|
|
</Button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-1">
|
|
<div class="text-primary text-xs font-semibold"> Script or flow to run on /windmill command </div>
|
|
<span class="text-xs text-secondary mb-2"
|
|
>Pick a script or flow meant to be triggered when the `/windmill` command is invoked.</span
|
|
>
|
|
|
|
<div class="flex flex-row gap-2">
|
|
<ScriptPicker
|
|
kinds={['script']}
|
|
allowFlow
|
|
bind:itemKind
|
|
bind:scriptPath
|
|
{initialPath}
|
|
on:select={onSelect}
|
|
disabled={!teamName || (!$enterpriseLicense && platform === 'teams')}
|
|
clearable
|
|
/>
|
|
|
|
{#if teamName && ($enterpriseLicense || platform === 'slack') && (scriptPath === '' || scriptPath === undefined)}
|
|
{#if itemKind === 'script'}
|
|
<Button size="sm" endIcon={{ icon: Code2 }} href={createScriptHref}>
|
|
Create a script from template to handle {platform} commands
|
|
</Button>
|
|
{:else if itemKind === 'flow'}
|
|
<Button size="sm" endIcon={{ icon: BarsStaggered }} href={createFlowHref}>
|
|
Create a flow from template to handle {platform} commands
|
|
</Button>
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
|
|
{#if !teamName}
|
|
<div class="text-red-500 text-xs"
|
|
>Please connect your workspace to {capitalizedPlatform} to use this feature</div
|
|
>
|
|
{/if}
|
|
|
|
<CollapseLink text="How to use">
|
|
<div class="prose text-2xs text-primary">
|
|
Upon connection, templates for a <a href="{$hubBaseUrlStore}/scripts/{platform}/1405/"
|
|
>script</a
|
|
>
|
|
and <a href="{$hubBaseUrlStore}/flows/28/">flow</a> are available.
|
|
|
|
<br /><br />
|
|
|
|
The script or flow chosen is passed the parameters `response_url: string` and `text: string`
|
|
respectively the url to reply directly to the trigger and the text of the command.
|
|
|
|
<br /><br />
|
|
|
|
It can take additionally the following args: channel_id, user_name, user_id, command,
|
|
trigger_id, api_app_id
|
|
|
|
<br /><br />
|
|
|
|
<span class="font-bold text-xs">
|
|
The script or flow is permissioned as group "{platform}" that will be automatically created
|
|
after connection to {platform.charAt(0).toUpperCase() + platform.slice(1)}.
|
|
</span>
|
|
|
|
<br /><br />
|
|
|
|
See more on
|
|
<a href={documentationLink}>documentation</a>.
|
|
</div>
|
|
</CollapseLink>
|
|
</div>
|