diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 5e59225745..67c39c95b6 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -9431876ca4dd2ce51a04b102557daa9f685dc5d5 \ No newline at end of file +80d9e507977b55d0b76272905c969c3d6e830083 \ No newline at end of file diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 3e5ac823f2..6e3a0fa543 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -845,6 +845,12 @@ paths: operationId: renewLicenseKey tags: - setting + parameters: + - name: license_key + in: query + required: false + schema: + type: string responses: "200": description: status @@ -859,6 +865,12 @@ paths: operationId: createCustomerPortalSession tags: - setting + parameters: + - name: license_key + in: query + required: false + schema: + type: string responses: "200": description: url to portal diff --git a/backend/windmill-api/src/settings.rs b/backend/windmill-api/src/settings.rs index c0a8c2960f..63bb0b11ce 100644 --- a/backend/windmill-api/src/settings.rs +++ b/backend/windmill-api/src/settings.rs @@ -350,7 +350,7 @@ pub async fn renew_license_key() -> Result { pub async fn renew_license_key(Extension(db): Extension, authed: ApiAuthed) -> Result { require_super_admin(&db, &authed.email).await?; windmill_common::stats_ee::send_stats(&"manual".to_string(), &HTTP_CLIENT, &db).await?; - let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db).await; + let result = windmill_common::ee::renew_license_key(&HTTP_CLIENT, &db, None).await; if result != "success" { return Err(error::Error::BadRequest(format!( @@ -371,7 +371,7 @@ pub async fn create_customer_portal_session() -> Result { #[cfg(feature = "enterprise")] pub async fn create_customer_portal_session() -> Result { - let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT).await?; + let url = windmill_common::ee::create_customer_portal_session(&HTTP_CLIENT, None).await?; return Ok(url); } diff --git a/backend/windmill-queue/src/schedule.rs b/backend/windmill-queue/src/schedule.rs index e1940708b3..aacfa87c10 100644 --- a/backend/windmill-queue/src/schedule.rs +++ b/backend/windmill-queue/src/schedule.rs @@ -14,6 +14,7 @@ use sqlx::{query_scalar, Postgres, Transaction}; use std::collections::HashMap; use std::str::FromStr; use windmill_common::db::Authed; +use windmill_common::ee::LICENSE_KEY_VALID; use windmill_common::flows::Retry; use windmill_common::jobs::JobPayload; use windmill_common::schedule::schedule_to_user; @@ -31,6 +32,13 @@ pub async fn push_scheduled_job<'c, R: rsmq_async::RsmqConnection + Send + 'c>( schedule: &Schedule, authed: Option<&Authed>, ) -> Result> { + if !*LICENSE_KEY_VALID.read().await { + return Err(error::Error::BadRequest( + "License key is not valid. Go to your superadmin settings to update your license key." + .to_string(), + )); + } + let sched = cron::Schedule::from_str(schedule.schedule.as_ref()) .map_err(|e| error::Error::BadRequest(e.to_string()))?; diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index 2155da32e0..4d81ccad68 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -144,16 +144,27 @@ let resourceName = '' - function parseDate(license_key: string): string | undefined { - let splitted = license_key.split('.') + function parseLicenseKey(key: string): { + valid: boolean + expiration?: string + } { + let splitted = key.split('.') if (splitted.length >= 3) { try { let i = parseInt(splitted[1]) let date = new Date(i * 1000) - return date.toLocaleDateString() + const stringDate = date.toLocaleDateString() + if (stringDate !== 'Invalid Date') { + return { + valid: date.getTime() > Date.now(), + expiration: date.toLocaleDateString() + } + } } catch {} } - return undefined + return { + valid: false + } } let to: string = '' @@ -191,7 +202,9 @@ export async function renewLicenseKey() { renewing = true try { - await SettingService.renewLicenseKey() + await SettingService.renewLicenseKey({ + licenseKey: values['license_key'] || undefined + }) sendUserToast('Key renewal successful') loadSettings() } catch (err) { @@ -206,7 +219,9 @@ export async function openCustomerPortal() { opening = true try { - const url = await SettingService.createCustomerPortalSession() + const url = await SettingService.createCustomerPortalSession({ + licenseKey: values['license_key'] || undefined + }) window.open(url, '_blank') } finally { opening = false @@ -492,6 +507,7 @@ {/if} {:else if setting.fieldType == 'license_key'} + {@const { valid, expiration } = parseLicenseKey(values[setting.key] ?? '')}
{#if values[setting.key]?.length > 0} - {#if parseDate(values[setting.key])} + {#if valid}
License key expires on {parseDate(values[setting.key])}License key expires on {expiration ?? ''}
+ {:else if expiration} +
+ + License key expired on {expiration} +
+ {:else} +
+ + Invalid license key format +
{/if} {/if} {#if latestKeyRenewalAttempt} @@ -581,7 +609,7 @@
{/if} - {#if $enterpriseLicense} + {#if valid || expiration}
- {/if} - {#if component.type === 'conditionalwrapper'} - - {:else if component.type === 'steppercomponent' || (component.type === 'tabscomponent' && component.configuration.tabsKind.type === 'static' && component.configuration.tabsKind.value === 'invisibleOnView')} - - {:else if component.type === 'decisiontreecomponent'} - - - {/if} - - - - - - - - +
{ + dispatch('mouseover') + }} on:mousedown|stopPropagation|capture - class={classNames( - 'px-1 text-2xs py-0.5 font-bold w-fit border cursor-move rounded-sm', - 'bg-indigo-100 text-indigo-600 border-indigo-500 hover:bg-indigo-200 hover:text-indigo-800', - 'flex items-center justify-center' + draggable="false" + title={`Id: ${component.id}`} + class={twMerge( + 'py-0.5 text-2xs w-fit h-full min-h-5 border rounded z-50 cursor-move flex flex-row flex-nowrap font-semibold items-center shadow', + selected + ? 'bg-indigo-500/90 border-indigo-500 text-white' + : $connectingInput.opened + ? 'bg-red-500/90 border-red-600 text-white' + : 'bg-blue-500/90 border-blue-600 text-white' )} > - +
+ {component.id} +
+ {#if !connecting} +
+ + + +
+ {/if}
+ {#if selected && !connecting && checkComponentOptions()} +
+ {#if hasInlineEditor} + + {/if} + {#if component.type === 'conditionalwrapper'} + + {:else if component.type === 'steppercomponent' || (component.type === 'tabscomponent' && component.configuration.tabsKind.type === 'static' && component.configuration.tabsKind.value === 'invisibleOnView')} + + {:else if component.type === 'decisiontreecomponent'} + + + {/if} + + + + +
+ {/if}
{/if} diff --git a/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte b/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte index d2c525aecc..e35f648a26 100644 --- a/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte +++ b/frontend/src/lib/components/apps/editor/DecisionTreeDebug.svelte @@ -6,10 +6,14 @@ import type { AppViewerContext } from '../types' import type { DecisionTreeNode } from './component' import { isDebugging } from './settingsPanel/decisionTree/utils' - import { X } from 'lucide-svelte' + import { X, Bug } from 'lucide-svelte' export let nodes: DecisionTreeNode[] = [] export let id: string + export let isSmall = false + export let componentIsDebugging = false + + $: componentIsDebugging = isDebugging($debuggingComponents, id) const { componentControl, debuggingComponents, worldStore } = getContext('AppViewerContext') @@ -71,20 +75,20 @@
- {:else} - {`Debug nodes (current node: ${currentNodeId})`} - {/if} + {:else if isSmall} +
+ {:else}
{`Debug nodes (current node: ${currentNodeId})`}
{/if}
diff --git a/frontend/src/lib/components/apps/editor/GridEditor.svelte b/frontend/src/lib/components/apps/editor/GridEditor.svelte index b6c1323228..52602551d2 100644 --- a/frontend/src/lib/components/apps/editor/GridEditor.svelte +++ b/frontend/src/lib/components/apps/editor/GridEditor.svelte @@ -45,6 +45,23 @@ .flatMap((id) => dfs($app.grid, id, $app.subgrids ?? {})) .filter((x) => x != undefined) as string[] } + + function handleLock(id: string) { + const gridItem = findGridItem($app, id) + if (gridItem) { + toggleFixed(gridItem) + } + $app = $app + } + + function handleFillHeight(id: string) { + const gridItem = findGridItem($app, id) + const b = $breakpoint === 'sm' ? 3 : 12 + if (gridItem?.[b]) { + gridItem[b].fullHeight = !gridItem[b].fullHeight + } + $app = $app + }
@@ -137,7 +154,23 @@ Boolean($selectedComponent?.includes(dataItem.id)) ? 'active-grid-item' : '' )} > - + { + push(history, $app) + $selectedComponent = [dataItem.id] + expandGriditem($app.grid, dataItem.id, $breakpoint) + $app = $app + }} + on:lock={() => { + handleLock(dataItem.id) + }} + on:fillHeight={() => { + handleFillHeight(dataItem.id) + }} + locked={isFixed(dataItem)} + fullHeight={dataItem?.[$breakpoint === 'sm' ? 3 : 12]?.fullHeight} + > { - const gridItem = findGridItem($app, dataItem.id) - if (gridItem) { - toggleFixed(gridItem) - } - $app = $app - }} - on:expand={() => { - push(history, $app) - $selectedComponent = [dataItem.id] - expandGriditem($app.grid, dataItem.id, $breakpoint) - $app = $app + handleLock(dataItem.id) }} on:fillHeight={() => { - const gridItem = findGridItem($app, dataItem.id) - const b = $breakpoint === 'sm' ? 3 : 12 - if (gridItem?.[b]) { - gridItem[b].fullHeight = !gridItem[b].fullHeight - } - $app = $app + handleFillHeight(dataItem.id) }} /> diff --git a/frontend/src/lib/components/apps/editor/GridEditorMenu.svelte b/frontend/src/lib/components/apps/editor/GridEditorMenu.svelte index ef0aee70c7..778eee0067 100644 --- a/frontend/src/lib/components/apps/editor/GridEditorMenu.svelte +++ b/frontend/src/lib/components/apps/editor/GridEditorMenu.svelte @@ -1,5 +1,6 @@