Compare commits
3 Commits
v1.536.0
...
rf/schemaT
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
277b97ffc3 | ||
|
|
4ad0d255f3 | ||
|
|
86f41ffcde |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, flow_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, flow_version) WHERE flow_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "434d8dfbc25cf7e92de51d763d3a2904ccc2e95ecc3d90b43a6394a7bb4d26ab"
|
||||
"hash": "2367e7c0f7fbafe0971a187c0909617da55251e97180babf6ac9e8068f26d73d"
|
||||
}
|
||||
12
backend/.sqlx/query-4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc.json
generated
Normal file
12
backend/.sqlx/query-4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4221d98d76f3cb32d6be581b0f63cf7578429009bee4f648e2c1bc3784fdbefc"
|
||||
}
|
||||
12
backend/.sqlx/query-807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa.json
generated
Normal file
12
backend/.sqlx/query-807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "807c920bff25f56b10e88900d879cf5e8484c147e457044d6b075323b163ebaa"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, script_hash, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5) \n ON CONFLICT (workspace_id, script_hash) WHERE script_hash IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e77fcf4e0d58855542605d13177df61671334418820ca942b442adfab413cbae"
|
||||
"hash": "8d119104337bf99e9aa9dcbac0a54154267a7db96cc0fb3ebaac95635e24da29"
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "VACUUM v2_job_queue, v2_job_runtime, v2_job_status",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b9b38d63af3670d1f11d5cbb82a8008a9479bf4b3d7231371ebf26382ecde365"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path) DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
|
||||
"query": "INSERT INTO deployment_metadata (workspace_id, path, app_version, callback_job_ids, deployment_msg) VALUES ($1, $2, $3, $4, $5)\n ON CONFLICT (workspace_id, path, app_version) WHERE app_version IS NOT NULL DO UPDATE SET callback_job_ids = $4, deployment_msg = $5",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -14,5 +14,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f04632c3a8e0d7c5b48cdd26a99bb1dc5bd12df221f82405d663b8f15f5c0c3a"
|
||||
"hash": "f37140fcdc721a8b199471b30c2baf124affa2eaf56c801c8dac3264c584f981"
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
eabd52eaa454c37a5beb1f456c1ea649052eb58a
|
||||
6396854336ae27fb14ccb792d80c31ff614b2afa
|
||||
@@ -8,7 +8,7 @@ use std::{
|
||||
atomic::{AtomicU16, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
@@ -1530,6 +1530,20 @@ pub async fn monitor_db(
|
||||
}
|
||||
};
|
||||
|
||||
// run every hour
|
||||
let vacuum_queue_f = async {
|
||||
if server_mode && iteration.is_some() && iteration.as_ref().unwrap().should_run(60) {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
let instant = Instant::now();
|
||||
tracing::info!("vacuuming tables");
|
||||
if let Err(e) = vacuuming_tables(&db).await {
|
||||
tracing::error!("Error vacuuming v2_job: {:?}", e);
|
||||
}
|
||||
tracing::info!("vacuum tables done in {}s", instant.elapsed().as_secs());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let expired_items_f = async {
|
||||
if server_mode && !initial_load {
|
||||
if let Some(db) = conn.as_sql() {
|
||||
@@ -1607,6 +1621,7 @@ pub async fn monitor_db(
|
||||
expired_items_f,
|
||||
zombie_jobs_f,
|
||||
stale_jobs_f,
|
||||
vacuum_queue_f,
|
||||
expose_queue_metrics_f,
|
||||
verify_license_key_f,
|
||||
worker_groups_alerts_f,
|
||||
@@ -1619,6 +1634,13 @@ pub async fn monitor_db(
|
||||
);
|
||||
}
|
||||
|
||||
async fn vacuuming_tables(db: &Pool<Postgres>) -> error::Result<()> {
|
||||
sqlx::query!("VACUUM v2_job, v2_job_completed, job_result_stream, job_stats, job_logs, concurrency_key, log_file, metrics")
|
||||
.execute(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn expose_queue_metrics(db: &Pool<Postgres>) {
|
||||
let last_check = sqlx::query_scalar!(
|
||||
"SELECT created_at FROM metrics WHERE id LIKE 'queue_count_%' ORDER BY created_at DESC LIMIT 1"
|
||||
|
||||
@@ -45,6 +45,7 @@ use windmill_worker::process_relative_imports;
|
||||
use windmill_common::{
|
||||
assets::{clear_asset_usage, insert_asset_usage, AssetUsageKind, AssetWithAltAccessType},
|
||||
error::to_anyhow,
|
||||
utils::WarnAfterExt,
|
||||
worker::CLOUD_HOSTED,
|
||||
};
|
||||
|
||||
@@ -1447,6 +1448,7 @@ async fn raw_script_by_path_internal(
|
||||
w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.warn_after_seconds(5)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
@@ -1457,6 +1459,7 @@ async fn raw_script_by_path_internal(
|
||||
w_id
|
||||
)
|
||||
.fetch_one(&db)
|
||||
.warn_after_seconds(5)
|
||||
.await?
|
||||
.unwrap_or(false);
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ use serde_json::Value;
|
||||
use windmill_audit::audit_oss::{audit_log, AuditAuthorable};
|
||||
use windmill_audit::ActionKind;
|
||||
use windmill_common::{
|
||||
db::UserDB, error::{Error, JsonResult, Result}, utils::{not_found_if_none, paginate, Pagination, StripPath}, variables::{
|
||||
db::UserDB,
|
||||
error::{Error, JsonResult, Result},
|
||||
utils::{not_found_if_none, paginate, Pagination, StripPath, WarnAfterExt},
|
||||
variables::{
|
||||
build_crypt, get_reserved_variables, ContextualVariable, CreateVariable, ListableVariable,
|
||||
},
|
||||
worker::CLOUD_HOSTED,
|
||||
@@ -693,6 +696,7 @@ pub async fn get_value_internal<'c>(
|
||||
LEFT JOIN account ON variable.account = account.id WHERE variable.path = $1 AND variable.workspace_id = $2", path, w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.warn_after_seconds(5)
|
||||
.await?;
|
||||
|
||||
let variable = if let Some(variable) = variable_o {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
use crate::error;
|
||||
use crate::utils::WarnAfterExt;
|
||||
use crate::worker::Connection;
|
||||
use crate::{worker::WORKER_GROUP, BASE_URL, DB};
|
||||
use chrono::{SecondsFormat, Utc};
|
||||
@@ -106,6 +107,7 @@ pub async fn get_workspace_key(w_id: &str, db: &DB) -> crate::error::Result<Stri
|
||||
w_id
|
||||
)
|
||||
.fetch_one(db)
|
||||
.warn_after_seconds(5)
|
||||
.await
|
||||
.map_err(|e| crate::Error::internal_err(format!("fetching workspace key: {e:#}")))?;
|
||||
Ok(key)
|
||||
|
||||
@@ -308,7 +308,7 @@ pub(crate) async fn queue_vacuum(conn: &Connection, worker_name: &str, hostname:
|
||||
tokio::task::spawn(
|
||||
(async move {
|
||||
tracing::info!(worker = %worker_name, hostname = %hostname, "vacuuming queue");
|
||||
if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status")
|
||||
if let Err(e) = sqlx::query!("VACUUM v2_job_queue, v2_job_runtime, v2_job_status, job_perms")
|
||||
.execute(&db2)
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -523,7 +523,6 @@
|
||||
<div class="shrink-0">
|
||||
<Toggle
|
||||
bind:checked={jsonView}
|
||||
label="JSON View"
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'JSON editor',
|
||||
@@ -650,7 +649,7 @@
|
||||
bind:order={schema.properties[argName].order}
|
||||
{isFlowInput}
|
||||
{isAppInput}
|
||||
on:change={() => {
|
||||
onChange={() => {
|
||||
schema = $state.snapshot(schema)
|
||||
dispatch('change', schema)
|
||||
}}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import type { EnumType } from '$lib/common'
|
||||
import { computeKind } from '$lib/utils'
|
||||
import { untrack } from 'svelte'
|
||||
import Label from './Label.svelte'
|
||||
import ResourceTypePicker from './ResourceTypePicker.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
@@ -29,6 +28,7 @@
|
||||
enumLabels?: Record<string, string> | undefined
|
||||
overrideAllowKindChange?: boolean
|
||||
originalType?: string | undefined
|
||||
onChange?: () => void
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -45,7 +45,8 @@
|
||||
dateFormat = $bindable(),
|
||||
enumLabels = $bindable(undefined),
|
||||
overrideAllowKindChange = true,
|
||||
originalType = undefined
|
||||
originalType = undefined,
|
||||
onChange = () => {}
|
||||
}: Props = $props()
|
||||
|
||||
let kind: 'none' | 'pattern' | 'enum' | 'resource' | 'format' | 'base64' | 'date-time' = $state(
|
||||
@@ -82,18 +83,22 @@
|
||||
['Pattern', 'pattern']
|
||||
]
|
||||
|
||||
run(() => {
|
||||
$effect.pre(() => {
|
||||
format =
|
||||
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : format
|
||||
kind == 'resource'
|
||||
? resource != undefined
|
||||
? `resource-${resource}`
|
||||
: 'resource'
|
||||
: untrack(() => format)
|
||||
})
|
||||
run(() => {
|
||||
$effect.pre(() => {
|
||||
pattern = patternStr == '' ? undefined : patternStr
|
||||
})
|
||||
run(() => {
|
||||
$effect.pre(() => {
|
||||
contentEncoding = kind == 'base64' ? 'base64' : undefined
|
||||
})
|
||||
|
||||
run(() => {
|
||||
$effect.pre(() => {
|
||||
if (format == 'email') {
|
||||
pattern = '^[\\w-+.]+@([\\w-]+\\.)+[\\w-]{2,63}$'
|
||||
}
|
||||
@@ -369,6 +374,7 @@
|
||||
options={{ right: 'Is Password' }}
|
||||
checked={password}
|
||||
on:change={(e) => {
|
||||
onChange?.()
|
||||
if (e.detail) {
|
||||
password = true
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { createBubbler, stopPropagation } from 'svelte/legacy'
|
||||
|
||||
const bubble = createBubbler()
|
||||
import { classNames } from '$lib/utils'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
@@ -6,35 +9,62 @@
|
||||
import { AlertTriangle } from 'lucide-svelte'
|
||||
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
|
||||
|
||||
export let options: {
|
||||
left?: string
|
||||
leftTooltip?: string
|
||||
right?: string
|
||||
rightTooltip?: string
|
||||
rightDocumentationLink?: string
|
||||
} = {}
|
||||
export let checked: boolean = false
|
||||
export let disabled = false
|
||||
export let textClass = ''
|
||||
export let textStyle = ''
|
||||
export let color: 'blue' | 'red' | 'nord' = 'blue'
|
||||
export let id = (Math.random() + 1).toString(36).substring(10)
|
||||
export let lightMode: boolean = false
|
||||
export let eeOnly: boolean = false
|
||||
export let aiId: string | undefined = undefined
|
||||
export let aiDescription: string | undefined = undefined
|
||||
|
||||
export let size: 'sm' | 'xs' | '2xs' | '2sm' = 'sm'
|
||||
|
||||
const dispatch = createEventDispatcher<{ change: boolean }>()
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right)
|
||||
|
||||
export let textDisabled = false
|
||||
interface Props {
|
||||
options?: {
|
||||
left?: string
|
||||
leftTooltip?: string
|
||||
right?: string
|
||||
rightTooltip?: string
|
||||
rightDocumentationLink?: string
|
||||
}
|
||||
checked?: boolean | undefined
|
||||
disabled?: boolean
|
||||
textClass?: string
|
||||
textStyle?: string
|
||||
color?: 'blue' | 'red' | 'nord'
|
||||
id?: any
|
||||
lightMode?: boolean
|
||||
eeOnly?: boolean
|
||||
aiId?: string | undefined
|
||||
aiDescription?: string | undefined
|
||||
size?: 'sm' | 'xs' | '2xs' | '2sm'
|
||||
class?: string | undefined
|
||||
textDisabled?: boolean
|
||||
right?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
options = {},
|
||||
checked = $bindable(undefined),
|
||||
disabled = false,
|
||||
textClass = '',
|
||||
textStyle = '',
|
||||
color = 'blue',
|
||||
id = (Math.random() + 1).toString(36).substring(10),
|
||||
lightMode = false,
|
||||
eeOnly = false,
|
||||
aiId = undefined,
|
||||
aiDescription = undefined,
|
||||
size = 'sm',
|
||||
class: clazz = undefined,
|
||||
textDisabled = false,
|
||||
right
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
if (checked == undefined) {
|
||||
checked = false
|
||||
}
|
||||
})
|
||||
|
||||
const bothOptions = Boolean(options.left) && Boolean(options.right)
|
||||
</script>
|
||||
|
||||
<label
|
||||
for={id}
|
||||
class="{$$props.class || ''} z-auto flex flex-row items-center duration-50 {disabled
|
||||
class="{clazz || ''} z-auto flex flex-row items-center duration-50 {disabled
|
||||
? 'grayscale opacity-50'
|
||||
: 'cursor-pointer'}"
|
||||
>
|
||||
@@ -55,11 +85,11 @@
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="relative"
|
||||
on:click|stopPropagation
|
||||
onclick={stopPropagation(bubble('click'))}
|
||||
use:triggerableByAI={{
|
||||
id: aiId,
|
||||
description: aiDescription,
|
||||
@@ -69,16 +99,16 @@
|
||||
}}
|
||||
>
|
||||
<input
|
||||
on:focus
|
||||
on:click
|
||||
onfocus={bubble('focus')}
|
||||
onclick={bubble('click')}
|
||||
{disabled}
|
||||
type="checkbox"
|
||||
{id}
|
||||
class="sr-only peer"
|
||||
bind:checked
|
||||
on:change|stopPropagation={(e) => {
|
||||
onchange={stopPropagation((e) => {
|
||||
dispatch('change', checked)
|
||||
}}
|
||||
})}
|
||||
/>
|
||||
<div
|
||||
class={classNames(
|
||||
@@ -116,7 +146,7 @@
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
<slot name="right" />
|
||||
{@render right?.()}
|
||||
</label>
|
||||
{#if eeOnly && disabled}
|
||||
<span class="inline-flex text-xs items-center gap-1 !text-yellow-500 whitespace-nowrap ml-8">
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { createEventDispatcher, onMount, untrack } from 'svelte'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
import { onMount, untrack } from 'svelte'
|
||||
|
||||
interface Props {
|
||||
description?: string
|
||||
@@ -45,6 +44,7 @@
|
||||
| undefined
|
||||
typeeditor?: import('svelte').Snippet
|
||||
children?: import('svelte').Snippet
|
||||
onChange?: () => void
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -66,7 +66,8 @@
|
||||
order = $bindable(),
|
||||
itemsType = $bindable(undefined),
|
||||
typeeditor,
|
||||
children
|
||||
children,
|
||||
onChange = undefined
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
@@ -75,8 +76,6 @@
|
||||
}
|
||||
})
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
|
||||
let el: HTMLTextAreaElement | undefined = undefined
|
||||
|
||||
let oneOfSelected: string | undefined = $state(
|
||||
@@ -140,7 +139,8 @@
|
||||
if (!deepEqual(extra, initialExtra)) {
|
||||
initialExtra = structuredClone($state.snapshot(extra))
|
||||
console.debug('property content updated')
|
||||
dispatchIfMounted('change')
|
||||
|
||||
onChange?.()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
order
|
||||
}
|
||||
console.debug('property schema updated')
|
||||
dispatchIfMounted('change')
|
||||
onChange?.()
|
||||
}
|
||||
}
|
||||
$effect(() => {
|
||||
@@ -177,7 +177,7 @@
|
||||
rows="2"
|
||||
bind:value={description}
|
||||
onkeydown={onKeyDown}
|
||||
onchange={() => dispatch('change')}
|
||||
onchange={onChange}
|
||||
placeholder="Field description"
|
||||
></textarea>
|
||||
</Label>
|
||||
@@ -188,7 +188,7 @@
|
||||
{/snippet}
|
||||
<input
|
||||
bind:value={title}
|
||||
onchange={() => dispatch('change')}
|
||||
onchange={onChange}
|
||||
onkeydown={onKeyDown}
|
||||
placeholder="Field title"
|
||||
/>
|
||||
@@ -206,7 +206,7 @@
|
||||
placeholder="Enter a placeholder"
|
||||
rows="1"
|
||||
bind:value={placeholder}
|
||||
onchange={() => dispatch('change')}
|
||||
onchange={onChange}
|
||||
disabled={!shouldDisplayPlaceholder(type, format, enum_, contentEncoding, pattern, extra)}
|
||||
></textarea>
|
||||
</Label>
|
||||
@@ -236,6 +236,7 @@
|
||||
bind:enumLabels={extra['enumLabels']}
|
||||
originalType={extra['originalType']}
|
||||
overrideAllowKindChange={isFlowInput || isAppInput}
|
||||
{onChange}
|
||||
/>
|
||||
{:else if type == 'number' || type == 'integer'}
|
||||
<NumberTypeNarrowing
|
||||
|
||||
@@ -26,4 +26,6 @@
|
||||
|
||||
<!-- <ScriptWrapper {script} neverShowMeta={true} {customUi} /> -->
|
||||
|
||||
<EditableSchemaSdkWrapper {customUi} {schema} />
|
||||
<EditableSchemaSdkWrapper onSchemaChange={(schema) => console.log(schema)} {customUi} {schema} />
|
||||
|
||||
{JSON.stringify(schema)}
|
||||
|
||||
Reference in New Issue
Block a user