feat: add workspace to URL via /w/<workspace_id>/ prefix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
centdix
2026-03-27 14:31:14 +01:00
co-authored by Claude Opus 4.6
parent 0885d8c986
commit 1a0c975fa5
60 changed files with 379 additions and 243 deletions
+10
View File
@@ -1,3 +1,5 @@
import type { Reroute } from '@sveltejs/kit'
/** @type {import('@sveltejs/kit').Handle} */
export async function handle({ event, resolve }) {
const response = await resolve(event, {
@@ -6,3 +8,11 @@ export async function handle({ event, resolve }) {
return response
}
export const reroute: Reroute = ({ url }) => {
const match = url.pathname.match(/^\/w\/([^/]+)(\/.*)?$/)
if (match) {
return match[2] || '/'
}
return url.pathname
}
+2 -2
View File
@@ -40,7 +40,7 @@
import MultiSelect from './select/MultiSelect.svelte'
import { safeSelectItems } from './select/utils.svelte'
import S3ArgInput from './common/fileUpload/S3ArgInput.svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { workspaceStore } from '$lib/stores'
import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte'
import AIProviderPicker from './AIProviderPicker.svelte'
@@ -568,7 +568,7 @@
{#if value && typeof value == 'string' && value?.startsWith('$res:')}
Linked to resource <a
target="_blank"
href="{base}/resources#/resource/{value.slice(5)}"
href="{$wsBase}/resources#/resource/{value.slice(5)}"
class="text-blue-500 underline"
>{value.slice(5)} <span class="inline-block -mb-0.5"><ExternalLink size={14} /></span></a
>
@@ -57,7 +57,7 @@
import type { TriggerKind } from './triggers'
import { triggerDisplayNamesMap, triggerKindToTriggerType } from './triggers/utils'
import { getEmailAddress, getEmailDomain } from './triggers/email/utils'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
@@ -683,7 +683,7 @@
function getTriggerHref(triggerKind: TriggerKind): string | undefined {
const pagePath = triggerKindToPagePath[triggerKind]
return pagePath ? `${base}${pagePath}` : undefined
return pagePath ? `${$wsBase}${pagePath}` : undefined
}
// Fetch triggers when workspace is available
@@ -1,6 +1,6 @@
<script lang="ts">
import { createEventDispatcher, untrack } from 'svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { enterpriseLicense, superadmin, workspaceStore } from '$lib/stores'
import {
AppService,
@@ -422,7 +422,7 @@
>
{:else}
<Alert type="info" title="Shareable page"
>Share this <a href="{base}/deploy/{kind}/{initialPath}">link</a> to have another properly permissioned
>Share this <a href="{$wsBase}/deploy/{kind}/{initialPath}">link</a> to have another properly permissioned
user do the deployment</Alert
>
+4 -4
View File
@@ -17,7 +17,7 @@
import { ResourceService, VariableService, WorkspaceService, type Script } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import type Editor from './Editor.svelte'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
@@ -681,7 +681,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
startIcon={{ icon: Plus }}
target="_blank"
variant="accent"
href="{base}/resources?connect_app=undefined"
href="{$wsBase}/resources?connect_app=undefined"
>
Add resource
</Button>
@@ -739,7 +739,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
startIcon={{ icon: Settings }}
target="_blank"
variant="accent"
href="{base}/workspace_settings?tab=windmill_lfs"
href="{$wsBase}/workspace_settings?tab=windmill_lfs"
>
Go to settings
</Button>
@@ -782,7 +782,7 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
startIcon={{ icon: Settings }}
target="_blank"
variant="accent"
href="{base}/workspace_settings?tab=windmill_data_tables"
href="{$wsBase}/workspace_settings?tab=windmill_data_tables"
>
Go to settings
</Button>
@@ -30,7 +30,7 @@
import ChannelSelector from '$lib/components/ChannelSelector.svelte'
import type { Schema, SupportedLanguage } from '$lib/common'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { enterpriseLicense, workspaceStore } from '$lib/stores'
import MsTeamsIcon from '$lib/components/icons/MSTeamsIcon.svelte'
import { classNames, emptySchema, emptyString, sendUserToast, tryEvery } from '$lib/utils'
@@ -493,7 +493,7 @@
<a
target="_blank"
href={`${base}/run/${connectionTestJob.uuid}?workspace=${$workspaceStore}`}
href={`${$wsBase}/run/${connectionTestJob.uuid}`}
class="inline-flex items-center gap-1"
>
{connectionTestJob.uuid}
@@ -585,7 +585,7 @@
Message sent via Windmill job
<a
target="_blank"
href={`${base}/run/${connectionTestJob.uuid}?workspace=${$workspaceStore}`}
href={`${$wsBase}/run/${connectionTestJob.uuid}`}
>
{connectionTestJob.uuid}
</a>
@@ -10,8 +10,7 @@
ExternalLink,
Keyboard
} from 'lucide-svelte'
import { base } from '$lib/base'
import { workspaceStore } from '$lib/stores'
import { wsBase } from '$lib/workspaceUrl'
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
import LogViewer from './LogViewer.svelte'
import FlowLogViewer from './FlowLogViewer.svelte'
@@ -98,7 +97,7 @@
function getJobLink(jobId: string | undefined): string {
if (!jobId) return ''
return `${base}/run/${jobId}?workspace=${workspaceId ?? $workspaceStore}`
return `${$wsBase}/run/${jobId}`
}
function getStatusColor(status: FlowStatusModule['type'] | undefined): string {
@@ -1,11 +1,10 @@
<script lang="ts">
import { type Job } from '$lib/gen'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import JobStatus from '$lib/components/JobStatus.svelte'
import { displayDate, truncateRev } from '$lib/utils'
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
import TimeAgo from './TimeAgo.svelte'
import { workspaceStore } from '$lib/stores'
import Tooltip from './Tooltip.svelte'
import { Clock, MemoryStick, Calendar, Bot, User, Code2 } from 'lucide-svelte'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
@@ -57,7 +56,7 @@
<BarsStaggered size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
Step of flow
<a href={`${base}/run/${job.parent_job}?workspace=${$workspaceStore}`}>
<a href={`${$wsBase}/run/${job.parent_job}`}>
{truncateRev(job.parent_job, 18)}
</a>
</span>
@@ -67,7 +66,7 @@
<Bot size={SMALL_ICON_SIZE} class="min-w-3.5" />
<span class="whitespace-nowrap">
Triggered by parent
<a href={`${base}/run/${job.parent_job}?workspace=${$workspaceStore}`}>
<a href={`${$wsBase}/run/${job.parent_job}`}>
{job.parent_job}</a
>
</span>
@@ -94,7 +93,7 @@
{#if (job && job.job_kind == 'flow') || job?.job_kind == 'script'}
{@const stem = `${job?.job_kind}s`}
{@const isScript = job?.job_kind === 'script'}
{@const viewHref = `${base}/${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
{@const viewHref = `${$wsBase}/${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
<div class="flex flex-row gap-2 items-center">
{#if isScript}
<Code2 size={SMALL_ICON_SIZE} class="min-w-3.5" />
@@ -125,6 +124,6 @@
</div>
<div class="text-secondary text-2xs pt-2 whitespace-nowrap">
run id:
<a href={`${base}/run/${job.id}?workspace=${job.workspace_id}`}> {job.id} </a>
<a href={`${$wsBase}/run/${job.id}`}> {job.id} </a>
</div>
</div>
@@ -15,7 +15,7 @@
type FlowValue
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import FlowJobResult from './FlowJobResult.svelte'
import WorkflowTimeline from './WorkflowTimeline.svelte'
import DisplayResult from './DisplayResult.svelte'
@@ -2058,8 +2058,8 @@
class="text-right text-xs"
rel="noreferrer"
target="_blank"
href="{base}/run/{node.job_id ??
''}?workspace={job?.workspace_id}"
href="{$wsBase}/run/{node.job_id ??
''}"
>
{truncateRev(node.job_id ?? '', 10)}
<ExternalLink size={12} class="inline-block" />
+5 -1
View File
@@ -13,6 +13,7 @@
import { usersWorkspaceStore, workspaceStore, userStore } from '$lib/stores'
import { classNames, emptyString, escapeHtml, parseQueryParams } from '$lib/utils'
import { base } from '$lib/base'
import { extractWorkspaceFromPath } from '$lib/workspaceUrl'
import { getUserExt } from '$lib/user'
import { sendUserToast } from '$lib/toast'
import { isCloudHosted } from '$lib/cloud'
@@ -140,7 +141,10 @@
if ($workspaceStore) {
goto(rd ?? '/')
} else {
let workspaceTarget = parseQueryParams(rd ?? undefined)['workspace']
// Extract workspace from rd path (/w/<ws>/...) or query param (?workspace=X)
let workspaceTarget =
extractWorkspaceFromPath(rd ?? '') ??
parseQueryParams(rd ?? undefined)['workspace']
if (rd && workspaceTarget) {
$workspaceStore = workspaceTarget
goto(rd)
@@ -4,7 +4,7 @@
import DrawerContent from './common/drawer/DrawerContent.svelte'
import { createEventDispatcher, onDestroy } from 'svelte'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { displayDate, sleep, sendUserToast } from '$lib/utils'
import TableCustom from './TableCustom.svelte'
import { Hourglass, Loader2, Play, RefreshCw } from 'lucide-svelte'
@@ -139,7 +139,7 @@
<td class="text-xs">
<a
class="pr-3"
href="{base}/scripts/get/{scriptHash}?workspace={$workspaceStore}"
href="{$wsBase}/scripts/get/{scriptHash}"
target="_blank"
>
{scriptHash}
@@ -148,7 +148,7 @@
<td class="text-xs">
<a
class="pr-3"
href="{base}/run/{jobId}?workspace={$workspaceStore}"
href="{$wsBase}/run/{jobId}"
target="_blank">{jobId.substring(24)}</a
>
</td>
@@ -30,6 +30,7 @@
type MoveS3FileResponse
} from '$lib/gen'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import {
displayDate,
displaySize,
@@ -555,7 +556,7 @@
<p class="text-clip grow min-w-0">
The workspace needs to be connected to an S3 storage to use this feature. You can <a
target="_blank"
href="{base}/workspace_settings?tab=windmill_lfs">configure it here</a
href="{$wsBase}/workspace_settings?tab=windmill_lfs">configure it here</a
>.
</p>
<Button variant="default" on:click={reloadContent} startIcon={{ icon: RotateCw }} />
@@ -80,7 +80,7 @@
import CaptureButton from './triggers/CaptureButton.svelte'
import { setContext } from 'svelte'
import HideButton from './apps/editor/settingsPanel/HideButton.svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { SUPPORTED_CHAT_SCRIPT_LANGUAGES } from './copilot/chat/script/core'
import { getStringError } from './copilot/chat/utils'
import type { ScriptOptions } from './copilot/chat/ContextManager.svelte'
@@ -1943,7 +1943,7 @@
{#snippet popoverOverride()}
<div class="text-sm">
Enable Windmill AI in the <a
href="{base}/workspace_settings?tab=ai"
href="{$wsBase}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex flex-row items-center gap-1"
>
@@ -2,7 +2,7 @@
import { ScriptService, FlowService, type Script, AppService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { createEventDispatcher, untrack } from 'svelte'
import Select from './select/Select.svelte'
@@ -158,7 +158,7 @@
target="_blank"
variant="default"
size="xs"
href="{base}/flows/edit/{scriptPath}">Edit</Button
href="{$wsBase}/flows/edit/{scriptPath}">Edit</Button
>
{/if}
{#if allowView}
@@ -181,7 +181,7 @@
target="_blank"
variant="default"
size="xs"
href="{base}/apps/edit/{scriptPath}"
href="{$wsBase}/apps/edit/{scriptPath}"
>
Edit
</Button>
@@ -192,7 +192,7 @@
size="xs"
target="_blank"
startIcon={{ icon: Code }}
href="{base}/apps/get/{scriptPath}"
href="{$wsBase}/apps/get/{scriptPath}"
>
View
</Button>
@@ -206,7 +206,7 @@
target="_blank"
variant="default"
size="xs"
href="{base}/scripts/edit/{scriptPath}"
href="{$wsBase}/scripts/edit/{scriptPath}"
>
Edit
</Button>
@@ -7,6 +7,7 @@
import { Button } from '$lib/components/common'
import { sendUserToast } from '$lib/toast'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import SearchItems from './SearchItems.svelte'
import { page } from '$app/state'
import { goto as gotoUrl } from '$app/navigation'
@@ -486,7 +487,7 @@
{/key}
{#if role_source === 'instance_group' && (super_admin || devops)}
<a
href="{base}/groups"
href="{$wsBase}/groups"
class="text-2xs text-tertiary mt-0.5 ml-1 hover:underline"
title="Role set by instance group. You can upgrade to a higher role manually, but demoting to &quot;User&quot; requires removing them from the group."
onclick={() => closeDrawer?.()}
@@ -1,6 +1,6 @@
<script lang="ts">
import { msToSec } from '$lib/utils'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { ExternalLink } from 'lucide-svelte'
import Popover from './Popover.svelte'
@@ -46,7 +46,7 @@
: 'rounded-sm'} center-center text-white text-2xs whitespace-nowrap hover:outline outline-1 outline-black"
>
{#snippet text()}
<a href="{base}/run/{id}" class="inline-flex items-center gap-1" target="_blank"
<a href="{$wsBase}/run/{id}" class="inline-flex items-center gap-1" target="_blank"
>{id} <ExternalLink size={14} /></a
>
{/snippet}
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { displayDate, msToSec, emptyString } from '$lib/utils'
import { onDestroy } from 'svelte'
import { getDbClockNow } from '$lib/forLater'
@@ -307,7 +307,7 @@
<a
target="_blank"
class="inline-flex gap-2 items-baseline hover:underline whitespace-nowrap"
href="{base}/run/{k}"
href="{$wsBase}/run/{k}"
onclick={(e) => e.stopPropagation()}
>
{v.name ?? k}
@@ -15,7 +15,7 @@
WorkspaceService
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { emptyString, formatCron, sendUserToast, tryEvery } from '$lib/utils'
import SchemaForm from '$lib/components/SchemaForm.svelte'
import Button from '$lib/components/common/button/Button.svelte'
@@ -644,7 +644,7 @@
<p class="text-clip grow min-w-0">
The workspace needs to be connected to Slack to use this feature. You can <a
target="_blank"
href="{base}/workspace_settings?tab=slack">configure it here</a
href="{$wsBase}/workspace_settings?tab=slack">configure it here</a
>.
</p>
<Button
@@ -3,7 +3,7 @@
const bubble = createBubbler()
import { Button, Drawer, DrawerContent } from '$lib/components/common'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import FlowModuleScript from '$lib/components/flows/content/FlowModuleScript.svelte'
import FlowPathViewer from '$lib/components/flows/content/FlowPathViewer.svelte'
import { emptySchema, sendUserToast } from '$lib/utils'
@@ -259,7 +259,7 @@
startIcon={{ icon: Eye }}
endIcon={{ icon: ExternalLink }}
target="_blank"
href="{base}/flows/get/{runnable.path}?workspace={$workspaceStore}"
href="{$wsBase}/flows/get/{runnable.path}"
>
Details
</Button>
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import Dropdown from '$lib/components/DropdownV2.svelte'
import type MoveDrawer from '$lib/components/MoveDrawer.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
@@ -78,7 +78,7 @@
{/if}
<Row
href="{base}/apps{app.raw_app ? '_raw' : ''}/get/{app.path}"
href="{$wsBase}/apps{app.raw_app ? '_raw' : ''}/get/{app.path}"
kind="app"
{marked}
path={app.path}
@@ -109,7 +109,7 @@
variant="subtle"
wrapperClasses="w-20"
startIcon={{ icon: Pen }}
href="{base}/apps{app.raw_app ? '_raw' : ''}/edit/{app.path}?nodraft=true"
href="{$wsBase}/apps{app.raw_app ? '_raw' : ''}/edit/{app.path}?nodraft=true"
>
Edit
</Button>
@@ -172,7 +172,7 @@
{
displayName: 'Duplicate/Fork',
icon: GitFork,
href: `${base}/apps${app.raw_app ? '_raw' : ''}/add?template=${path}`,
href: `${$wsBase}/apps${app.raw_app ? '_raw' : ''}/add?template=${path}`,
disabled: !showEditButton,
hide: $userStore?.operator
},
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import Dropdown from '$lib/components/DropdownV2.svelte'
import type MoveDrawer from '$lib/components/MoveDrawer.svelte'
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
@@ -102,8 +102,8 @@
aiId={`flow-row-${flow.path}`}
aiDescription={`Button to access the form to run the flow ${flow.summary ?? flow.path}`}
href={flow.draft_only
? `${base}/flows/edit/${flow.path}?nodraft=true`
: `${base}/flows/get/${flow.path}?workspace=${$workspaceStore}`}
? `${$wsBase}/flows/edit/${flow.path}?nodraft=true`
: `${$wsBase}/flows/get/${flow.path}`}
kind="flow"
workspaceId={flow.workspace_id ?? $workspaceStore ?? ''}
{marked}
@@ -131,7 +131,7 @@
wrapperClasses="w-20"
unifiedSize="md"
startIcon={{ icon: Pen }}
href="{base}/flows/edit/{flow.path}?nodraft=true"
href="{$wsBase}/flows/edit/{flow.path}?nodraft=true"
aiId={`edit-flow-button-${flow.summary?.length > 0 ? flow.summary : flow.path}`}
aiDescription={`Edits the flow ${flow.summary?.length > 0 ? flow.summary : flow.path}`}
>
@@ -187,12 +187,12 @@
{
displayName: 'View runs',
icon: List,
href: `${base}/runs/${path}`
href: `${$wsBase}/runs/${path}`
},
{
displayName: 'Duplicate/Fork',
icon: GitFork,
href: `${base}/flows/add?template=${path}`,
href: `${$wsBase}/flows/add?template=${path}`,
disabled: !showEditButton,
hide: $userStore?.operator
},
@@ -205,7 +205,7 @@
{
displayName: 'Audit logs',
icon: Eye,
href: `${base}/audit_logs?resource=${path}`,
href: `${$wsBase}/audit_logs?resource=${path}`,
hide: $userStore?.operator
},
{
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import Dropdown from '$lib/components/DropdownV2.svelte'
import SharedBadge from '$lib/components/SharedBadge.svelte'
import type ShareModal from '$lib/components/ShareModal.svelte'
@@ -31,7 +31,7 @@
</script>
<Row
href="{base}/apps/get_raw/{app.version}/{app.path}"
href="{$wsBase}/apps/get_raw/{app.version}/{app.path}"
kind="raw_app"
{marked}
path={app.path}
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import Dropdown from '$lib/components/DropdownV2.svelte'
import type MoveDrawer from '$lib/components/MoveDrawer.svelte'
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
@@ -120,8 +120,8 @@
aiId={`script-run-button-${script.path}`}
aiDescription={`Button to access the form to run the script ${script.summary ?? script.path}`}
href={script.draft_only || (script.auto_kind === 'lib' && script.kind !== 'preprocessor')
? `${base}/scripts/edit/${script.path}`
: `${base}/scripts/get/${script.hash}?workspace=${$workspaceStore}`}
? `${$wsBase}/scripts/edit/${script.path}`
: `${$wsBase}/scripts/get/${script.hash}`}
kind="script"
{marked}
path={script.path}
@@ -182,7 +182,7 @@
wrapperClasses="w-20"
unifiedSize="md"
startIcon={{ icon: Pen }}
href="{base}/scripts/edit/{script.path}"
href="{$wsBase}/scripts/edit/{script.path}"
>
Edit
</Button>
@@ -264,7 +264,7 @@
{
displayName: 'Duplicate/Fork',
icon: GitFork,
href: `${base}/scripts/add?template=${script.path}`,
href: `${$wsBase}/scripts/add?template=${script.path}`,
disabled: !showEditButton,
hide: $userStore?.operator
},
@@ -299,7 +299,7 @@
{
displayName: 'View runs',
icon: List,
href: `${base}/runs/${script.path}`
href: `${$wsBase}/runs/${script.path}`
},
{
displayName: 'Versions',
@@ -311,7 +311,7 @@
{
displayName: 'Audit logs',
icon: Eye,
href: `${base}/audit_logs?resource=${script.path}`,
href: `${$wsBase}/audit_logs?resource=${script.path}`,
hide: $userStore?.operator
},
{
@@ -431,7 +431,7 @@
openDetails
on:openDetails={(e) => {
if (script) {
goto(`/scripts/get/${e.detail.version}?workspace=${$workspaceStore}`)
goto(`/scripts/get/${e.detail.version}`)
}
versionsDrawerOpen = false
}}
@@ -1,6 +1,5 @@
<script lang="ts">
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import { Button } from '$lib/components/common'
import Drawer from '$lib/components/common/drawer/Drawer.svelte'
@@ -40,12 +39,12 @@
function selectLowCode() {
appTypeModalOpen = false
goto(`${base}/apps/add?nodraft=true`)
goto('/apps/add?nodraft=true')
}
function selectFullCode() {
appTypeModalOpen = false
goto(`${base}/apps_raw/add?nodraft=true`)
goto('/apps_raw/add?nodraft=true')
}
</script>
@@ -1,6 +1,5 @@
<script lang="ts">
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import BarsStaggered from '$lib/components/icons/BarsStaggered.svelte'
import { Button } from '$lib/components/common'
@@ -41,13 +40,13 @@
const parsed =
wacImportType === 'yaml' ? YAML.parse(pendingWacRaw ?? '') : JSON.parse(pendingWacRaw ?? '')
$importScriptStore = parsed
await goto(`${base}/scripts/add?import=true`)
await goto('/scripts/add?import=true')
wacDrawer?.closeDrawer?.()
}
function handleFlowClick() {
if (skipModal) {
goto(`${base}/flows/add?nodraft=true`)
goto('/flows/add?nodraft=true')
} else {
flowModalOpen = true
}
@@ -55,17 +54,17 @@
function selectFlowEditor() {
flowModalOpen = false
goto(`${base}/flows/add?nodraft=true`)
goto('/flows/add?nodraft=true')
}
function selectWacPython() {
flowModalOpen = false
goto(`${base}/scripts/add?nodraft=true&wac=python`)
goto('/scripts/add?nodraft=true&wac=python')
}
function selectWacTypescript() {
flowModalOpen = false
goto(`${base}/scripts/add?nodraft=true&wac=typescript`)
goto('/scripts/add?nodraft=true&wac=typescript')
}
function toggleSkipModal() {
@@ -1,7 +1,7 @@
<script lang="ts">
import type { Job } from '$lib/gen'
import { twMerge } from 'tailwind-merge'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { ExternalLink } from 'lucide-svelte'
import { emptyString } from '$lib/utils'
@@ -42,7 +42,7 @@
{#if !emptyString(job.id)}
<a
target="_blank"
href="{base}/run/{job.id}?workspace={job.workspace_id}"
href="{$wsBase}/run/{job.id}"
class="text-right float-right text-gray-300 group-hover:text-gray-400 transition-all duration-200 dark:text-gray-500 dark:group-hover:text-gray-300"
title="See run detail in a new tab"
>
@@ -1,7 +1,7 @@
<script lang="ts">
import { Button } from '$lib/components/common'
import { GraduationCap, X } from 'lucide-svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { goto } from '$app/navigation'
import { sendUserToast, type ToastAction } from '$lib/toast'
import { getLocalSetting, storeLocalSetting } from '$lib/utils'
@@ -115,7 +115,7 @@
}
function goToTutorials() {
goto(`${base}/tutorials`)
goto(`${$wsBase}/tutorials`)
}
</script>
@@ -1,6 +1,6 @@
<script lang="ts">
import type { CompletedJob, QueuedJob } from '$lib/gen'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import JobStatus from '../JobStatus.svelte'
import { ExternalLinkIcon } from 'lucide-svelte'
@@ -32,7 +32,7 @@
<a
rel="noreferrer"
target="_blank"
href="{base}/run/{job?.id}?workspace={job?.workspace_id}"
href="{$wsBase}/run/{job?.id}"
>
{truncateRev(job?.id, 8)}
<ExternalLinkIcon size={14} class="inline mb-1 ml-1" />
@@ -1,6 +1,6 @@
<script lang="ts">
import { type Job } from '$lib/gen'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { displayDate, truncateRev, truncateHash } from '$lib/utils'
import ScheduleEditor from '$lib/components/triggers/schedules/ScheduleEditor.svelte'
import TimeAgo from '$lib/components/TimeAgo.svelte'
@@ -189,14 +189,14 @@
<Tooltip>
{#snippet text()}
This job was run on worker:
<a href={`${base}/runs/?job_kinds=all&worker=${job?.worker}`}>
<a href={`${$wsBase}/runs/?job_kinds=all&worker=${job?.worker}`}>
{job?.worker}
</a>
<br />
<WorkerHostname worker={job.worker!} minTs={job?.['created_at']} />
{/snippet}
<a
href={`${base}/runs/?job_kinds=all&worker=${job?.worker}`}
href={`${$wsBase}/runs/?job_kinds=all&worker=${job?.worker}`}
class="flex items-center gap-1 text-primary"
>
<span class="truncate flex-shrink min-w-0">{displayValue}</span>
@@ -231,7 +231,7 @@
Triggered by
{/if}
<a
href={`${base}/run/${job.parent_job}?workspace=${$workspaceStore}`}
href={`${$wsBase}/run/${job.parent_job}`}
class="flex items-center gap-1"
>
{displayValue}
@@ -249,7 +249,7 @@
icon: Link,
action: () =>
navigator.clipboard.writeText(
`${window.location.origin}${base}/run/${job.id}?workspace=${job.workspace_id}`
`${window.location.origin}${$wsBase}/run/${job.id}`
)
},
{
@@ -300,7 +300,7 @@
<span class="text-secondary flex-shrink-0">Job ID</span>
<span class="text-primary">
<a
href={`${base}/run/${job.id}?workspace=${job.workspace_id}`}
href={`${$wsBase}/run/${job.id}`}
class="flex items-center gap-1 min-w-0"
>
<span class="truncate flex-shrink min-w-0">{truncateRev(job.id, 8)}</span>
@@ -388,7 +388,7 @@
{#if job.script_path && (job.job_kind === 'script' || job.job_kind === 'flow' || job.job_kind === 'singlestepflow')}
{@const stem = job.job_kind === 'script' ? 'scripts' : 'flows'}
{@const isScript = job.job_kind === 'script'}
{@const viewHref = `${base}/${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
{@const viewHref = `${$wsBase}/${stem}/get/${isScript ? job?.script_hash : job?.script_path}`}
<a
href={viewHref}
class="text-emphasis {compact
@@ -479,7 +479,7 @@
<span class="text-secondary flex-shrink-0">Job ID</span>
<span class="text-primary">
<a
href={`${base}/run/${job.id}?workspace=${job.workspace_id}`}
href={`${$wsBase}/run/${job.id}`}
class="flex items-center gap-1 min-w-0"
>
<span class="truncate flex-shrink min-w-0">{truncateRev(job.id, 8)}</span>
@@ -2,9 +2,8 @@
import Tooltip from '$lib/components/meltComponents/Tooltip.svelte'
import PreprocessedArgsDisplay from '$lib/components/runs/PreprocessedArgsDisplay.svelte'
import { truncateHash } from '$lib/utils'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { truncateRev } from '$lib/utils'
import { workspaceStore } from '$lib/stores'
import Badge from '$lib/components/common/badge/Badge.svelte'
import Button from '$lib/components/common/button/Button.svelte'
import { ListFilter } from 'lucide-svelte'
@@ -33,7 +32,7 @@
{#if job.script_hash && showScriptHash && job.job_kind !== 'aiagent'}
{#if job.job_kind == 'script'}
<a href="{base}/scripts/get/{job.script_hash}?workspace={$workspaceStore}"
<a href="{$wsBase}/scripts/get/{job.script_hash}"
><Badge color="gray" title={`Script hash: ${job.script_hash}`} {large}
>{truncateHash(job.script_hash)}
</Badge></a
@@ -104,7 +103,7 @@
</Button>
{:else}
<a
href={`${base}/runs/?job_kinds=all&graph=ConcurrencyChart&concurrency_key=${concurrencyKey}`}
href={`${$wsBase}/runs/?job_kinds=all&graph=ConcurrencyChart&concurrency_key=${concurrencyKey}`}
>
{concurrencyKey}
</a>
@@ -116,7 +115,7 @@
</button>
{:else}
<a
href={`${base}/runs/?job_kinds=all&graph=ConcurrencyChart&concurrency_key=${concurrencyKey}`}
href={`${$wsBase}/runs/?job_kinds=all&graph=ConcurrencyChart&concurrency_key=${concurrencyKey}`}
>
<Badge {large}>Concurrency: {truncateRev(concurrencyKey, 20)}</Badge>
</a>
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { goto } from '$lib/navigation'
import type { Job } from '$lib/gen'
import {
@@ -162,12 +162,12 @@
{/if}
{#if job && job.is_flow_step && job.parent_job}
<br /> Step of flow
<a href={`${base}/run/${job.parent_job}?workspace=${job.workspace_id}`}>
<a href={`${$wsBase}/run/${job.parent_job}`}>
{truncateRev(job.parent_job, 10)}
</a>
{:else if job && job.parent_job}
<br /> Parent
<a href={`${base}/run/${job.parent_job}?workspace=${job.workspace_id}`}>
<a href={`${$wsBase}/run/${job.parent_job}`}>
{truncateRev(job.parent_job, 10)}
</a>
{/if}
@@ -318,7 +318,7 @@
<div class="flex justify-end pr-2">
<a
target="_blank"
href="{base}/run/{job.id}?workspace={job.workspace_id}"
href="{$wsBase}/run/{job.id}"
class={twMerge(
'text-right float-right px-2',
selected ? 'text-accent' : 'text-secondary hover:text-accent'
@@ -1,8 +1,7 @@
<script lang="ts">
import SchemaPickerRow from './SchemaPickerRow.svelte'
import { workspaceStore } from '$lib/stores'
import { ExternalLink } from 'lucide-svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
interface Props {
job: any
@@ -60,7 +59,7 @@
<div class="center-center">
<a
target="_blank"
href="{base}/run/{job.id}?workspace={$workspaceStore}"
href="{$wsBase}/run/{job.id}"
class="text-right float-right text-secondary px-2"
title="See run detail in a new tab"
>
@@ -5,7 +5,7 @@
import { workspaceStore } from '$lib/stores'
import { createEventDispatcher, untrack } from 'svelte'
import { ExternalLink } from 'lucide-svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { Cell, Row } from '$lib/components/table'
import { twMerge } from 'tailwind-merge'
@@ -75,7 +75,7 @@
<div class="center-center {hovering ? '' : '!hidden'}">
<a
target="_blank"
href="{base}/run/{job.id}?workspace={$workspaceStore}"
href="{$wsBase}/run/{job.id}"
class="text-right float-right text-secondary"
title="See run detail in a new tab"
>
@@ -10,7 +10,7 @@
type WorkflowStatus
} from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { displayDate } from '$lib/utils'
import Tabs from '../common/tabs/Tabs.svelte'
import Tab from '../common/tabs/Tab.svelte'
@@ -244,7 +244,7 @@
<Cell first>
<a
class="pr-3"
href="{base}/run/{id}?workspace={workspace ?? $workspaceStore}"
href="{$wsBase}/run/{id}"
target="_blank">{id.substring(30)}</a
>
</Cell>
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { Code2, Plus } from 'lucide-svelte'
import { Button } from '$lib/components/common'
@@ -14,7 +14,7 @@
unifiedSize="lg"
variant="accent"
startIcon={{ icon: Plus }}
href="{base}/scripts/add"
href="{$wsBase}/scripts/add"
endIcon={{ icon: Code2 }}
>
Script
@@ -32,7 +32,8 @@
import { twMerge } from 'tailwind-merge'
import ContentSearchInner from '../ContentSearchInner.svelte'
import { goto } from '$app/navigation'
import { goto } from '$lib/navigation'
import { toWorkspacePath } from '$lib/workspaceUrl'
import QuickMenuItem from '../search/QuickMenuItem.svelte'
import { devopsRole, enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import uFuzzy from '@leeoniya/ufuzzy'
@@ -420,7 +421,7 @@
open = false
goto(path)
} else {
window.open(path, '_blank')
window.open(toWorkspacePath(path), '_blank')
}
}
@@ -19,6 +19,7 @@
import Toggle from '$lib/components/Toggle.svelte'
import { BellOff, Bell, ExternalLink, Settings } from 'lucide-svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import Notification from '$lib/components/common/alert/Notification.svelte'
interface Props {
@@ -277,7 +278,7 @@
<Button
size="xs"
color="light"
href="{base}/workspace_settings?tab=error_handler"
href="{$wsBase}/workspace_settings?tab=error_handler"
target="_blank"
>
Workspace Critical Alert Settings <ExternalLink size="16" />
@@ -290,7 +291,7 @@
<Button
size="xs"
variant="default"
href="{base}/workspace_settings?tab=error_handler"
href="{$wsBase}/workspace_settings?tab=error_handler"
target="_blank"
>
<List horizontal justify="between" gap="sm">
@@ -1,15 +1,18 @@
<script module lang="ts">
import { getWsBase } from '$lib/workspaceUrl'
export type FavoriteKind = Exclude<
NonNullable<StarData['requestBody']>['favorite_kind'],
undefined
>
export function getFavoriteHref(path: string, kind: FavoriteKind) {
const wsPrefix = getWsBase()
return {
script: `/scripts/get/${path}`,
flow: `/flows/get/${path}`,
app: `/apps/get/${path}`,
raw_app: `/apps_raw/get/${path}`,
script: `${wsPrefix}/scripts/get/${path}`,
flow: `${wsPrefix}/flows/get/${path}`,
app: `${wsPrefix}/apps/get/${path}`,
raw_app: `${wsPrefix}/apps_raw/get/${path}`,
asset: '#'
}[kind]
}
@@ -3,7 +3,6 @@
import type { MenubarMenuElements } from '@melt-ui/svelte'
import { navigating, page } from '$app/stores'
import Popover from '../Popover.svelte'
import { base } from '$app/paths'
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
import { goto } from '$app/navigation'
import { twMerge } from 'tailwind-merge'
@@ -40,7 +39,7 @@
let isSelected = $state(false)
navigating.subscribe(() => {
if (href === `${base}/`) {
if (href.endsWith('/')) {
isSelected = $page.url.pathname === href
} else {
isSelected = $page.url.pathname.startsWith(href)
@@ -16,6 +16,7 @@
Table2
} from 'lucide-svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import MultiplayerMenu from './MultiplayerMenu.svelte'
import {
@@ -52,16 +53,16 @@
let mainMenuLinks = $derived(
[
{ label: 'Home', id: 'home', href: `${base}/`, icon: Home },
{ label: 'Runs', id: 'runs', href: `${base}/runs`, icon: Play },
{ label: 'Schedules', id: 'schedules', href: `${base}/schedules`, icon: Calendar },
{ label: 'Home', id: 'home', href: `${$wsBase}/`, icon: Home },
{ label: 'Runs', id: 'runs', href: `${$wsBase}/runs`, icon: Play },
{ label: 'Schedules', id: 'schedules', href: `${$wsBase}/schedules`, icon: Calendar },
// Add Tutorials to main menu only if not all completed and not skipped
...($tutorialsToDo.length > 0 && !$skippedAll
? [
{
label: 'Tutorials',
id: 'tutorials',
href: `${base}/tutorials`,
href: `${$wsBase}/tutorials`,
icon: GraduationCap
}
]
@@ -88,32 +89,32 @@
{
label: 'Resources',
id: 'resources',
href: `${base}/resources`
href: `${$wsBase}/resources`
},
{
label: 'Variables',
id: 'variables',
href: `${base}/variables`
href: `${$wsBase}/variables`
},
{
label: 'Assets',
id: 'assets',
href: `${base}/assets`
href: `${$wsBase}/assets`
},
{
label: 'Groups',
id: 'groups',
href: `${base}/groups`
href: `${$wsBase}/groups`
},
{
label: 'Folders',
id: 'folders',
href: `${base}/folders`
href: `${$wsBase}/folders`
},
{
label: 'Workers',
id: 'workers',
href: `${base}/workers`
href: `${$wsBase}/workers`
}
].filter(filterLink)
)
@@ -122,52 +123,52 @@
{
label: 'Custom HTTP routes',
id: 'triggers',
href: `${base}/routes`
href: `${$wsBase}/routes`
},
{
label: 'Websocket triggers',
id: 'triggers',
href: `${base}/websocket_triggers`
href: `${$wsBase}/websocket_triggers`
},
{
label: 'Postgres triggers',
id: 'triggers',
href: `${base}/postgres_triggers`
href: `${$wsBase}/postgres_triggers`
},
{
label: 'Kafka triggers',
id: 'triggers',
href: `${base}/kafka_triggers`
href: `${$wsBase}/kafka_triggers`
},
{
label: 'NATS triggers',
id: 'triggers',
href: `${base}/nats_triggers`
href: `${$wsBase}/nats_triggers`
},
{
label: 'SQS triggers',
id: 'triggers',
href: `${base}/sqs_triggers`
href: `${$wsBase}/sqs_triggers`
},
{
label: 'GCP Pub/Sub triggers',
id: 'triggers',
href: `${base}/gcp_triggers`
href: `${$wsBase}/gcp_triggers`
},
{
label: 'MQTT triggers',
id: 'triggers',
href: `${base}/mqtt_triggers`
href: `${$wsBase}/mqtt_triggers`
},
{
label: 'Email triggers',
id: 'triggers',
href: `${base}/email_triggers`
href: `${$wsBase}/email_triggers`
},
{
label: 'Audit logs',
id: 'audit_logs',
href: `${base}/audit_logs`
href: `${$wsBase}/audit_logs`
}
].filter(filterLink)
)
@@ -50,7 +50,7 @@
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
import { twMerge } from 'tailwind-merge'
import { onMount } from 'svelte'
import { base } from '$lib/base'
import { wsBase, stripWsPrefix } from '$lib/workspaceUrl'
import { type Changelog, changelogs } from './changelogs'
import { page } from '$app/state'
import SideBarNotification from './SideBarNotification.svelte'
@@ -140,7 +140,7 @@
subItems: [
{
label: 'Tutorials',
href: `${base}/tutorials`,
href: `${$wsBase}/tutorials`,
icon: GraduationCap,
aiId: 'sidebar-menu-link-tutorials',
aiDescription: 'Button to navigate to tutorials',
@@ -206,7 +206,7 @@
let mainMenuLinks = $derived([
{
label: 'Home',
href: `${base}/`,
href: `${$wsBase}/`,
icon: Home,
aiId: 'sidebar-menu-link-home',
aiDescription:
@@ -214,7 +214,7 @@
},
{
label: 'Runs',
href: `${base}/runs`,
href: `${$wsBase}/runs`,
icon: Play,
aiId: 'sidebar-menu-link-runs',
aiDescription: 'Button to navigate to runs',
@@ -226,7 +226,7 @@
},
{
label: 'Variables',
href: `${base}/variables`,
href: `${$wsBase}/variables`,
icon: DollarSign,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-variables',
@@ -234,7 +234,7 @@
},
{
label: 'Resources',
href: `${base}/resources`,
href: `${$wsBase}/resources`,
icon: Boxes,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-resources',
@@ -242,7 +242,7 @@
},
{
label: 'Assets',
href: `${base}/assets`,
href: `${$wsBase}/assets`,
icon: Pyramid,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-assets',
@@ -253,7 +253,7 @@
? [
{
label: 'Tutorials',
href: `${base}/tutorials`,
href: `${$wsBase}/tutorials`,
icon: GraduationCap,
aiId: 'sidebar-menu-link-tutorials-main',
aiDescription: 'Button to navigate to tutorials'
@@ -264,7 +264,7 @@
let defaultExtraTriggerLinks = $derived([
{
label: 'HTTP',
href: '/routes',
href: `${$wsBase}/routes`,
icon: Route,
disabled: $userStore?.operator,
kind: 'http',
@@ -273,7 +273,7 @@
},
{
label: 'WebSockets',
href: '/websocket_triggers',
href: `${$wsBase}/websocket_triggers`,
icon: Unplug,
disabled: $userStore?.operator,
kind: 'ws',
@@ -282,7 +282,7 @@
},
{
label: 'Postgres',
href: '/postgres_triggers',
href: `${$wsBase}/postgres_triggers`,
icon: Database,
disabled: $userStore?.operator,
kind: 'postgres',
@@ -291,7 +291,7 @@
},
{
label: 'Kafka' + ($enterpriseLicense ? '' : ' (EE)'),
href: '/kafka_triggers',
href: `${$wsBase}/kafka_triggers`,
icon: KafkaIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
kind: 'kafka',
@@ -300,7 +300,7 @@
},
{
label: 'NATS' + ($enterpriseLicense ? '' : ' (EE)'),
href: '/nats_triggers',
href: `${$wsBase}/nats_triggers`,
icon: NatsIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
kind: 'nats',
@@ -309,7 +309,7 @@
},
{
label: 'SQS' + ($enterpriseLicense ? '' : ' (EE)'),
href: '/sqs_triggers',
href: `${$wsBase}/sqs_triggers`,
icon: AwsIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
kind: 'sqs',
@@ -318,7 +318,7 @@
},
{
label: 'GCP Pub/Sub' + ($enterpriseLicense ? '' : ' (EE)'),
href: '/gcp_triggers',
href: `${$wsBase}/gcp_triggers`,
icon: GoogleCloudIcon,
disabled: $userStore?.operator || !$enterpriseLicense,
kind: 'gcp',
@@ -327,7 +327,7 @@
},
{
label: 'MQTT',
href: '/mqtt_triggers',
href: `${$wsBase}/mqtt_triggers`,
icon: MqttIcon,
disabled: $userStore?.operator,
kind: 'mqtt',
@@ -336,7 +336,7 @@
},
{
label: 'Email',
href: '/email_triggers',
href: `${$wsBase}/email_triggers`,
icon: MailIcon,
disabled: $userStore?.operator,
kind: 'email',
@@ -348,7 +348,7 @@
let nativeTriggerLinks = $derived(
availableNativeServices.map(({ service, icon, config }) => ({
label: config?.serviceDisplayName || service,
href: `/native_triggers/${service}`,
href: `${$wsBase}/native_triggers/${service}`,
icon: icon,
disabled: $userStore?.operator,
kind: service,
@@ -359,22 +359,28 @@
let allTriggerLinks = $derived([...defaultExtraTriggerLinks, ...nativeTriggerLinks])
let strippedPathname = $derived(stripWsPrefix(page.url.pathname))
let triggerMenuLinks = $derived([
{
label: 'Schedules',
href: `${base}/schedules`,
href: `${$wsBase}/schedules`,
icon: Calendar,
disabled: !SIDEBAR_SHOW_SCHEDULES || $userStore?.operator,
aiId: 'sidebar-menu-link-schedules',
aiDescription: 'Button to navigate to schedules'
},
...allTriggerLinks.filter(
(link) => $usedTriggerKinds.includes(link.kind) || page.url.pathname.includes(link.href)
(link) =>
$usedTriggerKinds.includes(link.kind) ||
strippedPathname.includes(stripWsPrefix(link.href))
)
])
let extraTriggerLinks = $derived(
allTriggerLinks.filter((link) => {
return !page.url.pathname.includes(link.href) && !$usedTriggerKinds.includes(link.kind)
return (
!strippedPathname.includes(stripWsPrefix(link.href)) &&
!$usedTriggerKinds.includes(link.kind)
)
})
)
let secondaryMenuLinks = $derived([
@@ -403,7 +409,7 @@
? [
{
label: 'Workspace',
href: `${base}/workspace_settings`,
href: `${$wsBase}/workspace_settings`,
icon: FolderCog,
aiId: 'sidebar-menu-link-workspace',
aiDescription: 'Button to navigate to workspace settings',
@@ -453,7 +459,7 @@
},
{
label: 'Workers',
href: `${base}/workers`,
href: `${$wsBase}/workers`,
icon: ServerCog,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-workers',
@@ -467,7 +473,7 @@
subItems: [
{
label: 'Folders',
href: `${base}/folders`,
href: `${$wsBase}/folders`,
icon: FolderOpen,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-folders',
@@ -476,7 +482,7 @@
},
{
label: 'Groups',
href: `${base}/groups`,
href: `${$wsBase}/groups`,
icon: UserCog,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-groups',
@@ -495,7 +501,7 @@
subItems: [
{
label: 'Audit logs',
href: `${base}/audit_logs`,
href: `${$wsBase}/audit_logs`,
icon: Eye,
aiId: 'sidebar-menu-link-audit-logs',
aiDescription: 'Button to navigate to audit logs'
@@ -504,7 +510,7 @@
? [
{
label: 'Service logs',
href: `${base}/service_logs`,
href: `${$wsBase}/service_logs`,
icon: Logs,
aiId: 'sidebar-menu-link-service-logs',
aiDescription: 'Button to navigate to service logs'
@@ -529,7 +535,7 @@
}
: {
label: 'Audit logs',
href: `${base}/audit_logs`,
href: `${$wsBase}/audit_logs`,
icon: Eye,
disabled: $userStore?.operator,
aiId: 'sidebar-menu-link-audit-logs',
@@ -18,6 +18,7 @@
import { base } from '$lib/base'
import { page } from '$app/state'
import { switchWorkspace } from '$lib/storeUtils'
import { stripWsPrefix } from '$lib/workspaceUrl'
import MultiplayerMenu from './MultiplayerMenu.svelte'
import { enterpriseLicense } from '$lib/stores'
import { isCloudHosted } from '$lib/cloud'
@@ -58,14 +59,13 @@
]
const isOnEditPage = editPages.some((editPage) => page.route.id?.includes(editPage) ?? false)
if (!isOnEditPage) {
switchWorkspace(id)
if (page.url.searchParams.get('workspace')) {
page.url.searchParams.set('workspace', id)
}
} else {
switchWorkspace(id)
switchWorkspace(id)
if (isOnEditPage) {
await goto('/')
} else {
// Stay on same page in new workspace
const strippedPath = stripWsPrefix(page.url.pathname)
await goto(strippedPath)
}
}
@@ -30,7 +30,7 @@
import RelationPicker from './RelationPicker.svelte'
import { invalidRelations, savePostgresTriggerFromCfg } from './utils'
import CheckPostgresRequirement from './CheckPostgresRequirement.svelte'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { untrack, type Snippet } from 'svelte'
import TriggerEditorToolbar from '../TriggerEditorToolbar.svelte'
import PermissionedAsLine from '../PermissionedAsLine.svelte'
@@ -431,7 +431,7 @@
postgres_resource_path
}
})
window.open(`${base}/scripts/add?id=${templateId}`)
window.open(`${$wsBase}/scripts/add?id=${templateId}`)
loading = false
} catch (error) {
loading = false
@@ -24,7 +24,7 @@
} from '$lib/gen'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { canWrite, emptyString, formatCron, sendUserToast, cronV1toV2 } from '$lib/utils'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import Section from '$lib/components/Section.svelte'
import { List, Loader2, Save, AlertTriangle } from 'lucide-svelte'
import autosize from '$lib/autosize'
@@ -664,7 +664,7 @@
variant="default"
startIcon={{ icon: List }}
disabled={!allowSchedule || pathError != '' || emptyString(script_path)}
href={`${base}/runs/${script_path}?show_schedules=true&show_future_jobs=true`}
href={`${$wsBase}/runs/${script_path}?show_schedules=true&show_future_jobs=true`}
>
View runs
</Button>
+10 -4
View File
@@ -1,13 +1,19 @@
import { goto as svelteGoto } from '$app/navigation'
import { base as svelteBase } from '$app/paths'
import { toWorkspacePath } from './workspaceUrl'
export function goto(path: string, options = {}) {
if (svelteBase == '' || path.startsWith('?')) {
if (path.startsWith('?') || path.startsWith('#')) {
return svelteGoto(path, options)
} else {
const fullPath = path.startsWith(svelteBase) ? path : `${svelteBase}${path}`
return svelteGoto(fullPath, options)
}
let fullPath = toWorkspacePath(path)
if (svelteBase && !fullPath.startsWith(svelteBase)) {
fullPath = `${svelteBase}${fullPath}`
}
return svelteGoto(fullPath, options)
}
export async function setQuery(
+2 -2
View File
@@ -1,5 +1,5 @@
import { get } from 'svelte/store'
import { base } from '$lib/base'
import { getWsBase } from '$lib/workspaceUrl'
import type { Schema, SupportedLanguage } from './common'
import { FlowService, type Script, ScriptService, ScheduleService } from './gen'
import { workspaceStore } from './stores'
@@ -125,7 +125,7 @@ export function scriptPathToHref(path: string, hubBaseUrl: string): string {
if (path.startsWith('hub/')) {
return hubBaseUrl + '/from_version/' + path.substring(4)
} else {
return `${base}/scripts/get/${path}?workspace=${get(workspaceStore)}`
return `${getWsBase()}/scripts/get/${path}`
}
}
+6 -4
View File
@@ -1,21 +1,23 @@
import { base } from '$lib/base'
import { getWsBase } from '$lib/workspaceUrl'
type ItemType = 'script' | 'flow' | 'app' | 'raw_app'
export function buildForkEditUrl(itemType: ItemType, itemPath: string): string {
const wsBase = getWsBase()
let editPath: string
switch (itemType) {
case 'script':
editPath = `${base}/scripts/edit/${itemPath}`
editPath = `${wsBase}/scripts/edit/${itemPath}`
break
case 'flow':
editPath = `${base}/flows/edit/${itemPath}?nodraft=true`
editPath = `${wsBase}/flows/edit/${itemPath}?nodraft=true`
break
case 'app':
editPath = `${base}/apps/edit/${itemPath}?nodraft=true`
editPath = `${wsBase}/apps/edit/${itemPath}?nodraft=true`
break
case 'raw_app':
editPath = `${base}/apps_raw/edit/${itemPath}?nodraft=true`
editPath = `${wsBase}/apps_raw/edit/${itemPath}?nodraft=true`
break
}
return `${base}/user/fork_workspace?rd=${encodeURIComponent(editPath)}`
+64
View File
@@ -0,0 +1,64 @@
import { derived, get } from 'svelte/store'
import { workspaceStore } from './stores'
import { base } from './base'
const GLOBAL_PATH_PREFIXES = [
'/user/',
'/oauth/',
'/approve/',
'/public/',
'/a/',
'/api/',
'/view_graph',
'/kitchen_sink',
'/gh_success',
'/test_dev',
'/dev'
]
/** Extract workspace ID from a URL pathname matching `/w/<workspace>/...` */
export function extractWorkspaceFromPath(pathname: string): string | undefined {
const stripped = base && pathname.startsWith(base) ? pathname.slice(base.length) : pathname
const match = stripped.match(/^\/w\/([^/]+)/)
return match?.[1]
}
/** Strip `/w/<workspace>` prefix from a pathname, returning the rest */
export function stripWsPrefix(pathname: string): string {
const stripped = base && pathname.startsWith(base) ? pathname.slice(base.length) : pathname
const match = stripped.match(/^\/w\/[^/]+(\/.*)?$/)
if (match) {
return match[1] || '/'
}
return stripped
}
/** Returns true if the path is global (not workspace-scoped) */
export function isGlobalPath(path: string): boolean {
if (path.startsWith('?') || path.startsWith('#')) return true
const stripped = base && path.startsWith(base) ? path.slice(base.length) : path
// Already has /w/ prefix
if (stripped.match(/^\/w\/[^/]+/)) return true
return GLOBAL_PATH_PREFIXES.some((prefix) => stripped.startsWith(prefix))
}
/** Prepend `/w/<workspace>` to a workspace-scoped path */
export function toWorkspacePath(path: string, workspace?: string): string {
if (isGlobalPath(path)) return path
const ws = workspace ?? get(workspaceStore)
if (!ws) return path
return `/w/${ws}${path}`
}
/** Reactive store: `base + '/w/' + workspace` for use in `<a href>` attributes */
export const wsBase = derived(workspaceStore, ($ws) => {
if (!$ws) return base
return `${base}/w/${$ws}`
})
/** Imperative getter: returns `base + '/w/' + workspace` synchronously */
export function getWsBase(workspace?: string): string {
const ws = workspace ?? get(workspaceStore)
if (!ws) return base
return `${base}/w/${ws}`
}
@@ -56,6 +56,7 @@
import GlobalSearchModal from '$lib/components/search/GlobalSearchModal.svelte'
import MenuButton from '$lib/components/sidebar/MenuButton.svelte'
import { loadProtectionRules } from '$lib/workspaceProtectionRules.svelte'
import { extractWorkspaceFromPath, stripWsPrefix, isGlobalPath } from '$lib/workspaceUrl'
import { setContext, untrack } from 'svelte'
import { base } from '$app/paths'
import { Menubar } from '$lib/components/meltComponents'
@@ -107,14 +108,34 @@
}
function onQueryChange() {
let queryWorkspace = page.url.searchParams.get('workspace')
if (queryWorkspace) {
$workspaceStore = queryWorkspace
// Extract workspace from URL path /w/<workspace>/...
const wsFromUrl = extractWorkspaceFromPath(page.url.pathname)
if (wsFromUrl && wsFromUrl !== $workspaceStore) {
$workspaceStore = wsFromUrl
}
// Backward compat: support ?workspace=X query param, redirect to /w/X/...
let queryWorkspace = page.url.searchParams.get('workspace')
if (queryWorkspace && !wsFromUrl) {
$workspaceStore = queryWorkspace
const strippedPath = stripWsPrefix(page.url.pathname)
goto(`${strippedPath}${page.url.search.replace(/[?&]workspace=[^&]+/, '')}`, {
replaceState: true
})
return
}
// Redirect old URLs (no /w/ prefix) to new format
if (!wsFromUrl && $workspaceStore && !isGlobalPath(page.url.pathname)) {
const strippedPath = stripWsPrefix(page.url.pathname)
goto(`${strippedPath}${page.url.search}`, { replaceState: true })
return
}
const strippedPathname = stripWsPrefix(page.url.pathname)
menuHidden =
page.url.searchParams.get('nomenubar') === 'true' ||
page.url.pathname.startsWith('/oauth/callback/')
strippedPathname.startsWith('/oauth/callback/')
}
async function updateUserStore(workspace: string | undefined) {
@@ -144,8 +165,12 @@
// This ensures the cross-origin isolation headers are fetched from the server
// which are required for SharedArrayBuffer and TypeScript workers to work correctly
const toPath = navigation.to?.url.pathname
? stripWsPrefix(navigation.to.url.pathname)
: undefined
if (toPath && (toPath.startsWith('/apps_raw/add') || toPath.startsWith('/apps_raw/edit'))) {
const currentPath = navigation.from?.url.pathname
? stripWsPrefix(navigation.from.url.pathname)
: undefined
// Reload if we're not on an apps_raw path, or if we're on /apps/get_raw/ (viewing a raw app)
// The /apps/get_raw/ path doesn't have cross-origin isolation headers, so we need to reload
if (!currentPath?.startsWith('/apps_raw/') || currentPath?.startsWith('/apps_raw/get/')) {
@@ -295,12 +320,13 @@
function pathInAppMode(pathname: string | undefined): boolean {
if (!pathname) return false
const stripped = stripWsPrefix(pathname)
return (
pathname.startsWith(base + '/apps') ||
pathname.startsWith(base + '/flows/add') ||
pathname.startsWith(base + '/flows/edit') ||
pathname.startsWith(base + '/scripts/add') ||
pathname.startsWith(base + '/scripts/edit')
stripped.startsWith(base + '/apps') ||
stripped.startsWith(base + '/flows/add') ||
stripped.startsWith(base + '/flows/edit') ||
stripped.startsWith(base + '/scripts/add') ||
stripped.startsWith(base + '/scripts/edit')
)
}
afterNavigate((n) => {
@@ -315,7 +341,7 @@
}
}
let devOnly = $derived(page.url.pathname.startsWith(base + '/scripts/dev'))
let devOnly = $derived(stripWsPrefix(page.url.pathname).startsWith(base + '/scripts/dev'))
async function loadDefaultScripts(workspace: string, user: UserExt | undefined) {
if (!user?.operator) {
@@ -23,7 +23,7 @@
LayoutDashboard
} from 'lucide-svelte'
import { hubBaseUrlStore } from '$lib/stores'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import ItemsList from '$lib/components/home/ItemsList.svelte'
import CreateActionsApp from '$lib/components/flows/CreateActionsApp.svelte'
@@ -148,7 +148,7 @@
</div>
</Button>
<Button
href="{base}/scripts/add?hub={encodeURIComponent(codeViewerObj?.path ?? '')}"
href="{$wsBase}/scripts/add?hub={encodeURIComponent(codeViewerObj?.path ?? '')}"
startIcon={{ icon: GitFork }}
variant="accent"
size="xs"
@@ -185,7 +185,7 @@
</Button>
<Button
href="{base}/flows/add?hub={flowViewerFlow?.flow?.id}"
href="{$wsBase}/flows/add?hub={flowViewerFlow?.flow?.id}"
startIcon={{ icon: GitFork }}
variant="accent"
size="xs"
@@ -227,7 +227,7 @@
</Button>
<Button
href="{base}/apps/add?hub={appViewerApp?.app?.id}"
href="{$wsBase}/apps/add?hub={appViewerApp?.app?.id}"
startIcon={{ icon: GitFork }}
variant="accent"
disabled={appViewerApp == undefined}
@@ -1,6 +1,6 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import AppPreview from '$lib/components/apps/editor/AppPreview.svelte'
import type { EditorBreakpoint } from '$lib/components/apps/types'
@@ -81,7 +81,7 @@
size="sm"
startIcon={{ icon: Pen }}
variant="subtle"
href="{base}/apps/edit/{app.path}?nodraft=true">Edit</Button
href="{$wsBase}/apps/edit/{app.path}?nodraft=true">Edit</Button
>
</div>
{/if}
@@ -7,6 +7,7 @@
import { userStore, workspaceStore } from '$lib/stores'
import { afterNavigate, replaceState } from '$app/navigation'
import { goto } from '$lib/navigation'
import { wsBase } from '$lib/workspaceUrl'
import { sendUserToast } from '$lib/toast'
import RawAppEditor from '$lib/components/raw_apps/RawAppEditor.svelte'
@@ -422,7 +423,7 @@
{#if $userStore?.is_admin}
Configure datatables in
<a
href="/workspace_settings?tab=windmill_data_tables"
href="{$wsBase}/workspace_settings?tab=windmill_data_tables"
target="_blank"
class="inline-flex items-center gap-1"
>workspace settings <ExternalLinkIcon size={16} />
@@ -547,7 +548,7 @@
{#if $userStore?.is_admin}
Configure AI in
<a
href="/workspace_settings?tab=ai"
href="{$wsBase}/workspace_settings?tab=ai"
target="_blank"
class="inline-flex items-center gap-1 font-semibold"
>workspace settings <ExternalLinkIcon size={16} />
@@ -1,7 +1,7 @@
<script lang="ts">
import type { ConcurrencyGroup } from '$lib/gen'
import { ConcurrencyGroupsService } from '$lib/gen'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button } from '$lib/components/common'
@@ -91,7 +91,7 @@
<tr>
<td>
<a
href={`${base}/runs/?job_kinds=all&graph=ConcurrencyChart&concurrency_key=${concurrency_key}`}
href={`${$wsBase}/runs/?job_kinds=all&graph=ConcurrencyChart&concurrency_key=${concurrency_key}`}
>{concurrency_key}
</a>
</td>
@@ -13,7 +13,7 @@
import DetailPageLayout from '$lib/components/details/DetailPageLayout.svelte'
import { goto } from '$lib/navigation'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import { Badge as HeaderBadge, Alert } from '$lib/components/common'
import MoveDrawer from '$lib/components/MoveDrawer.svelte'
import RunForm from '$lib/components/RunForm.svelte'
@@ -197,7 +197,7 @@
tag: overrideTag,
skipPreprocessor: true
})
await goto('/run/' + run + '?workspace=' + $workspaceStore)
await goto('/run/' + run)
} catch (e) {
throw e
} finally {
@@ -247,7 +247,7 @@
buttons.push({
label: 'Fork',
buttonProps: {
href: `${base}/flows/add?template=${flow.path}`,
href: `${$wsBase}/flows/add?template=${flow.path}`,
variant: 'subtle',
unifiedSize: 'md',
disabled: !showEditButtons,
@@ -275,7 +275,7 @@
buttons.push({
label: `Runs`,
buttonProps: {
href: `${base}/runs/${flow.path}`,
href: `${$wsBase}/runs/${flow.path}`,
unifiedSize: 'md',
variant: 'subtle',
startIcon: Play
@@ -315,7 +315,7 @@
buttons.push({
label: 'Edit',
buttonProps: {
href: `${base}/flows/edit/${path}?nodraft=true`,
href: `${$wsBase}/flows/edit/${path}?nodraft=true`,
variant: 'accent',
unifiedSize: 'md',
disabled: !can_write || !showEditButtons,
@@ -474,7 +474,7 @@
<MoveDrawer
bind:this={moveDrawer}
on:update={async (e) => {
await goto('/flows/get/' + e.detail + `?workspace=${$workspaceStore}`)
await goto('/flows/get/' + e.detail)
loadFlow()
}}
/>
@@ -515,7 +515,7 @@
onSaved={can_write
? async (newPath) => {
if (newPath !== flow?.path) {
await goto(`/flows/get/${newPath}?workspace=${$workspaceStore}`)
await goto(`/flows/get/${newPath}`)
} else {
loadFlow()
}
@@ -593,7 +593,7 @@
Deployment in progress
{#if deploymentJobId}
<a
href="/run/{deploymentJobId}?workspace={$workspaceStore}"
href="/run/{deploymentJobId}"
class="underline"
target="_blank">view job</a
>
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import {
JobService,
type Job,
@@ -363,7 +363,7 @@
})
}
await goto('/run/' + id + '?workspace=' + $workspaceStore)
await goto('/run/' + id)
} else {
sendUserToast('Cannot run this job immediately', true)
}
@@ -464,7 +464,7 @@
</div>
{/each}
<div>
<Button href="{base}/runs" unifiedSize="md" variant="accent">Go to runs page</Button>
<Button href="{$wsBase}/runs" unifiedSize="md" variant="accent">Go to runs page</Button>
</div>
</div>
</div>
@@ -624,7 +624,7 @@
{selectedJobStepType}
{restartBranchNames}
onRestartComplete={(newJobId) => {
goto('/run/' + newJobId + '?workspace=' + $workspaceStore)
goto('/run/' + newJobId)
}}
flowPath={job.script_path}
flowVersionId={job.script_hash ? parseInt(job.script_hash, 16) : undefined}
@@ -6,7 +6,7 @@
WorkspaceService
} from '$lib/gen'
import { canWrite, displayDate, getLocalSetting, storeLocalSetting } from '$lib/utils'
import { base } from '$app/paths'
import { wsBase } from '$lib/workspaceUrl'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Badge, Button, Skeleton } from '$lib/components/common'
import Dropdown from '$lib/components/DropdownV2.svelte'
@@ -362,7 +362,7 @@
/>
<div class="flex gap-2 items-center justify-end">
<Button
href={`${base}/runs/?schedule_path=${path}&show_schedules=true&show_future_jobs=true`}
href={`${$wsBase}/runs/?schedule_path=${path}&show_schedules=true&show_future_jobs=true`}
unifiedSize="md"
startIcon={{ icon: List }}
variant="subtle"
@@ -433,7 +433,7 @@
displayName: 'View runs',
icon: List,
href:
base +
$wsBase +
'/runs/?schedule_path=' +
path +
'&show_schedules=true&show_future_jobs=true'
@@ -441,7 +441,7 @@
{
displayName: 'Audit logs',
icon: Eye,
href: `${base}/audit_logs?resource=${path}`
href: `${$wsBase}/audit_logs?resource=${path}`
},
{
displayName: 'Run now',
@@ -476,7 +476,7 @@
{/if}
{#each jobs ?? [] as job}
{@const h = (avg_s ? job.duration_ms / avg_s : 1) * 7 + 3}
<a href="{base}/run/{job.id}?workspace={$workspaceStore}">
<a href="{$wsBase}/run/{job.id}">
<JobPreview id={job.id} class="p-4">
<div>
<div
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import {
JobService,
ScriptService,
@@ -309,7 +309,7 @@
skipPreprocessor: true
})
}
await goto('/run/' + run + '?workspace=' + $workspaceStore)
await goto('/run/' + run)
} catch (err) {
runLoading = false
sendUserToast(`Could not create job: ${err.body}`, true)
@@ -348,7 +348,7 @@
buttons.push({
label: 'Fork',
buttonProps: {
href: `${base}/scripts/add?template=${script.path}`,
href: `${$wsBase}/scripts/add?template=${script.path}`,
unifiedSize: 'md',
variant: 'subtle',
disabled: !showEditButtons,
@@ -381,7 +381,7 @@
buttons.push({
label: `Runs`,
buttonProps: {
href: `${base}/runs/${script.path}`,
href: `${$wsBase}/runs/${script.path}`,
unifiedSize: 'md',
variant: 'subtle',
startIcon: Play
@@ -441,7 +441,7 @@
buttons.push({
label: 'Edit',
buttonProps: {
href: `${base}/scripts/edit/${script.path}?${
href: `${$wsBase}/scripts/edit/${script.path}?${
topHash ? `&hash=${script.hash}&topHash=` + topHash : ''
}`,
unifiedSize: 'md',
@@ -613,7 +613,7 @@
<MoveDrawer
bind:this={moveDrawer}
on:update={async (e) => {
await goto('/scripts/get/' + e.detail + `?workspace=${$workspaceStore}`)
await goto('/scripts/get/' + e.detail)
loadScript(page.params.hash ?? '')
}}
/>
@@ -632,7 +632,7 @@
openDetails
on:openDetails={(e) => {
if (script) {
goto(`/scripts/get/${e.detail.version}?workspace=${$workspaceStore}`)
goto(`/scripts/get/${e.detail.version}`)
}
versionsDrawerOpen = false
}}
@@ -663,7 +663,7 @@
onSaved={can_write
? async (newPath) => {
if (newPath !== script?.path) {
await goto(`/scripts/get/${newPath}?workspace=${$workspaceStore}`)
await goto(`/scripts/get/${newPath}`)
} else {
loadScript(page.params.hash ?? '')
}
@@ -750,7 +750,7 @@
<div class="mt-2"></div>
<Alert type="warning" title="Not HEAD">
This hash is not HEAD (latest non-archived version at this path) :
<a href="{base}/scripts/get/{topHash}?workspace={$workspaceStore}"
<a href="{$wsBase}/scripts/get/{topHash}"
>Go to the HEAD of this path</a
>
</Alert>
@@ -784,7 +784,7 @@
Deployment in progress
{#if deploymentJobId}
<a
href="/run/{deploymentJobId}?workspace={$workspaceStore}"
href="/run/{deploymentJobId}"
class="underline"
target="_blank">view job</a
>
@@ -11,6 +11,7 @@
whitelabelNameStore
} from '$lib/stores'
import { classNames, emptyString, parseQueryParams } from '$lib/utils'
import { extractWorkspaceFromPath } from '$lib/workspaceUrl'
import { getUserExt } from '$lib/user'
import { WindmillIcon } from '$lib/components/icons'
import LoginPageHeader from '$lib/components/LoginPageHeader.svelte'
@@ -69,7 +70,10 @@
if ($workspaceStore) {
goto(rd ?? '/')
} else {
let workspaceTarget = parseQueryParams(rd ?? undefined)['workspace']
// Extract workspace from rd path (/w/<ws>/...) or query param (?workspace=X)
let workspaceTarget =
extractWorkspaceFromPath(rd ?? '') ??
parseQueryParams(rd ?? undefined)['workspace']
if (rd && workspaceTarget) {
$workspaceStore = workspaceTarget
goto(rd)
@@ -51,6 +51,7 @@
type S3ResourceSettings
} from '$lib/workspace_settings'
import { base } from '$lib/base'
import { wsBase } from '$lib/workspaceUrl'
import ConnectionSection from '$lib/components/ConnectionSection.svelte'
import AISettings from '$lib/components/workspaceSettings/AISettings.svelte'
import StorageSettings from '$lib/components/workspaceSettings/StorageSettings.svelte'
@@ -1315,8 +1316,8 @@
}}
onSelect={editSlackCommand}
connectHref="{base}/api/oauth/connect_slack"
createScriptHref="{base}/scripts/add?hub=hub%2F28071%2Fslack%2Fexample_of_responding_to_a_slack_command_slack"
createFlowHref="{base}/flows/add?hub=28"
createScriptHref="{$wsBase}/scripts/add?hub=hub%2F28071%2Fslack%2Fexample_of_responding_to_a_slack_command_slack"
createFlowHref="{$wsBase}/flows/add?hub=28"
documentationLink="https://www.windmill.dev/docs/integrations/slack"
onLoadSettings={loadSettings}
display_name={slack_team_name}
@@ -1451,8 +1452,8 @@
}}
onSelect={editTeamsCommand}
connectHref={undefined}
createScriptHref="{base}/scripts/add?hub=hub%2F11591%2Fteams%2FExample%20of%20responding%20to%20a%20Microsoft%20Teams%20command"
createFlowHref="{base}/flows/add?hub=58"
createScriptHref="{$wsBase}/scripts/add?hub=hub%2F11591%2Fteams%2FExample%20of%20responding%20to%20a%20Microsoft%20Teams%20command"
createFlowHref="{$wsBase}/flows/add?hub=58"
documentationLink="https://www.windmill.dev/docs/integrations/teams"
onLoadSettings={loadSettings}
display_name={teams_team_name}
@@ -1684,7 +1685,7 @@
/>
<Button
variant="default"
href={`${base}/scripts/add?lang=bun#` +
href={`${$wsBase}/scripts/add?lang=bun#` +
encodeState({
path: 'f/success_handler',
summary: 'Workspace Success Handler',
+14 -3
View File
@@ -9,6 +9,7 @@
import { onDestroy, onMount } from 'svelte'
import { refreshSuperadmin } from '$lib/refreshUser'
import { extractWorkspaceFromPath, stripWsPrefix } from '$lib/workspaceUrl'
// import EditorTheme from '$lib/components/EditorTheme.svelte'
import { computeDrift } from '$lib/forLater'
import { setLicense } from '$lib/enterpriseUtils'
@@ -40,6 +41,14 @@
try {
await refreshSuperadmin()
// Extract workspace from URL path if present
if (!$workspaceStore) {
const wsFromUrl = extractWorkspaceFromPath(page.url.pathname)
if (wsFromUrl) {
$workspaceStore = wsFromUrl
}
}
if ($workspaceStore) {
if ($userStore) {
console.log(`Welcome back ${$userStore.username} to ${$workspaceStore}`)
@@ -50,9 +59,10 @@
}
}
} else {
const strippedPath = stripWsPrefix(page.url.pathname)
if (
(!page.url.pathname.startsWith('/user/') || page.url.pathname.startsWith('/user/cli')) &&
!page.url.pathname.startsWith('/oauth/mcp_authorize')
(!strippedPath.startsWith('/user/') || strippedPath.startsWith('/user/cli')) &&
!strippedPath.startsWith('/oauth/mcp_authorize')
) {
goto(
`/user/workspaces?rd=${encodeURIComponent(page.url.href.replace(page.url.origin, ''))}`
@@ -63,7 +73,8 @@
}
} catch (e) {
console.error(e)
if (page.url.pathname != '/user/login' && page.url.pathname != '/user/logout') {
const strippedPath = stripWsPrefix(page.url.pathname)
if (strippedPath != '/user/login' && strippedPath != '/user/logout') {
const url = page.url
console.log('logout 5', url.href.replace(url.origin, ''))
await logoutWithRedirect(url.href.replace(url.origin, ''))