mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-13 14:50:59 +00:00
fix(frontend): preserve edited server drafts (#6156)
* fix(frontend): preserve edited server drafts * fix(frontend): retain Xray server projections * fix(frontend): keep draft controls internal * fix(frontend): rehydrate saved redacted settings * fix(frontend): order saved draft hydration * fix(frontend): preserve draft baselines on security saves --------- Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { HttpUtil, Msg } from '@/utils';
|
||||
@@ -6,8 +6,13 @@ import { parseMsg } from '@/utils/zodValidate';
|
||||
import { AllSetting } from '@/models/setting';
|
||||
import { AllSettingSchema, type AllSettingInput } from '@/schemas/setting';
|
||||
import { keys } from '@/api/queryKeys';
|
||||
import { useServerDraft } from '@/hooks/useServerDraft';
|
||||
|
||||
type SettingSavePayload = Partial<AllSetting> & Record<string, unknown>;
|
||||
type SettingSaveResult = {
|
||||
msg: Msg<unknown>;
|
||||
saved?: AllSetting;
|
||||
};
|
||||
|
||||
async function fetchAllSetting(): Promise<AllSettingInput | null> {
|
||||
const msg = await HttpUtil.post('/panel/api/setting/all', undefined, { silent: true });
|
||||
@@ -18,7 +23,6 @@ async function fetchAllSetting(): Promise<AllSettingInput | null> {
|
||||
|
||||
export function useAllSettings() {
|
||||
const queryClient = useQueryClient();
|
||||
const [draft, setDraft] = useState<AllSetting>(() => new AllSetting());
|
||||
const [extraSpinning, setExtraSpinning] = useState(false);
|
||||
|
||||
const query = useQuery({
|
||||
@@ -28,41 +32,54 @@ export function useAllSettings() {
|
||||
});
|
||||
|
||||
const server = useMemo(() => new AllSetting(query.data), [query.data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (query.data !== undefined) {
|
||||
setDraft(new AllSetting(query.data));
|
||||
}
|
||||
}, [query.data]);
|
||||
const { draft, setDraft, isDirty, markSaved } = useServerDraft(
|
||||
query.data === undefined ? undefined : server,
|
||||
(setting) => new AllSetting(setting),
|
||||
(left, right) => left.equals(right),
|
||||
);
|
||||
const allSetting = draft ?? server;
|
||||
|
||||
const updateSetting = useCallback((patch: Partial<AllSetting>) => {
|
||||
setDraft((prev) => {
|
||||
const next = new AllSetting(prev);
|
||||
const next = new AllSetting(prev ?? server);
|
||||
Object.assign(next, patch);
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
}, [server, setDraft]);
|
||||
|
||||
const saveMut = useMutation({
|
||||
mutationFn: async (next: SettingSavePayload): Promise<Msg<unknown>> => {
|
||||
const payload = { ...next };
|
||||
const body = AllSettingSchema.partial().safeParse(payload);
|
||||
mutationFn: async ({ payload, saved }: { payload: SettingSavePayload; saved?: AllSetting }): Promise<SettingSaveResult> => {
|
||||
const next = { ...payload };
|
||||
const body = AllSettingSchema.partial().safeParse(next);
|
||||
if (!body.success) {
|
||||
console.warn('[zod] setting/update body failed validation', body.error.issues);
|
||||
}
|
||||
return HttpUtil.post('/panel/api/setting/update', body.success ? { ...payload, ...body.data } : payload);
|
||||
const msg = await HttpUtil.post('/panel/api/setting/update', body.success ? { ...next, ...body.data } : next);
|
||||
return { msg, saved };
|
||||
},
|
||||
onSuccess: (msg) => {
|
||||
if (msg?.success) queryClient.invalidateQueries({ queryKey: keys.settings.all() });
|
||||
onSuccess: ({ msg, saved }) => {
|
||||
if (!msg?.success) return;
|
||||
if (saved) markSaved(saved);
|
||||
queryClient.invalidateQueries({ queryKey: keys.settings.all() });
|
||||
},
|
||||
});
|
||||
|
||||
const saveAll = useCallback(() => saveMut.mutateAsync({ ...draft }), [saveMut, draft]);
|
||||
const savePayload = useCallback((payload: SettingSavePayload) => saveMut.mutateAsync(payload), [saveMut]);
|
||||
const saveDisabled = useMemo(() => server.equals(draft), [server, draft]);
|
||||
const saveAll = useCallback(async () => {
|
||||
const saved = new AllSetting(allSetting);
|
||||
return (await saveMut.mutateAsync({ payload: { ...saved }, saved })).msg;
|
||||
}, [allSetting, saveMut]);
|
||||
const savePayload = useCallback(
|
||||
async (payload: SettingSavePayload) => {
|
||||
const saved = new AllSetting(allSetting);
|
||||
Object.assign(saved, payload);
|
||||
return (await saveMut.mutateAsync({ payload, saved })).msg;
|
||||
},
|
||||
[allSetting, saveMut],
|
||||
);
|
||||
const saveDisabled = !isDirty;
|
||||
|
||||
return {
|
||||
allSetting: draft,
|
||||
allSetting,
|
||||
updateSetting,
|
||||
fetched: query.data !== undefined,
|
||||
spinning: extraSpinning || saveMut.isPending,
|
||||
|
||||
Reference in New Issue
Block a user