windmill-components 1.687.0 → 1.695.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package/components/ArgInput.svelte +2 -0
- package/package/components/AutoscalingConfigEditor.svelte +18 -4
- package/package/components/CompareWorkspaces.svelte +206 -157
- package/package/components/DatatableSchemaDiff.svelte +2 -2
- package/package/components/Dev.svelte +401 -85
- package/package/components/EditableSchemaForm.svelte +4 -0
- package/package/components/ErrorOrRecoveryHandler.svelte +2 -2
- package/package/components/FlowPreviewContent.svelte +32 -30
- package/package/components/FlowRestartButton.svelte +143 -61
- package/package/components/FlowRestartButton.svelte.d.ts +37 -0
- package/package/components/FlowStatusViewer.svelte +15 -1
- package/package/components/FlowStatusViewer.svelte.d.ts +10 -2
- package/package/components/FlowStatusViewerInner.svelte +1 -2
- package/package/components/FlowStatusViewerInner.svelte.d.ts +6 -2
- package/package/components/ForkConflictModal.svelte +57 -0
- package/package/components/ForkConflictModal.svelte.d.ts +3 -0
- package/package/components/GitRepoViewer.svelte +251 -97
- package/package/components/InputTransformSchemaForm.svelte +1 -1
- package/package/components/InstanceSettings.svelte +36 -16
- package/package/components/Login.svelte +113 -28
- package/package/components/Login.svelte.d.ts +1 -0
- package/package/components/Path.svelte +7 -1
- package/package/components/Path.svelte.d.ts +1 -1
- package/package/components/RunsPage.svelte +2 -1
- package/package/components/S3FilePickerInner.svelte +89 -89
- package/package/components/ScriptEditor.svelte +18 -5
- package/package/components/ShareModal.svelte.d.ts +1 -1
- package/package/components/apps/components/helpers/RunnableComponent.svelte.d.ts +3 -0
- package/package/components/apps/components/helpers/executeRunnable.js +2 -1
- package/package/components/apps/editor/AppReportsDrawerInner.svelte +1 -1
- package/package/components/apps/editor/appPolicy.js +2 -1
- package/package/components/apps/editor/commonAppUtils.d.ts +3 -0
- package/package/components/apps/editor/inlineScriptsPanel/CacheTtlPopup.svelte +1 -1
- package/package/components/apps/editor/inlineScriptsPanel/InlineScriptEditor.svelte +7 -0
- package/package/components/apps/editor/inlineScriptsPanel/TagPopup.svelte +49 -0
- package/package/components/apps/editor/inlineScriptsPanel/TagPopup.svelte.d.ts +9 -0
- package/package/components/apps/inputType.d.ts +1 -0
- package/package/components/apps/sharedTypes.d.ts +1 -0
- package/package/components/auditLogs/AuditLogsFilters.svelte +8 -3
- package/package/components/common/fileUpload/S3ArgInput.svelte +12 -10
- package/package/components/common/fileUpload/S3ArgInput.svelte.d.ts +2 -0
- package/package/components/copilot/chat/AIChatDisplay.svelte +5 -36
- package/package/components/copilot/chat/AIChatInput.svelte +56 -47
- package/package/components/copilot/chat/AIChatManager.svelte.js +48 -46
- package/package/components/copilot/chat/ContextElementBadge.svelte +6 -4
- package/package/components/copilot/chat/app/core.d.ts +12 -20
- package/package/components/copilot/chat/app/core.js +103 -160
- package/package/components/copilot/chat/app/core.test.js +234 -9
- package/package/components/copilot/chat/context.js +44 -0
- package/package/components/copilot/chat/flow/FlowAIChat.svelte +5 -3
- package/package/components/copilot/chat/flow/core.d.ts +2 -1
- package/package/components/copilot/chat/flow/core.js +48 -21
- package/package/components/copilot/chat/flow/helperUtils.d.ts +5 -2
- package/package/components/copilot/chat/flow/helperUtils.js +33 -1
- package/package/components/copilot/chat/flow/helperUtils.test.js +116 -1
- package/package/components/copilot/chat/flow/openFlow.json +1 -1
- package/package/components/copilot/chat/flow/openFlowZod.gen.js +24 -0
- package/package/components/copilot/chat/script/core.js +3 -0
- package/package/components/copilot/chat/shared.d.ts +6 -0
- package/package/components/copilot/chat/shared.js +22 -1
- package/package/components/copilot/chat/shared.test.d.ts +1 -0
- package/package/components/copilot/chat/shared.test.js +412 -0
- package/package/components/copilot/chat/workspaceTools.d.ts +7 -0
- package/package/components/copilot/chat/workspaceTools.js +239 -0
- package/package/components/copilot/chat/workspaceToolsZod.gen.d.ts +1295 -0
- package/package/components/copilot/chat/workspaceToolsZod.gen.js +424 -0
- package/package/components/copilot/lib.js +3 -1
- package/package/components/copilot/lib.test.d.ts +1 -0
- package/package/components/copilot/lib.test.js +19 -0
- package/package/components/copilot/modelConfig.d.ts +3 -0
- package/package/components/copilot/modelConfig.js +10 -0
- package/package/components/flows/FlowProgressBar.svelte +5 -2
- package/package/components/flows/content/FlowModuleComponent.svelte +636 -599
- package/package/components/flows/conversations/FlowChatManager.svelte.js +21 -10
- package/package/components/flows/flowStateUtils.svelte.js +5 -1
- package/package/components/flows/map/FlowModuleSchemaMap.svelte +3 -2
- package/package/components/flows/map/FlowModuleSchemaMap.svelte.d.ts +1 -0
- package/package/components/git_sync/GitSyncContext.svelte.js +0 -2
- package/package/components/graph/FlowGraphV2.svelte +7 -3
- package/package/components/graph/FlowGraphV2.svelte.d.ts +1 -0
- package/package/components/graph/renderers/triggers/TriggersBadge.svelte +3 -0
- package/package/components/home/deploy_ui.js +1 -1
- package/package/components/icons/AzureIcon.svelte +12 -25
- package/package/components/icons/AzureIcon.svelte.d.ts +3 -2
- package/package/components/instanceSettings.js +24 -0
- package/package/components/mcp/McpScopeSelector.svelte +119 -9
- package/package/components/mcp/McpScopeSelector.svelte.d.ts +1 -0
- package/package/components/offboarding-utils.js +2 -0
- package/package/components/progressBar/ProgressBar.svelte +9 -5
- package/package/components/progressBar/ProgressBar.svelte.d.ts +1 -0
- package/package/components/raw_apps/DeleteAfterUsePopup.svelte +52 -0
- package/package/components/raw_apps/DeleteAfterUsePopup.svelte.d.ts +9 -0
- package/package/components/raw_apps/RawAppBackgroundRunner.svelte +5 -1
- package/package/components/raw_apps/RawAppEditor.svelte +159 -102
- package/package/components/raw_apps/RawAppInlineScriptEditor.svelte +9 -3
- package/package/components/raw_apps/RawAppInlineScriptEditor.svelte.d.ts +2 -1
- package/package/components/raw_apps/RawAppInlineScriptRunnable.svelte +1 -0
- package/package/components/raw_apps/RawAppInlineScriptRunnable.svelte.d.ts +1 -0
- package/package/components/raw_apps/RawAppInputsSpecEditor.svelte +48 -5
- package/package/components/raw_apps/RawAppSharedUiDrawer.svelte +129 -0
- package/package/components/raw_apps/RawAppSharedUiDrawer.svelte.d.ts +5 -0
- package/package/components/raw_apps/RawAppSidebar.svelte +12 -0
- package/package/components/raw_apps/dataTableRefUtils.d.ts +7 -0
- package/package/components/raw_apps/dataTableRefUtils.js +34 -0
- package/package/components/raw_apps/dataTableRefUtils.test.d.ts +1 -0
- package/package/components/raw_apps/dataTableRefUtils.test.js +29 -0
- package/package/components/raw_apps/rawAppPolicy.d.ts +1 -0
- package/package/components/raw_apps/rawAppPolicy.js +17 -2
- package/package/components/resources/resourceTypesFilter.d.ts +19 -0
- package/package/components/resources/resourceTypesFilter.js +21 -0
- package/package/components/restartFromStepPath.d.ts +39 -0
- package/package/components/restartFromStepPath.js +89 -0
- package/package/components/runs/JobDetailFieldConfig.d.ts +1 -0
- package/package/components/runs/JobDetailFieldConfig.js +57 -10
- package/package/components/runs/JobDetailHeader.svelte +24 -3
- package/package/components/runs/runsFilter.d.ts +1 -1
- package/package/components/schema/FlowPropertyEditor.svelte +30 -1
- package/package/components/schema/FlowPropertyEditor.svelte.d.ts +5 -2
- package/package/components/search/GlobalSearchModal.svelte +8 -1
- package/package/components/select/Select.svelte +1 -1
- package/package/components/settings/CreateToken.svelte +48 -77
- package/package/components/settings/EditTokenScopesModal.svelte +57 -0
- package/package/components/settings/EditTokenScopesModal.svelte.d.ts +10 -0
- package/package/components/settings/ScopesPicker.svelte +43 -0
- package/package/components/settings/ScopesPicker.svelte.d.ts +11 -0
- package/package/components/settings/TokensTable.svelte +51 -15
- package/package/components/sidebar/OperatorMenu.svelte +6 -0
- package/package/components/sidebar/SidebarContent.svelte +11 -1
- package/package/components/triggers/AddTriggersButton.svelte +6 -0
- package/package/components/triggers/CaptureWrapper.svelte +19 -1
- package/package/components/triggers/TriggerEditorToolbar.svelte.d.ts +1 -1
- package/package/components/triggers/TriggerModeToggle.svelte +36 -7
- package/package/components/triggers/TriggerModeToggle.svelte.d.ts +1 -1
- package/package/components/triggers/TriggerSuspendedJobsModal.svelte.d.ts +1 -1
- package/package/components/triggers/TriggersEditor.svelte +5 -1
- package/package/components/triggers/TriggersWrapper.svelte +10 -0
- package/package/components/triggers/azure/AzureCapture.svelte +41 -0
- package/package/components/triggers/azure/AzureCapture.svelte.d.ts +44 -0
- package/package/components/triggers/azure/AzureTriggerEditor.svelte +20 -0
- package/package/components/triggers/azure/AzureTriggerEditor.svelte.d.ts +9 -0
- package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte +301 -0
- package/package/components/triggers/azure/AzureTriggerEditorConfigSection.svelte.d.ts +16 -0
- package/package/components/triggers/azure/AzureTriggerEditorInner.svelte +422 -0
- package/package/components/triggers/azure/AzureTriggerEditorInner.svelte.d.ts +25 -0
- package/package/components/triggers/azure/AzureTriggerPanel.svelte +55 -0
- package/package/components/triggers/azure/AzureTriggerPanel.svelte.d.ts +10 -0
- package/{dist/sharedUtils/components/triggers/kafka → package/components/triggers/azure}/utils.d.ts +1 -1
- package/package/components/triggers/azure/utils.js +56 -0
- package/package/components/triggers/email/EmailTriggerEditorInner.svelte +2 -0
- package/package/components/triggers/gcp/GcpTriggerEditorInner.svelte +9 -3
- package/package/components/triggers/http/RouteEditorInner.svelte +2 -0
- package/package/components/triggers/kafka/KafkaTriggerEditorInner.svelte +9 -3
- package/package/components/triggers/mqtt/MqttTriggerEditorInner.svelte +9 -3
- package/package/components/triggers/nats/NatsTriggerEditorInner.svelte +9 -3
- package/package/components/triggers/postgres/PostgresTriggerEditorInner.svelte +9 -3
- package/package/components/triggers/schedules/ScheduleEditorInner.svelte +9 -3
- package/package/components/triggers/sqs/SqsTriggerEditorInner.svelte +9 -3
- package/package/components/triggers/triggers.svelte.d.ts +1 -0
- package/package/components/triggers/triggers.svelte.js +23 -1
- package/package/components/triggers/utils.js +20 -0
- package/package/components/triggers/websocket/WebsocketTriggerEditorInner.svelte +9 -3
- package/package/components/triggers.d.ts +1 -1
- package/package/components/useNestedRestartState.svelte.d.ts +56 -0
- package/package/components/useNestedRestartState.svelte.js +320 -0
- package/package/components/workspaceSettings/SharedUiSettings.svelte +175 -0
- package/package/components/workspaceSettings/SharedUiSettings.svelte.d.ts +3 -0
- package/package/gen/core/OpenAPI.js +1 -1
- package/package/gen/schemas.gen.d.ts +294 -24
- package/package/gen/schemas.gen.js +297 -25
- package/package/gen/services.gen.d.ts +247 -4
- package/package/gen/services.gen.js +498 -7
- package/package/gen/types.gen.d.ts +990 -37
- package/package/hubPaths.json +2 -5
- package/package/infer.d.ts +1 -1
- package/package/infer.js +37 -51
- package/package/mcpEndpointTools.js +60 -4
- package/package/script_helpers.js +17 -0
- package/package/stores.d.ts +7 -0
- package/package/stores.js +6 -1
- package/package/system_prompts/index.d.ts +1 -0
- package/package/system_prompts/index.js +8 -0
- package/package/system_prompts/prompts.d.ts +16 -13
- package/package/system_prompts/prompts.js +653 -43
- package/package/templates/ci_test_bun.ts.template +8 -0
- package/package/templates/ci_test_python.py.template +8 -0
- package/package/utils/forkConflict.d.ts +26 -0
- package/package/utils/forkConflict.js +56 -0
- package/package/utils_deployable.d.ts +164 -121
- package/package/utils_deployable.js +61 -11
- package/package/utils_workspace_deploy.js +3 -1
- package/package.json +29 -5
- package/dist/sharedUtils/assets/tokens/colorTokensConfig.d.ts +0 -2
- package/dist/sharedUtils/base.d.ts +0 -1
- package/dist/sharedUtils/cloud.d.ts +0 -1
- package/dist/sharedUtils/common.d.ts +0 -111
- package/dist/sharedUtils/components/apps/components/display/dbtable/queries/count.d.ts +0 -5
- package/dist/sharedUtils/components/apps/components/display/dbtable/queries/delete.d.ts +0 -5
- package/dist/sharedUtils/components/apps/components/display/dbtable/queries/insert.d.ts +0 -5
- package/dist/sharedUtils/components/apps/components/display/dbtable/queries/select.d.ts +0 -13
- package/dist/sharedUtils/components/apps/components/display/dbtable/queries/update.d.ts +0 -11
- package/dist/sharedUtils/components/apps/components/display/dbtable/utils.d.ts +0 -95
- package/dist/sharedUtils/components/apps/editor/appPolicy.d.ts +0 -6
- package/dist/sharedUtils/components/apps/editor/appUtilsCore.d.ts +0 -7
- package/dist/sharedUtils/components/apps/editor/appUtilsS3.d.ts +0 -33
- package/dist/sharedUtils/components/apps/editor/commonAppUtils.d.ts +0 -10
- package/dist/sharedUtils/components/apps/editor/component/components.d.ts +0 -5371
- package/dist/sharedUtils/components/apps/editor/component/default-codes.d.ts +0 -3
- package/dist/sharedUtils/components/apps/editor/component/index.d.ts +0 -3
- package/dist/sharedUtils/components/apps/editor/component/sets.d.ts +0 -7
- package/dist/sharedUtils/components/apps/editor/componentsPanel/componentDefaultProps.d.ts +0 -3
- package/dist/sharedUtils/components/apps/gridUtils.d.ts +0 -14
- package/dist/sharedUtils/components/apps/inputType.d.ts +0 -178
- package/dist/sharedUtils/components/apps/rx.d.ts +0 -29
- package/dist/sharedUtils/components/apps/sharedTypes.d.ts +0 -21
- package/dist/sharedUtils/components/apps/types.d.ts +0 -274
- package/dist/sharedUtils/components/assets/lib.d.ts +0 -25
- package/dist/sharedUtils/components/common/alert/model.d.ts +0 -2
- package/dist/sharedUtils/components/common/badge/model.d.ts +0 -8
- package/dist/sharedUtils/components/common/button/model.d.ts +0 -45
- package/dist/sharedUtils/components/common/fileInput/model.d.ts +0 -1
- package/dist/sharedUtils/components/common/index.d.ts +0 -24
- package/dist/sharedUtils/components/common/skeleton/model.d.ts +0 -21
- package/dist/sharedUtils/components/dbTypes.d.ts +0 -14
- package/dist/sharedUtils/components/diff_drawer.d.ts +0 -26
- package/dist/sharedUtils/components/ducklake.d.ts +0 -1
- package/dist/sharedUtils/components/flows/scheduleUtils.d.ts +0 -7
- package/dist/sharedUtils/components/icons/index.d.ts +0 -101
- package/dist/sharedUtils/components/random_positive_adjetive.d.ts +0 -1
- package/dist/sharedUtils/components/raw_apps/rawAppPolicy.d.ts +0 -10
- package/dist/sharedUtils/components/raw_apps/utils.d.ts +0 -15
- package/dist/sharedUtils/components/triggers/email/utils.d.ts +0 -4
- package/dist/sharedUtils/components/triggers/gcp/utils.d.ts +0 -2
- package/dist/sharedUtils/components/triggers/http/utils.d.ts +0 -11
- package/dist/sharedUtils/components/triggers/mqtt/utils.d.ts +0 -2
- package/dist/sharedUtils/components/triggers/nats/utils.d.ts +0 -2
- package/dist/sharedUtils/components/triggers/postgres/utils.d.ts +0 -8
- package/dist/sharedUtils/components/triggers/sqs/utils.d.ts +0 -2
- package/dist/sharedUtils/components/triggers/triggers.svelte.d.ts +0 -32
- package/dist/sharedUtils/components/triggers/utils.d.ts +0 -80
- package/dist/sharedUtils/components/triggers/websocket/utils.d.ts +0 -2
- package/dist/sharedUtils/components/triggers.d.ts +0 -20
- package/dist/sharedUtils/gen/core/ApiError.d.ts +0 -10
- package/dist/sharedUtils/gen/core/ApiRequestOptions.d.ts +0 -13
- package/dist/sharedUtils/gen/core/ApiResult.d.ts +0 -7
- package/dist/sharedUtils/gen/core/CancelablePromise.d.ts +0 -26
- package/dist/sharedUtils/gen/core/OpenAPI.d.ts +0 -27
- package/dist/sharedUtils/gen/core/request.d.ts +0 -29
- package/dist/sharedUtils/gen/index.d.ts +0 -6
- package/dist/sharedUtils/gen/schemas.gen.d.ts +0 -7036
- package/dist/sharedUtils/gen/services.gen.d.ts +0 -6047
- package/dist/sharedUtils/gen/types.gen.d.ts +0 -21881
- package/dist/sharedUtils/history.svelte.d.ts +0 -9
- package/dist/sharedUtils/hub.d.ts +0 -49
- package/dist/sharedUtils/jsr.json +0 -6
- package/dist/sharedUtils/lib.d.ts +0 -5
- package/dist/sharedUtils/lib.es.js +0 -1588
- package/dist/sharedUtils/package.json +0 -12
- package/dist/sharedUtils/schema.d.ts +0 -3
- package/dist/sharedUtils/stores.d.ts +0 -97
- package/dist/sharedUtils/svelte5Utils.svelte.d.ts +0 -80
- package/dist/sharedUtils/toast.d.ts +0 -8
- package/dist/sharedUtils/utils.d.ts +0 -265
- package/package/components/copilot/chat/flow/openFlowZod.js +0 -24
- /package/package/components/copilot/chat/flow/{openFlowZod.d.ts → openFlowZod.gen.d.ts} +0 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { findStepPath, parseExpandedSubflowId } from './restartFromStepPath';
|
|
2
|
+
import { emptyString } from '../utils';
|
|
3
|
+
/**
|
|
4
|
+
* Returns true when the BranchOne ancestor's path branch (the one containing the
|
|
5
|
+
* selected leaf, encoded as `branchIndex` where -1 means default and 0..N-1
|
|
6
|
+
* means `branches[i]`) is the same branch the original run took. If the user
|
|
7
|
+
* clicked a step inside an `else` branch the original run didn't take, the
|
|
8
|
+
* step never executed — restart there is impossible.
|
|
9
|
+
*
|
|
10
|
+
* Three states for the lookup:
|
|
11
|
+
* - `status === undefined`: the BranchOne isn't at the parent's top level, so
|
|
12
|
+
* it lives on a child job we don't fetch here (e.g. nested inside a ForLoop
|
|
13
|
+
* iteration). Permissive fallback — let the backend reject if needed.
|
|
14
|
+
* - `status` defined but `chosen === undefined`: the BranchOne IS at the top
|
|
15
|
+
* level but has no chosen branch (never executed / skipped / still
|
|
16
|
+
* waiting). The leaf can't have run either, so reject.
|
|
17
|
+
* - both defined: compare against `branchIndex`.
|
|
18
|
+
*/
|
|
19
|
+
function branchOneAncestorMatchesOriginal(job, ancestor) {
|
|
20
|
+
if (ancestor.type !== 'branchone')
|
|
21
|
+
return true;
|
|
22
|
+
const status = job.flow_status?.modules?.find((m) => m.id === ancestor.stepId);
|
|
23
|
+
const chosen = status?.branch_chosen;
|
|
24
|
+
if (!chosen)
|
|
25
|
+
return status === undefined;
|
|
26
|
+
const taken = chosen.type === 'default' ? -1 : (chosen.branch ?? -1);
|
|
27
|
+
return ancestor.branchIndex === taken;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reactive state shared by the run page and the editor's flow preview to drive
|
|
31
|
+
* the "restart flow at step" UI. Given the currently selected step, the
|
|
32
|
+
* completed flow job, the graph's per-module display state, and any expanded
|
|
33
|
+
* subflow definitions, derives:
|
|
34
|
+
*
|
|
35
|
+
* - `selectedJobStepIsTopLevel` — whether the selected step appears in the
|
|
36
|
+
* parent flow's top-level `flow_status.modules`
|
|
37
|
+
* - `selectedJobStepType` — `single` / `forloop` / `branchall`, used by the
|
|
38
|
+
* popup to decide which controls to render for the OUTER container
|
|
39
|
+
* - `restartBranchNames` — for top-level BranchAll steps, the labels for the
|
|
40
|
+
* branch picker
|
|
41
|
+
* - `nestedRestartTopStepId` / `nestedRestartTopBranchOrIterationN` /
|
|
42
|
+
* `nestedRestartPath` — the API request shape when restarting at a nested
|
|
43
|
+
* step (single source of truth for both call sites)
|
|
44
|
+
* - `nestedRestartSupported` — gate for showing the button when the selected
|
|
45
|
+
* step is nested (subflow expansion or BranchOne / sequential ForLoop)
|
|
46
|
+
* - `topLevelLoopIteration` — when the selected step IS a top-level ForLoop,
|
|
47
|
+
* the iteration the user is currently viewing (pre-fills the popup input)
|
|
48
|
+
* - `iterationCounts` — map of step id → number of recorded iterations,
|
|
49
|
+
* so the popup can render an iteration `<select>` matching the graph's tabs
|
|
50
|
+
* instead of a free-form number input
|
|
51
|
+
*
|
|
52
|
+
* Inputs are passed as getters so the composable stays reactive in either
|
|
53
|
+
* caller's signal graph.
|
|
54
|
+
*/
|
|
55
|
+
export function useNestedRestartState(opts) {
|
|
56
|
+
let selectedJobStepIsTopLevel = $state(undefined);
|
|
57
|
+
let selectedJobStepType = $state('single');
|
|
58
|
+
let restartBranchNames = $state([]);
|
|
59
|
+
let nestedRestartTopStepId = $state(undefined);
|
|
60
|
+
let nestedRestartTopBranchOrIterationN = $state(undefined);
|
|
61
|
+
let nestedRestartPath = $state(undefined);
|
|
62
|
+
let nestedRestartSupported = $state(false);
|
|
63
|
+
// Iteration counts keyed by the popup's field-key ('top' or 'inner-N').
|
|
64
|
+
// Built during path construction so each entry uses the *correct* graph key
|
|
65
|
+
// for its level (prefixed for in-subflow ancestors). Avoids the collision
|
|
66
|
+
// that would happen if we double-indexed `iterationCounts` by bare step_id.
|
|
67
|
+
let nestedPathIterationCounts = $state({});
|
|
68
|
+
$effect(() => {
|
|
69
|
+
const selectedJobStep = opts.selectedJobStep();
|
|
70
|
+
const job = opts.job();
|
|
71
|
+
const graphModuleStates = opts.graphModuleStates();
|
|
72
|
+
const expandedSubflows = opts.expandedSubflows?.() ?? {};
|
|
73
|
+
nestedRestartTopStepId = undefined;
|
|
74
|
+
nestedRestartTopBranchOrIterationN = undefined;
|
|
75
|
+
nestedRestartPath = undefined;
|
|
76
|
+
nestedRestartSupported = false;
|
|
77
|
+
restartBranchNames = [];
|
|
78
|
+
selectedJobStepIsTopLevel = undefined;
|
|
79
|
+
nestedPathIterationCounts = {};
|
|
80
|
+
if (selectedJobStep === undefined || job?.flow_status?.modules === undefined) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const isTopLevel = job.flow_status.modules.findIndex((m) => m.id === selectedJobStep) >= 0;
|
|
84
|
+
selectedJobStepIsTopLevel = isTopLevel;
|
|
85
|
+
const moduleDefinition = job.raw_flow?.modules.find((m) => m.id === selectedJobStep);
|
|
86
|
+
if (moduleDefinition?.value.type === 'forloopflow') {
|
|
87
|
+
selectedJobStepType = 'forloop';
|
|
88
|
+
}
|
|
89
|
+
else if (moduleDefinition?.value.type === 'branchall') {
|
|
90
|
+
selectedJobStepType = 'branchall';
|
|
91
|
+
const newNames = [];
|
|
92
|
+
moduleDefinition.value.branches.forEach((branch, idx) => {
|
|
93
|
+
newNames.push([idx, emptyString(branch.summary) ? `Branch #${idx}` : branch.summary]);
|
|
94
|
+
});
|
|
95
|
+
restartBranchNames = newNames;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
selectedJobStepType = 'single';
|
|
99
|
+
}
|
|
100
|
+
// Read from the local — reading the `$state` we just wrote would register
|
|
101
|
+
// it as a dependency of this effect, causing `effect_update_depth_exceeded`
|
|
102
|
+
// because each write would reschedule the effect.
|
|
103
|
+
if (isTopLevel)
|
|
104
|
+
return;
|
|
105
|
+
// Inline-expanded subflow: id is `subflow:outerStep:[innerSubflow:...]<leaf>`.
|
|
106
|
+
// The graph adds the `subflow:` prefix only for subflow expansions, so each
|
|
107
|
+
// segment is a `Flow{path}` step. We walk the chain and, at the deepest
|
|
108
|
+
// subflow, recurse into its cached modules to detect BranchOne / ForLoop
|
|
109
|
+
// ancestors of the leaf so the chain captures locked branches and
|
|
110
|
+
// iteration indices for those too.
|
|
111
|
+
const subflowParse = parseExpandedSubflowId(selectedJobStep);
|
|
112
|
+
if (subflowParse && job.raw_flow?.modules) {
|
|
113
|
+
const top = job.raw_flow.modules.find((m) => m.id === subflowParse.subflowSteps[0]);
|
|
114
|
+
if (top && top.value.type === 'flow') {
|
|
115
|
+
const innerPath = [];
|
|
116
|
+
// Intermediate subflow boundaries: each is itself a Flow{path}, no
|
|
117
|
+
// branch_or_iteration_n applies.
|
|
118
|
+
for (const seg of subflowParse.subflowSteps.slice(1)) {
|
|
119
|
+
innerPath.push({ step_id: seg });
|
|
120
|
+
}
|
|
121
|
+
// Look up the deepest subflow's modules in expandedSubflows. The graph
|
|
122
|
+
// stores them under the prefixed key (the graph node id used at that
|
|
123
|
+
// nesting level).
|
|
124
|
+
const deepestKey = subflowParse.subflowSteps.length === 1
|
|
125
|
+
? subflowParse.subflowSteps[0]
|
|
126
|
+
: 'subflow:' + subflowParse.subflowSteps.join(':');
|
|
127
|
+
const deepestModules = expandedSubflows[deepestKey]?.modules;
|
|
128
|
+
if (deepestModules) {
|
|
129
|
+
const path = findStepPath(deepestModules, subflowParse.leaf);
|
|
130
|
+
if (path) {
|
|
131
|
+
// Same gating as the parent-flow case, minus the BranchOne
|
|
132
|
+
// branch-mismatch check (subflow's flow_status isn't directly
|
|
133
|
+
// reachable here; backend will still reject if the branch
|
|
134
|
+
// wasn't taken). Parallel and unsupported containers are checked.
|
|
135
|
+
const blocked = path.ancestors.some((a) => a.type === 'branchall' || a.type === 'whileloopflow' || a.parallel === true);
|
|
136
|
+
if (!blocked) {
|
|
137
|
+
// Inside the subflow, the graph state for ForLoop ancestors
|
|
138
|
+
// is keyed by the prefixed graph id. Build that key here so
|
|
139
|
+
// `selectedForloopIndex` and `flow_jobs.length` lookups land
|
|
140
|
+
// on the right entry (avoiding collisions with same-step-id
|
|
141
|
+
// loops in the parent flow — e.g. parent has `e` with 4 iters
|
|
142
|
+
// and the subflow also has `e` with 1 iter).
|
|
143
|
+
const subflowPrefix = 'subflow:' + subflowParse.subflowSteps.join(':') + ':';
|
|
144
|
+
const iterationFor = (stepId) => graphModuleStates[subflowPrefix + stepId]?.selectedForloopIndex ?? 0;
|
|
145
|
+
const iterCountFor = (stepId) => graphModuleStates[subflowPrefix + stepId]?.flow_jobs?.length ?? 0;
|
|
146
|
+
const counts = {};
|
|
147
|
+
// Top-level (parent) container is the subflow step `h` —
|
|
148
|
+
// not a ForLoop, no count.
|
|
149
|
+
for (let i = 0; i < path.ancestors.length; i++) {
|
|
150
|
+
const a = path.ancestors[i];
|
|
151
|
+
const entry = { step_id: a.stepId };
|
|
152
|
+
if (a.type === 'forloopflow') {
|
|
153
|
+
entry.branch_or_iteration_n = iterationFor(a.stepId);
|
|
154
|
+
// Path key: each subflow boundary already contributes an
|
|
155
|
+
// inner-N entry above (subflow steps without iterations).
|
|
156
|
+
// Match the index that FlowRestartButton will use when
|
|
157
|
+
// rendering iteration fields.
|
|
158
|
+
counts[`inner-${innerPath.length}`] = iterCountFor(a.stepId);
|
|
159
|
+
}
|
|
160
|
+
innerPath.push(entry);
|
|
161
|
+
}
|
|
162
|
+
const leafEntry = { step_id: subflowParse.leaf };
|
|
163
|
+
if (path.target.value.type === 'forloopflow') {
|
|
164
|
+
leafEntry.branch_or_iteration_n = iterationFor(subflowParse.leaf);
|
|
165
|
+
counts[`inner-${innerPath.length}`] = iterCountFor(subflowParse.leaf);
|
|
166
|
+
}
|
|
167
|
+
innerPath.push(leafEntry);
|
|
168
|
+
nestedRestartTopStepId = top.id;
|
|
169
|
+
nestedRestartPath = innerPath;
|
|
170
|
+
nestedPathIterationCounts = counts;
|
|
171
|
+
nestedRestartSupported = true;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Fallback: subflow's modules not yet loaded (user clicked a step
|
|
177
|
+
// without expanding the subflow first), or leaf not found / blocked.
|
|
178
|
+
// Send a flat path; the backend will reject if the leaf is nested
|
|
179
|
+
// inside an unsupported container.
|
|
180
|
+
innerPath.push({ step_id: subflowParse.leaf });
|
|
181
|
+
nestedRestartTopStepId = top.id;
|
|
182
|
+
nestedRestartPath = innerPath;
|
|
183
|
+
nestedRestartSupported = true;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
// BranchOne / sequential ForLoop within the parent's own flow_value. Walk
|
|
188
|
+
// the tree to find the path; supported when ancestors are BranchOne /
|
|
189
|
+
// sequential ForLoop only — and only when each BranchOne ancestor's chosen
|
|
190
|
+
// branch in the original run actually contains the leaf, otherwise the
|
|
191
|
+
// step never executed and the backend would error.
|
|
192
|
+
if (!job.raw_flow?.modules)
|
|
193
|
+
return;
|
|
194
|
+
const path = findStepPath(job.raw_flow.modules, selectedJobStep);
|
|
195
|
+
if (!path || path.ancestors.length === 0)
|
|
196
|
+
return;
|
|
197
|
+
const blocked = path.ancestors.some((a) => a.type === 'branchall' ||
|
|
198
|
+
a.type === 'whileloopflow' ||
|
|
199
|
+
a.parallel === true ||
|
|
200
|
+
(a.type === 'branchone' && !branchOneAncestorMatchesOriginal(job, a)));
|
|
201
|
+
if (blocked)
|
|
202
|
+
return;
|
|
203
|
+
// For each ForLoop ancestor, default to the user's currently-open iteration
|
|
204
|
+
// (`selectedForloopIndex`); fall back to 0. The popup surfaces every value
|
|
205
|
+
// for confirmation/editing before submit, so we never silently send a guess.
|
|
206
|
+
const iterationFor = (stepId) => graphModuleStates[stepId]?.selectedForloopIndex ?? 0;
|
|
207
|
+
const iterCountFor = (stepId) => graphModuleStates[stepId]?.flow_jobs?.length ?? 0;
|
|
208
|
+
const top = path.ancestors[0];
|
|
209
|
+
const inner = path.ancestors.slice(1);
|
|
210
|
+
const innerPath = [];
|
|
211
|
+
const counts = {};
|
|
212
|
+
for (const a of inner) {
|
|
213
|
+
const entry = { step_id: a.stepId };
|
|
214
|
+
if (a.type === 'forloopflow') {
|
|
215
|
+
entry.branch_or_iteration_n = iterationFor(a.stepId);
|
|
216
|
+
counts[`inner-${innerPath.length}`] = iterCountFor(a.stepId);
|
|
217
|
+
}
|
|
218
|
+
innerPath.push(entry);
|
|
219
|
+
}
|
|
220
|
+
// If the SELECTED step is itself a ForLoop, include its iteration too so
|
|
221
|
+
// the popup exposes a selector for it.
|
|
222
|
+
const leafEntry = { step_id: selectedJobStep };
|
|
223
|
+
if (path.target.value.type === 'forloopflow') {
|
|
224
|
+
leafEntry.branch_or_iteration_n = iterationFor(selectedJobStep);
|
|
225
|
+
counts[`inner-${innerPath.length}`] = iterCountFor(selectedJobStep);
|
|
226
|
+
}
|
|
227
|
+
innerPath.push(leafEntry);
|
|
228
|
+
nestedRestartTopStepId = top.stepId;
|
|
229
|
+
if (top.type === 'forloopflow') {
|
|
230
|
+
nestedRestartTopBranchOrIterationN = iterationFor(top.stepId);
|
|
231
|
+
counts['top'] = iterCountFor(top.stepId);
|
|
232
|
+
}
|
|
233
|
+
nestedRestartPath = innerPath;
|
|
234
|
+
nestedPathIterationCounts = counts;
|
|
235
|
+
nestedRestartSupported = true;
|
|
236
|
+
});
|
|
237
|
+
const topLevelLoopIteration = $derived.by(() => {
|
|
238
|
+
const sel = opts.selectedJobStep();
|
|
239
|
+
if (!sel)
|
|
240
|
+
return undefined;
|
|
241
|
+
if (selectedJobStepType !== 'forloop')
|
|
242
|
+
return undefined;
|
|
243
|
+
return opts.graphModuleStates()[sel]?.selectedForloopIndex;
|
|
244
|
+
});
|
|
245
|
+
// `selectedJobStepIsTopLevel` answers "is this a top-level module structurally";
|
|
246
|
+
// `topLevelRestartable` answers "AND should the restart button show". The
|
|
247
|
+
// difference: parallel ForLoop / parallel BranchAll at the top level are
|
|
248
|
+
// rejected by the backend, so we hide the button preemptively.
|
|
249
|
+
const topLevelRestartable = $derived.by(() => {
|
|
250
|
+
if (!selectedJobStepIsTopLevel)
|
|
251
|
+
return false;
|
|
252
|
+
const sel = opts.selectedJobStep();
|
|
253
|
+
const job = opts.job();
|
|
254
|
+
if (!sel || !job?.raw_flow?.modules)
|
|
255
|
+
return false;
|
|
256
|
+
const mod = job.raw_flow.modules.find((m) => m.id === sel);
|
|
257
|
+
if (!mod)
|
|
258
|
+
return false;
|
|
259
|
+
const v = mod.value;
|
|
260
|
+
if ((v.type === 'forloopflow' || v.type === 'branchall' || v.type === 'whileloopflow') &&
|
|
261
|
+
v.parallel === true) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
return true;
|
|
265
|
+
});
|
|
266
|
+
// Iteration counts indexed by the graph module-state key (i.e. the prefixed
|
|
267
|
+
// `subflow:...:<step_id>` for in-subflow loops, or the bare `step_id` for
|
|
268
|
+
// top-level loops). Used by the popup for the SELECTED step's iteration
|
|
269
|
+
// picker — that step is always at the unprefixed top level (the run page's
|
|
270
|
+
// graph-state key matches the bare step_id), so the lookup is unambiguous.
|
|
271
|
+
// For nested-path iteration fields, see `nestedPathIterationCounts` instead,
|
|
272
|
+
// which is keyed by the popup's field-key ('top' / 'inner-N') and pulled
|
|
273
|
+
// from the path-aware graph key — avoiding collisions when the same step
|
|
274
|
+
// id appears at both the parent flow and inside a subflow.
|
|
275
|
+
const iterationCounts = $derived.by(() => {
|
|
276
|
+
const out = {};
|
|
277
|
+
for (const [id, state] of Object.entries(opts.graphModuleStates())) {
|
|
278
|
+
const n = state.flow_jobs?.length;
|
|
279
|
+
if (typeof n !== 'number' || n <= 0)
|
|
280
|
+
continue;
|
|
281
|
+
out[id] = n;
|
|
282
|
+
}
|
|
283
|
+
return out;
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
get selectedJobStepIsTopLevel() {
|
|
287
|
+
return selectedJobStepIsTopLevel;
|
|
288
|
+
},
|
|
289
|
+
get selectedJobStepType() {
|
|
290
|
+
return selectedJobStepType;
|
|
291
|
+
},
|
|
292
|
+
get restartBranchNames() {
|
|
293
|
+
return restartBranchNames;
|
|
294
|
+
},
|
|
295
|
+
get nestedRestartTopStepId() {
|
|
296
|
+
return nestedRestartTopStepId;
|
|
297
|
+
},
|
|
298
|
+
get nestedRestartTopBranchOrIterationN() {
|
|
299
|
+
return nestedRestartTopBranchOrIterationN;
|
|
300
|
+
},
|
|
301
|
+
get nestedRestartPath() {
|
|
302
|
+
return nestedRestartPath;
|
|
303
|
+
},
|
|
304
|
+
get nestedRestartSupported() {
|
|
305
|
+
return nestedRestartSupported;
|
|
306
|
+
},
|
|
307
|
+
get topLevelLoopIteration() {
|
|
308
|
+
return topLevelLoopIteration;
|
|
309
|
+
},
|
|
310
|
+
get topLevelRestartable() {
|
|
311
|
+
return topLevelRestartable;
|
|
312
|
+
},
|
|
313
|
+
get iterationCounts() {
|
|
314
|
+
return iterationCounts;
|
|
315
|
+
},
|
|
316
|
+
get nestedPathIterationCounts() {
|
|
317
|
+
return nestedPathIterationCounts;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<script lang="ts">import { onMount } from 'svelte';
|
|
2
|
+
import JSZip from 'jszip';
|
|
3
|
+
import Button from '../common/button/Button.svelte';
|
|
4
|
+
import { workspaceStore, userStore } from '../../stores';
|
|
5
|
+
import { sendUserToast } from '../../toast';
|
|
6
|
+
import { WorkspaceService } from '../../gen';
|
|
7
|
+
import { Download, Upload, RefreshCw } from 'lucide-svelte';
|
|
8
|
+
let listing = $state(undefined);
|
|
9
|
+
let loading = $state(false);
|
|
10
|
+
let uploading = $state(false);
|
|
11
|
+
let fileInput = $state(undefined);
|
|
12
|
+
const isAdmin = $derived(!!$userStore?.is_admin);
|
|
13
|
+
async function loadListing() {
|
|
14
|
+
if (!$workspaceStore)
|
|
15
|
+
return;
|
|
16
|
+
loading = true;
|
|
17
|
+
try {
|
|
18
|
+
listing = (await WorkspaceService.listSharedUi({
|
|
19
|
+
workspace: $workspaceStore
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
sendUserToast(`Failed to load shared UI: ${e}`, true);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
loading = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function humanSize(bytes) {
|
|
30
|
+
if (bytes < 1024)
|
|
31
|
+
return `${bytes} B`;
|
|
32
|
+
if (bytes < 1024 * 1024)
|
|
33
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
34
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
35
|
+
}
|
|
36
|
+
async function downloadZip() {
|
|
37
|
+
if (!$workspaceStore)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
const got = await WorkspaceService.getSharedUi({ workspace: $workspaceStore });
|
|
41
|
+
const files = got.files ?? {};
|
|
42
|
+
const zip = new JSZip();
|
|
43
|
+
for (const [path, content] of Object.entries(files)) {
|
|
44
|
+
zip.file(path, content);
|
|
45
|
+
}
|
|
46
|
+
const blob = await zip.generateAsync({ type: 'blob' });
|
|
47
|
+
const url = URL.createObjectURL(blob);
|
|
48
|
+
const a = document.createElement('a');
|
|
49
|
+
a.href = url;
|
|
50
|
+
a.download = `${$workspaceStore}-ui.zip`;
|
|
51
|
+
a.click();
|
|
52
|
+
URL.revokeObjectURL(url);
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
sendUserToast(`Failed to download shared UI: ${e}`, true);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function onFileSelected(event) {
|
|
59
|
+
const target = event.target;
|
|
60
|
+
const file = target.files?.[0];
|
|
61
|
+
if (!file || !$workspaceStore)
|
|
62
|
+
return;
|
|
63
|
+
uploading = true;
|
|
64
|
+
try {
|
|
65
|
+
const zip = await JSZip.loadAsync(await file.arrayBuffer());
|
|
66
|
+
const files = {};
|
|
67
|
+
const tasks = [];
|
|
68
|
+
zip.forEach((relativePath, entry) => {
|
|
69
|
+
if (entry.dir)
|
|
70
|
+
return;
|
|
71
|
+
tasks.push(entry.async('string').then((content) => {
|
|
72
|
+
files[relativePath] = content;
|
|
73
|
+
}));
|
|
74
|
+
});
|
|
75
|
+
await Promise.all(tasks);
|
|
76
|
+
await WorkspaceService.updateSharedUi({
|
|
77
|
+
workspace: $workspaceStore,
|
|
78
|
+
requestBody: { files }
|
|
79
|
+
});
|
|
80
|
+
sendUserToast(`Replaced shared UI folder with ${Object.keys(files).length} file(s)`);
|
|
81
|
+
await loadListing();
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
sendUserToast(`Failed to replace shared UI: ${e}`, true);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
uploading = false;
|
|
88
|
+
if (fileInput) {
|
|
89
|
+
fileInput.value = '';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
onMount(() => {
|
|
94
|
+
loadListing();
|
|
95
|
+
});
|
|
96
|
+
</script>
|
|
97
|
+
|
|
98
|
+
<div class="mb-6">
|
|
99
|
+
<h2 class="text-lg font-semibold mb-1">Shared UI folder</h2>
|
|
100
|
+
<p class="text-sm text-secondary">
|
|
101
|
+
Workspace-shared frontend files (components, styles, helpers) that the raw app bundler merges
|
|
102
|
+
under the <code>/ui/</code> path. Raw apps can import shared components with
|
|
103
|
+
<code>{`import { Button } from '/ui/Button'`}</code>.
|
|
104
|
+
</p>
|
|
105
|
+
<p class="text-sm text-secondary mt-2">
|
|
106
|
+
Editing happens through the Windmill CLI (<code>wmill sync</code> writes/reads the
|
|
107
|
+
<code>ui/</code> folder at the root of your sync directory). This page lets you inspect or
|
|
108
|
+
replace the entire folder. Changes do <strong>not</strong> retroactively rebuild deployed raw apps
|
|
109
|
+
— re-push affected raw apps to pick up updates.
|
|
110
|
+
</p>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div class="flex items-center gap-2 mb-4">
|
|
114
|
+
<Button
|
|
115
|
+
size="xs"
|
|
116
|
+
variant="default"
|
|
117
|
+
startIcon={{ icon: RefreshCw }}
|
|
118
|
+
on:click={loadListing}
|
|
119
|
+
disabled={loading}
|
|
120
|
+
>
|
|
121
|
+
Refresh
|
|
122
|
+
</Button>
|
|
123
|
+
<Button
|
|
124
|
+
size="xs"
|
|
125
|
+
variant="default"
|
|
126
|
+
startIcon={{ icon: Download }}
|
|
127
|
+
on:click={downloadZip}
|
|
128
|
+
disabled={!listing || listing.paths.length === 0}
|
|
129
|
+
>
|
|
130
|
+
Download as zip
|
|
131
|
+
</Button>
|
|
132
|
+
{#if isAdmin}
|
|
133
|
+
<Button
|
|
134
|
+
size="xs"
|
|
135
|
+
variant="default"
|
|
136
|
+
startIcon={{ icon: Upload }}
|
|
137
|
+
on:click={() => fileInput?.click()}
|
|
138
|
+
disabled={uploading}
|
|
139
|
+
>
|
|
140
|
+
{uploading ? 'Replacing…' : 'Replace from zip'}
|
|
141
|
+
</Button>
|
|
142
|
+
<input
|
|
143
|
+
bind:this={fileInput}
|
|
144
|
+
type="file"
|
|
145
|
+
accept=".zip"
|
|
146
|
+
style="display: none;"
|
|
147
|
+
onchange={onFileSelected}
|
|
148
|
+
/>
|
|
149
|
+
{/if}
|
|
150
|
+
</div>
|
|
151
|
+
|
|
152
|
+
{#if listing}
|
|
153
|
+
<div class="text-xs text-secondary mb-2">
|
|
154
|
+
Version {listing.version}{#if listing.edited_by}
|
|
155
|
+
· Last edited by <code>{listing.edited_by}</code>
|
|
156
|
+
{/if}
|
|
157
|
+
</div>
|
|
158
|
+
{#if listing.paths.length === 0}
|
|
159
|
+
<div class="rounded border border-dashed p-6 text-center text-sm text-secondary">
|
|
160
|
+
The shared UI folder is empty. Create a <code>ui/</code> directory next to your
|
|
161
|
+
<code>f/</code> and <code>u/</code> folders, add files, then run <code>wmill sync push</code>.
|
|
162
|
+
</div>
|
|
163
|
+
{:else}
|
|
164
|
+
<div class="rounded border divide-y">
|
|
165
|
+
{#each listing.paths as p (p)}
|
|
166
|
+
<div class="flex items-center justify-between px-3 py-2 text-sm font-mono">
|
|
167
|
+
<span class="truncate">{p}</span>
|
|
168
|
+
<span class="text-xs text-secondary">{humanSize(listing.sizes[p] ?? 0)}</span>
|
|
169
|
+
</div>
|
|
170
|
+
{/each}
|
|
171
|
+
</div>
|
|
172
|
+
{/if}
|
|
173
|
+
{:else if loading}
|
|
174
|
+
<div class="text-sm text-secondary">Loading…</div>
|
|
175
|
+
{/if}
|