run prettier

This commit is contained in:
Ruben Fiszel
2022-05-10 21:29:54 +02:00
parent c4235a78bf
commit ce70ec783e
102 changed files with 2401 additions and 2415 deletions
+1 -1
View File
@@ -20,4 +20,4 @@ module.exports = {
rules: {
'no-console': ['log', { allow: ['warn', 'error'] }]
}
};
}
+2 -1
View File
@@ -2,5 +2,6 @@
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
"printWidth": 100,
"semi": false
}
+7 -7
View File
@@ -1,9 +1,9 @@
const tailwindcss = require('tailwindcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const tailwindcss = require('tailwindcss')
const autoprefixer = require('autoprefixer')
const cssnano = require('cssnano')
const mode = process.env.NODE_ENV;
const dev = mode === 'development';
const mode = process.env.NODE_ENV
const dev = mode === 'development'
const config = {
plugins: [
@@ -16,6 +16,6 @@ const config = {
preset: 'default'
})
]
};
}
module.exports = config;
module.exports = config
+1 -1
View File
@@ -1,5 +1,5 @@
declare namespace svelte.JSX {
interface DOMAttributes<T> {
onclick_outside?: CompositionEventHandler<T>;
onclick_outside?: CompositionEventHandler<T>
}
}
+10 -12
View File
@@ -1,16 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>windmill.dev</title>
%svelte.head%
</head>
<head>
<meta charset="utf-8" />
<link rel="icon" href="/logo.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>windmill.dev</title>
%svelte.head%
</head>
<body class="outline-none focus:outline-none">
%svelte.body%
</body>
<body class="outline-none focus:outline-none">
%svelte.body%
</body>
</html>
+2 -4
View File
@@ -2,16 +2,15 @@ export type OwnerKind = 'group' | 'user'
export type ActionKind = 'Create' | 'Update' | 'Delete' | 'Execute'
export interface SchemaProperty {
type: string | undefined
description: string
pattern?: string
default?: any
enum?: string[]
contentEncoding?: "base64" | "binary"
contentEncoding?: 'base64' | 'binary'
format?: string
items?: { type?: "string" | "number" }
items?: { type?: 'string' | 'number' }
}
export type Schema = {
@@ -23,7 +22,6 @@ export type Schema = {
export type Meta = { ownerKind: OwnerKind; owner: string; name: string }
export function pathToMeta(path: string): Meta {
const splitted = path.split('/')
let ownerKind: OwnerKind
+4 -4
View File
@@ -1,8 +1,8 @@
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
ssr: false
})
const response = await resolve(event, {
ssr: false
})
return response
return response
}
+50 -50
View File
@@ -1,61 +1,61 @@
import type { Schema, SchemaProperty } from "./common"
import { ScriptService } from "./gen"
import { sendUserToast } from "./utils"
import type { Schema, SchemaProperty } from './common'
import { ScriptService } from './gen'
import { sendUserToast } from './utils'
export async function inferArgs(code: string, schema: Schema): Promise<void> {
try {
const inferedSchema = await ScriptService.toJsonschema({
requestBody: code
})
schema.required = []
const oldProperties = Object.assign({}, schema.properties)
schema.properties = {}
try {
const inferedSchema = await ScriptService.toJsonschema({
requestBody: code
})
schema.required = []
const oldProperties = Object.assign({}, schema.properties)
schema.properties = {}
for (const arg of inferedSchema.args) {
if (!(arg.name in oldProperties)) {
schema.properties[arg.name] = { description: '', type: '' }
} else {
schema.properties[arg.name] = oldProperties[arg.name]
}
pythonToJsonSchemaType(arg.typ, schema.properties[arg.name])
schema.properties[arg.name].default = arg.default
for (const arg of inferedSchema.args) {
if (!(arg.name in oldProperties)) {
schema.properties[arg.name] = { description: '', type: '' }
} else {
schema.properties[arg.name] = oldProperties[arg.name]
}
pythonToJsonSchemaType(arg.typ, schema.properties[arg.name])
schema.properties[arg.name].default = arg.default
if (!arg.has_default) {
schema.required.push(arg.name)
}
}
} catch (err) {
console.error(err)
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true)
}
if (!arg.has_default) {
schema.required.push(arg.name)
}
}
} catch (err) {
console.error(err)
sendUserToast(`Could not infer schema: ${err.body ?? err}`, true)
}
}
function array_move<T>(arr: T[], fromIndex: number, toIndex: number) {
var element = arr[fromIndex]
arr.splice(fromIndex, 1)
arr.splice(toIndex, 0, element)
var element = arr[fromIndex]
arr.splice(fromIndex, 1)
arr.splice(toIndex, 0, element)
}
function pythonToJsonSchemaType(t: string, s: SchemaProperty): void {
if (t === 'int') {
s.type = 'integer'
} else if (t === 'float') {
s.type = 'number'
} else if (t === 'bool') {
s.type = 'boolean'
} else if (t === 'str') {
s.type = 'string'
} else if (t === 'dict') {
s.type = 'object'
} else if (t === 'list') {
s.type = 'array'
} else if (t === 'bytes') {
s.type = 'string'
s.contentEncoding = 'base64'
} else if (t === 'datetime') {
s.type = 'string'
s.format = 'date-time'
} else {
s.type = undefined
}
if (t === 'int') {
s.type = 'integer'
} else if (t === 'float') {
s.type = 'number'
} else if (t === 'bool') {
s.type = 'boolean'
} else if (t === 'str') {
s.type = 'string'
} else if (t === 'dict') {
s.type = 'object'
} else if (t === 'list') {
s.type = 'array'
} else if (t === 'bytes') {
s.type = 'string'
s.contentEncoding = 'base64'
} else if (t === 'datetime') {
s.type = 'string'
s.format = 'date-time'
} else {
s.type = undefined
}
}
+49 -49
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import '../app.css';
import '../app.css'
import { OpenAPI, UserService, WorkspaceService } from '../gen';
import { OpenAPI, UserService, WorkspaceService } from '../gen'
import {
logout,
clickOutside,
@@ -9,9 +9,9 @@
logoutWithRedirect,
getUser,
refreshSuperadmin
} from '../utils';
import { onDestroy, onMount } from 'svelte';
import Icon from 'svelte-awesome';
} from '../utils'
import { onDestroy, onMount } from 'svelte'
import Icon from 'svelte-awesome'
import {
faScroll,
faPlay,
@@ -29,20 +29,20 @@
faCrown,
faUsersCog,
faWind
} from '@fortawesome/free-solid-svg-icons';
import { SvelteToast } from '@zerodevx/svelte-toast';
import { faDiscord, faGithub, faPython } from '@fortawesome/free-brands-svg-icons';
import { page } from '$app/stores';
} from '@fortawesome/free-solid-svg-icons'
import { SvelteToast } from '@zerodevx/svelte-toast'
import { faDiscord, faGithub, faPython } from '@fortawesome/free-brands-svg-icons'
import { page } from '$app/stores'
import {
superadmin,
usernameStore,
userStore,
usersWorkspaceStore,
workspaceStore
} from '../stores';
import { goto } from '$app/navigation';
} from '../stores'
import { goto } from '$app/navigation'
OpenAPI.WITH_CREDENTIALS = true;
OpenAPI.WITH_CREDENTIALS = true
// Default toast options
const toastOptions = {
@@ -54,63 +54,63 @@
reversed: false, // insert new toast to bottom of stack
intro: { x: 256 }, // toast intro fly animation settings
theme: {} // css var overrides
};
}
let menuOpen = false;
let workspacePickerOpen = false;
let isMobile = false;
let viewportWidth = 3000;
let isCollapsed = false;
let menuOpen = false
let workspacePickerOpen = false
let isMobile = false
let viewportWidth = 3000
let isCollapsed = false
function openMenu(): void {
menuOpen = true;
menuOpen = true
}
function handleClickOutside(event: any): void {
if (isMobile || viewportWidth < 640) {
isCollapsed = true;
isCollapsed = true
}
}
function handleClickOutsideMenu(event: any): void {
menuOpen = false;
menuOpen = false
}
function handleClickOutsideWorkspacePicker(event: any): void {
workspacePickerOpen = false;
workspacePickerOpen = false
}
async function loadUserInfo() {
refreshSuperadmin();
refreshSuperadmin()
if (!$usersWorkspaceStore) {
try {
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
} catch {}
}
if ($usersWorkspaceStore) {
if (!$workspaceStore) {
workspaceStore.set(localStorage.getItem('workspace')?.toString());
workspaceStore.set(localStorage.getItem('workspace')?.toString())
}
if ($workspaceStore && $usernameStore) {
await getUser($workspaceStore);
await getUser($workspaceStore)
} else if ($superadmin) {
console.log('You are a superadmin, you can go wherever you please');
console.log('You are a superadmin, you can go wherever you please')
} else {
goto('/user/workspaces');
goto('/user/workspaces')
}
} else {
logoutWithRedirect($page.url.pathname);
logoutWithRedirect($page.url.pathname)
}
}
$: {
if ($workspaceStore) {
localStorage.setItem('workspace', $workspaceStore);
localStorage.setItem('workspace', $workspaceStore)
}
}
onMount(() => {
loadUserInfo();
loadUserInfo()
window.onunhandledrejection = (e) => {
if (e.reason && e.reason.message) {
if (
@@ -119,27 +119,27 @@
)
) {
// monaco editor promise cancelation
console.log('caught expected error');
console.log('caught expected error')
} else {
if (e.reason.status == '401') {
sendUserToast('Logged out after a request was unauthorized', true);
logout($page.url.pathname);
sendUserToast('Logged out after a request was unauthorized', true)
logout($page.url.pathname)
} else {
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`;
sendUserToast(message, true);
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`
sendUserToast(message, true)
}
}
} else {
console.log('unexpected error ignored', e);
console.log('unexpected error ignored', e)
}
e.preventDefault();
return false;
};
e.preventDefault()
return false
}
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
//Mobile
isCollapsed = isMobile;
});
isCollapsed = isMobile
})
</script>
<div bind:clientWidth={viewportWidth} class="h-full max-w-screen">
@@ -154,7 +154,7 @@
<button
class="w-full flex flex-row-reverse transform hover:translate-x-1 transition-transform ease-in duration-200"
on:click={() => {
isCollapsed = !isCollapsed;
isCollapsed = !isCollapsed
}}
>
<div class="pt-1 pr-3">
@@ -180,7 +180,7 @@
<div
class="flex flex-row items-center w-full justify-content"
on:click={() => {
workspacePickerOpen = true;
workspacePickerOpen = true
}}
>
<span class:hidden={isCollapsed} class="pr-2 font-mono text-xs flex"
@@ -202,8 +202,8 @@
{#each $usersWorkspaceStore?.workspaces ?? [] as workspace}
<button
on:click={() => {
workspaceStore.set(workspace.id);
workspacePickerOpen = false;
workspaceStore.set(workspace.id)
workspacePickerOpen = false
}}
class="block px-4 py-2 text-xs text-gray-500 "
role="menuitem"
@@ -230,7 +230,7 @@
tabindex="-1"
id="user-menu-item-2"
on:click={() => {
localStorage.removeItem('workspace');
localStorage.removeItem('workspace')
}}
>
See all workspaces & invites</a
@@ -404,7 +404,7 @@
<button
class="h-12 flex flex-row text-sm font-medium min-w-full px-5 items-center transform hover:translate-x-1 transition-transform ease-in duration-200"
on:click={() => {
isCollapsed = !isCollapsed;
isCollapsed = !isCollapsed
}}
>
<div class="w-full -ml-4">
+39 -39
View File
@@ -1,34 +1,34 @@
<script lang="ts">
import { AuditService, AuditLog, UserService } from '../gen';
import type { ActionKind } from '../common';
import { page } from '$app/stores';
import { displayDate, sendUserToast } from '../utils';
import { goto } from '$app/navigation';
import PageHeader from './components/PageHeader.svelte';
import { usernameStore, userStore, workspaceStore } from '../stores';
import TableCustom from './components/TableCustom.svelte';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
import { faCross, faEdit, faPlay, faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons';
import { AuditService, AuditLog, UserService } from '../gen'
import type { ActionKind } from '../common'
import { page } from '$app/stores'
import { displayDate, sendUserToast } from '../utils'
import { goto } from '$app/navigation'
import PageHeader from './components/PageHeader.svelte'
import { usernameStore, userStore, workspaceStore } from '../stores'
import TableCustom from './components/TableCustom.svelte'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import { faCross, faEdit, faPlay, faPlus, faQuestion } from '@fortawesome/free-solid-svg-icons'
let logs: AuditLog[];
let usernames: string[];
let logs: AuditLog[]
let usernames: string[]
// Get all page params
let username: string | undefined = $page.url.searchParams.get('username') ?? undefined;
let pageIndex: number | undefined = Number($page.url.searchParams.get('page')) || undefined;
let before: string | undefined = $page.url.searchParams.get('before') ?? undefined;
let after: string | undefined = $page.url.searchParams.get('after') ?? undefined;
let perPage: number | undefined = Number($page.url.searchParams.get('perPage')) || undefined;
let operation: string | undefined = $page.url.searchParams.get('operation') ?? undefined;
let resource: string | undefined = $page.url.searchParams.get('resource') ?? undefined;
let username: string | undefined = $page.url.searchParams.get('username') ?? undefined
let pageIndex: number | undefined = Number($page.url.searchParams.get('page')) || undefined
let before: string | undefined = $page.url.searchParams.get('before') ?? undefined
let after: string | undefined = $page.url.searchParams.get('after') ?? undefined
let perPage: number | undefined = Number($page.url.searchParams.get('perPage')) || undefined
let operation: string | undefined = $page.url.searchParams.get('operation') ?? undefined
let resource: string | undefined = $page.url.searchParams.get('resource') ?? undefined
let actionKind: ActionKind | undefined =
($page.url.searchParams.get('actionKind') as ActionKind) ?? undefined;
($page.url.searchParams.get('actionKind') as ActionKind) ?? undefined
async function loadLogs(username: string | undefined, page: number | undefined): Promise<void> {
try {
if (username == 'all') {
username = undefined;
username = undefined
}
logs = await AuditService.listAuditLogs({
workspace: $workspaceStore!,
@@ -40,49 +40,49 @@
operation,
resource,
actionKind
});
})
} catch (err) {
sendUserToast(`Could not load users: ${err}`, true);
sendUserToast(`Could not load users: ${err}`, true)
}
}
async function loadUsers() {
try {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
} catch (err) {
sendUserToast(`Could not load users: ${err}`, true);
sendUserToast(`Could not load users: ${err}`, true)
}
}
async function gotoUsername(username: string | undefined): Promise<void> {
goto(`?username=` + (username ? encodeURIComponent(username) : ''));
goto(`?username=` + (username ? encodeURIComponent(username) : ''))
}
async function gotoPage(index: number): Promise<void> {
pageIndex = index;
goto(`?page=${index}` + (username ? `&username=${encodeURIComponent(username)}` : ''));
pageIndex = index
goto(`?page=${index}` + (username ? `&username=${encodeURIComponent(username)}` : ''))
}
function kindToIcon(kind: string) {
if (kind == 'Execute') {
return faPlay;
return faPlay
} else if (kind == 'Delete') {
return faCross;
return faCross
} else if (kind == 'Update') {
return faEdit;
return faEdit
} else if (kind == 'Create') {
return faPlus;
return faPlus
}
return faQuestion;
return faQuestion
}
$: {
if ($workspaceStore) {
loadUsers();
loadLogs(username, pageIndex);
loadUsers()
loadLogs(username, pageIndex)
}
if ($usernameStore) {
username = $usernameStore;
username = $usernameStore
}
}
</script>
@@ -115,10 +115,10 @@
<TableCustom
on:next={() => {
gotoPage((pageIndex ?? 1) + 1);
gotoPage((pageIndex ?? 1) + 1)
}}
on:previous={() => {
gotoPage((pageIndex ?? 1) - 1);
gotoPage((pageIndex ?? 1) - 1)
}}
currentPage={pageIndex}
>
+16 -16
View File
@@ -1,26 +1,26 @@
<script lang="ts">
import { truncate } from '../../utils';
import Modal from './Modal.svelte';
import Tooltip from './Tooltip.svelte';
import json from 'svelte-highlight/src/languages/json';
import github from 'svelte-highlight/src/styles/github';
import { Highlight } from 'svelte-highlight';
import { ResourceService, type Resource } from '../../gen';
import { workspaceStore } from '../../stores';
import { truncate } from '../../utils'
import Modal from './Modal.svelte'
import Tooltip from './Tooltip.svelte'
import json from 'svelte-highlight/src/languages/json'
import github from 'svelte-highlight/src/styles/github'
import { Highlight } from 'svelte-highlight'
import { ResourceService, type Resource } from '../../gen'
import { workspaceStore } from '../../stores'
export let value: any;
let resourceViewer: Modal;
let resource: Resource;
export let value: any
let resourceViewer: Modal
let resource: Resource
function isString(value: any) {
return typeof value === 'string' || value instanceof String;
return typeof value === 'string' || value instanceof String
}
async function getResource(path) {
resource = await ResourceService.getResource({ workspace: $workspaceStore!, path });
resource = await ResourceService.getResource({ workspace: $workspaceStore!, path })
}
let asJson: string = JSON.stringify(value, null, 4);
let asJson: string = JSON.stringify(value, null, 4)
</script>
<svelte:head>
@@ -43,8 +43,8 @@
<button
class="text-xs text-blue-500"
on:click={async () => {
await getResource(value.substring('$res:'.length));
resourceViewer.openModal();
await getResource(value.substring('$res:'.length))
resourceViewer.openModal()
}}>{value}</button
>{:else if asJson.length > 40}
{truncate(asJson, 40)}<Tooltip>{asJson}</Tooltip>
+60 -60
View File
@@ -1,115 +1,115 @@
<script lang="ts">
import Tooltip from './Tooltip.svelte';
import Tooltip from './Tooltip.svelte'
import { slide } from 'svelte/transition';
import { slide } from 'svelte/transition'
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons';
import { faChevronDown, faChevronUp, faMinus, faPlus } from '@fortawesome/free-solid-svg-icons'
import StringTypeNarrowing from './StringTypeNarrowing.svelte';
import Icon from 'svelte-awesome';
import ResourcePicker from './ResourcePicker.svelte';
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte';
import ObjectResourceInput from './ObjectResourceInput.svelte';
import FieldHeader from './FieldHeader.svelte';
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
import Icon from 'svelte-awesome'
import ResourcePicker from './ResourcePicker.svelte'
import ObjectTypeNarrowing from './ObjectTypeNarrowing.svelte'
import ObjectResourceInput from './ObjectResourceInput.svelte'
import FieldHeader from './FieldHeader.svelte'
export let label: string = '';
export let value: any;
export let defaultValue: any = undefined;
export let description: string = '';
export let format: string = '';
export let contentEncoding = '';
export let type: string | undefined = undefined;
export let required = false;
export let pattern: undefined | string;
export let valid = required ? false : true;
export let minRows = 1;
export let maxRows = 10;
export let enum_: string[] | undefined = undefined;
export let disabled = false;
export let editableSchema = false;
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined;
export let displayHeader = true;
export let label: string = ''
export let value: any
export let defaultValue: any = undefined
export let description: string = ''
export let format: string = ''
export let contentEncoding = ''
export let type: string | undefined = undefined
export let required = false
export let pattern: undefined | string
export let valid = required ? false : true
export let minRows = 1
export let maxRows = 10
export let enum_: string[] | undefined = undefined
export let disabled = false
export let editableSchema = false
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined
export let displayHeader = true
let seeEditable: boolean = enum_ != undefined || pattern != undefined;
let seeEditable: boolean = enum_ != undefined || pattern != undefined
$: minHeight = `${1 + minRows * 1.2}em`;
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
$: minHeight = `${1 + minRows * 1.2}em`
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
$: validateInput(pattern, value);
$: validateInput(pattern, value)
let error: string = '';
let error: string = ''
let rawValue: string | undefined;
let rawValue: string | undefined
$: {
if (rawValue) {
try {
value = JSON.parse(rawValue);
value = JSON.parse(rawValue)
} catch (err) {
error = err.toString();
error = err.toString()
}
}
}
$: {
if (!type || type == 'object' || (type == 'array' && itemsType?.type == undefined)) {
evalValueToRaw();
evalValueToRaw()
}
if (defaultValue) {
let stringified = JSON.stringify(defaultValue, null, 4);
let stringified = JSON.stringify(defaultValue, null, 4)
if (stringified.length > 50) {
minRows = 3;
minRows = 3
}
if (type != 'string') {
minRows = Math.max(minRows, Math.min(stringified.split(/\r\n|\r|\n/).length + 1, maxRows));
minRows = Math.max(minRows, Math.min(stringified.split(/\r\n|\r|\n/).length + 1, maxRows))
}
}
}
export function evalValueToRaw() {
rawValue = JSON.stringify(value, null, 4);
rawValue = JSON.stringify(value, null, 4)
}
function fileChanged(e: any) {
let t = e.target;
let t = e.target
if (t && 'files' in t && t.files.length > 0) {
let reader = new FileReader();
let reader = new FileReader()
reader.onload = (e: any) => {
value = e.target.result.split('base64,')[1];
};
reader.readAsDataURL(t.files[0]);
value = e.target.result.split('base64,')[1]
}
reader.readAsDataURL(t.files[0])
} else {
value = undefined;
value = undefined
}
}
function validateInput(pattern: string | undefined, v: any): void {
if (required && v == undefined) {
error = 'This field is required';
valid = false;
error = 'This field is required'
valid = false
} else {
if (pattern && !testRegex(pattern, v)) {
error = `Should match ${pattern}`;
valid = false;
error = `Should match ${pattern}`
valid = false
} else {
error = '';
valid = true;
error = ''
valid = true
}
}
}
function testRegex(pattern: string, value: any): boolean {
try {
const regex = new RegExp(pattern);
return regex.test(value);
const regex = new RegExp(pattern)
return regex.test(value)
} catch (err) {
return false;
return false
}
}
$: {
if (value == undefined) {
value = defaultValue;
value = defaultValue
}
}
</script>
@@ -124,7 +124,7 @@
<span
class="underline"
on:click={() => {
seeEditable = !seeEditable;
seeEditable = !seeEditable
}}
>Customize argument<Icon
class="ml-2"
@@ -198,9 +198,9 @@
<button
class="default-button-secondary mx-6"
on:click={() => {
value = value.filter((el) => el != v);
value = value.filter((el) => el != v)
if (value.length == 0) {
value = undefined;
value = undefined
}
}}><Icon data={faMinus} class="mb-1" /></button
>
@@ -210,9 +210,9 @@
class="default-button-secondary mt-1"
on:click={() => {
if (value == undefined) {
value = [];
value = []
}
value = value.concat('');
value = value.concat('')
}}>Add item &nbsp;<Icon data={faPlus} class="mb-1" /></button
><span class="ml-2">{(value ?? []).length} item(s)</span>
{:else if type == 'object' && format?.startsWith('resource')}
@@ -1,11 +1,11 @@
<script lang="ts">
export let value: string;
export let placeholder = '';
export let minRows = 1;
export let maxRows = 20;
export let value: string
export let placeholder = ''
export let minRows = 1
export let maxRows = 20
$: minHeight = `${1 + minRows * 1.2}em`;
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`;
$: minHeight = `${1 + minRows * 1.2}em`
$: maxHeight = maxRows ? `${1 + maxRows * 1.2}em` : `auto`
</script>
<div class="container">
+4 -4
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import Tooltip from './Tooltip.svelte';
export let twBgColor = 'bg-blue-200';
export let twTextColor = 'text-gray-700';
export let tooltip: string | undefined = undefined;
import Tooltip from './Tooltip.svelte'
export let twBgColor = 'bg-blue-200'
export let twTextColor = 'text-gray-700'
export let tooltip: string | undefined = undefined
</script>
<span class="{twBgColor} {twTextColor} text-2xs rounded px-1 whitespace-nowrap">
+7 -7
View File
@@ -6,19 +6,19 @@
faPlay,
faShare,
faTrash
} from '@fortawesome/free-solid-svg-icons';
import { createEventDispatcher } from 'svelte';
import Icon from 'svelte-awesome';
} from '@fortawesome/free-solid-svg-icons'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
export let category: 'delete' | 'list' | 'run' | 'add' | 'edit' | 'archive' | 'share';
export let disabled: boolean = false;
const dispatch = createEventDispatcher();
export let category: 'delete' | 'list' | 'run' | 'add' | 'edit' | 'archive' | 'share'
export let disabled: boolean = false
const dispatch = createEventDispatcher()
</script>
<button
class="{$$props.class} inline-flex items-center default-button py-0 px-1 {category} default-button-secondary"
on:click={() => {
dispatch('click');
dispatch('click')
}}
{disabled}
>
@@ -1,20 +1,20 @@
<script lang="ts">
import { faSortDown } from '@fortawesome/free-solid-svg-icons';
import type { DropdownItem } from '../../utils';
import { createEventDispatcher } from 'svelte';
import Icon from 'svelte-awesome';
import Dropdown from './Dropdown.svelte';
import { faSortDown } from '@fortawesome/free-solid-svg-icons'
import type { DropdownItem } from '../../utils'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import Dropdown from './Dropdown.svelte'
export let dropdownItems: DropdownItem[] = [];
export let dropdownItems: DropdownItem[] = []
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
</script>
<div class="bg-blue-500 rounded-sm {$$props.class} inline-block py-0 my-0 ">
<button
class="inline pl-2 text-white text-sm py-0 my-0"
on:click={() => {
dispatch('clickMain');
dispatch('clickMain')
}}><slot name="name">Add script</slot></button
>
<Dropdown
@@ -1,18 +1,18 @@
<script lang="ts">
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome';
import Icon from 'svelte-awesome'
export let text: string;
export let text: string
export let viewOptions = false;
export let viewOptions = false
</script>
<button
type="submit"
class="mr-6 text-sm underline text-gray-700 inline-flex items-center"
on:click={() => {
viewOptions = !viewOptions;
viewOptions = !viewOptions
}}
>
<div>
@@ -1,56 +1,56 @@
<script lang="ts">
import { Highlight } from 'svelte-highlight';
import github from 'svelte-highlight/src/styles/github';
import { json } from 'svelte-highlight/src/languages';
import TableCustom from './TableCustom.svelte';
import { Highlight } from 'svelte-highlight'
import github from 'svelte-highlight/src/styles/github'
import { json } from 'svelte-highlight/src/languages'
import TableCustom from './TableCustom.svelte'
export let result: any;
export let result: any
let resultKind: 'json' | 'table-col' | 'table-row' | 'png' | 'file' | undefined =
inferResultKind(result);
inferResultKind(result)
function isArray(obj: any) {
return Object.prototype.toString.call(obj) === '[object Array]';
return Object.prototype.toString.call(obj) === '[object Array]'
}
function isRectangularArray(obj: any) {
if (!isArray(obj) || obj.length == 0) {
return false;
return false
}
if (
!Object.values(obj)
.map(isArray)
.reduce((a, b) => a && b)
) {
return false;
return false
}
let innerSize = obj[0].length;
let innerSize = obj[0].length
return Object.values(obj)
.map((x: any) => x.length == innerSize)
.reduce((a, b) => a && b);
.reduce((a, b) => a && b)
}
function asListOfList(obj: any): ArrayLike<ArrayLike<any>> {
return obj as ArrayLike<ArrayLike<any>>;
return obj as ArrayLike<ArrayLike<any>>
}
function inferResultKind(result: any) {
if (result) {
try {
let keys = Object.keys(result);
let keys = Object.keys(result)
if (keys.length == 1 && isRectangularArray(result[keys[0]])) {
return 'table-row';
return 'table-row'
} else if (keys.map((k) => isArray(result[k])).reduce((a, b) => a && b)) {
return 'table-col';
return 'table-col'
} else if (keys.length == 1 && keys[0] == 'png') {
return 'png';
return 'png'
} else if (keys.length == 1 && keys[0] == 'file') {
return 'file';
return 'file'
}
} catch (err) {}
}
return 'json';
return 'json'
}
</script>
+16 -16
View File
@@ -1,28 +1,28 @@
<script lang="ts">
import { clickOutside } from '../../utils';
import type { DropdownItem } from '../../utils';
import { createEventDispatcher } from 'svelte';
import Icon from 'svelte-awesome';
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons';
import { clickOutside } from '../../utils'
import type { DropdownItem } from '../../utils'
import { createEventDispatcher } from 'svelte'
import Icon from 'svelte-awesome'
import { faEllipsisH } from '@fortawesome/free-solid-svg-icons'
let open = false;
let open = false
export let dropdownItems: DropdownItem[];
export let name: string | undefined = undefined;
export let dropdownItems: DropdownItem[]
export let name: string | undefined = undefined
// The dropdown is positioned versus its first relatively positioned partent
// By default, the dropdown is positioned relative to its button
// This can cause the dropdown to be hidden if it is in an overflow-hidden div.
// In that case, set relative to false and control the dropdown positioning from the div
export let relative = true;
export let relative = true
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
function openMenu() {
open = true;
open = true
}
function handleClickOutsideMenu(event: Event) {
open = false;
open = false
}
</script>
@@ -57,9 +57,9 @@
<button
on:click={() => {
if (!item.disabled) {
open = false;
item.action && item.action();
dispatch('click', { item: item?.eventName });
open = false
item.action && item.action()
dispatch('click', { item: item?.eventName })
}
}}
class="block hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30 px-4 py-2 text-sm text-gray-700 text-left{item.separatorTop
@@ -86,7 +86,7 @@
href={item.href}
on:click={() => {
if (!item.disabled) {
open = false;
open = false
}
}}
class="block px-4 py-2 text-sm text-gray-700 hover:drop-shadow-sm hover:bg-gray-50 hover:bg-opacity-30"
+83 -83
View File
@@ -1,86 +1,86 @@
<script lang="ts">
import { page } from '$app/stores';
import type monaco from 'monaco-editor';
import { browser, mode } from '$app/env';
import { page } from '$app/stores'
import type monaco from 'monaco-editor'
import { browser, mode } from '$app/env'
import { listen } from '@codingame/monaco-jsonrpc';
import { onDestroy, onMount } from 'svelte';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
import { listen } from '@codingame/monaco-jsonrpc'
import { onDestroy, onMount } from 'svelte'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
let divEl: HTMLDivElement | null = null;
let editor: monaco.editor.IStandaloneCodeEditor;
let monaco;
export let lang = 'python';
export let code: string;
export let readOnly = false;
export let hash: string = (Math.random() + 1).toString(36).substring(2);
export let cmdEnterAction: (() => void) | undefined = undefined;
export let formatAction: (() => void) | undefined = undefined;
export let automaticLayout = true;
export let websocketAlive = { pyright: false, black: false };
let websockets: WebSocket[] = [];
let divEl: HTMLDivElement | null = null
let editor: monaco.editor.IStandaloneCodeEditor
let monaco
export let lang = 'python'
export let code: string
export let readOnly = false
export let hash: string = (Math.random() + 1).toString(36).substring(2)
export let cmdEnterAction: (() => void) | undefined = undefined
export let formatAction: (() => void) | undefined = undefined
export let automaticLayout = true
export let websocketAlive = { pyright: false, black: false }
let websockets: WebSocket[] = []
let disposeMethod: () => void | undefined;
let disposeMethod: () => void | undefined
if (browser) {
// @ts-ignore
self.MonacoEnvironment = {
getWorker: function (_moduleId: any, label: string) {
if (label === 'json') {
return new jsonWorker();
return new jsonWorker()
}
if (label === 'typescript' || label === 'javascript') {
return new tsWorker();
return new tsWorker()
}
return new editorWorker();
return new editorWorker()
}
};
}
}
export function getCode(): string {
return editor?.getValue();
return editor?.getValue()
}
export function insertAtCursor(code: string): void {
if (editor) {
editor.trigger('keyboard', 'type', { text: code });
editor.trigger('keyboard', 'type', { text: code })
}
}
export function insertAtBeginning(code: string): void {
if (editor) {
const range = new monaco.Range(1, 1, 1, 1);
const op = { range: range, text: code, forceMoveMarkers: true };
editor.executeEdits('external', [op]);
const range = new monaco.Range(1, 1, 1, 1)
const op = { range: range, text: code, forceMoveMarkers: true }
editor.executeEdits('external', [op])
}
}
export function setCode(ncode: string): void {
if (editor) {
return editor.setValue(ncode);
return editor.setValue(ncode)
} else {
code = ncode;
code = ncode
}
}
function format() {
if (editor) {
editor.getAction('editor.action.formatDocument').run();
editor.getAction('editor.action.formatDocument').run()
if (formatAction) {
formatAction();
formatAction()
}
}
}
export async function reloadWebsocket() {
closeWebsockets();
closeWebsockets()
if (lang == 'python') {
// install Monaco language client services
const { MonacoLanguageClient, CloseAction, ErrorAction, createConnection } = await import(
'@codingame/monaco-languageclient'
);
)
function createLanguageClient(connection: any, name: string, initializationOptions?: any) {
return new MonacoLanguageClient({
@@ -99,37 +99,37 @@
},
connectionProvider: {
get: (errorHandler, closeHandler) => {
return Promise.resolve(createConnection(connection, errorHandler, closeHandler));
return Promise.resolve(createConnection(connection, errorHandler, closeHandler))
}
}
});
})
}
function connectToLanguageServer(url: string, name: string, options?: any) {
try {
const webSocket = new WebSocket(url);
websockets.push(webSocket);
const webSocket = new WebSocket(url)
websockets.push(webSocket)
// listen when the web socket is opened
listen({
webSocket,
onConnection: (connection) => {
// create and start the language client
const languageClient = createLanguageClient(connection, name, options);
const disposable = languageClient.start();
websocketAlive[name] = true;
const languageClient = createLanguageClient(connection, name, options)
const disposable = languageClient.start()
websocketAlive[name] = true
connection.onClose(() => {
websocketAlive[name] = false;
websocketAlive[name] = false
try {
disposable.dispose();
disposable.dispose()
} catch (err) {
console.error('error disposing websocket', err);
console.error('error disposing websocket', err)
}
});
})
}
});
})
} catch (err) {
console.error(`connection to ${name} language server failed`);
console.error(`connection to ${name} language server failed`)
}
}
connectToLanguageServer(`wss://${$page.url.host}/ws/pyright`, 'pyright', {
@@ -141,7 +141,7 @@
extraPaths: []
}
]
});
})
connectToLanguageServer(`wss://${$page.url.host}/ws/black`, 'black', {
formatters: {
@@ -153,21 +153,21 @@
formatFiletypes: {
python: 'black'
}
});
})
}
}
function closeWebsockets() {
websockets.forEach((x) => {
try {
x.close();
x.close()
} catch (err) {
console.log('error disposing websocket', err);
console.log('error disposing websocket', err)
}
});
})
}
async function loadMonaco() {
monaco = await import('monaco-editor');
monaco = await import('monaco-editor')
if (lang == 'python') {
monaco.languages.register({
@@ -175,21 +175,21 @@
extensions: ['.py'],
aliases: ['python'],
mimetypes: ['application/text']
});
})
}
let path: string = 'unknown';
let path: string = 'unknown'
if (lang == 'python') {
path = `${hash}.py`;
path = `${hash}.py`
} else if (lang == 'json') {
path = `${hash}.json`;
path = `${hash}.json`
} else if (lang == 'javascript') {
path = `${hash}.js`;
path = `${hash}.js`
} else if (lang == 'typescript') {
path = `${hash}.ts`;
path = `${hash}.ts`
}
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${path}`));
model.updateOptions({ tabSize: 4, insertSpaces: true });
const model = monaco.editor.createModel(code, lang, monaco.Uri.parse(`file:///${path}`))
model.updateOptions({ tabSize: 4, insertSpaces: true })
editor = monaco.editor.create(divEl as HTMLDivElement, {
model: model,
value: code,
@@ -206,21 +206,21 @@
minimap: {
enabled: false
}
});
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, function () {
format();
});
format()
})
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () {
if (cmdEnterAction) {
cmdEnterAction();
cmdEnterAction()
}
});
})
editor.onDidChangeModelContent((event) => {
code = getCode();
});
code = getCode()
})
if (lang == 'json') {
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
@@ -228,7 +228,7 @@
allowComments: false,
schemas: [],
enableSchemaRequest: true
});
})
}
if (lang == 'typescript') {
@@ -237,7 +237,7 @@
target: monaco.languages.typescript.ScriptTarget.ES6,
allowNonTsExtensions: true,
noLib: true
});
})
monaco.languages.typescript.typescriptDefaults.addExtraLib(
`
@@ -277,39 +277,39 @@ export const previous_result: any;
export const params: any;
`,
'file:///node_modules/@types/windmill/index.d.ts'
);
)
}
if (lang == 'python') {
const { MonacoServices } = await import('@codingame/monaco-languageclient');
const { MonacoServices } = await import('@codingame/monaco-languageclient')
MonacoServices.install(monaco);
MonacoServices.install(monaco)
}
reloadWebsocket();
reloadWebsocket()
return () => {
if (editor) {
try {
editor.dispose();
editor.dispose()
} catch (err) {
console.log('error disposing editor', err);
console.log('error disposing editor', err)
}
}
};
}
}
onMount(() => {
if (browser) {
loadMonaco().then((x) => (disposeMethod = x));
loadMonaco().then((x) => (disposeMethod = x))
}
});
})
onDestroy(() => {
if (disposeMethod) {
disposeMethod();
disposeMethod()
}
});
})
</script>
<!-- <button class="default-button px-6 max-h-8" type="button" on:click={format}>
@@ -1,12 +1,12 @@
<script lang="ts">
import Required from './Required.svelte';
import Required from './Required.svelte'
export let label: string;
export let format: string = '';
export let contentEncoding = '';
export let type: string | undefined = undefined;
export let required = false;
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined;
export let label: string
export let format: string = ''
export let contentEncoding = ''
export let type: string | undefined = undefined
export let required = false
export let itemsType: { type?: 'string' | 'number' } | undefined = undefined
</script>
<h3>
@@ -1,20 +1,20 @@
<script lang="ts">
import { type Flow, FlowService } from '../../gen';
import { type Flow, FlowService } from '../../gen'
import { sendUserToast } from '../../utils';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import Path from './Path.svelte';
import SvelteMarkdown from 'svelte-markdown';
import { workspaceStore } from '../../stores';
import ScriptSchema from './ScriptSchema.svelte';
import Required from './Required.svelte';
import FlowEditor from './FlowEditor.svelte';
import { sendUserToast } from '../../utils'
import { page } from '$app/stores'
import { goto } from '$app/navigation'
import Path from './Path.svelte'
import SvelteMarkdown from 'svelte-markdown'
import { workspaceStore } from '../../stores'
import ScriptSchema from './ScriptSchema.svelte'
import Required from './Required.svelte'
import FlowEditor from './FlowEditor.svelte'
export let flow: Flow;
export let initialPath: string = '';
export let flow: Flow
export let initialPath: string = ''
$: step = Number($page.url.searchParams.get('step')) || 1;
$: step = Number($page.url.searchParams.get('step')) || 1
async function saveFlow(): Promise<void> {
try {
@@ -28,7 +28,7 @@
value: flow.value,
schema: flow.schema
}
});
})
} else {
await FlowService.updateFlow({
workspace: $workspaceStore!,
@@ -40,16 +40,16 @@
value: flow.value,
schema: flow.schema
}
});
})
}
sendUserToast(`Success! flow saved at ${flow.path}`);
goto(`/flows/get/${flow.path}`);
sendUserToast(`Success! flow saved at ${flow.path}`)
goto(`/flows/get/${flow.path}`)
} catch (error) {
if (error.status === 400) {
sendUserToast(error.body, true);
sendUserToast(error.body, true)
} else {
sendUserToast(`Ooops.Something bad happened: ${error}`, true);
console.error(error);
sendUserToast(`Ooops.Something bad happened: ${error}`, true)
console.error(error)
}
}
}
@@ -59,12 +59,12 @@
}
async function changeStep(step: number) {
goto(`?step=${step}`);
goto(`?step=${step}`)
}
$: {
$page.url.searchParams.set('state', btoa(JSON.stringify(flow)));
history.replaceState({}, '', $page.url);
$page.url.searchParams.set('state', btoa(JSON.stringify(flow)))
history.replaceState({}, '', $page.url)
}
</script>
@@ -78,7 +78,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(1);
changeStep(1)
}}>Step 1: Metadata</button
>
<button
@@ -86,7 +86,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(2);
changeStep(2)
}}>Step 2: Flow</button
>
<button
@@ -94,7 +94,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(3);
changeStep(3)
}}>Step 3: UI customisation</button
>
</div>
@@ -105,7 +105,7 @@
(flow.path == undefined || flow.path == '' || flow.path.split('/')[2] == '')}
class="default-button px-6 max-h-8"
on:click={() => {
changeStep(step + 1);
changeStep(step + 1)
}}>Next</button
>
{#if step == 2}
@@ -1,22 +1,22 @@
<script lang="ts">
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import type { Schema } from '../../common';
import { emptySchema } from '../../utils';
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import type { Schema } from '../../common'
import { emptySchema } from '../../utils'
import Icon from 'svelte-awesome';
import Icon from 'svelte-awesome'
import { type Flow, FlowModuleValue, ScriptService } from '../../gen';
import SchemaEditor from './SchemaEditor.svelte';
import type SchemaForm from './SchemaForm.svelte';
import { workspaceStore } from '../../stores';
import ModuleStep from './ModuleStep.svelte';
import FlowPreview from './FlowPreview.svelte';
import { type Flow, FlowModuleValue, ScriptService } from '../../gen'
import SchemaEditor from './SchemaEditor.svelte'
import type SchemaForm from './SchemaForm.svelte'
import { workspaceStore } from '../../stores'
import ModuleStep from './ModuleStep.svelte'
import FlowPreview from './FlowPreview.svelte'
export let flow: Flow;
export let flow: Flow
let args: Record<string, any> = {};
let schemas: Schema[] = [];
let schemaForms: (SchemaForm | undefined)[] = [];
let args: Record<string, any> = {}
let schemas: Schema[] = []
let schemaForms: (SchemaForm | undefined)[] = []
export async function loadSchemas() {
await Promise.all(
@@ -25,47 +25,47 @@
const script = await ScriptService.getScriptByPath({
workspace: $workspaceStore!,
path: x.value.path ?? ''
});
})
if (
JSON.stringify(Object.keys(script.schema?.properties ?? {}).sort()) !=
JSON.stringify(Object.keys(x.input_transform).sort())
) {
let it = {};
let it = {}
Object.keys(script.schema?.properties ?? {}).map(
(x) =>
(it[x] = {
type: 'static',
value: ''
})
);
schemaForms[i]?.setArgs(it);
)
schemaForms[i]?.setArgs(it)
}
schemas[i] = script.schema ?? emptySchema();
schemas[i] = script.schema ?? emptySchema()
} else {
schemaForms[i]?.setArgs({});
schemas[i] = emptySchema();
schemaForms[i]?.setArgs({})
schemas[i] = emptySchema()
}
})
);
schemas = schemas;
)
schemas = schemas
if (flow.value.modules.length == 0) {
addModule();
addModule()
}
}
function addModule() {
schemaForms.push(undefined);
schemaForms.push(undefined)
let newModule = {
value: { type: FlowModuleValue.type.SCRIPT, path: '' },
input_transform: {}
};
flow.value.modules = flow.value.modules.concat(newModule);
schemas.push(emptySchema());
}
flow.value.modules = flow.value.modules.concat(newModule)
schemas.push(emptySchema())
}
$: $workspaceStore && loadSchemas();
$: $workspaceStore && loadSchemas()
</script>
<!-- <PageHeader title="Flow" /> -->
@@ -87,8 +87,8 @@
const script = await ScriptService.getScriptByPath({
workspace: $workspaceStore ?? '',
path: flow.value.modules[0].value.path ?? ''
});
flow.schema = script.schema;
})
flow.schema = script.schema
}}
>Copy from step 1's schema
</button>
@@ -1,41 +1,41 @@
<script lang="ts">
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import { sendUserToast, truncateRev } from '../../utils';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
import { sendUserToast, truncateRev } from '../../utils'
import Icon from 'svelte-awesome';
import Icon from 'svelte-awesome'
import { type Flow, Job, JobService, InputTransform } from '../../gen';
import { type Flow, Job, JobService, InputTransform } from '../../gen'
import { workspaceStore } from '../../stores';
import RunForm from './RunForm.svelte';
import FlowStatusViewer from './FlowStatusViewer.svelte';
import { onDestroy } from 'svelte';
import ChevronButton from './ChevronButton.svelte';
import DisplayResult from './DisplayResult.svelte';
import Tabs from './Tabs.svelte';
import type { Schema } from '../../common';
import { workspaceStore } from '../../stores'
import RunForm from './RunForm.svelte'
import FlowStatusViewer from './FlowStatusViewer.svelte'
import { onDestroy } from 'svelte'
import ChevronButton from './ChevronButton.svelte'
import DisplayResult from './DisplayResult.svelte'
import Tabs from './Tabs.svelte'
import type { Schema } from '../../common'
export let i: number;
export let flow: Flow;
export let schemas: Schema[] = [];
export let i: number
export let flow: Flow
export let schemas: Schema[] = []
export let args: Record<string, any> = {};
export let args: Record<string, any> = {}
let stepArgs: Record<string, any> = {};
let stepArgs: Record<string, any> = {}
let tab: 'upto' | 'justthis' = 'upto';
let viewPreview = false;
let intervalId: NodeJS.Timer;
let tab: 'upto' | 'justthis' = 'upto'
let viewPreview = false
let intervalId: NodeJS.Timer
let uptoText =
i == flow.value.modules.length - 1 ? 'Preview whole flow' : 'Preview up to this step';
let job: Job | undefined;
let jobs = [];
let jobId: string;
i == flow.value.modules.length - 1 ? 'Preview whole flow' : 'Preview up to this step'
let job: Job | undefined
let jobs = []
let jobId: string
async function runPreview(args) {
intervalId && clearInterval(intervalId);
const newFlow = tab == 'upto' ? truncateFlow(flow) : extractStep(flow);
intervalId && clearInterval(intervalId)
const newFlow = tab == 'upto' ? truncateFlow(flow) : extractStep(flow)
jobId = await JobService.runFlowPreview({
workspace: $workspaceStore ?? '',
requestBody: {
@@ -43,46 +43,46 @@
value: newFlow.value,
path: newFlow.path
}
});
jobs = [];
intervalId = setInterval(loadJob, 1000);
sendUserToast(`started preview ${truncateRev(jobId, 10)}`);
})
jobs = []
intervalId = setInterval(loadJob, 1000)
sendUserToast(`started preview ${truncateRev(jobId, 10)}`)
}
function truncateFlow(flow: Flow): Flow {
const localFlow = JSON.parse(JSON.stringify(flow));
localFlow.value.modules = flow.value.modules.slice(0, i + 1);
return localFlow;
const localFlow = JSON.parse(JSON.stringify(flow))
localFlow.value.modules = flow.value.modules.slice(0, i + 1)
return localFlow
}
function extractStep(flow: Flow): Flow {
const localFlow = JSON.parse(JSON.stringify(flow));
localFlow.value.modules = flow.value.modules.slice(i, i + 1);
localFlow.schema = schemas[i];
stepArgs = {};
const localFlow = JSON.parse(JSON.stringify(flow))
localFlow.value.modules = flow.value.modules.slice(i, i + 1)
localFlow.schema = schemas[i]
stepArgs = {}
Object.entries(flow.value.modules[i].input_transform).forEach((x) => {
if (x[1].type == InputTransform.type.STATIC) {
stepArgs[x[0]] = x[1].value;
stepArgs[x[0]] = x[1].value
}
});
return localFlow;
})
return localFlow
}
async function loadJob() {
try {
job = await JobService.getJob({ workspace: $workspaceStore!, id: jobId });
job = await JobService.getJob({ workspace: $workspaceStore!, id: jobId })
if (job?.type == 'CompletedJob') {
//only CompletedJob has success property
clearInterval(intervalId);
clearInterval(intervalId)
}
} catch (err) {
sendUserToast(err, true);
sendUserToast(err, true)
}
}
onDestroy(() => {
intervalId && clearInterval(intervalId);
});
intervalId && clearInterval(intervalId)
})
</script>
<h2 class="mb-5 mt-2">
@@ -90,7 +90,7 @@
type="submit"
class="underline text-gray-700 inline-flex items-center"
on:click={() => {
viewPreview = !viewPreview;
viewPreview = !viewPreview
}}
>
<div>
@@ -1,24 +1,18 @@
<script lang="ts">
import { faHourglassHalf, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons';
import { truncateRev } from '../../utils';
import { faHourglassHalf, faSpinner, faTimes } from '@fortawesome/free-solid-svg-icons'
import { truncateRev } from '../../utils'
import Icon from 'svelte-awesome';
import { check } from 'svelte-awesome/icons';
import Icon from 'svelte-awesome'
import { check } from 'svelte-awesome/icons'
import {
CompletedJob,
FlowModuleValue,
FlowStatusModule,
JobService,
QueuedJob
} from '../../gen';
import { workspaceStore } from '../../stores';
import DisplayResult from './DisplayResult.svelte';
import ChevronButton from './ChevronButton.svelte';
import JobStatus from './JobStatus.svelte';
import { CompletedJob, FlowModuleValue, FlowStatusModule, JobService, QueuedJob } from '../../gen'
import { workspaceStore } from '../../stores'
import DisplayResult from './DisplayResult.svelte'
import ChevronButton from './ChevronButton.svelte'
import JobStatus from './JobStatus.svelte'
export let job: QueuedJob | CompletedJob;
export let jobs: (CompletedJob | undefined)[];
export let job: QueuedJob | CompletedJob
export let jobs: (CompletedJob | undefined)[]
function loadResults() {
job?.flow_status?.modules?.forEach(async (x, i) => {
@@ -26,13 +20,13 @@
(i >= jobs.length && x.type == FlowStatusModule.type.SUCCESS) ||
x.type == FlowStatusModule.type.FAILURE
) {
jobs.push(undefined);
jobs[i] = await JobService.getCompletedJob({ workspace: $workspaceStore!, id: x.job! });
jobs.push(undefined)
jobs[i] = await JobService.getCompletedJob({ workspace: $workspaceStore!, id: x.job! })
}
});
})
}
$: $workspaceStore && job && loadResults();
$: $workspaceStore && job && loadResults()
</script>
<div class="flow-root max-w-lg w-full p-4">
@@ -1,26 +1,26 @@
<script lang="ts">
import { workspaceStore } from '../../stores';
import { workspaceStore } from '../../stores'
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
import Modal from '../../routes/components/Modal.svelte';
import { Group, GroupService, UserService } from '../../gen';
import AutoComplete from 'simple-svelte-autocomplete';
import PageHeader from './PageHeader.svelte';
import TableCustom from './TableCustom.svelte';
import { canWrite, getUser } from '../../utils';
import Modal from '../../routes/components/Modal.svelte'
import { Group, GroupService, UserService } from '../../gen'
import AutoComplete from 'simple-svelte-autocomplete'
import PageHeader from './PageHeader.svelte'
import TableCustom from './TableCustom.svelte'
import { canWrite, getUser } from '../../utils'
let name = '';
let modal: Modal;
let can_write = false;
let name = ''
let modal: Modal
let can_write = false
let group: Group | undefined;
let members: { name: string; isAdmin: boolean }[] = [];
let usernames: string[] = [];
let username: string = '';
let group: Group | undefined
let members: { name: string; isAdmin: boolean }[] = []
let usernames: string[] = []
let username: string = ''
async function loadUsernames(): Promise<void> {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
}
$: {
@@ -29,15 +29,15 @@
return {
name: x,
isAdmin: x in (group?.extra_perms ?? {}) && (group?.extra_perms ?? {})[name]
};
});
}
})
}
}
export function openModal(newName: string): void {
name = newName;
loadGroup();
loadUsernames();
modal.openModal();
name = newName
loadGroup()
loadUsernames()
modal.openModal()
}
async function addToGroup() {
@@ -45,14 +45,14 @@
workspace: $workspaceStore ?? '',
name,
requestBody: { username }
});
loadGroup();
})
loadGroup()
}
async function loadGroup(): Promise<void> {
group = await GroupService.getGroup({ workspace: $workspaceStore!, name });
const user = await getUser($workspaceStore!);
can_write = canWrite(group.name!, group.extra_perms ?? {}, user);
group = await GroupService.getGroup({ workspace: $workspaceStore!, name })
const user = await getUser($workspaceStore!)
can_write = canWrite(group.name!, group.extra_perms ?? {}, user)
}
</script>
@@ -88,8 +88,8 @@
workspace: $workspaceStore ?? '',
name: group?.name ?? '',
requestBody: { username: name }
});
loadGroup();
})
loadGroup()
}}>remove</button
>
{/if}</td
@@ -1,16 +1,16 @@
<script lang="ts">
import Mysql from './icons/Mysql.svelte';
import Mail from './icons/Mail.svelte';
import DbIcon from './icons/DbIcon.svelte';
import PostgresIcon from './icons/PostgresIcon.svelte';
import Icon from 'svelte-awesome';
import { faSlack } from '@fortawesome/free-brands-svg-icons';
import Slack from './icons/Slack.svelte';
import Mysql from './icons/Mysql.svelte'
import Mail from './icons/Mail.svelte'
import DbIcon from './icons/DbIcon.svelte'
import PostgresIcon from './icons/PostgresIcon.svelte'
import Icon from 'svelte-awesome'
import { faSlack } from '@fortawesome/free-brands-svg-icons'
import Slack from './icons/Slack.svelte'
export let name: string;
export let after: boolean = false;
export let height = '24px';
export let width = '24px';
export let name: string
export let after: boolean = false
export let height = '24px'
export let width = '24px'
</script>
<div class="flex flex-row gap-2">
@@ -1,32 +1,32 @@
<script lang="ts">
import { sendUserToast } from '../../utils';
import Switch from '../components/Switch.svelte';
import { sendUserToast } from '../../utils'
import Switch from '../components/Switch.svelte'
import type Modal from './Modal.svelte';
import { createEventDispatcher } from 'svelte';
import { UserService } from '../../gen';
import type Modal from './Modal.svelte'
import { createEventDispatcher } from 'svelte'
import { UserService } from '../../gen'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
let valid = true;
let valid = true
let modal: Modal;
let modal: Modal
export function openModal(): void {
modal.openModal();
modal.openModal()
}
let email: string;
let is_super_admin = false;
let password: string;
let name: string | undefined;
let company: string | undefined;
let email: string
let is_super_admin = false
let password: string
let name: string | undefined
let company: string | undefined
function handleKeyUp(event: KeyboardEvent) {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 13 || key === 'Enter') {
event.preventDefault();
addUser();
event.preventDefault()
addUser()
}
}
@@ -39,9 +39,9 @@
name,
company
}
});
sendUserToast(`Successfully added ${email}. Welcome to them!`);
dispatch('new');
})
sendUserToast(`Successfully added ${email}. Welcome to them!`)
dispatch('new')
}
</script>
@@ -57,7 +57,7 @@
class="ml-4 w-40 {valid ? 'default-button' : 'default-button-disabled'}"
type="button"
on:click={() => {
addUser();
addUser()
}}
disabled={email == undefined || password == undefined}
>
@@ -1,30 +1,30 @@
<script lang="ts">
import { sendUserToast } from '../../utils';
import Switch from '../components/Switch.svelte';
import { sendUserToast } from '../../utils'
import Switch from '../components/Switch.svelte'
import type Modal from './Modal.svelte';
import { createEventDispatcher } from 'svelte';
import { workspaceStore } from '../../stores';
import { WorkspaceService } from '../../gen';
import type Modal from './Modal.svelte'
import { createEventDispatcher } from 'svelte'
import { workspaceStore } from '../../stores'
import { WorkspaceService } from '../../gen'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
let valid = true;
let valid = true
let modal: Modal;
let modal: Modal
export function openModal(): void {
modal.openModal();
modal.openModal()
}
let email: string;
let is_admin = false;
let email: string
let is_admin = false
function handleKeyUp(event: KeyboardEvent) {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 13 || key === 'Enter') {
event.preventDefault();
inviteUser();
event.preventDefault()
inviteUser()
}
}
@@ -35,9 +35,9 @@
email,
is_admin
}
});
sendUserToast(`Successfully invited ${email}. Welcome to them!`);
dispatch('new');
})
sendUserToast(`Successfully invited ${email}. Welcome to them!`)
dispatch('new')
}
</script>
@@ -49,7 +49,7 @@
class="ml-4 w-40 {valid ? 'default-button' : 'default-button-disabled'}"
type="button"
on:click={() => {
inviteUser();
inviteUser()
}}
disabled={email == undefined}
>
@@ -1,37 +1,37 @@
<script lang="ts">
import Fuse from 'fuse.js';
import Fuse from 'fuse.js'
import Modal from './Modal.svelte';
import Modal from './Modal.svelte'
type Item = Record<string, any>;
export let pickCallback: (path: string, f: string) => void;
export let loadItems: () => Promise<Item[]>;
export let extraField: string;
export let itemName: string;
export let closeOnClick = true;
type Item = Record<string, any>
export let pickCallback: (path: string, f: string) => void
export let loadItems: () => Promise<Item[]>
export let extraField: string
export let itemName: string
export let closeOnClick = true
let items: Item[] = [];
let filteredItems: Item[] = [];
let itemsFilter = '';
let items: Item[] = []
let filteredItems: Item[] = []
let itemsFilter = ''
const fuseOptions = {
includeScore: false,
keys: ['path', extraField]
};
const fuse: Fuse<Item> = new Fuse(items, fuseOptions);
}
const fuse: Fuse<Item> = new Fuse(items, fuseOptions)
export function openModal() {
loadItems().then((v) => {
items = v;
fuse.setCollection(items);
});
modal.openModal();
items = v
fuse.setCollection(items)
})
modal.openModal()
}
$: filteredItems =
itemsFilter.length > 0 ? fuse.search(itemsFilter).map((value) => value.item) : items;
itemsFilter.length > 0 ? fuse.search(itemsFilter).map((value) => value.item) : items
let modal: Modal;
let modal: Modal
</script>
<Modal bind:this={modal} z="z-30">
@@ -46,9 +46,9 @@
class="py-4 px-1 gap-1 flex flex-col hover:bg-white hover:border text-black cursor-pointer"
on:click={() => {
if (closeOnClick) {
modal.closeModal();
modal.closeModal()
}
pickCallback(obj['path'], obj[extraField]);
pickCallback(obj['path'], obj[extraField])
}}
>
<p class="text-sm font-semibold">
@@ -5,17 +5,17 @@
faClock,
faHourglassHalf,
faTimes
} from '@fortawesome/free-solid-svg-icons';
import { displayDate, forLater } from '../../utils';
} from '@fortawesome/free-solid-svg-icons'
import { displayDate, forLater } from '../../utils'
import Icon from 'svelte-awesome';
import { check } from 'svelte-awesome/icons';
import Icon from 'svelte-awesome'
import { check } from 'svelte-awesome/icons'
import type { CompletedJob, QueuedJob } from '../../gen';
import type { CompletedJob, QueuedJob } from '../../gen'
const SMALL_ICON_SCALE = 0.7;
const SMALL_ICON_SCALE = 0.7
export let job: QueuedJob | CompletedJob | undefined;
export let job: QueuedJob | CompletedJob | undefined
</script>
{#if job && 'success' in job && job.success}
+14 -14
View File
@@ -1,27 +1,27 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
export let open: boolean = false;
export let z = 'z-30';
export let open: boolean = false
export let z = 'z-30'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
export function closeModal(): void {
open = false;
dispatch('close');
open = false
dispatch('close')
}
export function openModal(): void {
open = true;
dispatch('open');
open = true
dispatch('open')
}
function handleKeyUp(event: KeyboardEvent): void {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 27 || key === 'Escape' || key === 'Esc') {
if (open) {
event.preventDefault();
closeModal();
event.preventDefault()
closeModal()
}
}
}
@@ -39,8 +39,8 @@
<div class="flex flex-row justify-between p-2 bg-white border-b border-gray-200">
<button
on:click={() => {
open = false;
closeModal();
open = false
closeModal()
}}
>
<svg
@@ -68,7 +68,7 @@
<div class="flex flex-row justify-between p-2 ">
<button
on:click={() => {
closeModal();
closeModal()
}}
>
<svg
@@ -1,49 +1,49 @@
<script lang="ts">
import { workspaceStore } from '../../stores';
import { workspaceStore } from '../../stores'
import type { Schema } from '../../common';
import { ScriptService, type Flow, type FlowModule } from '../../gen';
import type { Schema } from '../../common'
import { ScriptService, type Flow, type FlowModule } from '../../gen'
import SchemaForm from './SchemaForm.svelte';
import ScriptPicker from './ScriptPicker.svelte';
import { emptySchema } from '../../utils';
import FlowPreview from './FlowPreview.svelte';
import SchemaForm from './SchemaForm.svelte'
import ScriptPicker from './ScriptPicker.svelte'
import { emptySchema } from '../../utils'
import FlowPreview from './FlowPreview.svelte'
export let flow: Flow;
export let i: number;
export let mod: FlowModule;
export let args: Record<string, any> = {};
export let flow: Flow
export let i: number
export let mod: FlowModule
export let args: Record<string, any> = {}
export let schemas: Schema[] = [];
export let schemaForms: (SchemaForm | undefined)[] = [];
export let schemas: Schema[] = []
export let schemaForms: (SchemaForm | undefined)[] = []
export async function loadSchema() {
if (mod.value.path) {
const script = await ScriptService.getScriptByPath({
workspace: $workspaceStore!,
path: mod.value.path ?? ''
});
})
if (
JSON.stringify(Object.keys(script.schema?.properties ?? {}).sort()) !=
JSON.stringify(Object.keys(mod.input_transform).sort())
) {
let it = {};
let it = {}
Object.keys(script.schema?.properties ?? {}).map(
(x) =>
(it[x] = {
type: 'static',
value: ''
})
);
schemaForms[i]?.setArgs(it);
)
schemaForms[i]?.setArgs(it)
}
schemas[i] = script.schema ?? emptySchema();
schemas[i] = script.schema ?? emptySchema()
} else {
schemaForms[i]?.setArgs({});
schemas[i] = emptySchema();
schemaForms[i]?.setArgs({})
schemas[i] = emptySchema()
}
schemas = schemas;
schemas = schemas
}
</script>
@@ -56,10 +56,10 @@
<button
class="text-xs default-button-secondary max-h-6 place-self-end"
on:click={() => {
flow.value.modules.splice(i, 1);
schemas.splice(i, 1);
schemaForms.splice(i, 1);
flow = flow;
flow.value.modules.splice(i, 1)
schemas.splice(i, 1)
schemaForms.splice(i, 1)
flow = flow
}}
>Remove this step
</button>
@@ -1,11 +1,11 @@
<script>
// @ts-nocheck
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
export let id = '';
export let value = [];
export let readonly = false;
export let placeholder = '';
import { onMount } from 'svelte'
import { fly } from 'svelte/transition'
export let id = ''
export let value = []
export let readonly = false
export let placeholder = ''
let input,
inputValue,
@@ -14,51 +14,51 @@
showOptions = false,
selected = {},
first = true,
slot;
slot
const iconClearPath =
'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z';
'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'
onMount(() => {
slot.querySelectorAll('option').forEach((o) => {
o.selected && !value.includes(o.value) && (value = [...value, o.value]);
options = [...options, { value: o.value, name: o.textContent }];
});
o.selected && !value.includes(o.value) && (value = [...value, o.value])
options = [...options, { value: o.value, name: o.textContent }]
})
value &&
(selected = options.reduce(
(obj, op) => (value.includes(op.value) ? { ...obj, [op.value]: op } : obj),
{}
));
first = false;
});
))
first = false
})
$: if (!first) value = Object.values(selected).map((o) => o.value);
$: if (!first) value = Object.values(selected).map((o) => o.value)
$: filtered = options.filter((o) =>
inputValue ? o.name.toLowerCase().includes(inputValue.toLowerCase()) : o
);
)
$: if ((activeOption && !filtered.includes(activeOption)) || (!activeOption && inputValue))
activeOption = filtered[0];
activeOption = filtered[0]
function add(token) {
if (!readonly) selected[token.value] = token;
if (!readonly) selected[token.value] = token
}
function remove(value) {
if (!readonly) {
const { [value]: val, ...rest } = selected;
selected = rest;
const { [value]: val, ...rest } = selected
selected = rest
}
}
function optionsVisibility(show) {
if (readonly) return;
if (readonly) return
if (typeof show === 'boolean') {
showOptions = show;
show && input.focus();
showOptions = show
show && input.focus()
} else {
showOptions = !showOptions;
showOptions = !showOptions
}
if (!showOptions) {
activeOption = undefined;
activeOption = undefined
}
}
@@ -66,45 +66,45 @@
if (e.keyCode === 13) {
Object.keys(selected).includes(activeOption.value)
? remove(activeOption.value)
: add(activeOption);
inputValue = '';
: add(activeOption)
inputValue = ''
}
if ([38, 40].includes(e.keyCode)) {
// up and down arrows
const increment = e.keyCode === 38 ? -1 : 1;
const calcIndex = filtered.indexOf(activeOption) + increment;
const increment = e.keyCode === 38 ? -1 : 1
const calcIndex = filtered.indexOf(activeOption) + increment
activeOption =
calcIndex < 0
? filtered[filtered.length - 1]
: calcIndex === filtered.length
? filtered[0]
: filtered[calcIndex];
: filtered[calcIndex]
}
}
function handleBlur(e) {
optionsVisibility(false);
optionsVisibility(false)
}
function handleTokenClick(e) {
if (e.target.closest('.token-remove')) {
e.stopPropagation();
remove(e.target.closest('.token').dataset.id);
e.stopPropagation()
remove(e.target.closest('.token').dataset.id)
} else if (e.target.closest('.remove-all')) {
selected = [];
inputValue = '';
selected = []
inputValue = ''
} else {
optionsVisibility(true);
optionsVisibility(true)
}
}
function handleOptionMousedown(e) {
const value = e.target.dataset.value;
const value = e.target.dataset.value
if (selected[value]) {
remove(value);
remove(value)
} else {
add(options.filter((o) => o.value === value)[0]);
input.focus();
add(options.filter((o) => o.value === value)[0])
input.focus()
}
}
</script>
@@ -1,48 +1,48 @@
<script lang="ts">
import { ResourceService } from '../../gen';
import { ResourceService } from '../../gen'
import ResourcePicker from './ResourcePicker.svelte';
import { workspaceStore } from '../../stores';
import SchemaForm from './SchemaForm.svelte';
import RadioButton from './RadioButton.svelte';
import ResourcePicker from './ResourcePicker.svelte'
import { workspaceStore } from '../../stores'
import SchemaForm from './SchemaForm.svelte'
import RadioButton from './RadioButton.svelte'
export let format: string;
export let value: any;
export let format: string
export let value: any
function isString(value: any) {
return typeof value === 'string' || value instanceof String;
return typeof value === 'string' || value instanceof String
}
let path: string =
isString(value) && value.length >= '$res:'.length ? value.substr('$res:'.length) : undefined;
let args: Record<string, any> = {};
isString(value) && value.length >= '$res:'.length ? value.substr('$res:'.length) : undefined
let args: Record<string, any> = {}
if (!isString(value) && value) {
console.log(value);
args = value;
console.log(value)
args = value
}
let schema: any | undefined = undefined;
let isValid = true;
let resourceTypeName: string = '';
let schema: any | undefined = undefined
let isValid = true
let resourceTypeName: string = ''
async function loadSchema(format: string) {
resourceTypeName = format.substring('resource-'.length);
resourceTypeName = format.substring('resource-'.length)
schema = (
await ResourceService.getResourceType({ workspace: $workspaceStore!, path: resourceTypeName })
).schema;
).schema
}
let option: 'resource' | 'raw' = isString(value) || value == undefined ? 'resource' : 'raw';
let option: 'resource' | 'raw' = isString(value) || value == undefined ? 'resource' : 'raw'
$: {
if (option == 'resource') {
value = `$res:${path}`;
value = `$res:${path}`
} else {
value = args;
value = args
}
}
$: format.startsWith('resource-') && loadSchema(format);
$: format.startsWith('resource-') && loadSchema(format)
</script>
<div class="max-w-lg">
@@ -1,17 +1,17 @@
<script lang="ts">
import RadioButton from './RadioButton.svelte';
import ResourceTypePicker from './ResourceTypePicker.svelte';
import RadioButton from './RadioButton.svelte'
import ResourceTypePicker from './ResourceTypePicker.svelte'
export let format: string | undefined;
export let format: string | undefined
let kind: 'resource' | 'none' = format?.startsWith('resource') ? 'resource' : 'none';
let kind: 'resource' | 'none' = format?.startsWith('resource') ? 'resource' : 'none'
let resource: string | undefined = format?.startsWith('resource-')
? format.substring('resource-'.length)
: undefined;
: undefined
$: format =
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined;
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined
</script>
<RadioButton
@@ -1,9 +1,9 @@
<script lang="ts">
import Tooltip from './Tooltip.svelte';
import Tooltip from './Tooltip.svelte'
export let title: string;
export let tooltip: string = '';
export let primary: boolean = true;
export let title: string
export let tooltip: string = ''
export let primary: boolean = true
</script>
<div class="flex flex-col sm:flex-row justify-between mt-4 mb-2">
+14 -14
View File
@@ -1,31 +1,31 @@
<script lang="ts">
// @ts-nocheck
import { onMount } from 'svelte';
export let password: string;
export let label = 'password';
export let placeholder = '******';
import { onMount } from 'svelte'
export let password: string
export let label = 'password'
export let placeholder = '******'
onMount(() => {
const passwordToggle = document.querySelector('.js-password-toggle');
const passwordToggle = document.querySelector('.js-password-toggle')
if (passwordToggle) {
passwordToggle.addEventListener('change', function () {
const password = document.querySelector('.js-password'),
passwordLabel = document.querySelector('.js-password-label');
passwordLabel = document.querySelector('.js-password-label')
if (password.type === 'password') {
password.type = 'text';
passwordLabel.innerHTML = 'hide';
password.type = 'text'
passwordLabel.innerHTML = 'hide'
} else {
password.type = 'password';
passwordLabel.innerHTML = 'show';
password.type = 'password'
passwordLabel.innerHTML = 'show'
}
password.focus();
});
password.focus()
})
} else {
throw Error('Password component is undefined');
throw Error('Password component is undefined')
}
});
})
</script>
<label class="block text-gray-700" for="password"> {label} </label>
+31 -31
View File
@@ -1,75 +1,75 @@
<script lang="ts">
import { type Meta, pathToMeta } from '../../common';
import { type Meta, pathToMeta } from '../../common'
import type { Group } from '../../gen';
import { GroupService } from '../../gen';
import Tooltip from './Tooltip.svelte';
import { userStore, workspaceStore } from '../../stores';
import { sleep } from '../../utils';
import type { Group } from '../../gen'
import { GroupService } from '../../gen'
import Tooltip from './Tooltip.svelte'
import { userStore, workspaceStore } from '../../stores'
import { sleep } from '../../utils'
export let meta: Meta = {
ownerKind: 'user',
owner: '',
name: ''
};
export let namePlaceholder = '';
export let initialPath: string;
export let path = '';
}
export let namePlaceholder = ''
export let initialPath: string
export let path = ''
let groups: Group[] = [];
let error = '';
let groups: Group[] = []
let error = ''
$: {
path = [meta.ownerKind === 'group' ? 'g' : 'u', meta.owner, meta.name].join('/');
path = [meta.ownerKind === 'group' ? 'g' : 'u', meta.owner, meta.name].join('/')
}
export function getPath() {
return path;
return path
}
export async function reset() {
if (path == '' || path == 'u//') {
meta.ownerKind = 'user';
meta.ownerKind = 'user'
while ($userStore == undefined) {
await sleep(500);
await sleep(500)
}
meta.owner = $userStore!.username;
meta.name = '';
meta.owner = $userStore!.username
meta.name = ''
} else {
meta = pathToMeta(path);
meta = pathToMeta(path)
}
}
$: validateName(meta);
$: validateName(meta)
async function loadGroups(): Promise<void> {
groups = await GroupService.listGroups({ workspace: $workspaceStore! });
groups = await GroupService.listGroups({ workspace: $workspaceStore! })
}
function validateName(meta: Meta): void {
if (meta.name == undefined || meta.name == '') {
error = 'choose a name';
return;
error = 'choose a name'
return
}
const regex = new RegExp(/^[\w-]+(\/[\w-]+)*$/);
const regex = new RegExp(/^[\w-]+(\/[\w-]+)*$/)
if (regex.test(meta.name)) {
error = '';
error = ''
} else {
error = 'This name is not valid. ';
error = 'This name is not valid. '
}
}
$: {
if ($workspaceStore) {
loadGroups();
loadGroups()
}
}
$: {
if (initialPath == undefined || initialPath == '') {
reset();
reset()
} else {
meta = pathToMeta(initialPath);
meta = pathToMeta(initialPath)
}
}
</script>
@@ -87,9 +87,9 @@
bind:value={meta.ownerKind}
on:change={() => {
if (meta.ownerKind === 'group') {
meta.owner = 'all';
meta.owner = 'all'
} else {
meta.owner = $userStore?.username ?? '';
meta.owner = $userStore?.username ?? ''
}
}}
>
@@ -1,12 +1,12 @@
<script lang="ts">
export let label = '';
export let options: [string, any][];
export let value: any;
export let small = false;
export let label = ''
export let options: [string, any][]
export let value: any
export let small = false
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
</script>
<fieldset class="mt-2 mr-4">
@@ -1,6 +1,6 @@
<script lang="ts">
export let required: boolean;
export let detail = '';
export let required: boolean
export let detail = ''
</script>
{#if required}
@@ -4,67 +4,67 @@
ResourceService,
type ResourceType,
VariableService
} from '../../../src/gen';
import { allTrue, emptySchema, sendUserToast } from '../../../src/utils';
import { createEventDispatcher } from 'svelte';
import type { Schema } from '../../common';
import Modal from './Modal.svelte';
import Path from './Path.svelte';
import ArgInput from './ArgInput.svelte';
import AutosizedTextarea from './AutosizedTextarea.svelte';
import ItemPicker from './ItemPicker.svelte';
import VariableEditor from './VariableEditor.svelte';
import Required from './Required.svelte';
} from '../../../src/gen'
import { allTrue, emptySchema, sendUserToast } from '../../../src/utils'
import { createEventDispatcher } from 'svelte'
import type { Schema } from '../../common'
import Modal from './Modal.svelte'
import Path from './Path.svelte'
import ArgInput from './ArgInput.svelte'
import AutosizedTextarea from './AutosizedTextarea.svelte'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
import Required from './Required.svelte'
import { workspaceStore } from '../../stores';
import ResourceTypePicker from './ResourceTypePicker.svelte';
import { workspaceStore } from '../../stores'
import ResourceTypePicker from './ResourceTypePicker.svelte'
let path = '';
let initialPath = '';
let path = ''
let initialPath = ''
let step = 1;
let step = 1
let resourceToEdit: Resource | undefined;
let resourceToEdit: Resource | undefined
let description: string = '';
let description: string = ''
let DESCRIPTION_PLACEHOLDER = `You can use markdown to style your description.
A good way to make resources user friendly is to link to a default script for your resource [example](scripts/add?template=f2d1dc8df796d9e8)`;
let selectedResourceType: string | undefined;
let resourceType: ResourceType;
let resourceSchema: Schema | undefined;
let args: Record<string, any> = {};
A good way to make resources user friendly is to link to a default script for your resource [example](scripts/add?template=f2d1dc8df796d9e8)`
let selectedResourceType: string | undefined
let resourceType: ResourceType
let resourceSchema: Schema | undefined
let args: Record<string, any> = {}
let error: string | undefined;
let error: string | undefined
let pickForField: string | undefined;
let itemPicker: ItemPicker;
let variableEditor: VariableEditor;
let modal: Modal;
let pickForField: string | undefined
let itemPicker: ItemPicker
let variableEditor: VariableEditor
let modal: Modal
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
export async function initNew() {
selectedResourceType = undefined;
step = 1;
args = {};
path = '';
description = '';
initialPath = '';
resourceSchema = emptySchema();
resourceToEdit = undefined;
modal.openModal();
selectedResourceType = undefined
step = 1
args = {}
path = ''
description = ''
initialPath = ''
resourceSchema = emptySchema()
resourceToEdit = undefined
modal.openModal()
}
export async function initEdit(p: string): Promise<void> {
initialPath = p;
path = p;
step = 2;
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p });
description = resourceToEdit!.description ?? '';
selectedResourceType = resourceToEdit!.resource_type;
await loadResourceType();
args = resourceToEdit!.value;
modal.openModal();
initialPath = p
path = p
step = 2
resourceToEdit = await ResourceService.getResource({ workspace: $workspaceStore!, path: p })
description = resourceToEdit!.description ?? ''
selectedResourceType = resourceToEdit!.resource_type
await loadResourceType()
args = resourceToEdit!.value
modal.openModal()
}
async function createResource(): Promise<void> {
@@ -72,13 +72,13 @@ A good way to make resources user friendly is to link to a default script for yo
await ResourceService.createResource({
workspace: $workspaceStore!,
requestBody: { path, value: args, description, resource_type: resourceType.name }
});
sendUserToast(`Successfully created resource at ${path}`);
})
sendUserToast(`Successfully created resource at ${path}`)
dispatch('refresh');
modal.closeModal();
dispatch('refresh')
modal.closeModal()
} catch (err) {
sendUserToast(`${err}`, true);
sendUserToast(`${err}`, true)
}
}
@@ -89,15 +89,15 @@ A good way to make resources user friendly is to link to a default script for yo
workspace: $workspaceStore!,
path: resourceToEdit.path,
requestBody: { path, value: args, description }
});
sendUserToast(`Successfully updated resource at ${path}`);
dispatch('refresh');
modal.closeModal();
})
sendUserToast(`Successfully updated resource at ${path}`)
dispatch('refresh')
modal.closeModal()
} else {
throw Error('Cannot edit undefined resourceToEdit');
throw Error('Cannot edit undefined resourceToEdit')
}
} catch (err) {
sendUserToast(`${err}`, true);
sendUserToast(`${err}`, true)
}
}
@@ -106,25 +106,25 @@ A good way to make resources user friendly is to link to a default script for yo
resourceType = await ResourceService.getResourceType({
workspace: $workspaceStore!,
path: selectedResourceType
});
})
if (resourceType.schema) {
resourceSchema = resourceType.schema as Schema;
resourceSchema = resourceType.schema as Schema
}
} else {
sendUserToast(`ResourceType cannot be undefined.`, true);
sendUserToast(`ResourceType cannot be undefined.`, true)
}
}
let inputCheck: { [id: string]: boolean } = {};
let inputCheck: { [id: string]: boolean } = {}
$: isValid = allTrue(inputCheck) ?? false;
$: isValid = allTrue(inputCheck) ?? false
</script>
<Modal
bind:this={modal}
on:close={() => {
dispatch('close');
dispatch('close')
}}
>
<div slot="title">{resourceToEdit ? 'Edit ' + resourceToEdit.path : 'Add a resource'}</div>
@@ -161,7 +161,7 @@ A good way to make resources user friendly is to link to a default script for yo
bind:value={selectedResourceType}
notPickable={resourceToEdit != undefined}
on:click={() => {
args = {};
args = {}
}}
/>
</div>
@@ -189,8 +189,8 @@ A good way to make resources user friendly is to link to a default script for yo
<button
class="default-button-secondary min-w-min items-center leading-4 py-0"
on:click={() => {
pickForField = fieldName;
itemPicker.openModal();
pickForField = fieldName
itemPicker.openModal()
}}>insert variable</button
>
</div>
@@ -208,8 +208,8 @@ A good way to make resources user friendly is to link to a default script for yo
<button
class="default-button px-4 py-2 font-semibold"
on:click={async () => {
await loadResourceType();
step = 2;
await loadResourceType()
step = 2
}}
>
Next
@@ -221,7 +221,7 @@ A good way to make resources user friendly is to link to a default script for yo
<button
class="default-button-secondary px-4 py-2 font-semibold"
on:click={() => {
step = 1;
step = 1
}}
>
Back
@@ -231,9 +231,9 @@ A good way to make resources user friendly is to link to a default script for yo
class="default-button px-4 py-2 font-semibold"
on:click={() => {
if (resourceToEdit) {
editResource();
editResource()
} else {
createResource();
createResource()
}
}}
>
@@ -247,7 +247,7 @@ A good way to make resources user friendly is to link to a default script for yo
bind:this={itemPicker}
pickCallback={(path, _) => {
if (pickForField) {
args[pickForField] = '$var:' + path;
args[pickForField] = '$var:' + path
}
}}
itemName="Variable"
@@ -266,7 +266,7 @@ A good way to make resources user friendly is to link to a default script for yo
class="default-button-secondary"
type="button"
on:click={() => {
variableEditor.initNew();
variableEditor.initNew()
}}
>
Create a new variable
@@ -1,20 +1,20 @@
<script lang="ts">
import { type Resource, ResourceService } from '../../gen';
import { workspaceStore } from '../../stores';
import { type Resource, ResourceService } from '../../gen'
import { workspaceStore } from '../../stores'
let resources: Resource[] = [];
let resources: Resource[] = []
export let value: string | undefined;
export let value: string | undefined
export let resourceType: string | undefined;
export let resourceType: string | undefined
async function loadResources(resourceType: string | undefined) {
resources = await ResourceService.listResource({ workspace: $workspaceStore!, resourceType });
resources = await ResourceService.listResource({ workspace: $workspaceStore!, resourceType })
}
$: {
if ($workspaceStore) {
loadResources(resourceType);
loadResources(resourceType)
}
}
</script>
@@ -1,25 +1,25 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
import { ResourceService } from '../../gen';
import { workspaceStore } from '../../stores';
import IconedResourceType from './IconedResourceType.svelte';
import { ResourceService } from '../../gen'
import { workspaceStore } from '../../stores'
import IconedResourceType from './IconedResourceType.svelte'
let resources: string[] = [];
let resources: string[] = []
export let value: string | undefined;
export let value: string | undefined
export let notPickable = false;
export let notPickable = false
async function loadResources() {
resources = await ResourceService.listResourceTypeNames({ workspace: $workspaceStore! });
resources = await ResourceService.listResourceTypeNames({ workspace: $workspaceStore! })
}
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
$: {
if ($workspaceStore) {
loadResources();
loadResources()
}
}
</script>
@@ -33,8 +33,8 @@
? 'item-button-disabled'
: 'item-button'}"
on:click={() => {
value = r;
dispatch('click');
value = r
dispatch('click')
}}
>
<IconedResourceType name={r} after={true} />
+27 -27
View File
@@ -1,40 +1,40 @@
<script lang="ts">
import { page } from '$app/stores';
import type { Script, Flow } from '../../gen';
import { getToday } from '../../utils';
import { slide } from 'svelte/transition';
import { page } from '$app/stores'
import type { Script, Flow } from '../../gen'
import { getToday } from '../../utils'
import { slide } from 'svelte/transition'
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import Icon from 'svelte-awesome';
import Tooltip from './Tooltip.svelte';
import SvelteMarkdown from 'svelte-markdown';
import SchemaForm from './SchemaForm.svelte';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
import Tooltip from './Tooltip.svelte'
import SvelteMarkdown from 'svelte-markdown'
import SchemaForm from './SchemaForm.svelte'
export let runnable: Script | Flow | undefined;
export let runAction: (scheduledForStr: string | undefined, args: Record<string, any>) => void;
export let buttonText = 'Run';
export let schedulable = true;
export let detailed = true;
export let runnable: Script | Flow | undefined
export let runAction: (scheduledForStr: string | undefined, args: Record<string, any>) => void
export let buttonText = 'Run'
export let schedulable = true
export let detailed = true
export let args: Record<string, any> = {};
export let args: Record<string, any> = {}
let isValid = true;
let isValid = true
let queryArgs = $page.url.searchParams.get('args');
let queryArgs = $page.url.searchParams.get('args')
if (queryArgs) {
const parsed = JSON.parse(atob(queryArgs));
const parsed = JSON.parse(atob(queryArgs))
Object.entries(parsed).forEach(([k, v]) => {
if (v == '<function call>') {
parsed[k] = undefined;
parsed[k] = undefined
}
});
console.log(parsed);
args = parsed;
})
console.log(parsed)
args = parsed
}
// Run later
let viewOptions = false;
let scheduledForStr: string | undefined;
let viewOptions = false
let scheduledForStr: string | undefined
</script>
<div class="max-w-5xl">
@@ -97,7 +97,7 @@
<button
class="default-button-secondary mx-2 mb-1"
on:click={() => {
scheduledForStr = undefined;
scheduledForStr = undefined
}}>clear</button
>
</div>
@@ -109,7 +109,7 @@
type="submit"
class="mr-6 text-sm underline text-gray-700 inline-flex items-center"
on:click={() => {
viewOptions = !viewOptions;
viewOptions = !viewOptions
}}
>
{#if schedulable}
@@ -126,7 +126,7 @@
disabled={!isValid}
class="{isValid ? 'default-button' : 'default-button-disabled'} w-min px-6"
on:click={() => {
runAction(scheduledForStr, args);
runAction(scheduledForStr, args)
}}
>
{scheduledForStr ? 'Schedule run to a later time' : buttonText}
@@ -1,130 +1,126 @@
<script lang="ts">
import SchemaModal, {
DEFAULT_PROPERTY,
modalToSchema,
schemaToModal
} from './SchemaModal.svelte';
import type { ModalSchemaProperty } from './SchemaModal.svelte';
import type { Schema } from '../../common';
import Editor from './Editor.svelte';
import { emptySchema, sendUserToast } from '../../utils';
import Tooltip from './Tooltip.svelte';
import TableCustom from './TableCustom.svelte';
import SchemaModal, { DEFAULT_PROPERTY, modalToSchema, schemaToModal } from './SchemaModal.svelte'
import type { ModalSchemaProperty } from './SchemaModal.svelte'
import type { Schema } from '../../common'
import Editor from './Editor.svelte'
import { emptySchema, sendUserToast } from '../../utils'
import Tooltip from './Tooltip.svelte'
import TableCustom from './TableCustom.svelte'
export let schema: Schema = emptySchema();
export let schema: Schema = emptySchema()
let schemaModal: SchemaModal;
let schemaString: string = '';
let schemaModal: SchemaModal
let schemaString: string = ''
// Internal state: bound to args builder modal
let modalProperty: ModalSchemaProperty = Object.assign({}, DEFAULT_PROPERTY);
let argError = '';
let editing = false;
let oldArgName: string | undefined; // when editing argument and changing name
let modalProperty: ModalSchemaProperty = Object.assign({}, DEFAULT_PROPERTY)
let argError = ''
let editing = false
let oldArgName: string | undefined // when editing argument and changing name
let viewJsonSchema = false;
let editor: Editor;
let viewJsonSchema = false
let editor: Editor
$: schemaString = JSON.stringify(schema, null, '\t');
$: schemaString = JSON.stringify(schema, null, '\t')
export function getEditor(): Editor {
return editor;
return editor
}
// Binding is not enough because monaco Editor does not support two-way binding
export function getSchema(): Schema {
if (viewJsonSchema) {
try {
schema = JSON.parse(editor.getCode());
return schema;
schema = JSON.parse(editor.getCode())
return schema
} catch (err) {
throw Error(`Error: input is not a valid schema: ${err}`);
throw Error(`Error: input is not a valid schema: ${err}`)
}
} else {
try {
editor.setCode(JSON.stringify(schema, null, '\t'));
return schema;
editor.setCode(JSON.stringify(schema, null, '\t'))
return schema
} catch (err) {
throw Error(`Error: input is not a valid schema: ${err}`);
throw Error(`Error: input is not a valid schema: ${err}`)
}
}
}
function handleAddOrEditArgument(): void {
// If editing the arg's name, oldName containing the old argument name must be provided
argError = '';
argError = ''
if (modalProperty.name.length === 0) {
argError = 'Arguments need to have a name';
argError = 'Arguments need to have a name'
} else if (Object.keys(schema.properties).includes(modalProperty.name) && !editing) {
argError = 'There is already an argument with this name';
argError = 'There is already an argument with this name'
} else {
schema.properties[modalProperty.name] = modalToSchema(modalProperty);
schema.properties[modalProperty.name] = modalToSchema(modalProperty)
if (modalProperty.required) {
schema.required = [...schema.required, modalProperty.name];
schema.required = [...schema.required, modalProperty.name]
} else if (schema.required.includes(modalProperty.name)) {
const index = schema.required.indexOf(modalProperty.name, 0);
const index = schema.required.indexOf(modalProperty.name, 0)
if (index > -1) {
schema.required.splice(index, 1);
schema.required.splice(index, 1)
}
}
if (editing && oldArgName && oldArgName !== modalProperty.name) {
handleDeleteArgument(oldArgName);
handleDeleteArgument(oldArgName)
}
modalProperty = Object.assign({}, DEFAULT_PROPERTY);
editing = false;
oldArgName = undefined;
schemaModal.closeModal();
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
editing = false
oldArgName = undefined
schemaModal.closeModal()
}
}
function startEditArgument(argName: string): void {
argError = '';
argError = ''
if (Object.keys(schema.properties).includes(argName)) {
schemaModal.openModal();
editing = true;
schemaModal.openModal()
editing = true
modalProperty = schemaToModal(
schema.properties[argName],
argName,
schema.required.includes(argName)
);
oldArgName = argName;
)
oldArgName = argName
} else {
sendUserToast(`This argument does not exist and can't be edited`, true);
sendUserToast(`This argument does not exist and can't be edited`, true)
}
}
function handleDeleteArgument(argName: string): void {
try {
if (Object.keys(schema.properties).includes(argName)) {
delete schema.properties[argName];
schema = schema; //needed for reactivity, see https://svelte.dev/tutorial/updating-arrays-and-objects
delete schema.properties[argName]
schema = schema //needed for reactivity, see https://svelte.dev/tutorial/updating-arrays-and-objects
} else {
throw Error('Argument not found!');
throw Error('Argument not found!')
}
} catch (err) {
console.error(err);
sendUserToast(`Could not delete argument: ${err}`, true);
console.error(err)
sendUserToast(`Could not delete argument: ${err}`, true)
}
}
function switchTab(): void {
if (viewJsonSchema) {
let schemaString = editor.getCode();
let schemaString = editor.getCode()
if (schemaString === '') {
schemaString = JSON.stringify(emptySchema(), null, 4);
schemaString = JSON.stringify(emptySchema(), null, 4)
}
try {
schema = JSON.parse(schemaString);
viewJsonSchema = false;
schema = JSON.parse(schemaString)
viewJsonSchema = false
} catch (err) {
sendUserToast(err, true);
sendUserToast(err, true)
}
} else {
try {
editor.setCode(JSON.stringify(schema, null, '\t'));
viewJsonSchema = true;
editor.setCode(JSON.stringify(schema, null, '\t'))
viewJsonSchema = true
} catch (err) {
sendUserToast(err, true);
sendUserToast(err, true)
}
}
}
@@ -155,8 +151,8 @@
<button
class="default-button-secondary grow"
on:click={() => {
modalProperty = Object.assign({}, DEFAULT_PROPERTY);
schemaModal.openModal();
modalProperty = Object.assign({}, DEFAULT_PROPERTY)
schemaModal.openModal()
}}>Add argument</button
>
</div>
@@ -203,7 +199,7 @@
<button
class="default-button-secondary text-xs inline-flex"
on:click={() => {
startEditArgument(name);
startEditArgument(name)
}}>edit</button
></td
>
@@ -1,32 +1,32 @@
<script lang="ts">
import { allTrue } from '../../utils';
import { allTrue } from '../../utils'
import { slide } from 'svelte/transition';
import { slide } from 'svelte/transition'
import type { Schema } from '../../common';
import ArgInput from './ArgInput.svelte';
import RadioButton from './RadioButton.svelte';
import Editor from './Editor.svelte';
import FieldHeader from './FieldHeader.svelte';
import Icon from 'svelte-awesome';
import type { Schema } from '../../common'
import ArgInput from './ArgInput.svelte'
import RadioButton from './RadioButton.svelte'
import Editor from './Editor.svelte'
import FieldHeader from './FieldHeader.svelte'
import Icon from 'svelte-awesome'
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons';
import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
export let inputTransform = false;
export let schema: Schema;
export let args: Record<string, any> = {};
export let inputTransform = false
export let schema: Schema
export let args: Record<string, any> = {}
export let isValid: boolean = true;
export let editableSchema = false;
export let isValid: boolean = true
export let editableSchema = false
let inputCheck: { [id: string]: boolean } = {};
let seeHelp: { [id: string]: boolean } = {};
let inputCheck: { [id: string]: boolean } = {}
let seeHelp: { [id: string]: boolean } = {}
export function setArgs(nargs: Record<string, any>) {
args = nargs;
args = nargs
}
$: isValid = allTrue(inputCheck) ?? false;
$: isValid = allTrue(inputCheck) ?? false
</script>
<div class="w-full">
@@ -51,13 +51,13 @@
small={true}
bind:value={args[argName].type}
on:change={(e) => {
console.log(e.detail);
console.log(e.detail)
args[argName].expr =
e.detail == 'javascript'
? `import { previous_result, flow_input, step, variable, resource, params } from 'windmill'
previous_result.myfield`
: undefined;
: undefined
}}
/>
</div>
@@ -86,7 +86,7 @@ previous_result.myfield`
<span
class="underline mr-4"
on:click={() => {
seeHelp[argName] = seeHelp[argName] == undefined ? true : !seeHelp[argName];
seeHelp[argName] = seeHelp[argName] == undefined ? true : !seeHelp[argName]
}}
>Help<Icon
class="ml-2"
@@ -1,20 +1,20 @@
<script lang="ts" context="module">
import type { SchemaProperty } from '../../common';
import Modal from '../../routes/components/Modal.svelte';
import type { SchemaProperty } from '../../common'
import Modal from '../../routes/components/Modal.svelte'
export const ARG_TYPES = ['integer', 'number', 'string', 'boolean', 'object', 'array'] as const;
export type ArgType = typeof ARG_TYPES[number];
export const ARG_TYPES = ['integer', 'number', 'string', 'boolean', 'object', 'array'] as const
export type ArgType = typeof ARG_TYPES[number]
export interface ModalSchemaProperty {
selectedType?: string;
description: string;
name: string;
required: boolean;
format?: string;
pattern?: string;
enum_?: string[];
default?: any;
items?: { type?: 'string' | 'number' };
selectedType?: string
description: string
name: string
required: boolean
format?: string
pattern?: string
enum_?: string[]
default?: any
items?: { type?: 'string' | 'number' }
}
export function modalToSchema(schema: ModalSchemaProperty): SchemaProperty {
@@ -25,7 +25,7 @@
default: schema.default,
enum: schema.enum_,
items: schema.items
};
}
}
export function schemaToModal(
@@ -40,7 +40,7 @@
pattern: schema.pattern,
default: schema.default,
required
};
}
}
export const DEFAULT_PROPERTY: ModalSchemaProperty = {
@@ -48,41 +48,41 @@
description: '',
name: '',
required: false
};
}
</script>
<script lang="ts">
import Switch from './Switch.svelte';
import { createEventDispatcher } from 'svelte';
import ArgInput from './ArgInput.svelte';
import StringTypeNarrowing from './StringTypeNarrowing.svelte';
import Required from './Required.svelte';
import Switch from './Switch.svelte'
import { createEventDispatcher } from 'svelte'
import ArgInput from './ArgInput.svelte'
import StringTypeNarrowing from './StringTypeNarrowing.svelte'
import Required from './Required.svelte'
export let property: ModalSchemaProperty = DEFAULT_PROPERTY;
export let error = '';
export let editing = false;
export let oldArgName: string | undefined;
export let property: ModalSchemaProperty = DEFAULT_PROPERTY
export let error = ''
export let editing = false
export let oldArgName: string | undefined
const dispatch = createEventDispatcher();
let modal: Modal;
const dispatch = createEventDispatcher()
let modal: Modal
export function openModal(): void {
modal.openModal();
modal.openModal()
}
export function closeModal(): void {
modal.closeModal();
modal.closeModal()
}
function clearModal(): void {
error = '';
editing = false;
oldArgName = undefined;
property.name = DEFAULT_PROPERTY.name;
property.default = DEFAULT_PROPERTY.default;
property.description = DEFAULT_PROPERTY.description;
property.required = DEFAULT_PROPERTY.required;
property.selectedType = DEFAULT_PROPERTY.selectedType;
error = ''
editing = false
oldArgName = undefined
property.name = DEFAULT_PROPERTY.name
property.default = DEFAULT_PROPERTY.default
property.description = DEFAULT_PROPERTY.description
property.required = DEFAULT_PROPERTY.required
property.selectedType = DEFAULT_PROPERTY.selectedType
}
</script>
@@ -111,14 +111,14 @@
<button
class={argType == property.selectedType ? 'item-button-selected' : 'item-button'}
on:click={() => {
property.selectedType = argType;
property.selectedType = argType
}}>{argType}</button
>
{/each}
<button
class={!property.selectedType ? 'item-button-selected' : 'item-button'}
on:click={() => {
property.selectedType = undefined;
property.selectedType = undefined
}}>any</button
>
</div>
@@ -160,7 +160,7 @@
slot="submission"
class="px-4 py-2 text-white font-semibold bg-blue-500 rounded"
on:click={() => {
dispatch('save');
dispatch('save')
}}
>
Save
@@ -1,15 +1,15 @@
<script lang="ts">
import type { Schema } from '../../common';
import { emptySchema } from '../../utils';
import type { Schema } from '../../common'
import { emptySchema } from '../../utils'
import Highlight from 'svelte-highlight';
import json from 'svelte-highlight/src/languages/json';
import github from 'svelte-highlight/src/styles/github';
import TableCustom from './TableCustom.svelte';
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/src/languages/json'
import github from 'svelte-highlight/src/styles/github'
import TableCustom from './TableCustom.svelte'
export let schema: Schema | undefined = emptySchema();
export let schema: Schema | undefined = emptySchema()
let viewJsonSchema = false;
let viewJsonSchema = false
</script>
<svelte:head>
@@ -1,28 +1,28 @@
<script lang="ts">
import { ScriptService, type Script } from '../../gen';
import { ScriptService, type Script } from '../../gen'
import { emptySchema, sendUserToast } from '../../utils';
import { onDestroy } from 'svelte';
import ScriptEditor from './ScriptEditor.svelte';
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import Path from './Path.svelte';
import SvelteMarkdown from 'svelte-markdown';
import { workspaceStore } from '../../stores';
import ScriptSchema from './ScriptSchema.svelte';
import { inferArgs } from '../../infer';
import Required from './Required.svelte';
import { emptySchema, sendUserToast } from '../../utils'
import { onDestroy } from 'svelte'
import ScriptEditor from './ScriptEditor.svelte'
import { page } from '$app/stores'
import { goto } from '$app/navigation'
import Path from './Path.svelte'
import SvelteMarkdown from 'svelte-markdown'
import { workspaceStore } from '../../stores'
import ScriptSchema from './ScriptSchema.svelte'
import { inferArgs } from '../../infer'
import Required from './Required.svelte'
let editor: ScriptEditor;
let scriptSchema: ScriptSchema;
$: step = Number($page.url.searchParams.get('step')) || 1;
let editor: ScriptEditor
let scriptSchema: ScriptSchema
$: step = Number($page.url.searchParams.get('step')) || 1
export let script: Script;
export let initialPath: string = '';
export let script: Script
export let initialPath: string = ''
$: {
$page.url.searchParams.set('state', btoa(JSON.stringify(script)));
history.replaceState({}, '', $page.url);
$page.url.searchParams.set('state', btoa(JSON.stringify(script)))
history.replaceState({}, '', $page.url)
}
async function editScript(): Promise<void> {
@@ -38,47 +38,47 @@
schema: script.schema,
is_template: script.is_template
}
});
sendUserToast(`Success! New script version created with hash ${newHash}`);
goto(`/scripts/get/${newHash}`);
})
sendUserToast(`Success! New script version created with hash ${newHash}`)
goto(`/scripts/get/${newHash}`)
} catch (error) {
if (error.status === 400) {
sendUserToast(error.body, true);
sendUserToast(error.body, true)
} else {
sendUserToast(`Ooops.Something bad happened: ${error}`, true);
console.error(error);
sendUserToast(`Ooops.Something bad happened: ${error}`, true)
console.error(error)
}
}
}
export function setCode(script: Script) {
editor?.getEditor().setCode(script.content);
editor?.getEditor().setCode(script.content)
if (scriptSchema) {
if (script.schema) {
scriptSchema.setSchema(script.schema);
scriptSchema.setSchema(script.schema)
} else {
scriptSchema.setSchema(emptySchema());
scriptSchema.setSchema(emptySchema())
}
}
}
async function inferSchema() {
await inferArgs(script.content, script.schema);
await inferArgs(script.content, script.schema)
}
async function changeStep(step: number) {
if (step == 3) {
script.content = editor?.getEditor().getCode() ?? script.content;
await inferSchema();
script.schema = script.schema;
script.content = editor?.getEditor().getCode() ?? script.content
await inferSchema()
script.schema = script.schema
}
goto(`?step=${step}`);
goto(`?step=${step}`)
}
onDestroy(() => {
editor?.$destroy();
});
editor?.$destroy()
})
</script>
<div class="flex flex-col h-screen max-w-screen-lg xl:-ml-20 xl:pl-4 w-full -mt-4 pt-4 md:mx-10 ">
@@ -91,7 +91,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(1);
changeStep(1)
}}>Step 1: Metadata</button
>
<button
@@ -99,7 +99,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(2);
changeStep(2)
}}>Step 2: Code</button
>
<button
@@ -107,7 +107,7 @@
? 'default-button-disabled text-gray-700'
: 'default-button-secondary'} min-w-max ml-2"
on:click={() => {
changeStep(3);
changeStep(3)
}}>Step 3: UI customisation</button
>
</div>
@@ -118,15 +118,15 @@
(script.path == undefined || script.path == '' || script.path.split('/')[2] == '')}
class="default-button px-6 max-h-8"
on:click={() => {
changeStep(step + 1);
changeStep(step + 1)
}}>Next</button
>
{#if step == 2}
<button
class="default-button-secondary px-6 max-h-8 mr-2"
on:click={async () => {
await inferSchema();
editScript();
await inferSchema()
editScript()
}}>Save (commit)</button
>
{/if}
+150 -150
View File
@@ -6,11 +6,11 @@
VariableService,
ResourceService,
ScriptService
} from '../../gen';
import { sendUserToast, emptySchema, displayDate } from '../../utils';
import type { Schema } from '../../common';
import { fade } from 'svelte/transition';
import Icon from 'svelte-awesome';
} from '../../gen'
import { sendUserToast, emptySchema, displayDate } from '../../utils'
import type { Schema } from '../../common'
import { fade } from 'svelte/transition'
import Icon from 'svelte-awesome'
import {
faCheck,
faChevronDown,
@@ -20,87 +20,87 @@
faSearch,
faSpinner,
faTimes
} from '@fortawesome/free-solid-svg-icons';
import Editor from './Editor.svelte';
import Tooltip from './Tooltip.svelte';
import { onDestroy, onMount } from 'svelte';
import { userStore, workspaceStore } from '../../stores';
import TableCustom from './TableCustom.svelte';
import { check } from 'svelte-awesome/icons';
import Modal from './Modal.svelte';
import { Highlight } from 'svelte-highlight';
import { json, python } from 'svelte-highlight/src/languages';
import github from 'svelte-highlight/src/styles/github';
import ItemPicker from './ItemPicker.svelte';
import VariableEditor from './VariableEditor.svelte';
import ResourceEditor from './ResourceEditor.svelte';
import { inferArgs } from '../../infer';
} from '@fortawesome/free-solid-svg-icons'
import Editor from './Editor.svelte'
import Tooltip from './Tooltip.svelte'
import { onDestroy, onMount } from 'svelte'
import { userStore, workspaceStore } from '../../stores'
import TableCustom from './TableCustom.svelte'
import { check } from 'svelte-awesome/icons'
import Modal from './Modal.svelte'
import { Highlight } from 'svelte-highlight'
import { json, python } from 'svelte-highlight/src/languages'
import github from 'svelte-highlight/src/styles/github'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
import ResourceEditor from './ResourceEditor.svelte'
import { inferArgs } from '../../infer'
// @ts-ignore
import { VSplitPane } from 'svelte-split-pane';
import SchemaForm from './SchemaForm.svelte';
import DisplayResult from './DisplayResult.svelte';
import { VSplitPane } from 'svelte-split-pane'
import SchemaForm from './SchemaForm.svelte'
import DisplayResult from './DisplayResult.svelte'
// Exported
export let schema: Schema = emptySchema();
export let schema: Schema = emptySchema()
export let code: string;
export let path: string | undefined;
export let code: string
export let path: string | undefined
// Control Editor layout
export let viewPreview = true;
export let previewTab: 'logs' | 'input' | 'output' | 'history' | 'last_save' = 'logs';
export let viewPreview = true
export let previewTab: 'logs' | 'input' | 'output' | 'history' | 'last_save' = 'logs'
let websocketAlive = { pyright: false, black: false };
let websocketAlive = { pyright: false, black: false }
// Internal state
let editor: Editor;
let editor: Editor
// Preview args input
let args: Record<string, any> = {};
let isValid: boolean = true;
let args: Record<string, any> = {}
let isValid: boolean = true
// Preview
let previewIsLoading = false;
let previewIntervalId: NodeJS.Timer;
let previewJob: Job | undefined;
let pastPreviews: CompletedJob[] = [];
let previewIsLoading = false
let previewIntervalId: NodeJS.Timer
let previewJob: Job | undefined
let pastPreviews: CompletedJob[] = []
let modalViewer: Modal;
let modalViewerTitle: string = '';
let modalViewerContent: any;
let modalViewerMode: 'logs' | 'result' | 'code' = 'logs';
let modalViewer: Modal
let modalViewerTitle: string = ''
let modalViewerContent: any
let modalViewerMode: 'logs' | 'result' | 'code' = 'logs'
let variablePicker: ItemPicker;
let resourcePicker: ItemPicker;
let scriptPicker: ItemPicker;
let variableEditor: VariableEditor;
let resourceEditor: ResourceEditor;
let variablePicker: ItemPicker
let resourcePicker: ItemPicker
let scriptPicker: ItemPicker
let variableEditor: VariableEditor
let resourceEditor: ResourceEditor
let syncIteration: number = 0;
let ITERATIONS_BEFORE_SLOW_REFRESH = 100;
let syncIteration: number = 0
let ITERATIONS_BEFORE_SLOW_REFRESH = 100
let lastSave: string | null;
let lastSave: string | null
$: lastSave = localStorage.getItem(path ?? 'last_save');
$: lastSave = localStorage.getItem(path ?? 'last_save')
export function getEditor(): Editor {
return editor;
return editor
}
export async function runPreview(): Promise<void> {
try {
if (previewIntervalId) {
clearInterval(previewIntervalId);
clearInterval(previewIntervalId)
}
if (previewIsLoading && previewJob) {
JobService.cancelQueuedJob({
workspace: $workspaceStore!,
id: previewJob.id,
requestBody: {}
});
})
}
previewIsLoading = true;
previewIsLoading = true
const previewId = await JobService.runScriptPreview({
workspace: $workspaceStore!,
@@ -109,17 +109,17 @@
content: editor.getCode(),
args: args
}
});
previewJob = undefined;
loadPreviewJob(previewId);
syncIteration = 0;
})
previewJob = undefined
loadPreviewJob(previewId)
syncIteration = 0
previewIntervalId = setInterval(() => {
syncer(previewId);
}, 500);
syncer(previewId)
}, 500)
//TODO fetch preview, every x time, until it's completed
} catch (err) {
previewIsLoading = false;
sendUserToast(`Could not run preview: ${err} `, true);
previewIsLoading = false
sendUserToast(`Could not run preview: ${err} `, true)
}
}
@@ -129,7 +129,7 @@
jobKinds: 'preview',
createdBy: $userStore?.username,
scriptPathExact: path
});
})
}
async function loadPreviewJob(id: string): Promise<void> {
@@ -140,98 +140,98 @@
id,
running: previewJob.running,
logOffset: previewJob.logs?.length ?? 0
});
})
if (previewJobUpdates.new_logs) {
previewJob.logs = (previewJob.logs ?? '').concat(previewJobUpdates.new_logs);
previewJob.logs = (previewJob.logs ?? '').concat(previewJobUpdates.new_logs)
}
if ((previewJobUpdates.running ?? false) || (previewJobUpdates.completed ?? false)) {
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id });
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id })
}
} else {
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id });
previewJob = await JobService.getJob({ workspace: $workspaceStore!, id })
}
if (previewJob?.type === 'CompletedJob') {
//only CompletedJob has success property
clearInterval(previewIntervalId);
previewIsLoading = false;
loadPastPreviews();
clearInterval(previewIntervalId)
previewIsLoading = false
loadPastPreviews()
}
} catch (err) {
console.error(err);
console.error(err)
}
}
async function inferSchema() {
let isDefault: string[] = [];
let isDefault: string[] = []
Object.entries(args).forEach(([k, v]) => {
if (schema.properties[k].default == v) {
isDefault.push(k);
isDefault.push(k)
}
});
await inferArgs(editor.getCode(), schema);
schema = schema;
})
await inferArgs(editor.getCode(), schema)
schema = schema
isDefault.forEach((key) => (args[key] = schema.properties[key].default));
isDefault.forEach((key) => (args[key] = schema.properties[key].default))
for (const key of Object.keys(args)) {
if (schema.properties[key] == undefined) {
delete args[key];
delete args[key]
}
}
}
function syncer(id: string): void {
if (syncIteration > ITERATIONS_BEFORE_SLOW_REFRESH) {
loadPreviewJob(id);
loadPreviewJob(id)
if (previewIntervalId) {
clearInterval(previewIntervalId);
previewIntervalId = setInterval(() => loadPreviewJob(id), 5000);
clearInterval(previewIntervalId)
previewIntervalId = setInterval(() => loadPreviewJob(id), 5000)
}
} else {
syncIteration++;
loadPreviewJob(id);
syncIteration++
loadPreviewJob(id)
}
}
async function loadVariables() {
let r: { name: string; path?: string; description?: string }[] = [];
let r: { name: string; path?: string; description?: string }[] = []
const variables = (
await VariableService.listVariable({ workspace: $workspaceStore ?? 'NO_W' })
).map((x) => {
return { name: x.path, ...x };
});
return { name: x.path, ...x }
})
const rvariables = await VariableService.listContextualVariables({
workspace: $workspaceStore ?? 'NO_W'
});
r = r.concat(variables).concat(rvariables);
return r;
})
r = r.concat(variables).concat(rvariables)
return r
}
async function loadScripts(): Promise<{ path: string; summary?: string }[]> {
return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' });
return await ScriptService.listScripts({ workspace: $workspaceStore ?? 'NO_W' })
}
let syncCode: NodeJS.Timer;
let syncCode: NodeJS.Timer
onMount(() => {
syncCode = setInterval(() => {
const newCode = editor?.getCode();
const newCode = editor?.getCode()
if (newCode && code != newCode) {
code = editor.getCode();
code = editor.getCode()
}
}, 3000);
});
}, 3000)
})
onDestroy(() => {
if (editor) {
code = editor.getCode();
code = editor.getCode()
}
if (previewIntervalId) {
clearInterval(previewIntervalId);
clearInterval(previewIntervalId)
}
if (syncCode) {
clearInterval(syncCode);
clearInterval(syncCode)
}
});
})
</script>
<svelte:head>
@@ -241,15 +241,15 @@
<ItemPicker
bind:this={scriptPicker}
pickCallback={async (path, _) => {
modalViewerMode = 'code';
modalViewerTitle = 'Script ' + path;
modalViewerMode = 'code'
modalViewerTitle = 'Script ' + path
modalViewerContent = (
await ScriptService.getScriptByPath({
workspace: $workspaceStore ?? '',
path
})
).content;
modalViewer.openModal();
).content
modalViewer.openModal()
}}
closeOnClick={false}
itemName="script"
@@ -277,16 +277,16 @@
pickCallback={(path, name) => {
if (!path) {
if (!getEditor().getCode().includes('import os')) {
getEditor().insertAtBeginning('import os\n');
getEditor().insertAtBeginning('import os\n')
}
getEditor().insertAtCursor(`os.environ.get("${name}")`);
sendUserToast(`${name} inserted at cursor`);
getEditor().insertAtCursor(`os.environ.get("${name}")`)
sendUserToast(`${name} inserted at cursor`)
} else {
if (!getEditor().getCode().includes('import wmill')) {
getEditor().insertAtBeginning('import wmill\n');
getEditor().insertAtBeginning('import wmill\n')
}
getEditor().insertAtCursor(`wmill.get_variable("${path}")`);
sendUserToast(`${name} inserted at cursor`);
getEditor().insertAtCursor(`wmill.get_variable("${path}")`)
sendUserToast(`${name} inserted at cursor`)
}
}}
itemName="Variable"
@@ -301,7 +301,7 @@
class="default-button-secondary"
type="button"
on:click={() => {
variableEditor.initNew();
variableEditor.initNew()
}}
>
Create a new variable
@@ -312,8 +312,8 @@
<ItemPicker
bind:this={resourcePicker}
pickCallback={(path, _) => {
getEditor().insertAtCursor(`client.get_resource("${path}")`);
sendUserToast(`${path} inserted at cursor`);
getEditor().insertAtCursor(`client.get_resource("${path}")`)
sendUserToast(`${path} inserted at cursor`)
}}
itemName="Resource"
extraField="resource_type"
@@ -328,7 +328,7 @@
class="default-button-secondary"
type="button"
on:click={() => {
resourceEditor.initNew();
resourceEditor.initNew()
}}
>
Create a new resource
@@ -346,7 +346,7 @@
downPanelSize={viewPreview ? '25%' : '10%'}
updateCallback={() => {
if (!viewPreview) {
viewPreview = true;
viewPreview = true
}
}}
>
@@ -357,7 +357,7 @@
<button
class="default-button-secondary font-semibold py-px mr-2 text-xs align-middle max-h-8"
on:click|stopPropagation={() => {
variablePicker.openModal();
variablePicker.openModal()
}}
>Variable picker <Icon data={faSearch} scale={0.7} />
</button>
@@ -365,7 +365,7 @@
<button
class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
on:click|stopPropagation={() => {
resourcePicker.openModal();
resourcePicker.openModal()
}}
>Resource picker <Icon data={faSearch} scale={0.7} />
</button>
@@ -373,7 +373,7 @@
<button
class="default-button-secondary font-semibold py-px text-xs mr-2 align-middle max-h-8"
on:click|stopPropagation={() => {
scriptPicker.openModal();
scriptPicker.openModal()
}}
>Script explorer <Icon data={faSearch} scale={0.7} />
</button>
@@ -381,7 +381,7 @@
<button
class="default-button-secondary py-px max-h-8 text-xs"
on:click|stopPropagation={() => {
editor.reloadWebsocket();
editor.reloadWebsocket()
}}
>
Reload assistants (status: <span
@@ -396,12 +396,12 @@
bind:websocketAlive
bind:this={editor}
cmdEnterAction={() => {
runPreview();
viewPreview = true;
runPreview()
viewPreview = true
}}
formatAction={() => {
code = getEditor().getCode();
localStorage.setItem(path ?? 'last_save', code);
code = getEditor().getCode()
localStorage.setItem(path ?? 'last_save', code)
}}
class="h-full"
automaticLayout={true}
@@ -414,7 +414,7 @@
<div
class="flex flex-row w-full cursor-pointer h-full"
on:click={() => {
viewPreview = !viewPreview;
viewPreview = !viewPreview
}}
>
<div class="flex flex-row items-baseline">
@@ -437,9 +437,9 @@
? 'underline drop-shadow-md'
: ''}"
on:click|stopPropagation={() => {
previewTab = 'input';
viewPreview = true;
inferSchema();
previewTab = 'input'
viewPreview = true
inferSchema()
}}
>
Inputs
@@ -447,8 +447,8 @@
<button
class="font-semibold my-0 py-0 h-full ml-3 {previewTab === 'logs' ? 'underline' : ''}"
on:click|stopPropagation={() => {
previewTab = 'logs';
viewPreview = true;
previewTab = 'logs'
viewPreview = true
}}
>
Logs
@@ -456,8 +456,8 @@
<button
class="font-semibold my-0 py-0 h-full ml-3 {previewTab === 'output' ? 'underline' : ''}"
on:click|stopPropagation={() => {
previewTab = 'output';
viewPreview = true;
previewTab = 'output'
viewPreview = true
}}
>
Result
@@ -468,10 +468,10 @@
: ''}"
on:click|stopPropagation={() => {
if (pastPreviews.length == 0) {
loadPastPreviews();
loadPastPreviews()
}
previewTab = 'history';
viewPreview = true;
previewTab = 'history'
viewPreview = true
}}
>
History
@@ -481,8 +481,8 @@
? 'underline'
: ''}"
on:click|stopPropagation={() => {
previewTab = 'last_save';
viewPreview = true;
previewTab = 'last_save'
viewPreview = true
}}
>
Local save
@@ -492,16 +492,16 @@
<button
class="mb-1 ml-2"
on:click|stopPropagation={() => {
viewPreview = !viewPreview;
viewPreview = !viewPreview
}}
><Icon data={viewPreview ? faChevronDown : faChevronUp} scale={0.7} />
</button>
<button
class="default-button py-px text-xs mx-2 align-middle max-h-8"
on:click|stopPropagation={() => {
runPreview();
viewPreview = true;
previewTab = 'logs';
runPreview()
viewPreview = true
previewTab = 'logs'
}}
>Run preview
</button>
@@ -559,9 +559,9 @@
href="#last_save"
class="text-xs"
on:click={() => {
modalViewerContent = lastSave;
modalViewerMode = 'code';
modalViewer.openModal();
modalViewerContent = lastSave
modalViewerMode = 'code'
modalViewer.openModal()
}}>View last local save for path {path}</a
>
{:else}No local save{/if}
@@ -595,9 +595,9 @@
href="#result"
class="text-xs"
on:click={() => {
modalViewerContent = result;
modalViewerMode = 'result';
modalViewer.openModal();
modalViewerContent = result
modalViewerMode = 'result'
modalViewer.openModal()
}}>{JSON.stringify(result).substring(0, 30)}...</a
></td
>
@@ -611,9 +611,9 @@
workspace: $workspaceStore ?? 'NO_W',
id
})
).raw_code;
modalViewerMode = 'code';
modalViewer.openModal();
).raw_code
modalViewerMode = 'code'
modalViewer.openModal()
}}
>View code
</a></td
@@ -628,9 +628,9 @@
workspace: $workspaceStore ?? 'NO_W',
id
})
).logs;
modalViewerMode = 'logs';
modalViewer.openModal();
).logs
modalViewerMode = 'logs'
modalViewer.openModal()
}}
>View logs
</a></td
@@ -1,49 +1,49 @@
<script lang="ts">
import { sendUserToast } from '../../utils';
import { ScriptService, FlowService } from '../../gen';
import { sendUserToast } from '../../utils'
import { ScriptService, FlowService } from '../../gen'
import Icon from 'svelte-awesome';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { workspaceStore } from '../../stores';
import { createEventDispatcher } from 'svelte';
import ItemPicker from './ItemPicker.svelte';
import RadioButton from './RadioButton.svelte';
import Modal from './Modal.svelte';
import { Highlight } from 'svelte-highlight';
import { python } from 'svelte-highlight/src/languages';
import github from 'svelte-highlight/src/styles/github';
import Icon from 'svelte-awesome'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import { workspaceStore } from '../../stores'
import { createEventDispatcher } from 'svelte'
import ItemPicker from './ItemPicker.svelte'
import RadioButton from './RadioButton.svelte'
import Modal from './Modal.svelte'
import { Highlight } from 'svelte-highlight'
import { python } from 'svelte-highlight/src/languages'
import github from 'svelte-highlight/src/styles/github'
export let scriptPath: string | undefined = undefined;
export let allowFlow = false;
export let isFlow = false;
export let scriptPath: string | undefined = undefined
export let allowFlow = false
export let isFlow = false
let items: { summary: String; path: String; version?: String }[] = [];
let itemPicker: ItemPicker;
let modalViewer: Modal;
let code: string = '';
let items: { summary: String; path: String; version?: String }[] = []
let itemPicker: ItemPicker
let modalViewer: Modal
let code: string = ''
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
async function getScript() {
code = (await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: scriptPath! }))
.content;
.content
}
async function loadItems(isFlow: boolean): Promise<void> {
try {
if (isFlow) {
items = await FlowService.listFlows({ workspace: $workspaceStore! });
items = await FlowService.listFlows({ workspace: $workspaceStore! })
} else {
items = await ScriptService.listScripts({ workspace: $workspaceStore! });
items = await ScriptService.listScripts({ workspace: $workspaceStore! })
}
} catch (err) {
sendUserToast(`Could not load items: ${err}`, true);
sendUserToast(`Could not load items: ${err}`, true)
}
}
$: {
if ($workspaceStore) {
loadItems(isFlow);
loadItems(isFlow)
}
}
</script>
@@ -55,12 +55,12 @@
<ItemPicker
bind:this={itemPicker}
pickCallback={(path, _) => {
scriptPath = path;
scriptPath = path
}}
itemName={isFlow ? 'Flow' : 'Script'}
extraField="summary"
loadItems={async () => {
return items;
return items
}}
/>
@@ -77,7 +77,7 @@
<select
bind:value={scriptPath}
on:change={() => {
dispatch('select', { path: scriptPath });
dispatch('select', { path: scriptPath })
}}
class="max-w-lg"
>
@@ -93,8 +93,8 @@
<button
class="text-xs text-blue-500"
on:click={async () => {
await getScript();
modalViewer.openModal();
await getScript()
modalViewer.openModal()
}}>show code</button
>
{/if}
@@ -1,23 +1,23 @@
<script lang="ts">
import type { Schema } from '../../common';
import type { Schema } from '../../common'
import PageHeader from './PageHeader.svelte';
import SchemaForm from './SchemaForm.svelte';
import Tabs from './Tabs.svelte';
import PageHeader from './PageHeader.svelte'
import SchemaForm from './SchemaForm.svelte'
import Tabs from './Tabs.svelte'
import Highlight from 'svelte-highlight';
import json from 'svelte-highlight/src/languages/json';
import github from 'svelte-highlight/src/styles/github';
import SvelteMarkdown from 'svelte-markdown';
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/src/languages/json'
import github from 'svelte-highlight/src/styles/github'
import SvelteMarkdown from 'svelte-markdown'
export let schema: Schema;
export let summary: string;
export let description: string | undefined;
export let synchronizedHeader = true;
export let schema: Schema
export let summary: string
export let description: string | undefined
export let synchronizedHeader = true
let tab: 'ui' | 'jsonschema' = 'ui';
let tab: 'ui' | 'jsonschema' = 'ui'
export function setSchema(newSchema: Schema) {
schema = newSchema;
schema = newSchema
}
</script>
@@ -1,54 +1,54 @@
<script lang="ts">
import Modal from './Modal.svelte';
import TableCustom from './TableCustom.svelte';
import Modal from './Modal.svelte'
import TableCustom from './TableCustom.svelte'
import { GranularAclService } from '../../gen/services/GranularAclService';
import { sendUserToast } from '../../utils';
import { GroupService, UserService } from '../../gen';
import { createEventDispatcher } from 'svelte';
import AutoComplete from 'simple-svelte-autocomplete';
import { workspaceStore } from '../../stores';
import { GranularAclService } from '../../gen/services/GranularAclService'
import { sendUserToast } from '../../utils'
import { GroupService, UserService } from '../../gen'
import { createEventDispatcher } from 'svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import { workspaceStore } from '../../stores'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
export let kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow';
export let path: string = '';
export let kind: 'script' | 'group_' | 'resource' | 'schedule' | 'variable' | 'flow'
export let path: string = ''
let ownerKind: 'user' | 'group' = 'user';
let owner: string = '';
let ownerKind: 'user' | 'group' = 'user'
let owner: string = ''
let newOwner: string = '';
let write: boolean = false;
let acls: [string, boolean][] = [];
let groups: String[] = [];
let usernames: string[] = [];
let newOwner: string = ''
let write: boolean = false
let acls: [string, boolean][] = []
let groups: String[] = []
let usernames: string[] = []
let modal: Modal;
let modal: Modal
$: newOwner = [ownerKind === 'group' ? 'g' : 'u', owner].join('/');
$: newOwner = [ownerKind === 'group' ? 'g' : 'u', owner].join('/')
export async function openModal(newPath?: string) {
if (newPath) {
path = newPath;
path = newPath
}
loadAcls();
loadGroups();
loadUsernames();
modal.openModal();
loadAcls()
loadGroups()
loadUsernames()
modal.openModal()
}
async function loadAcls() {
acls = Object.entries(
await GranularAclService.getGranularAcls({ workspace: $workspaceStore!, path, kind })
);
)
}
async function loadGroups(): Promise<void> {
groups = await GroupService.listGroupNames({ workspace: $workspaceStore! });
groups = await GroupService.listGroupNames({ workspace: $workspaceStore! })
}
async function loadUsernames(): Promise<void> {
usernames = await UserService.listUsernames({ workspace: $workspaceStore! });
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
}
async function deleteAcl(owner: string) {
@@ -58,11 +58,11 @@
path,
kind,
requestBody: { owner }
});
loadAcls();
dispatch('change');
})
loadAcls()
dispatch('change')
} catch (err) {
sendUserToast(err.toString(), true);
sendUserToast(err.toString(), true)
}
}
@@ -73,11 +73,11 @@
path,
kind,
requestBody: { owner, write }
});
loadAcls();
dispatch('change');
})
loadAcls()
dispatch('change')
} catch (err) {
sendUserToast(err.toString(), true);
sendUserToast(err.toString(), true)
}
}
</script>
@@ -95,9 +95,9 @@
bind:value={ownerKind}
on:change={() => {
if (ownerKind === 'group') {
owner = 'all';
owner = 'all'
} else {
owner = '';
owner = ''
}
}}
>
@@ -1,47 +1,47 @@
<script lang="ts">
import { userStore } from '../../stores';
import { userStore } from '../../stores'
import Badge from './Badge.svelte';
export let extraPerms: Record<string, boolean> = {};
export let canWrite: boolean;
import Badge from './Badge.svelte'
export let extraPerms: Record<string, boolean> = {}
export let canWrite: boolean
let kind: 'read' | 'write' | undefined = undefined;
let reason = '';
let kind: 'read' | 'write' | undefined = undefined
let reason = ''
$: {
let username = $userStore?.username ?? '';
let pgroups = $userStore?.pgroups ?? [];
let pusername = `u/${username}`;
let extraPermsKeys = Object.keys(extraPerms);
let username = $userStore?.username ?? ''
let pgroups = $userStore?.pgroups ?? []
let pusername = `u/${username}`
let extraPermsKeys = Object.keys(extraPerms)
if (pusername in extraPermsKeys) {
if (extraPerms[pusername]) {
kind = 'write';
kind = 'write'
} else {
kind = 'read';
kind = 'read'
}
reason = 'This item was shared to you personally';
reason = 'This item was shared to you personally'
} else {
let writeGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x]);
let writeGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x])
if (pgroups.find((x) => x in extraPermsKeys && extraPerms[x])) {
kind = 'write';
reason = `This item was write shared to the group ${writeGroup} which you are a member of`;
kind = 'write'
reason = `This item was write shared to the group ${writeGroup} which you are a member of`
} else {
let readGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x]);
let readGroup = pgroups.find((x) => extraPermsKeys.includes(x) && extraPerms[x])
if (pgroups.find((x) => extraPermsKeys.includes(x))) {
kind = 'read';
reason = `This item was read-only shared to the group ${readGroup} which you are a member of`;
kind = 'read'
reason = `This item was read-only shared to the group ${readGroup} which you are a member of`
} else {
kind = undefined;
kind = undefined
}
}
}
if (kind == 'read' && canWrite) {
kind = undefined;
kind = undefined
}
if (kind == undefined && !canWrite) {
kind = 'read';
reason = '';
kind = 'read'
reason = ''
}
}
</script>
@@ -1,15 +1,15 @@
<script lang="ts">
import RadioButton from './RadioButton.svelte';
import ResourceTypePicker from './ResourceTypePicker.svelte';
import RadioButton from './RadioButton.svelte'
import ResourceTypePicker from './ResourceTypePicker.svelte'
export let pattern: string | undefined;
export let enum_: string[] | undefined;
export let format: string | undefined;
export let pattern: string | undefined
export let enum_: string[] | undefined
export let format: string | undefined
let kind: 'none' | 'pattern' | 'enum' | 'resource' | 'format' = 'none';
let patternStr: string = pattern ?? '';
let kind: 'none' | 'pattern' | 'enum' | 'resource' | 'format' = 'none'
let patternStr: string = pattern ?? ''
let resource: string | undefined;
let resource: string | undefined
const FORMATS = [
'email',
@@ -22,11 +22,11 @@
// 'duration',
// 'ipv6',
// 'jsonpointer'
];
]
$: format =
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined;
$: pattern = patternStr == '' ? undefined : patternStr;
kind == 'resource' ? (resource != undefined ? `resource-${resource}` : 'resource') : undefined
$: pattern = patternStr == '' ? undefined : patternStr
</script>
<RadioButton
@@ -54,7 +54,7 @@
<button
class="default-button-secondary mx-2 mb-1"
on:click={() => {
patternStr = '';
patternStr = ''
}}>clear</button
>
</div>
@@ -68,9 +68,9 @@
<button
class="default-button mx-6"
on:click={() => {
enum_ = (enum_ ?? []).filter((el) => el != e);
enum_ = (enum_ ?? []).filter((el) => el != e)
if (enum_.length == 0) {
enum_ = undefined;
enum_ = undefined
}
}}>-</button
>
@@ -81,15 +81,15 @@
class="default-button"
on:click={() => {
if (enum_ == undefined) {
enum_ = [];
enum_ = []
}
enum_ = enum_.concat('');
enum_ = enum_.concat('')
}}>+</button
>
<button
class="default-button-secondary ml-2"
on:click={() => {
enum_ = undefined;
enum_ = undefined
}}>clear</button
>
</div>
+4 -4
View File
@@ -1,8 +1,8 @@
<script>
export let checked = false;
export let label = '';
export let horizontal = true; // Determines if the switch and label are on a row or col
export let textFormat = 'text-base'; // Tailwind CSS classes to format the text
export let checked = false
export let label = ''
export let horizontal = true // Determines if the switch and label are on a row or col
export let textFormat = 'text-base' // Tailwind CSS classes to format the text
</script>
<div class="flex {horizontal ? 'flex-row justify-between' : 'flex-col'} {$$props.class}">
@@ -1,11 +1,11 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
export let paginated = false;
export let currentPage = 1;
export let showNext = true;
export let paginated = false
export let currentPage = 1
export let showNext = true
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
</script>
<!-- A custom table
@@ -26,13 +26,13 @@
<button
class="ml-2 drop-shadow-md {showNext ? 'visible' : 'invisible'}"
on:click={() => {
dispatch('next');
dispatch('next')
}}>Next</button
>
<button
class="mx-2 drop-shadow-md {currentPage === 1 ? 'hidden' : ''}"
on:click={() => {
dispatch('previous');
dispatch('previous')
}}>Previous</button
>
</div>
@@ -1,14 +1,14 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
// A table suitable if you can pass data as a list of row objects
export let headers: string[] | undefined;
export let data: any[] | undefined; // Object containing the data
export let keys: string[];
export let defaultText: string = 'No data to display';
export let paginated = false;
export let twTextSize: string = 'text-sm md:text-base';
export let headers: string[] | undefined
export let data: any[] | undefined // Object containing the data
export let keys: string[]
export let defaultText: string = 'No data to display'
export let paginated = false
export let twTextSize: string = 'text-sm md:text-base'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
</script>
<div class="mt-8 flex flex-col {$$props.class}">
@@ -55,13 +55,13 @@
<div class="flex flex-row-reverse text-gray-500">
<button
on:click={() => {
dispatch('next');
dispatch('next')
}}>Next</button
>
<button
class="mx-2"
on:click={() => {
dispatch('next');
dispatch('next')
}}>Previous</button
>
</div>
+7 -7
View File
@@ -1,12 +1,12 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { createEventDispatcher } from 'svelte'
export let tabs: [string, string][];
export let dflt = 0;
export let tab = tabs[dflt][0];
export let tabs: [string, string][]
export let dflt = 0
export let tab = tabs[dflt][0]
const dispatch = createEventDispatcher();
$: dispatch('update', tab);
const dispatch = createEventDispatcher()
$: dispatch('update', tab)
</script>
<div class="flex flex-col sm:flex-row text-base">
@@ -15,7 +15,7 @@
class=" py-1 px-6 block hover:text-blue-500 focus:outline-noneborder-gray-200
{t === tab ? 'text-gray-700 font-semibold underline' : 'text-gray-500'}"
on:click={() => {
tab = t;
tab = t
}}
>
{label}
+20 -20
View File
@@ -1,27 +1,27 @@
<script lang="ts">
import Icon from 'svelte-awesome';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { fade } from 'svelte/transition';
import Icon from 'svelte-awesome'
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'
import { fade } from 'svelte/transition'
export let position: ActionKind = 'above';
export let direction: DirectionKind = 'default';
type ActionKind = 'above' | 'below';
type DirectionKind = 'default' | 'left';
export let position: ActionKind = 'above'
export let direction: DirectionKind = 'default'
type ActionKind = 'above' | 'below'
type DirectionKind = 'default' | 'left'
let viewTooltip = false;
let mouseOnMessage = false;
let mouseOver = false;
let viewTooltip = false
let mouseOnMessage = false
let mouseOver = false
// This function makes sure the tooltip does not disappear when moving the mouse to the tooltip
// message, so that users can click links there for example
function hideTooltip(): void {
setTimeout(function () {
if (!mouseOnMessage && !mouseOver) {
viewTooltip = false;
viewTooltip = false
} else {
hideTooltip();
hideTooltip()
}
}, 300);
}, 300)
}
</script>
@@ -33,11 +33,11 @@
{viewTooltip ? 'tooltip-visible' : 'tooltip'} {direction === 'left' ? 'right-0' : ''}
text-2xs text-gray-700"
on:mouseover={() => {
mouseOnMessage = true;
mouseOnMessage = true
}}
on:focus={() => {}}
on:mouseout={() => {
mouseOnMessage = false;
mouseOnMessage = false
}}
on:blur={() => {}}
><slot />
@@ -48,15 +48,15 @@
<div
class="relative w-4 h-5 -mt-5 -ml-1"
on:mouseover={() => {
viewTooltip = true;
mouseOver = true;
viewTooltip = true
mouseOver = true
}}
on:focus={() => {
viewTooltip = true;
viewTooltip = true
}}
on:mouseout={() => {
hideTooltip();
mouseOver = false;
hideTooltip()
mouseOver = false
}}
on:blur={() => {}}
/>
@@ -1,62 +1,62 @@
<script lang="ts">
import Password from '../components/Password.svelte';
import { sendUserToast } from '../../utils';
import { VariableService } from '../../gen';
import AutosizedTextarea from '../components/AutosizedTextarea.svelte';
import Path from '../components/Path.svelte';
import Modal from './Modal.svelte';
import { createEventDispatcher } from 'svelte';
import { workspaceStore } from '../../stores';
import Required from './Required.svelte';
import Password from '../components/Password.svelte'
import { sendUserToast } from '../../utils'
import { VariableService } from '../../gen'
import AutosizedTextarea from '../components/AutosizedTextarea.svelte'
import Path from '../components/Path.svelte'
import Modal from './Modal.svelte'
import { createEventDispatcher } from 'svelte'
import { workspaceStore } from '../../stores'
import Required from './Required.svelte'
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher()
let path: string = '';
let path: string = ''
let variable: {
value: string;
is_secret: boolean;
description: string;
value: string
is_secret: boolean
description: string
} = {
value: '',
is_secret: true,
description: ''
};
let valid = true;
}
let valid = true
let modal: Modal;
let edit = false;
let initialPath: string;
let modal: Modal
let edit = false
let initialPath: string
export function initNew(): void {
variable = {
value: '',
is_secret: true,
description: ''
};
edit = false;
modal.openModal();
}
edit = false
modal.openModal()
}
export async function editVariable(path: string): Promise<void> {
edit = true;
edit = true
const getV = await VariableService.getVariable({
workspace: $workspaceStore ?? '',
path,
decryptSecret: false
});
})
variable = {
value: getV.value ?? '',
is_secret: getV.is_secret,
description: getV.description ?? ''
};
initialPath = path;
modal.openModal();
}
initialPath = path
modal.openModal()
}
const MAX_VARIABLE_LENGTH = 3000;
const MAX_VARIABLE_LENGTH = 3000
$: valid = variable.value.length < MAX_VARIABLE_LENGTH;
$: valid = variable.value.length < MAX_VARIABLE_LENGTH
async function createVariable(): Promise<void> {
try {
@@ -68,12 +68,12 @@
is_secret: variable.is_secret,
description: variable.description
}
});
sendUserToast(`Successfully created variable ${path}`);
dispatch('create');
modal.closeModal();
})
sendUserToast(`Successfully created variable ${path}`)
dispatch('create')
modal.closeModal()
} catch (err) {
sendUserToast(`Could not create variable: ${err.body}`, true);
sendUserToast(`Could not create variable: ${err.body}`, true)
}
}
@@ -83,7 +83,7 @@
workspace: $workspaceStore ?? '',
path: initialPath,
decryptSecret: false
});
})
await VariableService.updateVariable({
workspace: $workspaceStore!,
@@ -94,12 +94,12 @@
is_secret: getV.is_secret != variable.is_secret ? variable.is_secret : undefined,
description: getV.description != variable.description ? variable.description : undefined
}
});
sendUserToast(`Successfully updated variable at ${initialPath}`);
dispatch('create');
modal.closeModal();
})
sendUserToast(`Successfully updated variable at ${initialPath}`)
dispatch('create')
modal.closeModal()
} catch (err) {
sendUserToast(`Could not update variable: ${err.body}`, true);
sendUserToast(`Could not update variable: ${err.body}`, true)
}
}
</script>
@@ -157,9 +157,9 @@
type="button"
on:click={() => {
if (edit) {
updateVariable();
updateVariable()
} else {
createVariable();
createVariable()
}
}}
disabled={!valid}
@@ -1,6 +1,6 @@
<script lang="ts">
export let height = '24px';
export let width = '24px';
export let height = '24px'
export let width = '24px'
</script>
<!-- By Fabián Alexis - https://github.com/fabianalexisinostroza/Antu, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=49951194 -->
@@ -1,6 +1,6 @@
<script lang="ts">
export let height = '24px';
export let width = '24px';
export let height = '24px'
export let width = '24px'
</script>
<svg
@@ -1,7 +1,7 @@
<!-- Used under Fair Use: https://fr.wikipedia.org/wiki/Fichier:MySQL.svg -->
<script lang="ts">
export let height = '24px';
export let width = '24px';
export let height = '24px'
export let width = '24px'
</script>
<svg
@@ -1,6 +1,6 @@
<script lang="ts">
export let height = '24px';
export let width = '24px';
export let height = '24px'
export let width = '24px'
</script>
<svg
@@ -1,6 +1,6 @@
<script lang="ts">
export let height = '24px';
export let width = '24px';
export let height = '24px'
export let width = '24px'
</script>
<svg
+10 -10
View File
@@ -1,22 +1,22 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { sendUserToast } from '../utils';
import CenteredPage from './components/CenteredPage.svelte';
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { sendUserToast } from '../utils'
import CenteredPage from './components/CenteredPage.svelte'
let error = $page.url.searchParams.get('error');
let client_name = $page.url.searchParams.get('client_name');
let error = $page.url.searchParams.get('error')
let client_name = $page.url.searchParams.get('client_name')
if (client_name) {
sendUserToast(
`Connection added for ${client_name}. The oauth token has been stored in the variables`
);
)
} else if (error) {
sendUserToast(`Error adding connection ${error}`, true);
sendUserToast(`Error adding connection ${error}`, true)
} else {
goto('/variables');
goto('/variables')
}
setTimeout(() => goto('/workspace_settings'), 5000);
setTimeout(() => goto('/workspace_settings'), 5000)
</script>
<CenteredPage>
+49 -49
View File
@@ -1,10 +1,10 @@
<script lang="ts">
import Fuse from 'fuse.js';
import { FlowService } from '../gen';
import type { Flow } from '../gen';
import Fuse from 'fuse.js'
import { FlowService } from '../gen'
import type { Flow } from '../gen'
import { sendUserToast, groupBy, canWrite } from '../utils';
import Icon from 'svelte-awesome';
import { sendUserToast, groupBy, canWrite } from '../utils'
import Icon from 'svelte-awesome'
import {
faArchive,
faCalendarAlt,
@@ -14,95 +14,95 @@
faPlay,
faPlus,
faShare
} from '@fortawesome/free-solid-svg-icons';
} from '@fortawesome/free-solid-svg-icons'
import Dropdown from './components/Dropdown.svelte';
import PageHeader from './components/PageHeader.svelte';
import Tooltip from './components/Tooltip.svelte';
import ShareModal from './components/ShareModal.svelte';
import SharedBadge from './components/SharedBadge.svelte';
import { superadmin, usernameStore, userStore, workspaceStore } from '../stores';
import CenteredPage from './components/CenteredPage.svelte';
import Tabs from './components/Tabs.svelte';
import { slide } from 'svelte/transition';
import Dropdown from './components/Dropdown.svelte'
import PageHeader from './components/PageHeader.svelte'
import Tooltip from './components/Tooltip.svelte'
import ShareModal from './components/ShareModal.svelte'
import SharedBadge from './components/SharedBadge.svelte'
import { superadmin, usernameStore, userStore, workspaceStore } from '../stores'
import CenteredPage from './components/CenteredPage.svelte'
import Tabs from './components/Tabs.svelte'
import { slide } from 'svelte/transition'
type Tab = 'all' | 'personal' | 'groups' | 'shared';
type Section = [string, FlowW[]];
type FlowW = Flow & { canWrite: boolean; tab: Tab };
let flows: FlowW[] = [];
let filteredFlows: FlowW[];
let flowFilter = '';
let groupedFlows: Section[] = [];
type Tab = 'all' | 'personal' | 'groups' | 'shared'
type Section = [string, FlowW[]]
type FlowW = Flow & { canWrite: boolean; tab: Tab }
let flows: FlowW[] = []
let filteredFlows: FlowW[]
let flowFilter = ''
let groupedFlows: Section[] = []
let tab: Tab = 'all';
let tab: Tab = 'all'
let shareModal: ShareModal;
let shareModal: ShareModal
const fuseOptions = {
includeScore: false,
keys: ['description', 'path', 'content', 'hash', 'summary']
};
const fuse: Fuse<FlowW> = new Fuse(flows, fuseOptions);
}
const fuse: Fuse<FlowW> = new Fuse(flows, fuseOptions)
$: filteredFlows =
flowFilter.length > 0 ? fuse.search(flowFilter).map((value) => value.item) : flows;
flowFilter.length > 0 ? fuse.search(flowFilter).map((value) => value.item) : flows
$: {
let defaults: string[] = [];
let defaults: string[] = []
if (tab == 'all' || tab == 'personal') {
defaults = defaults.concat(`u/${$usernameStore}`);
defaults = defaults.concat(`u/${$usernameStore}`)
}
if (tab == 'all' || tab == 'groups') {
defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? []);
defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? [])
}
groupedFlows = groupBy(
filteredFlows,
(sc: Flow) => sc.path.split('/').slice(0, 2).join('/'),
defaults
);
)
}
function tabFromPath(path: string) {
let t: Tab = 'shared';
let path_prefix = path.split('/').slice(0, 2);
let t: Tab = 'shared'
let path_prefix = path.split('/').slice(0, 2)
if (path_prefix[0] == 'u' && path_prefix[1] == $usernameStore) {
t = 'personal';
t = 'personal'
} else if (path_prefix[0] == 'g' && $userStore?.groups.includes(path_prefix[1])) {
t = 'groups';
t = 'groups'
}
return t;
return t
}
async function loadFlows(): Promise<void> {
const allFlows = (await FlowService.listFlows({ workspace: $workspaceStore! })).map(
(x: Flow) => {
let t: Tab = tabFromPath(x.path);
let t: Tab = tabFromPath(x.path)
return {
canWrite:
canWrite(x.path, x.extra_perms, $userStore) && x.workspace_id == $workspaceStore,
tab: t,
...x
};
}
}
);
flows = tab == 'all' ? allFlows : allFlows.filter((x) => x.tab == tab);
fuse.setCollection(flows);
)
flows = tab == 'all' ? allFlows : allFlows.filter((x) => x.tab == tab)
fuse.setCollection(flows)
}
async function archiveFlow(path: string): Promise<void> {
try {
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path });
loadFlows();
sendUserToast(`Successfully archived flow ${path}`);
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path })
loadFlows()
sendUserToast(`Successfully archived flow ${path}`)
} catch (err) {
sendUserToast(`Could not archive this flow ${err.body}`, true);
sendUserToast(`Could not archive this flow ${err.body}`, true)
}
}
$: {
if ($workspaceStore && ($userStore || $superadmin)) {
loadFlows();
loadFlows()
}
}
</script>
@@ -215,7 +215,7 @@
displayName: 'Share',
icon: faShare,
action: () => {
shareModal.openModal(path);
shareModal.openModal(path)
},
disabled: !canWrite
},
@@ -223,7 +223,7 @@
displayName: 'Archive',
icon: faArchive,
action: () => {
path ? archiveFlow(path) : null;
path ? archiveFlow(path) : null
},
type: 'delete',
disabled: !canWrite
@@ -258,7 +258,7 @@
bind:this={shareModal}
kind="flow"
on:change={() => {
loadFlows();
loadFlows()
}}
/>
+6 -6
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import { page } from '$app/stores';
import { page } from '$app/stores'
import FlowBuilder from '../components/FlowBuilder.svelte';
import type { Flow } from '../../gen';
import { emptySchema } from '../../utils';
import FlowBuilder from '../components/FlowBuilder.svelte'
import type { Flow } from '../../gen'
import { emptySchema } from '../../utils'
const initialState = $page.url.searchParams.get('state');
const initialState = $page.url.searchParams.get('state')
let flow: Flow =
initialState != undefined
@@ -19,7 +19,7 @@
archived: false,
extra_perms: {},
schema: emptySchema()
};
}
</script>
<FlowBuilder {flow} />
+12 -12
View File
@@ -1,13 +1,13 @@
<script lang="ts">
import { FlowService, type Flow } from '../../../gen';
import { FlowService, type Flow } from '../../../gen'
import { page } from '$app/stores';
import { workspaceStore } from '../../../stores';
import FlowBuilder from '../../components/FlowBuilder.svelte';
import { emptySchema } from '../../../utils';
import { page } from '$app/stores'
import { workspaceStore } from '../../../stores'
import FlowBuilder from '../../components/FlowBuilder.svelte'
import { emptySchema } from '../../../utils'
const initialState = $page.url.searchParams.get('state');
let flowLoadedFromUrl = initialState != undefined ? JSON.parse(atob(initialState)) : undefined;
const initialState = $page.url.searchParams.get('state')
let flowLoadedFromUrl = initialState != undefined ? JSON.parse(atob(initialState)) : undefined
let flow: Flow = {
path: $page.params.path,
@@ -18,9 +18,9 @@
archived: false,
extra_perms: {},
schema: emptySchema()
};
}
let initialPath: string = '';
let initialPath: string = ''
async function loadFlow(): Promise<void> {
flow =
@@ -29,13 +29,13 @@
: await FlowService.getFlowByPath({
workspace: $workspaceStore!,
path: flow.path
});
initialPath = flow.path;
})
initialPath = flow.path
}
$: {
if ($workspaceStore) {
loadFlow();
loadFlow()
}
}
</script>
+30 -30
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { page } from '$app/stores';
import { FlowService, type Flow } from '../../../gen';
import { sendUserToast, displayDaysAgo, canWrite, getUser } from '../../../utils';
import Icon from 'svelte-awesome';
import { page } from '$app/stores'
import { FlowService, type Flow } from '../../../gen'
import { sendUserToast, displayDaysAgo, canWrite, getUser } from '../../../utils'
import Icon from 'svelte-awesome'
import {
faPlay,
faEdit,
@@ -10,46 +10,46 @@
faList,
faCalendar,
faShare
} from '@fortawesome/free-solid-svg-icons';
import Highlight from 'svelte-highlight';
import json from 'svelte-highlight/src/languages/json';
} from '@fortawesome/free-solid-svg-icons'
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/src/languages/json'
import github from 'svelte-highlight/src/styles/github';
import Tooltip from '../../components/Tooltip.svelte';
import ShareModal from '../../components/ShareModal.svelte';
import { workspaceStore } from '../../../stores';
import SharedBadge from '../../components/SharedBadge.svelte';
import SvelteMarkdown from 'svelte-markdown';
import SchemaViewer from '../../components/SchemaViewer.svelte';
import Dropdown from '../../components/Dropdown.svelte';
import CenteredPage from '../../components/CenteredPage.svelte';
import github from 'svelte-highlight/src/styles/github'
import Tooltip from '../../components/Tooltip.svelte'
import ShareModal from '../../components/ShareModal.svelte'
import { workspaceStore } from '../../../stores'
import SharedBadge from '../../components/SharedBadge.svelte'
import SvelteMarkdown from 'svelte-markdown'
import SchemaViewer from '../../components/SchemaViewer.svelte'
import Dropdown from '../../components/Dropdown.svelte'
import CenteredPage from '../../components/CenteredPage.svelte'
let flow: Flow | undefined;
let can_write = false;
let flow: Flow | undefined
let can_write = false
let path = $page.params.path;
let shareModal: ShareModal;
let path = $page.params.path
let shareModal: ShareModal
$: {
if ($workspaceStore) {
loadFlow(path);
loadFlow(path)
}
}
async function archiveFlow(hash: string): Promise<void> {
try {
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path });
loadFlow(path);
await FlowService.archiveFlowByPath({ workspace: $workspaceStore!, path })
loadFlow(path)
} catch (err) {
console.error(err);
sendUserToast(`Could not archive this flow ${err.body}`, true);
console.error(err)
sendUserToast(`Could not archive this flow ${err.body}`, true)
}
}
async function loadFlow(hash: string): Promise<void> {
flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path });
const user = await getUser($workspaceStore!);
can_write = canWrite(flow.path, flow.extra_perms!, user);
flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path })
const user = await getUser($workspaceStore!)
can_write = canWrite(flow.path, flow.extra_perms!, user)
}
</script>
@@ -78,7 +78,7 @@
displayName: 'Share',
icon: faShare,
action: () => {
shareModal.openModal();
shareModal.openModal()
},
disabled: !can_write
},
@@ -92,7 +92,7 @@
icon: faArchive,
type: 'delete',
action: () => {
flow?.path && archiveFlow(flow.path);
flow?.path && archiveFlow(flow.path)
},
disabled: flow.archived || !can_write
}
+20 -20
View File
@@ -1,48 +1,48 @@
<script lang="ts">
import { page } from '$app/stores';
import { sendUserToast } from '../../../utils';
import { FlowService, type Flow, JobService } from '../../../gen';
import { goto } from '$app/navigation';
import { workspaceStore } from '../../../stores';
import CenteredPage from '../../components/CenteredPage.svelte';
import RunForm from '../../components/RunForm.svelte';
import PageHeader from '../../components/PageHeader.svelte';
import { page } from '$app/stores'
import { sendUserToast } from '../../../utils'
import { FlowService, type Flow, JobService } from '../../../gen'
import { goto } from '$app/navigation'
import { workspaceStore } from '../../../stores'
import CenteredPage from '../../components/CenteredPage.svelte'
import RunForm from '../../components/RunForm.svelte'
import PageHeader from '../../components/PageHeader.svelte'
const path = $page.params.path;
let flow: Flow | undefined;
const path = $page.params.path
let flow: Flow | undefined
async function loadFlow() {
try {
if (path) {
flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path });
flow = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path })
} else {
sendUserToast(`Failed to fetch flow path from URL`, true);
sendUserToast(`Failed to fetch flow path from URL`, true)
}
} catch (err) {
console.error(err);
sendUserToast(`Could not load flow: ${err}`, true);
console.error(err)
sendUserToast(`Could not load flow: ${err}`, true)
}
}
async function runFlow(scheduledForStr: string | undefined, args: Record<string, any>) {
try {
const scheduledFor = scheduledForStr ? new Date(scheduledForStr).toISOString() : undefined;
const scheduledFor = scheduledForStr ? new Date(scheduledForStr).toISOString() : undefined
let run = await JobService.runFlowByPath({
workspace: $workspaceStore!,
path,
requestBody: args,
scheduledFor
});
sendUserToast(`Job <a href='/run/${run}'>${run}</a> was created.`);
goto('/run/' + run);
})
sendUserToast(`Job <a href='/run/${run}'>${run}</a> was created.`)
goto('/run/' + run)
} catch (err) {
sendUserToast(`Could not create job: ${err}`, true);
sendUserToast(`Could not create job: ${err}`, true)
}
}
$: {
if ($workspaceStore) {
loadFlow();
loadFlow()
}
}
</script>
+31 -31
View File
@@ -1,51 +1,51 @@
<script lang="ts">
import { canWrite, getUser } from '../utils';
import { GroupService } from '../gen';
import type { Group } from '../gen';
import { canWrite, getUser } from '../utils'
import { GroupService } from '../gen'
import type { Group } from '../gen'
import PageHeader from './components/PageHeader.svelte';
import TableCustom from './components/TableCustom.svelte';
import Dropdown from './components/Dropdown.svelte';
import ShareModal from './components/ShareModal.svelte';
import SharedBadge from './components/SharedBadge.svelte';
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons';
import { workspaceStore } from '../stores';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
import GroupModal from './components/GroupModal.svelte';
import PageHeader from './components/PageHeader.svelte'
import TableCustom from './components/TableCustom.svelte'
import Dropdown from './components/Dropdown.svelte'
import ShareModal from './components/ShareModal.svelte'
import SharedBadge from './components/SharedBadge.svelte'
import { faEdit, faPlus, faShare } from '@fortawesome/free-solid-svg-icons'
import { workspaceStore } from '../stores'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import GroupModal from './components/GroupModal.svelte'
type GroupW = Group & { canWrite: boolean };
type GroupW = Group & { canWrite: boolean }
let newGroupName: string = '';
let groups: GroupW[] = [];
let shareModal: ShareModal;
let groupModal: GroupModal;
let newGroupName: string = ''
let groups: GroupW[] = []
let shareModal: ShareModal
let groupModal: GroupModal
async function loadGroups(): Promise<void> {
const user = await getUser($workspaceStore!);
const user = await getUser($workspaceStore!)
groups = (await GroupService.listGroups({ workspace: $workspaceStore! })).map((x) => {
return { canWrite: canWrite(x.name, x.extra_perms ?? {}, user), ...x };
});
return { canWrite: canWrite(x.name, x.extra_perms ?? {}, user), ...x }
})
}
function handleKeyUp(event: KeyboardEvent) {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 13 || key === 'Enter') {
event.preventDefault();
addGroup();
event.preventDefault()
addGroup()
}
}
async function addGroup() {
await GroupService.createGroup({
workspace: $workspaceStore ?? '',
requestBody: { name: newGroupName }
});
loadGroups();
})
loadGroups()
}
$: {
if ($workspaceStore) {
loadGroups();
loadGroups()
}
}
</script>
@@ -54,7 +54,7 @@
bind:this={shareModal}
kind="group_"
on:change={() => {
loadGroups();
loadGroups()
}}
/>
@@ -90,7 +90,7 @@
><a
href="#{name}"
on:click={() => {
groupModal.openModal(name);
groupModal.openModal(name)
}}>{name}</a
>
<div>
@@ -106,7 +106,7 @@
icon: faEdit,
disabled: !canWrite,
action: () => {
groupModal.openModal(name);
groupModal.openModal(name)
}
},
{
@@ -114,7 +114,7 @@
icon: faShare,
disabled: !canWrite,
action: () => {
shareModal.openModal(name);
shareModal.openModal(name)
}
}
]}
+3 -3
View File
@@ -1,8 +1,8 @@
<script lang="ts">
import { goto } from '$app/navigation';
import CenteredPage from './components/CenteredPage.svelte';
import { goto } from '$app/navigation'
import CenteredPage from './components/CenteredPage.svelte'
goto('/scripts');
goto('/scripts')
</script>
<CenteredPage>
+76 -76
View File
@@ -1,58 +1,58 @@
<script lang="ts">
import { canWrite, emptySchema, getUser, sendUserToast } from '../utils';
import { ResourceService } from '../gen';
import type { Resource, ResourceType } from '../gen';
import PageHeader from './components/PageHeader.svelte';
import ResourceEditor from './components/ResourceEditor.svelte';
import Button from './components/Button.svelte';
import TableCustom from './components/TableCustom.svelte';
import Modal from './components/Modal.svelte';
import Highlight from 'svelte-highlight';
import json from 'svelte-highlight/src/languages/json';
import github from 'svelte-highlight/src/styles/github';
import IconedResourceType from './components/IconedResourceType.svelte';
import ShareModal from './components/ShareModal.svelte';
import SharedBadge from './components/SharedBadge.svelte';
import SvelteMarkdown from 'svelte-markdown';
import { userStore, workspaceStore, type UserExt } from '../stores';
import SchemaEditor from './components/SchemaEditor.svelte';
import type { Schema } from '../common';
import SchemaViewer from './components/SchemaViewer.svelte';
import Dropdown from './components/Dropdown.svelte';
import { faEdit, faPlus, faShare, faTrash } from '@fortawesome/free-solid-svg-icons';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
import Required from './components/Required.svelte';
import { canWrite, emptySchema, getUser, sendUserToast } from '../utils'
import { ResourceService } from '../gen'
import type { Resource, ResourceType } from '../gen'
import PageHeader from './components/PageHeader.svelte'
import ResourceEditor from './components/ResourceEditor.svelte'
import Button from './components/Button.svelte'
import TableCustom from './components/TableCustom.svelte'
import Modal from './components/Modal.svelte'
import Highlight from 'svelte-highlight'
import json from 'svelte-highlight/src/languages/json'
import github from 'svelte-highlight/src/styles/github'
import IconedResourceType from './components/IconedResourceType.svelte'
import ShareModal from './components/ShareModal.svelte'
import SharedBadge from './components/SharedBadge.svelte'
import SvelteMarkdown from 'svelte-markdown'
import { userStore, workspaceStore, type UserExt } from '../stores'
import SchemaEditor from './components/SchemaEditor.svelte'
import type { Schema } from '../common'
import SchemaViewer from './components/SchemaViewer.svelte'
import Dropdown from './components/Dropdown.svelte'
import { faEdit, faPlus, faShare, faTrash } from '@fortawesome/free-solid-svg-icons'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import Required from './components/Required.svelte'
type ResourceW = Resource & { canWrite: boolean };
type ResourceTypeW = ResourceType & { canWrite: boolean };
type ResourceW = Resource & { canWrite: boolean }
type ResourceTypeW = ResourceType & { canWrite: boolean }
let resources: ResourceW[] | undefined;
let resourceTypes: ResourceTypeW[] | undefined;
let resourceViewer: Modal;
let resourceViewerTitle: string = '';
let resourceViewerSchema: Schema = emptySchema();
let resources: ResourceW[] | undefined
let resourceTypes: ResourceTypeW[] | undefined
let resourceViewer: Modal
let resourceViewerTitle: string = ''
let resourceViewerSchema: Schema = emptySchema()
let typeModalMode: 'view' | 'view-type' | 'create' = 'view';
let newResourceTypeName: string;
let newResourceTypeSchema: Schema;
let newResourceTypeDescription: string;
let typeModalMode: 'view' | 'view-type' | 'create' = 'view'
let newResourceTypeName: string
let newResourceTypeSchema: Schema
let newResourceTypeDescription: string
let resourceEditor: ResourceEditor | undefined;
let resourceEditor: ResourceEditor | undefined
let user: UserExt | undefined;
$: user = $userStore;
let user: UserExt | undefined
$: user = $userStore
let shareModal: ShareModal;
let shareModal: ShareModal
async function loadResources(): Promise<void> {
const user = await getUser($workspaceStore!);
const user = await getUser($workspaceStore!)
resources = (await ResourceService.listResource({ workspace: $workspaceStore! })).map((x) => {
return {
canWrite: canWrite(x.path, x.extra_perms!, user) && $workspaceStore! == x.workspace_id,
...x
};
});
}
})
}
async function loadResourceTypes(): Promise<void> {
@@ -61,14 +61,14 @@
return {
canWrite: $workspaceStore! == x.workspace_id,
...x
};
}
}
);
)
}
async function deleteResource(path: string): Promise<void> {
await ResourceService.deleteResource({ workspace: $workspaceStore!, path });
loadResources();
await ResourceService.deleteResource({ workspace: $workspaceStore!, path })
loadResources()
}
async function addResourceType(): Promise<void> {
@@ -80,34 +80,34 @@
schema: newResourceTypeSchema,
description: newResourceTypeDescription
}
});
resourceViewer.closeModal();
loadResourceTypes();
})
resourceViewer.closeModal()
loadResourceTypes()
} catch (err) {
sendUserToast(`Could not create resource type: ${err?.body || err}`, true);
sendUserToast(`Could not create resource type: ${err?.body || err}`, true)
}
}
async function handleDeleteResourceType(name: string) {
try {
await ResourceService.deleteResourceType({ workspace: $workspaceStore!, path: name });
loadResourceTypes();
await ResourceService.deleteResourceType({ workspace: $workspaceStore!, path: name })
loadResourceTypes()
} catch (err) {
if (err.status === 400 && err.body.includes('foreign key')) {
sendUserToast(
`Could not delete resource type because there are resources attached to it. Delete resources using this type first.`,
true
);
)
} else {
sendUserToast(`Could not delete resource type: ${err?.body || err}`, true);
sendUserToast(`Could not delete resource type: ${err?.body || err}`, true)
}
}
}
$: {
if ($workspaceStore) {
loadResources();
loadResourceTypes();
loadResources()
loadResourceTypes()
}
}
</script>
@@ -121,7 +121,7 @@
<button
class="default-button"
on:click={() => {
resourceEditor?.initNew();
resourceEditor?.initNew()
}}><Icon class="text-white mb-1" data={faPlus} scale={0.9} /> &nbsp; Add a resource</button
>
</PageHeader>
@@ -142,15 +142,15 @@
><a
href="#{path}"
on:click={async () => {
resourceViewerTitle = `Resource ${path}`;
resourceViewerTitle = `Resource ${path}`
resourceViewerSchema = (
await ResourceService.getResource({
workspace: $workspaceStore ?? 'no_workspace',
path
})
).value;
typeModalMode = 'view';
resourceViewer.openModal();
).value
typeModalMode = 'view'
resourceViewer.openModal()
}}>{path}</a
>
<div class="mb-1 -mt-1"><SharedBadge {canWrite} extraPerms={extra_perms} /></div>
@@ -165,7 +165,7 @@
icon: faShare,
disabled: !canWrite,
action: () => {
shareModal.openModal(path);
shareModal.openModal(path)
}
},
{
@@ -173,7 +173,7 @@
icon: faEdit,
disabled: !canWrite,
action: () => {
resourceEditor?.initEdit(path);
resourceEditor?.initEdit(path)
}
},
{
@@ -182,7 +182,7 @@
icon: faTrash,
type: 'delete',
action: () => {
deleteResource(path);
deleteResource(path)
}
}
]}
@@ -204,13 +204,13 @@
<button
class="default-button"
on:click={() => {
resourceViewerTitle = `Create resource type`;
resourceViewerTitle = `Create resource type`
newResourceTypeName = 'my_resource_type';
newResourceTypeSchema = emptySchema();
newResourceTypeDescription = 'my description';
typeModalMode = 'create';
resourceViewer.openModal();
newResourceTypeName = 'my_resource_type'
newResourceTypeSchema = emptySchema()
newResourceTypeDescription = 'my description'
typeModalMode = 'create'
resourceViewer.openModal()
}}><Icon class="text-white mb-1" data={faPlus} scale={0.9} /> &nbsp; Add a type</button
>
</PageHeader>
@@ -229,10 +229,10 @@
><a
href="#{name}"
on:click={() => {
resourceViewerTitle = `Resource type ${name}`;
resourceViewerSchema = schema;
typeModalMode = 'view-type';
resourceViewer.openModal();
resourceViewerTitle = `Resource type ${name}`
resourceViewerSchema = schema
typeModalMode = 'view-type'
resourceViewer.openModal()
}}><IconedResourceType {name} /></a
></td
>
@@ -243,7 +243,7 @@
category="delete"
class="mx-2"
on:click={() => {
handleDeleteResourceType(name);
handleDeleteResourceType(name)
}}
disabled={!(user?.is_admin ?? false)}
/>
@@ -266,7 +266,7 @@
bind:this={shareModal}
kind="resource"
on:change={() => {
loadResources();
loadResources()
}}
/>
+60 -60
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import { JobService, Job } from '../../gen';
import { onDestroy } from 'svelte';
import { page } from '$app/stores'
import { JobService, Job } from '../../gen'
import { onDestroy } from 'svelte'
import {
canWrite,
displayDaysAgo,
@@ -9,9 +9,9 @@
sendUserToast,
truncate,
truncateHash
} from '../../utils';
import Icon from 'svelte-awesome';
import { check, minus } from 'svelte-awesome/icons';
} from '../../utils'
import Icon from 'svelte-awesome'
import { check, minus } from 'svelte-awesome/icons'
import {
faBolt,
faCircle,
@@ -27,121 +27,121 @@
faRobot,
faScroll,
faWind
} from '@fortawesome/free-solid-svg-icons';
import Tooltip from '../components/Tooltip.svelte';
import DisplayResult from '../components/DisplayResult.svelte';
} from '@fortawesome/free-solid-svg-icons'
import Tooltip from '../components/Tooltip.svelte'
import DisplayResult from '../components/DisplayResult.svelte'
import Highlight from 'svelte-highlight';
import python from 'svelte-highlight/src/languages/python';
import github from 'svelte-highlight/src/styles/github';
import { userStore, workspaceStore } from '../../stores';
import CenteredPage from '../components/CenteredPage.svelte';
import FlowStatusViewer from '../components/FlowStatusViewer.svelte';
import JobStatus from '../components/JobStatus.svelte';
import TableCustom from '../components/TableCustom.svelte';
import ArgInfo from '../components/ArgInfo.svelte';
import Highlight from 'svelte-highlight'
import python from 'svelte-highlight/src/languages/python'
import github from 'svelte-highlight/src/styles/github'
import { userStore, workspaceStore } from '../../stores'
import CenteredPage from '../components/CenteredPage.svelte'
import FlowStatusViewer from '../components/FlowStatusViewer.svelte'
import JobStatus from '../components/JobStatus.svelte'
import TableCustom from '../components/TableCustom.svelte'
import ArgInfo from '../components/ArgInfo.svelte'
let workspace_id_query: string | undefined = $page.url.searchParams.get('workspace') ?? undefined;
let workspace_id: string | undefined;
let workspace_id_query: string | undefined = $page.url.searchParams.get('workspace') ?? undefined
let workspace_id: string | undefined
let intervalId: NodeJS.Timer;
let job: Job | undefined;
let jobs = [];
let error: Error | undefined;
const iconScale = 1;
let syncIteration: number = 0;
const iterationsBeforeSlowRefresh = 100;
let viewTab: 'result' | 'logs' | 'code' = 'result';
let intervalId: NodeJS.Timer
let job: Job | undefined
let jobs = []
let error: Error | undefined
const iconScale = 1
let syncIteration: number = 0
const iterationsBeforeSlowRefresh = 100
let viewTab: 'result' | 'logs' | 'code' = 'result'
const SMALL_ICON_SCALE = 0.7;
const SMALL_ICON_SCALE = 0.7
async function deleteCompletedJob(id: string): Promise<void> {
try {
await JobService.deleteCompletedJob({ workspace: workspace_id!, id });
loadLogs();
await JobService.deleteCompletedJob({ workspace: workspace_id!, id })
loadLogs()
} catch (err) {
console.error(err);
sendUserToast(`Could not delete this script ${err.body}`, true);
console.error(err)
sendUserToast(`Could not delete this script ${err.body}`, true)
}
}
async function cancelJob(id: string) {
try {
await JobService.cancelQueuedJob({ workspace: workspace_id!, id, requestBody: {} });
sendUserToast(`job ${id} canceled`);
await JobService.cancelQueuedJob({ workspace: workspace_id!, id, requestBody: {} })
sendUserToast(`job ${id} canceled`)
} catch (err) {
sendUserToast('could not cancel job', true);
sendUserToast('could not cancel job', true)
}
}
async function checkCompleted(): Promise<void> {
if (job?.type === 'CompletedJob') {
//only CompletedJob has success property
clearInterval(intervalId);
clearInterval(intervalId)
}
}
async function loadLogs(): Promise<void> {
try {
let jobId = $page.params.run;
let jobId = $page.params.run
if (job && `running` in job && job.job_kind != 'flow' && job.job_kind != 'flowpreview') {
let jobUpdates = await JobService.getJobUpdates({
workspace: workspace_id!,
id: jobId,
running: job.running,
logOffset: job.logs?.length ?? 0
});
})
if (jobUpdates.new_logs) {
job.logs = (job.logs ?? '').concat(jobUpdates.new_logs);
job.logs = (job.logs ?? '').concat(jobUpdates.new_logs)
}
if ((jobUpdates.running ?? false) || (jobUpdates.completed ?? false)) {
job = await JobService.getJob({ workspace: workspace_id!, id: jobId });
job = await JobService.getJob({ workspace: workspace_id!, id: jobId })
}
} else {
job = await JobService.getJob({ workspace: workspace_id!, id: jobId });
job = await JobService.getJob({ workspace: workspace_id!, id: jobId })
}
checkCompleted();
initView();
checkCompleted()
initView()
} catch (err) {
error = err;
console.error(error);
error = err
console.error(error)
}
}
function syncer(): void {
if (syncIteration > iterationsBeforeSlowRefresh) {
loadLogs();
loadLogs()
if (intervalId) {
clearInterval(intervalId);
intervalId = setInterval(loadLogs, 5000);
clearInterval(intervalId)
intervalId = setInterval(loadLogs, 5000)
}
} else {
syncIteration++;
loadLogs();
syncIteration++
loadLogs()
}
}
// If we get results, focus on that tab. Else, focus on logs
function initView(): void {
if (job && 'result' in job && job.result) {
viewTab = 'result';
viewTab = 'result'
} else {
viewTab = 'logs';
viewTab = 'logs'
}
}
$: {
if ($workspaceStore && $page.params.run) {
workspace_id = workspace_id_query ?? $workspaceStore;
intervalId && clearInterval(intervalId);
intervalId = setInterval(syncer, 500);
workspace_id = workspace_id_query ?? $workspaceStore
intervalId && clearInterval(intervalId)
intervalId = setInterval(syncer, 500)
}
}
onDestroy(() => {
intervalId && clearInterval(intervalId);
});
intervalId && clearInterval(intervalId)
})
</script>
<CenteredPage>
@@ -206,7 +206,7 @@
class="default-button-secondary bg-transparent hover:bg-red-500 text-red-700 border-red-500 hover:text-white hover:border-transparent py-1"
on:click={() => {
if (job?.id) {
deleteCompletedJob(job?.id);
deleteCompletedJob(job?.id)
}
}}
>
@@ -221,7 +221,7 @@
class="default-button-secondary bg-transparent hover:bg-red-500 text-red-700 border-red-500 hover:text-white hover:border-transparent"
on:click|once={() => {
if (job?.id) {
cancelJob(job?.id);
cancelJob(job?.id)
}
}}
>
+49 -49
View File
@@ -1,9 +1,9 @@
<script lang="ts">
import { onDestroy, onMount } from 'svelte';
import { JobService, Job, CompletedJob } from '../../gen';
import { displayDate, displayDaysAgo, forLater, truncateHash } from '../../utils';
import Icon from 'svelte-awesome';
import { check } from 'svelte-awesome/icons';
import { onDestroy, onMount } from 'svelte'
import { JobService, Job, CompletedJob } from '../../gen'
import { displayDate, displayDaysAgo, forLater, truncateHash } from '../../utils'
import Icon from 'svelte-awesome'
import { check } from 'svelte-awesome/icons'
import {
faCalendar,
faCircle,
@@ -13,33 +13,33 @@
faTimes,
faUser,
faWind
} from '@fortawesome/free-solid-svg-icons';
import { page } from '$app/stores';
import { sendUserToast } from '../../utils';
import { goto } from '$app/navigation';
import PageHeader from '../components/PageHeader.svelte';
import { workspaceStore } from '../../stores';
import CenteredPage from '../components/CenteredPage.svelte';
import Tabs from '../components/Tabs.svelte';
} from '@fortawesome/free-solid-svg-icons'
import { page } from '$app/stores'
import { sendUserToast } from '../../utils'
import { goto } from '$app/navigation'
import PageHeader from '../components/PageHeader.svelte'
import { workspaceStore } from '../../stores'
import CenteredPage from '../components/CenteredPage.svelte'
import Tabs from '../components/Tabs.svelte'
let jobs: Job[] | undefined;
let error: Error | undefined;
let intervalId: NodeJS.Timer | undefined;
let path: string = $page.params.path;
let createdBefore: string | undefined = $page.url.searchParams.get('createdBefore') ?? undefined;
let showOlderJobs = true;
let jobs: Job[] | undefined
let error: Error | undefined
let intervalId: NodeJS.Timer | undefined
let path: string = $page.params.path
let createdBefore: string | undefined = $page.url.searchParams.get('createdBefore') ?? undefined
let showOlderJobs = true
// The API returns 30 jobs per page. We use it to display a next page button or not.
const jobsPerPage = 30;
const jobsPerPage = 30
let jobKinds: string;
let jobKinds: string
$: jobKinds =
$page.url.searchParams.get('jobKinds') ??
`${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW}`;
`${CompletedJob.job_kind.SCRIPT},${CompletedJob.job_kind.FLOW}`
$: jobKinds && $workspaceStore && loadJobs();
$: jobKinds && $workspaceStore && loadJobs()
const SMALL_ICON_SCALE = 0.7;
const SMALL_ICON_SCALE = 0.7
async function fetchJobs(
createdBefore: string | undefined,
@@ -51,7 +51,7 @@
createdAfter,
scriptPathExact: path === '' ? undefined : path,
jobKinds
});
})
}
async function fetchCompletedJobs(createdBefore: string): Promise<CompletedJob[]> {
@@ -60,57 +60,57 @@
createdBefore,
scriptPathExact: path === '' ? undefined : path,
jobKinds: jobKinds
});
})
}
async function loadJobs(): Promise<void> {
try {
const newJobs = await fetchJobs(createdBefore);
showOlderJobs = newJobs.length === jobsPerPage;
jobs = newJobs;
const newJobs = await fetchJobs(createdBefore)
showOlderJobs = newJobs.length === jobsPerPage
jobs = newJobs
} catch (err) {
sendUserToast(`There was a problem fetching jobs: ${err}`, true);
error = err;
console.error(JSON.stringify(err));
sendUserToast(`There was a problem fetching jobs: ${err}`, true)
error = err
console.error(JSON.stringify(err))
}
}
async function loadOlderJobs() {
if (jobs) {
const ts = jobs[jobs.length - 1].created_at;
const olderJobs = await fetchCompletedJobs(ts!);
showOlderJobs = olderJobs.length === jobsPerPage;
jobs = jobs.concat(...olderJobs);
const ts = jobs[jobs.length - 1].created_at
const olderJobs = await fetchCompletedJobs(ts!)
showOlderJobs = olderJobs.length === jobsPerPage
jobs = jobs.concat(...olderJobs)
}
}
async function syncer() {
if (jobs && createdBefore === undefined) {
const reversedJobs = jobs.slice(0, jobsPerPage).reverse();
const lastIndex = reversedJobs.findIndex((x) => x.type == Job.type.QUEUED_JOB) - 1;
const ts = lastIndex >= 0 ? reversedJobs[lastIndex].created_at : undefined;
const newJobs = await fetchJobs(undefined, ts);
const oldJobs = jobs.map((x) => x.id);
jobs = newJobs.filter((x) => !oldJobs.includes(x.id)).concat(jobs);
const reversedJobs = jobs.slice(0, jobsPerPage).reverse()
const lastIndex = reversedJobs.findIndex((x) => x.type == Job.type.QUEUED_JOB) - 1
const ts = lastIndex >= 0 ? reversedJobs[lastIndex].created_at : undefined
const newJobs = await fetchJobs(undefined, ts)
const oldJobs = jobs.map((x) => x.id)
jobs = newJobs.filter((x) => !oldJobs.includes(x.id)).concat(jobs)
newJobs
.filter((x) => oldJobs.includes(x.id))
.forEach((x) => (jobs![jobs?.findIndex((y) => y.id == x.id)!] = x));
jobs = jobs;
.forEach((x) => (jobs![jobs?.findIndex((y) => y.id == x.id)!] = x))
jobs = jobs
}
}
$: {
if ($workspaceStore) {
loadJobs();
intervalId = setInterval(syncer, 5000);
loadJobs()
intervalId = setInterval(syncer, 5000)
}
}
onDestroy(() => {
if (intervalId) {
clearInterval(intervalId);
clearInterval(intervalId)
}
});
})
</script>
<CenteredPage>
@@ -137,7 +137,7 @@ the bearer token they use has less privilege."
]}
dflt={1}
on:update={(tab) => {
goto(`?jobKinds=${tab.detail}`);
goto(`?jobKinds=${tab.detail}`)
}}
/>
{#if jobs}
+62 -62
View File
@@ -1,58 +1,58 @@
<script lang="ts">
import { page } from '$app/stores';
import { sendUserToast, displayDate } from '../../utils';
import { ScriptService, type Script, ScheduleService, type Flow, FlowService } from '../../gen';
import { page } from '$app/stores'
import { sendUserToast, displayDate } from '../../utils'
import { ScriptService, type Script, ScheduleService, type Flow, FlowService } from '../../gen'
import PageHeader from '../components/PageHeader.svelte';
import Path from '../components/Path.svelte';
import PageHeader from '../components/PageHeader.svelte'
import Path from '../components/Path.svelte'
import Tooltip from '../components/Tooltip.svelte';
import { goto } from '$app/navigation';
import { workspaceStore } from '../../stores';
import CenteredPage from '../components/CenteredPage.svelte';
import SchemaForm from '../components/SchemaForm.svelte';
import ScriptPicker from '../components/ScriptPicker.svelte';
import Required from '../components/Required.svelte';
import Tooltip from '../components/Tooltip.svelte'
import { goto } from '$app/navigation'
import { workspaceStore } from '../../stores'
import CenteredPage from '../components/CenteredPage.svelte'
import SchemaForm from '../components/SchemaForm.svelte'
import ScriptPicker from '../components/ScriptPicker.svelte'
import Required from '../components/Required.svelte'
let initialPath = $page.url.searchParams.get('edit') || '';
let edit = initialPath === '' ? false : true;
let scheduleInput: string = '0 0 12 * *';
let cronError = '';
let initialPath = $page.url.searchParams.get('edit') || ''
let edit = initialPath === '' ? false : true
let scheduleInput: string = '0 0 12 * *'
let cronError = ''
let script_path = $page.url.searchParams.get('path') || '';
let is_flow = $page.url.searchParams.get('isFlow') == 'true';
let script_path = $page.url.searchParams.get('path') || ''
let is_flow = $page.url.searchParams.get('isFlow') == 'true'
let runnable: Script | Flow | undefined;
let args: Record<string, any> = {};
let runnable: Script | Flow | undefined
let args: Record<string, any> = {}
let validCRON = true;
let allowSchedule: boolean;
let isValid = true;
let preview: string[] = [];
let validCRON = true
let allowSchedule: boolean
let isValid = true
let preview: string[] = []
let path: string = '';
let path: string = ''
const offset = new Date().getTimezoneOffset();
const offset = new Date().getTimezoneOffset()
$: allowSchedule = isValid && validCRON && script_path != '';
$: allowSchedule = isValid && validCRON && script_path != ''
$: handleScheduleInput(scheduleInput);
$: handleScheduleInput(scheduleInput)
$: script_path && loadScript(script_path);
$: script_path && loadScript(script_path)
async function loadScript(p: string | undefined): Promise<void> {
if (p) {
try {
if (is_flow) {
runnable = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path: p });
runnable = await FlowService.getFlowByPath({ workspace: $workspaceStore!, path: p })
} else {
runnable = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: p });
runnable = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: p })
}
} catch (err) {
sendUserToast(`Could not load script: ${err}`, true);
sendUserToast(`Could not load script: ${err}`, true)
}
} else {
runnable = undefined;
runnable = undefined
}
}
@@ -61,12 +61,12 @@
const s = await ScheduleService.getSchedule({
workspace: $workspaceStore!,
path: initialPath
});
scheduleInput = s.schedule;
script_path = s.script_path ?? '';
args = s.args ?? {};
})
scheduleInput = s.schedule
script_path = s.script_path ?? ''
args = s.args ?? {}
} catch (err) {
sendUserToast(`Could not load schedule: ${err}`, true);
sendUserToast(`Could not load schedule: ${err}`, true)
}
}
@@ -82,8 +82,8 @@
is_flow: is_flow,
args
}
});
goto('/schedules');
})
goto('/schedules')
} else {
await ScheduleService.createSchedule({
workspace: $workspaceStore!,
@@ -95,22 +95,22 @@
is_flow,
args
}
});
goto('/schedules');
})
goto('/schedules')
}
} catch (err) {
sendUserToast(`Could not schedule script: ${err}`, true);
sendUserToast(`Could not schedule script: ${err}`, true)
}
}
function formatInput(input: string): string {
// Allow for cron expressions inputted by the user to omit month and year
let splitted = input.split(' ');
splitted = splitted.filter(String); //remove empty string elements
let splitted = input.split(' ')
splitted = splitted.filter(String) //remove empty string elements
if (6 - splitted.length > 0) {
return splitted.concat(Array(6 - splitted.length).fill('*')).join(' ');
return splitted.concat(Array(6 - splitted.length).fill('*')).join(' ')
} else {
return input;
return input
}
}
@@ -118,16 +118,16 @@
try {
preview = await ScheduleService.previewSchedule({
requestBody: { schedule: formatInput(input), offset }
});
cronError = '';
validCRON = true;
})
cronError = ''
validCRON = true
} catch (err) {
if (err.status == 400 && err.body.includes('cron')) {
cronError = `Invalid cron expression`;
validCRON = false;
cronError = `Invalid cron expression`
validCRON = false
} else {
sendUserToast(`Cannot preview: ${err}`, true);
validCRON = false;
sendUserToast(`Cannot preview: ${err}`, true)
validCRON = false
}
}
}
@@ -135,7 +135,7 @@
$: {
if ($workspaceStore) {
if (edit) {
loadSchedule();
loadSchedule()
}
}
}
@@ -197,20 +197,20 @@
<div class="flex flex-row text-xs text-blue-500 gap-3 pl-28">
<button
on:click={() => {
scheduleInput = '0 */15 * * *';
cronError = '';
scheduleInput = '0 */15 * * *'
cronError = ''
}}>every 15 min</button
>
<button
on:click={() => {
scheduleInput = '0 0 * * * *';
cronError = '';
scheduleInput = '0 0 * * * *'
cronError = ''
}}>every hour</button
>
<button
on:click={() => {
scheduleInput = '0 0 8 * * *';
cronError = '';
scheduleInput = '0 0 8 * * *'
cronError = ''
}}>once a day at 8AM</button
>
</div>
@@ -233,7 +233,7 @@
The next 10 runs of this script will be:
<button
on:click={() => {
preview = [];
preview = []
}}
>
<svg
+28 -28
View File
@@ -1,35 +1,35 @@
<script lang="ts">
import { sendUserToast, truncateHash, displayDate, canWrite, getUser } from '../utils';
import { type Schedule, ScheduleService } from '../gen';
import { sendUserToast, truncateHash, displayDate, canWrite, getUser } from '../utils'
import { type Schedule, ScheduleService } from '../gen'
import PageHeader from './components/PageHeader.svelte';
import TableCustom from './components/TableCustom.svelte';
import Dropdown from './components/Dropdown.svelte';
import { goto } from '$app/navigation';
import ShareModal from './components/ShareModal.svelte';
import SharedBadge from './components/SharedBadge.svelte';
import PageHeader from './components/PageHeader.svelte'
import TableCustom from './components/TableCustom.svelte'
import Dropdown from './components/Dropdown.svelte'
import { goto } from '$app/navigation'
import ShareModal from './components/ShareModal.svelte'
import SharedBadge from './components/SharedBadge.svelte'
import {
faEdit,
faPlus,
faShare,
faToggleOff,
faToggleOn
} from '@fortawesome/free-solid-svg-icons';
import { workspaceStore } from '../stores';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
} from '@fortawesome/free-solid-svg-icons'
import { workspaceStore } from '../stores'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
type ScheduleW = Schedule & { canWrite: boolean };
type ScheduleW = Schedule & { canWrite: boolean }
let schedules: ScheduleW[] = [];
let schedules: ScheduleW[] = []
let shareModal: ShareModal;
let shareModal: ShareModal
async function loadSchedules(): Promise<void> {
const user = await getUser($workspaceStore!);
const user = await getUser($workspaceStore!)
schedules = (await ScheduleService.listSchedules({ workspace: $workspaceStore! })).map((x) => {
return { canWrite: canWrite(x.path, x.extra_perms!, user), ...x };
});
return { canWrite: canWrite(x.path, x.extra_perms!, user), ...x }
})
}
async function setScheduleEnabled(path: string, enabled: boolean): Promise<void> {
@@ -38,16 +38,16 @@
path,
workspace: $workspaceStore!,
requestBody: { enabled }
});
loadSchedules();
})
loadSchedules()
} catch (err) {
sendUserToast(`Cannot ` + enabled ? 'disable' : 'enable' + ` schedule: ${err}`, true);
sendUserToast(`Cannot ` + enabled ? 'disable' : 'enable' + ` schedule: ${err}`, true)
}
}
$: {
if ($workspaceStore) {
loadSchedules();
loadSchedules()
}
}
</script>
@@ -92,9 +92,9 @@
style="cursor: pointer;"
on:click={() => {
if (canWrite) {
setScheduleEnabled(path, enabled ? false : true);
setScheduleEnabled(path, enabled ? false : true)
} else {
sendUserToast('not enough permission', true);
sendUserToast('not enough permission', true)
}
}}
><span class="m-auto block text-center">
@@ -112,7 +112,7 @@
icon: enabled ? faToggleOff : faToggleOn,
disabled: !canWrite,
action: () => {
setScheduleEnabled(path, enabled ? false : true);
setScheduleEnabled(path, enabled ? false : true)
}
},
{
@@ -120,7 +120,7 @@
icon: faEdit,
disabled: !canWrite,
action: () => {
goto(`/schedule/add?edit=${path}&isFlow=${is_flow}`);
goto(`/schedule/add?edit=${path}&isFlow=${is_flow}`)
}
},
{
@@ -128,7 +128,7 @@
icon: faShare,
disabled: !canWrite,
action: () => {
shareModal.openModal(path);
shareModal.openModal(path)
}
}
]}
@@ -146,7 +146,7 @@
bind:this={shareModal}
kind="schedule"
on:change={() => {
loadSchedules();
loadSchedules()
}}
/>
+63 -63
View File
@@ -1,10 +1,10 @@
<script lang="ts">
import Fuse from 'fuse.js';
import { ScriptService } from '../gen';
import type { Script } from '../gen';
import Fuse from 'fuse.js'
import { ScriptService } from '../gen'
import type { Script } from '../gen'
import { sendUserToast, groupBy, truncateHash, canWrite } from '../utils';
import Icon from 'svelte-awesome';
import { sendUserToast, groupBy, truncateHash, canWrite } from '../utils'
import Icon from 'svelte-awesome'
import {
faArchive,
faCalendarAlt,
@@ -14,118 +14,118 @@
faPlay,
faPlus,
faShare
} from '@fortawesome/free-solid-svg-icons';
} from '@fortawesome/free-solid-svg-icons'
import Dropdown from './components/Dropdown.svelte';
import PageHeader from './components/PageHeader.svelte';
import Modal from './components/Modal.svelte';
import Tooltip from './components/Tooltip.svelte';
import ShareModal from './components/ShareModal.svelte';
import SharedBadge from './components/SharedBadge.svelte';
import { superadmin, usernameStore, userStore, workspaceStore } from '../stores';
import CenteredPage from './components/CenteredPage.svelte';
import Tabs from './components/Tabs.svelte';
import { slide } from 'svelte/transition';
import Badge from './components/Badge.svelte';
import Dropdown from './components/Dropdown.svelte'
import PageHeader from './components/PageHeader.svelte'
import Modal from './components/Modal.svelte'
import Tooltip from './components/Tooltip.svelte'
import ShareModal from './components/ShareModal.svelte'
import SharedBadge from './components/SharedBadge.svelte'
import { superadmin, usernameStore, userStore, workspaceStore } from '../stores'
import CenteredPage from './components/CenteredPage.svelte'
import Tabs from './components/Tabs.svelte'
import { slide } from 'svelte/transition'
import Badge from './components/Badge.svelte'
type Tab = 'all' | 'personal' | 'groups' | 'shared' | 'community';
type Section = [string, ScriptW[]];
type ScriptW = Script & { canWrite: boolean; tab: Tab };
let scripts: ScriptW[] = [];
let filteredScripts: ScriptW[];
let scriptFilter = '';
let groupedScripts: Section[] = [];
let communityScripts: Section[] = [];
type Tab = 'all' | 'personal' | 'groups' | 'shared' | 'community'
type Section = [string, ScriptW[]]
type ScriptW = Script & { canWrite: boolean; tab: Tab }
let scripts: ScriptW[] = []
let filteredScripts: ScriptW[]
let scriptFilter = ''
let groupedScripts: Section[] = []
let communityScripts: Section[] = []
let templateModal: Modal;
let templateScripts: Script[] = [];
let templateFilter = '';
let filteredTemplates: Script[] | undefined;
let tab: Tab = 'all';
let templateModal: Modal
let templateScripts: Script[] = []
let templateFilter = ''
let filteredTemplates: Script[] | undefined
let tab: Tab = 'all'
let shareModal: ShareModal;
let shareModal: ShareModal
const fuseOptions = {
includeScore: false,
keys: ['description', 'path', 'content', 'hash', 'summary']
};
const fuse: Fuse<ScriptW> = new Fuse(scripts, fuseOptions);
}
const fuse: Fuse<ScriptW> = new Fuse(scripts, fuseOptions)
const templateFuse: Fuse<Script> = new Fuse(templateScripts, fuseOptions);
const templateFuse: Fuse<Script> = new Fuse(templateScripts, fuseOptions)
$: filteredScripts =
scriptFilter.length > 0 ? fuse.search(scriptFilter).map((value) => value.item) : scripts;
scriptFilter.length > 0 ? fuse.search(scriptFilter).map((value) => value.item) : scripts
$: filteredTemplates =
templateFilter.length > 0
? templateFuse.search(templateFilter).map((value) => value.item)
: templateScripts;
: templateScripts
$: {
let defaults: string[] = [];
let defaults: string[] = []
if (tab == 'all' || tab == 'personal') {
defaults = defaults.concat(`u/${$usernameStore}`);
defaults = defaults.concat(`u/${$usernameStore}`)
}
if (tab == 'all' || tab == 'groups') {
defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? []);
defaults = defaults.concat($userStore?.groups.map((x) => `g/${x}`) ?? [])
}
groupedScripts = groupBy(
filteredScripts.filter((x) => x.tab != 'community'),
(sc: Script) => sc.path.split('/').slice(0, 2).join('/'),
defaults
);
communityScripts = [['community', filteredScripts.filter((x) => x.tab == 'community')]];
)
communityScripts = [['community', filteredScripts.filter((x) => x.tab == 'community')]]
}
async function loadTemplateScripts(): Promise<void> {
templateScripts = await ScriptService.listScripts({
workspace: $workspaceStore!,
isTemplate: true
});
templateFuse.setCollection(templateScripts);
})
templateFuse.setCollection(templateScripts)
}
function tabFromPath(path: string) {
let t: Tab = 'shared';
let path_prefix = path.split('/').slice(0, 2);
let t: Tab = 'shared'
let path_prefix = path.split('/').slice(0, 2)
if (path_prefix[0] == 'u' && path_prefix[1] == $usernameStore) {
t = 'personal';
t = 'personal'
} else if (path_prefix[0] == 'g' && $userStore?.groups.includes(path_prefix[1])) {
t = 'groups';
t = 'groups'
}
return t;
return t
}
async function loadScripts(): Promise<void> {
const allScripts = (await ScriptService.listScripts({ workspace: $workspaceStore! })).map(
(x: Script) => {
let t: Tab = x.workspace_id == $workspaceStore ? tabFromPath(x.path) : 'community';
let t: Tab = x.workspace_id == $workspaceStore ? tabFromPath(x.path) : 'community'
return {
canWrite:
canWrite(x.path, x.extra_perms, $userStore) && x.workspace_id == $workspaceStore,
tab: t,
...x
};
}
}
);
scripts = tab == 'all' ? allScripts : allScripts.filter((x) => x.tab == tab);
fuse.setCollection(scripts);
)
scripts = tab == 'all' ? allScripts : allScripts.filter((x) => x.tab == tab)
fuse.setCollection(scripts)
}
async function archiveScript(path: string): Promise<void> {
try {
await ScriptService.archiveScriptByPath({ workspace: $workspaceStore!, path });
loadScripts();
sendUserToast(`Successfully archived script ${path}`);
await ScriptService.archiveScriptByPath({ workspace: $workspaceStore!, path })
loadScripts()
sendUserToast(`Successfully archived script ${path}`)
} catch (err) {
sendUserToast(`Could not archive this script ${err.body}`, true);
sendUserToast(`Could not archive this script ${err.body}`, true)
}
}
$: {
if ($workspaceStore && ($userStore || $superadmin)) {
loadScripts();
loadScripts()
}
}
</script>
@@ -144,7 +144,7 @@
<button
class="default-button-secondary border-none"
on:click={() => {
templateModal.openModal();
templateModal.openModal()
}}
><Icon class="text-blue-500 mb-1" data={faPlus} scale={0.9} /> New script from template</button
>
@@ -279,7 +279,7 @@
displayName: 'Share',
icon: faShare,
action: () => {
shareModal.openModal(path);
shareModal.openModal(path)
},
disabled: !canWrite
},
@@ -287,7 +287,7 @@
displayName: 'Archive',
icon: faArchive,
action: () => {
path ? archiveScript(path) : null;
path ? archiveScript(path) : null
},
type: 'delete',
disabled: !canWrite
@@ -322,14 +322,14 @@
bind:this={shareModal}
kind="script"
on:change={() => {
loadScripts();
loadScripts()
}}
/>
<Modal
bind:this={templateModal}
on:open={() => {
loadTemplateScripts();
loadTemplateScripts()
}}
>
<div slot="title">Pick a template</div>
+19 -19
View File
@@ -1,11 +1,11 @@
<script lang="ts">
import { ScriptService, type Script } from '../../gen';
import { ScriptService, type Script } from '../../gen'
import { page } from '$app/stores';
import { workspaceStore } from '../../stores';
import ScriptBuilder from '../components/ScriptBuilder.svelte';
import type { Schema } from '../../common';
import { sendUserToast } from '../../utils';
import { page } from '$app/stores'
import { workspaceStore } from '../../stores'
import ScriptBuilder from '../components/ScriptBuilder.svelte'
import type { Schema } from '../../common'
import { sendUserToast } from '../../utils'
// Default
let schema: Schema = {
@@ -36,7 +36,7 @@
},
required: [],
type: 'object'
};
}
let code = `import os
import wmill
@@ -79,11 +79,11 @@ def main(name: str = "Nicolas Bourbaki",
# the return value is then parsed and can be retrieved by other scripts conveniently
return {"version": version, "splitted": name.split(), "user": user}
`;
`
$: templatePath = $page.url.searchParams.get('template');
$: templatePath = $page.url.searchParams.get('template')
const initialState = $page.url.searchParams.get('state');
const initialState = $page.url.searchParams.get('state')
let script: Script =
initialState != undefined
@@ -100,7 +100,7 @@ def main(name: str = "Nicolas Bourbaki",
deleted: false,
is_template: false,
extra_perms: {}
};
}
async function loadTemplate(): Promise<void> {
if (templatePath) {
@@ -108,21 +108,21 @@ def main(name: str = "Nicolas Bourbaki",
const template = await ScriptService.getScriptByPath({
workspace: $workspaceStore!,
path: templatePath
});
script.summary = `Copy of ${template.summary}`;
script.description = template.description;
script.content = template.content;
script.schema = template.schema;
sendUserToast('Code & arguments have been loaded from template.');
})
script.summary = `Copy of ${template.summary}`
script.description = template.description
script.content = template.content
script.schema = template.schema
sendUserToast('Code & arguments have been loaded from template.')
} catch (err) {
sendUserToast(`Could not load template: ${err}`, true);
sendUserToast(`Could not load template: ${err}`, true)
}
}
}
$: {
if ($workspaceStore) {
loadTemplate();
loadTemplate()
}
}
</script>
@@ -1,12 +1,12 @@
<script lang="ts">
import { ScriptService, type Script } from '../../../gen';
import { ScriptService, type Script } from '../../../gen'
import { page } from '$app/stores';
import { workspaceStore } from '../../../stores';
import ScriptBuilder from '../../components/ScriptBuilder.svelte';
import { page } from '$app/stores'
import { workspaceStore } from '../../../stores'
import ScriptBuilder from '../../components/ScriptBuilder.svelte'
const initialState = $page.url.searchParams.get('state');
let scriptLoadedFromUrl = initialState != undefined ? JSON.parse(atob(initialState)) : undefined;
const initialState = $page.url.searchParams.get('state')
let scriptLoadedFromUrl = initialState != undefined ? JSON.parse(atob(initialState)) : undefined
let script: Script = {
hash: $page.params.hash,
@@ -19,10 +19,10 @@
deleted: false,
is_template: false,
extra_perms: {}
};
}
let initialPath: string = '';
let scriptBuilder: ScriptBuilder;
let initialPath: string = ''
let scriptBuilder: ScriptBuilder
async function loadScript(): Promise<void> {
script =
@@ -31,14 +31,14 @@
: await ScriptService.getScriptByHash({
workspace: $workspaceStore!,
hash: script.hash
});
initialPath = script.path;
scriptBuilder.setCode(script);
})
initialPath = script.path
scriptBuilder.setCode(script)
}
$: {
if ($workspaceStore && scriptBuilder) {
loadScript();
loadScript()
}
}
</script>
@@ -1,8 +1,8 @@
<script lang="ts">
import { page } from '$app/stores';
import { ScriptService, type Script } from '../../../gen';
import { truncateHash, sendUserToast, displayDaysAgo, canWrite, getUser } from '../../../utils';
import Icon from 'svelte-awesome';
import { page } from '$app/stores'
import { ScriptService, type Script } from '../../../gen'
import { truncateHash, sendUserToast, displayDaysAgo, canWrite, getUser } from '../../../utils'
import Icon from 'svelte-awesome'
import {
faPlay,
faEdit,
@@ -12,52 +12,52 @@
faCalendar,
faShare,
faSpinner
} from '@fortawesome/free-solid-svg-icons';
import Highlight from 'svelte-highlight';
import python from 'svelte-highlight/src/languages/python';
} from '@fortawesome/free-solid-svg-icons'
import Highlight from 'svelte-highlight'
import python from 'svelte-highlight/src/languages/python'
import github from 'svelte-highlight/src/styles/github';
import Tooltip from '../../components/Tooltip.svelte';
import ShareModal from '../../components/ShareModal.svelte';
import { userStore, workspaceStore } from '../../../stores';
import SharedBadge from '../../components/SharedBadge.svelte';
import SvelteMarkdown from 'svelte-markdown';
import SchemaViewer from '../../components/SchemaViewer.svelte';
import Dropdown from '../../components/Dropdown.svelte';
import CenteredPage from '../../components/CenteredPage.svelte';
import { onDestroy } from 'svelte';
import github from 'svelte-highlight/src/styles/github'
import Tooltip from '../../components/Tooltip.svelte'
import ShareModal from '../../components/ShareModal.svelte'
import { userStore, workspaceStore } from '../../../stores'
import SharedBadge from '../../components/SharedBadge.svelte'
import SvelteMarkdown from 'svelte-markdown'
import SchemaViewer from '../../components/SchemaViewer.svelte'
import Dropdown from '../../components/Dropdown.svelte'
import CenteredPage from '../../components/CenteredPage.svelte'
import { onDestroy } from 'svelte'
let script: Script | undefined;
let topHash: string | undefined;
let can_write = false;
let deploymentInProgress = false;
let intervalId: NodeJS.Timer;
let script: Script | undefined
let topHash: string | undefined
let can_write = false
let deploymentInProgress = false
let intervalId: NodeJS.Timer
let shareModal: ShareModal;
let shareModal: ShareModal
$: {
if ($workspaceStore) {
loadScript($page.params.hash);
loadScript($page.params.hash)
}
}
async function deleteScript(hash: string): Promise<void> {
try {
await ScriptService.deleteScriptByHash({ workspace: $workspaceStore!, hash });
loadScript(hash);
await ScriptService.deleteScriptByHash({ workspace: $workspaceStore!, hash })
loadScript(hash)
} catch (err) {
console.error(err);
sendUserToast(`Could not delete this script ${err.body}`, true);
console.error(err)
sendUserToast(`Could not delete this script ${err.body}`, true)
}
}
async function archiveScript(hash: string): Promise<void> {
try {
await ScriptService.archiveScriptByHash({ workspace: $workspaceStore!, hash });
loadScript(hash);
await ScriptService.archiveScriptByHash({ workspace: $workspaceStore!, hash })
loadScript(hash)
} catch (err) {
console.error(err);
sendUserToast(`Could not archive this script ${err.body}`, true);
console.error(err)
sendUserToast(`Could not archive this script ${err.body}`, true)
}
}
@@ -66,45 +66,45 @@
const status = await ScriptService.getScriptDeploymentStatus({
workspace: $workspaceStore!,
hash: script?.hash!
});
})
if (status.lock != undefined || status.lock_error_logs != undefined) {
deploymentInProgress = false;
script.lock = status.lock;
script.lock_error_logs = status.lock_error_logs;
clearInterval(intervalId);
deploymentInProgress = false
script.lock = status.lock
script.lock_error_logs = status.lock_error_logs
clearInterval(intervalId)
}
}
}
async function loadScript(hash: string): Promise<void> {
try {
script = await ScriptService.getScriptByHash({ workspace: $workspaceStore!, hash });
script = await ScriptService.getScriptByHash({ workspace: $workspaceStore!, hash })
} catch {
script = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: hash });
hash = script.hash;
script = await ScriptService.getScriptByPath({ workspace: $workspaceStore!, path: hash })
hash = script.hash
}
const user = await getUser($workspaceStore!);
const user = await getUser($workspaceStore!)
can_write =
script.workspace_id == $workspaceStore && canWrite(script.path, script.extra_perms!, user);
script.workspace_id == $workspaceStore && canWrite(script.path, script.extra_perms!, user)
if (script.path && script.archived) {
const script_by_path = await ScriptService.getScriptByPath({
workspace: $workspaceStore!,
path: script.path
}).catch((e) => console.error('this script has no non-archived version'));
topHash = script_by_path?.hash;
}).catch((e) => console.error('this script has no non-archived version'))
topHash = script_by_path?.hash
} else {
topHash = undefined;
topHash = undefined
}
intervalId && clearInterval(intervalId);
deploymentInProgress = script.lock == undefined && script.lock_error_logs == undefined;
intervalId && clearInterval(intervalId)
deploymentInProgress = script.lock == undefined && script.lock_error_logs == undefined
if (deploymentInProgress) {
intervalId = setInterval(syncer, 500);
intervalId = setInterval(syncer, 500)
}
}
onDestroy(() => {
intervalId && clearInterval(intervalId);
});
intervalId && clearInterval(intervalId)
})
</script>
<svelte:head>
@@ -146,7 +146,7 @@
displayName: 'Share',
icon: faShare,
action: () => {
shareModal.openModal();
shareModal.openModal()
},
disabled: !can_write
},
@@ -160,7 +160,7 @@
icon: faArchive,
type: 'delete',
action: () => {
script?.hash && archiveScript(script.hash);
script?.hash && archiveScript(script.hash)
},
disabled: script.archived || !can_write
},
@@ -169,7 +169,7 @@
icon: faTrash,
type: 'delete',
action: () => {
script?.hash && deleteScript(script.hash);
script?.hash && deleteScript(script.hash)
},
disabled: script.deleted || !($userStore?.is_admin ?? false)
}
@@ -1,54 +1,54 @@
<script lang="ts">
import { page } from '$app/stores';
import { emptySchema, sendUserToast } from '../../../utils';
import { ScriptService, type Script, JobService } from '../../../gen';
import { goto } from '$app/navigation';
import { workspaceStore } from '../../../stores';
import { inferArgs } from '../../../infer';
import CenteredPage from '../../components/CenteredPage.svelte';
import RunForm from '../../components/RunForm.svelte';
import PageHeader from '../../components/PageHeader.svelte';
import { page } from '$app/stores'
import { emptySchema, sendUserToast } from '../../../utils'
import { ScriptService, type Script, JobService } from '../../../gen'
import { goto } from '$app/navigation'
import { workspaceStore } from '../../../stores'
import { inferArgs } from '../../../infer'
import CenteredPage from '../../components/CenteredPage.svelte'
import RunForm from '../../components/RunForm.svelte'
import PageHeader from '../../components/PageHeader.svelte'
const hash = $page.params.hash;
let script: Script | undefined;
const hash = $page.params.hash
let script: Script | undefined
async function loadScript() {
try {
if (hash) {
script = await ScriptService.getScriptByHash({ workspace: $workspaceStore!, hash });
script = await ScriptService.getScriptByHash({ workspace: $workspaceStore!, hash })
if (script.schema == undefined) {
script.schema = emptySchema();
inferArgs(script.content, script.schema);
script = script;
script.schema = emptySchema()
inferArgs(script.content, script.schema)
script = script
}
} else {
sendUserToast(`Failed to fetch script hash from URL`, true);
sendUserToast(`Failed to fetch script hash from URL`, true)
}
} catch (err) {
console.error(err);
sendUserToast(`Could not load script: ${err}`, true);
console.error(err)
sendUserToast(`Could not load script: ${err}`, true)
}
}
async function runScript(scheduledForStr: string | undefined, args: Record<string, any>) {
try {
const scheduledFor = scheduledForStr ? new Date(scheduledForStr).toISOString() : undefined;
const scheduledFor = scheduledForStr ? new Date(scheduledForStr).toISOString() : undefined
let run = await JobService.runScriptByHash({
workspace: $workspaceStore!,
hash,
requestBody: args,
scheduledFor
});
sendUserToast(`Job <a href='/run/${run}'>${run}</a> was created.`);
goto('/run/' + run);
})
sendUserToast(`Job <a href='/run/${run}'>${run}</a> was created.`)
goto('/run/' + run)
} catch (err) {
sendUserToast(`Could not create job: ${err}`, true);
sendUserToast(`Could not create job: ${err}`, true)
}
}
$: {
if ($workspaceStore) {
loadScript();
loadScript()
}
}
</script>
@@ -1,13 +1,13 @@
<script lang="ts">
import { SettingsService } from '../../gen';
import { SettingsService } from '../../gen'
export let subtitle: string | undefined = undefined;
export let title = 'Welcome to Windmill (beta)';
let version = '';
export let subtitle: string | undefined = undefined
export let title = 'Welcome to Windmill (beta)'
let version = ''
SettingsService.backendVersion().then((x) => {
version = x;
});
version = x
})
</script>
<div class="flex justify-center min-h-screen bg-gray-50 pt-10">
@@ -1,9 +1,9 @@
<script lang="ts">
import { SvelteToast } from '@zerodevx/svelte-toast';
import { logout, refreshSuperadmin, sendUserToast } from '../../utils';
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { superadmin, userStore, workspaceStore } from '../../stores';
import { SvelteToast } from '@zerodevx/svelte-toast'
import { logout, refreshSuperadmin, sendUserToast } from '../../utils'
import { onMount } from 'svelte'
import { page } from '$app/stores'
import { superadmin, userStore, workspaceStore } from '../../stores'
// Default options
const toastOptions = {
@@ -15,17 +15,17 @@
reversed: false, // insert new toast to bottom of stack
intro: { x: 256 }, // toast intro fly animation settings
theme: {} // css var overrides
};
}
$: {
if ($workspaceStore) {
localStorage.setItem('workspace', $workspaceStore);
localStorage.setItem('workspace', $workspaceStore)
}
}
onMount(() => {
window.onunhandledrejection = (e) => {
e.preventDefault();
e.preventDefault()
if (e.reason && e.reason.message) {
if (
['Model not found', 'Connection is disposed.', 'Connection got disposed.'].includes(
@@ -33,25 +33,25 @@
)
) {
// monaco editor promise cancelation
console.log('caught expected error');
console.log('caught expected error')
}
if (e.reason.status == '401') {
sendUserToast('Logged out after a request was unauthorized', true);
logout($page.url.pathname);
sendUserToast('Logged out after a request was unauthorized', true)
logout($page.url.pathname)
} else {
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`;
sendUserToast(message, true);
let message = `${e.reason?.message}: ${e.reason?.body ?? ''}`
sendUserToast(message, true)
}
} else {
console.log('unexpected error ignored', e);
console.log('unexpected error ignored', e)
}
e.preventDefault();
return false;
};
workspaceStore.set(undefined);
userStore.set(undefined);
refreshSuperadmin();
});
e.preventDefault()
return false
}
workspaceStore.set(undefined)
userStore.set(undefined)
refreshSuperadmin()
})
</script>
<div class="min-h-screen antialiased text-gray-900">
@@ -1,17 +1,17 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { goto } from '$app/navigation'
import { UserService, WorkspaceService } from '../../gen';
import { sendUserToast } from '../../utils';
import { page } from '$app/stores';
import { usersWorkspaceStore, workspaceStore } from '../../stores';
import CenteredModal from './CenteredModal.svelte';
import { UserService, WorkspaceService } from '../../gen'
import { sendUserToast } from '../../utils'
import { page } from '$app/stores'
import { usersWorkspaceStore, workspaceStore } from '../../stores'
import CenteredModal from './CenteredModal.svelte'
let workspace_id = $page.url.searchParams.get('workspace') ?? '';
let username = '';
let errorUsername = '';
let workspace_id = $page.url.searchParams.get('workspace') ?? ''
let username = ''
let errorUsername = ''
$: validateName(username);
$: validateName(username)
async function acceptInvite(): Promise<void> {
await UserService.acceptInvite({
@@ -19,38 +19,38 @@
username,
workspace_id
}
});
sendUserToast(`Invitation to ${workspace_id} accepted as ${username}`);
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
workspaceStore.set(workspace_id);
goto('/');
})
sendUserToast(`Invitation to ${workspace_id} accepted as ${username}`)
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
workspaceStore.set(workspace_id)
goto('/')
}
async function validateName(username: string): Promise<void> {
try {
await WorkspaceService.validateUsername({ requestBody: { id: workspace_id, username } });
errorUsername = '';
await WorkspaceService.validateUsername({ requestBody: { id: workspace_id, username } })
errorUsername = ''
} catch {
errorUsername = 'username already exists';
errorUsername = 'username already exists'
}
}
function handleKey(event: KeyboardEvent) {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 13 || key === 'Enter') {
event.preventDefault();
acceptInvite();
event.preventDefault()
acceptInvite()
}
}
UserService.globalWhoami().then((x) => {
if (x.name) {
username = x.name.split(' ')[0];
username = x.name.split(' ')[0]
} else {
username = x.email.split('@')[0];
username = x.email.split('@')[0]
}
username = username.toLowerCase();
});
username = username.toLowerCase()
})
</script>
<!-- Enable submit form on enter -->
@@ -1,29 +1,29 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { goto } from '$app/navigation'
import { UserService, WorkspaceService } from '../../gen';
import { logoutWithRedirect, sendUserToast } from '../../utils';
import { page } from '$app/stores';
import { usersWorkspaceStore, workspaceStore } from '../../stores';
import CenteredModal from './CenteredModal.svelte';
import { UserService, WorkspaceService } from '../../gen'
import { logoutWithRedirect, sendUserToast } from '../../utils'
import { page } from '$app/stores'
import { usersWorkspaceStore, workspaceStore } from '../../stores'
import CenteredModal from './CenteredModal.svelte'
let id = '';
let name = '';
let username = '';
let domain = '';
let id = ''
let name = ''
let username = ''
let domain = ''
let errorId = '';
let errorId = ''
$: id = name.toLowerCase().replace(/\s/gi, '-');
$: id = name.toLowerCase().replace(/\s/gi, '-')
$: validateName(id);
$: validateName(id)
async function validateName(id: string): Promise<void> {
try {
await WorkspaceService.validateId({ requestBody: { id } });
errorId = '';
await WorkspaceService.validateId({ requestBody: { id } })
errorId = ''
} catch {
errorId = 'id already exists';
errorId = 'id already exists'
}
}
async function createWorkspace(): Promise<void> {
@@ -35,46 +35,46 @@
username,
domain
}
});
sendUserToast(`Successfully created workspace id: ${id}`);
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
workspaceStore.set(id);
goto('/');
})
sendUserToast(`Successfully created workspace id: ${id}`)
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
workspaceStore.set(id)
goto('/')
} catch (err) {
console.error(err);
sendUserToast(`Cannot create workspace: ${err.body}`, true);
console.error(err)
sendUserToast(`Cannot create workspace: ${err.body}`, true)
}
}
function handleKeyUp(event: KeyboardEvent) {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 13 || key === 'Enter') {
event.preventDefault();
createWorkspace();
event.preventDefault()
createWorkspace()
}
}
async function loadWorkspaces() {
if (!$usersWorkspaceStore) {
try {
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
} catch {}
}
if (!$usersWorkspaceStore) {
logoutWithRedirect($page.url.pathname);
logoutWithRedirect($page.url.pathname)
}
}
loadWorkspaces();
loadWorkspaces()
UserService.globalWhoami().then((x) => {
if (x.name) {
username = x.name.split(' ')[0];
username = x.name.split(' ')[0]
} else {
username = x.email.split('@')[0];
username = x.email.split('@')[0]
}
username = username.toLowerCase();
});
username = username.toLowerCase()
})
</script>
<CenteredModal title="Create a new workspace">
+31 -31
View File
@@ -1,22 +1,22 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { goto } from '$app/navigation'
import { UserService } from '../../gen';
import { refreshSuperadmin, sendUserToast } from '../../utils';
import { page } from '$app/stores';
import { userStore, usersWorkspaceStore, workspaceStore } from '../../stores';
import CenteredModal from './CenteredModal.svelte';
import Icon from 'svelte-awesome';
import { faGithub } from '@fortawesome/free-brands-svg-icons';
import { slide } from 'svelte/transition';
import { onMount } from 'svelte';
import { UserService } from '../../gen'
import { refreshSuperadmin, sendUserToast } from '../../utils'
import { page } from '$app/stores'
import { userStore, usersWorkspaceStore, workspaceStore } from '../../stores'
import CenteredModal from './CenteredModal.svelte'
import Icon from 'svelte-awesome'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { slide } from 'svelte/transition'
import { onMount } from 'svelte'
let email = $page.url.searchParams.get('email') ?? '';
let password = $page.url.searchParams.get('password') ?? '';
const error = $page.url.searchParams.get('error') ?? undefined;
const rd = $page.url.searchParams.get('rd');
let email = $page.url.searchParams.get('email') ?? ''
let password = $page.url.searchParams.get('password') ?? ''
const error = $page.url.searchParams.get('error') ?? undefined
const rd = $page.url.searchParams.get('rd')
let showPassword = false;
let showPassword = false
async function login(): Promise<void> {
try {
@@ -25,40 +25,40 @@
email: email,
password
}
});
refreshSuperadmin();
})
refreshSuperadmin()
if (rd) {
goto(decodeURI(rd));
goto(decodeURI(rd))
} else {
goto('/user/workspaces');
goto('/user/workspaces')
}
} catch (err) {
sendUserToast(`Cannot login: ${err.body}`, true);
sendUserToast(`Cannot login: ${err.body}`, true)
}
}
function handleKeyUp(event: KeyboardEvent) {
const key = event.key || event.keyCode;
const key = event.key || event.keyCode
if (key === 13 || key === 'Enter') {
event.preventDefault();
login();
event.preventDefault()
login()
}
}
async function redirectIfLoggedIn() {
try {
await UserService.getCurrentEmail();
goto('/');
await UserService.getCurrentEmail()
goto('/')
} catch {
usersWorkspaceStore.set(undefined);
workspaceStore.set(undefined);
userStore.set(undefined);
usersWorkspaceStore.set(undefined)
workspaceStore.set(undefined)
userStore.set(undefined)
}
}
onMount(() => {
setTimeout(redirectIfLoggedIn, 1000);
});
setTimeout(redirectIfLoggedIn, 1000)
})
</script>
<!-- Enable submit form on enter -->
@@ -77,7 +77,7 @@
class="my-6 text-xs text-blue-400 m-auto"
id="showPassword"
on:click={() => {
showPassword = !showPassword;
showPassword = !showPassword
}}>login without third-party</button
>
</div>
+45 -45
View File
@@ -1,24 +1,24 @@
<script lang="ts">
import { usersWorkspaceStore } from '../../stores';
import { usersWorkspaceStore } from '../../stores'
import type { TruncatedToken, NewToken } from '../../gen';
import { UserService, SettingsService } from '../../gen';
import { displayDate, sendUserToast, getToday } from '../../utils';
import PageHeader from './../components/PageHeader.svelte';
import Icon from 'svelte-awesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import TableCustom from '../components/TableCustom.svelte';
import CenteredModal from './CenteredModal.svelte';
import type { TruncatedToken, NewToken } from '../../gen'
import { UserService, SettingsService } from '../../gen'
import { displayDate, sendUserToast, getToday } from '../../utils'
import PageHeader from './../components/PageHeader.svelte'
import Icon from 'svelte-awesome'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import TableCustom from '../components/TableCustom.svelte'
import CenteredModal from './CenteredModal.svelte'
let newPassword: string | undefined;
let passwordError: string | undefined;
let version: string | undefined;
let tokens: TruncatedToken[];
let newToken: string | undefined;
let newTokenLabel: string | undefined;
let newTokenExpiration: string | undefined;
let displayCreateToken = false;
let login_type = 'none';
let newPassword: string | undefined
let passwordError: string | undefined
let version: string | undefined
let tokens: TruncatedToken[]
let newToken: string | undefined
let newTokenLabel: string | undefined
let newTokenExpiration: string | undefined
let displayCreateToken = false
let login_type = 'none'
async function setPassword(): Promise<void> {
try {
@@ -27,61 +27,61 @@
requestBody: {
password: newPassword
}
});
sendUserToast('Your password was successfully updated');
})
sendUserToast('Your password was successfully updated')
} else {
sendUserToast('Specify a new password value to change your passord', true);
sendUserToast('Specify a new password value to change your passord', true)
}
} catch (error) {
sendUserToast(`Could not set this user's password: ${error}`, true);
sendUserToast(`Could not set this user's password: ${error}`, true)
}
}
async function loadVersion(): Promise<void> {
version = await SettingsService.backendVersion();
version = await SettingsService.backendVersion()
}
async function loadLoginType(): Promise<void> {
login_type = (await UserService.globalWhoami()).login_type;
login_type = (await UserService.globalWhoami()).login_type
}
async function createToken(): Promise<void> {
newToken = undefined;
let expirationISO: Date | undefined;
newToken = undefined
let expirationISO: Date | undefined
if (newTokenExpiration) {
expirationISO = new Date(newTokenExpiration);
expirationISO = new Date(newTokenExpiration)
}
try {
newToken = await UserService.createToken({
requestBody: { label: newTokenLabel, expiration: expirationISO?.toISOString() } as NewToken
});
listTokens();
displayCreateToken = false;
})
listTokens()
displayCreateToken = false
} catch (err) {
sendUserToast(`Could not create token: ${err}`, true);
sendUserToast(`Could not create token: ${err}`, true)
}
}
async function listTokens(): Promise<void> {
try {
tokens = await UserService.listTokens();
tokens = await UserService.listTokens()
} catch (err) {
sendUserToast(`Could not fetch tokens: ${err}`, true);
sendUserToast(`Could not fetch tokens: ${err}`, true)
}
}
async function deleteToken(tokenPrefix: string) {
try {
await UserService.deleteToken({ tokenPrefix });
sendUserToast('Succesfully deleted token');
listTokens();
await UserService.deleteToken({ tokenPrefix })
sendUserToast('Succesfully deleted token')
listTokens()
} catch (err) {
sendUserToast(`There was an error deleting this token: ${err}`, true);
sendUserToast(`There was an error deleting this token: ${err}`, true)
}
}
loadVersion();
loadLoginType();
listTokens();
loadVersion()
loadLoginType()
listTokens()
</script>
<CenteredModal title="User settings">
@@ -135,10 +135,10 @@
<div class="text-right py-0 my-0 border-b">
<button
on:click={() => {
displayCreateToken = !displayCreateToken;
newToken = undefined;
newTokenExpiration = undefined;
newTokenLabel = undefined;
displayCreateToken = !displayCreateToken
newToken = undefined
newTokenExpiration = undefined
newTokenLabel = undefined
}}
class="default-button py-0 {displayCreateToken ? 'hidden' : ''}"
><Icon class="text-white mb-1" data={faPlus} scale={0.9} /> &nbsp; Create token</button
@@ -218,7 +218,7 @@
><button
class="text-red-500 text-xs underline"
on:click={() => {
deleteToken(token_prefix);
deleteToken(token_prefix)
}}>Delete</button
></td
>
@@ -1,41 +1,41 @@
<script lang="ts">
import Fuse from 'fuse.js';
import Fuse from 'fuse.js'
import { superadmin, usersWorkspaceStore } from '../../stores';
import { superadmin, usersWorkspaceStore } from '../../stores'
import { UserService, SettingsService, GlobalUserInfo } from '../../gen';
import { displayDate, sendUserToast, getToday } from '../../utils';
import Icon from 'svelte-awesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import TableCustom from '../components/TableCustom.svelte';
import CenteredModal from './CenteredModal.svelte';
import PageHeader from '../components/PageHeader.svelte';
import InviteGlobalUser from '../components/InviteGlobalUser.svelte';
import { UserService, SettingsService, GlobalUserInfo } from '../../gen'
import { displayDate, sendUserToast, getToday } from '../../utils'
import Icon from 'svelte-awesome'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import TableCustom from '../components/TableCustom.svelte'
import CenteredModal from './CenteredModal.svelte'
import PageHeader from '../components/PageHeader.svelte'
import InviteGlobalUser from '../components/InviteGlobalUser.svelte'
let version: string | undefined;
let users: GlobalUserInfo[] = [];
let filteredUsers: GlobalUserInfo[] | undefined;
let userFilter = '';
let version: string | undefined
let users: GlobalUserInfo[] = []
let filteredUsers: GlobalUserInfo[] | undefined
let userFilter = ''
const fuseOptions = {
includeScore: false,
keys: ['email', 'name', 'company']
};
}
const fuse: Fuse<GlobalUserInfo> = new Fuse(users, fuseOptions);
$: filteredUsers = fuse?.search(userFilter).map((value) => value.item);
const fuse: Fuse<GlobalUserInfo> = new Fuse(users, fuseOptions)
$: filteredUsers = fuse?.search(userFilter).map((value) => value.item)
async function loadVersion(): Promise<void> {
version = await SettingsService.backendVersion();
version = await SettingsService.backendVersion()
}
async function listUsers(): Promise<void> {
users = await UserService.listUsersAsSuperAdmin({});
fuse?.setCollection(users);
users = await UserService.listUsersAsSuperAdmin({})
fuse?.setCollection(users)
}
loadVersion();
listUsers();
loadVersion()
listUsers()
</script>
<CenteredModal title="Global Users Settings">
@@ -79,8 +79,8 @@
requestBody: {
is_super_admin: !super_admin
}
});
listUsers();
})
listUsers()
}}>{super_admin ? 'demote' : 'promote'}</button
></td
>
+28 -28
View File
@@ -1,53 +1,53 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { logout, logoutWithRedirect, sendUserToast } from '../../utils';
import { UserService, type WorkspaceInvite, WorkspaceService } from '../../gen';
import { superadmin, usersWorkspaceStore, workspaceStore } from '../../stores';
import CenteredModal from './CenteredModal.svelte';
import Switch from '../components/Switch.svelte';
import { faCrown, faUser, faUserAlt, faUserCog } from '@fortawesome/free-solid-svg-icons';
import Icon from 'svelte-awesome';
import { goto } from '$app/navigation'
import { page } from '$app/stores'
import { logout, logoutWithRedirect, sendUserToast } from '../../utils'
import { UserService, type WorkspaceInvite, WorkspaceService } from '../../gen'
import { superadmin, usersWorkspaceStore, workspaceStore } from '../../stores'
import CenteredModal from './CenteredModal.svelte'
import Switch from '../components/Switch.svelte'
import { faCrown, faUser, faUserAlt, faUserCog } from '@fortawesome/free-solid-svg-icons'
import Icon from 'svelte-awesome'
let invites: WorkspaceInvite[] = [];
let list_all_as_super_admin: boolean = false;
let workspaces: { id: string; name: string; username: string }[] = [];
let invites: WorkspaceInvite[] = []
let list_all_as_super_admin: boolean = false
let workspaces: { id: string; name: string; username: string }[] = []
async function loadInvites() {
invites = await UserService.listWorkspaceInvites();
invites = await UserService.listWorkspaceInvites()
}
async function loadWorkspaces() {
if (!$usersWorkspaceStore) {
try {
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces());
usersWorkspaceStore.set(await WorkspaceService.listUserWorkspaces())
} catch {}
}
if ($usersWorkspaceStore) {
if (!$workspaceStore) {
workspaceStore.set(localStorage.getItem('workspace')?.toString());
workspaceStore.set(localStorage.getItem('workspace')?.toString())
}
} else {
logoutWithRedirect($page.url.pathname);
logoutWithRedirect($page.url.pathname)
}
}
async function loadWorkspacesAsAdmin() {
workspaces = (await WorkspaceService.listWorkspacesAsSuperAdmin({ perPage: 1000 })).map((x) => {
return { ...x, username: 'superadmin' };
});
return { ...x, username: 'superadmin' }
})
}
$: {
if (list_all_as_super_admin) {
loadWorkspacesAsAdmin();
loadWorkspacesAsAdmin()
} else {
workspaces = $usersWorkspaceStore?.workspaces ?? [];
workspaces = $usersWorkspaceStore?.workspaces ?? []
}
}
loadInvites();
loadWorkspaces();
loadInvites()
loadWorkspaces()
</script>
<CenteredModal title="Select a workspace" subtitle="Logged in as {$usersWorkspaceStore?.email}">
@@ -84,8 +84,8 @@
hover:ring-indigo-300
"
on:click={() => {
workspaceStore.set(workspace.id);
goto('/');
workspaceStore.set(workspace.id)
goto('/')
}}
><span class="font-mono">{workspace.id}</span> - {workspace.name} as
<span class="font-mono">{workspace.username}</span>
@@ -136,9 +136,9 @@
on:click={async () => {
await UserService.declineInvite({
requestBody: { workspace_id: invite.workspace_id }
});
sendUserToast(`Declined invite to ${invite.workspace_id}`);
loadInvites();
})
sendUserToast(`Declined invite to ${invite.workspace_id}`)
loadInvites()
}}
>
decline
@@ -158,7 +158,7 @@
<button
class="default-button-secondary"
on:click={async () => {
logout();
logout()
}}
>
logout
+31 -31
View File
@@ -1,53 +1,53 @@
<script lang="ts">
import { canWrite, getUser, sendUserToast } from '../utils';
import { VariableService } from '../gen';
import type { ListableVariable, ContextualVariable } from '../gen';
import Dropdown from './components/Dropdown.svelte';
import PageHeader from './components/PageHeader.svelte';
import TableSimple from './components/TableSimple.svelte';
import TableCustom from './components/TableCustom.svelte';
import ShareModal from './components/ShareModal.svelte';
import SharedBadge from './components/SharedBadge.svelte';
import VariableEditor from './components/VariableEditor.svelte';
import { workspaceStore } from './../stores';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { canWrite, getUser, sendUserToast } from '../utils'
import { VariableService } from '../gen'
import type { ListableVariable, ContextualVariable } from '../gen'
import Dropdown from './components/Dropdown.svelte'
import PageHeader from './components/PageHeader.svelte'
import TableSimple from './components/TableSimple.svelte'
import TableCustom from './components/TableCustom.svelte'
import ShareModal from './components/ShareModal.svelte'
import SharedBadge from './components/SharedBadge.svelte'
import VariableEditor from './components/VariableEditor.svelte'
import { workspaceStore } from './../stores'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
type ListableVariableW = ListableVariable & { canWrite: boolean };
type ListableVariableW = ListableVariable & { canWrite: boolean }
let variables: ListableVariableW[] = [];
let contextualVariables: ContextualVariable[] = [];
let variables: ListableVariableW[] = []
let contextualVariables: ContextualVariable[] = []
let shareModal: ShareModal;
let variableEditor: VariableEditor;
let shareModal: ShareModal
let variableEditor: VariableEditor
// If relative, the dropdown is positioned relative to its button
async function loadVariables(): Promise<void> {
const user = await getUser($workspaceStore!);
const user = await getUser($workspaceStore!)
variables = (await VariableService.listVariable({ workspace: $workspaceStore! })).map((x) => {
return {
canWrite: canWrite(x.path, x.extra_perms!, user) && x.workspace_id == $workspaceStore,
...x
};
});
}
})
}
async function loadContextualVariables(): Promise<void> {
contextualVariables = await VariableService.listContextualVariables({
workspace: $workspaceStore!
});
})
}
async function deleteVariable(path: string): Promise<void> {
await VariableService.deleteVariable({ workspace: $workspaceStore!, path });
loadVariables();
sendUserToast(`Variable ${path} was deleted`);
await VariableService.deleteVariable({ workspace: $workspaceStore!, path })
loadVariables()
sendUserToast(`Variable ${path} was deleted`)
}
$: {
if ($workspaceStore) {
loadVariables();
loadContextualVariables();
loadVariables()
loadContextualVariables()
}
}
</script>
@@ -57,7 +57,7 @@
<button
class="default-button"
on:click={() => {
variableEditor.initNew();
variableEditor.initNew()
}}
>
<Icon class="text-white mb-1" data={faPlus} scale={0.9} /> &nbsp; New variable</button
@@ -104,7 +104,7 @@
{
displayName: 'Share',
action: () => {
shareModal.openModal(path);
shareModal.openModal(path)
},
disabled: !canWrite
}
@@ -122,7 +122,7 @@
bind:this={shareModal}
kind="variable"
on:change={() => {
loadVariables();
loadVariables()
}}
/>
<div class="my-10" />
+19 -19
View File
@@ -1,37 +1,37 @@
<script lang="ts">
import { WorkerService, type WorkerPing } from '../gen';
import { onDestroy, onMount } from 'svelte';
import { displayDate, elapsedSinceSecs, groupBy, sendUserToast } from '../utils';
import PageHeader from './components/PageHeader.svelte';
import TableCustom from './components/TableCustom.svelte';
import CenteredPage from './components/CenteredPage.svelte';
import { WorkerService, type WorkerPing } from '../gen'
import { onDestroy, onMount } from 'svelte'
import { displayDate, elapsedSinceSecs, groupBy, sendUserToast } from '../utils'
import PageHeader from './components/PageHeader.svelte'
import TableCustom from './components/TableCustom.svelte'
import CenteredPage from './components/CenteredPage.svelte'
let workers: WorkerPing[] = [];
let filteredWorkers: WorkerPing[] = [];
let groupedWorkers: [string, WorkerPing[]][] = [];
let intervalId: NodeJS.Timer | undefined;
let workers: WorkerPing[] = []
let filteredWorkers: WorkerPing[] = []
let groupedWorkers: [string, WorkerPing[]][] = []
let intervalId: NodeJS.Timer | undefined
$: filteredWorkers = workers.filter((x) => elapsedSinceSecs(x.ping_at) < 300);
$: groupedWorkers = groupBy(filteredWorkers, (wp: WorkerPing) => wp.worker_instance);
$: filteredWorkers = workers.filter((x) => elapsedSinceSecs(x.ping_at) < 300)
$: groupedWorkers = groupBy(filteredWorkers, (wp: WorkerPing) => wp.worker_instance)
async function loadWorkers(): Promise<void> {
try {
workers = await WorkerService.listWorkers({ perPage: 100 });
workers = await WorkerService.listWorkers({ perPage: 100 })
} catch (err) {
sendUserToast(`Could not load workers: ${err}`, true);
sendUserToast(`Could not load workers: ${err}`, true)
}
}
onMount(() => {
loadWorkers();
intervalId = setInterval(loadWorkers, 5000);
});
loadWorkers()
intervalId = setInterval(loadWorkers, 5000)
})
onDestroy(() => {
if (intervalId) {
clearInterval(intervalId);
clearInterval(intervalId)
}
});
})
</script>
<CenteredPage>
+48 -48
View File
@@ -1,33 +1,33 @@
<script lang="ts">
import Fuse from 'fuse.js';
import { UserService, type WorkspaceInvite, WorkspaceService } from '../gen';
import type { User } from '../gen';
import { sendUserToast } from '../utils';
import PageHeader from './components/PageHeader.svelte';
import { userStore, usersWorkspaceStore, workspaceStore } from '../stores';
import CenteredPage from './components/CenteredPage.svelte';
import Icon from 'svelte-awesome';
import { faSlack } from '@fortawesome/free-brands-svg-icons';
import TableCustom from './components/TableCustom.svelte';
import { goto } from '$app/navigation';
import InviteUser from './components/InviteUser.svelte';
import ScriptPicker from './components/ScriptPicker.svelte';
import Fuse from 'fuse.js'
import { UserService, type WorkspaceInvite, WorkspaceService } from '../gen'
import type { User } from '../gen'
import { sendUserToast } from '../utils'
import PageHeader from './components/PageHeader.svelte'
import { userStore, usersWorkspaceStore, workspaceStore } from '../stores'
import CenteredPage from './components/CenteredPage.svelte'
import Icon from 'svelte-awesome'
import { faSlack } from '@fortawesome/free-brands-svg-icons'
import TableCustom from './components/TableCustom.svelte'
import { goto } from '$app/navigation'
import InviteUser from './components/InviteUser.svelte'
import ScriptPicker from './components/ScriptPicker.svelte'
let users: User[] = [];
let invites: WorkspaceInvite[] = [];
let filteredUsers: User[] | undefined;
let userFilter = '';
let scriptPath: string;
let team_name: string | undefined;
let slackLoaded = false;
let users: User[] = []
let invites: WorkspaceInvite[] = []
let filteredUsers: User[] | undefined
let userFilter = ''
let scriptPath: string
let team_name: string | undefined
let slackLoaded = false
const fuseOptions = {
includeScore: false,
keys: ['username', 'email']
};
}
const fuse: Fuse<User> = new Fuse(users, fuseOptions);
$: filteredUsers = fuse?.search(userFilter).map((value) => value.item);
const fuse: Fuse<User> = new Fuse(users, fuseOptions)
$: filteredUsers = fuse?.search(userFilter).map((value) => value.item)
// function getDropDownItems(username: string): DropdownItem[] {
// return [
@@ -58,30 +58,30 @@
await WorkspaceService.editSlackCommand({
workspace: $workspaceStore!,
requestBody: { slack_command_script: scriptPath }
});
sendUserToast(`slack command script set to ${scriptPath}`);
})
sendUserToast(`slack command script set to ${scriptPath}`)
}
async function loadSlack(): Promise<void> {
const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! });
team_name = settings.slack_name;
scriptPath = settings.slack_command_script ?? '';
const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
team_name = settings.slack_name
scriptPath = settings.slack_command_script ?? ''
}
async function listUsers(): Promise<void> {
users = await UserService.listUsers({ workspace: $workspaceStore! });
fuse?.setCollection(users);
users = await UserService.listUsers({ workspace: $workspaceStore! })
fuse?.setCollection(users)
}
async function listInvites(): Promise<void> {
invites = await WorkspaceService.listPendingInvites({ workspace: $workspaceStore! });
invites = await WorkspaceService.listPendingInvites({ workspace: $workspaceStore! })
}
$: {
if ($workspaceStore) {
listUsers();
listInvites();
loadSlack();
listUsers()
listInvites()
loadSlack()
}
}
</script>
@@ -116,8 +116,8 @@
await UserService.deleteUser({
workspace: $workspaceStore ?? '',
username
});
listUsers();
})
listUsers()
}}>remove</button
>
-
@@ -130,8 +130,8 @@
requestBody: {
is_admin: !is_admin
}
});
listUsers();
})
listUsers()
}}>{is_admin ? 'demote' : 'promote'}</button
></td
>
@@ -165,8 +165,8 @@
email,
is_admin
}
});
listInvites();
})
listInvites()
}}>remove</button
></td
>
@@ -192,9 +192,9 @@
await WorkspaceService.disconnectClient({
workspace: $workspaceStore ?? '',
clientName: 'slack'
});
loadSlack();
sendUserToast('Disconnected slack');
})
loadSlack()
sendUserToast('Disconnected slack')
}}
>Disconnect slack <Icon class="text-white mb-1" data={faSlack} scale={0.9} />
</button>
@@ -226,11 +226,11 @@
</p>
<button
on:click={async () => {
await WorkspaceService.deleteWorkspace({ workspace: $workspaceStore ?? '' });
sendUserToast(`Successfully deleted workspace ${$workspaceStore}`);
workspaceStore.set(undefined);
usersWorkspaceStore.set(undefined);
goto('/user/workspaces');
await WorkspaceService.deleteWorkspace({ workspace: $workspaceStore ?? '' })
sendUserToast(`Successfully deleted workspace ${$workspaceStore}`)
workspaceStore.set(undefined)
usersWorkspaceStore.set(undefined)
goto('/user/workspaces')
}}
class="default-button mt-2 bg-red-500">Delete workspace</button
>
+12 -9
View File
@@ -3,18 +3,21 @@ import type { Readable } from 'svelte/store'
import type { UserWorkspaceList } from './gen'
export interface UserExt {
email: string
username: string
is_admin: boolean
created_at: string
groups: string[]
pgroups: string[]
email: string
username: string
is_admin: boolean
created_at: string
groups: string[]
pgroups: string[]
}
export const userStore = writable<UserExt | undefined>(undefined)
export const workspaceStore = writable<string | undefined>(undefined)
export const usersWorkspaceStore = writable<UserWorkspaceList | undefined>(undefined)
export const usernameStore: Readable<string | undefined> = derived([usersWorkspaceStore, workspaceStore], ($values, set) => {
set($values[0]?.workspaces.find(x => x.id == $values[1])?.username)
})
export const usernameStore: Readable<string | undefined> = derived(
[usersWorkspaceStore, workspaceStore],
($values, set) => {
set($values[0]?.workspaces.find((x) => x.id == $values[1])?.username)
}
)
export const superadmin = writable<String | false | undefined>(undefined)
+1 -1
View File
@@ -1 +1 @@
declare module "simple-svelte-autocomplete"
declare module 'simple-svelte-autocomplete'
+10 -9
View File
@@ -43,8 +43,9 @@ export function displayDate(dateString: string | undefined): string {
if (date.toString() === 'Invalid Date') {
return ''
} else {
return `${date.getFullYear()}/${date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString()}`
return `${date.getFullYear()}/${
date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString()}`
}
}
@@ -155,7 +156,11 @@ export async function logout(logoutMessage?: string): Promise<void> {
await UserService.logout()
sendUserToast('you have been logged out')
} catch (error) {
goto(`/user/login?error=${encodeURIComponent('There was a problem logging you out, check the logs')}`)
goto(
`/user/login?error=${encodeURIComponent(
'There was a problem logging you out, check the logs'
)}`
)
console.error(error)
}
}
@@ -274,19 +279,16 @@ export function elapsedSinceSecs(date: string): number {
return Math.round((new Date().getTime() - new Date(date).getTime()) / 1000)
}
export function groupBy<T>(
scripts: T[],
toGroup: (t: T) => string,
dflts: string[] = [],
dflts: string[] = []
): [string, T[]][] {
let r: Record<string, T[]> = {}
for (const dflt of dflts) {
r[dflt] = []
}
scripts.forEach((sc) => {
let section = toGroup(sc)
if (section in r) {
@@ -302,8 +304,7 @@ export function groupBy<T>(
if (n1 > n2) {
return 1
}
else if (n1 < n2) {
} else if (n1 < n2) {
return -1
} else {
return 0
+6 -6
View File
@@ -1,6 +1,6 @@
import preprocess from 'svelte-preprocess';
import adapter from '@sveltejs/adapter-static';
const ppath = process.env.PREVIEW_PATH;
import preprocess from 'svelte-preprocess'
import adapter from '@sveltejs/adapter-static'
const ppath = process.env.PREVIEW_PATH
/** @type {import('@sveltejs/kit').Config} */
const config = {
@@ -44,8 +44,8 @@ const config = {
}),
prerender: {
enabled: true
},
}
}
};
}
export default config;
export default config

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