mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-19 00:02:03 +00:00
fix: hash long dedicated worker tags (#7914)
* fix: hash long dedicated worker tags * Update frontend/src/lib/components/dedicated_worker.ts Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,10 @@ use windmill_api_auth::{
|
||||
check_scopes, maybe_refresh_folders, require_owner_of_path, ApiAuthed,
|
||||
};
|
||||
use windmill_common::{
|
||||
utils::{BulkDeleteRequest, WithStarredInfoQuery, HTTP_CLIENT}, webhook::{WebhookMessage, WebhookShared}, workspaces::{check_user_against_rule, ProtectionRuleKind, RuleCheckResult}, DB
|
||||
utils::{BulkDeleteRequest, WithStarredInfoQuery, HTTP_CLIENT},
|
||||
webhook::{WebhookMessage, WebhookShared},
|
||||
workspaces::{check_user_against_rule, ProtectionRuleKind, RuleCheckResult},
|
||||
DB,
|
||||
};
|
||||
use windmill_queue::schedule::clear_schedule;
|
||||
|
||||
@@ -859,10 +862,13 @@ async fn create_script_internal<'c>(
|
||||
}
|
||||
};
|
||||
|
||||
let runnable_settings_handle = windmill_common::runnable_settings::insert_rs(RunnableSettings {
|
||||
debouncing_settings: ns.debouncing_settings.insert_cached(&db).await?,
|
||||
concurrency_settings: ns.concurrency_settings.insert_cached(&db).await?,
|
||||
}, &db)
|
||||
let runnable_settings_handle = windmill_common::runnable_settings::insert_rs(
|
||||
RunnableSettings {
|
||||
debouncing_settings: ns.debouncing_settings.insert_cached(&db).await?,
|
||||
concurrency_settings: ns.concurrency_settings.insert_cached(&db).await?,
|
||||
},
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let (
|
||||
@@ -1072,7 +1078,9 @@ async fn create_script_internal<'c>(
|
||||
}
|
||||
if needs_lock_gen {
|
||||
let tag = if ns.dedicated_worker.is_some_and(|x| x) {
|
||||
Some(format!("{}:{}", &w_id, &ns.path,))
|
||||
Some(windmill_common::worker::dedicated_worker_tag(
|
||||
&w_id, &ns.path,
|
||||
))
|
||||
} else if ns.tag.as_ref().is_some_and(|x| x.contains("$args[")) {
|
||||
None
|
||||
} else {
|
||||
@@ -1839,7 +1847,9 @@ async fn get_script_by_hash(
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(Json(windmill_common::scripts::prefetch_cached_script_with_starred(r, &db).await?))
|
||||
Ok(Json(
|
||||
windmill_common::scripts::prefetch_cached_script_with_starred(r, &db).await?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn raw_script_by_hash(
|
||||
@@ -2042,7 +2052,9 @@ async fn archive_script_by_hash(
|
||||
WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() },
|
||||
);
|
||||
|
||||
Ok(Json(windmill_common::scripts::prefetch_cached_script(script, &db).await?))
|
||||
Ok(Json(
|
||||
windmill_common::scripts::prefetch_cached_script(script, &db).await?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn delete_script_by_hash(
|
||||
@@ -2097,7 +2109,9 @@ async fn delete_script_by_hash(
|
||||
WebhookMessage::DeleteScript { workspace: w_id, hash: hash.to_string() },
|
||||
);
|
||||
|
||||
Ok(Json(windmill_common::scripts::prefetch_cached_script(script, &db).await?))
|
||||
Ok(Json(
|
||||
windmill_common::scripts::prefetch_cached_script(script, &db).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
||||
@@ -2135,9 +2135,7 @@ pub async fn resume_suspended_flow_as_owner(
|
||||
|
||||
// Check approval conditions (self-approval, required groups, etc.)
|
||||
if let Some(ref flow_status_value) = flow.flow_status {
|
||||
if let Ok(flow_status) =
|
||||
serde_json::from_value::<FlowStatus>(flow_status_value.clone())
|
||||
{
|
||||
if let Ok(flow_status) = serde_json::from_value::<FlowStatus>(flow_status_value.clone()) {
|
||||
let trigger_email = flow.email.as_deref().unwrap_or("");
|
||||
conditionally_require_authed_user(Some(authed.clone()), flow_status, trigger_email)?;
|
||||
}
|
||||
@@ -5224,7 +5222,7 @@ async fn add_batch_jobs(
|
||||
|
||||
let tag = if let Some(dedicated_worker) = dedicated_worker {
|
||||
if dedicated_worker && path.is_some() {
|
||||
format!("{}:{}", w_id, path.clone().unwrap())
|
||||
windmill_common::worker::dedicated_worker_tag(&w_id, &path.clone().unwrap())
|
||||
} else {
|
||||
format!("{}", language.as_str())
|
||||
}
|
||||
|
||||
@@ -1560,6 +1560,24 @@ pub async fn update_worker_ping_main_loop_query(
|
||||
// occupancy_rate = $3, memory_usage = $4, wm_memory_usage = $5, vcpus = COALESCE($7, vcpus),
|
||||
// memory = COALESCE($8, memory), occupancy_rate_15s = $9, occupancy_rate_5m = $10, occupancy_rate_30m = $11 WHERE worker = $6",
|
||||
|
||||
const MAX_TAG_LEN: usize = 50;
|
||||
const HASH_SUFFIX_LEN: usize = 16;
|
||||
|
||||
pub fn dedicated_worker_tag(workspace_id: &str, path: &str) -> String {
|
||||
let full_tag = format!("{}:{}", workspace_id, path);
|
||||
if full_tag.len() <= MAX_TAG_LEN {
|
||||
return full_tag;
|
||||
}
|
||||
let hash = <sha2::Sha256 as sha2::Digest>::digest(full_tag.as_bytes());
|
||||
let hex_hash = hex::encode(hash);
|
||||
let prefix_len = MAX_TAG_LEN - 1 - HASH_SUFFIX_LEN;
|
||||
format!(
|
||||
"{}#{}",
|
||||
&full_tag[..prefix_len],
|
||||
&hex_hash[..HASH_SUFFIX_LEN]
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn load_worker_config(
|
||||
db: &DB,
|
||||
killpill_tx: KillpillSender,
|
||||
@@ -1671,7 +1689,7 @@ pub async fn load_worker_config(
|
||||
if let Some(ref dws) = dedicated_workers.as_ref() {
|
||||
let mut dedi_tags: Vec<String> = dws
|
||||
.iter()
|
||||
.map(|dw| format!("{}:{}", dw.workspace_id, dw.path))
|
||||
.map(|dw| dedicated_worker_tag(&dw.workspace_id, &dw.path))
|
||||
.collect();
|
||||
if std::env::var("ADD_FLOW_TAG").is_ok() {
|
||||
dedi_tags.push("flow".to_string());
|
||||
@@ -1679,9 +1697,9 @@ pub async fn load_worker_config(
|
||||
Some(dedi_tags)
|
||||
} else if let Some(ref dedicated_worker) = dedicated_worker.as_ref() {
|
||||
// Fallback to single dedicated worker for backward compatibility
|
||||
let mut dedi_tags = vec![format!(
|
||||
"{}:{}",
|
||||
dedicated_worker.workspace_id, dedicated_worker.path
|
||||
let mut dedi_tags = vec![dedicated_worker_tag(
|
||||
&dedicated_worker.workspace_id,
|
||||
&dedicated_worker.path,
|
||||
)];
|
||||
if std::env::var("ADD_FLOW_TAG").is_ok() {
|
||||
dedi_tags.push("flow".to_string());
|
||||
@@ -2178,4 +2196,58 @@ mod tests {
|
||||
result.sort();
|
||||
assert_eq!(result, vec!["foo", "legacy(^ws1^ws2)", "urgent(ws1+ws2)"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedicated_worker_tag_short() {
|
||||
let tag = dedicated_worker_tag("demo", "u/alice/script");
|
||||
assert_eq!(tag, "demo:u/alice/script");
|
||||
assert!(tag.len() <= MAX_TAG_LEN);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedicated_worker_tag_exactly_50() {
|
||||
// 50 chars exactly should not be hashed
|
||||
let workspace = "ws";
|
||||
let path = "a".repeat(50 - workspace.len() - 1); // -1 for ':'
|
||||
let tag = dedicated_worker_tag(workspace, &path);
|
||||
assert_eq!(tag.len(), 50);
|
||||
assert!(!tag.contains('#'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedicated_worker_tag_long_is_hashed() {
|
||||
let tag = dedicated_worker_tag(
|
||||
"my_workspace",
|
||||
"u/engineering/team/automation/critical_workflow_script_v2",
|
||||
);
|
||||
assert_eq!(tag.len(), MAX_TAG_LEN);
|
||||
assert_eq!(tag, "my_workspace:u/engineering/team/a#5bc26db79926d4f0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedicated_worker_tag_deterministic() {
|
||||
let a = dedicated_worker_tag(
|
||||
"ws",
|
||||
"some/very/long/path/that/exceeds/the/fifty/char/limit/easily",
|
||||
);
|
||||
assert_eq!(a, "ws:some/very/long/path/that/excee#bbb038d4268a0b41");
|
||||
let b = dedicated_worker_tag(
|
||||
"ws",
|
||||
"some/very/long/path/that/exceeds/the/fifty/char/limit/easily",
|
||||
);
|
||||
assert_eq!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedicated_worker_tag_different_paths_differ() {
|
||||
let a = dedicated_worker_tag(
|
||||
"ws",
|
||||
"some/very/long/path/that/exceeds/the/fifty/char/limit/easily_a",
|
||||
);
|
||||
let b = dedicated_worker_tag(
|
||||
"ws",
|
||||
"some/very/long/path/that/exceeds/the/fifty/char/limit/easily_b",
|
||||
);
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5274,16 +5274,17 @@ async fn push_inner<'c, 'd>(
|
||||
.unwrap_or_else(|| (None, None));
|
||||
|
||||
let tag = if dedicated_worker.is_some_and(|x| x) {
|
||||
format!(
|
||||
"{}:{}{}",
|
||||
workspace_id,
|
||||
if job_kind == JobKind::Flow || job_kind == JobKind::FlowDependencies {
|
||||
"flow/"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
let flow_prefix = if job_kind == JobKind::Flow || job_kind == JobKind::FlowDependencies {
|
||||
"flow/"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let full_path = format!(
|
||||
"{}{}",
|
||||
flow_prefix,
|
||||
runnable_path.clone().expect("dedicated script has a path")
|
||||
)
|
||||
);
|
||||
windmill_common::worker::dedicated_worker_tag(workspace_id, &full_path)
|
||||
} else {
|
||||
if tag == Some("".to_string()) {
|
||||
tag = None;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { SvelteMap } from 'svelte/reactivity'
|
||||
import { untrack } from 'svelte'
|
||||
import BarsStaggered from './icons/BarsStaggered.svelte'
|
||||
import { parseTag } from './dedicated_worker'
|
||||
|
||||
// A "Runnable" is a script or flow with dedicated_worker=true
|
||||
interface Runnable {
|
||||
@@ -62,21 +63,6 @@
|
||||
// Languages that support dedicated workers
|
||||
const DEDICATED_WORKER_LANGUAGES = ['python3', 'bun', 'deno']
|
||||
|
||||
// Parse a tag to extract workspace, type (script/flow), and path
|
||||
function parseTag(tag: string): { workspace: string; type: 'script' | 'flow'; path: string } | null {
|
||||
const colonIndex = tag.indexOf(':')
|
||||
if (colonIndex === -1) return null
|
||||
|
||||
const workspace = tag.substring(0, colonIndex)
|
||||
const rest = tag.substring(colonIndex + 1)
|
||||
|
||||
if (rest.startsWith('flow/')) {
|
||||
return { workspace, type: 'flow', path: rest.substring(5) }
|
||||
} else {
|
||||
return { workspace, type: 'script', path: rest }
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve workspace script languages and filter to supported languages
|
||||
async function resolveAndFilterRunners(
|
||||
workspace: string,
|
||||
@@ -438,25 +424,27 @@
|
||||
{:else}
|
||||
<div class="w-7"></div>
|
||||
{/if}
|
||||
<div class="flex-1 flex items-center gap-2 px-2 py-1.5 min-w-0">
|
||||
{#if info}
|
||||
{#if info.type === 'flow'}
|
||||
<BarsStaggered size={14} class="flex-shrink-0 text-secondary" />
|
||||
<div class="flex-1 flex flex-col gap-0.5 px-2 py-1.5 min-w-0">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
{#if info}
|
||||
{#if info.type === 'flow'}
|
||||
<BarsStaggered size={14} class="flex-shrink-0 text-secondary" />
|
||||
{:else}
|
||||
<CodeXml size={14} class="flex-shrink-0 text-secondary" />
|
||||
{/if}
|
||||
<span class="text-xs truncate flex-1 min-w-0">{info.path}</span>
|
||||
<span class="text-xs text-tertiary flex-shrink-0">({info.workspace})</span>
|
||||
{#if info.type === 'flow' && info.runners}
|
||||
<Badge color="indigo" small>
|
||||
{info.runners.length} runner{info.runners.length !== 1 ? 's' : ''}
|
||||
</Badge>
|
||||
{:else if info.type === 'script'}
|
||||
<Badge color="blue" small>1 runner</Badge>
|
||||
{/if}
|
||||
{:else}
|
||||
<CodeXml size={14} class="flex-shrink-0 text-secondary" />
|
||||
<span class="text-xs text-tertiary truncate">{tag}</span>
|
||||
{/if}
|
||||
<span class="text-xs truncate flex-1">{info.path}</span>
|
||||
<span class="text-xs text-tertiary flex-shrink-0">({info.workspace})</span>
|
||||
{#if info.type === 'flow' && info.runners}
|
||||
<Badge color="indigo" small>
|
||||
{info.runners.length} runner{info.runners.length !== 1 ? 's' : ''}
|
||||
</Badge>
|
||||
{:else if info.type === 'script'}
|
||||
<Badge color="blue" small>1 runner</Badge>
|
||||
{/if}
|
||||
{:else}
|
||||
<span class="text-xs text-tertiary truncate">{tag}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{#if !disabled}
|
||||
<button
|
||||
@@ -474,10 +462,14 @@
|
||||
{#if info?.type === 'flow' && info.expanded && info.runners}
|
||||
<div class="bg-surface-secondary border-t">
|
||||
{#each info.runners as runner (runner.stepId)}
|
||||
<div class="flex items-center gap-2 px-9 py-1 text-xs border-t first:border-t-0">
|
||||
<span class="font-mono text-tertiary">{runner.stepId}</span>
|
||||
<div
|
||||
class="flex items-center gap-2 px-9 py-1 text-xs border-t first:border-t-0 min-w-0"
|
||||
>
|
||||
<span class="font-mono text-tertiary flex-shrink-0">{runner.stepId}</span>
|
||||
{#if runner.stepSummary}
|
||||
<span class="text-secondary truncate flex-1">{runner.stepSummary}</span>
|
||||
<span class="text-secondary truncate flex-1 min-w-0"
|
||||
>{runner.stepSummary}</span
|
||||
>
|
||||
{/if}
|
||||
<Badge color="gray" small>
|
||||
{runner.isInline ? runner.language : runner.scriptPath}
|
||||
@@ -577,7 +569,7 @@
|
||||
<div class="w-7"></div>
|
||||
{/if}
|
||||
<button
|
||||
class="flex-1 flex items-center gap-2 px-2 py-1.5 hover:bg-surface-hover transition-colors text-left"
|
||||
class="flex-1 flex items-center gap-2 px-2 py-1.5 hover:bg-surface-hover transition-colors text-left min-w-0"
|
||||
onclick={(e) => {
|
||||
e.stopPropagation()
|
||||
if (!disabled) toggleRunnable(runnable)
|
||||
@@ -593,9 +585,11 @@
|
||||
<Check class="h-3 w-3 text-white" />
|
||||
{/if}
|
||||
</div>
|
||||
<span class="flex-1 text-xs truncate">{runnable.displayName}</span>
|
||||
<span class="flex-1 text-xs truncate min-w-0"
|
||||
>{runnable.displayName}</span
|
||||
>
|
||||
{#if runnable.type === 'flow' && runnable.runners}
|
||||
<span class="text-xs text-tertiary">
|
||||
<span class="text-xs text-tertiary flex-shrink-0">
|
||||
{runnable.runners.length}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -614,11 +608,13 @@
|
||||
{:else}
|
||||
{#each runnable.runners as runner (runner.stepId)}
|
||||
<div
|
||||
class="flex items-center gap-2 px-9 py-1 text-xs border-t first:border-t-0"
|
||||
class="flex items-center gap-2 px-9 py-1 text-xs border-t first:border-t-0 min-w-0"
|
||||
>
|
||||
<span class="font-mono text-tertiary">{runner.stepId}</span>
|
||||
<span class="font-mono text-tertiary flex-shrink-0"
|
||||
>{runner.stepId}</span
|
||||
>
|
||||
{#if runner.stepSummary}
|
||||
<span class="text-secondary truncate flex-1">
|
||||
<span class="text-secondary truncate flex-1 min-w-0">
|
||||
{runner.stepSummary}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
@@ -1443,8 +1443,8 @@
|
||||
{#if script.dedicated_worker}
|
||||
<div class="py-2">
|
||||
<Alert type="info" title="Require dedicated workers">
|
||||
One worker in a worker group needs to be configured with dedicated worker
|
||||
set to: <pre>{$workspaceStore}:{script.path}</pre>
|
||||
A worker group needs to be configured to listen to this script. Select
|
||||
it in the dedicated workers section of the worker group configuration.
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
import Dropdown from './DropdownV2.svelte'
|
||||
import TagList from './TagList.svelte'
|
||||
import DedicatedWorkersSelector from './DedicatedWorkersSelector.svelte'
|
||||
import { computeHashedTag } from './dedicated_worker'
|
||||
|
||||
function computeVCpuAndMemory(workers: [string, WorkerPing[]][]) {
|
||||
let vcpus = 0
|
||||
@@ -252,6 +253,21 @@
|
||||
let openDelete = $state(false)
|
||||
let openClean = $state(false)
|
||||
|
||||
// Compute hashed tags for display (actual tags used by the worker)
|
||||
let hashedDedicatedTags: Map<string, string> = $state(new Map())
|
||||
$effect(() => {
|
||||
const dws = config?.dedicated_workers ?? (config?.dedicated_worker ? [config.dedicated_worker] : [])
|
||||
if (dws.length > 0) {
|
||||
Promise.all(dws.map(async (dw) => [dw, await computeHashedTag(dw)] as const)).then(
|
||||
(entries) => {
|
||||
hashedDedicatedTags = new Map(entries)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
hashedDedicatedTags = new Map()
|
||||
}
|
||||
})
|
||||
|
||||
let drawer: Drawer | undefined = $state()
|
||||
let vcpus_memory = $derived(computeVCpuAndMemory(workers))
|
||||
let selected = $derived(
|
||||
@@ -1166,21 +1182,27 @@
|
||||
<TagList tags={config.worker_tags} maxVisible={25} class="flex-wrap" />
|
||||
</div>
|
||||
{:else if config?.dedicated_workers && config.dedicated_workers.length > 0}
|
||||
<div class="flex flex-row items-start gap-2 w-full">
|
||||
<div class="text-secondary text-xs mt-1">Dedicated to:</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<div class="flex flex-row items-start gap-2 w-full min-w-0">
|
||||
<div class="text-secondary text-xs mt-1 flex-shrink-0">Dedicated to:</div>
|
||||
<div class="flex flex-wrap gap-1 min-w-0">
|
||||
{#each config.dedicated_workers as dw}
|
||||
<div class="text-xs bg-surface-secondary px-2 py-1 rounded text-primary font-mono">
|
||||
{dw}
|
||||
<div
|
||||
class="text-xs bg-surface-secondary px-2 py-1 rounded text-primary font-mono truncate max-w-xs"
|
||||
title={hashedDedicatedTags.get(dw) ?? dw}
|
||||
>
|
||||
{hashedDedicatedTags.get(dw) ?? dw}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{:else if config?.dedicated_worker}
|
||||
<div class="flex flex-row items-start gap-2 w-full">
|
||||
<div class="text-secondary text-xs mt-1">Dedicated to:</div>
|
||||
<div class="text-xs bg-surface-secondary px-2 py-1 rounded text-primary font-mono">
|
||||
{config.dedicated_worker}
|
||||
<div class="flex flex-row items-start gap-2 w-full min-w-0">
|
||||
<div class="text-secondary text-xs mt-1 flex-shrink-0">Dedicated to:</div>
|
||||
<div
|
||||
class="text-xs bg-surface-secondary px-2 py-1 rounded text-primary font-mono truncate max-w-xs"
|
||||
title={hashedDedicatedTags.get(config.dedicated_worker) ?? config.dedicated_worker}
|
||||
>
|
||||
{hashedDedicatedTags.get(config.dedicated_worker) ?? config.dedicated_worker}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
const MAX_TAG_LEN = 50
|
||||
const HASH_SUFFIX_LEN = 16
|
||||
|
||||
// IMPORTANT: This implementation must stay in sync with the Rust backend
|
||||
// `dedicated_worker_tag()` in backend/windmill-common/src/worker.rs.
|
||||
// The frontend version is used for display; the backend version is authoritative.
|
||||
export async function dedicatedWorkerTag(workspaceId: string, path: string): Promise<string> {
|
||||
const fullTag = `${workspaceId}:${path}`
|
||||
if (fullTag.length <= MAX_TAG_LEN) return fullTag
|
||||
|
||||
const encoded = new TextEncoder().encode(fullTag)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', encoded)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hexHash = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')
|
||||
|
||||
const prefixLen = MAX_TAG_LEN - 1 - HASH_SUFFIX_LEN
|
||||
return `${fullTag.substring(0, prefixLen)}#${hexHash.substring(0, HASH_SUFFIX_LEN)}`
|
||||
}
|
||||
|
||||
export function parseTag(
|
||||
tag: string
|
||||
): { workspace: string; type: 'script' | 'flow'; path: string } | null {
|
||||
const colonIndex = tag.indexOf(':')
|
||||
if (colonIndex === -1) return null
|
||||
|
||||
const workspace = tag.substring(0, colonIndex)
|
||||
const rest = tag.substring(colonIndex + 1)
|
||||
|
||||
if (rest.startsWith('flow/')) {
|
||||
return { workspace, type: 'flow', path: rest.substring(5) }
|
||||
} else {
|
||||
return { workspace, type: 'script', path: rest }
|
||||
}
|
||||
}
|
||||
|
||||
export async function computeHashedTag(rawTag: string): Promise<string> {
|
||||
const parsed = parseTag(rawTag)
|
||||
if (!parsed) return rawTag
|
||||
const fullPath = parsed.type === 'flow' ? `flow/${parsed.path}` : parsed.path
|
||||
return dedicatedWorkerTag(parsed.workspace, fullPath)
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
import { Alert, Button, SecondsInput } from '$lib/components/common'
|
||||
import { getContext } from 'svelte'
|
||||
import type { FlowEditorContext } from '../types'
|
||||
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
|
||||
import { enterpriseLicense, userStore } from '$lib/stores'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import Tooltip from '$lib/components/Tooltip.svelte'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
@@ -554,9 +554,8 @@
|
||||
{#if flowStore.val.dedicated_worker}
|
||||
<div class="mt-2">
|
||||
<Alert type="info" title="Require dedicated workers">
|
||||
One worker in a worker group needs to be configured with dedicated worker set to: <pre
|
||||
>{$workspaceStore}:flow/{$pathStore}</pre
|
||||
>
|
||||
A worker group needs to be configured to listen to this flow. Select it in the dedicated
|
||||
workers section of the worker group configuration.
|
||||
</Alert>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user