PUT /panels/ carries a version and refuses a stale one with 409
The Panels dialog re-reads the list on 409. A panels.json without a version reads as 0.
This commit is contained in:
@@ -2933,6 +2933,11 @@ export const PanelsConfigSchema = {
|
||||
},
|
||||
type: 'array',
|
||||
title: 'Panels'
|
||||
},
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version',
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
@@ -2949,6 +2954,11 @@ export const PanelsPublicSchema = {
|
||||
type: 'array',
|
||||
title: 'Panels'
|
||||
},
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version',
|
||||
default: 0
|
||||
},
|
||||
frontend_host: {
|
||||
type: 'string',
|
||||
title: 'Frontend Host',
|
||||
|
||||
@@ -1946,6 +1946,11 @@ export class PanelsService {
|
||||
* A panel that disappears here takes its credential with it, so this is also
|
||||
* how a device is unpaired along with its assignment. Re-pairing one screen
|
||||
* and keeping the assignment is ``/{panel_id}/unpair`` below.
|
||||
*
|
||||
* The body carries the ``version`` it was based on, and a save someone else
|
||||
* has moved past is refused with 409, as a dashboard's is. Two clients each
|
||||
* writing back the whole list would otherwise drop each other's panels, and
|
||||
* a dropped panel revokes its screen at once.
|
||||
* @param data The data for the request.
|
||||
* @param data.requestBody
|
||||
* @returns PanelsPublic Successful Response
|
||||
|
||||
@@ -994,6 +994,7 @@ export type direction = 'in' | 'out';
|
||||
*/
|
||||
export type PanelsConfig = {
|
||||
panels?: Array<PanelDef>;
|
||||
version?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -1009,6 +1010,7 @@ export type PanelsConfig = {
|
||||
*/
|
||||
export type PanelsPublic = {
|
||||
panels?: Array<PanelDef>;
|
||||
version?: number;
|
||||
frontend_host?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -107,9 +107,17 @@ export function PanelsDialog() {
|
||||
const host = config?.frontend_host ?? ""
|
||||
|
||||
const write = (next: PanelsConfig) =>
|
||||
save.mutate(next, {
|
||||
onError: (error) => handleError.call(showErrorToast, error as ApiError),
|
||||
})
|
||||
save.mutate(
|
||||
{ ...next, version: config?.version },
|
||||
{
|
||||
onError: (error) =>
|
||||
(error as ApiError).status === 409
|
||||
? showErrorToast(
|
||||
"Someone else changed the panels first. Their version is shown now, so make the change again.",
|
||||
)
|
||||
: handleError.call(showErrorToast, error as ApiError),
|
||||
},
|
||||
)
|
||||
|
||||
const replace = (id: string, panel: PanelDef) =>
|
||||
write({ panels: panels.map((p) => (p.id === id ? panel : p)) })
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query"
|
||||
|
||||
import {
|
||||
ApiError,
|
||||
type DashboardDef_Input,
|
||||
DashboardsService,
|
||||
MessagesService,
|
||||
@@ -55,7 +56,13 @@ export const panelQueryOptions = (id: string) => ({
|
||||
queryFn: () => PanelsService.readPanel({ panelId: id }),
|
||||
})
|
||||
|
||||
/** Replace the panels. Superuser-only on the server. */
|
||||
/**
|
||||
* Replace the panels, carrying the version they were read at. Superuser-only
|
||||
* on the server.
|
||||
*
|
||||
* A 409 means someone else saved first, so the list is read again and the
|
||||
* next change is based on theirs.
|
||||
*/
|
||||
export function useSavePanels() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
@@ -65,6 +72,10 @@ export function useSavePanels() {
|
||||
queryClient.setQueryData(panelKeys.all, saved)
|
||||
queryClient.invalidateQueries({ queryKey: panelKeys.all })
|
||||
},
|
||||
onError: (error) => {
|
||||
if (error instanceof ApiError && error.status === 409)
|
||||
queryClient.invalidateQueries({ queryKey: panelKeys.all })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user