* 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 commit6e1588d59e. * 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 commit0631d03cb0. * 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 commit1e1f930568. * Revert "claude step 1: detect var declarations" This reverts commitf866f4819d. * 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 commit88068b1a77. * sqlx prepare * Box pin in test_workflow_as_code to fix stack overflow * go to settings button * ee repo ref * fix compilation * wording nit
302 lines
7.9 KiB
Svelte
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>
|