mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 08:00:59 +00:00
feat: configurable languages and orders
This commit is contained in:
@@ -1698,6 +1698,43 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/default_scripts:
|
||||
post:
|
||||
summary: edit default scripts for workspace
|
||||
operationId: editDefaultScripts
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
description: Workspace default app
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkspaceDefaultScripts"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
get:
|
||||
summary: get default scripts for workspace
|
||||
operationId: get default scripts
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/WorkspaceDefaultScripts"
|
||||
|
||||
/w/{workspace}/workspaces/encryption_key:
|
||||
get:
|
||||
summary: retrieves the encryption key for this workspace
|
||||
@@ -9834,6 +9871,24 @@ components:
|
||||
items:
|
||||
$ref: "#/components/schemas/GitRepositorySettings"
|
||||
|
||||
WorkspaceDefaultScripts:
|
||||
type: object
|
||||
properties:
|
||||
order:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
hidden:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
default_script_content:
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
||||
|
||||
|
||||
|
||||
GitRepositorySettings:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -100,6 +100,10 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/edit_git_sync_config", post(edit_git_sync_config))
|
||||
.route("/edit_default_app", post(edit_default_app))
|
||||
.route("/default_app", get(get_default_app))
|
||||
.route(
|
||||
"/default_scripts",
|
||||
post(edit_default_scripts).get(get_default_scripts),
|
||||
)
|
||||
.route(
|
||||
"/encryption_key",
|
||||
get(get_encryption_key).post(set_encryption_key),
|
||||
@@ -164,6 +168,7 @@ pub struct WorkspaceSettings {
|
||||
pub git_sync: Option<serde_json::Value>, // effectively: WorkspaceGitSyncSettings
|
||||
pub default_app: Option<String>,
|
||||
pub automatic_billing: bool,
|
||||
pub default_scripts: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -984,6 +989,66 @@ async fn edit_default_app(
|
||||
));
|
||||
}
|
||||
|
||||
async fn edit_default_scripts(
|
||||
authed: ApiAuthed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
ApiAuthed { is_admin, username, .. }: ApiAuthed,
|
||||
Json(new_config): Json<Option<serde_json::Value>>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
|
||||
audit_log(
|
||||
&mut *tx,
|
||||
&authed.username,
|
||||
"workspaces.edit_default_scripts",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(config) = new_config {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET default_scripts = $1 WHERE workspace_id = $2",
|
||||
config,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
} else {
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET default_scripts = NULL WHERE workspace_id = $1",
|
||||
&w_id,
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
}
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit default scripts for workspace {}", &w_id))
|
||||
}
|
||||
|
||||
async fn get_default_scripts(
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
) -> JsonResult<Option<serde_json::Value>> {
|
||||
let mut tx = db.begin().await?;
|
||||
let default_scripts = sqlx::query_scalar!(
|
||||
"SELECT default_scripts FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_optional(&mut *tx)
|
||||
.await
|
||||
.map_err(|err| Error::InternalErr(format!("getting default_app: {err}")))?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Json(default_scripts.flatten()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "enterprise")]
|
||||
async fn edit_default_app(
|
||||
authed: ApiAuthed,
|
||||
@@ -1012,7 +1077,7 @@ async fn edit_default_app(
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some([("args_for_audit", args_for_audit.as_str())].into()),
|
||||
Some([("default_app", args_for_audit.as_str())].into()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import { userStore } from '$lib/stores'
|
||||
import { SettingsIcon } from 'lucide-svelte'
|
||||
import { Button } from './common'
|
||||
import Drawer from './common/drawer/Drawer.svelte'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import DefaultScriptsInner from './DefaultScriptsInner.svelte'
|
||||
import Portal from 'svelte-portal'
|
||||
|
||||
let drawer: Drawer
|
||||
</script>
|
||||
|
||||
{#if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
<Portal>
|
||||
<Drawer bind:this={drawer} placement="left">
|
||||
<DrawerContent title="Edit Default Scripts" on:close={drawer.closeDrawer}>
|
||||
<DefaultScriptsInner />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</Portal>
|
||||
<Button
|
||||
on:click={drawer?.openDrawer}
|
||||
startIcon={{ icon: SettingsIcon }}
|
||||
color="light"
|
||||
size="xs2"
|
||||
variant="contained">defaults</Button
|
||||
>
|
||||
{/if}
|
||||
@@ -0,0 +1,75 @@
|
||||
<script lang="ts">
|
||||
import { WorkspaceService, type WorkspaceDefaultScripts } from '$lib/gen'
|
||||
import { defaultScripts, workspaceStore } from '$lib/stores'
|
||||
import { flip } from 'svelte/animate'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { defaultScriptLanguages } from '$lib/scripts'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
|
||||
$: langs = computeLangs($defaultScripts)
|
||||
|
||||
function computeLangs(defaultScripts: WorkspaceDefaultScripts | undefined) {
|
||||
const allLangs = Object.keys(defaultScriptLanguages)
|
||||
if (!defaultScripts || defaultScripts.order == undefined) return allLangs
|
||||
return defaultScripts.order?.concat(allLangs.filter((l) => !defaultScripts.order?.includes(l)))
|
||||
}
|
||||
|
||||
async function changePosition(i: number, up: boolean) {
|
||||
let norder = langs
|
||||
if (up) {
|
||||
;[norder[i], norder[i - 1]] = [norder[i - 1], norder[i]]
|
||||
} else {
|
||||
;[norder[i], norder[i + 1]] = [norder[i + 1], norder[i]]
|
||||
}
|
||||
defaultScripts.update((s) => ({ ...s, order: norder }))
|
||||
await WorkspaceService.editDefaultScripts({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: $defaultScripts
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<Alert title="Global to workspace" type="info" class="mb-4">
|
||||
This setting is only available to admins and will affect all users in the workspace.
|
||||
</Alert>
|
||||
<div class="h-full w-full flex-col gap-2 flex">
|
||||
{#each langs as lang, i (lang)}
|
||||
<div
|
||||
animate:flip={{ duration: 300 }}
|
||||
class="w-full p-2 rounded border border-seconadry grid grid-cols-3"
|
||||
><h3>{lang}</h3>
|
||||
<div>
|
||||
{#if i > 0}
|
||||
<button on:click={() => changePosition(i ?? 0, true)} class="text-lg mr-2">
|
||||
↑</button
|
||||
>
|
||||
{/if}
|
||||
{#if i < langs.length - 1}
|
||||
<button on:click={() => changePosition(i ?? 0, false)} class="text-lg mr-2"
|
||||
>↓</button
|
||||
>
|
||||
{/if}</div
|
||||
>
|
||||
<!-- <Toggle options={{ right: 'custom default' }} size="xs" /> -->
|
||||
<div class="flex justify-end">
|
||||
<Toggle
|
||||
options={{ right: 'hide' }}
|
||||
size="xs"
|
||||
color="red"
|
||||
checked={$defaultScripts?.hidden?.includes(lang)}
|
||||
on:change={(e) => {
|
||||
let toggled = e.detail
|
||||
if (toggled) {
|
||||
defaultScripts.update((s) => ({ ...(s ?? {}), hidden: [...(s?.hidden ?? []), lang] }))
|
||||
} else {
|
||||
defaultScripts.update((s) => ({
|
||||
...(s ?? {}),
|
||||
hidden: (s?.hidden ?? []).filter((h) => h != lang)
|
||||
}))
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
export let simpleTooltip: string | undefined = undefined
|
||||
</script>
|
||||
|
||||
<div class="inline-flex flex-row items-center truncated">
|
||||
<div class="inline-flex flex-row items-baseline truncated">
|
||||
<span class={twMerge(disabled ? 'text-tertiary' : '', 'font-semibold', labelClass)}>
|
||||
{#if prettify}
|
||||
{label.replace(/_/g, ' ').split(' ').map(capitalize).join(' ')}
|
||||
@@ -43,10 +43,10 @@
|
||||
{/if}
|
||||
|
||||
{#if !emptyString(simpleTooltip)}
|
||||
<Tooltip class="ml-2">
|
||||
<span class="text-xs">
|
||||
{simpleTooltip}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
<Tooltip class="ml-2">
|
||||
<span class="text-xs">
|
||||
{simpleTooltip}
|
||||
</span>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { page } from '$app/stores'
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { defaultScripts, enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
cleanValueProperties,
|
||||
emptySchema,
|
||||
@@ -43,7 +43,6 @@
|
||||
Settings,
|
||||
X
|
||||
} from 'lucide-svelte'
|
||||
import { SCRIPT_SHOW_BASH, SCRIPT_SHOW_GO } from '$lib/consts'
|
||||
import UnsavedConfirmationModal from './common/confirmationModal/UnsavedConfirmationModal.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
@@ -61,11 +60,12 @@
|
||||
import MetadataGen from './copilot/MetadataGen.svelte'
|
||||
import ScriptSchedules from './ScriptSchedules.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { type ScriptSchedule, loadScriptSchedule } from '$lib/scripts'
|
||||
import { type ScriptSchedule, loadScriptSchedule, defaultScriptLanguages } from '$lib/scripts'
|
||||
import DefaultScripts from './DefaultScripts.svelte'
|
||||
|
||||
export let script: NewScript
|
||||
export let initialPath: string = ''
|
||||
export let template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' = 'script'
|
||||
export let template: 'docker' | 'script' = 'script'
|
||||
export let initialArgs: Record<string, any> = {}
|
||||
export let lockedLanguage = false
|
||||
export let showMeta: boolean = false
|
||||
@@ -107,25 +107,11 @@
|
||||
editor?.setCode(code)
|
||||
}
|
||||
|
||||
const langs: [string, SupportedLanguage][] = [
|
||||
['TypeScript (Bun)', Script.language.BUN],
|
||||
['Python', Script.language.PYTHON3],
|
||||
['TypeScript (Deno)', Script.language.DENO]
|
||||
]
|
||||
if (SCRIPT_SHOW_BASH) {
|
||||
langs.push(['Bash', Script.language.BASH])
|
||||
}
|
||||
if (SCRIPT_SHOW_GO) {
|
||||
langs.push(['Go', Script.language.GO])
|
||||
}
|
||||
langs.push(['REST', Script.language.NATIVETS])
|
||||
langs.push(['PostgreSQL', Script.language.POSTGRESQL])
|
||||
langs.push(['MySQL', Script.language.MYSQL])
|
||||
langs.push(['BigQuery', Script.language.BIGQUERY])
|
||||
langs.push(['Snowflake', Script.language.SNOWFLAKE])
|
||||
langs.push(['MS SQL Server', Script.language.MSSQL])
|
||||
langs.push(['GraphQL', Script.language.GRAPHQL])
|
||||
langs.push(['PowerShell', Script.language.POWERSHELL])
|
||||
$: langs = ($defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
|
||||
.map((l) => [defaultScriptLanguages[l], l])
|
||||
.filter(
|
||||
(x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1])
|
||||
) as [string, SupportedLanguage | 'docker'][]
|
||||
|
||||
const scriptKindOptions: {
|
||||
value: Script.kind
|
||||
@@ -551,14 +537,17 @@
|
||||
</Section>
|
||||
|
||||
<Section label="Language">
|
||||
<svelte:fragment slot="action"><DefaultScripts /></svelte:fragment>
|
||||
{#if lockedLanguage}
|
||||
<div class="text-sm text-tertiary italic mb-2">
|
||||
As a forked script, the language '{script.language}' cannot be modified.
|
||||
</div>
|
||||
{/if}
|
||||
<div class=" grid grid-cols-3 gap-2">
|
||||
{#each langs as [label, lang]}
|
||||
{@const isPicked = script.language == lang && template == 'script'}
|
||||
{#each langs as [label, lang] (lang)}
|
||||
{@const isPicked =
|
||||
(lang == script.language && template == 'script') ||
|
||||
(template == 'docker' && lang == 'docker')}
|
||||
<Popover
|
||||
disablePopup={!enterpriseLangs.includes(lang) || !!$enterpriseLicense}
|
||||
>
|
||||
@@ -570,9 +559,33 @@
|
||||
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
|
||||
: 'm-[1px]'}
|
||||
on:click={() => {
|
||||
template = 'script'
|
||||
initContent(lang, script.kind, template)
|
||||
script.language = lang
|
||||
if (lang == 'docker') {
|
||||
if (isCloudHosted()) {
|
||||
sendUserToast(
|
||||
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'Learn more',
|
||||
callback: () => {
|
||||
window.open(
|
||||
'https://www.windmill.dev/docs/advanced/docker',
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
template = 'docker'
|
||||
} else {
|
||||
template = 'script'
|
||||
}
|
||||
let language = lang == 'docker' ? Script.language.BASH : lang
|
||||
//
|
||||
initContent(language, script.kind, template)
|
||||
script.language = language
|
||||
}}
|
||||
disabled={lockedLanguage ||
|
||||
(enterpriseLangs.includes(lang) && !$enterpriseLicense)}
|
||||
@@ -585,40 +598,6 @@
|
||||
>
|
||||
</Popover>
|
||||
{/each}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="border"
|
||||
color={template == 'docker' ? 'blue' : 'light'}
|
||||
btnClasses={template == 'docker'
|
||||
? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
|
||||
: 'm-[1px]'}
|
||||
disabled={lockedLanguage}
|
||||
on:click={() => {
|
||||
if (isCloudHosted()) {
|
||||
sendUserToast(
|
||||
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'Learn more',
|
||||
callback: () => {
|
||||
window.open(
|
||||
'https://www.windmill.dev/docs/advanced/docker',
|
||||
'_blank'
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
template = 'docker'
|
||||
initContent(Script.language.BASH, script.kind, template)
|
||||
script.language = Script.language.BASH
|
||||
}}
|
||||
>
|
||||
<LanguageIcon lang="docker" /><span class="ml-2 py-2">Docker</span>
|
||||
</Button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
minute: '2-digit'
|
||||
})}`
|
||||
} else {
|
||||
return !withDate ? displayDate(dateString) : ''
|
||||
return !withDate ? displayDate(dateString, false, withDate) : ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+12
-18
@@ -8,7 +8,7 @@
|
||||
import { inferArgs } from '$lib/infer'
|
||||
import { initialCode } from '$lib/script_helpers'
|
||||
import { emptySchema } from '$lib/utils'
|
||||
import { getScriptByPath } from '$lib/scripts'
|
||||
import { defaultScriptLanguages, getScriptByPath } from '$lib/scripts'
|
||||
|
||||
import { Building, GitFork, Globe2 } from 'lucide-svelte'
|
||||
import { createEventDispatcher, getContext } from 'svelte'
|
||||
@@ -18,6 +18,8 @@
|
||||
import InlineScriptList from '../settingsPanel/mainInput/InlineScriptList.svelte'
|
||||
import WorkspaceScriptList from '../settingsPanel/mainInput/WorkspaceScriptList.svelte'
|
||||
import RunnableSelector from '../settingsPanel/mainInput/RunnableSelector.svelte'
|
||||
import { defaultScripts } from '$lib/stores'
|
||||
import DefaultScripts from '$lib/components/DefaultScripts.svelte'
|
||||
|
||||
export let name: string
|
||||
export let componentType: string | undefined = undefined
|
||||
@@ -92,21 +94,13 @@
|
||||
dispatch('new', unusedInlineScript.inlineScript)
|
||||
}
|
||||
|
||||
const langs = [
|
||||
['bun', 'TypeScript (Bun)'],
|
||||
['python3', 'Python'],
|
||||
['deno', 'TypeScript (Deno)'],
|
||||
['go', 'Go'],
|
||||
['bash', 'Bash'],
|
||||
['powershell', 'PowerShell'],
|
||||
['nativets', 'REST'],
|
||||
['postgresql', 'PostgreSQL'],
|
||||
['mysql', 'MySQL'],
|
||||
['bigquery', 'BigQuery'],
|
||||
['snowflake', 'Snowflake'],
|
||||
['mssql', 'MS SQL Server'],
|
||||
['graphql', 'GraphQL']
|
||||
] as [Script.language, string][]
|
||||
$: langs = ($defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
|
||||
.map((l) => [defaultScriptLanguages[l], l])
|
||||
.filter(
|
||||
(x) =>
|
||||
x[1] != 'docker' &&
|
||||
($defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1]))
|
||||
) as [string, Preview.language][]
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={picker} size="1000px">
|
||||
@@ -192,10 +186,10 @@
|
||||
|
||||
<div class="flex flex-row w-full gap-8">
|
||||
<div id="app-editor-backend-runnables">
|
||||
<div class="mb-1 text-sm font-semibold">Backend</div>
|
||||
<div class="mb-1 text-sm font-semibold flex gap-4">Backend <DefaultScripts /> </div>
|
||||
|
||||
<div class="flex flex-row flex-wrap gap-2">
|
||||
{#each langs as [lang, label]}
|
||||
{#each langs as [label, lang] (lang)}
|
||||
<FlowScriptPicker
|
||||
{label}
|
||||
{lang}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Alert } from '$lib/components/common'
|
||||
import ToggleHubWorkspace from '$lib/components/ToggleHubWorkspace.svelte'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import { RawScript, Script } from '$lib/gen'
|
||||
import { Script } from '$lib/gen'
|
||||
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import FlowScriptPicker from '../pickers/FlowScriptPicker.svelte'
|
||||
@@ -14,6 +14,10 @@
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { Check, Code, Zap } from 'lucide-svelte'
|
||||
import SuspendDrawer from './SuspendDrawer.svelte'
|
||||
import { defaultScripts } from '$lib/stores'
|
||||
import { defaultScriptLanguages } from '$lib/scripts'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import DefaultScripts from '$lib/components/DefaultScripts.svelte'
|
||||
|
||||
export let failureModule: boolean
|
||||
export let shouldDisableTriggerScripts: boolean = false
|
||||
@@ -30,6 +34,30 @@
|
||||
: 'script'
|
||||
let pick_existing: 'workspace' | 'hub' = 'hub'
|
||||
let filter = ''
|
||||
|
||||
$: langs = ($defaultScripts?.order ?? Object.keys(defaultScriptLanguages))
|
||||
.map((l) => [defaultScriptLanguages[l], l])
|
||||
.filter(
|
||||
(x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1])
|
||||
) as [string, SupportedLanguage | 'docker'][]
|
||||
|
||||
function displayLang(lang: SupportedLanguage | 'docker', kind: string) {
|
||||
if (
|
||||
lang == Script.language.BUN ||
|
||||
lang == Script.language.PYTHON3 ||
|
||||
lang == Script.language.DENO
|
||||
) {
|
||||
return true
|
||||
}
|
||||
if (lang == Script.language.GO) {
|
||||
return kind == 'script' || kind == 'trigger' || failureModule
|
||||
}
|
||||
|
||||
if (lang == Script.language.BASH || lang == Script.language.NATIVETS) {
|
||||
return kind == 'script'
|
||||
}
|
||||
return kind == 'script' && !failureModule
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="p-4 h-full flex flex-col" id="flow-editor-flow-inputs">
|
||||
@@ -136,21 +164,24 @@
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
<h3 class="pb-2 pt-4">
|
||||
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
|
||||
<Tooltip
|
||||
documentationLink={kind === 'script'
|
||||
? 'https://www.windmill.dev/docs/flows/editor_components#flow-actions'
|
||||
: kind === 'trigger'
|
||||
? 'https://www.windmill.dev/docs/flows/flow_trigger'
|
||||
: kind === 'approval'
|
||||
? 'https://www.windmill.dev/docs/flows/flow_approval'
|
||||
: 'https://www.windmill.dev/docs/getting_started/flows_quickstart#flow-editor'}
|
||||
>
|
||||
Embed <span>{kind == 'script' ? 'action' : kind}</span> script directly inside a flow instead
|
||||
of saving the script into your workspace for reuse. You can always save an inline script to your
|
||||
workspace later.
|
||||
</Tooltip>
|
||||
<h3 class="pb-2 pt-4 flex gap-x-8 flex-wrap">
|
||||
<div>
|
||||
Inline new <span class="text-blue-500">{kind == 'script' ? 'action' : kind}</span> script
|
||||
<Tooltip
|
||||
documentationLink={kind === 'script'
|
||||
? 'https://www.windmill.dev/docs/flows/editor_components#flow-actions'
|
||||
: kind === 'trigger'
|
||||
? 'https://www.windmill.dev/docs/flows/flow_trigger'
|
||||
: kind === 'approval'
|
||||
? 'https://www.windmill.dev/docs/flows/flow_approval'
|
||||
: 'https://www.windmill.dev/docs/getting_started/flows_quickstart#flow-editor'}
|
||||
>
|
||||
Embed <span>{kind == 'script' ? 'action' : kind}</span> script directly inside a flow instead
|
||||
of saving the script into your workspace for reuse. You can always save an inline script to
|
||||
your workspace later.
|
||||
</Tooltip>
|
||||
</div>
|
||||
<DefaultScripts />
|
||||
</h3>
|
||||
{#if noEditor}
|
||||
<div
|
||||
@@ -165,226 +196,41 @@
|
||||
{/if}
|
||||
<div class="flex flex-row">
|
||||
<div class="flex flex-row flex-wrap gap-2" id="flow-editor-action-script">
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="TypeScript (Bun)"
|
||||
lang={Script.language.BUN}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.BUN,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="Python"
|
||||
lang={Script.language.PYTHON3}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.PYTHON3,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="TypeScript (Deno)"
|
||||
lang={Script.language.DENO}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.DENO,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if kind != 'approval'}
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="Go"
|
||||
lang={Script.language.GO}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.GO,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if kind == 'script'}
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="Bash"
|
||||
lang={Script.language.BASH}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.BASH,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="REST"
|
||||
lang={Script.language.NATIVETS}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.NATIVETS,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
{#if !failureModule}
|
||||
{#each langs as [label, lang] (lang)}
|
||||
{#if displayLang(lang, kind)}
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="PostgreSQL"
|
||||
lang={Script.language.POSTGRESQL}
|
||||
{label}
|
||||
lang={lang == 'docker' ? Script.language.BASH : lang}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.POSTGRESQL,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="MySQL"
|
||||
lang={Script.language.MYSQL}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.MYSQL,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="BigQuery"
|
||||
lang={Script.language.BIGQUERY}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.BIGQUERY,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="Snowflake"
|
||||
lang={Script.language.SNOWFLAKE}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.SNOWFLAKE,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="MS SQL Server"
|
||||
lang={Script.language.MSSQL}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.MSSQL,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="GraphQL"
|
||||
lang={Script.language.GRAPHQL}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.GRAPHQL,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label={`Docker`}
|
||||
lang="docker"
|
||||
on:click={() => {
|
||||
if (isCloudHosted()) {
|
||||
sendUserToast(
|
||||
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'Learn more',
|
||||
callback: () => {
|
||||
window.open('https://www.windmill.dev/docs/advanced/docker', '_blank')
|
||||
if (lang == 'docker') {
|
||||
if (isCloudHosted()) {
|
||||
sendUserToast(
|
||||
'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
|
||||
true,
|
||||
[
|
||||
{
|
||||
label: 'Learn more',
|
||||
callback: () => {
|
||||
window.open('https://www.windmill.dev/docs/advanced/docker', '_blank')
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
)
|
||||
return
|
||||
]
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
console.log(lang, kind)
|
||||
dispatch('new', {
|
||||
language: RawScript.language.BASH,
|
||||
language: lang == 'docker' ? Script.language.BASH : lang,
|
||||
kind,
|
||||
subkind: 'docker',
|
||||
subkind: lang == 'docker' ? 'docker' : 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<FlowScriptPicker
|
||||
disabled={noEditor && (summary == undefined || summary == '')}
|
||||
label="PowerShell"
|
||||
lang={Script.language.POWERSHELL}
|
||||
on:click={() => {
|
||||
dispatch('new', {
|
||||
language: RawScript.language.POWERSHELL,
|
||||
kind,
|
||||
subkind: 'flow',
|
||||
summary
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<!-- <FlowScriptPicker
|
||||
label={`MySQL`}
|
||||
lang="mysql"
|
||||
on:click={() =>
|
||||
dispatch('new', { language: RawScript.language.DENO, kind, subkind: 'mysql' })}
|
||||
/> -->
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,15 +7,7 @@
|
||||
|
||||
export let disabled: boolean = false
|
||||
export let label: string
|
||||
export let lang:
|
||||
| SupportedLanguage
|
||||
| 'pgsql'
|
||||
| 'mysql'
|
||||
| 'javascript'
|
||||
| 'fetch'
|
||||
| 'docker'
|
||||
| 'powershell'
|
||||
| undefined = undefined
|
||||
export let lang: SupportedLanguage | 'docker' | 'javascript' | undefined = undefined
|
||||
|
||||
export let id: string | undefined = undefined
|
||||
|
||||
|
||||
@@ -30,16 +30,6 @@
|
||||
export let containerWidth: number = 0
|
||||
|
||||
let scheduleEditor: ScheduleEditor
|
||||
|
||||
function endedDate(started_at: string, duration_ms: number): string {
|
||||
const started = new Date(started_at)
|
||||
started.setMilliseconds(started.getMilliseconds() + duration_ms)
|
||||
return `${started.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
})}`
|
||||
}
|
||||
</script>
|
||||
|
||||
<Portal>
|
||||
@@ -93,16 +83,11 @@
|
||||
<div class="flex flex-row items-center gap-1 text-gray-500 dark:text-gray-300 text-2xs">
|
||||
{#if job}
|
||||
{#if 'started_at' in job && job.started_at}
|
||||
{#if job?.['duration_ms']}
|
||||
Ended {endedDate(job.started_at, job?.['duration_ms'])}
|
||||
{#if job && 'duration_ms' in job && job.duration_ms != undefined}
|
||||
(Ran in {msToSec(job.duration_ms)}s)
|
||||
{/if}
|
||||
{:else}
|
||||
<div>
|
||||
Started
|
||||
<TimeAgo date={job.started_at ?? ''} />
|
||||
</div>
|
||||
Started <TimeAgo date={job.started_at ?? ''} />
|
||||
{#if job && 'duration_ms' in job && job.duration_ms != undefined}
|
||||
(Ran in {msToSec(
|
||||
job.duration_ms
|
||||
)}s{#if job.job_kind == 'flow' || job.job_kind == 'flowpreview'} total{/if})
|
||||
{/if}
|
||||
{:else if `scheduled_for` in job && job.scheduled_for && forLater(job.scheduled_for)}
|
||||
Scheduled for {displayDate(job.scheduled_for)}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
const field: string | undefined = getTime(job)
|
||||
if (field) {
|
||||
const date = new Date(field)
|
||||
date.setMilliseconds(date.getMilliseconds() + (job['duration_ms'] ?? 0))
|
||||
date.setMilliseconds(date.getMilliseconds())
|
||||
|
||||
const day = date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
|
||||
@@ -11,10 +11,6 @@ export const HOME_SEARCH_PLACEHOLDER = 'Search Scripts, Flows & Apps'
|
||||
|
||||
export const SIDEBAR_SHOW_SCHEDULES = true
|
||||
|
||||
export const SCRIPT_SHOW_PSQL = true
|
||||
export const SCRIPT_SHOW_GO = true
|
||||
export const SCRIPT_SHOW_BASH = true
|
||||
|
||||
export const WORKSPACE_SHOW_SLACK_CMD = true
|
||||
export const WORKSPACE_SHOW_WEBHOOK_CLI_SYNC = true
|
||||
|
||||
|
||||
@@ -340,6 +340,7 @@ export function initialCode(
|
||||
kind: Script.kind | undefined,
|
||||
subkind: 'pgsql' | 'mysql' | 'flow' | 'script' | 'fetch' | 'docker' | 'powershell' | undefined
|
||||
): string {
|
||||
console.log(language, kind, subkind)
|
||||
if (!kind) {
|
||||
kind = Script.kind.SCRIPT
|
||||
}
|
||||
|
||||
@@ -87,6 +87,23 @@ export function scriptPathToHref(path: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
export const defaultScriptLanguages = Object.fromEntries([
|
||||
[Script.language.BUN, 'TypeScript (Bun)'],
|
||||
[Script.language.PYTHON3, 'Python'],
|
||||
[Script.language.DENO, 'TypeScript (Deno)'],
|
||||
[Script.language.BASH, 'Bash'],
|
||||
[Script.language.GO, 'Go'],
|
||||
[Script.language.NATIVETS, 'REST'],
|
||||
[Script.language.POSTGRESQL, 'PostgreSQL'],
|
||||
[Script.language.MYSQL, 'MySQL'],
|
||||
[Script.language.BIGQUERY, 'BigQuery'],
|
||||
[Script.language.SNOWFLAKE, 'Snowflake'],
|
||||
[Script.language.MSSQL, 'MS SQL Server'],
|
||||
[Script.language.GRAPHQL, 'GraphQL'],
|
||||
[Script.language.POWERSHELL, 'PowerShell'],
|
||||
['docker', 'Docker']
|
||||
])
|
||||
|
||||
export async function getScriptByPath(path: string): Promise<{
|
||||
content: string
|
||||
language: SupportedLanguage
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BROWSER } from 'esm-env'
|
||||
import { derived, type Readable, writable } from 'svelte/store'
|
||||
import type { UserWorkspaceList } from '$lib/gen/models/UserWorkspaceList.js'
|
||||
import type { TokenResponse } from './gen'
|
||||
import { type WorkspaceDefaultScripts, type TokenResponse } from './gen'
|
||||
import type { IntrospectionQuery } from 'graphql'
|
||||
|
||||
export interface UserExt {
|
||||
@@ -39,6 +39,7 @@ export const userStore = writable<UserExt | undefined>(undefined)
|
||||
export const workspaceStore = writable<string | undefined>(
|
||||
persistedWorkspace ? String(persistedWorkspace) : undefined
|
||||
)
|
||||
export const defaultScripts = writable<WorkspaceDefaultScripts | undefined>(undefined)
|
||||
export const dbClockDrift = writable<number | undefined>(undefined)
|
||||
export const isPremiumStore = writable<boolean>(false)
|
||||
export const starStore = writable(1)
|
||||
|
||||
@@ -36,7 +36,11 @@ export function parseQueryParams(url: string | undefined) {
|
||||
return params
|
||||
}
|
||||
|
||||
export function displayDate(dateString: string | Date | undefined, displaySecond = false): string {
|
||||
export function displayDate(
|
||||
dateString: string | Date | undefined,
|
||||
displaySecond = false,
|
||||
displayDate = true
|
||||
): string {
|
||||
const date = new Date(dateString ?? '')
|
||||
if (date.toString() === 'Invalid Date') {
|
||||
return ''
|
||||
@@ -45,7 +49,7 @@ export function displayDate(dateString: string | Date | undefined, displaySecond
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: displaySecond ? '2-digit' : undefined
|
||||
})} ${date.getDate()}/${date.getMonth() + 1}`
|
||||
})}${displayDate ? ` ${date.getDate()}/${date.getMonth() + 1}` : ''}`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
usageStore,
|
||||
userStore,
|
||||
workspaceStore,
|
||||
type UserExt
|
||||
type UserExt,
|
||||
defaultScripts
|
||||
} from '$lib/stores'
|
||||
import CenteredModal from '$lib/components/CenteredModal.svelte'
|
||||
import { afterNavigate, beforeNavigate, goto } from '$app/navigation'
|
||||
@@ -186,22 +187,32 @@
|
||||
|
||||
let devOnly = $page.url.pathname.startsWith('/scripts/dev')
|
||||
|
||||
workspaceStore.subscribe(async (value) => {
|
||||
if (value) {
|
||||
workspacedOpenai.init(value)
|
||||
try {
|
||||
copilotInfo.set(await WorkspaceService.getCopilotInfo({ workspace: value }))
|
||||
} catch (err) {
|
||||
copilotInfo.set({
|
||||
exists_openai_resource_path: false,
|
||||
code_completion_enabled: false
|
||||
})
|
||||
console.error('Could not get copilot info')
|
||||
}
|
||||
async function loadCopilot(workspace: string) {
|
||||
workspacedOpenai.init(workspace)
|
||||
try {
|
||||
copilotInfo.set(await WorkspaceService.getCopilotInfo({ workspace }))
|
||||
} catch (err) {
|
||||
copilotInfo.set({
|
||||
exists_openai_resource_path: false,
|
||||
code_completion_enabled: false
|
||||
})
|
||||
console.error('Could not get copilot info')
|
||||
}
|
||||
}
|
||||
|
||||
workspaceStore.subscribe(async (workspace) => {
|
||||
if (workspace) {
|
||||
loadCopilot(workspace)
|
||||
}
|
||||
})
|
||||
$: onUserStore($userStore)
|
||||
|
||||
$: onUserStore($userStore)
|
||||
$: $workspaceStore && $userStore && loadDefaultScripts($workspaceStore, $userStore)
|
||||
async function loadDefaultScripts(workspace: string, user: UserExt | undefined) {
|
||||
if (!user?.operator) {
|
||||
$defaultScripts = await WorkspaceService.getDefaultScripts({ workspace })
|
||||
}
|
||||
}
|
||||
let timeout: NodeJS.Timeout | undefined
|
||||
async function onUserStore(u: UserExt | undefined) {
|
||||
if (u && timeout) {
|
||||
|
||||
Reference in New Issue
Block a user