mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
32994df427
* fix: return to parent workspace when a fork is deleted remotely When a workspace fork was deleted remotely while a user had it open, reloading stranded them: a regular member got logged out (whoami fails on the vanished workspace) and a superadmin silently landed on a dead workspace whose requests 404. Detect the deleted fork on load and redirect to its parent (or the workspace picker) with a toast instead. - forkParentMemory.ts: persist a bounded fork->parent map in localStorage while a fork is reachable (the parent is unrecoverable post-deletion). - (logged) layout: record the current fork's parent via an effect. - root layout: tryRecoverFromDeletedFork detects the vanished fork in loadUser and redirects to the remembered parent or the workspace picker, reusing the workspace list already fetched on mount. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: verify fork existence for superadmins before deleted-fork redirect * fix: only recover deleted fork on actual 404 from superadmin check * fix: recover prefixless dev-workspace forks via remembered parent * fix: use workspace exists check to detect deleted forks * fix: restore wm-fork- detection for non-member superadmin forks * fix: record fork parent for non-member superadmin dev workspaces * Update frontend/src/routes/(root)/+layout.svelte Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix: store fork parent map with null-prototype to handle __proto__ ids --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
import type { UserWorkspace } from './stores'
|
|
|
|
// A fork's parent linkage lives only in its own `workspace` row. Once the fork
|
|
// is deleted remotely, `listUserWorkspaces` stops returning it and the parent is
|
|
// unrecoverable from the server. We therefore mirror `fork id -> parent id` into
|
|
// localStorage while the fork is still reachable, so that after a reload landing
|
|
// on a now-deleted fork we can send the user back to its parent instead of a
|
|
// dead workspace / forced logout.
|
|
|
|
const FORK_PARENTS_KEY = 'fork_parents'
|
|
const MAX_ENTRIES = 50
|
|
|
|
// A null-prototype map so workspace ids that collide with Object prototype members
|
|
// (`__proto__`, `constructor`, …) are stored as plain own properties rather than
|
|
// triggering prototype semantics on read/write.
|
|
function emptyMap(): Record<string, string> {
|
|
return Object.create(null)
|
|
}
|
|
|
|
function readMap(): Record<string, string> {
|
|
const map = emptyMap()
|
|
try {
|
|
const raw = localStorage.getItem(FORK_PARENTS_KEY)
|
|
if (!raw) return map
|
|
const parsed = JSON.parse(raw)
|
|
if (!parsed || typeof parsed !== 'object') return map
|
|
for (const key of Object.keys(parsed)) {
|
|
const value = (parsed as Record<string, unknown>)[key]
|
|
if (typeof value === 'string') map[key] = value
|
|
}
|
|
} catch (e) {
|
|
console.error('Could not read fork parent mapping', e)
|
|
}
|
|
return map
|
|
}
|
|
|
|
function writeMap(map: Record<string, string>): void {
|
|
try {
|
|
localStorage.setItem(FORK_PARENTS_KEY, JSON.stringify(map))
|
|
} catch (e) {
|
|
console.error('Could not persist fork parent mapping', e)
|
|
}
|
|
}
|
|
|
|
export function rememberForkParent(forkId: string, parentId: string): void {
|
|
const map = readMap()
|
|
if (map[forkId] === parentId) return
|
|
// Re-insert at the end so the oldest entries are the ones trimmed below.
|
|
delete map[forkId]
|
|
map[forkId] = parentId
|
|
const keys = Object.keys(map)
|
|
if (keys.length > MAX_ENTRIES) {
|
|
for (const k of keys.slice(0, keys.length - MAX_ENTRIES)) delete map[k]
|
|
}
|
|
writeMap(map)
|
|
}
|
|
|
|
export function getRememberedForkParent(forkId: string): string | undefined {
|
|
return readMap()[forkId]
|
|
}
|
|
|
|
export function forgetForkParent(forkId: string): void {
|
|
const map = readMap()
|
|
if (forkId in map) {
|
|
delete map[forkId]
|
|
writeMap(map)
|
|
}
|
|
}
|
|
|
|
// Records the parent of the current workspace when it is a fork still present in
|
|
// the user's workspace list. No-op otherwise, so it never clobbers a previously
|
|
// remembered parent when the fork has already disappeared.
|
|
export function recordForkParent(
|
|
workspaceId: string | undefined,
|
|
workspaces: UserWorkspace[]
|
|
): void {
|
|
if (!workspaceId) return
|
|
const ws = workspaces.find((w) => w.id === workspaceId)
|
|
if (ws?.parent_workspace_id) {
|
|
rememberForkParent(workspaceId, ws.parent_workspace_id)
|
|
}
|
|
}
|