Fix config hot reload and release v0.2.1

This commit is contained in:
LPF
2026-03-11 22:14:45 +08:00
parent 40fd8fe104
commit 045927f6d1
9 changed files with 184 additions and 44 deletions

View File

@@ -9,9 +9,11 @@ type UI = {
type UseConfigSaveActionArgs = {
cfg: any;
cfgRaw: string;
loadConfig: (force?: boolean, tokenOverride?: string) => Promise<any>;
q: string;
setBaseline: React.Dispatch<React.SetStateAction<any>>;
setConfigEditing: (editing: boolean) => void;
setToken: (token: string) => void;
setShowDiff: React.Dispatch<React.SetStateAction<boolean>>;
showRaw: boolean;
t: (key: string, options?: any) => string;
@@ -21,9 +23,11 @@ type UseConfigSaveActionArgs = {
export function useConfigSaveAction({
cfg,
cfgRaw,
loadConfig,
q,
setBaseline,
setConfigEditing,
setToken,
setShowDiff,
showRaw,
t,
@@ -68,8 +72,14 @@ export function useConfigSaveAction({
throw new Error(result.data?.error || result.text || 'save failed');
}
const hasGatewayToken = typeof payload?.gateway?.token === 'string';
const nextToken = hasGatewayToken ? payload.gateway.token.trim() : '';
const reloaded = await loadConfig(true, nextToken || undefined);
if (hasGatewayToken) {
setToken(nextToken);
}
await ui.notify({ title: t('saved'), message: t('configSaved') });
setBaseline(cloneJSON(payload));
setBaseline(cloneJSON(reloaded ?? payload));
setConfigEditing(false);
setShowDiff(false);
} catch (error) {

View File

@@ -87,7 +87,7 @@ interface AppContextType {
refreshTaskQueue: () => Promise<void>;
refreshEKGSummary: () => Promise<void>;
refreshVersion: () => Promise<void>;
loadConfig: (force?: boolean) => Promise<void>;
loadConfig: (force?: boolean, tokenOverride?: string) => Promise<any>;
gatewayVersion: string;
webuiVersion: string;
compiledChannels: string[];
@@ -145,15 +145,20 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
const q = token ? `?token=${encodeURIComponent(token)}` : '';
const loadConfig = useCallback(async (force = false) => {
const loadConfig = useCallback(async (force = false, tokenOverride?: string) => {
let loadedConfig: any = null;
try {
const hotQ = q ? `${q}&include_hot_reload_fields=1` : '?include_hot_reload_fields=1';
const authQ = tokenOverride
? `?token=${encodeURIComponent(tokenOverride)}`
: q;
const hotQ = authQ ? `${authQ}&include_hot_reload_fields=1` : '?include_hot_reload_fields=1';
const r = await fetch(`/webui/api/config${hotQ}`);
if (!r.ok) throw new Error('Failed to load config');
const txt = await r.text();
try {
const parsed = JSON.parse(txt);
if (parsed && parsed.config) {
loadedConfig = parsed.config;
if (!configEditing || force) {
setCfg(parsed.config);
setCfgRaw(JSON.stringify(parsed.config, null, 2));
@@ -161,15 +166,23 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
setHotReloadFields(Array.isArray(parsed.hot_reload_fields) ? parsed.hot_reload_fields : []);
setHotReloadFieldDetails(Array.isArray(parsed.hot_reload_field_details) ? parsed.hot_reload_field_details : []);
} else {
loadedConfig = parsed || {};
if (!configEditing || force) {
setCfg(parsed || {});
setCfgRaw(txt);
}
}
} catch {
loadedConfig = null;
if (!configEditing || force) {
setCfgRaw(txt);
try { setCfg(JSON.parse(txt)); } catch { setCfg({}); }
try {
loadedConfig = JSON.parse(txt);
setCfg(loadedConfig);
} catch {
loadedConfig = {};
setCfg({});
}
}
}
setIsGatewayOnline(true);
@@ -177,6 +190,7 @@ export const AppProvider: React.FC<{ children: React.ReactNode }> = ({ children
setIsGatewayOnline(false);
console.error(e);
}
return loadedConfig;
}, [q, configEditing]);
const refreshNodes = useCallback(async () => {

View File

@@ -15,7 +15,7 @@ import { cloneJSON } from '../utils/object';
const Config: React.FC = () => {
const { t } = useTranslation();
const ui = useUI();
const { cfg, setCfg, cfgRaw, setCfgRaw, loadConfig, hotReloadFieldDetails, q, setConfigEditing } = useAppContext();
const { cfg, setCfg, cfgRaw, setCfgRaw, loadConfig, hotReloadFieldDetails, q, setConfigEditing, setToken } = useAppContext();
const [showRaw, setShowRaw] = useState(false);
const [basicMode, setBasicMode] = useState(true);
const [hotOnly, setHotOnly] = useState(false);
@@ -43,9 +43,11 @@ const Config: React.FC = () => {
const { saveConfig } = useConfigSaveAction({
cfg,
cfgRaw,
loadConfig,
q,
setBaseline,
setConfigEditing,
setToken,
setShowDiff,
showRaw,
t,
@@ -87,7 +89,10 @@ const Config: React.FC = () => {
basicMode={basicMode}
hotOnly={hotOnly}
onHotOnlyChange={setHotOnly}
onReload={async () => { await loadConfig(true); setTimeout(() => setBaseline(cloneJSON(cfg)), 0); }}
onReload={async () => {
const reloaded = await loadConfig(true);
setBaseline(cloneJSON(reloaded ?? cfg));
}}
onSearchChange={setSearch}
onShowDiff={() => setShowDiff(true)}
onToggleBasicMode={() => setBasicMode((value) => !value)}

View File

@@ -16,7 +16,7 @@ import { cloneJSON } from '../utils/object';
const Providers: React.FC = () => {
const { t } = useTranslation();
const ui = useUI();
const { cfg, setCfg, cfgRaw, loadConfig, q, setConfigEditing, providerRuntimeItems } = useAppContext();
const { cfg, setCfg, cfgRaw, loadConfig, q, setConfigEditing, providerRuntimeItems, setToken } = useAppContext();
const [newProxyName, setNewProxyName] = useState('');
const [runtimeAutoRefresh, setRuntimeAutoRefresh] = useState(true);
const [runtimeRefreshSec, setRuntimeRefreshSec] = useState(10);
@@ -134,9 +134,11 @@ const Providers: React.FC = () => {
const { saveConfig } = useConfigSaveAction({
cfg,
cfgRaw,
loadConfig,
q,
setBaseline,
setConfigEditing,
setToken,
setShowDiff,
showRaw: false,
t,
@@ -152,7 +154,10 @@ const Providers: React.FC = () => {
titleClassName="ui-text-primary"
actions={
<div className="flex items-center gap-2 flex-wrap justify-end">
<FixedButton onClick={async () => { await loadConfig(true); setTimeout(() => setBaseline(cloneJSON(cfg)), 0); }} label={t('reload')}>
<FixedButton onClick={async () => {
const reloaded = await loadConfig(true);
setBaseline(cloneJSON(reloaded ?? cfg));
}} label={t('reload')}>
<RefreshCw className="w-4 h-4" />
</FixedButton>
<Button onClick={() => setShowDiff(true)} size="sm">{t('configDiffPreview')}</Button>