Files
windmill/frontend/src/lib/components/sidebar/CriticalAlertModal.svelte
Diego Imbert 9bbab3321e feat: Data tables (#7226)
* data tables settings ui

* install runed

* zod 4 fixes

* use new toJSONSchema

* Migrate ducklake catalogs to more generic custom instance databases

* fix compilation

* Safety conversion for old duckdb ffi

* data tables settings

* ts client basis

* inline run works

* datatables work

* Revert "datatables work"

This reverts commit 6e1588d59e.

* datatables work (without leaking pg credentials)

* println

* separate sqlUtils.ts

* nit

* Separate custom instance db Select and Wizard components

* nit

* nit wording

* add tags to custom instance dbs

* error when trying to use ducklake as datatable or opposite

* show status in dropdown

* data table instance setup works

* sqk function for ducklake

* factorize logic

* fix temp reactivity

* Data table assetexplore

* Migrate S3 permissions to modal

* Revert "Migrate S3 permissions to modal"

This reverts commit 0631d03cb0.

* nit query -> fetch

* Custom instance setup new look

* run_language_executor separate fn

* run_inline param

* nit wording

* Better typed client

* Data tables display as assets in frontend

* asset db icon

* nit

* cleaner errors

* nit

* Fix sed calls in mac

* run_inline_script_preview in python client

* basic python datatable client

* datatable and datalake parser in python

* ducklake client python

* nit fix

* Fix migration producing NULL instead of {} when no custom databases

* merge conflict fail

* python ducklake client arg fix

* parse or infer sql types in ts client

* ts asset parser, detect datatable & ducklake R/W

* fix sql repl for other read ops than select

* export type SqlTemplateFunction

* rename list_custom_instance_pg_databases

* typecheck datatable and ducklake name in Typescript

* Fix typecheck datatable and ducklake in TS

* declare module overriding instead of extending

* infer_sql_type in python client

* SqlQuery object in python

* fix merge conflicts

* update const_format

* CI fix

* factor out to var_identifiers

* sqlx prepare

* unnecessary security (admin is required)

* clearer comment

* ee repo ref

* nit snake case

* claude step 1: detect var declarations

* move detect_sql_access_type to common mod

* claude step 2: detect when saved vars are queried

* Revert "claude step 2: detect when saved vars are queried"

This reverts commit 1e1f930568.

* Revert "claude step 1: detect var declarations"

This reverts commit f866f4819d.

* remove ducklake/datatable and default

* detect data table assigns in var_identifiers

* Python parser successfully infers R/W/RW from ducklake / datatable

* still register ducklake/datatable if not used as unknown R/W

* Go to settings button in Assets Dropdown on not found

* nit

* sqlx prepare fail

* manual fix, somehow sqlx prepare won't do it

* fix frontend ci

* ee repo ref

* ducklake_user doesnt exist in unit tests

* nit fix

* ui nit

* nit

* nit missing clone

* fork ducklakes and datatables

* fix surface hover bug

* stupid mistake

* better deeply reactive mutable derived

* Ducklake picker

* Editor bar data tables

* DuckDB supports datatables

* datatable in duckdb asset parser

* duckdb asset parser var_identifiers

* Revert "duckdb asset parser var_identifiers"

This reverts commit 88068b1a77.

* sqlx prepare

* Box pin in test_workflow_as_code to fix stack overflow

* go to settings button

* ee repo ref

* fix compilation

* wording nit
2025-12-05 23:08:58 +00:00

302 lines
7.9 KiB
Svelte

<script lang="ts">
import { onMount, onDestroy } from 'svelte'
import CriticalAlertModalInner from './CriticalAlertModalInner.svelte'
import { SettingService, type CriticalAlert } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import {
workspaceStore,
isCriticalAlertsUIOpen,
devopsRole,
userStore,
superadmin
} from '$lib/stores'
import Modal2 from '../common/modal/Modal2.svelte'
import { Button } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import List from '$lib/components/common/layout/List.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { BellOff, Bell, ExternalLink, Settings } from 'lucide-svelte'
import { base } from '$lib/base'
import Notification from '$lib/components/common/alert/Notification.svelte'
export let open: boolean = false
export let numUnacknowledgedCriticalAlerts: number = 0
export let muteSettings
let workspaceContext = false
let childRef
$: {
setupApiFunctions(workspaceContext)
}
function setupApiFunctions(_ctx?) {
getCriticalAlerts = withSuperadminLogic(
SettingService.getCriticalAlerts,
SettingService.workspaceGetCriticalAlerts
)
acknowledgeCriticalAlert = withSuperadminLogic(
SettingService.acknowledgeCriticalAlert,
SettingService.workspaceAcknowledgeCriticalAlert
)
acknowledgeAllCriticalAlerts = withSuperadminLogic(
SettingService.acknowledgeAllCriticalAlerts,
SettingService.workspaceAcknowledgeAllCriticalAlerts
)
}
$: isCriticalAlertsUIOpen.set(open)
$: if ($isCriticalAlertsUIOpen) open = $isCriticalAlertsUIOpen
let checkForNewAlertsInterval: ReturnType<typeof setInterval>
let checkingForNewAlerts = false
const withSuperadminLogic = (superadminFunction, workspaceFunction) => {
return async (params = {}) => {
if (!$devopsRole || workspaceContext) {
return workspaceFunction({
...params,
workspace: $workspaceStore
})
} else {
return superadminFunction(params)
}
}
}
let getCriticalAlerts
let acknowledgeCriticalAlert
let acknowledgeAllCriticalAlerts
setupApiFunctions()
onMount(async () => {
await updateHasUnacknowledgedCriticalAlerts(false)
checkForNewAlertsInterval = setInterval(() => {
updateHasUnacknowledgedCriticalAlerts(true)
}, 15000)
})
onDestroy(() => {
clearInterval(checkForNewAlertsInterval)
})
async function saveWorkSpaceMuteSetting() {
await SettingService.workspaceMuteCriticalAlertsUi({
workspace: $workspaceStore!,
requestBody: {
mute_critical_alerts: muteSettings.workspace
}
})
sendUserToast(
`Critical alert UI mute settings changed.\nPlease reload page for UI changes to take effect.`
)
childRef.refreshAlerts()
}
async function saveGlobalMuteSetting() {
await SettingService.setGlobal({
key: 'critical_alert_mute_ui',
requestBody: { value: muteSettings.global }
})
sendUserToast(
`Critical alert UI mute settings changed.\nPlease reload page for UI changes to take effect.`
)
childRef.refreshAlerts()
}
async function updateHasUnacknowledgedCriticalAlerts(sendToast: boolean = false) {
if (typeof document !== 'undefined' && document.visibilityState !== 'visible') {
return
}
if (checkingForNewAlerts) return
checkingForNewAlerts = true
try {
const params = {
page: 1,
pageSize: 1000,
acknowledged: false
}
let unacknowledged: CriticalAlert[] = []
if (!$devopsRole && $workspaceStore) {
const res = await SettingService.workspaceGetCriticalAlerts({
...params,
workspace: $workspaceStore
})
unacknowledged = res.alerts ?? []
} else {
const res = await SettingService.getCriticalAlerts(params)
unacknowledged = res.alerts ?? []
}
if (
numUnacknowledgedCriticalAlerts === 0 &&
unacknowledged.length > 0 &&
sendToast &&
(($devopsRole && !muteSettings.global) || (!$devopsRole && !muteSettings.workspace))
) {
sendUserToast(
'Critical Alert:',
true,
[
{
label: 'View',
callback: () => {
open = true
}
},
{
label: 'Acknowledge',
callback: () => {
if (unacknowledged[0].id) acknowledgeAlert(unacknowledged[0].id)
}
}
],
unacknowledged[0].message,
10000
)
}
numUnacknowledgedCriticalAlerts = unacknowledged.length
} catch (e) {
console.error('Error fetching critical alerts', e)
sendUserToast('Error fetching critical alerts', true)
} finally {
checkingForNewAlerts = false
}
}
async function acknowledgeAlert(id: number) {
await acknowledgeCriticalAlert({ id })
updateHasUnacknowledgedCriticalAlerts()
}
</script>
<Modal2
bind:isOpen={open}
title="Critical Alerts"
target="#content"
fixedHeight="lg"
fixedWidth="lg"
>
<svelte:fragment slot="header-left">
<Notification notificationCount={numUnacknowledgedCriticalAlerts} notificationLimit={9999} />
</svelte:fragment>
<svelte:fragment slot="header-right">
<List horizontal>
{#if $superadmin || $userStore?.is_admin}
<Popover
floatingConfig={{ strategy: 'fixed', placement: 'bottom-end' }}
portal="#mute-settings-button"
contentClasses="p-4"
>
<svelte:fragment slot="trigger">
<div id="mute-settings-button">
<Button variant="default" nonCaptureEvent>
{#if muteSettings.global || muteSettings.workspace}
<BellOff size="16" />
{:else}
<Bell size="16" />
{/if}
</Button>
</div>
</svelte:fragment>
<svelte:fragment slot="content">
<List justify="start">
<div class="w-full">
{#if $superadmin}
<Toggle
on:change={saveGlobalMuteSetting}
bind:checked={muteSettings.global}
options={{
right: 'Automatically acknowledge critical alerts instance wide'
}}
size="xs"
/>
{/if}
</div>
<div class="w-full">
<Toggle
on:change={saveWorkSpaceMuteSetting}
bind:checked={muteSettings.workspace}
options={{
right: 'Automatically acknowledge critical alerts for current workspace'
}}
size="xs"
/>
</div>
</List>
</svelte:fragment>
</Popover>
{/if}
{#if $superadmin}
<Popover
floatingConfig={{ strategy: 'fixed', placement: 'bottom-end' }}
portal="#settings-button"
contentClasses="p-4"
>
<svelte:fragment slot="trigger">
<div id="settings-button">
<Button variant="default" nonCaptureEvent>
<Settings size="16" />
</Button>
</div>
</svelte:fragment>
<svelte:fragment slot="content">
<List justify="start" gap="none">
<div class="w-full">
<Button
size="xs"
color="light"
href="{base}/?workspace=admins#superadmin-settings"
target="_blank"
>
<div class="w-full">
<List horizontal justify="between" gap="sm">
<div>Instance Critical Alert Settings</div>
<ExternalLink size="16" />
</List>
</div>
</Button>
</div>
<div class="w-full">
<Button
size="xs"
color="light"
href="{base}/workspace_settings?tab=error_handler"
target="_blank"
>
Workspace Critical Alert Settings <ExternalLink size="16" />
</Button>
</div>
</List>
</svelte:fragment>
</Popover>
{:else}
<Button
size="xs"
variant="default"
href="{base}/workspace_settings?tab=error_handler"
target="_blank"
>
<List horizontal justify="between" gap="sm">
<Settings size="16" />
<ExternalLink size="16" />
</List>
</Button>
{/if}
</List>
</svelte:fragment>
<CriticalAlertModalInner
bind:workspaceContext
{numUnacknowledgedCriticalAlerts}
{updateHasUnacknowledgedCriticalAlerts}
{getCriticalAlerts}
{acknowledgeCriticalAlert}
{acknowledgeAllCriticalAlerts}
bind:this={childRef}
/>
</Modal2>