mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: judge every session-editor permission by the user acting in that workspace
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dbe0b49aca
commit
f4e8fe4838
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { superadmin, userStore, type DBSchema } from '$lib/stores'
|
||||
import { superadmin, type DBSchema } from '$lib/stores'
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
EditIcon,
|
||||
@@ -33,6 +33,10 @@
|
||||
import type { DbFeatures } from './apps/components/display/dbtable/dbFeatures'
|
||||
import Star from './Star.svelte'
|
||||
import type { Asset } from '$lib/gen'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
/** Represents a selected table with its schema */
|
||||
export interface SelectedTable {
|
||||
@@ -644,7 +648,7 @@
|
||||
Import schema from database
|
||||
</span>
|
||||
</button>
|
||||
{#if !!$userStore?.is_admin || !!$superadmin}
|
||||
{#if !!actingUser?.is_admin || !!$superadmin}
|
||||
<button
|
||||
onclick={() => onImport('schema_and_data')}
|
||||
class="hover:opacity-70 transition-opacity rounded-md border aspect-square w-52 gap-4 p-4 center-center flex-col"
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { userStore } from '$lib/stores'
|
||||
import { SettingsIcon } from 'lucide-svelte'
|
||||
import { Button } from './common'
|
||||
import Drawer from './common/drawer/Drawer.svelte'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import DefaultScriptsInner from './DefaultScriptsInner.svelte'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
placement?: 'left' | 'right'
|
||||
@@ -17,7 +20,7 @@
|
||||
let drawer: Drawer | undefined = $state()
|
||||
</script>
|
||||
|
||||
{#if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
{#if actingUser?.is_admin || actingUser?.is_super_admin}
|
||||
<Drawer bind:this={drawer} {placement}>
|
||||
<DrawerContent title="Edit Default Scripts" on:close={drawer?.closeDrawer}>
|
||||
<DefaultScriptsInner />
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
import BreadcrumbSegment from '$lib/components/BreadcrumbSegment.svelte'
|
||||
import { isOwner } from '$lib/utils'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
summary?: string
|
||||
@@ -118,7 +123,7 @@
|
||||
// Treat an empty path as ownable so the pen popover lets a user pick the
|
||||
// path for a brand-new item. `Path.reset()` then synthesizes a default
|
||||
// under their own user/folder scope.
|
||||
let own = $derived(!path || isOwner(path, $userStore, $operatingWorkspace))
|
||||
let own = $derived(!path || isOwner(path, actingUser, $operatingWorkspace))
|
||||
|
||||
// Virtual entry for the picker: surfaces the currently-edited item at its
|
||||
// live path (which may differ from `savedPath` mid-rename, so the picker
|
||||
|
||||
@@ -33,13 +33,18 @@
|
||||
import { Button, ButtonType } from '$lib/components/common'
|
||||
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
|
||||
import { VolumeService } from '$lib/gen'
|
||||
import { globalDbManagerDrawer, globalS3FilePickerExplorer, userStore } from '$lib/stores'
|
||||
import { globalDbManagerDrawer, globalS3FilePickerExplorer } from '$lib/stores'
|
||||
import { isS3Uri } from '$lib/utils'
|
||||
import { Database, File, HardDriveIcon } from 'lucide-svelte'
|
||||
import DucklakeIcon from './icons/DucklakeIcon.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
const {
|
||||
asset,
|
||||
@@ -78,7 +83,7 @@
|
||||
</script>
|
||||
|
||||
<Button
|
||||
disabled={$userStore?.operator || disabled}
|
||||
disabled={actingUser?.operator || disabled}
|
||||
unifiedSize={'md'}
|
||||
variant={buttonVariant}
|
||||
wrapperClasses={className}
|
||||
|
||||
@@ -105,9 +105,14 @@
|
||||
import { UserDraft } from '$lib/userDraft.svelte'
|
||||
import { setOpenInSessionHandoff } from './sessions/openInSessionContext'
|
||||
import { getEditorStoragePath, setEditorStoragePath } from './editorStoragePathContext'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let {
|
||||
initialPath = $bindable(''),
|
||||
@@ -604,7 +609,7 @@
|
||||
await deployTriggers(
|
||||
triggersToDeploy,
|
||||
opWorkspace,
|
||||
!!$userStore?.is_admin || !!$userStore?.is_super_admin,
|
||||
!!actingUser?.is_admin || !!actingUser?.is_super_admin,
|
||||
usedTriggerKinds,
|
||||
$pathStore,
|
||||
true
|
||||
@@ -615,7 +620,7 @@
|
||||
await deployTriggers(
|
||||
triggersToDeploy,
|
||||
opWorkspace,
|
||||
!!$userStore?.is_admin || !!$userStore?.is_super_admin,
|
||||
!!actingUser?.is_admin || !!actingUser?.is_super_admin,
|
||||
usedTriggerKinds,
|
||||
initialPath
|
||||
)
|
||||
@@ -1391,7 +1396,7 @@
|
||||
<AIChangesWarningModal bind:open={aiChangesWarningOpen} onConfirm={aiChangesConfirmCallback} />
|
||||
|
||||
{#key renderCount}
|
||||
{#if !$userStore?.operator}
|
||||
{#if !actingUser?.operator}
|
||||
{#if $pathStore}
|
||||
<FlowHistory bind:this={flowHistory} path={$pathStore} {onHistoryRestore} />
|
||||
{/if}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { enterpriseLicense, userStore } from '$lib/stores'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import Popover from './meltComponents/Popover.svelte'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { Loader2, Github, RotateCw, Plus, Minus, Download, AlertTriangle } from 'lucide-svelte'
|
||||
@@ -18,9 +18,14 @@
|
||||
type GitHubAppState
|
||||
} from '$lib/githubApp'
|
||||
import RepositorySelector from './RepositorySelector.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
resourceType: string
|
||||
@@ -74,7 +79,7 @@
|
||||
let showGitHubApp = $derived(
|
||||
resourceType === 'git_repository' &&
|
||||
$operatingWorkspace &&
|
||||
($userStore?.is_admin || $userStore?.is_super_admin)
|
||||
(actingUser?.is_admin || actingUser?.is_super_admin)
|
||||
)
|
||||
|
||||
// Load GitHub installations when conditions are met
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { userStore, enterpriseLicense } from '$lib/stores'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import { GitSyncService, type GitlabProject } from '$lib/gen'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Popover from './meltComponents/Popover.svelte'
|
||||
@@ -8,9 +8,14 @@
|
||||
import TextInput from './text_input/TextInput.svelte'
|
||||
import Select from './select/Select.svelte'
|
||||
import { GitBranch, Gitlab, Loader2 } from 'lucide-svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
resourceType: string
|
||||
@@ -53,7 +58,7 @@
|
||||
let show = $derived(
|
||||
resourceType === 'git_repository' &&
|
||||
!!ws &&
|
||||
($userStore?.is_admin || $userStore?.is_super_admin)
|
||||
(actingUser?.is_admin || actingUser?.is_super_admin)
|
||||
)
|
||||
// The project listing is served by an enterprise-only route, so on a build
|
||||
// without it the form's first request would 404. The button still shows,
|
||||
|
||||
@@ -34,9 +34,14 @@
|
||||
type GroupDraft,
|
||||
type GroupRole
|
||||
} from '$lib/groupDraft'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
const ROLE_TOOLTIPS = {
|
||||
member:
|
||||
@@ -77,7 +82,7 @@
|
||||
}: Props = $props()
|
||||
|
||||
const restricted = $derived(
|
||||
isDemoWorkspaceRestricted($operatingWorkspace, $userStore?.is_admin, $userStore?.is_super_admin)
|
||||
isDemoWorkspaceRestricted($operatingWorkspace, actingUser?.is_admin, actingUser?.is_super_admin)
|
||||
)
|
||||
|
||||
let can_write = $state(false)
|
||||
@@ -163,7 +168,7 @@
|
||||
opts?.baselineOnly ? (baseline = structuredClone(value)) : setDraft(value)
|
||||
try {
|
||||
group = await GroupService.getGroup({ workspace: $operatingWorkspace!, name })
|
||||
can_write = canWrite(name, group.extra_perms ?? {}, $userStore)
|
||||
can_write = canWrite(name, group.extra_perms ?? {}, actingUser)
|
||||
apply({
|
||||
summary: group.summary ?? '',
|
||||
members: Array.from(
|
||||
|
||||
@@ -12,9 +12,14 @@
|
||||
import InfiniteList from './InfiniteList.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import SavedInputsPickerViewer from './SavedInputsPickerViewer.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
previewArgs?: any
|
||||
@@ -257,8 +262,8 @@
|
||||
{#snippet children({ item, hover })}
|
||||
{@const editOptions =
|
||||
item.created_by == $userStore?.username ||
|
||||
$userStore?.is_admin ||
|
||||
$userStore?.is_super_admin}
|
||||
actingUser?.is_admin ||
|
||||
actingUser?.is_super_admin}
|
||||
<Cell>
|
||||
<div class="center-center">
|
||||
<Save size={12} />
|
||||
|
||||
@@ -30,7 +30,10 @@
|
||||
workerTags,
|
||||
workspaceStore
|
||||
} from '$lib/stores'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
emptySchema,
|
||||
emptyString,
|
||||
@@ -191,6 +194,8 @@
|
||||
// than the navigation workspace ($workspaceStore, which stays put). indicatorPath
|
||||
// is the matching draft path (URL path full-page, session target in preview).
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
const opWorkspace = $derived(autosaveWorkspace ?? $operatingWorkspace)
|
||||
const indicatorPath = $derived(autosavePath ?? userDraftPath)
|
||||
|
||||
@@ -225,8 +230,8 @@
|
||||
let preserveOnBehalfOf = $state(false)
|
||||
|
||||
const WM_DEPLOYERS_GROUP = 'wm_deployers'
|
||||
let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
let canPreserve = $derived(!!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer)
|
||||
let isDeployer = $derived(actingUser?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
let canPreserve = $derived(!!actingUser?.is_admin || !!actingUser?.is_super_admin || isDeployer)
|
||||
let originalOnBehalfOfEmail = $derived(savedScript?.on_behalf_of_email)
|
||||
let originalOnBehalfOfPermissionedAs = $derived(savedScript?.on_behalf_of)
|
||||
let onBehalfOfChoice: OnBehalfOfChoice = $state(undefined)
|
||||
@@ -742,7 +747,7 @@
|
||||
await deployTriggers(
|
||||
triggersToDeploy,
|
||||
opWorkspace,
|
||||
!!$userStore?.is_admin || !!$userStore?.is_super_admin,
|
||||
!!actingUser?.is_admin || !!actingUser?.is_super_admin,
|
||||
usedTriggerKinds,
|
||||
script.path,
|
||||
true
|
||||
@@ -1160,7 +1165,7 @@
|
||||
on:confirmed={handleDraftTriggersConfirmed}
|
||||
/>
|
||||
|
||||
{#if !$userStore?.operator}
|
||||
{#if !actingUser?.operator}
|
||||
<Drawer
|
||||
placement="right"
|
||||
bind:open={metadataOpen}
|
||||
|
||||
@@ -11,9 +11,14 @@
|
||||
import LabelsInput from './LabelsInput.svelte'
|
||||
import InheritedLabels from './InheritedLabels.svelte'
|
||||
import Badge from './common/badge/Badge.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
summary?: string
|
||||
@@ -50,7 +55,7 @@
|
||||
editSummary = summary ?? ''
|
||||
editPath = path ?? ''
|
||||
labelsDirty = false
|
||||
own = isOwner(path ?? '', $userStore, $operatingWorkspace)
|
||||
own = isOwner(path ?? '', actingUser, $operatingWorkspace)
|
||||
onBehalfOfEmail = undefined
|
||||
if (kind === 'flow' && $operatingWorkspace && path) {
|
||||
checkFlowOnBehalfOf($operatingWorkspace, path).then((email) => {
|
||||
|
||||
@@ -9,14 +9,19 @@
|
||||
import ObjectViewer from './propertyPicker/ObjectViewer.svelte'
|
||||
import { CheckCircle2, XCircle } from 'lucide-svelte'
|
||||
import { JobService, type Job, type WorkflowStatus } from '$lib/gen'
|
||||
import { enterpriseLicense, userStore } from '$lib/stores'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import { Button } from '$lib/components/common'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import SchemaForm from '$lib/components/SchemaForm.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
flow_status: Record<string, WorkflowStatus>
|
||||
@@ -263,7 +268,7 @@
|
||||
<span class="text-tertiary">{msToSec(v.duration_ms ?? 0)}s</span>
|
||||
{/if}
|
||||
</div>
|
||||
{#if canApprove && selfApprovalDisabled && $userStore?.is_admin}
|
||||
{#if canApprove && selfApprovalDisabled && actingUser?.is_admin}
|
||||
<div class="mt-1 ml-5 text-yellow-600 text-2xs">
|
||||
Self-approval is disabled but allowed because you are an admin/owner
|
||||
</div>
|
||||
|
||||
@@ -26,9 +26,14 @@
|
||||
import { canUserBypassRuleKind, protectionRulesState } from '$lib/workspaceProtectionRules.svelte'
|
||||
import { FRONTEND_SDK_SCOPES } from '$lib/components/raw_apps/sdkScopes'
|
||||
import { logFeatureUsage } from '$lib/utils/featureUsage'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspaceStore = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
const WM_DEPLOYERS_GROUP = 'wm_deployers'
|
||||
|
||||
@@ -84,20 +89,20 @@
|
||||
|
||||
const opWs = $derived(operatingWorkspace ?? $operatingWorkspaceStore)
|
||||
|
||||
let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
let isDeployer = $derived(actingUser?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
// Admins always pass the backend check. For everyone else, fail closed
|
||||
// while the workspace protection rules are still loading so the toggle
|
||||
// is never briefly enabled for a user the rules will end up restricting.
|
||||
let rulesetsLoaded = $derived(protectionRulesState.rulesets !== undefined)
|
||||
let canSetAnonymous = $derived(
|
||||
!!$userStore?.is_admin ||
|
||||
!!$userStore?.is_super_admin ||
|
||||
!!actingUser?.is_admin ||
|
||||
!!actingUser?.is_super_admin ||
|
||||
(rulesetsLoaded &&
|
||||
canUserBypassRuleKind('RestrictAnonymousAppDeployment', $userStore ?? undefined))
|
||||
)
|
||||
let canSetGuest = $derived(
|
||||
!!$userStore?.is_admin ||
|
||||
!!$userStore?.is_super_admin ||
|
||||
!!actingUser?.is_admin ||
|
||||
!!actingUser?.is_super_admin ||
|
||||
(rulesetsLoaded &&
|
||||
canUserBypassRuleKind('RestrictGuestAppDeployment', $userStore ?? undefined))
|
||||
)
|
||||
@@ -144,7 +149,7 @@
|
||||
setPublishState()
|
||||
}
|
||||
}
|
||||
let canPreserve = $derived(!!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer)
|
||||
let canPreserve = $derived(!!actingUser?.is_admin || !!actingUser?.is_super_admin || isDeployer)
|
||||
let savedOnBehalfOfEmail = $derived(savedApp?.policy?.on_behalf_of_email)
|
||||
let savedOnBehalfOf = $derived(savedApp?.policy?.on_behalf_of)
|
||||
let onBehalfOfChoice: OnBehalfOfChoice = $state(undefined)
|
||||
@@ -597,7 +602,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="mt-4">
|
||||
{#if !($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
{#if !(actingUser?.is_admin || actingUser?.is_super_admin)}
|
||||
<Alert type="warning" title="Admin only" size="xs">
|
||||
Custom path can only be set by workspace admins
|
||||
</Alert>
|
||||
@@ -618,7 +623,7 @@
|
||||
options={{
|
||||
right: 'Use a custom URL'
|
||||
}}
|
||||
disabled={!$enterpriseLicense || !($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
disabled={!$enterpriseLicense || !(actingUser?.is_admin || actingUser?.is_super_admin)}
|
||||
/>
|
||||
|
||||
{#if customPath !== undefined}
|
||||
@@ -626,7 +631,7 @@
|
||||
<div>Custom path</div>
|
||||
</div>
|
||||
<input
|
||||
disabled={!($userStore?.is_admin || $userStore?.is_super_admin)}
|
||||
disabled={!(actingUser?.is_admin || actingUser?.is_super_admin)}
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
bind:value={customPath}
|
||||
|
||||
@@ -48,7 +48,10 @@
|
||||
import AssetRunsPanel from './AssetRunsPanel.svelte'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
// Regular selection — loads the script by path for inline editing.
|
||||
@@ -662,7 +665,7 @@
|
||||
// hand-rolled here because we need *two* confirm buttons, not one.
|
||||
let removeOpen = $state(false)
|
||||
let removing = $state(false)
|
||||
let canHardDelete = $derived(!!($userStore?.is_admin || $userStore?.is_super_admin))
|
||||
let canHardDelete = $derived(!!(actingUser?.is_admin || actingUser?.is_super_admin))
|
||||
|
||||
// React to the parent's remove-signal counter and pop the same modal
|
||||
// the in-pane trash button uses. Skipped for drafts (the parent calls
|
||||
|
||||
+6
-3
@@ -9,9 +9,12 @@
|
||||
import { Bot, Star } from 'lucide-svelte'
|
||||
import ToggleButtonGroup from '../toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '../toggleButton-v2/ToggleButton.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import Badge from '../badge/Badge.svelte'
|
||||
import type { LinkedAgentDraft } from '$lib/components/flows/linkedAgentDrafts'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
open?: boolean
|
||||
@@ -80,8 +83,8 @@
|
||||
// Creating http trigger is forbidden for non-admin users
|
||||
const adminOnly =
|
||||
trigger.type === 'http' &&
|
||||
!$userStore?.is_admin &&
|
||||
!$userStore?.is_super_admin &&
|
||||
!actingUser?.is_admin &&
|
||||
!actingUser?.is_super_admin &&
|
||||
trigger.isDraft
|
||||
|
||||
const invalidConfig = !trigger.draftConfig?.canSave
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
import {
|
||||
COPILOT_SESSION_MODEL_SETTING_NAME,
|
||||
COPILOT_SESSION_PROVIDER_SETTING_NAME,
|
||||
COPILOT_SESSION_REASONING_SETTING_NAME,
|
||||
userStore
|
||||
COPILOT_SESSION_REASONING_SETTING_NAME
|
||||
} from '$lib/stores'
|
||||
import { storeLocalSetting, type Item } from '$lib/utils'
|
||||
import {
|
||||
@@ -32,9 +31,14 @@
|
||||
REASONING_OFF,
|
||||
type ReasoningProviderModel
|
||||
} from '../reasoningRegistry'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let {
|
||||
/** Whether this dropdown carries the custom-prompt entries. Off where the surface
|
||||
@@ -99,7 +103,7 @@
|
||||
// operations must key off this snapshot, not the reactive `mode`.
|
||||
let activeMode = $state(aiChatManager.mode)
|
||||
|
||||
let isAdmin = $derived(Boolean($userStore?.is_admin || $userStore?.is_super_admin))
|
||||
let isAdmin = $derived(Boolean(actingUser?.is_admin || actingUser?.is_super_admin))
|
||||
// True when the workspace has no AI providers of its own (it uses instance defaults).
|
||||
// In that case the backend never makes workspace custom_prompts effective, so a saved
|
||||
// workspace prompt would be dead config — mirror the settings page and surface it read-only.
|
||||
|
||||
@@ -24,6 +24,10 @@ writes whichever of the two changed, including the tab that is not on screen.
|
||||
import { Building2, ExternalLink, User } from 'lucide-svelte'
|
||||
import { untrack } from 'svelte'
|
||||
import { getAiChatManager } from './aiChatManagerContext'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let {
|
||||
ws,
|
||||
@@ -55,7 +59,7 @@ writes whichever of the two changed, including the tab that is not on screen.
|
||||
? `Applies to everyone in ${ws}.`
|
||||
: 'Stored in this browser and sent in every workspace, so they follow you rather than the workspace.'
|
||||
)
|
||||
// `$userStore.is_admin` is the role in the nav workspace, not necessarily in `ws`, so
|
||||
// `actingUser?.is_admin` is the role in the nav workspace, not necessarily in `ws`, so
|
||||
// the resolved role is keyed to the workspace it was read for, and an unresolved one
|
||||
// reads as no admin: offering the field and taking it away on resolve would discard
|
||||
// whatever was typed in between. Superadmin holds everywhere.
|
||||
@@ -69,8 +73,8 @@ writes whichever of the two changed, including the tab that is not on screen.
|
||||
let roleUnknown = $derived(roleRead !== undefined && !roleRead.user && !navIsTarget)
|
||||
let isAdmin = $derived(
|
||||
Boolean(
|
||||
$userStore?.is_super_admin ||
|
||||
(roleForTarget ? roleForTarget.is_admin : navIsTarget && $userStore?.is_admin)
|
||||
actingUser?.is_super_admin ||
|
||||
(roleForTarget ? roleForTarget.is_admin : navIsTarget && actingUser?.is_admin)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -40,10 +40,14 @@
|
||||
import FlowPanelPlacementPicker from './common/FlowPanelPlacementPicker.svelte'
|
||||
import { prefersSessionHandoff } from '../copilot/chat/global/gate'
|
||||
import { openSourceInSession } from '$lib/components/sessions/sessionSwitch.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
const { flowStore, selectionManager, pathStore, opWorkspace } =
|
||||
getContext<FlowEditorContext>('FlowEditorContext')
|
||||
// Flow paths repeat across workspaces, and a session keeps every tab it has visited alive, so two
|
||||
@@ -407,7 +411,7 @@
|
||||
if (
|
||||
!sessionScopedManager &&
|
||||
sessionOpen &&
|
||||
prefersSessionHandoff($userStore?.operator)
|
||||
prefersSessionHandoff(actingUser?.operator)
|
||||
) {
|
||||
void openSourceInSession(sessionOpen, {
|
||||
previewParams: { selected: detail.moduleId },
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import FlowScriptPickerQuick from '../pickers/FlowScriptPickerQuick.svelte'
|
||||
import { defaultScriptLanguages, processInlineLangs } from '$lib/scripts'
|
||||
import { defaultScripts, enterpriseLicense, hubBaseUrlStore, userStore } from '$lib/stores'
|
||||
import { defaultScripts, enterpriseLicense, hubBaseUrlStore } from '$lib/stores'
|
||||
import type { SupportedLanguage } from '$lib/common'
|
||||
import { createEventDispatcher, getContext, untrack } from 'svelte'
|
||||
import type { FlowBuilderWhitelabelCustomUi } from '$lib/components/custom_ui'
|
||||
@@ -28,9 +28,14 @@
|
||||
canHaveApproval,
|
||||
canHaveFailure
|
||||
} from '$lib/script_helpers'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -397,7 +402,7 @@
|
||||
<div class="text-2xs font-normal text-secondary ml-2"
|
||||
>New {selectedKind != 'script' ? selectedKind + ' ' : ''}script</div
|
||||
>
|
||||
{#if $userStore?.is_admin || $userStore?.is_super_admin}
|
||||
{#if actingUser?.is_admin || actingUser?.is_super_admin}
|
||||
{#if !openScriptSettings}
|
||||
<Button
|
||||
onClick={() => (openScriptSettings = true)}
|
||||
|
||||
@@ -31,9 +31,14 @@
|
||||
type OnBehalfOfChoice
|
||||
} from '$lib/components/OnBehalfOfSelector.svelte'
|
||||
import { modulesWithRetryOrSleep, SAME_WORKER_INCOMPATIBLE_MSG } from '../utils.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
noEditor: boolean
|
||||
@@ -55,8 +60,8 @@
|
||||
} = getContext<FlowEditorContext>('FlowEditorContext')
|
||||
|
||||
const WM_DEPLOYERS_GROUP = 'wm_deployers'
|
||||
let isDeployer = $derived($userStore?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
let canPreserve = $derived(!!$userStore?.is_admin || !!$userStore?.is_super_admin || isDeployer)
|
||||
let isDeployer = $derived(actingUser?.groups?.includes(WM_DEPLOYERS_GROUP) ?? false)
|
||||
let canPreserve = $derived(!!actingUser?.is_admin || !!actingUser?.is_super_admin || isDeployer)
|
||||
let onBehalfOfChoice: OnBehalfOfChoice = $state(undefined)
|
||||
let customOnBehalfOfEmail: string = $state('')
|
||||
let myPermissionedAs = $derived($userStore?.username ? `u/${$userStore.username}` : undefined)
|
||||
|
||||
@@ -207,10 +207,13 @@
|
||||
import type { Edge, Node } from '@xyflow/svelte'
|
||||
|
||||
import { getNodeColorClasses, NODE } from '../../util'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { slide } from 'svelte/transition'
|
||||
import AssetColumnBadges from '$lib/components/assets/AssetColumnBadges.svelte'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
data: AssetN['data']
|
||||
@@ -279,7 +282,7 @@
|
||||
Could not find resource
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
{:else if isSelected && assetCanBeExplored(data.asset, cachedResourceMetadata) && !$userStore?.operator}
|
||||
{:else if isSelected && assetCanBeExplored(data.asset, cachedResourceMetadata) && !actingUser?.operator}
|
||||
<div transition:slide={{ axis: 'x', duration: 100 }}>
|
||||
<ExploreAssetButton
|
||||
btnClasses="rounded-none"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { dirname, join, relative, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
@@ -59,31 +59,29 @@ function reachableComponents(): string[] {
|
||||
|
||||
// Module scripts have no component context to read the operating workspace from, so what
|
||||
// they fetch takes its workspace explicitly; only instance code and markup are checked.
|
||||
function readsNavigationStore(file: string): boolean {
|
||||
const source = readFileSync(join(components, file), 'utf-8')
|
||||
function instanceCode(file: string): string {
|
||||
return readFileSync(join(components, file), 'utf-8')
|
||||
.replace(/<script\b[^>]*\bmodule\b[^>]*>[\s\S]*?<\/script>/g, '')
|
||||
.replace(/<!--[\s\S]*?-->|\/\*[\s\S]*?\*\/|(^|[^:])\/\/.*$/gm, '$1')
|
||||
return /\$workspaceStore\b|\bget\(workspaceStore\)/.test(source)
|
||||
}
|
||||
|
||||
describe('trigger editors', () => {
|
||||
it('judge permissions by the acting user, not the navigation one', () => {
|
||||
// `$userStore` describes the navigation workspace: a session tab editing a fork would
|
||||
// enable or disable its controls by the parent's roles. `useOperatingUser()` answers for
|
||||
// the workspace the editor acts on.
|
||||
const editors = readdirSync(join(components, 'triggers'), {
|
||||
recursive: true,
|
||||
encoding: 'utf-8'
|
||||
})
|
||||
.filter((f) => f.endsWith('EditorInner.svelte'))
|
||||
.map((f) => join('triggers', f))
|
||||
expect(editors.length).toBeGreaterThan(10)
|
||||
const offenders = editors.filter((f) =>
|
||||
/\$userStore\b/.test(readFileSync(join(components, f), 'utf-8'))
|
||||
)
|
||||
expect(offenders).toEqual([])
|
||||
})
|
||||
})
|
||||
function readsNavigationStore(file: string): boolean {
|
||||
return /\$workspaceStore\b|\bget\(workspaceStore\)/.test(instanceCode(file))
|
||||
}
|
||||
|
||||
// Roles are per workspace, so a permission judged from `$userStore` inside a fork's editor
|
||||
// answers about the parent: it disables edits the user may make, or offers ones the backend
|
||||
// then refuses. Identity (username, email) is the same person in either, and stays.
|
||||
const PERMISSION_READ =
|
||||
/canWrite\((?:[^()]|\([^()]*\))*?\$userStore|isOwner\((?:[^()]|\([^()]*\))*?\$userStore|\$userStore(?:\?|!)?\.(?:is_admin|is_super_admin|operator|folders|groups)\b/
|
||||
|
||||
// Components that judge by the navigation user on purpose, with why.
|
||||
const NAVIGATION_JUDGES: Record<string, string> = {
|
||||
'FolderPicker.svelte':
|
||||
'resolves the target workspace’s member itself, and asks `$userStore` only for the navigation one',
|
||||
'FolderEditor.svelte':
|
||||
'resolves the target workspace’s member itself, and asks `$userStore` only for the navigation one'
|
||||
}
|
||||
|
||||
describe('components under a session editor', () => {
|
||||
it('read the operating workspace, not the navigation store', () => {
|
||||
@@ -93,4 +91,13 @@ describe('components under a session editor', () => {
|
||||
const offenders = reachable.filter((f) => !(f in NAVIGATION_READERS) && readsNavigationStore(f))
|
||||
expect(offenders).toEqual([])
|
||||
})
|
||||
|
||||
it('judge permissions by the user acting in that workspace', () => {
|
||||
const reachable = reachableComponents()
|
||||
expect(reachable).toContain('triggers/PermissionedAsLine.svelte')
|
||||
const offenders = reachable.filter(
|
||||
(f) => !(f in NAVIGATION_JUDGES) && PERMISSION_READ.test(instanceCode(f))
|
||||
)
|
||||
expect(offenders).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,9 +74,14 @@
|
||||
import type { RawAppData } from './dataTableRefUtils'
|
||||
import { editInForkAllowed, editInForkLabel, openEditInFork } from '$lib/utils/editInFork'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
// async function hash(message) {
|
||||
// try {
|
||||
@@ -445,7 +450,7 @@
|
||||
// custom_path requires admin so to accept update without it, we need to send as undefined when non-admin (when undefined, it will be ignored)
|
||||
// it also means that customPath needs to be set to '' instead of undefined to unset it (when admin)
|
||||
custom_path:
|
||||
$userStore?.is_admin || $userStore?.is_super_admin ? (customPath ?? '') : undefined,
|
||||
actingUser?.is_admin || actingUser?.is_super_admin ? (customPath ?? '') : undefined,
|
||||
labels
|
||||
},
|
||||
js,
|
||||
|
||||
@@ -29,9 +29,14 @@
|
||||
import { userStore } from '$lib/stores'
|
||||
import { isHubFlowPath } from '$lib/utils'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
let opWs = $derived($operatingWorkspace)
|
||||
|
||||
type RunnableWithInlineScript = RunnableWithFields & {
|
||||
@@ -133,7 +138,7 @@
|
||||
case 'email':
|
||||
return $userStore?.email ?? ''
|
||||
case 'groups':
|
||||
return $userStore?.groups ?? []
|
||||
return actingUser?.groups ?? []
|
||||
case 'workspace':
|
||||
return opWs ?? ''
|
||||
case 'author':
|
||||
|
||||
@@ -14,9 +14,14 @@
|
||||
import type { InputType } from '../apps/inputType'
|
||||
import Select from '$lib/components/select/Select.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
let opWs = $derived($operatingWorkspace)
|
||||
|
||||
// Build ctx properties with current user's actual values
|
||||
@@ -30,7 +35,7 @@
|
||||
{
|
||||
value: 'groups',
|
||||
label: 'Groups',
|
||||
subtitle: `string[] — ${JSON.stringify($userStore?.groups ?? [])}`
|
||||
subtitle: `string[] — ${JSON.stringify(actingUser?.groups ?? [])}`
|
||||
},
|
||||
{
|
||||
value: 'workspace',
|
||||
|
||||
@@ -31,9 +31,14 @@
|
||||
import { flowPathToHref } from '$lib/scripts'
|
||||
import { slide } from 'svelte/transition'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
job: Job
|
||||
@@ -499,7 +504,7 @@
|
||||
<div class="flex items-baseline flex-wrap gap-x-2 gap-y-1">
|
||||
<JobStatus {job} />
|
||||
|
||||
{#if isJobResolvable(job) && !$userStore?.operator}
|
||||
{#if isJobResolvable(job) && !actingUser?.operator}
|
||||
<!-- No startIcon on these: the row is baseline-aligned, and a Button is
|
||||
itself a flex container whose baseline comes from its icon rather
|
||||
than its label, which sits the text ~3px above the badges. Text
|
||||
|
||||
@@ -41,9 +41,12 @@
|
||||
import { AIBtnClasses } from '$lib/components/copilot/chat/AIButtonStyle'
|
||||
import { prefersSessionHandoff } from '$lib/components/copilot/chat/global/gate'
|
||||
import { copilotInfo } from '$lib/aiStore'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { openSourceInSession } from './sessionSwitch.svelte'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let {
|
||||
source,
|
||||
@@ -83,7 +86,7 @@
|
||||
const show = $derived(
|
||||
!inSessionPanel &&
|
||||
!!(source?.target || source?.page) &&
|
||||
prefersSessionHandoff($userStore?.operator)
|
||||
prefersSessionHandoff(actingUser?.operator)
|
||||
)
|
||||
|
||||
// Not $state: only read inside open() as a re-entrancy latch, never rendered.
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
import Label from '../Label.svelte'
|
||||
import AIPromptsModal from './AIPromptsModal.svelte'
|
||||
import { ExternalLink, Settings } from 'lucide-svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
const USER_CUSTOM_PROMPTS_KEY = 'userCustomAIPrompts'
|
||||
|
||||
@@ -49,7 +52,7 @@
|
||||
<div class="flex flex-col gap-4">
|
||||
<p class="text-xs text-secondary">
|
||||
Customize AI behavior with system prompts. These are stored locally in your browser and
|
||||
apply in addition to {#if $userStore?.is_admin || $userStore?.is_super_admin}<a
|
||||
apply in addition to {#if actingUser?.is_admin || actingUser?.is_super_admin}<a
|
||||
href="/workspace_settings?tab=ai"
|
||||
>workspace-level prompts <ExternalLink size={12} class="inline-block" /></a
|
||||
>{:else}workspace-level prompts{/if}.
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
import { useFolderDefaultPermissionedAs } from '$lib/components/useFolderDefaultPermissionedAs.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { AlertTriangle } from 'lucide-svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
interface Props {
|
||||
/** Current permissioned_as value from the trigger (e.g., 'u/admin') */
|
||||
@@ -23,10 +26,12 @@
|
||||
|
||||
let { permissionedAs, onPermissionedAsChange, path = undefined }: Props = $props()
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
const wsId = $derived($operatingWorkspace)
|
||||
|
||||
const canPreserve = $derived(
|
||||
$userStore?.is_admin || ($userStore?.groups ?? []).includes('wm_deployers')
|
||||
actingUser?.is_admin || (actingUser?.groups ?? []).includes('wm_deployers')
|
||||
)
|
||||
|
||||
const myPermissionedAs = $derived($userStore?.username ? `u/${$userStore.username}` : undefined)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import { Alert } from '$lib/components/common'
|
||||
import Required from '$lib/components/Required.svelte'
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
// import { page } from '$app/state'
|
||||
import { getEmailAddress, getEmailDomain } from './utils'
|
||||
import { isCloudHosted } from '$lib/cloud'
|
||||
@@ -11,7 +10,10 @@
|
||||
import { untrack } from 'svelte'
|
||||
import { EmailTriggerService } from '$lib/gen'
|
||||
import ClipboardPanel from '$lib/components/details/ClipboardPanel.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
interface Props {
|
||||
initialTriggerPath?: string | undefined
|
||||
dirtyLocalPart?: boolean
|
||||
@@ -36,6 +38,8 @@
|
||||
showTestingBadge = false
|
||||
}: Props = $props()
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
const wsId = $derived($operatingWorkspace)
|
||||
|
||||
let validateTimeout: number | undefined = undefined
|
||||
@@ -95,7 +99,7 @@
|
||||
local_part === undefined && (local_part = '')
|
||||
})
|
||||
|
||||
let userIsAdmin = $derived($userStore?.is_admin || $userStore?.is_super_admin)
|
||||
let userIsAdmin = $derived(actingUser?.is_admin || actingUser?.is_super_admin)
|
||||
let userCanEditConfig = $derived(userIsAdmin || isDraftOnly) // User can edit config if they are admin or if the trigger is a draft which will not be saved
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<script lang="ts">
|
||||
import EmailTriggerEditorInner from './EmailTriggerEditorInner.svelte'
|
||||
import Description from '$lib/components/Description.svelte'
|
||||
import { enterpriseLicense, userStore } from '$lib/stores'
|
||||
import { enterpriseLicense } from '$lib/stores'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import { onMount, type Snippet } from 'svelte'
|
||||
import { getEmailDomain } from './utils'
|
||||
import type { Trigger } from '../utils'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let emailTriggerEditor = $state<EmailTriggerEditorInner | null>(null)
|
||||
|
||||
@@ -73,7 +77,7 @@
|
||||
trigger can be configured with a specific local part.
|
||||
</Description>
|
||||
|
||||
{#if !$userStore?.is_admin && !$userStore?.is_super_admin && selectedTrigger.isDraft}
|
||||
{#if !actingUser?.is_admin && !actingUser?.is_super_admin && selectedTrigger.isDraft}
|
||||
<Alert title="Only workspace admins can create email triggers" type="info" size="xs" />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { base } from '$lib/base'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
|
||||
import { Button, Url } from '$lib/components/common'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
@@ -24,11 +23,16 @@
|
||||
import TestingBadge from '../testingBadge.svelte'
|
||||
import Select from '$lib/components/select/Select.svelte'
|
||||
import { safeSelectItems } from '$lib/components/select/utils.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
// Declared before `DEFAULT_PUSH_CONFIG` / the `base_endpoint` prop default,
|
||||
// which call `getBaseUrl()` (a `wsId` reader) during component init.
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
const wsId = $derived($operatingWorkspace)
|
||||
|
||||
let topic_items: string[] = $state([])
|
||||
@@ -152,14 +156,14 @@
|
||||
// Keyed on the loaded mode, not the live one: reading the live mode would make the toggle a
|
||||
// one-way door, disabling itself the moment a non-admin switched an inherited ADC trigger away.
|
||||
const canUseDefaultCredentials = $derived(
|
||||
$userStore?.is_admin === true || loaded_uses_default_credentials
|
||||
actingUser?.is_admin === true || loaded_uses_default_credentials
|
||||
)
|
||||
const hasCredentials = $derived(usesDefaultCredentials || !emptyStringTrimmed(gcp_resource_path))
|
||||
/** Saving re-provisions the subscription with the instance's credentials, so the backend runs
|
||||
* the admin check on every write, not only when the mode is switched. A non-admin who inherits
|
||||
* such a trigger can open it, so say why saving is unavailable instead of letting them hit a
|
||||
* bare 403. */
|
||||
const blockedByAdminGate = $derived(usesDefaultCredentials && $userStore?.is_admin !== true)
|
||||
const blockedByAdminGate = $derived(usesDefaultCredentials && actingUser?.is_admin !== true)
|
||||
|
||||
// One-shot on mount, so read the props rather than the derived: referencing `$derived` state
|
||||
// here captures its initial value anyway, and Svelte warns about it.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import Section from '$lib/components/Section.svelte'
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { HttpTriggerService, SettingService } from '$lib/gen'
|
||||
// import { page } from '$app/state'
|
||||
import { getHttpRoute } from './utils'
|
||||
@@ -13,7 +12,10 @@
|
||||
import TestingBadge from '../testingBadge.svelte'
|
||||
import { untrack } from 'svelte'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
interface Props {
|
||||
initialTriggerPath?: string | undefined
|
||||
@@ -43,6 +45,8 @@
|
||||
showTestingBadge = false
|
||||
}: Props = $props()
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
const wsId = $derived($operatingWorkspace)
|
||||
|
||||
let validateTimeout: number | undefined = undefined
|
||||
@@ -107,7 +111,7 @@
|
||||
route_path === undefined && (route_path = '')
|
||||
})
|
||||
|
||||
let userIsAdmin = $derived($userStore?.is_admin || $userStore?.is_super_admin)
|
||||
let userIsAdmin = $derived(actingUser?.is_admin || actingUser?.is_super_admin)
|
||||
|
||||
let globalHttpWorkspacedRoute = $state(false)
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<script lang="ts">
|
||||
import RouteEditorInner from './RouteEditorInner.svelte'
|
||||
import Description from '$lib/components/Description.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { Alert } from '$lib/components/common'
|
||||
import { onMount } from 'svelte'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let routeEditor = $state<RouteEditorInner | null>(null)
|
||||
let {
|
||||
@@ -48,7 +51,7 @@
|
||||
specific HTTP method and path.</Description
|
||||
>
|
||||
|
||||
{#if !$userStore?.is_admin && !$userStore?.is_super_admin && selectedTrigger.isDraft}
|
||||
{#if !actingUser?.is_admin && !actingUser?.is_super_admin && selectedTrigger.isDraft}
|
||||
<Alert title="Non-admin users are limited to workspaced routes" type="info" size="xs" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
getTemplatePath,
|
||||
saveNativeTriggerFromCfg
|
||||
} from './utils'
|
||||
import { usedTriggerKinds, userStore } from '$lib/stores'
|
||||
import { usedTriggerKinds } from '$lib/stores'
|
||||
import { canWrite, emptyString, sendUserToast } from '$lib/utils'
|
||||
import { Button } from '$lib/components/common'
|
||||
import TextInput from '$lib/components/text_input/TextInput.svelte'
|
||||
@@ -27,9 +27,14 @@
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import type { Snippet } from 'svelte'
|
||||
import Alert from '$lib/components/common/alert/Alert.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
service: NativeServiceName
|
||||
@@ -215,7 +220,7 @@
|
||||
serviceConfig = (fullTrigger.service_config as Record<string, any>) || {}
|
||||
scriptPath = fullTrigger.script_path
|
||||
initialScriptPath = fullTrigger.script_path
|
||||
can_write = canWrite(fullTrigger.script_path, {}, $userStore)
|
||||
can_write = canWrite(fullTrigger.script_path, {}, actingUser)
|
||||
summary = fullTrigger.summary ?? ''
|
||||
externalData = fullTrigger.external_data
|
||||
externalError = fullTrigger.external_error ?? undefined
|
||||
@@ -518,7 +523,7 @@
|
||||
bind:itemKind
|
||||
kinds={['script']}
|
||||
allowFlow={true}
|
||||
allowEdit={!$userStore?.operator}
|
||||
allowEdit={!actingUser?.operator}
|
||||
clearable
|
||||
/>
|
||||
{#if emptyString(scriptPath)}
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount } from 'svelte'
|
||||
import DarkModeObserver from '../DarkModeObserver.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { AppService, type ListableApp } from '$lib/gen'
|
||||
import { canWrite } from '$lib/utils'
|
||||
import type { AppViewerContext } from '../apps/types'
|
||||
import Alert from '../common/alert/Alert.svelte'
|
||||
import Select from '../select/Select.svelte'
|
||||
import { useOperatingWorkspace } from '$lib/components/operatingWorkspace.svelte'
|
||||
import {
|
||||
useOperatingUser,
|
||||
useOperatingWorkspace
|
||||
} from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingWorkspace = useOperatingWorkspace()
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
interface Props {
|
||||
value?: string
|
||||
@@ -28,9 +32,9 @@
|
||||
).map((app: ListableApp) => {
|
||||
return {
|
||||
canWrite:
|
||||
canWrite(app.path!, app.extra_perms!, $userStore) &&
|
||||
canWrite(app.path!, app.extra_perms!, actingUser) &&
|
||||
app.workspace_id == $operatingWorkspace &&
|
||||
!$userStore?.operator,
|
||||
!actingUser?.operator,
|
||||
...app
|
||||
}
|
||||
})
|
||||
|
||||
@@ -26,7 +26,11 @@
|
||||
import Portal from '$lib/components/Portal.svelte'
|
||||
import DropdownV2 from '../DropdownV2.svelte'
|
||||
import MissingWorkerTagAlert from '../jobs/MissingWorkerTagAlert.svelte'
|
||||
import { superadmin, userStore } from '$lib/stores'
|
||||
import { superadmin } from '$lib/stores'
|
||||
import { useOperatingUser } from '$lib/components/operatingWorkspace.svelte'
|
||||
|
||||
const operatingUser = useOperatingUser()
|
||||
const actingUser = $derived(operatingUser.current)
|
||||
|
||||
let {
|
||||
workspace,
|
||||
@@ -56,7 +60,7 @@
|
||||
let generatingInitial = $state(false)
|
||||
|
||||
// Only workspace admins and super admins can opt a data table in or out.
|
||||
const canManage = $derived(!!$userStore?.is_admin || !!$superadmin)
|
||||
const canManage = $derived(!!actingUser?.is_admin || !!$superadmin)
|
||||
|
||||
let newMigrationModal = $state<NewDataTableMigrationModal | undefined>(undefined)
|
||||
let newMigrationOpen = $state(false)
|
||||
|
||||
Reference in New Issue
Block a user