mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: a group member can be told where their item went, and a deploy names the head's author
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
5baae5c3d1
commit
0a6d49800e
@@ -1177,8 +1177,8 @@ async fn require_can_write_path(
|
||||
|
||||
/// Resolves to `Ok(())` if `authed` can read at `path`. Three layers:
|
||||
/// 1. admin → always.
|
||||
/// 2. Path-prefix match against own `u/{username}` or any folder in
|
||||
/// `authed.folders` (the precomputed read set, with groups + direct
|
||||
/// 2. Path-prefix match against own `u/{username}`, a group in `authed.groups`, or any
|
||||
/// folder in `authed.folders` (the precomputed read set, with groups + direct
|
||||
/// grants already factored in).
|
||||
/// 3. RLS-aware `SELECT 1` against the backing table — covers item-level
|
||||
/// extra_perms grants that bypass folder/owner checks.
|
||||
@@ -1203,6 +1203,10 @@ async fn require_can_read_path(
|
||||
if parts.len() >= 2 {
|
||||
match parts[0] {
|
||||
"u" if parts[1] == authed.username => return Ok(()),
|
||||
// As `require_can_write_path` and the `see_member` RLS policy read it: a
|
||||
// draft-only `g/` path has no row for the probe below to fall back on, so
|
||||
// without this a member cannot see a draft their group owns.
|
||||
"g" if authed.groups.iter().any(|g| g == parts[1]) => return Ok(()),
|
||||
"f" => {
|
||||
let folder = parts[1];
|
||||
if authed.folders.iter().any(|(name, _, _)| name == folder) {
|
||||
|
||||
@@ -111,8 +111,9 @@
|
||||
// event up through these runes-mode components silently drops it.
|
||||
onRestore?: (restoredApp: any) => void
|
||||
// Fired after a successful deploy, which keeps this editor open: `version` is what
|
||||
// the deploy wrote, for the next draft's fork base, and `head` what is deployed now.
|
||||
onDeploy?: (e: { version?: number; head?: number }) => void
|
||||
// the deploy wrote, for the next draft's fork base, and `head` what is deployed
|
||||
// now, with its author and time.
|
||||
onDeploy?: (e: { version?: number; head?: number; headBy?: string; headAt?: string }) => void
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -416,7 +417,12 @@
|
||||
if ($app) $app.parent_version = claimed
|
||||
// The route owns the pair the out-of-date prompt reads, and this editor stays open
|
||||
// across the deploy, so hand both over rather than leaving it on the old ones.
|
||||
onDeploy?.({ version: claimed, head: version })
|
||||
onDeploy?.({
|
||||
version: claimed,
|
||||
head: version,
|
||||
headBy: appHistory[0]?.created_by,
|
||||
headAt: appHistory[0]?.created_at
|
||||
})
|
||||
|
||||
closeSaveDrawer()
|
||||
sendUserToast('App deployed successfully')
|
||||
@@ -1042,7 +1048,13 @@
|
||||
itemKind="app"
|
||||
path={userDraftPath}
|
||||
draftOnly={newApp}
|
||||
{onResetToDeployed}
|
||||
onResetToDeployed={onResetToDeployed &&
|
||||
(async () => {
|
||||
// Back on the deployed version, so whatever the last deploy could not claim
|
||||
// no longer describes this editor.
|
||||
baseUnknown = false
|
||||
await onResetToDeployed()
|
||||
})}
|
||||
{loadedFromDraft}
|
||||
{othersDraftsCount}
|
||||
{onOpenOthersDrafts}
|
||||
|
||||
@@ -184,9 +184,10 @@ export interface AppEditorProps {
|
||||
// which does not propagate through these runes-mode components.
|
||||
onRestore?: (restoredApp: any) => void
|
||||
// Fired after a successful deploy, which keeps this editor open: `version` is what
|
||||
// the deploy wrote, for the next draft's fork base, and `head` what is deployed now.
|
||||
// The two differ when another deploy landed beside this one.
|
||||
onDeploy?: (e: { version?: number; head?: number }) => void
|
||||
// the deploy wrote, for the next draft's fork base, and `head` what is deployed now,
|
||||
// with its author and time. `version` and `head` differ when another deploy landed
|
||||
// beside this one, which is when the out-of-date prompt has something to say.
|
||||
onDeploy?: (e: { version?: number; head?: number; headBy?: string; headAt?: string }) => void
|
||||
}
|
||||
|
||||
export type App = {
|
||||
|
||||
@@ -105,7 +105,13 @@
|
||||
/** Fired after a successful deploy; the session preview reloads on it and the route
|
||||
* re-pins the draft's fork base. `version` is what this deploy wrote and `head`
|
||||
* what is deployed now: the two differ when another deploy landed beside it. */
|
||||
onDeploy?: (e: { path: string; version?: number; head?: number }) => void
|
||||
onDeploy?: (e: {
|
||||
path: string
|
||||
version?: number
|
||||
head?: number
|
||||
headBy?: string
|
||||
headAt?: string
|
||||
}) => void
|
||||
/** Initial collapsed state for the file/runnable sidebar. The user's
|
||||
* toggled preference is persisted under `sidebarStorageKey`; this prop
|
||||
* only seeds the very first open. */
|
||||
|
||||
@@ -155,8 +155,15 @@
|
||||
autosavePath?: string
|
||||
// Fired after a successful deploy; lets the session preview reload. `version` is
|
||||
// what this deploy wrote, for the next draft's fork base; `head` is what is
|
||||
// deployed now, and the two differ when another deploy landed beside this one.
|
||||
onDeploy?: (e: { path: string; version?: number; head?: number }) => void
|
||||
// deployed now, with its author and time, and the two differ when another deploy
|
||||
// landed beside this one.
|
||||
onDeploy?: (e: {
|
||||
path: string
|
||||
version?: number
|
||||
head?: number
|
||||
headBy?: string
|
||||
headAt?: string
|
||||
}) => void
|
||||
/** Surfaces the user-typed path (`newEditedPath`) up to the route
|
||||
* when (and only when) it differs from the deployed/seeded
|
||||
* `savedApp.path`. The route writes it into the autosaved raw-app
|
||||
@@ -575,7 +582,13 @@
|
||||
if (appPath !== npath) {
|
||||
onSavedNewAppPath?.(npath)
|
||||
}
|
||||
onDeploy?.({ path: npath, version: claimed, head: version })
|
||||
onDeploy?.({
|
||||
path: npath,
|
||||
version: claimed,
|
||||
head: version,
|
||||
headBy: appHistory[0]?.created_by,
|
||||
headAt: appHistory[0]?.created_at
|
||||
})
|
||||
}
|
||||
|
||||
async function setPublishState(message?: string) {
|
||||
@@ -899,7 +912,13 @@
|
||||
itemKind="raw_app"
|
||||
path={indicatorPath}
|
||||
draftOnly={newApp}
|
||||
{onResetToDeployed}
|
||||
onResetToDeployed={onResetToDeployed &&
|
||||
(async () => {
|
||||
// Back on the deployed version, so whatever the last deploy could not claim
|
||||
// no longer describes this editor.
|
||||
baseUnknown = false
|
||||
await onResetToDeployed()
|
||||
})}
|
||||
{loadedFromDraft}
|
||||
{othersDraftsCount}
|
||||
{onOpenOthersDrafts}
|
||||
|
||||
@@ -495,13 +495,16 @@
|
||||
{loadedFromDraft}
|
||||
othersDraftsCount={otherDraftsUsers.length}
|
||||
onOpenOthersDrafts={() => (othersModalOpen = true)}
|
||||
onDeploy={({ version, head }) => {
|
||||
// The editor stays open across a deploy, so the pair the out-of-date prompt
|
||||
// reads has to move with it: the base is what the deploy could claim it
|
||||
// wrote (unknown when another landed beside it), the head what is deployed.
|
||||
onDeploy={({ version, head, headBy, headAt }) => {
|
||||
// The editor stays open across a deploy, so what the out-of-date prompt reads
|
||||
// has to move with it: the base is what the deploy could claim it wrote
|
||||
// (unknown when another landed beside it), and the head is what is deployed,
|
||||
// named by whoever deployed it rather than by the page load's author.
|
||||
draftBaseVersion = version != null ? String(version) : undefined
|
||||
if (head != null) {
|
||||
deployedHeadVersion = String(head)
|
||||
deployedBy = headBy
|
||||
deployedAt = headAt
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -655,7 +655,7 @@
|
||||
}
|
||||
}
|
||||
: undefined}
|
||||
onDeploy={({ version, head }) => {
|
||||
onDeploy={({ version, head, headBy, headAt }) => {
|
||||
// The version this deploy wrote is the base the next autosave carries; the
|
||||
// head is what is deployed now. They differ when another deploy landed
|
||||
// beside this one, and the next draft is then behind from the start. A
|
||||
@@ -665,6 +665,9 @@
|
||||
draftBaseVersion = version != null ? String(version) : undefined
|
||||
if (head != null) {
|
||||
deployedHeadVersion = String(head)
|
||||
// Named by whoever deployed the head, not by the page load's author.
|
||||
deployedBy = headBy
|
||||
deployedAt = headAt
|
||||
}
|
||||
}}
|
||||
onResetToDeployed={reloadDeployed}
|
||||
|
||||
Reference in New Issue
Block a user