feat: deploy triggers to prod/staging workspace (#5429)

* add deploy to staging prod option to trigger

* remove unused import

* handle schedule trigger, fix update fn

* Update frontend/src/lib/utils_deployable.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Update backend/windmill-common/src/workspaces.rs

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* Update frontend/src/lib/components/DeployWorkspace.svelte

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* update kind name and handle script kind flow for websocket

* nits: english wording

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
This commit is contained in:
dieriba
2025-03-06 17:02:11 +01:00
committed by GitHub
parent 802a16743b
commit 996cebeb8a
18 changed files with 902 additions and 120 deletions
-1
View File
@@ -12,5 +12,4 @@
"conventionalCommits.scopes": [
"restructring triggers, decoding trigger message on work"
],
"rust-analyzer.cargo.features": ["mqtt_trigger"]
}
-6
View File
@@ -1,6 +0,0 @@
{
"name": "backend",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}
+1
View File
@@ -15275,6 +15275,7 @@ components:
- resource
- variable
- secret
- trigger
WorkspaceDefaultScripts:
type: object
+1 -3
View File
@@ -459,7 +459,6 @@ async fn get_settings(
.fetch_one(&mut *tx)
.await
.map_err(|e| Error::internal_err(format!("getting settings: {e:#}")))?;
tx.commit().await?;
Ok(Json(settings))
}
@@ -926,7 +925,7 @@ async fn edit_git_sync_config(
Ok(format!("Edit git sync config for workspace {}", &w_id))
}
#[derive(Deserialize)]
#[derive(Debug, Deserialize)]
struct EditDeployUIConfig {
#[cfg(feature = "enterprise")]
deploy_ui_settings: Option<WorkspaceDeploymentUISettings>,
@@ -954,7 +953,6 @@ async fn edit_deploy_ui_config(
require_admin(is_admin, &username)?;
let mut tx = db.begin().await?;
let args_for_audit = format!("{:?}", new_config.deploy_ui_settings);
audit_log(
&mut *tx,
@@ -27,6 +27,7 @@ pub enum ObjectType {
ResourceType,
User,
Group,
Trigger
}
#[derive(Serialize, Deserialize, Debug)]
@@ -9,7 +9,6 @@
import Toggle from './Toggle.svelte'
import { emptyString } from '$lib/utils'
$: deployableWorkspaces = $usersWorkspaceStore?.workspaces
.map((w) => w.id)
.filter((w) => w != $workspaceStore)
@@ -21,8 +20,9 @@
resources: boolean
variables: boolean
secrets: boolean
triggers: boolean
}
type DeployUIType = 'script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret'
type DeployUIType = 'script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret' | 'trigger'
const all_ok: DeployUITypeMap = {
scripts: true,
@@ -30,7 +30,8 @@
apps: true,
resources: true,
variables: true,
secrets: true
secrets: true,
triggers: true
}
export let workspaceToDeployTo: string | undefined
@@ -41,8 +42,6 @@
include_path: [],
include_type: all_ok
}
function deployUITypeMapToArray(
typesMap: DeployUITypeMap,
expectedValue: boolean
@@ -66,6 +65,9 @@
if (typesMap.secrets == expectedValue) {
result.push('secret')
}
if (typesMap.triggers == expectedValue) {
result.push('trigger')
}
return result
}
@@ -165,18 +167,9 @@
</Tooltip></h4
>
<div class="flex flex-col gap-2 mt-1">
<Toggle
bind:checked={deployUiSettings.include_type.scripts}
options={{ right: 'Scripts' }}
/>
<Toggle
bind:checked={deployUiSettings.include_type.flows}
options={{ right: 'Flows' }}
/>
<Toggle
bind:checked={deployUiSettings.include_type.apps}
options={{ right: 'Apps' }}
/>
<Toggle bind:checked={deployUiSettings.include_type.scripts} options={{ right: 'Scripts' }} />
<Toggle bind:checked={deployUiSettings.include_type.flows} options={{ right: 'Flows' }} />
<Toggle bind:checked={deployUiSettings.include_type.apps} options={{ right: 'Apps' }} />
<Toggle
bind:checked={deployUiSettings.include_type.resources}
options={{ right: 'Resources' }}
@@ -198,6 +191,10 @@
options={{ left: 'Include secrets' }}
/>
</div>
<Toggle
bind:checked={deployUiSettings.include_type.triggers}
options={{ right: 'Trigger' }}
/>
</div>
</div>
</div>
@@ -22,22 +22,21 @@
import { Loader2 } from 'lucide-svelte'
import Badge from './common/badge/Badge.svelte'
import DiffDrawer from './DiffDrawer.svelte'
import {
existsTrigger,
getTriggerDependency,
getTriggersDeployData,
getTriggerValue,
type AdditionalInformation,
type Kind
} from '$lib/utils_deployable'
import { sendUserToast } from '$lib/toast'
const dispatch = createEventDispatcher()
type Kind =
| 'script'
| 'resource'
| 'schedule'
| 'variable'
| 'flow'
| 'app'
| 'raw_app'
| 'resource_type'
| 'folder'
export let kind: Kind
export let initialPath: string = ''
export let additionalInformation: AdditionalInformation | undefined = undefined
export let workspaceToDeployTo: string | undefined = undefined
export let hideButton: boolean = false
@@ -148,6 +147,11 @@
}
return [...recObj(res.value), { kind: 'resource_type', path: res.resource_type }]
} else if (kind == 'trigger') {
if (additionalInformation?.triggers) {
return getTriggerDependency(additionalInformation.triggers.kind, path, $workspaceStore!)
}
throw new Error('Missing trigger information')
}
return []
}
@@ -171,60 +175,70 @@
}
async function checkAlreadyExists(kind: Kind, path: string): Promise<boolean> {
if (kind == 'flow') {
return await FlowService.existsFlowByPath({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'script') {
return await ScriptService.existsScriptByPath({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'app') {
return await AppService.existsApp({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'raw_app') {
return await RawAppService.existsRawApp({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'variable') {
return await VariableService.existsVariable({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'resource') {
return await ResourceService.existsResource({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'schedule') {
return await ScheduleService.existsSchedule({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'resource_type') {
return await ResourceService.existsResourceType({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'folder') {
let exists = true
try {
let exists = true
try {
if (kind == 'flow') {
exists = await FlowService.existsFlowByPath({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'script') {
exists = await ScriptService.existsScriptByPath({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'app') {
exists = await AppService.existsApp({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'raw_app') {
exists = await RawAppService.existsRawApp({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'variable') {
exists = await VariableService.existsVariable({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'resource') {
exists = await ResourceService.existsResource({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'schedule') {
exists = await ScheduleService.existsSchedule({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'resource_type') {
exists = await ResourceService.existsResourceType({
workspace: workspaceToDeployTo!,
path: path
})
} else if (kind == 'folder') {
await FolderService.getFolder({
workspace: workspaceToDeployTo!,
name: path
})
} catch (e) {
exists = false
} else if (kind === 'trigger') {
if (additionalInformation?.triggers) {
exists = await existsTrigger(
{ workspace: workspaceToDeployTo!, path },
additionalInformation.triggers.kind
)
} else {
throw new Error('Missing triggers kind')
}
} else {
throw new Error(`Unknown kind ${kind}`)
}
return exists
} else {
throw new Error(`Unknown kind ${kind}`)
} catch (error) {
sendUserToast(error.body ? error.body : error instanceof Error ? error.message : error, true)
exists = false
}
return exists
}
const deploymentStatus: Record<
@@ -405,6 +419,28 @@
name: path
}
})
} else if (kind === 'trigger') {
if (additionalInformation?.triggers) {
const { data, createFn, updateFn } = await getTriggersDeployData(
additionalInformation.triggers.kind,
path,
$workspaceStore!
)
if (alreadyExists) {
await updateFn({
path,
workspace: workspaceToDeployTo!,
requestBody: data
} as any)
} else {
await createFn({
workspace: workspaceToDeployTo!,
requestBody: data
} as any)
}
} else {
throw new Error('Missing triggers kind')
}
} else {
throw new Error(`Unknown kind ${kind}`)
}
@@ -502,6 +538,12 @@
return {
name: folder.name
}
} else if (kind == 'trigger') {
if (additionalInformation?.triggers) {
return await getTriggerValue(additionalInformation.triggers.kind, path, workspace)
} else {
throw new Error(`Missing trigger information`)
}
} else {
throw new Error(`Unknown kind ${kind}`)
}
@@ -2,16 +2,21 @@
import { Button, Drawer } from './common'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import DeployWorkspace from './DeployWorkspace.svelte'
type Kind = 'script' | 'resource' | 'schedule' | 'variable' | 'flow' | 'app' | 'raw_app'
import { type AdditionalInformation, type Kind } from '$lib/utils_deployable'
let initialPath: string | undefined = undefined
let kind: Kind | undefined = undefined
let drawer: Drawer | undefined = undefined
let workspaceToDeployTo: string | undefined = undefined
let deployWorkspace: DeployWorkspace | undefined = undefined
export async function openDrawer(initialPath_l: string, kind_l: Kind) {
let additionalInformation: AdditionalInformation | undefined = undefined
export async function openDrawer(
initialPath_l: string,
kind_l: Kind,
additionalInformation_l?: AdditionalInformation
) {
additionalInformation = additionalInformation_l
initialPath = initialPath_l
kind = kind_l
drawer?.openDrawer()
@@ -20,11 +25,12 @@
<Drawer bind:this={drawer} size="900px">
<DrawerContent title="Deploy {initialPath}" on:close={drawer.closeDrawer}>
{#if kind != undefined && initialPath != undefined}
{#if (kind != 'trigger' && kind != undefined && initialPath != undefined) || (kind === 'trigger' && initialPath != undefined && additionalInformation?.triggers != undefined)}
<DeployWorkspace
hideButton
{initialPath}
{kind}
{additionalInformation}
bind:workspaceToDeployTo
bind:this={deployWorkspace}
/>
+471 -5
View File
@@ -1,7 +1,41 @@
import { minimatch } from 'minimatch'
import type { WorkspaceDeployUISettings } from './gen'
import {
HttpTriggerService,
KafkaTriggerService,
MqttTriggerService,
NatsTriggerService,
PostgresTriggerService,
ScheduleService,
SqsTriggerService,
WebsocketTriggerService,
type WorkspaceDeployUISettings
} from './gen'
import type { TriggerKind } from './components/triggers'
type DeployUIType = 'script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret'
type DeployUIType = 'script' | 'flow' | 'app' | 'resource' | 'variable' | 'secret' | 'trigger'
export type Kind =
| 'script'
| 'resource'
| 'schedule'
| 'variable'
| 'flow'
| 'app'
| 'raw_app'
| 'resource_type'
| 'folder'
| 'trigger'
export const ALL_DEPLOYABLE: WorkspaceDeployUISettings = {
include_path: [],
include_type: ['script', 'flow', 'app', 'resource', 'variable', 'secret', 'trigger']
}
export type AdditionalInformation = {
triggers?: {
kind: TriggerKind
}
}
export function isDeployable(
type: DeployUIType,
@@ -27,7 +61,439 @@ export function isDeployable(
return true
}
export const ALL_DEPLOYABLE: WorkspaceDeployUISettings = {
include_path: [],
include_type: ['script', 'flow', 'app', 'resource', 'variable', 'secret']
export async function existsTrigger(
data: { workspace: string; path: string },
triggerKind: TriggerKind
) {
if (triggerKind === 'routes') {
return await HttpTriggerService.existsHttpTrigger(data)
} else if (triggerKind === 'kafka') {
return await KafkaTriggerService.existsKafkaTrigger(data)
} else if (triggerKind === 'mqtt') {
return await MqttTriggerService.existsMqttTrigger(data)
} else if (triggerKind === 'postgres') {
return await PostgresTriggerService.existsPostgresTrigger(data)
} else if (triggerKind === 'sqs') {
return await SqsTriggerService.existsSqsTrigger(data)
} else if (triggerKind === 'websockets') {
return await WebsocketTriggerService.existsWebsocketTrigger(data)
} else if (triggerKind === 'nats') {
return await NatsTriggerService.existsNatsTrigger(data)
} else if (triggerKind === 'schedules') {
return await ScheduleService.existsSchedule(data)
}
throw new Error(
`Unexpected trigger kind ${triggerKind}. Allowed kinds are: routes, kafka, mqtt, postgres, sqs, websockets, nats, schedules.`
)
}
export async function getTriggersDeployData(kind: TriggerKind, path: string, workspace: string) {
if (kind === 'sqs') {
const sqsTrigger = await SqsTriggerService.getSqsTrigger({
workspace: workspace!,
path: path
})
return {
data: sqsTrigger,
createFn: SqsTriggerService.createSqsTrigger,
updateFn: SqsTriggerService.updateSqsTrigger
}
} else if (kind === 'kafka') {
const kafkaTrigger = await KafkaTriggerService.getKafkaTrigger({
workspace: workspace!,
path: path
})
return {
data: kafkaTrigger,
createFn: KafkaTriggerService.createKafkaTrigger,
updateFn: KafkaTriggerService.updateKafkaTrigger
}
} else if (kind === 'mqtt') {
const mqttTrigger = await MqttTriggerService.getMqttTrigger({
workspace: workspace!,
path: path
})
return {
data: mqttTrigger,
createFn: MqttTriggerService.createMqttTrigger,
updateFn: MqttTriggerService.updateMqttTrigger
}
} else if (kind === 'nats') {
const natsTrigger = await NatsTriggerService.getNatsTrigger({
workspace: workspace!,
path: path
})
return {
data: natsTrigger,
createFn: NatsTriggerService.createNatsTrigger,
updateFn: NatsTriggerService.updateNatsTrigger
}
} else if (kind === 'postgres') {
const postgresTrigger = await PostgresTriggerService.getPostgresTrigger({
workspace: workspace!,
path: path
})
return {
data: postgresTrigger,
createFn: PostgresTriggerService.createPostgresTrigger,
updateFn: PostgresTriggerService.updatePostgresTrigger
}
} else if (kind === 'websockets') {
const websocketTrigger = await WebsocketTriggerService.getWebsocketTrigger({
workspace: workspace!,
path: path
})
return {
data: websocketTrigger,
createFn: WebsocketTriggerService.createWebsocketTrigger,
updateFn: WebsocketTriggerService.updateWebsocketTrigger
}
} else if (kind === 'routes') {
const httpTrigger = await HttpTriggerService.getHttpTrigger({
workspace: workspace!,
path: path
})
return {
data: httpTrigger,
createFn: HttpTriggerService.createHttpTrigger,
updateFn: HttpTriggerService.updateHttpTrigger
}
} else if (kind === 'schedules') {
const schedulesTrigger = await ScheduleService.getSchedule({
workspace: workspace!,
path: path
})
return {
data: schedulesTrigger,
createFn: ScheduleService.createSchedule,
updateFn: ScheduleService.updateSchedule
}
}
throw new Error(`Unexpected trigger kind got: ${kind}`)
}
export async function getTriggerValue(kind: TriggerKind, path: string, workspace: string) {
if (kind === 'sqs') {
const { enabled, script_path, is_flow, queue_url, aws_resource_path, message_attributes } =
await SqsTriggerService.getSqsTrigger({
workspace: workspace!,
path: path
})
return {
enabled,
script_path,
is_flow,
queue_url,
aws_resource_path,
message_attributes
}
} else if (kind === 'kafka') {
const { enabled, script_path, is_flow, kafka_resource_path, topics, group_id } =
await KafkaTriggerService.getKafkaTrigger({
workspace: workspace!,
path: path
})
return {
enabled,
script_path,
is_flow,
kafka_resource_path,
topics,
group_id
}
} else if (kind === 'mqtt') {
const {
enabled,
script_path,
is_flow,
mqtt_resource_path,
v3_config,
v5_config,
client_id,
subscribe_topics,
client_version
} = await MqttTriggerService.getMqttTrigger({
workspace: workspace!,
path: path
})
return {
enabled,
script_path,
is_flow,
mqtt_resource_path,
v3_config,
v5_config,
client_id,
subscribe_topics,
client_version
}
} else if (kind === 'nats') {
const {
enabled,
script_path,
is_flow,
nats_resource_path,
use_jetstream,
stream_name,
consumer_name,
subjects
} = await NatsTriggerService.getNatsTrigger({
workspace: workspace!,
path: path
})
return {
enabled,
script_path,
is_flow,
nats_resource_path,
use_jetstream,
stream_name,
consumer_name,
subjects
}
} else if (kind === 'postgres') {
const {
enabled,
script_path,
is_flow,
postgres_resource_path,
replication_slot_name,
publication_name
} = await PostgresTriggerService.getPostgresTrigger({
workspace: workspace!,
path: path
})
return {
enabled,
script_path,
is_flow,
postgres_resource_path,
replication_slot_name,
publication_name
}
} else if (kind === 'websockets') {
const {
enabled,
script_path,
is_flow,
url,
url_runnable_args,
can_return_message,
filters,
initial_messages
} = await WebsocketTriggerService.getWebsocketTrigger({
workspace: workspace!,
path: path
})
return {
enabled,
script_path,
is_flow,
url,
url_runnable_args,
can_return_message,
filters,
initial_messages
}
} else if (kind === 'routes') {
const {
script_path,
is_flow,
http_method,
route_path,
static_asset_config,
is_async,
requires_auth,
is_static_website
} = await HttpTriggerService.getHttpTrigger({
workspace: workspace!,
path: path
})
return {
script_path,
is_flow,
http_method,
route_path,
static_asset_config,
is_async,
requires_auth,
is_static_website
}
} else if (kind === 'schedules') {
const {
script_path,
is_flow,
on_failure,
schedule,
timezone,
on_failure_times,
on_failure_exact,
on_failure_extra_args,
on_recovery,
on_recovery_times,
on_recovery_extra_args,
on_success,
on_success_extra_args,
ws_error_handler_muted,
retry,
summary,
no_flow_overlap,
tag,
paused_until,
cron_version
} = await ScheduleService.getSchedule({
workspace: workspace!,
path: path
})
return {
script_path,
is_flow,
on_failure,
schedule,
timezone,
on_failure_times,
on_failure_exact,
on_failure_extra_args,
on_recovery,
on_recovery_times,
on_recovery_extra_args,
on_success,
on_success_extra_args,
ws_error_handler_muted,
retry,
summary,
no_flow_overlap,
tag,
paused_until,
cron_version
}
}
throw new Error(`Unexpected trigger kind got: ${kind}`)
}
function retrieveScriptOrFlowKind(path: string, is_flow: boolean): { kind: Kind; path: string } {
return {
kind: is_flow ? 'flow' : 'script',
path
}
}
function retrieveKindsValues({
resource_path,
script_path,
is_flow
}: {
resource_path?: string
script_path: string
is_flow: boolean
}) {
const result: { kind: Kind; path: string }[] = []
if (resource_path) {
result.push({ kind: 'resource', path: resource_path })
}
result.push(retrieveScriptOrFlowKind(script_path, is_flow))
return result
}
export async function getTriggerDependency(kind: TriggerKind, path: string, workspace: string) {
let result: { kind: Kind; path: string }[]
if (kind === 'sqs') {
const { aws_resource_path, script_path, is_flow } = await SqsTriggerService.getSqsTrigger({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ resource_path: aws_resource_path, script_path, is_flow })
} else if (kind === 'kafka') {
const { kafka_resource_path, script_path, is_flow } = await KafkaTriggerService.getKafkaTrigger(
{
workspace: workspace!,
path: path
}
)
result = retrieveKindsValues({ resource_path: kafka_resource_path, script_path, is_flow })
} else if (kind === 'mqtt') {
const { mqtt_resource_path, script_path, is_flow } = await MqttTriggerService.getMqttTrigger({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ resource_path: mqtt_resource_path, script_path, is_flow })
} else if (kind === 'nats') {
const { nats_resource_path, script_path, is_flow } = await NatsTriggerService.getNatsTrigger({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ resource_path: nats_resource_path, script_path, is_flow })
} else if (kind === 'postgres') {
const { postgres_resource_path, script_path, is_flow } =
await PostgresTriggerService.getPostgresTrigger({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ resource_path: postgres_resource_path, script_path, is_flow })
} else if (kind === 'websockets') {
const { script_path, is_flow, url, initial_messages } =
await WebsocketTriggerService.getWebsocketTrigger({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ script_path, is_flow })
const SCRIPT_PREFIX = '$script:'
const FLOW_PREFIX = '$flow:'
if (url.startsWith(SCRIPT_PREFIX))
result.push(retrieveScriptOrFlowKind(url.substring(SCRIPT_PREFIX.length), false))
else if (url.startsWith(FLOW_PREFIX))
result.push(retrieveScriptOrFlowKind(url.substring(FLOW_PREFIX.length), true))
initial_messages?.map((message) => {
if ('runnable_result' in message) {
result.push(
retrieveScriptOrFlowKind(message.runnable_result.path, message.runnable_result.is_flow)
)
}
})
} else if (kind === 'routes') {
const { script_path, is_flow } = await HttpTriggerService.getHttpTrigger({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ script_path, is_flow })
} else if (kind === 'schedules') {
const { script_path, is_flow } = await ScheduleService.getSchedule({
workspace: workspace!,
path: path
})
result = retrieveKindsValues({ script_path, is_flow })
} else {
throw new Error(`Unexpected trigger kind got: ${kind}`)
}
return result
}
@@ -1,5 +1,10 @@
<script lang="ts">
import { KafkaTriggerService, type KafkaTrigger } from '$lib/gen'
import {
KafkaTriggerService,
WorkspaceService,
type KafkaTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import {
canWrite,
displayDate,
@@ -15,8 +20,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle } from 'lucide-svelte'
import { userStore, workspaceStore, userWorkspaces, enterpriseLicense } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -30,12 +35,26 @@
import { isCloudHosted } from '$lib/cloud'
import KafkaIcon from '$lib/components/icons/KafkaIcon.svelte'
import KafkaTriggerEditor from '$lib/components/triggers/kafka/KafkaTriggerEditor.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
type TriggerW = KafkaTrigger & { canWrite: boolean }
let triggers: TriggerW[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (await KafkaTriggerService.listKafkaTriggers({ workspace: $workspaceStore! })).map(
@@ -199,6 +218,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<KafkaTriggerEditor on:update={loadTriggers} bind:this={kafkaTriggerEditor} />
<SearchItems
@@ -394,6 +414,21 @@
kafkaTriggerEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'kafka'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -1,5 +1,10 @@
<script lang="ts">
import { MqttTriggerService, type MqttTrigger } from '$lib/gen'
import {
MqttTriggerService,
WorkspaceService,
type MqttTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import {
canWrite,
displayDate,
@@ -15,8 +20,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle } from 'lucide-svelte'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -30,12 +35,26 @@
import { isCloudHosted } from '$lib/cloud'
import MqttTriggerEditor from '$lib/components/triggers/mqtt/MqttTriggerEditor.svelte'
import { MqttIcon } from '$lib/components/icons'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
type TriggerM = MqttTrigger & { canWrite: boolean }
let triggers: TriggerM[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (await MqttTriggerService.listMqttTriggers({ workspace: $workspaceStore! })).map(
@@ -199,6 +218,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<MqttTriggerEditor on:update={loadTriggers} bind:this={mqttTriggerEditor} />
<SearchItems
@@ -374,6 +394,21 @@
mqttTriggerEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'mqtt'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -1,5 +1,10 @@
<script lang="ts">
import { NatsTriggerService, type NatsTrigger } from '$lib/gen'
import {
NatsTriggerService,
WorkspaceService,
type NatsTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import {
canWrite,
displayDate,
@@ -15,8 +20,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle } from 'lucide-svelte'
import { userStore, workspaceStore, userWorkspaces, enterpriseLicense } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -30,13 +35,26 @@
import { isCloudHosted } from '$lib/cloud'
import NatsIcon from '$lib/components/icons/NatsIcon.svelte'
import NatsTriggerEditor from '$lib/components/triggers/nats/NatsTriggerEditor.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
type TriggerW = NatsTrigger & { canWrite: boolean }
let triggers: TriggerW[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (await NatsTriggerService.listNatsTriggers({ workspace: $workspaceStore! })).map(
(x) => {
@@ -199,6 +217,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<NatsTriggerEditor on:update={loadTriggers} bind:this={natsTriggerEditor} />
<SearchItems
@@ -394,6 +413,21 @@
natsTriggerEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'nats'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -1,5 +1,10 @@
<script lang="ts">
import { PostgresTriggerService, type PostgresTrigger } from '$lib/gen'
import {
PostgresTriggerService,
WorkspaceService,
type PostgresTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import {
canWrite,
displayDate,
@@ -15,8 +20,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, Database } from 'lucide-svelte'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, Database, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -29,13 +34,26 @@
import Popover from '$lib/components/Popover.svelte'
import { isCloudHosted } from '$lib/cloud'
import PostgresTriggerEditor from '$lib/components/triggers/postgres/PostgresTriggerEditor.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
type TriggerD = PostgresTrigger & { canWrite: boolean }
let triggers: TriggerD[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (
await PostgresTriggerService.listPostgresTriggers({ workspace: $workspaceStore! })
@@ -198,6 +216,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<PostgresTriggerEditor on:update={loadTriggers} bind:this={postgresTriggerEditor} />
<SearchItems
@@ -380,6 +399,21 @@
postgresTriggerEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'postgres'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -1,5 +1,10 @@
<script lang="ts">
import { HttpTriggerService, type HttpTrigger } from '$lib/gen'
import {
HttpTriggerService,
WorkspaceService,
type HttpTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import { canWrite, displayDate, getLocalSetting, storeLocalSetting } from '$lib/utils'
import { base } from '$app/paths'
import CenteredPage from '$lib/components/CenteredPage.svelte'
@@ -9,8 +14,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import { Route, Code, Eye, Pen, Plus, Share, Trash } from 'lucide-svelte'
import { userStore, workspaceStore, userWorkspaces, enterpriseLicense } from '$lib/stores'
import { Route, Code, Eye, Pen, Plus, Share, Trash, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -21,13 +26,26 @@
import { setQuery } from '$lib/navigation'
import { onMount } from 'svelte'
import RouteEditor from '$lib/components/triggers/http/RouteEditor.svelte'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
type TriggerW = HttpTrigger & { canWrite: boolean }
let triggers: TriggerW[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (await HttpTriggerService.listHttpTriggers({ workspace: $workspaceStore! })).map(
(x) => {
@@ -151,6 +169,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<RouteEditor on:update={loadTriggers} bind:this={routeEditor} />
<SearchItems
@@ -293,6 +312,21 @@
routeEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'routes'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -1,5 +1,11 @@
<script lang="ts">
import { ScheduleService, JobService, type ScheduleWJobs } from '$lib/gen'
import {
ScheduleService,
JobService,
type ScheduleWJobs,
type WorkspaceDeployUISettings,
WorkspaceService
} from '$lib/gen'
import { canWrite, displayDate, getLocalSetting, storeLocalSetting } from '$lib/utils'
import { base } from '$app/paths'
import CenteredPage from '$lib/components/CenteredPage.svelte'
@@ -11,12 +17,13 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore, userWorkspaces } from '$lib/stores'
import { userStore, workspaceStore, userWorkspaces, enterpriseLicense } from '$lib/stores'
import {
Calendar,
Circle,
Code,
Eye,
FileUp,
List,
Loader2,
Pen,
@@ -36,6 +43,8 @@
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
import { setQuery } from '$lib/navigation'
import { onMount } from 'svelte'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
type ScheduleW = ScheduleWJobs & { canWrite: boolean }
@@ -43,7 +52,18 @@
let shareModal: ShareModal
let loading = true
let loadingSchedulesWithJobStats = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadSchedules(): Promise<void> {
schedules = (await ScheduleService.listSchedules({ workspace: $workspaceStore! })).map((x) => {
return { canWrite: canWrite(x.path, x.extra_perms!, $userStore), ...x }
@@ -243,6 +263,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders, filterEnabledDisabled)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<ScheduleEditor on:update={loadSchedules} bind:this={scheduleEditor} />
<SearchItems
@@ -429,6 +450,21 @@
scheduleEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'schedules'
}
})
}
}
]
: []),
{
displayName: 'View runs',
icon: List,
@@ -1,5 +1,10 @@
<script lang="ts">
import { SqsTriggerService, type SqsTrigger } from '$lib/gen'
import {
SqsTriggerService,
WorkspaceService,
type SqsTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import {
canWrite,
displayDate,
@@ -15,8 +20,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, Database } from 'lucide-svelte'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { Code, Eye, Pen, Plus, Share, Trash, Circle, Database, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -29,13 +34,26 @@
import Popover from '$lib/components/Popover.svelte'
import { isCloudHosted } from '$lib/cloud'
import SqsTriggerEditor from '$lib/components/triggers/sqs/SqsTriggerEditor.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
type TriggerD = SqsTrigger & { canWrite: boolean }
let triggers: TriggerD[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (await SqsTriggerService.listSqsTriggers({ workspace: $workspaceStore! })).map(
(x) => {
@@ -198,6 +216,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<SqsTriggerEditor on:update={loadTriggers} bind:this={sqsTriggerEditor} />
<SearchItems
@@ -375,6 +394,21 @@
sqsTriggerEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'sqs'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -1,5 +1,10 @@
<script lang="ts">
import { WebsocketTriggerService, type WebsocketTrigger } from '$lib/gen'
import {
WebsocketTriggerService,
WorkspaceService,
type WebsocketTrigger,
type WorkspaceDeployUISettings
} from '$lib/gen'
import {
canWrite,
displayDate,
@@ -15,8 +20,8 @@
import SharedBadge from '$lib/components/SharedBadge.svelte'
import ShareModal from '$lib/components/ShareModal.svelte'
import Toggle from '$lib/components/Toggle.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { Unplug, Code, Eye, Pen, Plus, Share, Trash, Circle } from 'lucide-svelte'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { Unplug, Code, Eye, Pen, Plus, Share, Trash, Circle, FileUp } from 'lucide-svelte'
import { goto } from '$lib/navigation'
import SearchItems from '$lib/components/SearchItems.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
@@ -29,13 +34,26 @@
import Popover from '$lib/components/Popover.svelte'
import { isCloudHosted } from '$lib/cloud'
import WebsocketTriggerEditor from '$lib/components/triggers/websocket/WebsocketTriggerEditor.svelte'
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
import { ALL_DEPLOYABLE, isDeployable } from '$lib/utils_deployable'
type TriggerW = WebsocketTrigger & { canWrite: boolean }
let triggers: TriggerW[] = []
let shareModal: ShareModal
let loading = true
let deploymentDrawer: DeployWorkspaceDrawer
let deployUiSettings: WorkspaceDeployUISettings | undefined = undefined
async function getDeployUiSettings() {
if (!$enterpriseLicense) {
deployUiSettings = ALL_DEPLOYABLE
return
}
let settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
deployUiSettings = settings.deploy_ui ?? ALL_DEPLOYABLE
}
getDeployUiSettings()
async function loadTriggers(): Promise<void> {
triggers = (
await WebsocketTriggerService.listWebsocketTriggers({ workspace: $workspaceStore! })
@@ -198,6 +216,7 @@
$: updateQueryFilters(selectedFilterKind, filterUserFolders)
</script>
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
<WebsocketTriggerEditor on:update={loadTriggers} bind:this={websocketTriggerEditor} />
<SearchItems
@@ -385,6 +404,21 @@
websocketTriggerEditor?.openEdit(path, is_flow)
}
},
...(isDeployable('trigger', path, deployUiSettings)
? [
{
displayName: 'Deploy to prod/staging',
icon: FileUp,
action: () => {
deploymentDrawer.openDrawer(path, 'trigger', {
triggers: {
kind: 'websockets'
}
})
}
}
]
: []),
{
displayName: 'Audit logs',
icon: Eye,
@@ -539,7 +539,8 @@
apps: (settings.deploy_ui.include_type?.indexOf('app') ?? -1) >= 0,
resources: (settings.deploy_ui.include_type?.indexOf('resource') ?? -1) >= 0,
variables: (settings.deploy_ui.include_type?.indexOf('variable') ?? -1) >= 0,
secrets: (settings.deploy_ui.include_type?.indexOf('secret') ?? -1) >= 0
secrets: (settings.deploy_ui.include_type?.indexOf('secret') ?? -1) >= 0,
triggers: (settings.deploy_ui.include_type?.indexOf('trigger') ?? -1) >= 0
}
}
}
@@ -562,6 +563,7 @@
resources: boolean
variables: boolean
secrets: boolean
triggers: boolean
}
}