mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 00:02:23 +00:00
feat: add local cache for folder path used + invalidate cache on folder creation
This commit is contained in:
@@ -6,9 +6,11 @@
|
||||
* LICENSE-AGPL for a copy of the license.
|
||||
*/
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
users::Authed,
|
||||
users::{AuthCache, Authed, Tokened},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
};
|
||||
use axum::{
|
||||
@@ -144,8 +146,10 @@ lazy_static! {
|
||||
|
||||
async fn create_folder(
|
||||
authed: Authed,
|
||||
Tokened { token }: Tokened,
|
||||
Extension(user_db): Extension<UserDB>,
|
||||
Extension(webhook): Extension<WebhookShared>,
|
||||
Extension(cache): Extension<Arc<AuthCache>>,
|
||||
Path(w_id): Path<String>,
|
||||
Json(ng): Json<NewFolder>,
|
||||
) -> Result<String> {
|
||||
@@ -157,7 +161,7 @@ async fn create_folder(
|
||||
)));
|
||||
}
|
||||
|
||||
check_name_conflict(&mut tx, &w_id, &ng.name).await?;
|
||||
cache.invalidate(&w_id, token).await;
|
||||
let owner = username_to_permissioned_as(&authed.username);
|
||||
let owners = &ng.owners.unwrap_or(vec![owner.clone()]);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use windmill_audit::{audit_log, ActionKind};
|
||||
use crate::{
|
||||
db::{UserDB, DB},
|
||||
schedule::clear_schedule,
|
||||
users::{require_owner_of_path, Authed},
|
||||
users::{maybe_refresh_folders, require_owner_of_path, Authed},
|
||||
webhook_util::{WebhookMessage, WebhookShared},
|
||||
HTTP_CLIENT,
|
||||
};
|
||||
@@ -190,6 +190,7 @@ async fn create_script(
|
||||
Json(ns): Json<NewScript>,
|
||||
) -> Result<(StatusCode, String)> {
|
||||
let hash = ScriptHash(hash_script(&ns));
|
||||
// let authed = maybe_refresh_folders(&ns.path, &w_id, authed, &db).await;
|
||||
let mut tx = user_db.begin(&authed).await?;
|
||||
|
||||
if sqlx::query_scalar!(
|
||||
|
||||
@@ -53,73 +53,78 @@
|
||||
|
||||
async function saveFlow(leave: boolean): Promise<void> {
|
||||
loadingSave = true
|
||||
const flow = cleanInputs($flowStore)
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
$dirtyStore = false
|
||||
if (initialPath === '') {
|
||||
localStorage.removeItem('flow')
|
||||
await FlowService.createFlow({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
try {
|
||||
const flow = cleanInputs($flowStore)
|
||||
const { cron, args, enabled } = $scheduleStore
|
||||
$dirtyStore = false
|
||||
if (initialPath === '') {
|
||||
localStorage.removeItem('flow')
|
||||
await FlowService.createFlow({
|
||||
workspace: $workspaceStore!,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
})
|
||||
if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
})
|
||||
if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
} else {
|
||||
localStorage.removeItem(`flow-${initialPath}`)
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
})
|
||||
const scheduleExists = await ScheduleService.existsSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path
|
||||
})
|
||||
if (scheduleExists) {
|
||||
const schedule = await ScheduleService.getSchedule({
|
||||
} else {
|
||||
localStorage.removeItem(`flow-${initialPath}`)
|
||||
await FlowService.updateFlow({
|
||||
workspace: $workspaceStore!,
|
||||
path: initialPath,
|
||||
requestBody: {
|
||||
path: flow.path,
|
||||
summary: flow.summary,
|
||||
description: flow.description ?? '',
|
||||
value: flow.value,
|
||||
schema: flow.schema
|
||||
}
|
||||
})
|
||||
const scheduleExists = await ScheduleService.existsSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path
|
||||
})
|
||||
if (JSON.stringify(schedule.args) != JSON.stringify(args) || schedule.schedule != cron) {
|
||||
await ScheduleService.updateSchedule({
|
||||
if (scheduleExists) {
|
||||
const schedule = await ScheduleService.getSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: {
|
||||
schedule: formatCron(cron),
|
||||
args
|
||||
}
|
||||
path: flow.path
|
||||
})
|
||||
if (JSON.stringify(schedule.args) != JSON.stringify(args) || schedule.schedule != cron) {
|
||||
await ScheduleService.updateSchedule({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: {
|
||||
schedule: formatCron(cron),
|
||||
args
|
||||
}
|
||||
})
|
||||
}
|
||||
if (enabled != schedule.enabled) {
|
||||
await ScheduleService.setScheduleEnabled({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: { enabled }
|
||||
})
|
||||
}
|
||||
} else if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
if (enabled != schedule.enabled) {
|
||||
await ScheduleService.setScheduleEnabled({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: flow.path,
|
||||
requestBody: { enabled }
|
||||
})
|
||||
}
|
||||
} else if (enabled) {
|
||||
await createSchedule(flow.path)
|
||||
}
|
||||
}
|
||||
loadingSave = false
|
||||
if (leave) {
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
} else if (initialPath !== $flowStore.path) {
|
||||
initialPath = $flowStore.path
|
||||
goto(`/flows/edit/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
loadingSave = false
|
||||
if (leave) {
|
||||
goto(`/flows/get/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
} else if (initialPath !== $flowStore.path) {
|
||||
initialPath = $flowStore.path
|
||||
goto(`/flows/edit/${$flowStore.path}?workspace_id=${$workspaceStore}`)
|
||||
}
|
||||
} catch (err) {
|
||||
sendUserToast(`The flow could not be saved: ${err.body}`, true)
|
||||
loadingSave = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
const lastMetaUsed = writable<Meta | undefined>(undefined)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { type Meta, pathToMeta } from '$lib/common'
|
||||
|
||||
@@ -24,6 +28,8 @@
|
||||
import GroupEditor from './GroupEditor.svelte'
|
||||
import { random_adj } from './random_positive_adjetive'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { canWrite } from '$lib/utils'
|
||||
|
||||
type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app'
|
||||
let meta: Meta | undefined = undefined
|
||||
@@ -40,7 +46,7 @@
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
let folders: string[] = []
|
||||
let folders: { name: string; write: boolean }[] = []
|
||||
let groups: string[] = []
|
||||
|
||||
$: meta && onMetaChange()
|
||||
@@ -49,6 +55,10 @@
|
||||
if (meta) {
|
||||
path = metaToPath(meta)
|
||||
validate(meta, path, kind)
|
||||
$lastMetaUsed = {
|
||||
...meta,
|
||||
name: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,14 +81,17 @@
|
||||
|
||||
export async function reset() {
|
||||
if (path == '' || path == 'u//') {
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
if ($lastMetaUsed == undefined) {
|
||||
meta = {
|
||||
ownerKind: 'user',
|
||||
name: random_adj() + '_' + namePlaceholder,
|
||||
owner: ''
|
||||
}
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0].replace(/[^a-zA-Z0-9]/g, '')
|
||||
} else {
|
||||
meta = { ...$lastMetaUsed, name: random_adj() + '_' + namePlaceholder }
|
||||
}
|
||||
|
||||
meta.owner = $userStore!.username.split('@')[0]
|
||||
|
||||
let newMeta = { ...meta }
|
||||
while (await pathExists(metaToPath(newMeta), kind)) {
|
||||
disabled = true
|
||||
@@ -95,18 +108,20 @@
|
||||
}
|
||||
|
||||
async function loadFolders(): Promise<void> {
|
||||
let initialFolders: string[] = []
|
||||
let initialFolders: { name: string; write: boolean }[] = []
|
||||
let initialFolder = ''
|
||||
if (initialPath?.split('/')?.[0] == 'f') {
|
||||
initialFolder = initialPath?.split('/')?.[1]
|
||||
initialFolders.push(initialFolder)
|
||||
initialFolders.push({ name: initialFolder, write: true })
|
||||
}
|
||||
folders = initialFolders.concat(
|
||||
(
|
||||
await FolderService.listFolderNames({
|
||||
workspace: $workspaceStore!
|
||||
})
|
||||
).filter((x) => x != initialFolder)
|
||||
)
|
||||
.filter((x) => x != initialFolder)
|
||||
.map((x) => ({ name: x, write: $userStore?.folders?.includes(x) == true }))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -224,56 +239,14 @@
|
||||
requestBody: { name: newFolderName }
|
||||
})
|
||||
folderCreated = newFolderName
|
||||
$userStore?.folders?.push(newFolderName)
|
||||
loadFolders()
|
||||
if (meta) {
|
||||
meta.owner = newFolderName
|
||||
}
|
||||
loadFolders()
|
||||
}
|
||||
|
||||
let newGroup: Drawer
|
||||
let viewGroup: Drawer
|
||||
let newGroupName: string
|
||||
let groupCreated: string | undefined = undefined
|
||||
|
||||
async function addGroup() {
|
||||
await GroupService.createGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { name: newGroupName }
|
||||
})
|
||||
groupCreated = newGroupName
|
||||
if (meta) {
|
||||
meta.owner = newGroupName
|
||||
}
|
||||
loadGroups()
|
||||
}
|
||||
</script>
|
||||
|
||||
<Drawer bind:this={newGroup}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
on:close={() => {
|
||||
newGroup.closeDrawer()
|
||||
groupCreated = undefined
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-row">
|
||||
<input class="mr-2" placeholder="New group name" bind:value={newGroupName} />
|
||||
<Button size="md" endIcon={{ icon: faPlus }} disabled={!newGroupName} on:click={addGroup}>
|
||||
New group
|
||||
</Button>
|
||||
</div>
|
||||
{#if groupCreated}
|
||||
<div class="mt-8" />
|
||||
<GroupEditor name={groupCreated} />
|
||||
{/if}
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
<Drawer bind:this={viewGroup}>
|
||||
<DrawerContent title="Folder {meta?.owner}" on:close={viewGroup.closeDrawer}>
|
||||
<GroupEditor name={meta?.owner ?? ''} />
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
|
||||
<Drawer bind:this={newFolder}>
|
||||
<DrawerContent
|
||||
title="New Folder"
|
||||
@@ -356,8 +329,8 @@
|
||||
|
||||
<div class="flex flex-row gap-1 w-full">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#each folders as f}
|
||||
<option>{f}</option>
|
||||
{#each folders as { name, write }}
|
||||
<option disabled={!write}>{name}{write ? '' : ' (read-only)'}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button variant="border" size="xs" on:click={viewFolder.openDrawer}>
|
||||
@@ -368,27 +341,6 @@
|
||||
></div
|
||||
>
|
||||
</label>
|
||||
{:else if meta.ownerKind === 'group'}
|
||||
<label class="block grow w-48">
|
||||
<span class="text-gray-700 text-sm"
|
||||
>Group <Tooltip>Item will be owned by the group and hence all its member</Tooltip
|
||||
></span
|
||||
>
|
||||
|
||||
<div class="flex flex-row gap-1">
|
||||
<select class="grow w-full" {disabled} bind:value={meta.owner}>
|
||||
{#each groups as g}
|
||||
<option>{g}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Button variant="border" size="xs" on:click={viewGroup.openDrawer}>
|
||||
<Icon scale={0.8} data={faEye} /></Button
|
||||
>
|
||||
<Button variant="border" size="xs" on:click={newGroup.openDrawer}>
|
||||
<Icon scale={0.8} data={faPlus} /></Button
|
||||
></div
|
||||
>
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
<label class="block grow w-full max-w-md">
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
workspace: $workspaceStore ?? '',
|
||||
requestBody: { name: newFolderName }
|
||||
})
|
||||
$userStore?.folders.push(newFolderName)
|
||||
loadFolders()
|
||||
editFolderName = newFolderName
|
||||
folderDrawer.openDrawer()
|
||||
|
||||
@@ -131,10 +131,12 @@
|
||||
</script>
|
||||
|
||||
<!-- Enable submit form on enter -->
|
||||
<CenteredModal title="Login">
|
||||
<CenteredModal title={isCloudHosted() ? 'Login/Signup' : 'Login'}>
|
||||
{#if isCloudHosted()}
|
||||
<div class="text-center -mt-4">
|
||||
<span class=" text-gray-600 text-sm">Login or sign up with any of the methods below</span>
|
||||
<span class=" text-gray-600 text-sm"
|
||||
>Login or sign up (no cc required) with any of the methods below</span
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="justify-center text-center flex flex-col">
|
||||
@@ -194,7 +196,7 @@
|
||||
<span class="text-gray-700 text-sm">Email</span>
|
||||
<input type="email" bind:value={email} id="email" />
|
||||
</label>
|
||||
<label class="block ">
|
||||
<label class="block">
|
||||
<span class="text-gray-700 text-sm">Password</span>
|
||||
<input type="password" on:keyup={handleKeyUp} bind:value={password} id="password" />
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user