feat: dev/staging/prod and deploy from web (#1733)
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
ALTER TABLE workspace_settings DROP COLUMN deploy_to;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
ALTER TABLE workspace_settings ADD COLUMN deploy_to VARCHAR(255);
|
||||
@@ -677,6 +677,11 @@
|
||||
"name": "webhook",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "deploy_to",
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -689,6 +694,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
@@ -2369,6 +2375,11 @@
|
||||
"name": "webhook",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "deploy_to",
|
||||
"ordinal": 10,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
@@ -2381,6 +2392,7 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
@@ -6950,6 +6962,26 @@
|
||||
},
|
||||
"query": "SELECT distinct(path) FROM script WHERE workspace_id = $1"
|
||||
},
|
||||
"f2f42aded6f1a400c84e2575abb551dfcd9260714eef333a85b1e5d7d5c9f67e": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "deploy_to",
|
||||
"ordinal": 0,
|
||||
"type_info": "Varchar"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "SELECT deploy_to FROM workspace_settings WHERE workspace_id = $1"
|
||||
},
|
||||
"f325a1262084bd3468e12dc8bcc289a96536f172b679af54dd0fbc82d4d7c987": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@@ -7275,5 +7307,18 @@
|
||||
}
|
||||
},
|
||||
"query": "UPDATE variable SET value = $1 WHERE workspace_id = $2 AND path = $3"
|
||||
},
|
||||
"ff89e9f0941507d5cbfbb27e7c3b1ebad6b2b0a836334a5e20f151711fc11370": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Varchar",
|
||||
"Text"
|
||||
]
|
||||
}
|
||||
},
|
||||
"query": "UPDATE workspace_settings SET deploy_to = $1 WHERE workspace_id = $2"
|
||||
}
|
||||
}
|
||||
@@ -935,6 +935,28 @@ paths:
|
||||
type: string
|
||||
webhook:
|
||||
type: string
|
||||
deploy_to:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/get_deploy_to:
|
||||
get:
|
||||
summary: get deploy to
|
||||
operationId: getDeployTo
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deploy_to:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/premium_info:
|
||||
get:
|
||||
@@ -987,6 +1009,32 @@ paths:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/edit_deploy_to:
|
||||
post:
|
||||
summary: edit deploy to
|
||||
operationId: editDeployTo
|
||||
tags:
|
||||
- workspace
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/WorkspaceId"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deploy_to:
|
||||
type: string
|
||||
|
||||
responses:
|
||||
"200":
|
||||
description: status
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
/w/{workspace}/workspaces/edit_auto_invite:
|
||||
post:
|
||||
summary: edit auto invite
|
||||
|
||||
@@ -59,9 +59,11 @@ pub fn workspaced_service() -> Router {
|
||||
.route("/add_user", post(add_user))
|
||||
.route("/delete_invite", post(delete_invite))
|
||||
.route("/get_settings", get(get_settings))
|
||||
.route("/get_deploy_to", get(get_deploy_to))
|
||||
.route("/edit_slack_command", post(edit_slack_command))
|
||||
.route("/edit_webhook", post(edit_webhook))
|
||||
.route("/edit_auto_invite", post(edit_auto_invite))
|
||||
.route("/edit_deploy_to", post(edit_deploy_to))
|
||||
.route("/tarball", get(tarball_workspace))
|
||||
.route("/premium_info", get(premium_info));
|
||||
|
||||
@@ -109,6 +111,7 @@ pub struct WorkspaceSettings {
|
||||
pub customer_id: Option<String>,
|
||||
pub plan: Option<String>,
|
||||
pub webhook: Option<String>,
|
||||
pub deploy_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize, Debug)]
|
||||
@@ -131,6 +134,11 @@ struct EditCommandScript {
|
||||
slack_command_script: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditDeployTo {
|
||||
deploy_to: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct EditAutoInvite {
|
||||
operator: Option<bool>,
|
||||
@@ -407,6 +415,29 @@ async fn get_settings(
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct DeployTo {
|
||||
deploy_to: Option<String>,
|
||||
}
|
||||
async fn get_deploy_to(
|
||||
authed: Authed,
|
||||
Path(w_id): Path<String>,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
) -> JsonResult<DeployTo> {
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
let settings = sqlx::query_as!(
|
||||
DeployTo,
|
||||
"SELECT deploy_to FROM workspace_settings WHERE workspace_id = $1",
|
||||
&w_id
|
||||
)
|
||||
.fetch_one(&mut tx)
|
||||
.await
|
||||
.map_err(|e| Error::InternalErr(format!("getting deploy_to: {e}")))?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(Json(settings))
|
||||
}
|
||||
|
||||
async fn edit_slack_command(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
@@ -447,6 +478,51 @@ async fn edit_slack_command(
|
||||
Ok(format!("Edit command script {}", &w_id))
|
||||
}
|
||||
|
||||
async fn edit_deploy_to(
|
||||
authed: Authed,
|
||||
Extension(db): Extension<DB>,
|
||||
Path(w_id): Path<String>,
|
||||
Authed { is_admin, username, .. }: Authed,
|
||||
Json(es): Json<EditDeployTo>,
|
||||
) -> Result<String> {
|
||||
require_admin(is_admin, &username)?;
|
||||
#[cfg(not(feature = "enterprise"))]
|
||||
{
|
||||
return Err(Error::BadRequest(
|
||||
"Deploy to is only available on enterprise".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut tx = db.begin().await?;
|
||||
sqlx::query!(
|
||||
"UPDATE workspace_settings SET deploy_to = $1 WHERE workspace_id = $2",
|
||||
es.deploy_to,
|
||||
&w_id
|
||||
)
|
||||
.execute(&mut tx)
|
||||
.await?;
|
||||
|
||||
audit_log(
|
||||
&mut tx,
|
||||
&authed.username,
|
||||
"workspaces.edit_deploy_to",
|
||||
ActionKind::Update,
|
||||
&w_id,
|
||||
Some(&authed.email),
|
||||
Some(
|
||||
[(
|
||||
"script",
|
||||
es.deploy_to.unwrap_or("NO_DEPLOY_TO".to_string()).as_str(),
|
||||
)]
|
||||
.into(),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(format!("Edit deploy to for {}", &w_id))
|
||||
}
|
||||
|
||||
const BANNED_DOMAINS: &str = include_str!("../banned_domains.txt");
|
||||
|
||||
async fn is_allowed_auto_domain(Authed { email, .. }: Authed) -> JsonResult<bool> {
|
||||
|
||||
@@ -1927,7 +1927,7 @@ pub async fn get_reserved_variables(
|
||||
job.script_path.clone(),
|
||||
job.parent_job.map(|x| x.to_string()),
|
||||
flow_path,
|
||||
job.schedule_path.clone(),
|
||||
job.schedule_path.clone()
|
||||
);
|
||||
Ok(variables
|
||||
.into_iter()
|
||||
|
||||
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"diff": "^5.1.0",
|
||||
"esm-env": "^1.0.0",
|
||||
"fast-equals": "^5.0.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
@@ -2799,6 +2800,14 @@
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
|
||||
"integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
|
||||
"engines": {
|
||||
"node": ">=0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
@@ -10089,6 +10098,11 @@
|
||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||
"dev": true
|
||||
},
|
||||
"diff": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
|
||||
"integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw=="
|
||||
},
|
||||
"dir-glob": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"chartjs-plugin-zoom": "^2.0.0",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"diff": "^5.1.0",
|
||||
"esm-env": "^1.0.0",
|
||||
"fast-equals": "^5.0.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={jsonViewer} size="800px">
|
||||
<DrawerContent title="Argument Details" on:close={jsonViewer.toggleDrawer}>
|
||||
<DrawerContent title="Argument Details" on:close={jsonViewer.closeDrawer}>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
on:click={() => copyToClipboard(JSON.stringify(jsonViewerContent, null, 4))}
|
||||
|
||||
38
frontend/src/lib/components/DeployToSetting.svelte
Normal file
38
frontend/src/lib/components/DeployToSetting.svelte
Normal file
@@ -0,0 +1,38 @@
|
||||
<script lang="ts">
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import { usersWorkspaceStore, workspaceStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
|
||||
export let workspaceToDeployTo: string | undefined
|
||||
|
||||
$: deployableWorkspaces = $usersWorkspaceStore?.workspaces
|
||||
.map((w) => w.id)
|
||||
.filter((w) => w != $workspaceStore)
|
||||
</script>
|
||||
|
||||
<h3 class="mt-8">Workspace to link to</h3>
|
||||
<div class="flex min-w-0 mt-2">
|
||||
<select
|
||||
bind:value={workspaceToDeployTo}
|
||||
on:change={async (e) => {
|
||||
await WorkspaceService.editDeployTo({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { deploy_to: workspaceToDeployTo == '' ? undefined : workspaceToDeployTo }
|
||||
})
|
||||
if (workspaceToDeployTo == '') {
|
||||
workspaceToDeployTo = undefined
|
||||
sendUserToast('Disabled setting deployable workspace')
|
||||
} else {
|
||||
sendUserToast('Set deployable workspace to ' + workspaceToDeployTo)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{#if deployableWorkspaces?.length == 0}
|
||||
<option disabled>No workspace deployable to</option>
|
||||
{/if}
|
||||
<option value="">Disable deployment</option>
|
||||
{#each deployableWorkspaces ?? [] as name}
|
||||
<option value={name}>{name}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
558
frontend/src/lib/components/DeployWorkspace.svelte
Normal file
558
frontend/src/lib/components/DeployWorkspace.svelte
Normal file
@@ -0,0 +1,558 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { enterpriseLicense, superadmin, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
AppService,
|
||||
FlowService,
|
||||
RawAppService,
|
||||
ResourceService,
|
||||
ScheduleService,
|
||||
ScriptService,
|
||||
UserService,
|
||||
VariableService,
|
||||
WorkspaceService
|
||||
} from '$lib/gen'
|
||||
import { getAllModules } from './flows/flowExplorer'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import Alert from './common/alert/Alert.svelte'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import * as Diff from 'diff'
|
||||
import { Drawer, DrawerContent } from './common'
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
type Kind = 'script' | 'resource' | 'schedule' | 'variable' | 'flow' | 'app' | 'raw_app'
|
||||
|
||||
export let kind: Kind
|
||||
export let initialPath: string = ''
|
||||
export let workspaceToDeployTo: string | undefined = undefined
|
||||
export let hideButton: boolean = false
|
||||
|
||||
let seeTarget: boolean | undefined = undefined
|
||||
|
||||
let dependencies: { kind: Kind; path: string; include: boolean }[] | undefined = undefined
|
||||
|
||||
const allAlreadyExists: { [key: string]: boolean } = {}
|
||||
|
||||
let notSet: boolean | undefined = undefined
|
||||
$: WorkspaceService.getDeployTo({ workspace: $workspaceStore! }).then((x) => {
|
||||
workspaceToDeployTo = x.deploy_to
|
||||
if (x.deploy_to == undefined) {
|
||||
notSet = true
|
||||
}
|
||||
})
|
||||
$: workspaceToDeployTo && reload(initialPath)
|
||||
|
||||
async function reload(path: string) {
|
||||
try {
|
||||
if (!$superadmin) {
|
||||
await UserService.whoami({ workspace: workspaceToDeployTo! })
|
||||
}
|
||||
seeTarget = true
|
||||
} catch {
|
||||
seeTarget = false
|
||||
}
|
||||
|
||||
dependencies = (await getDependencies(kind, path)).map((x) => ({
|
||||
...x,
|
||||
include: x.kind != 'variable' && x.kind != 'resource'
|
||||
}))
|
||||
dependencies.forEach((x) => {
|
||||
checkAlreadyExists(x.kind, x.path).then(
|
||||
(y) => (allAlreadyExists[computeStatusPath(x.kind, x.path)] = y)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
async function getDependencies(
|
||||
kind: Kind,
|
||||
path: string
|
||||
): Promise<{ kind: Kind; path: string }[]> {
|
||||
async function rec(kind: Kind, path: string): Promise<{ kind: Kind; path: string }[]> {
|
||||
if (kind == 'schedule') {
|
||||
const schedule = await ScheduleService.getSchedule({ workspace: $workspaceStore!, path })
|
||||
if (schedule.script_path && schedule.script_path != '') {
|
||||
if (schedule.script_path) {
|
||||
return [{ kind: 'script', path: schedule.script_path }]
|
||||
} else {
|
||||
return [{ kind: 'flow', path: schedule.script_path }]
|
||||
}
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
} else if (kind == 'flow') {
|
||||
const flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path })
|
||||
return getAllModules(flow.value.modules, flow.value.failure_module).flatMap((x) => {
|
||||
let result: { kind: Kind; path: string }[] = []
|
||||
if (x.value.type == 'script' || x.value.type == 'rawscript' || x.value.type == 'flow') {
|
||||
Object.values(x.value.input_transforms).forEach((y) => {
|
||||
if (y.type == 'static' && typeof y.value == 'string') {
|
||||
if (y.value.startsWith('$res:')) {
|
||||
result.push({ kind: 'resource', path: y.value.substring(5) })
|
||||
} else if (y.value.startsWith('$var:')) {
|
||||
result.push({ kind: 'variable', path: y.value.substring(5) })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (x.value.type == 'script') {
|
||||
if (x.value.path) {
|
||||
result.push({ kind: 'script', path: x.value.path })
|
||||
}
|
||||
} else if (x.value.type == 'flow') {
|
||||
if (x.value.path) {
|
||||
result.push({ kind: 'flow', path: x.value.path })
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
} else if (kind == 'resource') {
|
||||
const res = await ResourceService.getResource({ workspace: $workspaceStore!, path })
|
||||
function recObj(obj: any) {
|
||||
if (typeof obj == 'string' && obj.startsWith('$var:')) {
|
||||
return [{ kind: 'variable', path: obj.substring(5) }]
|
||||
} else if (typeof obj == 'object') {
|
||||
return Object.values(obj).flatMap((x) => recObj(x))
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
return recObj(res.value)
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
let toProcess = [{ kind, path }]
|
||||
let processed: { kind: Kind; path: string }[] = []
|
||||
while (toProcess.length > 0) {
|
||||
const { kind, path } = toProcess.pop()!
|
||||
console.log('BAR', kind, path)
|
||||
toProcess.push(...(await rec(kind, path)))
|
||||
processed.push({ kind, path })
|
||||
}
|
||||
processed.reverse()
|
||||
return processed
|
||||
}
|
||||
|
||||
async function checkAlreadyExists(kind: Kind, path: string): Promise<boolean> {
|
||||
if (kind == 'flow') {
|
||||
return await FlowService.existsFlowByPath({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'script') {
|
||||
return await ScriptService.existsScriptByPath({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'app') {
|
||||
return await AppService.existsApp({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'raw_app') {
|
||||
return await RawAppService.existsRawApp({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'variable') {
|
||||
return await VariableService.existsVariable({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'resource') {
|
||||
return await ResourceService.existsResource({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else if (kind == 'schedule') {
|
||||
return await ScheduleService.existsSchedule({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
} else {
|
||||
throw new Error(`Unknown kind ${kind}`)
|
||||
}
|
||||
}
|
||||
|
||||
const deploymentStatus: Record<
|
||||
string,
|
||||
{ status: 'loading' | 'deployed' | 'failed'; error?: string }
|
||||
> = {}
|
||||
|
||||
async function deploy(kind: Kind, path: string) {
|
||||
const statusPath = `${kind}:${path}`
|
||||
deploymentStatus[statusPath] = { status: 'loading' }
|
||||
try {
|
||||
let alreadyExists = await checkAlreadyExists(kind, path)
|
||||
if (kind == 'flow') {
|
||||
const flow = await FlowService.getFlowByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
getAllModules(flow.value.modules).forEach((x) => {
|
||||
if (x.value.type == 'script' && x.value.hash != undefined) {
|
||||
x.value.hash = undefined
|
||||
}
|
||||
})
|
||||
if (alreadyExists) {
|
||||
await FlowService.updateFlow({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path,
|
||||
requestBody: {
|
||||
...flow
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await FlowService.createFlow({
|
||||
workspace: workspaceToDeployTo!,
|
||||
requestBody: {
|
||||
...flow
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (kind == 'script') {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
await ScriptService.createScript({
|
||||
workspace: workspaceToDeployTo!,
|
||||
requestBody: {
|
||||
...script,
|
||||
lock: script.lock?.split('\n'),
|
||||
parent_hash: alreadyExists
|
||||
? (
|
||||
await ScriptService.getScriptByPath({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path
|
||||
})
|
||||
).hash
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
} else if (kind == 'app') {
|
||||
const app = await AppService.getAppByPath({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
if (alreadyExists) {
|
||||
await AppService.updateApp({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path,
|
||||
requestBody: {
|
||||
...app
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await AppService.createApp({
|
||||
workspace: workspaceToDeployTo!,
|
||||
requestBody: {
|
||||
...app
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (kind == 'variable') {
|
||||
const variable = await VariableService.getVariable({
|
||||
workspace: $workspaceStore!,
|
||||
path: path,
|
||||
decryptSecret: true
|
||||
})
|
||||
if (alreadyExists) {
|
||||
await VariableService.updateVariable({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path,
|
||||
requestBody: {
|
||||
path: path,
|
||||
value: variable.value ?? '',
|
||||
is_secret: variable.is_secret,
|
||||
description: variable.description ?? ''
|
||||
},
|
||||
alreadyEncrypted: false
|
||||
})
|
||||
} else {
|
||||
await VariableService.createVariable({
|
||||
workspace: workspaceToDeployTo!,
|
||||
requestBody: {
|
||||
path: path,
|
||||
value: variable.value ?? '',
|
||||
is_secret: variable.is_secret,
|
||||
description: variable.description ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (kind == 'resource') {
|
||||
const resource = await ResourceService.getResource({
|
||||
workspace: $workspaceStore!,
|
||||
path: path
|
||||
})
|
||||
if (alreadyExists) {
|
||||
await ResourceService.updateResource({
|
||||
workspace: workspaceToDeployTo!,
|
||||
path: path,
|
||||
requestBody: {
|
||||
path: path,
|
||||
value: resource.value ?? '',
|
||||
description: resource.description ?? ''
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await ResourceService.createResource({
|
||||
workspace: workspaceToDeployTo!,
|
||||
requestBody: {
|
||||
path: path,
|
||||
value: resource.value ?? '',
|
||||
resource_type: resource.resource_type,
|
||||
description: resource.description ?? ''
|
||||
}
|
||||
})
|
||||
}
|
||||
} else if (kind == 'raw_app') {
|
||||
throw new Error('Raw app deploy not implemented yet')
|
||||
// const app = await RawAppService.getRawAppData({
|
||||
// workspace: $workspaceStore!,
|
||||
// path: path
|
||||
// })
|
||||
// if (alreadyExists) {
|
||||
// }
|
||||
// await RawAppService.updateRawApp({
|
||||
// workspace: $workspaceStore!,
|
||||
// path: path,
|
||||
// requestBody: {
|
||||
// path: path
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
allAlreadyExists[statusPath] = true
|
||||
deploymentStatus[statusPath] = { status: 'deployed' }
|
||||
} catch (e) {
|
||||
deploymentStatus[statusPath] = { status: 'failed', error: e.body || e.message }
|
||||
}
|
||||
}
|
||||
|
||||
function deployAll() {
|
||||
dependencies?.slice().forEach(async ({ kind, path, include }) => {
|
||||
if (include) {
|
||||
await deploy(kind, path)
|
||||
}
|
||||
})
|
||||
dispatch('update', initialPath)
|
||||
}
|
||||
|
||||
function computeStatusPath(kind: Kind, path: string) {
|
||||
return `${kind}:${path}`
|
||||
}
|
||||
|
||||
export function showDiff(local: string, remote: string) {
|
||||
let finalString = ''
|
||||
for (const part of Diff.diffLines(local, remote)) {
|
||||
if (part.removed) {
|
||||
// print red if removed without newline
|
||||
finalString += `<span class="text-red-600">${part.value}</span>`
|
||||
} else if (part.added) {
|
||||
// print green if added
|
||||
finalString += `<span class="text-green-600">${part.value}</span>`
|
||||
} else {
|
||||
let lines = part.value.split('\n')
|
||||
|
||||
if (lines.length > 12) {
|
||||
lines = lines.slice(0, 6)
|
||||
lines.push('...')
|
||||
lines = lines.concat(part.value.split('\n').slice(-6))
|
||||
}
|
||||
// print white if unchanged
|
||||
finalString += `${lines.join('\n')}`
|
||||
}
|
||||
}
|
||||
return finalString
|
||||
}
|
||||
|
||||
let diffViewer: Drawer
|
||||
let diffContent: string | undefined = undefined
|
||||
|
||||
async function getValue(kind: Kind, path: string, workspace: string) {
|
||||
try {
|
||||
if (kind == 'flow') {
|
||||
const flow = await FlowService.getFlowByPath({
|
||||
workspace: workspace,
|
||||
path: path
|
||||
})
|
||||
getAllModules(flow.value.modules).forEach((x) => {
|
||||
if (x.value.type == 'script' && x.value.hash != undefined) {
|
||||
x.value.hash = undefined
|
||||
}
|
||||
})
|
||||
return flow.value
|
||||
} else if (kind == 'script') {
|
||||
const script = await ScriptService.getScriptByPath({
|
||||
workspace: workspace,
|
||||
path: path
|
||||
})
|
||||
return {
|
||||
content: script.content,
|
||||
lock: script.lock,
|
||||
schema: script.schema,
|
||||
summary: script.summary
|
||||
}
|
||||
} else if (kind == 'app') {
|
||||
const app = await AppService.getAppByPath({
|
||||
workspace: workspace,
|
||||
path: path
|
||||
})
|
||||
return app
|
||||
} else if (kind == 'variable') {
|
||||
const variable = await VariableService.getVariable({
|
||||
workspace: workspace,
|
||||
path: path,
|
||||
decryptSecret: true
|
||||
})
|
||||
return variable.value
|
||||
} else if (kind == 'resource') {
|
||||
const resource = await ResourceService.getResource({
|
||||
workspace: workspace,
|
||||
path: path
|
||||
})
|
||||
return resource.value
|
||||
} else if (kind == 'raw_app') {
|
||||
throw new Error('Raw app deploy not implemented yet')
|
||||
// const app = await RawAppService.getRawAppData({
|
||||
// workspace: workspace,
|
||||
// path: path
|
||||
// })
|
||||
// if (alreadyExists) {
|
||||
// }
|
||||
// await RawAppService.updateRawApp({
|
||||
// workspace: workspace,
|
||||
// path: path,
|
||||
// requestBody: {
|
||||
// path: path
|
||||
// }
|
||||
// })
|
||||
}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
async function computeDiff(kind: Kind, path: string) {
|
||||
let values = await Promise.all([
|
||||
getValue(kind, path, $workspaceStore!),
|
||||
getValue(kind, path, workspaceToDeployTo!)
|
||||
])
|
||||
diffContent = showDiff(JSON.stringify(values[0], null, 2), JSON.stringify(values[1], null, 2))
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="mt-6" />
|
||||
|
||||
{#if !$enterpriseLicense}
|
||||
<Alert type="error" title="Enterprise license required"
|
||||
>Deploy to staging/prod from the web UI is only available with an enterprise license</Alert
|
||||
>
|
||||
{:else if notSet == true}
|
||||
<Alert type="error" title="Staging/Prod deploy not set up"
|
||||
>As an admin, go to "Workspace {'->'} Dev/Staging/Prod"</Alert
|
||||
>
|
||||
{:else}
|
||||
<Alert type="info" title="Shareable page"
|
||||
>Share this <a href="/deploy/{kind}/{initialPath}">link</a> to have another properly permissioned
|
||||
user do the deployment</Alert
|
||||
>
|
||||
|
||||
<h3 class="mb-2 mt-8"
|
||||
>Destination Workspace <Tooltip
|
||||
>Workspace to deploy to is set in the workspace settings</Tooltip
|
||||
></h3
|
||||
>
|
||||
<input class="max-w-xs" type="text" disabled value={workspaceToDeployTo} />
|
||||
|
||||
{#if seeTarget == undefined}
|
||||
<div class="mt-6" />
|
||||
<Loader2 class="animate-spin" />
|
||||
{:else if seeTarget == true}
|
||||
<h3 class="mb-6 mt-16">All related deployable items</h3>
|
||||
|
||||
<Drawer bind:this={diffViewer} size="800px">
|
||||
<DrawerContent title="Diff" on:close={diffViewer.closeDrawer}>
|
||||
{#if diffContent == undefined}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:else}
|
||||
<pre class="border bg-white p-2"><code>{@html diffContent}</code></pre>
|
||||
<div class="flex flex-row-reverse gap-2">
|
||||
<div class="text-red-600">Removed</div>
|
||||
<div class="text-green-600">Added</div></div
|
||||
>
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="grid grid-cols-9 justify-center max-w-3xl gap-2">
|
||||
{#each dependencies ?? [] as { kind, path, include }}
|
||||
{@const statusPath = computeStatusPath(kind, path)}
|
||||
<div class="col-span-1 truncate text-gray-700 text-sm">{kind}</div><div
|
||||
class="col-span-5 truncate font-semibold">{path}</div
|
||||
><div class="col-span-1"><Toggle size="xs" bind:checked={include} /></div><div
|
||||
class="col-span-1"
|
||||
>
|
||||
{#if allAlreadyExists[statusPath] == false}
|
||||
{#if include}
|
||||
<Badge
|
||||
>New <Tooltip
|
||||
>This {kind} doesn't exist yet on the target and will be created by the deployment</Tooltip
|
||||
></Badge
|
||||
>
|
||||
{:else}
|
||||
<Badge color="red">
|
||||
Missing
|
||||
<Tooltip
|
||||
>This {kind} doesn't exist and is not included in the deployment. Variable and Resources
|
||||
are considered to be workspace specific and are never included by default.</Tooltip
|
||||
>
|
||||
</Badge>
|
||||
{/if}
|
||||
{:else if allAlreadyExists[statusPath] == true}
|
||||
<button
|
||||
class="text-blue-600 font-normal mt-1"
|
||||
on:click={() => {
|
||||
diffContent = undefined
|
||||
computeDiff(kind, path)
|
||||
diffViewer.openDrawer()
|
||||
}}>diff</button
|
||||
>
|
||||
{/if}</div
|
||||
>
|
||||
<div class="col-span-1">
|
||||
{#if deploymentStatus[statusPath]}
|
||||
{#if deploymentStatus[statusPath].status == 'loading'}
|
||||
<Loader2 class="animate-spin" />
|
||||
{:else if deploymentStatus[statusPath].status == 'deployed'}
|
||||
<Badge color="green">Deployed</Badge>
|
||||
{:else if deploymentStatus[statusPath].status == 'failed'}
|
||||
<Badge color="red">Failed</Badge>
|
||||
<Tooltip>{deploymentStatus[statusPath].error}</Tooltip>
|
||||
{/if}
|
||||
{:else}
|
||||
<Button color="light" size="xs" on:click={() => deploy(kind, path)}>Deploy</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if !hideButton}
|
||||
<div class="mt-16 flex flex-row-reverse max-w-3xl"
|
||||
><Button on:click={deployAll}>Deploy All Toggled</Button></div
|
||||
>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="my-2" />
|
||||
<Alert type="error" title="User not allowed to deploy to this workspace"
|
||||
>Ask a permissioned user to deploy this item using the shareable link or get the proper
|
||||
permissions on the target workspace</Alert
|
||||
>
|
||||
{/if}
|
||||
{/if}
|
||||
40
frontend/src/lib/components/DeployWorkspaceDrawer.svelte
Normal file
40
frontend/src/lib/components/DeployWorkspaceDrawer.svelte
Normal file
@@ -0,0 +1,40 @@
|
||||
<script lang="ts">
|
||||
import { Button, Drawer } from './common'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import DeployWorkspace from './DeployWorkspace.svelte'
|
||||
|
||||
type Kind = 'script' | 'resource' | 'schedule' | 'variable' | 'flow' | 'app' | 'raw_app'
|
||||
|
||||
let initialPath: string | undefined = undefined
|
||||
let kind: Kind | undefined = undefined
|
||||
|
||||
let drawer: Drawer | undefined = undefined
|
||||
let workspaceToDeployTo: string | undefined = undefined
|
||||
let deployWorkspace: DeployWorkspace | undefined = undefined
|
||||
export async function openDrawer(initialPath_l: string, kind_l: Kind) {
|
||||
initialPath = initialPath_l
|
||||
kind = kind_l
|
||||
drawer?.openDrawer()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={drawer} size="900px">
|
||||
<DrawerContent title="Deploy {initialPath} to staging or prod" on:close={drawer.closeDrawer}>
|
||||
{#if kind != undefined && initialPath != undefined}
|
||||
<DeployWorkspace
|
||||
hideButton
|
||||
{initialPath}
|
||||
{kind}
|
||||
bind:workspaceToDeployTo
|
||||
bind:this={deployWorkspace}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button
|
||||
disabled={workspaceToDeployTo == undefined}
|
||||
on:click={() => deployWorkspace?.deploy(kind, initialPath)}>Deploy All</Button
|
||||
>
|
||||
</svelte:fragment>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
@@ -111,9 +111,9 @@
|
||||
<h2 class="border-b pb-1 mt-10 mb-4">Path</h2>
|
||||
<div class="flex flex-col mb-2 gap-6">
|
||||
<Path disabled={!own} {kind} {initialPath} bind:path />
|
||||
<div class="mt-4" />
|
||||
<Button disabled={!own} on:click={updatePath}>Move/Rename</Button>
|
||||
<div />
|
||||
</div>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={!own} on:click={updatePath}>Move/Rename</Button>
|
||||
</svelte:fragment>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
@@ -21,12 +21,13 @@
|
||||
import { writable } from 'svelte/store'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import ToggleButton from './common/toggleButton/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import FolderEditor from './FolderEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import Required from './Required.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { Folder, User } from 'lucide-svelte'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app' | 'raw_app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -277,7 +278,7 @@
|
||||
<div class="flex gap-4 shrink">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="block">
|
||||
<span class="text-gray-700 text-sm whitespace-nowrap">Owner</span>
|
||||
<span class="text-gray-700 text-sm whitespace-nowrap"> </span>
|
||||
|
||||
<ToggleButtonGroup
|
||||
class="mt-0.5"
|
||||
@@ -286,7 +287,8 @@
|
||||
const kind = e.detail
|
||||
if (meta) {
|
||||
if (kind === 'folder') {
|
||||
meta.owner = $userStore?.folders?.[0] ?? ''
|
||||
console.log($userStore?.folders)
|
||||
meta.owner = folders?.[0]?.name ?? ''
|
||||
} else if (kind === 'group') {
|
||||
meta.owner = 'all'
|
||||
} else {
|
||||
@@ -295,9 +297,25 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ToggleButton light size="xs" value="user" position="left">User</ToggleButton>
|
||||
<ToggleButton
|
||||
icon={User}
|
||||
{disabled}
|
||||
light
|
||||
size="xs"
|
||||
value="user"
|
||||
position="left"
|
||||
label="User"
|
||||
/>
|
||||
<!-- <ToggleButton light size="xs" value="group" position="center">Group</ToggleButton> -->
|
||||
<ToggleButton light size="xs" value="folder" position="right">Folder</ToggleButton>
|
||||
<ToggleButton
|
||||
icon={Folder}
|
||||
{disabled}
|
||||
light
|
||||
size="xs"
|
||||
value="folder"
|
||||
position="right"
|
||||
label="Folder"
|
||||
/>
|
||||
</ToggleButtonGroup>
|
||||
</label>
|
||||
{#if meta.ownerKind === 'user'}
|
||||
@@ -308,7 +326,7 @@
|
||||
type="text"
|
||||
bind:value={meta.owner}
|
||||
placeholder={$userStore?.username ?? ''}
|
||||
disabled={!($superadmin || ($userStore?.is_admin ?? false))}
|
||||
disabled={disabled || !($superadmin || ($userStore?.is_admin ?? false))}
|
||||
/>
|
||||
</label>
|
||||
{:else if meta.ownerKind === 'folder'}
|
||||
@@ -334,6 +352,7 @@
|
||||
title="View folder"
|
||||
btnClasses="!p-1.5"
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
disabled={!meta.owner || meta.owner == ''}
|
||||
on:click={viewFolder.openDrawer}
|
||||
@@ -344,6 +363,7 @@
|
||||
title="New folder"
|
||||
btnClasses="!p-1.5"
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
{disabled}
|
||||
on:click={newFolder.openDrawer}
|
||||
|
||||
@@ -409,7 +409,7 @@
|
||||
{#if !isCloudHosted()}
|
||||
<h2 class="border-b pb-1 mt-10 mb-4"
|
||||
>Custom env variables<Tooltip
|
||||
documentationLink="https://docs.windmill.dev/docs/reference#custom-environment-variables"
|
||||
documentationLink="https://docs.windmill.dev/docs/reference#custom-environment-variables"
|
||||
>Additional static custom env variables to pass to the script.</Tooltip
|
||||
></h2
|
||||
>
|
||||
@@ -463,7 +463,7 @@
|
||||
|
||||
<div class="flex flex-col h-screen">
|
||||
<div class="flex flex-col w-full px-2 py-1 border-b shadow-sm">
|
||||
<div class="justify-between flex gap-8 w-full items-center px-2">
|
||||
<div class="justify-between flex gap-2 lg:gap-8 w-full items-center px-2">
|
||||
<div class="min-w-64 w-full max-w-md">
|
||||
<input
|
||||
type="text"
|
||||
@@ -513,7 +513,7 @@
|
||||
<Awareness />
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-row gap-x-4">
|
||||
<div class="flex flex-row gap-x-1 lg:gap-x-4">
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
@@ -548,7 +548,7 @@
|
||||
size="sm"
|
||||
startIcon={{ icon: faSave }}
|
||||
on:click={() => editScript()}
|
||||
dropdownItems={initialPath != '' ? computeDropdownItems : undefined}
|
||||
dropdownItems={initialPath != '' ? computeDropdownItems : []}
|
||||
>
|
||||
Deploy
|
||||
</Button>
|
||||
@@ -563,7 +563,7 @@
|
||||
Script kind
|
||||
<Tooltip>
|
||||
Tag this script's purpose within flows such that it is available as the corresponding
|
||||
action.
|
||||
action.
|
||||
</Tooltip>
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -599,6 +599,7 @@
|
||||
<ScriptSchema bind:schema={script.schema} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<ScriptEditor
|
||||
collabMode
|
||||
edit={initialPath != ''}
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
import Select from 'svelte-select'
|
||||
|
||||
import { getScriptByPath } from '$lib/scripts'
|
||||
import RadioButton from './RadioButton.svelte'
|
||||
import { Button, Drawer, DrawerContent } from './common'
|
||||
import HighlightCode from './HighlightCode.svelte'
|
||||
import FlowPathViewer from './flows/content/FlowPathViewer.svelte'
|
||||
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import { Code2, Globe } from 'lucide-svelte'
|
||||
|
||||
export let initialPath: string | undefined = undefined
|
||||
export let scriptPath: string | undefined = undefined
|
||||
@@ -27,9 +29,9 @@
|
||||
let code: string = ''
|
||||
let lang: 'deno' | 'python3' | 'go' | 'bash' | undefined
|
||||
|
||||
let options: [[string, any]] = [['Script', 'script']]
|
||||
allowHub && options.unshift(['Hub', 'hub'])
|
||||
allowFlow && options.push(['Flow', 'flow'])
|
||||
let options: [[string, any, any]] = [['Script', 'script', Code2]]
|
||||
allowHub && options.unshift(['Hub', 'hub', Globe])
|
||||
allowFlow && options.push(['Flow', 'flow', undefined])
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
async function loadItems(): Promise<void> {
|
||||
@@ -69,10 +71,14 @@
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<div class="flex flex-row items-center gap-4 w-full">
|
||||
<div class="flex flex-row items-center gap-4 w-full mt-2">
|
||||
{#if options.length > 1}
|
||||
<div class="w-80 mt-1">
|
||||
<RadioButton {disabled} bind:value={itemKind} {options} />
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={itemKind}>
|
||||
{#each options as [label, value, icon]}
|
||||
<ToggleButton {icon} {disabled} {value} {label} />
|
||||
{/each}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -91,7 +91,9 @@
|
||||
}
|
||||
}
|
||||
|
||||
$: value = (result ?? []).map((x, i) => ({ ...x, __index: i.toString() }))
|
||||
$: value = Array.isArray(result)
|
||||
? result.map((x, i) => ({ ...x, __index: i.toString() }))
|
||||
: [{ error: 'input was not an array' }]
|
||||
</script>
|
||||
|
||||
{#each Object.keys(components['aggridcomponent'].initialData.configuration) as key (key)}
|
||||
|
||||
@@ -23,12 +23,14 @@
|
||||
import { Eye } from 'lucide-svelte'
|
||||
import { goto } from '$app/navigation'
|
||||
import { page } from '$app/stores'
|
||||
import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
|
||||
export let app: ListableApp & { has_draft?: boolean; draft_only?: boolean; canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
export let shareModal: ShareModal
|
||||
export let moveDrawer: MoveDrawer
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
export let deleteConfirmedCallback: (() => void) | undefined
|
||||
|
||||
let {
|
||||
@@ -157,6 +159,13 @@
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Deploy to staging/prod',
|
||||
icon: faFileExport,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'app')
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: canWrite ? 'Share' : 'See Permissions',
|
||||
icon: faShare,
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import DraftBadge from '$lib/components/DraftBadge.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { isOwner } from '$lib/utils'
|
||||
import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
|
||||
export let flow: Flow & { has_draft?: boolean; draft_only?: boolean; canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
@@ -33,6 +34,7 @@
|
||||
export let shareModal: ShareModal
|
||||
export let moveDrawer: MoveDrawer
|
||||
export let deleteConfirmedCallback: (() => void) | undefined
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
|
||||
let { summary, path, extra_perms, canWrite, workspace_id, archived, draft_only, has_draft } = flow
|
||||
|
||||
@@ -190,6 +192,14 @@
|
||||
},
|
||||
disabled: !owner || archived
|
||||
},
|
||||
{
|
||||
displayName: 'Deploy to staging/prod',
|
||||
icon: faFileExport,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'flow')
|
||||
},
|
||||
disabled: archived
|
||||
},
|
||||
{
|
||||
displayName: 'Schedule',
|
||||
icon: faCalendarAlt,
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
import DrawerContent from '../drawer/DrawerContent.svelte'
|
||||
import FileInput from '../fileInput/FileInput.svelte'
|
||||
import { goto } from '$app/navigation'
|
||||
import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
|
||||
export let app: ListableRawApp & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
@@ -26,6 +27,7 @@
|
||||
export let shareModal: ShareModal
|
||||
export let moveDrawer: MoveDrawer
|
||||
export let deleteConfirmedCallback: (() => void) | undefined
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
|
||||
let updateAppDrawer: Drawer
|
||||
|
||||
@@ -112,6 +114,21 @@
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Move/Rename',
|
||||
icon: faFileExport,
|
||||
action: () => {
|
||||
moveDrawer.openDrawer(path, summary, 'raw_app')
|
||||
},
|
||||
disabled: !canWrite
|
||||
},
|
||||
{
|
||||
displayName: 'Deploy to prod/staging',
|
||||
icon: faFileExport,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'raw_app')
|
||||
}
|
||||
},
|
||||
{
|
||||
displayName: canWrite ? 'Share' : 'See Permissions',
|
||||
icon: faShare,
|
||||
|
||||
@@ -28,12 +28,14 @@
|
||||
import DraftBadge from '$lib/components/DraftBadge.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { isOwner } from '$lib/utils'
|
||||
import type DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
|
||||
export let script: Script & { canWrite: boolean }
|
||||
export let marked: string | undefined
|
||||
export let starred: boolean
|
||||
export let shareModal: ShareModal
|
||||
export let moveDrawer: MoveDrawer
|
||||
export let deploymentDrawer: DeployWorkspaceDrawer
|
||||
export let deleteConfirmedCallback: (() => void) | undefined
|
||||
|
||||
let {
|
||||
@@ -209,6 +211,14 @@
|
||||
},
|
||||
disabled: !owner || archived
|
||||
},
|
||||
{
|
||||
displayName: 'Deploy to staging/prod',
|
||||
icon: faFileExport,
|
||||
action: () => {
|
||||
deploymentDrawer.openDrawer(path, 'script')
|
||||
},
|
||||
disabled: archived
|
||||
},
|
||||
{
|
||||
displayName: 'View runs',
|
||||
icon: faList,
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
export let wrapperClass = ''
|
||||
export let style = ''
|
||||
export let hashNavigation = false
|
||||
export let dflt: string | undefined = undefined
|
||||
export let values: string[] | undefined = undefined
|
||||
|
||||
$: selected && updateSelected()
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
import { canWrite } from '$lib/utils'
|
||||
import { page } from '$app/stores'
|
||||
import { setQuery } from '$lib/navigation'
|
||||
import DeployWorkspaceDrawer from '../DeployWorkspaceDrawer.svelte'
|
||||
|
||||
type TableItem<T, U extends 'script' | 'flow' | 'app' | 'raw_app'> = T & {
|
||||
canWrite: boolean
|
||||
@@ -62,6 +63,7 @@
|
||||
|
||||
let shareModal: ShareModal
|
||||
let moveDrawer: MoveDrawer
|
||||
let deploymentDrawer: DeployWorkspaceDrawer
|
||||
|
||||
let loading = true
|
||||
|
||||
@@ -270,6 +272,7 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
|
||||
<MoveDrawer
|
||||
bind:this={moveDrawer}
|
||||
on:update={() => {
|
||||
@@ -376,6 +379,7 @@
|
||||
script={item}
|
||||
{shareModal}
|
||||
{moveDrawer}
|
||||
{deploymentDrawer}
|
||||
/>
|
||||
{:else if item.type == 'flow'}
|
||||
<FlowRow
|
||||
@@ -386,6 +390,7 @@
|
||||
flow={item}
|
||||
{shareModal}
|
||||
{moveDrawer}
|
||||
{deploymentDrawer}
|
||||
/>
|
||||
{:else if item.type == 'app'}
|
||||
<AppRow
|
||||
@@ -396,6 +401,7 @@
|
||||
app={item}
|
||||
{moveDrawer}
|
||||
{shareModal}
|
||||
{deploymentDrawer}
|
||||
/>
|
||||
{:else if item.type == 'raw_app'}
|
||||
<RawAppRow
|
||||
@@ -406,6 +412,7 @@
|
||||
app={item}
|
||||
{moveDrawer}
|
||||
{shareModal}
|
||||
{deploymentDrawer}
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
|
||||
@@ -201,7 +201,7 @@
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
<div class="w-full overflow-auto scrollbar-hidden">
|
||||
<Tabs values={['hub', 'workspace']} dflt="workspace" hashNavigation bind:selected={tab}>
|
||||
<Tabs values={['hub', 'workspace']} hashNavigation bind:selected={tab}>
|
||||
<Tab size="md" value="workspace">
|
||||
<div class="flex gap-2 items-center my-1">
|
||||
<Building size={18} />
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores'
|
||||
import CenteredPage from '$lib/components/CenteredPage.svelte'
|
||||
import DeployWorkspace from '$lib/components/DeployWorkspace.svelte'
|
||||
|
||||
$: kind = asKind($page.params.kind)
|
||||
|
||||
function asKind(
|
||||
kind: string
|
||||
): 'script' | 'resource' | 'schedule' | 'variable' | 'flow' | 'app' | 'raw_app' {
|
||||
return kind as 'script' | 'resource' | 'schedule' | 'variable' | 'flow' | 'app' | 'raw_app'
|
||||
}
|
||||
</script>
|
||||
|
||||
<CenteredPage>
|
||||
<h1 class="mt-6">Deploy {$page.params.kind} {$page.params.path}</h1>
|
||||
<DeployWorkspace {kind} initialPath={$page.params.path} />
|
||||
</CenteredPage>
|
||||
@@ -17,6 +17,7 @@
|
||||
faClipboard,
|
||||
faCodeFork,
|
||||
faEdit,
|
||||
faFileExport,
|
||||
faList,
|
||||
faPlay,
|
||||
faShare,
|
||||
@@ -41,6 +42,7 @@
|
||||
import { slide } from 'svelte/transition'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Urlize from '$lib/components/Urlize.svelte'
|
||||
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
|
||||
let userSettings: UserSettings
|
||||
|
||||
@@ -147,6 +149,7 @@
|
||||
|
||||
let webhook: HTMLHeadElement
|
||||
let moveDrawer: MoveDrawer
|
||||
let deploymentDrawer: DeployWorkspaceDrawer
|
||||
</script>
|
||||
|
||||
<ScheduleEditor on:update={() => loadSchedule()} bind:this={scheduleEditor} />
|
||||
@@ -159,6 +162,8 @@
|
||||
layout={[0.75, [2, 0, 2], 2.25, [{ h: 1.5, w: 40 }], 0.2, [{ h: 1, w: 30 }]]}
|
||||
/>
|
||||
|
||||
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
|
||||
|
||||
<MoveDrawer
|
||||
bind:this={moveDrawer}
|
||||
on:update={async (e) => {
|
||||
@@ -286,6 +291,15 @@
|
||||
>
|
||||
Move/Rename
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => deploymentDrawer.openDrawer(flow?.path ?? '', 'flow')}
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: faFileExport }}
|
||||
>
|
||||
Deploy to staging/prod
|
||||
</Button>
|
||||
<Button
|
||||
btnClasses="ml-2"
|
||||
variant="border"
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
faClipboard,
|
||||
faArrowLeft,
|
||||
faChevronUp,
|
||||
faChevronDown
|
||||
faChevronDown,
|
||||
faFileExport
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import ShareModal from '$lib/components/ShareModal.svelte'
|
||||
@@ -56,6 +57,7 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { scriptToHubUrl } from '$lib/hub'
|
||||
import Urlize from '$lib/components/Urlize.svelte'
|
||||
import DeployWorkspaceDrawer from '$lib/components/DeployWorkspaceDrawer.svelte'
|
||||
|
||||
let userSettings: UserSettings
|
||||
let script: Script | undefined
|
||||
@@ -197,6 +199,7 @@
|
||||
}/p/${script?.path}`
|
||||
}
|
||||
let moveDrawer: MoveDrawer
|
||||
let deploymentDrawer: DeployWorkspaceDrawer
|
||||
</script>
|
||||
|
||||
<MoveDrawer
|
||||
@@ -206,6 +209,7 @@
|
||||
loadScript($page.params.hash)
|
||||
}}
|
||||
/>
|
||||
<DeployWorkspaceDrawer bind:this={deploymentDrawer} />
|
||||
|
||||
<ScheduleEditor bind:this={scheduleEditor} />
|
||||
|
||||
@@ -344,6 +348,15 @@
|
||||
>
|
||||
Move/Rename
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => deploymentDrawer.openDrawer(script?.path ?? '', 'script')}
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
startIcon={{ icon: faFileExport }}
|
||||
>
|
||||
Deploy to staging/prod
|
||||
</Button>
|
||||
<Button
|
||||
color="dark"
|
||||
variant="border"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { Alert, Badge, Button, Skeleton, Tab, Tabs } from '$lib/components/common'
|
||||
import ToggleButton from '$lib/components/common/toggleButton/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton/ToggleButtonGroup.svelte'
|
||||
import DeployToSetting from '$lib/components/DeployToSetting.svelte'
|
||||
import InviteUser from '$lib/components/InviteUser.svelte'
|
||||
import PageHeader from '$lib/components/PageHeader.svelte'
|
||||
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
|
||||
@@ -24,9 +25,15 @@
|
||||
WorkspaceService,
|
||||
type WorkspaceInvite
|
||||
} from '$lib/gen'
|
||||
import { superadmin, userStore, usersWorkspaceStore, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
enterpriseLicense,
|
||||
superadmin,
|
||||
userStore,
|
||||
usersWorkspaceStore,
|
||||
workspaceStore
|
||||
} from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { capitalize } from '$lib/utils'
|
||||
import { capitalize, setQueryWithoutLoad } from '$lib/utils'
|
||||
import { faSlack } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faBarsStaggered, faExternalLink, faScroll } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@@ -45,13 +52,15 @@
|
||||
let plan: string | undefined = undefined
|
||||
let customer_id: string | undefined = undefined
|
||||
let webhook: string | undefined = undefined
|
||||
let workspaceToDeployTo: string | undefined = undefined
|
||||
let tab =
|
||||
($page.url.searchParams.get('tab') as
|
||||
| 'users'
|
||||
| 'slack'
|
||||
| 'premium'
|
||||
| 'export_delete'
|
||||
| 'webhook') ?? 'users'
|
||||
| 'webhook'
|
||||
| 'deploy_to') ?? 'users'
|
||||
|
||||
// function getDropDownItems(username: string): DropdownItem[] {
|
||||
// return [
|
||||
@@ -112,6 +121,7 @@
|
||||
plan = settings.plan
|
||||
customer_id = settings.customer_id
|
||||
initialPath = scriptPath
|
||||
workspaceToDeployTo = settings.deploy_to
|
||||
webhook = settings.webhook
|
||||
}
|
||||
|
||||
@@ -197,10 +207,18 @@
|
||||
<PageHeader title="Workspace Settings of {$workspaceStore}" />
|
||||
|
||||
<div class="overflow-x-auto scrollbar-hidden">
|
||||
<Tabs bind:selected={tab}>
|
||||
<Tabs
|
||||
bind:selected={tab}
|
||||
on:selected={() => {
|
||||
setQueryWithoutLoad($page.url, [{ key: 'tab', value: tab }], 0)
|
||||
}}
|
||||
>
|
||||
<Tab size="md" value="users">
|
||||
<div class="flex gap-2 items-center my-1"> Users & Invites </div>
|
||||
</Tab>
|
||||
<Tab size="md" value="deploy_to">
|
||||
<div class="flex gap-2 items-center my-1"> Dev/Staging/Prod</div>
|
||||
</Tab>
|
||||
{#if WORKSPACE_SHOW_SLACK_CMD}
|
||||
<Tab size="md" value="slack">
|
||||
<div class="flex gap-2 items-center my-1"> Slack Command </div>
|
||||
@@ -448,6 +466,22 @@
|
||||
{#if !allowedAutoDomain}
|
||||
<div class="text-red-400 text-sm mb-2">{domain} domain not allowed for auto-invite</div>
|
||||
{/if}
|
||||
{:else if tab == 'deploy_to'}
|
||||
<div class="my-2"
|
||||
><Alert type="info" title="Link this workspace to another Staging/Prod workspace"
|
||||
>Linking this workspace to another staging/prod workspace unlock the Web-based flow to
|
||||
deploy to another workspace.</Alert
|
||||
></div
|
||||
>
|
||||
{#if $enterpriseLicense}
|
||||
<DeployToSetting bind:workspaceToDeployTo />
|
||||
{:else}
|
||||
<div class="my-2"
|
||||
><Alert type="error" title="Enterprise license required"
|
||||
>Deploy to staging/prod from the web UI is only available with an enterprise license</Alert
|
||||
></div
|
||||
>
|
||||
{/if}
|
||||
{:else if tab == 'premium'}
|
||||
{#if isCloudHosted()}
|
||||
<div class="mt-4" />
|
||||
@@ -639,9 +673,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
{:else}
|
||||
<Button size="sm" endIcon={{ icon: faSlack }} href="/api/oauth/connect_slack">
|
||||
Connect to Slack
|
||||
</Button>
|
||||
<div class="flex">
|
||||
<Button size="sm" endIcon={{ icon: faSlack }} href="/api/oauth/connect_slack">
|
||||
Connect to Slack
|
||||
</Button>
|
||||
</div>
|
||||
{/if}
|
||||
<h3 class="mt-5 text-gray-700"
|
||||
>Script or flow to run on /windmill command <Tooltip>
|
||||
|
||||
64
react-sdk/package-lock.json
generated
64
react-sdk/package-lock.json
generated
@@ -30,16 +30,16 @@
|
||||
},
|
||||
"../frontend": {
|
||||
"name": "windmill",
|
||||
"version": "1.109.0",
|
||||
"version": "1.114.2",
|
||||
"license": "AGPL-3.0",
|
||||
"dependencies": {
|
||||
"@aws-crypto/sha256-js": "^4.0.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@leeoniya/ufuzzy": "^1.0.6",
|
||||
"@leeoniya/ufuzzy": "^1.0.7",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@tanstack/svelte-table": "^8.9.1",
|
||||
"@tanstack/svelte-table": "^8.9.2",
|
||||
"ag-grid-svelte": "^0.1.4",
|
||||
"chart.js": "^4.3.0",
|
||||
"chartjs-adapter-date-fns": "^3.0.0",
|
||||
@@ -50,8 +50,9 @@
|
||||
"fast-equals": "^5.0.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-svelte": "^0.224.0",
|
||||
"monaco-languageclient": "~6.0.1",
|
||||
"lucide-svelte": "^0.242.0",
|
||||
"monaco-languageclient": "~6.0.3",
|
||||
"quill": "^1.3.7",
|
||||
"svelte-autosize": "^1.0.1",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-dnd-action": "^0.9.22",
|
||||
@@ -60,33 +61,34 @@
|
||||
"svelte-timezone-picker": "^2.0.3",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"vscode-ws-jsonrpc": "3.0.0",
|
||||
"windmill-parser-wasm": "file:../backend/parsers/windmill-parser-wasm/pkg",
|
||||
"y-monaco": "^0.1.4",
|
||||
"y-websocket": "^1.5.0",
|
||||
"yjs": "^13.6.1"
|
||||
"yjs": "^13.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@sveltejs/kit": "^1.18.0",
|
||||
"@sveltejs/kit": "^1.20.1",
|
||||
"@sveltejs/package": "^2.0.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-zoom": "^3.0.2",
|
||||
"@types/d3-zoom": "^3.0.3",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/vscode": "~1.78.0",
|
||||
"@types/node": "^20.3.0",
|
||||
"@types/vscode": "~1.78.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"@zerodevx/svelte-toast": "^0.9.3",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"cssnano": "^6.0.1",
|
||||
"d3-dag": "^0.11.5",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"monaco-editor-workers": "~0.37.0",
|
||||
"monaco-editor-workers": "~0.38.0",
|
||||
"ol": "^7.2.2",
|
||||
"openapi-typescript-codegen": "^0.24.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
@@ -101,23 +103,23 @@
|
||||
"svelte": "^3.59.1",
|
||||
"svelte-awesome": "^3.2.0",
|
||||
"svelte-awesome-color-picker": "^2.4.3",
|
||||
"svelte-check": "^3.3.2",
|
||||
"svelte-check": "^3.4.3",
|
||||
"svelte-highlight": "^7.3.0",
|
||||
"svelte-multiselect": "^8.6.2",
|
||||
"svelte-overlay": "^1.4.1",
|
||||
"svelte-popperjs": "^1.3.2",
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"svelte-range-slider-pips": "^2.1.1",
|
||||
"svelte-splitpanes": "^0.7.3",
|
||||
"svelte-splitpanes": "^0.7.14",
|
||||
"svelte2tsx": "^0.6.14",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.5.2",
|
||||
"typescript": "^5.0.4",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.3",
|
||||
"yootils": "^0.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@sveltejs/kit": "^1.18.0",
|
||||
"@sveltejs/kit": "^1.20.1",
|
||||
"svelte": "^3.59.1"
|
||||
}
|
||||
},
|
||||
@@ -4122,22 +4124,22 @@
|
||||
"@aws-crypto/sha256-js": "^4.0.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@leeoniya/ufuzzy": "^1.0.6",
|
||||
"@leeoniya/ufuzzy": "^1.0.7",
|
||||
"@playwright/test": "^1.34.3",
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"@redocly/json-to-json-schema": "^0.0.1",
|
||||
"@rgossiaux/svelte-headlessui": "^1.0.2",
|
||||
"@sveltejs/adapter-static": "^2.0.2",
|
||||
"@sveltejs/kit": "^1.18.0",
|
||||
"@sveltejs/kit": "^1.20.1",
|
||||
"@sveltejs/package": "^2.0.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/typography": "^0.5.8",
|
||||
"@tanstack/svelte-table": "^8.9.1",
|
||||
"@tanstack/svelte-table": "^8.9.2",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-zoom": "^3.0.2",
|
||||
"@types/d3-zoom": "^3.0.3",
|
||||
"@types/lodash": "^4.14.195",
|
||||
"@types/node": "^20.2.5",
|
||||
"@types/vscode": "~1.78.0",
|
||||
"@types/node": "^20.3.0",
|
||||
"@types/vscode": "~1.78.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"@zerodevx/svelte-toast": "^0.9.3",
|
||||
@@ -4150,16 +4152,16 @@
|
||||
"d3-dag": "^0.11.5",
|
||||
"d3-zoom": "^3.0.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"eslint": "^8.40.0",
|
||||
"eslint": "^8.42.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"esm-env": "^1.0.0",
|
||||
"fast-equals": "^5.0.1",
|
||||
"highlight.js": "^11.8.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-svelte": "^0.224.0",
|
||||
"monaco-editor-workers": "~0.37.0",
|
||||
"monaco-languageclient": "~6.0.1",
|
||||
"lucide-svelte": "^0.242.0",
|
||||
"monaco-editor-workers": "~0.38.0",
|
||||
"monaco-languageclient": "~6.0.3",
|
||||
"ol": "^7.2.2",
|
||||
"openapi-typescript-codegen": "^0.24.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
@@ -4168,6 +4170,7 @@
|
||||
"postcss-load-config": "^4.0.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-svelte": "^2.10.1",
|
||||
"quill": "^1.3.7",
|
||||
"simple-svelte-autocomplete": "^2.5.1",
|
||||
"style-to-object": "^0.4.1",
|
||||
"stylelint-config-recommended": "^12.0.0",
|
||||
@@ -4176,7 +4179,7 @@
|
||||
"svelte-awesome": "^3.2.0",
|
||||
"svelte-awesome-color-picker": "^2.4.3",
|
||||
"svelte-chartjs": "^3.1.0",
|
||||
"svelte-check": "^3.3.2",
|
||||
"svelte-check": "^3.4.3",
|
||||
"svelte-dnd-action": "^0.9.22",
|
||||
"svelte-highlight": "^7.3.0",
|
||||
"svelte-multiselect": "^8.6.2",
|
||||
@@ -4186,18 +4189,19 @@
|
||||
"svelte-preprocess": "^5.0.1",
|
||||
"svelte-range-slider-pips": "^2.1.1",
|
||||
"svelte-select": "^5.6.1",
|
||||
"svelte-splitpanes": "^0.7.3",
|
||||
"svelte-splitpanes": "^0.7.14",
|
||||
"svelte-timezone-picker": "^2.0.3",
|
||||
"svelte2tsx": "^0.6.14",
|
||||
"tailwind-merge": "^1.12.0",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tslib": "^2.5.2",
|
||||
"typescript": "^5.0.4",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.3",
|
||||
"vscode-ws-jsonrpc": "3.0.0",
|
||||
"windmill-parser-wasm": "file:../backend/parsers/windmill-parser-wasm/pkg",
|
||||
"y-monaco": "^0.1.4",
|
||||
"y-websocket": "^1.5.0",
|
||||
"yjs": "^13.6.1",
|
||||
"yjs": "^13.6.2",
|
||||
"yootils": "^0.3.1"
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user