Apply saved settings on the server after a save
A registry write is stored at once, but the running server only picks up most settings when it rebuilds its configuration, which it does on a write for directories and the default authentication alone. Everything else, a delivery schedule for one, sat unapplied until someone ran Management > Actions > Reload > Server settings by hand. Every x:<Type>/set now goes past a listener in the JMAP client. A write that changed a settings object queues the reload action it needs, and once no write has been in flight for 600 ms the queued actions go out together in one x:Action/set. A bulk edit or a page that saves several objects in a row costs one reload, not one per object; a save still in flight holds the reload back however long it takes. Which action a type needs lives in lib/settingsApply.ts. Certificates, lookup stores and blocked IPs have their own reload actions, and the full settings reload doesn't rebuild them. Directories and authentication, data read live or kept current by cache invalidation (accounts, domains, DKIM keys, tenants, roles, lists and the like), operations and records, and stores, which a reload never reopens, get none. A type the list doesn't know is reloaded: an unneeded reload costs a second, a missing one leaves a setting unapplied. If the server later applies these writes by itself, this becomes one extra, harmless reload per burst of saves. Applying straight after a save is safe because a reload is all or nothing: the new configuration replaces the running one only when every settings object builds. When one doesn't, the server keeps what it had and names the object and the problem. That now shows as a banner above the page, "Saved, but the server couldn't apply the settings: <reason>", naming the object with a link to it, and an Apply now button. It stays until a reload succeeds or it is dismissed. A failure that is already known is not retried on unrelated saves, only on Apply now or a save that needs a reload. On success a "Saved and applied" toast replaces the form's own "Saved successfully" and "Created successfully" for settings objects, so a save shows one message, not two. New strings, in src/i18n/en.json (the only catalogue) under settingsApply: applied, applyNow, dismiss, failed, failedObject, noAnswer, notConfirmed, openObject, stillRunning.
This commit is contained in:
@@ -12,6 +12,7 @@ import { apiFetch } from '@/services/api';
|
||||
import { logJmapExchange } from '@/lib/debug';
|
||||
import type { JmapMethodCall, JmapMethodResponse, JmapQueryResponse, JmapResponse } from '@/types/jmap';
|
||||
import type { Schema } from '@/types/schema';
|
||||
import { writesRegistry, writtenRegistryTypes } from '@/lib/settingsApply';
|
||||
|
||||
const JMAP_USING = [
|
||||
'urn:ietf:params:jmap:core',
|
||||
@@ -35,10 +36,44 @@ export function getAccountId(objectType: string): string {
|
||||
return activeAccountId;
|
||||
}
|
||||
|
||||
/**
|
||||
* inbuxa: told about every registry write, so that saved settings can be
|
||||
* applied on the server (settingsApplyStore). `started` comes before the
|
||||
* request goes out and `finished` after it settles, with the types it changed.
|
||||
*/
|
||||
export interface RegistryWriteListener {
|
||||
started(): void;
|
||||
finished(objectNames: string[]): void;
|
||||
}
|
||||
|
||||
let registryWriteListener: RegistryWriteListener | null = null;
|
||||
|
||||
export function setRegistryWriteListener(listener: RegistryWriteListener | null) {
|
||||
registryWriteListener = listener;
|
||||
}
|
||||
|
||||
export async function jmapRequest(
|
||||
methodCalls: JmapMethodCall[],
|
||||
signal?: AbortSignal,
|
||||
extraUsing: string[] = [],
|
||||
): Promise<JmapMethodResponse[]> {
|
||||
const listener = writesRegistry(methodCalls) ? registryWriteListener : null;
|
||||
if (!listener) return sendJmapRequest(methodCalls, signal, extraUsing);
|
||||
listener.started();
|
||||
let written: string[] = [];
|
||||
try {
|
||||
const responses = await sendJmapRequest(methodCalls, signal, extraUsing);
|
||||
written = writtenRegistryTypes(responses);
|
||||
return responses;
|
||||
} finally {
|
||||
listener.finished(written);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendJmapRequest(
|
||||
methodCalls: JmapMethodCall[],
|
||||
signal?: AbortSignal,
|
||||
extraUsing: string[] = [],
|
||||
): Promise<JmapMethodResponse[]> {
|
||||
const { apiUrl } = useAuthStore.getState();
|
||||
let path = apiUrl || '/jmap';
|
||||
|
||||
Reference in New Issue
Block a user