feat: expose a react sdk to integrate windmill into react apps (#1605)

* expose react sdk

* expose react sdk

* iterate

* iterate

* iterate

* nit

* update example

* update example

* small fixes

* update all
This commit is contained in:
Ruben Fiszel
2023-05-19 20:44:07 +02:00
committed by GitHub
parent 741fc3f1ba
commit 36280cffc8
114 changed files with 11500 additions and 659 deletions
+3 -3
View File
@@ -1,13 +1,13 @@
Source code in this repository is variously licensed under the Apache License
Version 2.0 (see file ./LICENSE-APACHE),or the AGPLv3 License (see file ./LICENSE-AGPL)
Version 2.0 (see file ./LICENSE-APACHE), or the AGPLv3 License (see file ./LICENSE-AGPL)
Every file is under copyright (c) Windmill Labs, Inc 2022 unless otherwise specified.
Every file is under License AGPL unless otherwise specified
or belonging to one of the below cases:
The files under backend/ are AGPL Licensed.
The files under frontend/ are AGPL Licensed.
The files under backend/ are AGPLv3 Licensed.
The files under frontend/ are AGPLv3 Licensed.
The files under python-client/ deno-client/ go-client/ are Apache 2.0 Licensed.
The openapi files, including the OpenFlow spec is Apache 2.0 Licensed.
+18 -10
View File
@@ -74,7 +74,23 @@ pub async fn pip_compile(
) -> error::Result<String> {
logs.push_str(&format!("\nresolving dependencies..."));
set_logs(logs, job_id, db).await;
logs.push_str(&format!("\ncontent of requirements:\n{}", requirements));
logs.push_str(&format!("\ncontent of requirements:\n{}\n", requirements));
let requirements = if let Some(pip_local_dependencies) = PIP_LOCAL_DEPENDENCIES.as_ref() {
let deps = pip_local_dependencies.clone();
requirements
.lines()
.filter(|s| {
if !deps.contains(&s.to_string()) {
return true;
} else {
logs.push_str(&format!("\nignoring local dependency: {}", s));
return false;
}
})
.join("\n")
} else {
requirements.to_string()
};
let req_hash = calculate_hash(&requirements);
if let Some(cached) = sqlx::query_scalar!(
"SELECT lockfile FROM pip_resolution_cache WHERE hash = $1",
@@ -87,15 +103,7 @@ pub async fn pip_compile(
return Ok(cached);
}
let file = "requirements.in";
let requirements = if let Some(pip_local_dependencies) = PIP_LOCAL_DEPENDENCIES.as_ref() {
let deps = pip_local_dependencies.clone();
requirements
.lines()
.filter(|s| !deps.contains(&s.to_string()))
.join("\n")
} else {
requirements.to_string()
};
write_file(job_dir, file, &requirements).await?;
let mut args = vec!["-q", "--no-header", file, "--resolver=backtracking"];
+1 -1
View File
@@ -90,7 +90,7 @@ FROM python:3.10.8-slim-buster
ARG APP=/usr/src/app
RUN apt-get update \
&& apt-get install -y ca-certificates wget curl git jq libprotobuf-dev libnl-route-3-dev unzip build-essential pkg-config libcairo2-dev \
&& apt-get install -y ca-certificates wget curl git jq libprotobuf-dev libnl-route-3-dev unzip build-essential pkg-config libcairo2-dev libwebkit2gtk-4.0-37 \
&& rm -rf /var/lib/apt/lists/*
RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \
+3 -4
View File
@@ -22,6 +22,7 @@
"chartjs-plugin-zoom": "^2.0.0",
"d3-zoom": "^3.0.0",
"date-fns": "^2.29.3",
"esm-env": "^1.0.0",
"fast-equals": "^5.0.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
@@ -2940,8 +2941,7 @@
"node_modules/esm-env": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==",
"dev": true
"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
},
"node_modules/esm-env-robust": {
"version": "0.0.3",
@@ -9452,8 +9452,7 @@
"esm-env": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz",
"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==",
"dev": true
"integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA=="
},
"esm-env-robust": {
"version": "0.0.3",
+25 -1
View File
@@ -82,6 +82,7 @@
"chartjs-plugin-zoom": "^2.0.0",
"d3-zoom": "^3.0.0",
"date-fns": "^2.29.3",
"esm-env": "^1.0.0",
"fast-equals": "^5.0.1",
"highlight.js": "^11.8.0",
"lodash": "^4.17.21",
@@ -157,6 +158,16 @@
"svelte": "./package/components/FlowViewer.svelte",
"default": "./package/components/FlowViewer.svelte"
},
"./components/FlowBuilder.svelte": {
"types": "./package/components/FlowBuilder.svelte.d.ts",
"svelte": "./package/components/FlowBuilder.svelte",
"default": "./package/components/FlowBuilder.svelte"
},
"./components/FlowEditor.svelte": {
"types": "./package/components/flows/FlowEditor.svelte.d.ts",
"svelte": "./package/components/flows/FlowEditor.svelte",
"default": "./package/components/flows/FlowEditor.svelte"
},
"./components/SchemaViewer.svelte": {
"types": "./package/components/SchemaViewer.svelte.d.ts",
"svelte": "./package/components/SchemaViewer.svelte",
@@ -171,6 +182,10 @@
"types": "./package/common.d.ts",
"default": "./package/common.js"
},
"./stores": {
"types": "./package/stores.d.ts",
"default": "./package/stores.js"
},
"./components/icons": {
"types": "./package/components/icons/index.d.ts",
"svelte": "./package/components/icons/index.js",
@@ -193,7 +208,7 @@
"dist",
"package"
],
"license": "Apache-2.0",
"license": "AGPL-3.0",
"svelte": "./dist/index.js",
"typesVersions": {
">4.0": {
@@ -227,6 +242,12 @@
"components/FlowViewer.svelte": [
"./package/components/FlowViewer.svelte.d.ts"
],
"components/FlowBuilder.svelte": [
"./package/components/FlowBuilder.svelte.d.ts"
],
"components/FlowEditor.svelte": [
"./package/components/flows/FlowEditor.svelte.d.ts"
],
"components/SchemaViewer.svelte": [
"./package/components/SchemaViewer.svelte.d.ts"
],
@@ -236,6 +257,9 @@
"common": [
"./package/common.d.ts"
],
"stores": [
"./package/stores.d.ts"
],
"components/icons": [
"./package/components/icons/index.d.ts"
],
+43
View File
@@ -0,0 +1,43 @@
import { BROWSER } from 'esm-env'
import { page } from '$app/stores'
import { get } from 'svelte/store'
import { premiumStore, userStore, workspaceStore } from './stores'
import { getUserExt } from './user'
import { WorkspaceService } from './gen'
export function isCloudHosted(): boolean {
return get(page)?.url?.hostname == 'app.windmill.dev'
}
if (BROWSER) {
workspaceStore.subscribe(async (workspace) => {
if (workspace) {
try {
localStorage.setItem('workspace', String(workspace))
} catch (e) {
console.error('Could not persist workspace to local storage', e)
}
const user = await getUserExt(workspace)
userStore.set(user)
if (isCloudHosted() && user?.is_admin) {
premiumStore.set(await WorkspaceService.getPremiumInfo({ workspace }))
}
} else {
userStore.set(undefined)
}
})
setInterval(async () => {
try {
const workspace = get(workspaceStore)
const user = get(userStore)
if (workspace && user && !user.is_super_admin && !user.is_admin) {
userStore.set(await getUserExt(workspace))
console.log('refreshed user')
}
} catch (e) {
console.error('Could not refresh user', e)
}
}, 30000)
}
+1 -1
View File
@@ -1,10 +1,10 @@
<script lang="ts">
import { sendUserToast } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import { workspaceStore } from '$lib/stores'
import { WorkspaceService } from '$lib/gen'
import { Button, ToggleButton, ToggleButtonGroup } from './common'
import Tooltip from './Tooltip.svelte'
import { sendUserToast } from '$lib/toast'
const dispatch = createEventDispatcher()
@@ -1,12 +1,13 @@
<script lang="ts">
import { JobService, Preview, ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { emptySchema, emptyString, sendUserToast } from '$lib/utils'
import { emptySchema, emptyString } from '$lib/utils'
import { Loader2 } from 'lucide-svelte'
import Button from './common/button/Button.svelte'
import SchemaForm from './SchemaForm.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import Toggle from './Toggle.svelte'
import { sendUserToast } from '$lib/toast'
export let resource_type: string
export let args: Record<string, any> | any = {}
@@ -57,8 +57,7 @@
import { faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import IconedResourceType from './IconedResourceType.svelte'
import { OauthService, ResourceService, VariableService, type TokenResponse } from '$lib/gen'
import { page } from '$app/stores'
import { emptyString, sendUserToast, truncateRev } from '$lib/utils'
import { emptyString, truncateRev } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import Path from './Path.svelte'
@@ -68,6 +67,7 @@
import SearchItems from './SearchItems.svelte'
import autosize from 'svelte-autosize'
import WhitelistIp from './WhitelistIp.svelte'
import { sendUserToast } from '$lib/toast'
export let newPageOAuth = false
@@ -163,7 +163,7 @@
step += 1
args = {}
} else if (step == 1 && !manual) {
const url = new URL(`/api/oauth/connect/${resource_type}`, $page.url.origin)
const url = new URL(`/api/oauth/connect/${resource_type}`, window.location.origin)
url.searchParams.append('scopes', scopes.join('+'))
if (extra_params.length > 0) {
extra_params.forEach(([key, value]) => url.searchParams.append(key, value))
@@ -4,7 +4,6 @@
import Icon from 'svelte-awesome'
import { MoreVertical } from 'lucide-svelte'
import { Button, Menu } from './common'
import { goto } from '$app/navigation'
import { twMerge } from 'tailwind-merge'
type Alignment = 'start' | 'end'
@@ -79,7 +78,6 @@
{:else if item.href && !item.disabled}
<a
href={item.href}
on:click|stopPropagation|preventDefault={() => goto(item.href ?? '')}
class="block w-full px-4 font-semibold text-left py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30
{item.disabled ? 'bg-gray-200' : ''}"
role="menuitem"
+18 -19
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { browser } from '$app/environment'
import { page } from '$app/stores'
import { sendUserToast } from '$lib/utils'
import { BROWSER } from 'esm-env'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
@@ -339,7 +339,8 @@
}
}
const wsProtocol = $page.url.protocol == 'https:' ? 'wss' : 'ws'
const wsProtocol = BROWSER && window.location.protocol == 'https:' ? 'wss' : 'ws'
const hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
let encodedImportMap = ''
if (lang == 'typescript') {
@@ -349,14 +350,7 @@
const token = await UserService.createToken({
requestBody: { label: 'Ephemeral lsp token', expiration: expiration.toISOString() }
})
let root =
$page.url.protocol +
'//' +
$page.url.host +
'/api/scripts_u/tokened_raw/' +
$workspaceStore +
'/' +
token
let root = hostname + '/api/scripts_u/tokened_raw/' + $workspaceStore + '/' + token
const importMap = {
imports: {
'file:///': root + '/'
@@ -375,7 +369,7 @@
encodedImportMap = 'data:text/plain;base64,' + btoa(JSON.stringify(importMap))
}
await connectToLanguageServer(
`${wsProtocol}://${$page.url.host}/ws/deno`,
`${wsProtocol}://${window.location.host}/ws/deno`,
'deno',
{
certificateStores: null,
@@ -417,7 +411,7 @@
)
} else if (lang === 'python') {
await connectToLanguageServer(
`${wsProtocol}://${$page.url.host}/ws/pyright`,
`${wsProtocol}://${window.location.host}/ws/pyright`,
'pyright',
{},
(params, token, next) => {
@@ -447,9 +441,14 @@
}
)
connectToLanguageServer(`${wsProtocol}://${$page.url.host}/ws/ruff`, 'ruff', {}, undefined)
connectToLanguageServer(
`${wsProtocol}://${$page.url.host}/ws/diagnostic`,
`${wsProtocol}://${window.location.host}/ws/ruff`,
'ruff',
{},
undefined
)
connectToLanguageServer(
`${wsProtocol}://${window.location.host}/ws/diagnostic`,
'black',
{
formatters: {
@@ -466,7 +465,7 @@
)
} else if (lang === 'go') {
connectToLanguageServer(
`${wsProtocol}://${$page.url.host}/ws/go`,
`${wsProtocol}://${window.location.host}/ws/go`,
'go',
{
'build.allowImplicitNetworkAccess': true
@@ -475,7 +474,7 @@
)
} else if (lang === 'shell') {
connectToLanguageServer(
`${wsProtocol}://${$page.url.host}/ws/diagnostic`,
`${wsProtocol}://${window.location.host}/ws/diagnostic`,
'shellcheck',
{
linters: {
@@ -658,7 +657,7 @@
}
onMount(() => {
if (browser) {
if (BROWSER) {
loadMonaco().then((x) => (disposeMethod = x))
}
})
+2 -1
View File
@@ -4,7 +4,6 @@
<script lang="ts">
import { ResourceService, VariableService } from '$lib/gen'
import { getScriptByPath, sendUserToast } from '$lib/utils'
import {
faCube,
@@ -30,6 +29,8 @@
import Skeleton from './common/skeleton/Skeleton.svelte'
import Popover from './Popover.svelte'
import { SCRIPT_EDITOR_SHOW_EXPLORE_OTHER_SCRIPTS } from '$lib/consts'
import { sendUserToast } from '$lib/toast'
import { getScriptByPath } from '$lib/scripts'
export let lang: 'python3' | 'deno' | 'go' | 'bash'
export let editor: Editor | undefined
+14 -9
View File
@@ -1,9 +1,10 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { FlowService, ScheduleService, type Flow, type FlowModule, DraftService } from '$lib/gen'
import { initHistory, redo, undo } from '$lib/history'
import { userStore, workspaceStore } from '$lib/stores'
import { encodeState, formatCron, loadHubScripts, sendUserToast } from '$lib/utils'
import { encodeState, formatCron } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { faCalendarAlt, faSave } from '@fortawesome/free-solid-svg-icons'
import { setContext } from 'svelte'
import { writable, type Writable } from 'svelte/store'
@@ -20,7 +21,8 @@
import type { FlowEditorContext } from './flows/types'
import { cleanInputs } from './flows/utils'
import { Pen } from 'lucide-svelte'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { loadHubScripts } from '$lib/scripts'
import { createEventDispatcher } from 'svelte'
export let initialPath: string = ''
export let selectedId: string | undefined
@@ -29,6 +31,8 @@
export let flowStore: Writable<Flow>
export let flowStateStore: Writable<FlowState>
const dispatch = createEventDispatcher()
async function createSchedule(path: string) {
const { cron, timezone, args, enabled } = $scheduleStore
@@ -79,7 +83,7 @@
})
if (initialPath == '') {
$dirtyStore = false
goto(`/flows/edit/${flow.path}`)
dispatch('saveInitial')
}
sendUserToast('Saved as draft')
} catch (error) {
@@ -155,8 +159,7 @@
}
loadingSave = false
$dirtyStore = false
window.history.replaceState(window.history.state, '', `/flows/edit/${flow.path}`)
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
dispatch('deploy')
} catch (err) {
sendUserToast(`The flow could not be saved: ${err.body}`, true)
loadingSave = false
@@ -190,6 +193,10 @@
const selectedIdStore = writable<string>(selectedId ?? 'settings-metadata')
export function getSelectedId() {
return $selectedIdStore
}
const scheduleStore = writable<Schedule>({
args: {},
cron: '',
@@ -306,7 +313,7 @@
}> = [
{
label: 'Exit & see details',
onClick: () => goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
onClick: () => dispatch('details')
}
]
@@ -320,8 +327,6 @@
<svelte:window on:keydown={onKeyDown} />
<UnsavedConfirmationModal />
{#if !$userStore?.operator}
<ScriptEditorDrawer bind:this={$scriptEditorDrawer} />
@@ -3,7 +3,6 @@
import HighlightCode from './HighlightCode.svelte'
import InputTransformsViewer from './InputTransformsViewer.svelte'
import IconedPath from './IconedPath.svelte'
import { scriptPathToHref } from '../utils'
import type { FlowModule, FlowValue } from '$lib/gen'
import { Badge, Button, Drawer, DrawerContent } from './common'
import { Highlight } from 'svelte-highlight'
@@ -12,6 +11,7 @@
import { cleanExpr } from './flows/utils'
import FlowPathViewer from './flows/content/FlowPathViewer.svelte'
import SchemaViewer from './SchemaViewer.svelte'
import { scriptPathToHref } from '$lib/scripts'
export let flow: {
summary: string
description?: string
@@ -1,7 +1,6 @@
<script lang="ts">
import { GranularAclService, GroupService, UserService, type Group } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { canWrite, sendUserToast } from '$lib/utils'
import AutoComplete from 'simple-svelte-autocomplete'
import { createEventDispatcher } from 'svelte'
import autosize from 'svelte-autosize'
@@ -9,6 +8,8 @@
import Skeleton from './common/skeleton/Skeleton.svelte'
import TableCustom from './TableCustom.svelte'
import Tooltip from './Tooltip.svelte'
import { sendUserToast } from '$lib/toast'
import { canWrite } from '$lib/utils'
export let name: string
let can_write = false
@@ -1,5 +1,5 @@
<script lang="ts">
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher } from 'svelte'
import { UserService } from '$lib/gen'
import { Button } from './common'
@@ -1,5 +1,5 @@
<script lang="ts">
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher } from 'svelte'
import { workspaceStore } from '$lib/stores'
import { WorkspaceService } from '$lib/gen'
@@ -2,7 +2,9 @@
import type { Schema } from '$lib/common'
import { ScriptService, type FlowModule, type Job } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { getModifierKey, getScriptByPath } from '$lib/utils'
import { getModifierKey } from '$lib/utils'
import { getScriptByPath } from '$lib/scripts'
import { Loader2 } from 'lucide-svelte'
import { getContext } from 'svelte'
import { Pane, Splitpanes } from 'svelte-splitpanes'
@@ -1,11 +1,11 @@
<script lang="ts">
import { isOwner } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { Alert, Button, Drawer } from './common'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import Path from './Path.svelte'
import { AppService, FlowService, RawAppService, ScriptService } from '$lib/gen'
import { isOwner } from '$lib/utils'
const dispatch = createEventDispatcher()
@@ -1,7 +1,7 @@
<script lang="ts">
import type { Schema } from '$lib/common'
import { ResourceService, type Resource } from '$lib/gen'
import { canWrite, emptyString, isOwner, sendUserToast } from '$lib/utils'
import { canWrite, emptyString, isOwner } from '$lib/utils'
import { createEventDispatcher } from 'svelte'
import { Alert, Button, Drawer, Skeleton } from './common'
import Path from './Path.svelte'
@@ -14,6 +14,7 @@
import SchemaForm from './SchemaForm.svelte'
import SimpleEditor from './SimpleEditor.svelte'
import Toggle from './Toggle.svelte'
import { sendUserToast } from '$lib/toast'
let path = ''
let initialPath = ''
@@ -5,13 +5,7 @@
import { inferArgs } from '$lib/infer'
import { initialCode } from '$lib/script_helpers'
import { userStore, workerTags, workspaceStore } from '$lib/stores'
import {
emptySchema,
encodeState,
getModifierKey,
sendUserToast,
setQueryWithoutLoad
} from '$lib/utils'
import { emptySchema, encodeState, getModifierKey, setQueryWithoutLoad } from '$lib/utils'
import Path from './Path.svelte'
import ScriptEditor from './ScriptEditor.svelte'
import ScriptSchema from './ScriptSchema.svelte'
@@ -32,6 +26,7 @@
SCRIPT_CUSTOMISE_SHOW_KIND
} from '$lib/consts'
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
import { sendUserToast } from '$lib/toast'
export let script: NewScript
export let initialPath: string = ''
@@ -2,7 +2,7 @@
import type { Schema } from '$lib/common'
import { CompletedJob, Job, JobService } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { emptySchema, getModifierKey, scriptLangToEditorLang } from '$lib/utils'
import { emptySchema, getModifierKey } from '$lib/utils'
import { faPlay } from '@fortawesome/free-solid-svg-icons'
import Editor from './Editor.svelte'
import { inferArgs } from '$lib/infer'
@@ -17,6 +17,7 @@
import { Button, Kbd } from './common'
import SplitPanesWrapper from './splitPanes/SplitPanesWrapper.svelte'
import WindmillIcon from './icons/WindmillIcon.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
// Exported
export let schema: Schema = emptySchema()
@@ -51,11 +52,8 @@
let testIsLoading = false
let testJob: Job | undefined
let pastPreviews: CompletedJob[] = []
let lastSave: string | null
let validCode = true
$: lastSave = localStorage.getItem(path ?? 'last_save')
function onKeyDown(event: KeyboardEvent) {
if ((event.ctrlKey || event.metaKey) && event.key == 'Enter') {
event.preventDefault()
@@ -177,7 +175,6 @@
} catch (e) {
console.error('Could not save last_save to local storage', e)
}
lastSave = code
dispatch('format')
}}
class="flex flex-1 h-full !overflow-visible"
@@ -231,14 +228,7 @@
</div>
</Pane>
<Pane size={67}>
<LogPanel
{path}
{lang}
previewJob={testJob}
{pastPreviews}
previewIsLoading={testIsLoading}
bind:lastSave
/>
<LogPanel {lang} previewJob={testJob} {pastPreviews} previewIsLoading={testIsLoading} />
</Pane>
</Splitpanes>
</div>
@@ -6,7 +6,7 @@
import Select from 'svelte-select'
import { getScriptByPath } from '$lib/utils'
import { getScriptByPath } from '$lib/scripts'
import RadioButton from './RadioButton.svelte'
import { Button, Drawer, DrawerContent } from './common'
import HighlightCode from './HighlightCode.svelte'
@@ -2,7 +2,6 @@
import TableCustom from './TableCustom.svelte'
import { GranularAclService } from '$lib/gen/services/GranularAclService'
import { isOwner, sendUserToast } from '$lib/utils'
import { GroupService, UserService } from '$lib/gen'
import { createEventDispatcher } from 'svelte'
import AutoComplete from 'simple-svelte-autocomplete'
@@ -10,6 +9,8 @@
import { Alert, Button, Drawer, ToggleButton, ToggleButtonGroup } from './common'
import DrawerContent from './common/drawer/DrawerContent.svelte'
import Tooltip from './Tooltip.svelte'
import { sendUserToast } from '$lib/toast'
import { isOwner } from '$lib/utils'
const dispatch = createEventDispatcher()
@@ -1,5 +1,5 @@
<script lang="ts">
import { browser } from '$app/environment'
import { BROWSER } from 'esm-env'
import { createHash, editorConfig, langToExt, updateOptions } from '$lib/editorUtils'
import 'monaco-editor/esm/vs/editor/edcore.main'
@@ -181,7 +181,7 @@
let mounted = false
onMount(async () => {
if (browser) {
if (BROWSER) {
mounted = true
await loadMonaco()
}
+1 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { FavoriteService } from '$lib/gen'
import { starStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher } from 'svelte'
import { Star, StarOff } from 'lucide-svelte'
@@ -4,7 +4,7 @@
import PageHeader from '$lib/components/PageHeader.svelte'
import InviteGlobalUser from '$lib/components/InviteGlobalUser.svelte'
import { Badge, Drawer, DrawerContent } from '$lib/components/common'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import SearchItems from './SearchItems.svelte'
import { page } from '$app/stores'
import { goto } from '$app/navigation'
@@ -1,5 +1,5 @@
<script lang="ts">
import { browser } from '$app/environment'
import { BROWSER } from 'esm-env'
import {
convertKind,
createDocumentationString,
@@ -578,7 +578,7 @@
let mounted = false
onMount(async () => {
if (browser) {
if (BROWSER) {
await loadMonaco()
mounted = true
}
+1 -1
View File
@@ -1,9 +1,9 @@
<script lang="ts">
import type { ToastAction } from '$lib/utils'
import { toast } from '@zerodevx/svelte-toast'
import { CheckCircle2, XCircleIcon } from 'lucide-svelte'
import { onMount } from 'svelte'
import Button from './common/button/Button.svelte'
import type { ToastAction } from '$lib/toast'
export let message: string
export let toastId: string
@@ -2,7 +2,7 @@
import { usersWorkspaceStore } from '$lib/stores'
import type { TruncatedToken, NewToken } from '$lib/gen'
import { UserService, SettingsService } from '$lib/gen'
import { displayDate, sendUserToast, copyToClipboard } from '$lib/utils'
import { displayDate, copyToClipboard } from '$lib/utils'
import { faClipboard, faPlus } from '@fortawesome/free-solid-svg-icons'
import TableCustom from '$lib/components/TableCustom.svelte'
import { Button } from '$lib/components/common'
@@ -22,6 +22,7 @@
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import { page } from '$app/stores'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/toast'
let drawer: Drawer
@@ -1,5 +1,4 @@
<script lang="ts">
import { canWrite, isOwner, sendUserToast } from '$lib/utils'
import { VariableService } from '$lib/gen'
import Path from './Path.svelte'
import { createEventDispatcher } from 'svelte'
@@ -14,6 +13,8 @@
import Toggle from './Toggle.svelte'
import { faSave } from '@fortawesome/free-solid-svg-icons'
import SimpleEditor from './SimpleEditor.svelte'
import { sendUserToast } from '$lib/toast'
import { canWrite, isOwner } from '$lib/utils'
const dispatch = createEventDispatcher()
@@ -73,6 +73,7 @@
color={resolvedConfig.color}
download={resolvedConfig.filename}
href={transformBareBase64IfNecessary(resolvedConfig.source)}
target="_self"
nonCaptureEvent
>
<span class="truncate inline-flex gap-2 items-center">
@@ -6,8 +6,7 @@
import { isScriptByNameDefined, isScriptByPathDefined } from '../../utils'
import NonRunnableComponent from './NonRunnableComponent.svelte'
import RunnableComponent from './RunnableComponent.svelte'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import InitializeComponent from './InitializeComponent.svelte'
export let componentInput: AppInput | undefined
@@ -108,7 +107,7 @@
if (sideEffect.configuration.gotoUrl.newTab) {
window.open(sideEffect.configuration.gotoUrl.url, '_blank')
} else {
goto(sideEffect.configuration.gotoUrl.url)
window.location.href = sideEffect.configuration.gotoUrl.url
}
} else if (
sideEffect.selected == 'sendToast' &&
@@ -1,6 +1,6 @@
import { sendUserToast } from '$lib/utils'
import { isPlainObject } from 'lodash'
import type { World } from '../../rx'
import { sendUserToast } from '$lib/toast'
export function computeGlobalContext(world: World | undefined, extraContext: any = {}) {
return {
@@ -29,7 +29,7 @@
import { getContext } from 'svelte'
import { Icon } from 'svelte-awesome'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { classNames, copyToClipboard, sendUserToast } from '../../../utils'
import { classNames, copyToClipboard } from '../../../utils'
import type {
AppInput,
ConnectedAppInput,
@@ -51,6 +51,7 @@
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { Sha256 } from '@aws-crypto/sha256-js'
import { sendUserToast } from '$lib/toast'
async function hash(message) {
try {
@@ -21,7 +21,8 @@ import gridHelp from '../svelte-grid/utils/helper'
import type { FilledItem } from '../svelte-grid/types'
import type { EvalAppInput, StaticAppInput } from '../inputType'
import { get, type Writable } from 'svelte/store'
import { deepMergeWithPriority, sendUserToast } from '$lib/utils'
import { deepMergeWithPriority } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { getNextId } from '$lib/components/flows/idUtils'
export function dfs(
@@ -8,7 +8,7 @@
} from '../appUtils'
import type { AppEditorContext, AppViewerContext, FocusedGrid, GridItem } from '../../types'
import { push } from '$lib/history'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { gridColumns } from '../../gridUtils'
const { app, selectedComponent, focusedGrid, componentControl } =
@@ -7,7 +7,9 @@
import { Script, type Preview } from '$lib/gen'
import { inferArgs } from '$lib/infer'
import { initialCode } from '$lib/script_helpers'
import { capitalize, emptySchema, getScriptByPath } from '$lib/utils'
import { capitalize, emptySchema } from '$lib/utils'
import { getScriptByPath } from '$lib/scripts'
import { faCodeBranch } from '@fortawesome/free-solid-svg-icons'
import { Building, Globe2 } from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
@@ -9,7 +9,7 @@
import type { Schema } from '$lib/common'
import Badge from '$lib/components/common/badge/Badge.svelte'
import Editor from '$lib/components/Editor.svelte'
import { emptySchema, getModifierKey, scriptLangToEditorLang } from '$lib/utils'
import { emptySchema, getModifierKey } from '$lib/utils'
import { computeFields } from './utils'
import { deepEqual } from 'fast-equals'
import type { AppInput } from '../../inputType'
@@ -17,6 +17,7 @@
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
import { buildExtraLib } from '../../utils'
import RunButton from './RunButton.svelte'
import { scriptLangToEditorLang } from '$lib/scripts'
let inlineScriptEditorDrawer: InlineScriptEditorDrawer
@@ -2,7 +2,7 @@
import { Button, Drawer, DrawerContent } from '$lib/components/common'
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
import FlowPathViewer from '$lib/components/flows/content/FlowPathViewer.svelte'
import { emptySchema, getScriptByPath, sendUserToast } from '$lib/utils'
import { emptySchema } from '$lib/utils'
import { getContext } from 'svelte'
import type {
ConnectedAppInput,
@@ -27,6 +27,8 @@
import { loadSchema } from '../../utils'
import { inferArgs } from '$lib/infer'
import RunButton from './RunButton.svelte'
import { getScriptByPath } from '$lib/scripts'
import { sendUserToast } from '$lib/toast'
export let runnable: RunnableByPath
export let fields: Record<string, StaticAppInput | ConnectedAppInput | RowAppInput | UserAppInput>
@@ -1,6 +1,6 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { Copy } from 'lucide-svelte'
import { getContext } from 'svelte'
import type { AppViewerContext } from '../../types'
@@ -2,7 +2,6 @@
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { ButtonType } from './model'
import { goto } from '$app/navigation'
import { Loader2 } from 'lucide-svelte'
import { twMerge } from 'tailwind-merge'
import ButtonDropdown from './ButtonDropdown.svelte'
@@ -18,14 +17,13 @@
export let wrapperStyle: string = ''
export let disabled: boolean = false
export let href: string | undefined = undefined
export let target: ButtonType.Target = '_self'
export let target: '_self' | '_blank' | undefined = undefined
export let iconOnly: boolean = false
export let startIcon: ButtonType.Icon | undefined = undefined
export let endIcon: ButtonType.Icon | undefined = undefined
export let element: ButtonType.Element | undefined = undefined
export let id: string = ''
export let nonCaptureEvent: boolean = false
export let buttonType: 'button' | 'submit' | 'reset' = 'button'
export let loading = false
export let title: string | undefined = undefined
export let style: string = ''
@@ -97,33 +95,11 @@
}
}
$: buttonProps = {
id,
href,
target,
tabindex: disabled ? -1 : 0,
type: buttonType,
title,
...$$restProps
}
async function onClick(event: MouseEvent) {
if (!nonCaptureEvent) {
event.preventDefault()
event.stopPropagation()
dispatch('click', event)
if (href) {
if (href.startsWith('data')) {
return
}
if (href.startsWith('http') || target == '_blank') {
window.open(href, target)
} else {
loading = true
await goto(href)
loading = false
}
}
}
}
@@ -149,33 +125,75 @@
class="{dropdownItems ? colorVariants[color].divider : ''} {wrapperClasses} flex flex-row"
style={wrapperStyle}
>
<svelte:element
this={href ? 'a' : 'button'}
bind:this={element}
on:pointerdown
on:click={onClick}
on:focus
on:blur
{download}
class={twMerge(buttonClass, disabled ? '!bg-gray-300 !text-gray-600 !cursor-not-allowed' : '')}
{...buttonProps}
disabled={disabled || loading}
type="submit"
{style}
>
{#if loading}
<Loader2 class="animate-spin mr-1" size={14} />
{:else if startIcon}
<Icon data={startIcon.icon} class={startIconClass} scale={ButtonType.IconScale[size]} />
{/if}
{#if href}
<a
data-sveltekit-preload-code="hover"
bind:this={element}
on:pointerdown
on:focus
on:blur
on:click={() => {
loading = true
dispatch('click', event)
loading = false
}}
{href}
{download}
class={twMerge(
buttonClass,
disabled ? '!bg-gray-300 !text-gray-600 !cursor-not-allowed' : ''
)}
{id}
{target}
tabindex={disabled ? -1 : 0}
{...$$restProps}
{style}
>
{#if loading}
<Loader2 class="animate-spin mr-1" size={14} />
{:else if startIcon}
<Icon data={startIcon.icon} class={startIconClass} scale={ButtonType.IconScale[size]} />
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon data={endIcon.icon} class={endIconClass} scale={ButtonType.IconScale[size]} />
{/if}
</svelte:element>
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon data={endIcon.icon} class={endIconClass} scale={ButtonType.IconScale[size]} />
{/if}
</a>
{:else}
<button
bind:this={element}
on:pointerdown
on:click={onClick}
on:focus
on:blur
class={twMerge(
buttonClass,
disabled ? '!bg-gray-300 !text-gray-600 !cursor-not-allowed' : ''
)}
{id}
tabindex={disabled ? -1 : 0}
{title}
{...$$restProps}
disabled={disabled || loading}
{style}
>
{#if loading}
<Loader2 class="animate-spin mr-1" size={14} />
{:else if startIcon}
<Icon data={startIcon.icon} class={startIconClass} scale={ButtonType.IconScale[size]} />
{/if}
{#if !iconOnly}
<slot />
{/if}
{#if endIcon}
<Icon data={endIcon.icon} class={endIconClass} scale={ButtonType.IconScale[size]} />
{/if}
</button>
{/if}
{#if dropdownItems}
<div class={twMerge(buttonClass, 'rounded-r-md rounded-l-none m-0 p-0 h-auto')}>
@@ -1,7 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { browser } from '$app/environment'
import { BROWSER } from 'esm-env'
export let open = false
export let duration = 0.3
@@ -35,7 +35,7 @@
$: style = `--duration: ${duration}s; --size: ${size};`
function scrollLock(open: boolean) {
if (browser) {
if (BROWSER) {
const body = document.querySelector('body')
if (mounted && body) {
@@ -7,7 +7,6 @@
import type ShareModal from '$lib/components/ShareModal.svelte'
import { FlowService, type Flow } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { isOwner, sendUserToast } from '$lib/utils'
import {
faArchive,
faCalendarAlt,
@@ -25,6 +24,8 @@
import Button from '../button/Button.svelte'
import Row from './Row.svelte'
import DraftBadge from '$lib/components/DraftBadge.svelte'
import { sendUserToast } from '$lib/toast'
import { isOwner } from '$lib/utils'
export let flow: Flow & { has_draft?: boolean; draft_only?: boolean; canWrite: boolean }
export let marked: string | undefined
@@ -8,7 +8,6 @@
import { ScriptService, type Script } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { isOwner, sendUserToast } from '$lib/utils'
import {
faArchive,
faCalendarAlt,
@@ -27,6 +26,8 @@
import LanguageBadge from './LanguageBadge.svelte'
import Row from './Row.svelte'
import DraftBadge from '$lib/components/DraftBadge.svelte'
import { sendUserToast } from '$lib/toast'
import { isOwner } from '$lib/utils'
export let script: Script & { canWrite: boolean }
export let marked: string | undefined
@@ -1,5 +1,5 @@
<script lang="ts">
import { page } from '$app/stores'
import { BROWSER } from 'esm-env'
import { Button } from '$lib/components/common'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
@@ -44,6 +44,8 @@
captureInput = capture
jsonSchema = { required: [], properties: {}, ...convert(capture) }
}
let hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
</script>
<Drawer
@@ -63,14 +65,10 @@
class="text-2xl"
on:click={(e) => {
e.preventDefault()
copyToClipboard(
`${$page.url.protocol}//${$page.url.hostname}/api/w/${$workspaceStore}/capture_u/${$flowStore.path}`
)
copyToClipboard(`${hostname}/api/w/${$workspaceStore}/capture_u/${$flowStore.path}`)
}}
href="{$page.url.protocol}//{$page.url
.hostname}/api/w/{$workspaceStore}/capture_u/{$flowStore.path}"
>{$page.url.protocol}//{$page.url
.hostname}/api/w/{$workspaceStore}/capture_u/{$flowStore.path}
href="{hostname}/api/w/{$workspaceStore}/capture_u/{$flowStore.path}"
>{hostname}/api/w/{$workspaceStore}/capture_u/{$flowStore.path}
<Icon data={faClipboard} /></a
>
</div>
@@ -78,7 +76,7 @@
<div class="text-xs box mb-4 b">
<pre class="overflow-auto"
>{`curl -X POST ${$page.url.protocol}//${$page.url.hostname}/api/w/${$workspaceStore}/capture_u/${$flowStore.path} \\
>{`curl -X POST ${hostname}/api/w/${$workspaceStore}/capture_u/${$flowStore.path} \\
-H 'Content-Type: application/json' \\
-d '{"foo": 42}'`}</pre
>
@@ -10,7 +10,7 @@
import { RawScript, type FlowModule, type PathFlow, type PathScript } from '$lib/gen'
import FlowCard from '../common/FlowCard.svelte'
import FlowModuleHeader from './FlowModuleHeader.svelte'
import { getLatestHashForScript, schemaToObject, scriptLangToEditorLang } from '$lib/utils'
import { getLatestHashForScript, scriptLangToEditorLang } from '$lib/scripts'
import PropPickerWrapper from '../propPicker/PropPickerWrapper.svelte'
import { afterUpdate, getContext } from 'svelte'
import type { FlowEditorContext } from '../types'
@@ -27,6 +27,7 @@
import FlowModuleSleep from './FlowModuleSleep.svelte'
import FlowPathViewer from './FlowPathViewer.svelte'
import InputTransformSchemaForm from '$lib/components/InputTransformSchemaForm.svelte'
import { schemaToObject } from '$lib/schema'
const { selectedId, previewArgs, flowStateStore, flowStore, saveDraft } =
getContext<FlowEditorContext>('FlowEditorContext')
@@ -6,8 +6,9 @@
import { Bed, PhoneIncoming, Repeat, Square } from 'lucide-svelte'
import Popover from '../../Popover.svelte'
import type { FlowEditorContext } from '../types'
import { getLatestHashForScript, sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/utils'
import { workerTags } from '$lib/stores'
import { getLatestHashForScript } from '$lib/scripts'
export let module: FlowModule
const { scriptEditorDrawer } = getContext<FlowEditorContext>('FlowEditorContext')
@@ -1,8 +1,8 @@
<script lang="ts">
import HighlightCode from '$lib/components/HighlightCode.svelte'
import { ScriptService } from '$lib/gen'
import { getScriptByPath } from '$lib/scripts'
import { workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/utils'
export let path: string
export let hash: string | undefined = undefined
@@ -3,6 +3,7 @@
import Tab from '$lib/components/common/tabs/Tab.svelte'
import TabContent from '$lib/components/common/tabs/TabContent.svelte'
import { BROWSER } from 'esm-env'
import Path from '$lib/components/Path.svelte'
import Required from '$lib/components/Required.svelte'
import FlowCard from '../common/FlowCard.svelte'
@@ -13,7 +14,6 @@
import type { FlowEditorContext } from '../types'
import autosize from 'svelte-autosize'
import Slider from '$lib/components/Slider.svelte'
import { page } from '$app/stores'
import { workspaceStore } from '$lib/stores'
import { copyToClipboard } from '$lib/utils'
import { Icon } from 'svelte-awesome'
@@ -22,8 +22,9 @@
const { selectedId, flowStore, initialPath } = getContext<FlowEditorContext>('FlowEditorContext')
$: url = `${$page.url.hostname}/api/w/${$workspaceStore}/jobs/run/f/${$flowStore?.path}`
$: syncedUrl = `${$page.url.hostname}/api/w/${$workspaceStore}/jobs/run_wait_result/f/${$flowStore?.path}`
let hostname = BROWSER ? window.location.protocol + '//' + window.location.host : 'SSR'
$: url = `${hostname}/api/w/${$workspaceStore}/jobs/run/f/${$flowStore?.path}`
$: syncedUrl = `${hostname}/api/w/${$workspaceStore}/jobs/run_wait_result/f/${$flowStore?.path}`
</script>
<div class="h-full overflow-hidden">
@@ -107,7 +108,7 @@
e.preventDefault()
copyToClipboard(url)
}}
href={$page.url.protocol + '//' + url}
href={url}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{url}
@@ -123,7 +124,7 @@
e.preventDefault()
copyToClipboard(syncedUrl)
}}
href={$page.url.protocol + '//' + syncedUrl}
href={syncedUrl}
class="whitespace-nowrap text-ellipsis overflow-hidden mr-1"
>
{syncedUrl}
@@ -10,7 +10,7 @@ import {
} from '$lib/gen'
import { initialCode } from '$lib/script_helpers'
import { userStore, workspaceStore } from '$lib/stores'
import { getScriptByPath } from '$lib/utils'
import { getScriptByPath } from '$lib/scripts'
import { get, type Writable } from 'svelte/store'
import type { FlowModuleState, FlowState } from './flowState'
import { charsToNumber, numberToChars } from './idUtils'
@@ -2,10 +2,10 @@
import { createEventDispatcher, onMount } from 'svelte'
import { Badge, Skeleton } from '$lib/components/common'
import SearchItems from '$lib/components/SearchItems.svelte'
import { loadHubApps } from '$lib/utils'
import ListFilters from '$lib/components/home/ListFilters.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
import { loadHubApps } from '$lib/hub'
export let filter = ''
@@ -53,7 +53,7 @@
<div class="flex items-center gap-4">
<RowIcon kind="app" />
<div class="w-full text-left font-normal ">
<div class="w-full text-left font-normal">
<div class="text-gray-900 flex-wrap text-md font-semibold mb-1">
{#if item.marked}
{@html item.marked ?? ''}
@@ -2,10 +2,10 @@
import { createEventDispatcher, onMount } from 'svelte'
import { Badge, Skeleton } from '$lib/components/common'
import SearchItems from '$lib/components/SearchItems.svelte'
import { loadHubFlows } from '$lib/utils'
import ListFilters from '$lib/components/home/ListFilters.svelte'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import RowIcon from '$lib/components/common/table/RowIcon.svelte'
import { loadHubFlows } from '$lib/hub'
export let filter = ''
@@ -53,7 +53,7 @@
<div class="flex items-center gap-4">
<RowIcon kind="flow" />
<div class="w-full text-left font-normal ">
<div class="w-full text-left font-normal">
<div class="text-gray-900 flex-wrap text-md font-semibold mb-1">
{#if item.marked}
{@html item.marked ?? ''}
@@ -4,10 +4,11 @@
import type { HubItem } from './model'
import { Badge, Skeleton } from '$lib/components/common'
import SearchItems from '$lib/components/SearchItems.svelte'
import { capitalize, classNames, loadHubScripts } from '$lib/utils'
import { capitalize, classNames } from '$lib/utils'
import NoItemFound from '$lib/components/home/NoItemFound.svelte'
import { APP_TO_ICON_COMPONENT } from '$lib/components/icons'
import ListFilters from '$lib/components/home/ListFilters.svelte'
import { loadHubScripts } from '$lib/scripts'
export let kind: 'script' | 'trigger' | 'approval' | 'failure' = 'script'
export let filter = ''
@@ -61,7 +62,7 @@
/>
</div>
<div class="w-full text-left font-normal ">
<div class="w-full text-left font-normal">
<div class="text-gray-900 flex-wrap text-md font-semibold mb-1">
{#if item.marked}
{@html item.marked ?? ''}
@@ -69,7 +70,7 @@
{item.summary ?? ''}
{/if}
</div>
<div class="text-gray-600 text-xs ">
<div class="text-gray-600 text-xs">
{item.path}
</div>
</div>
@@ -1,12 +1,12 @@
import type { Schema } from '$lib/common'
import type { Flow, FlowModule } from '$lib/gen'
import { schemaToObject } from '$lib/utils'
import { schemaToObject } from '$lib/schema'
import type { FlowState } from './flowState'
export type PickableProperties = {
flow_input: Object
priorIds: Record<string, any>
previousId: string | undefined,
previousId: string | undefined
hasResume: boolean
}
@@ -15,7 +15,6 @@ type StepPropPicker = {
extraLib: string
}
type ModuleBranches = FlowModule[][]
function getSubModules(flowModule: FlowModule): ModuleBranches {
@@ -24,19 +23,18 @@ function getSubModules(flowModule: FlowModule): ModuleBranches {
} else if (flowModule.value.type === 'branchall') {
return flowModule.value.branches.map((branch) => branch.modules)
} else if (flowModule.value.type == 'branchone') {
return [
...flowModule.value.branches.map((branch) => branch.modules),
flowModule.value.default
]
return [...flowModule.value.branches.map((branch) => branch.modules), flowModule.value.default]
}
return []
}
function getAllSubmodules(flowModule: FlowModule): ModuleBranches {
return getSubModules(flowModule).map((modules) => {
return modules.map((module) => {
return [module, ...getAllSubmodules(module).flat()]
}).flat()
return modules
.map((module) => {
return [module, ...getAllSubmodules(module).flat()]
})
.flat()
})
}
@@ -47,8 +45,6 @@ function dfs(id: string | undefined, flow: Flow, getParents: boolean = true): Fl
function rec(id: string, moduleBranches: ModuleBranches): FlowModule[] | undefined {
for (let modules of moduleBranches) {
for (const [i, module] of modules.entries()) {
if (module.id === id) {
return getParents ? [module] : modules.slice(0, i + 1).reverse()
@@ -93,7 +89,7 @@ function getFlowInput(
value: "Iteration's value",
index: "Iteration's index"
},
...parentFlowInput,
...parentFlowInput
}
} else {
return parentFlowInput
@@ -111,38 +107,40 @@ export function getStepPropPicker(
id: string,
flow: Flow,
args: any,
include_node: boolean,
include_node: boolean
): StepPropPicker {
const flowInput = getFlowInput(dfs(parentModule?.id, flow), flowState, args, flow.schema)
const previousIds = dfs(id, flow, false).map((x) => {
let submodules = getAllSubmodules(x).flat().map((x) => x.id)
const previousIds = dfs(id, flow, false)
.map((x) => {
let submodules = getAllSubmodules(x)
.flat()
.map((x) => x.id)
if (submodules.includes(id)) {
return [x.id]
} else {
return [x.id, ...submodules]
}
}).flat()
if (submodules.includes(id)) {
return [x.id]
} else {
return [x.id, ...submodules]
}
})
.flat()
if (!include_node) {
previousIds.shift()
}
let priorIds = Object.fromEntries(previousIds.map((id) => [id, flowState[id]?.previewResult ?? {}]).reverse())
let priorIds = Object.fromEntries(
previousIds.map((id) => [id, flowState[id]?.previewResult ?? {}]).reverse()
)
const pickableProperties = {
flow_input: flowInput,
priorIds: priorIds,
previousId: previousIds[0],
hasResume: previousModule?.suspend != undefined,
hasResume: previousModule?.suspend != undefined
}
if (pickableProperties.hasResume) {
pickableProperties["approvers"] = "The list of approvers"
pickableProperties['approvers'] = 'The list of approvers'
}
return {
@@ -151,7 +149,11 @@ export function getStepPropPicker(
}
}
export function buildExtraLib(flowInput: Record<string, any>, results: Record<string, any>, resume: boolean): string {
export function buildExtraLib(
flowInput: Record<string, any>,
results: Record<string, any>,
resume: boolean
): string {
return `
/**
* get variable (including secret) at path
@@ -180,7 +182,9 @@ declare const params: any;
*/
declare const results = ${JSON.stringify(results)};
${resume ? `
${
resume
? `
/**
* resume payload
*/
@@ -190,7 +194,8 @@ declare const resume: any
* The list of approvers separated by ,
*/
declare const approvers: string
` : ''}
`
: ''
}
`
}
+2 -1
View File
@@ -10,11 +10,12 @@ import {
import { inferArgs } from '$lib/infer'
import { loadSchema, loadSchemaFlow } from '$lib/scripts'
import { workspaceStore } from '$lib/stores'
import { emptySchema, sendUserToast } from '$lib/utils'
import { emptySchema } from '$lib/utils'
import { get } from 'svelte/store'
import type { FlowModuleState } from './flowState'
import type { PickableProperties } from './previousResults'
import { NEVER_TESTED_THIS_FAR } from './models'
import { sendUserToast } from '$lib/toast'
function create_context_function_template(eval_string: string, context: Record<string, any>) {
return `
@@ -14,7 +14,6 @@
RawAppService
} from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { canWrite } from '$lib/utils'
import type uFuzzy from '@leeoniya/ufuzzy'
import { Code2, LayoutDashboard } from 'lucide-svelte'
@@ -35,6 +34,7 @@
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import FlowIcon from './FlowIcon.svelte'
import RawAppRow from '../common/table/RawAppRow.svelte'
import { canWrite } from '$lib/utils'
type TableItem<T, U extends 'script' | 'flow' | 'app' | 'raw_app'> = T & {
canWrite: boolean
@@ -20,12 +20,10 @@
import SplitPanesWrapper from '../splitPanes/SplitPanesWrapper.svelte'
import { Loader2 } from 'lucide-svelte'
export let path: string | undefined
export let lang: Preview.language
export let previewIsLoading = false
export let previewJob: Job | undefined
export let pastPreviews: CompletedJob[] = []
export let lastSave: string | null
type DrawerContent = {
mode: 'json' | Preview.language | 'plain'
@@ -67,9 +65,8 @@
</Drawer>
<Tabs bind:selected={selectedTab} class="mt-1">
<Tab value="logs" size="xs">Logs/Result</Tab>
<Tab value="logs" size="xs">Logs & Result</Tab>
<Tab value="history" size="xs">History</Tab>
<Tab value="last_save" size="xs">Last save</Tab>
<svelte:fragment slot="content">
<!--
@@ -180,16 +177,5 @@
</tbody>
</TableCustom>
</TabContent>
<TabContent value="last_save" class="p-2">
{#if lastSave}
<div class="text-sm font-bold text-gray-600">
Last local save for path
<span class="italic">{path}</span>
</div>
<HighlightCode language={lang} code={lastSave} />
{:else}
No local save
{/if}
</TabContent>
</svelte:fragment>
</Tabs>
@@ -10,11 +10,12 @@
switchWorkspace,
workspaceStore
} from '$lib/stores'
import { classNames, isCloudHosted } from '$lib/utils'
import { classNames } from '$lib/utils'
import { faCog, faCrown, faHardHat, faSignOut, faUser } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import Menu from '../common/menu/Menu.svelte'
import { SUPERADMIN_SETTINGS_HASH, USER_SETTINGS_HASH } from './settings'
import { isCloudHosted } from '$lib/cloud'
export let isCollapsed: boolean = false
</script>
+63
View File
@@ -0,0 +1,63 @@
import type { Schema } from './common'
import { AppService, FlowService, type Flow, type Script } from './gen'
import { encodeState } from './utils'
export function scriptToHubUrl(
content: string,
summary: string,
description: string,
kind: Script.kind,
language: Script.language,
schema: Schema | undefined,
lock: string | undefined
): URL {
const url = new URL('https://hub.windmill.dev/scripts/add')
url.searchParams.append('content', content)
url.searchParams.append('summary', summary)
url.searchParams.append('description', description)
url.searchParams.append('kind', kind)
url.searchParams.append('language', language)
url.searchParams.append('schema', JSON.stringify(schema, null, 2))
lock && url.searchParams.append('lockfile', lock)
return url
}
export async function loadHubFlows() {
try {
const flows = (await FlowService.listHubFlows()).flows ?? []
const processed = flows.sort((a, b) => b.votes - a.votes)
return processed
} catch {
console.error('Hub is not available')
}
}
export async function loadHubApps() {
try {
const apps = (await AppService.listHubApps()).apps ?? []
const processed = apps.sort((a, b) => b.votes - a.votes)
return processed
} catch {
console.error('Hub is not available')
}
}
export function flowToHubUrl(flow: Flow): URL {
const url = new URL('https://hub.windmill.dev/flows/add')
const openFlow = {
value: flow.value,
summary: flow.summary,
description: flow.description,
schema: flow.schema
}
url.searchParams.append('flow', encodeState(openFlow))
return url
}
export function appToHubUrl(staticApp: any): URL {
const url = new URL('https://hub.windmill.dev/apps/add')
url.searchParams.append('app', encodeState(staticApp))
return url
}
+5 -7
View File
@@ -1,16 +1,15 @@
import { goto } from '$app/navigation'
import { UserService } from '$lib/gen'
import { clearStores } from './stores.js'
import { sendUserToast } from './utils.js'
import { sendUserToast } from './toast'
export async function logoutWithRedirect(rd?: string): Promise<void> {
await clearUser()
if (rd && rd != "/" && rd?.split('?')[0] != '/user/login') {
if (rd && rd != '/' && rd?.split('?')[0] != '/user/login') {
const error = document.cookie.includes('token')
? `error=${encodeURIComponent('You have been logged out because your session has expired.')}&`
: ''
console.log({rd});
console.log({ rd })
goto(`/user/login?${error}${rd ? 'rd=' + encodeURIComponent(rd) : ''}`, { replaceState: true })
} else {
goto('/user/login', { replaceState: true })
@@ -27,6 +26,5 @@ export async function clearUser() {
try {
clearStores()
await UserService.logout()
} catch (error) {
}
}
} catch (error) {}
}
+6
View File
@@ -0,0 +1,6 @@
import { goto } from '$app/navigation'
export async function setQuery(url: URL, key: string, value: string): Promise<void> {
url.searchParams.set(key, value)
await goto(`?${url.searchParams.toString()}`)
}
+47
View File
@@ -0,0 +1,47 @@
import type { Schema } from './common'
export function schemaToTsType(schema: Schema): string {
if (!schema || !schema.properties) {
return 'any'
}
const propKeys = Object.keys(schema.properties)
const types = propKeys
.map((key: string) => {
const prop = schema.properties[key]
const isOptional = !schema.required.includes(key)
const prefix = `${key}${isOptional ? '?' : ''}`
let type: string = 'any'
if (prop.type === 'string') {
type = 'string'
} else if (prop.type === 'number' || prop.type === 'integer') {
type = 'number'
} else if (prop.type === 'boolean') {
type = 'boolean'
} else if (prop.type === 'array') {
let type = prop.items?.type ?? 'any'
if (type === 'integer') {
type = 'number'
}
type = `${type}[]`
}
return `${prefix}: ${type}`
})
.join(';')
return `{ ${types} }`
}
export function schemaToObject(schema: Schema, args: Record<string, any>): Object {
const object = {}
if (!schema || !schema.properties) {
return object
}
const propKeys = Object.keys(schema.properties)
propKeys.forEach((key: string) => {
object[key] = args[key] ?? null
})
return object
}
+84 -3
View File
@@ -1,10 +1,22 @@
import { get } from 'svelte/store'
import type { Schema } from './common'
import { FlowService, ScriptService } from './gen'
import type { Schema, SupportedLanguage } from './common'
import { FlowService, Script, ScriptService } from './gen'
import { inferArgs } from './infer'
import { workspaceStore } from './stores'
import { workspaceStore, hubScripts } from './stores'
import { emptySchema } from './utils'
export function scriptLangToEditorLang(lang: Script.language) {
if (lang == 'deno') {
return 'typescript'
} else if (lang == 'python3') {
return 'python'
} else if (lang == 'bash') {
return 'shell'
} else {
return lang
}
}
export async function loadSchema(path: string, hash?: string): Promise<Schema> {
if (path.startsWith('hub/')) {
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
@@ -37,3 +49,72 @@ export async function loadSchemaFlow(path: string): Promise<Schema> {
})
return flow.schema
}
export function scriptPathToHref(path: string): string {
if (path.startsWith('hub/')) {
return 'https://hub.windmill.dev/from_version/' + path.substring(4)
} else {
return `/scripts/get/${path}?workspace=${get(workspaceStore)}`
}
}
export async function getScriptByPath(path: string): Promise<{
content: string
language: SupportedLanguage
schema: any
description: string
tag: string | undefined
}> {
if (path.startsWith('hub/')) {
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
return {
content,
language: language as SupportedLanguage,
schema,
description: '',
tag: undefined
}
} else {
const script = await ScriptService.getScriptByPath({
workspace: get(workspaceStore)!,
path: path ?? ''
})
return {
content: script.content,
language: script.language,
schema: script.schema,
description: script.description,
tag: script.tag
}
}
}
export async function getLatestHashForScript(path: string): Promise<string> {
const script = await ScriptService.getScriptByPath({
workspace: get(workspaceStore)!,
path: path ?? ''
})
return script.hash
}
export async function loadHubScripts() {
try {
const scripts = (await ScriptService.listHubScripts()).asks ?? []
const processed = scripts
.map((x) => ({
path: `hub/${x.id}/${x.app}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
summary: `${x.summary} (${x.app})`,
approved: x.approved,
kind: x.kind,
app: x.app,
views: x.views,
votes: x.votes,
ask_id: x.ask_id
}))
.sort((a, b) => b.views - a.views)
hubScripts.set(processed)
} catch {
console.error('Hub is not available')
}
}
+4 -39
View File
@@ -1,9 +1,7 @@
import { browser } from '$app/environment'
import { derived, type Readable, writable, get } from 'svelte/store'
import { BROWSER } from 'esm-env'
import { derived, type Readable, writable } from 'svelte/store'
import type { UserWorkspaceList } from '$lib/gen/models/UserWorkspaceList.js'
import { getUserExt } from './user'
import { WorkspaceService, type TokenResponse } from './gen'
import { isCloudHosted } from './utils'
import type { TokenResponse } from './gen'
export interface UserExt {
email: string
@@ -18,7 +16,7 @@ export interface UserExt {
folders_owners: string[]
}
let persistedWorkspace = browser && localStorage.getItem('workspace')
let persistedWorkspace = BROWSER && localStorage.getItem('workspace')
export const workerTags = writable<string[] | undefined>(undefined)
export const usageStore = writable<number>(0)
@@ -65,39 +63,6 @@ export const hubScripts = writable<
| undefined
>(undefined)
if (browser) {
workspaceStore.subscribe(async (workspace) => {
if (workspace) {
try {
localStorage.setItem('workspace', String(workspace))
} catch (e) {
console.error('Could not persist workspace to local storage', e)
}
const user = await getUserExt(workspace)
userStore.set(user)
if (isCloudHosted() && user?.is_admin) {
premiumStore.set(await WorkspaceService.getPremiumInfo({ workspace }))
}
} else {
userStore.set(undefined)
}
})
setInterval(async () => {
try {
const workspace = get(workspaceStore)
const user = get(userStore)
if (workspace && user && !user.is_super_admin && !user.is_admin) {
userStore.set(await getUserExt(workspace))
console.log('refreshed user')
}
} catch (e) {
console.error('Could not refresh user', e)
}
}, 30000)
}
export function switchWorkspace(workspace: string | undefined) {
localStorage.removeItem('flow')
localStorage.removeItem('app')
+33
View File
@@ -0,0 +1,33 @@
import Toast from '$lib/components/Toast.svelte'
import { toast } from '@zerodevx/svelte-toast'
export type ToastAction = {
label: string
callback: () => void
}
export function sendUserToast(
message: string,
error: boolean = false,
actions: ToastAction[] = [],
errorMessage: string | undefined = undefined
): void {
toast.push({
component: {
src: Toast,
props: {
message,
error,
actions,
errorMessage
},
sendIdTo: 'toastId'
},
dismissable: false,
initial: 0,
theme: {
'--toastPadding': '0',
'--toastMsgPadding': '0'
}
})
}
+85 -317
View File
@@ -1,12 +1,15 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { goto } from '$app/navigation'
import { AppService, type Flow, FlowService, Script, ScriptService, type User } from '$lib/gen'
import { toast } from '@zerodevx/svelte-toast'
import type { Schema, SupportedLanguage } from './common'
import { hubScripts, type UserExt, workspaceStore } from './stores'
import { page } from '$app/stores'
import { get } from 'svelte/store'
import Toast from '$lib/components/Toast.svelte'
// /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
// import { goto } from '$app/navigation'
// import { AppService, type Flow, FlowService, Script, ScriptService, type User } from '$lib/gen'
// import { toast } from '@zerodevx/svelte-toast'
// import type { Schema, SupportedLanguage } from './common'
// import { hubScripts, type UserExt, workspaceStore } from './stores'
// import { page } from '$app/stores'
// import { get } from 'svelte/store'
import type { UserExt } from './stores'
import { sendUserToast } from './toast'
export { sendUserToast }
export function validateUsername(username: string): string {
if (username != '' && !/^\w+$/.test(username)) {
@@ -79,37 +82,6 @@ export function getToday() {
return today
}
export type ToastAction = {
label: string
callback: () => void
}
export function sendUserToast(
message: string,
error: boolean = false,
actions: ToastAction[] = [],
errorMessage: string | undefined = undefined
): void {
toast.push({
component: {
src: Toast,
props: {
message,
error,
actions,
errorMessage
},
sendIdTo: 'toastId'
},
dismissable: false,
initial: 0,
theme: {
'--toastPadding': '0',
'--toastMsgPadding': '0'
}
})
}
export function truncateHash(hash: string): string {
if (hash.length >= 6) {
return hash.substr(hash.length - 6)
@@ -193,79 +165,6 @@ export function removeItemAll<T>(arr: T[], value: T) {
return arr
}
export function isOwner(
path: string,
user: UserExt | undefined,
workspace: string | undefined
): boolean {
if (!user || !workspace) {
return false
}
if (user.is_super_admin) {
return true
}
if (workspace == 'admin') {
return false
} else if (user.is_admin) {
return true
} else if (path.startsWith('u/' + user.username + '/')) {
return true
} else if (path.startsWith('f/')) {
return user.folders_owners.some((x) => path.startsWith('f/' + x + '/'))
} else {
return false
}
}
export function isObviousOwner(path: string, user?: UserExt): boolean {
if (!user) {
return false
}
if (user.is_admin || user.is_super_admin) {
return true
}
let userOwner = `u/${user.username}`
if (path.startsWith(userOwner)) {
return true
}
if (user.pgroups.findIndex((x) => path.startsWith(x)) != -1) {
return true
}
if (user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1) {
return true
}
return false
}
export function canWrite(
path: string,
extra_perms: Record<string, boolean>,
user?: UserExt
): boolean {
if (user?.is_admin || user?.is_super_admin) {
return true
}
let keys = Object.keys(extra_perms)
if (!user) {
return false
}
if (isObviousOwner(path, user)) {
return true
}
let userOwner = `u/${user.username}`
if (keys.includes(userOwner) && extra_perms[userOwner]) {
return true
}
if (user.pgroups.findIndex((x) => keys.includes(x) && extra_perms[x]) != -1) {
return true
}
if (user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1) {
return true
}
return false
}
export function emptyString(str: string | undefined | null): boolean {
return str === undefined || str === null || str === ''
}
@@ -320,11 +219,6 @@ export function decodeArgs(queryArgs: string | undefined): any {
return {}
}
export async function setQuery(url: URL, key: string, value: string): Promise<void> {
url.searchParams.set(key, value)
await goto(`?${url.searchParams.toString()}`)
}
let debounced: NodeJS.Timeout | undefined = undefined
export function setQueryWithoutLoad(
url: URL,
@@ -412,60 +306,6 @@ export function isString(value: any) {
return typeof value === 'string' || value instanceof String
}
export function mapUserToUserExt(user: User): UserExt {
return {
...user,
groups: user.groups!,
pgroups: user.groups!.map((x) => `g/${x}`)
}
}
export function schemaToTsType(schema: Schema): string {
if (!schema || !schema.properties) {
return 'any'
}
const propKeys = Object.keys(schema.properties)
const types = propKeys
.map((key: string) => {
const prop = schema.properties[key]
const isOptional = !schema.required.includes(key)
const prefix = `${key}${isOptional ? '?' : ''}`
let type: string = 'any'
if (prop.type === 'string') {
type = 'string'
} else if (prop.type === 'number' || prop.type === 'integer') {
type = 'number'
} else if (prop.type === 'boolean') {
type = 'boolean'
} else if (prop.type === 'array') {
let type = prop.items?.type ?? 'any'
if (type === 'integer') {
type = 'number'
}
type = `${type}[]`
}
return `${prefix}: ${type}`
})
.join(';')
return `{ ${types} }`
}
export function schemaToObject(schema: Schema, args: Record<string, any>): Object {
const object = {}
if (!schema || !schema.properties) {
return object
}
const propKeys = Object.keys(schema.properties)
propKeys.forEach((key: string) => {
object[key] = args[key] ?? null
})
return object
}
export type InputCat =
| 'string'
| 'number'
@@ -512,95 +352,6 @@ export function setInputCat(
}
}
export function scriptPathToHref(path: string): string {
if (path.startsWith('hub/')) {
return 'https://hub.windmill.dev/from_version/' + path.substring(4)
} else {
return `/scripts/get/${path}?workspace=${get(workspaceStore)}`
}
}
export async function getScriptByPath(path: string): Promise<{
content: string
language: SupportedLanguage
schema: any
description: string
tag: string | undefined
}> {
if (path.startsWith('hub/')) {
const { content, language, schema } = await ScriptService.getHubScriptByPath({ path })
return {
content,
language: language as SupportedLanguage,
schema,
description: '',
tag: undefined
}
} else {
const script = await ScriptService.getScriptByPath({
workspace: get(workspaceStore)!,
path: path ?? ''
})
return {
content: script.content,
language: script.language,
schema: script.schema,
description: script.description,
tag: script.tag
}
}
}
export async function getLatestHashForScript(path: string): Promise<string> {
const script = await ScriptService.getScriptByPath({
workspace: get(workspaceStore)!,
path: path ?? ''
})
return script.hash
}
export async function loadHubScripts() {
try {
const scripts = (await ScriptService.listHubScripts()).asks ?? []
const processed = scripts
.map((x) => ({
path: `hub/${x.id}/${x.app}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
summary: `${x.summary} (${x.app})`,
approved: x.approved,
kind: x.kind,
app: x.app,
views: x.views,
votes: x.votes,
ask_id: x.ask_id
}))
.sort((a, b) => b.views - a.views)
hubScripts.set(processed)
} catch {
console.error('Hub is not available')
}
}
export async function loadHubFlows() {
try {
const flows = (await FlowService.listHubFlows()).flows ?? []
const processed = flows.sort((a, b) => b.votes - a.votes)
return processed
} catch {
console.error('Hub is not available')
}
}
export async function loadHubApps() {
try {
const apps = (await AppService.listHubApps()).apps ?? []
const processed = apps.sort((a, b) => b.votes - a.votes)
return processed
} catch {
console.error('Hub is not available')
}
}
export function formatCron(inp: string): string {
// Allow for cron expressions inputted by the user to omit month and year
let splitted = inp.split(' ')
@@ -612,62 +363,10 @@ export function formatCron(inp: string): string {
}
}
export function flowToHubUrl(flow: Flow): URL {
const url = new URL('https://hub.windmill.dev/flows/add')
const openFlow = {
value: flow.value,
summary: flow.summary,
description: flow.description,
schema: flow.schema
}
url.searchParams.append('flow', encodeState(openFlow))
return url
}
export function appToHubUrl(staticApp: any): URL {
const url = new URL('https://hub.windmill.dev/apps/add')
url.searchParams.append('app', encodeState(staticApp))
return url
}
export function scriptToHubUrl(
content: string,
summary: string,
description: string,
kind: Script.kind,
language: Script.language,
schema: Schema | undefined,
lock: string | undefined
): URL {
const url = new URL('https://hub.windmill.dev/scripts/add')
url.searchParams.append('content', content)
url.searchParams.append('summary', summary)
url.searchParams.append('description', description)
url.searchParams.append('kind', kind)
url.searchParams.append('language', language)
url.searchParams.append('schema', JSON.stringify(schema, null, 2))
lock && url.searchParams.append('lockfile', lock)
return url
}
export function classNames(...classes: Array<string | undefined>): string {
return classes.filter(Boolean).join(' ')
}
export function scriptLangToEditorLang(lang: Script.language) {
if (lang == 'deno') {
return 'typescript'
} else if (lang == 'python3') {
return 'python'
} else if (lang == 'bash') {
return 'shell'
} else {
return lang
}
}
export async function copyToClipboard(value?: string, sendToast = true): Promise<boolean> {
if (!value) {
return false
@@ -706,10 +405,6 @@ export function addWhitespaceBeforeCapitals(word?: string): string {
return word.replace(/([A-Z])/g, ' $1').trim()
}
export function isCloudHosted(): boolean {
return get(page)?.url?.hostname == 'app.windmill.dev'
}
export function isObject(obj: any) {
return typeof obj === 'object'
}
@@ -788,3 +483,76 @@ export function deepMergeWithPriority<T>(target: T, source: T): T {
return merged
}
export function canWrite(
path: string,
extra_perms: Record<string, boolean>,
user?: UserExt
): boolean {
if (user?.is_admin || user?.is_super_admin) {
return true
}
let keys = Object.keys(extra_perms)
if (!user) {
return false
}
if (isObviousOwner(path, user)) {
return true
}
let userOwner = `u/${user.username}`
if (keys.includes(userOwner) && extra_perms[userOwner]) {
return true
}
if (user.pgroups.findIndex((x) => keys.includes(x) && extra_perms[x]) != -1) {
return true
}
if (user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1) {
return true
}
return false
}
export function isOwner(
path: string,
user: UserExt | undefined,
workspace: string | undefined
): boolean {
if (!user || !workspace) {
return false
}
if (user.is_super_admin) {
return true
}
if (workspace == 'admin') {
return false
} else if (user.is_admin) {
return true
} else if (path.startsWith('u/' + user.username + '/')) {
return true
} else if (path.startsWith('f/')) {
return user.folders_owners.some((x) => path.startsWith('f/' + x + '/'))
} else {
return false
}
}
export function isObviousOwner(path: string, user?: UserExt): boolean {
if (!user) {
return false
}
if (user.is_admin || user.is_super_admin) {
return true
}
let userOwner = `u/${user.username}`
if (path.startsWith(userOwner)) {
return true
}
if (user.pgroups.findIndex((x) => path.startsWith(x)) != -1) {
return true
}
if (user.folders.findIndex((x) => path.startsWith('f/' + x)) != -1) {
return true
}
return false
}
@@ -1,4 +1,5 @@
<script lang="ts">
import { BROWSER } from 'esm-env'
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
@@ -11,8 +12,7 @@
ScriptService,
UserService
} from '$lib/gen'
import { classNames, isCloudHosted } from '$lib/utils'
import { browser } from '$app/environment'
import { classNames } from '$lib/utils'
import WorkspaceMenu from '$lib/components/sidebar/WorkspaceMenu.svelte'
import SidebarContent from '$lib/components/sidebar/SidebarContent.svelte'
@@ -25,6 +25,7 @@
import { page } from '$app/stores'
import FavoriteMenu from '$lib/components/sidebar/FavoriteMenu.svelte'
import { SUPERADMIN_SETTINGS_HASH, USER_SETTINGS_HASH } from '$lib/components/sidebar/settings'
import { isCloudHosted } from '$lib/cloud'
OpenAPI.WITH_CREDENTIALS = true
let menuOpen = false
@@ -53,7 +54,7 @@
menuOpen = false
})
let innerWidth = browser ? window.innerWidth : 2000
let innerWidth = BROWSER ? window.innerWidth : 2000
let favoriteLinks = [] as {
label: string
@@ -5,7 +5,7 @@
import PageHeader from '$lib/components/PageHeader.svelte'
import CreateActionsFlow from '$lib/components/flows/CreateActionsFlow.svelte'
import CreateActionsScript from '$lib/components/scripts/CreateActionsScript.svelte'
import { getScriptByPath } from '$lib/utils'
import { getScriptByPath } from '$lib/scripts'
import type { HubItem } from '$lib/components/flows/pickers/model'
import { faCodeFork } from '@fortawesome/free-solid-svg-icons'
import PickHubScript from '$lib/components/flows/pickers/PickHubScript.svelte'
@@ -4,11 +4,12 @@
import AppEditor from '$lib/components/apps/editor/AppEditor.svelte'
import { AppService, Policy } from '$lib/gen'
import { page } from '$app/stores'
import { decodeState, sendUserToast } from '$lib/utils'
import { decodeState } from '$lib/utils'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { userStore, workspaceStore } from '$lib/stores'
import type { App } from '$lib/components/apps/types'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/toast'
let nodraft = $page.url.searchParams.get('nodraft')
const hubId = $page.url.searchParams.get('hub')
@@ -3,9 +3,10 @@
import { AppService, AppWithLastVersion } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { page } from '$app/stores'
import { decodeState, sendUserToast, type ToastAction } from '$lib/utils'
import { decodeState } from '$lib/utils'
import { goto } from '$app/navigation'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { sendUserToast, type ToastAction } from '$lib/toast'
let app = undefined as (AppWithLastVersion & { draft_only?: boolean }) | undefined
@@ -2,7 +2,7 @@
import { page } from '$app/stores'
import { Skeleton } from '$lib/components/common'
import { userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { onMount } from 'svelte'
let loaded = false
@@ -2,13 +2,15 @@
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
import type { FlowState } from '$lib/components/flows/flowState'
import { importFlowStore, initFlow } from '$lib/components/flows/flowStore'
import { FlowService, type Flow } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { decodeState, emptySchema, sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { decodeState, emptySchema } from '$lib/utils'
import { writable } from 'svelte/store'
let nodraft = $page.url.searchParams.get('nodraft')
@@ -104,8 +106,26 @@
loadFlow()
$dirtyStore = true
let getSelectedId: (() => string) | undefined = undefined
</script>
<div id="monaco-widgets-root" class="monaco-editor" style="z-index: 1200;" />
<UnsavedConfirmationModal />
<FlowBuilder {flowStore} {flowStateStore} {selectedId} {loading} />
<FlowBuilder
on:saveInitial={() => {
goto(`/flows/edit/${$flowStore.path}?selected=${getSelectedId?.()}`)
}}
on:deploy={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
}}
on:details={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
}}
bind:getSelectedId
{flowStore}
{flowStateStore}
{selectedId}
{loading}
/>
@@ -4,12 +4,14 @@
import { page } from '$app/stores'
import FlowBuilder from '$lib/components/FlowBuilder.svelte'
import { workspaceStore } from '$lib/stores'
import { decodeArgs, decodeState, emptySchema, sendUserToast } from '$lib/utils'
import { decodeArgs, decodeState, emptySchema } from '$lib/utils'
import { initFlow } from '$lib/components/flows/flowStore'
import { dirtyStore } from '$lib/components/common/confirmationModal/dirtyStore'
import { goto } from '$app/navigation'
import { writable } from 'svelte/store'
import type { FlowState } from '$lib/components/flows/flowState'
import { sendUserToast } from '$lib/toast'
import UnsavedConfirmationModal from '$lib/components/common/confirmationModal/UnsavedConfirmationModal.svelte'
let nodraft = $page.url.searchParams.get('nodraft')
const initialState = nodraft ? undefined : localStorage.getItem(`flow-${$page.params.path}`)
@@ -78,7 +80,7 @@
await initFlow(flow, flowStore, flowStateStore)
loading = false
selectedId = stateLoadedFromUrl?.selectedId
selectedId = stateLoadedFromUrl?.selectedId ?? $page.url.searchParams.get('selected')
$dirtyStore = false
}
@@ -91,7 +93,15 @@
<div id="monaco-widgets-root" class="monaco-editor" style="z-index: 1200;" />
<UnsavedConfirmationModal />
<FlowBuilder
on:deploy={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
}}
on:details={() => {
goto(`/flows/get/${$flowStore.path}?workspace=${$workspaceStore}`)
}}
{flowStore}
{flowStateStore}
initialPath={$page.params.path}
@@ -2,13 +2,12 @@
import { page } from '$app/stores'
import { FlowService, JobService, ScheduleService, type Flow, type Schedule } from '$lib/gen'
import {
canWrite,
canWrite,
copyToClipboard,
defaultIfEmptyString,
displayDaysAgo,
emptyString,
encodeState,
sendUserToast
encodeState
} from '$lib/utils'
import {
faArchive,
@@ -40,6 +39,7 @@
import { userStore, workspaceStore } from '$lib/stores'
import Icon from 'svelte-awesome'
import { slide } from 'svelte/transition'
import { sendUserToast } from '$lib/toast'
let userSettings: UserSettings
@@ -12,8 +12,7 @@
defaultIfEmptyString,
displayDaysAgo,
emptyString,
getModifierKey,
sendUserToast
getModifierKey
} from '$lib/utils'
import { faEye, faPen, faPlay } from '@fortawesome/free-solid-svg-icons'
import SplitPanesWrapper from '$lib/components/splitPanes/SplitPanesWrapper.svelte'
@@ -21,6 +20,7 @@
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-svelte'
import { sendUserToast } from '$lib/toast'
const path = $page.params.path
let flow: Flow | undefined
@@ -1,7 +1,6 @@
<script lang="ts">
import type { Folder } from '$lib/gen'
import { FolderService } from '$lib/gen'
import { canWrite } from '$lib/utils'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import Dropdown from '$lib/components/Dropdown.svelte'
@@ -14,6 +13,7 @@
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
import FolderInfo from '$lib/components/FolderInfo.svelte'
import FolderUsageInfo from '$lib/components/FolderUsageInfo.svelte'
import { canWrite } from '$lib/utils'
type FolderW = Folder & { canWrite: boolean }
@@ -1,7 +1,6 @@
<script lang="ts">
import type { Group } from '$lib/gen'
import { GroupService } from '$lib/gen'
import { canWrite } from '$lib/utils'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button, Drawer, DrawerContent, Skeleton } from '$lib/components/common'
@@ -13,6 +12,7 @@
import TableCustom from '$lib/components/TableCustom.svelte'
import { userStore, workspaceStore } from '$lib/stores'
import { faEdit, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
import { canWrite } from '$lib/utils'
type GroupW = Group & { canWrite: boolean }
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { onMount } from 'svelte'
import { OauthService } from '$lib/gen'
import { oauthStore } from '$lib/stores'
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { onMount } from 'svelte'
import { OauthService } from '$lib/gen'
import { workspaceStore, oauthStore } from '$lib/stores'
@@ -26,14 +26,8 @@
import type { ResourceType } from '$lib/gen'
import { OauthService, ResourceService, type ListableResource } from '$lib/gen'
import { oauthStore, userStore, workspaceStore } from '$lib/stores'
import {
canWrite,
classNames,
emptySchema,
removeMarkdown,
sendUserToast,
truncate
} from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { canWrite, classNames, emptySchema, removeMarkdown, truncate } from '$lib/utils'
import {
faChain,
faCircle,
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from '$app/stores'
import { JobService, Job } from '$lib/gen'
import { canWrite, displayDate, forLater, sendUserToast, truncateHash } from '$lib/utils'
import { canWrite, displayDate, forLater, truncateHash } from '$lib/utils'
import Icon from 'svelte-awesome'
import { check } from 'svelte-awesome/icons'
import {
@@ -33,6 +33,7 @@
import Tooltip from '$lib/components/Tooltip.svelte'
import Dropdown from '$lib/components/Dropdown.svelte'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/toast'
let job: Job | undefined
const iconScale = 1
@@ -1,10 +1,10 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte'
import { JobService, Job, CompletedJob, ScriptService, FlowService } from '$lib/gen'
import { setQuery, setQueryWithoutLoad } from '$lib/utils'
import { setQueryWithoutLoad } from '$lib/utils'
import { page } from '$app/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { workspaceStore } from '$lib/stores'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import Tabs from '$lib/components/common/tabs/Tabs.svelte'
@@ -21,6 +21,7 @@
import Slider from '$lib/components/Slider.svelte'
import JsonEditor from '$lib/components/apps/editor/settingsPanel/inputEditor/JsonEditor.svelte'
import { openStore } from '$lib/components/jobs/JobPreview.svelte'
import { setQuery } from '$lib/navigation'
let jobs: Job[] | undefined
let error: Error | undefined
@@ -1,6 +1,6 @@
<script lang="ts">
import { ScheduleService, type Schedule, JobService, type ScriptArgs } from '$lib/gen'
import { canWrite, displayDate, sendUserToast } from '$lib/utils'
import { canWrite, displayDate } from '$lib/utils'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Badge, Button, Skeleton } from '$lib/components/common'
@@ -26,6 +26,7 @@
} from '@fortawesome/free-solid-svg-icons'
import { Icon } from 'svelte-awesome'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/toast'
type ScheduleW = Schedule & { canWrite: boolean }
@@ -4,8 +4,9 @@
import { page } from '$app/stores'
import { runFormStore, workspaceStore } from '$lib/stores'
import ScriptBuilder from '$lib/components/ScriptBuilder.svelte'
import { decodeState, sendUserToast } from '$lib/utils'
import { decodeState } from '$lib/utils'
import { goto } from '$app/navigation'
import { sendUserToast } from '$lib/toast'
const initialState = $page.url.searchParams.get('state')
let initialArgs = {}
@@ -3,14 +3,14 @@
import { JobService, ScriptService, type Script } from '$lib/gen'
import {
truncateHash,
sendUserToast,
displayDaysAgo,
canWrite,
defaultIfEmptyString,
scriptToHubUrl,
copyToClipboard,
emptyString,
encodeState
encodeState,
canWrite
} from '$lib/utils'
import {
faPlay,
@@ -55,6 +55,8 @@
SCRIPT_VIEW_WEBHOOK_INFO_LINK,
SCRIPT_VIEW_WEBHOOK_INFO_TIP
} from '$lib/consts'
import { sendUserToast } from '$lib/toast'
import { scriptToHubUrl } from '$lib/hub'
let userSettings: UserSettings
let script: Script | undefined
@@ -10,19 +10,19 @@
import { inferArgs } from '$lib/infer'
import { userStore, workspaceStore } from '$lib/stores'
import {
canWrite,
canWrite,
defaultIfEmptyString,
displayDaysAgo,
emptySchema,
emptyString,
getModifierKey,
sendUserToast
} from '$lib/utils'
import { faEye, faPen, faPlay } from '@fortawesome/free-solid-svg-icons'
import { Pane, Splitpanes } from 'svelte-splitpanes'
import { tweened } from 'svelte/motion'
import { cubicOut } from 'svelte/easing'
import { ArrowLeftIcon, ArrowRightIcon } from 'lucide-svelte'
import { sendUserToast } from '$lib/toast'
$: hash = $page.params.hash
let script: Script | undefined
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { UserService, WorkspaceService } from '$lib/gen'
import { sendUserToast, validateUsername } from '$lib/utils'
import { validateUsername } from '$lib/utils'
import { logoutWithRedirect } from '$lib/logout'
import { page } from '$app/stores'
import { switchWorkspace, usersWorkspaceStore } from '$lib/stores'
@@ -10,6 +10,7 @@
import Toggle from '$lib/components/Toggle.svelte'
import Tooltip from '$lib/components/Tooltip.svelte'
import { onMount } from 'svelte'
import { sendUserToast } from '$lib/toast'
const rd = $page.url.searchParams.get('rd')
@@ -5,10 +5,12 @@
import { onMount } from 'svelte'
import { OauthService, SettingsService, UserService, WorkspaceService } from '$lib/gen'
import { clearStores, usersWorkspaceStore, workspaceStore, userStore } from '$lib/stores'
import { classNames, isCloudHosted, sendUserToast } from '$lib/utils'
import { classNames } from '$lib/utils'
import { getUserExt, refreshSuperadmin } from '$lib/user'
import { Button, Skeleton } from '$lib/components/common'
import { WindmillIcon } from '$lib/components/icons'
import { sendUserToast } from '$lib/toast'
import { isCloudHosted } from '$lib/cloud'
let email = $page.url.searchParams.get('email') ?? ''
let password = $page.url.searchParams.get('password') ?? ''
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { logout, logoutWithRedirect } from '$lib/logout'
import { UserService, type WorkspaceInvite, WorkspaceService } from '$lib/gen'
import {
@@ -3,7 +3,7 @@
import CenteredModal from '$lib/components/CenteredModal.svelte'
import WindmillIcon from '$lib/components/icons/WindmillIcon.svelte'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
sendUserToast('Sucess. You can return to your terminal now.')
goto('/')
@@ -16,7 +16,8 @@
import type { ContextualVariable, ListableVariable } from '$lib/gen'
import { OauthService, VariableService } from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { canWrite, isOwner, sendUserToast, truncate } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { canWrite, isOwner, truncate } from '$lib/utils'
import {
faChain,
faCircle,
@@ -5,7 +5,8 @@
import PageHeader from '$lib/components/PageHeader.svelte'
import TableCustom from '$lib/components/TableCustom.svelte'
import { WorkerService, type WorkerPing } from '$lib/gen'
import { displayDate, groupBy, sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { displayDate, groupBy } from '$lib/utils'
import { onDestroy, onMount } from 'svelte'
let workers: WorkerPing[] | undefined = undefined
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { isCloudHosted } from '$lib/cloud'
import AddUser from '$lib/components/AddUser.svelte'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Alert, Badge, Button, Skeleton, Tab, Tabs } from '$lib/components/common'
@@ -24,7 +25,8 @@
type WorkspaceInvite
} from '$lib/gen'
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '$lib/stores'
import { capitalize, isCloudHosted, sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { capitalize } from '$lib/utils'
import { faSlack } from '@fortawesome/free-brands-svg-icons'
import { faBarsStaggered, faExternalLink, faScroll } from '@fortawesome/free-solid-svg-icons'
@@ -208,9 +210,9 @@
<div class="flex gap-2 items-center my-1"> Users & Invites </div>
</Tab>
{#if WORKSPACE_SHOW_SLACK_CMD}
<Tab size="md" value="slack">
<div class="flex gap-2 items-center my-1"> Slack Command </div>
</Tab>
<Tab size="md" value="slack">
<div class="flex gap-2 items-center my-1"> Slack Command </div>
</Tab>
{/if}
{#if isCloudHosted()}
<Tab size="md" value="premium">
@@ -221,9 +223,9 @@
<div class="flex gap-2 items-center my-1"> Export & Delete Workspace </div>
</Tab>
{#if WORKSPACE_SHOW_WEBHOOK_CLI_SYNC}
<Tab size="md" value="webhook">
<div class="flex gap-2 items-center my-1">Webhook for CLI Sync</div>
</Tab>
<Tab size="md" value="webhook">
<div class="flex gap-2 items-center my-1">Webhook for CLI Sync</div>
</Tab>
{/if}
</Tabs>
</div>
@@ -6,7 +6,7 @@
import { WindmillIcon } from '$lib/components/icons'
import { WorkspaceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
let success = $page.url.searchParams.get('success') === 'true'
+1 -1
View File
@@ -5,7 +5,7 @@
import { logoutWithRedirect } from '$lib/logout'
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '$lib/stores'
import { getUserExt, refreshSuperadmin } from '$lib/user'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { onMount } from 'svelte'
import github from 'svelte-highlight/styles/github'
@@ -3,7 +3,7 @@
import { page } from '$app/stores'
import Button from '$lib/components/common/button/Button.svelte'
import CenteredModal from '$lib/components/CenteredModal.svelte'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import FlowMetadata from '$lib/components/FlowMetadata.svelte'
import JobArgs from '$lib/components/JobArgs.svelte'
import { onDestroy, onMount } from 'svelte'
@@ -1,5 +1,5 @@
<script lang="ts">
import { browser } from '$app/environment'
import { BROWSER } from 'esm-env'
import { page } from '$app/stores'
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
import { IS_APP_PUBLIC_CONTEXT_KEY, type EditorBreakpoint } from '$lib/components/apps/types'
@@ -30,7 +30,7 @@
}
}
if (browser) {
if (BROWSER) {
loadApp()
}
@@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { sendUserToast } from '$lib/utils'
import { sendUserToast } from '$lib/toast'
import { onMount } from 'svelte'
import { UserService, WorkspaceService } from '$lib/gen'
import CenteredModal from '$lib/components/CenteredModal.svelte'
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

Some files were not shown because too many files have changed in this diff Show More