fix(dashboard): Config page header shows the switched profile's config.yaml path (#44374)
The Config page read config_path from /api/status, which is machine-global and always reports the profile the dashboard process was started under. After switching profiles with the global switcher, the header kept showing the old profile's path (e.g. /root/.hermes/profiles/worker_1/config.yaml) even though reads/writes correctly targeted the new profile. Fix: /api/config/raw now returns the resolved path alongside the YAML (resolved inside _profile_scope, so it follows ?profile=). ConfigPage prefers that scoped path and only falls back to /api/status for old servers. ProfileKeyedRoutes already remounts the page on switch, so the header refreshes immediately.
This commit is contained in:
parent
9121834b31
commit
c7bfc938d5
@ -9374,11 +9374,18 @@ class RawConfigUpdate(BaseModel):
|
|||||||
|
|
||||||
@app.get("/api/config/raw")
|
@app.get("/api/config/raw")
|
||||||
async def get_config_raw(profile: Optional[str] = None):
|
async def get_config_raw(profile: Optional[str] = None):
|
||||||
|
"""Raw config.yaml text plus its resolved path.
|
||||||
|
|
||||||
|
``path`` is resolved inside ``_profile_scope`` so the Config page header
|
||||||
|
shows the file the switched profile actually reads/writes — /api/status's
|
||||||
|
``config_path`` is machine-global and always reports the dashboard
|
||||||
|
process's own profile, which is wrong under the global profile switcher.
|
||||||
|
"""
|
||||||
with _profile_scope(profile):
|
with _profile_scope(profile):
|
||||||
path = get_config_path()
|
path = get_config_path()
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return {"yaml": ""}
|
return {"yaml": "", "path": str(path)}
|
||||||
return {"yaml": path.read_text(encoding="utf-8")}
|
return {"yaml": path.read_text(encoding="utf-8"), "path": str(path)}
|
||||||
|
|
||||||
|
|
||||||
@app.put("/api/config/raw")
|
@app.put("/api/config/raw")
|
||||||
|
|||||||
@ -92,6 +92,16 @@ class TestProfileScopedConfig:
|
|||||||
resp = client.get("/api/config/raw")
|
resp = client.get("/api/config/raw")
|
||||||
assert "Io/Volcano" not in resp.json()["yaml"]
|
assert "Io/Volcano" not in resp.json()["yaml"]
|
||||||
|
|
||||||
|
def test_config_raw_path_reflects_requested_profile(self, client, isolated_profiles):
|
||||||
|
"""The Config page header shows /api/config/raw's ``path`` — it must
|
||||||
|
point at the SWITCHED profile's config.yaml, not the dashboard's own
|
||||||
|
(the stale-path bug reported after the profile unification launch)."""
|
||||||
|
resp = client.get("/api/config/raw", params={"profile": "worker_beta"})
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json()["path"] == str(isolated_profiles["worker_beta"] / "config.yaml")
|
||||||
|
resp = client.get("/api/config/raw")
|
||||||
|
assert resp.json()["path"] == str(isolated_profiles["default"] / "config.yaml")
|
||||||
|
|
||||||
def test_unknown_profile_404(self, client, isolated_profiles):
|
def test_unknown_profile_404(self, client, isolated_profiles):
|
||||||
resp = client.get("/api/config", params={"profile": "ghost"})
|
resp = client.get("/api/config", params={"profile": "ghost"})
|
||||||
assert resp.status_code == 404
|
assert resp.status_code == 404
|
||||||
|
|||||||
@ -432,7 +432,7 @@ export const api = {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ config }),
|
body: JSON.stringify({ config }),
|
||||||
}),
|
}),
|
||||||
getConfigRaw: () => fetchJSON<{ yaml: string }>("/api/config/raw"),
|
getConfigRaw: () => fetchJSON<{ yaml: string; path?: string }>("/api/config/raw"),
|
||||||
saveConfigRaw: (yaml_text: string) =>
|
saveConfigRaw: (yaml_text: string) =>
|
||||||
fetchJSON<{ ok: boolean }>("/api/config/raw", {
|
fetchJSON<{ ok: boolean }>("/api/config/raw", {
|
||||||
method: "PUT",
|
method: "PUT",
|
||||||
|
|||||||
@ -177,9 +177,19 @@ export default function ConfigPage() {
|
|||||||
.getDefaults()
|
.getDefaults()
|
||||||
.then(setDefaults)
|
.then(setDefaults)
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
|
// getConfigRaw is profile-scoped (fetchJSON appends ?profile=), so its
|
||||||
|
// `path` reflects the switched profile's config.yaml. /api/status's
|
||||||
|
// config_path is machine-global (the dashboard's own profile) — wrong
|
||||||
|
// header under the global profile switcher, so it's only a fallback.
|
||||||
|
api
|
||||||
|
.getConfigRaw()
|
||||||
|
.then((resp) => {
|
||||||
|
if (resp.path) setConfigPath(resp.path);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
api
|
api
|
||||||
.getStatus()
|
.getStatus()
|
||||||
.then((resp) => setConfigPath(resp.config_path))
|
.then((resp) => setConfigPath((prev) => prev ?? resp.config_path))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user