feat(map): add reference layers and stabilize workspace controls

This commit is contained in:
Codex
2026-07-25 13:12:24 +03:00
parent c859ae4db0
commit eaecce9f56
29 changed files with 1593 additions and 247 deletions
+1 -119
View File
@@ -118,13 +118,6 @@ interface FoundrySessionProfile {
};
}
interface CesiumIonSecretStatus {
configured: boolean;
updatedAt: string | null;
updatedBy: string | null;
verification?: "verified" | "failed" | "not-configured";
}
const materialDefaults: Record<NodedcTheme, MaterialDraft> = {
dark: {
panelHex: "#151517",
@@ -320,12 +313,7 @@ export function CatalogApp() {
const [toasts, setToasts] = useState<ToastItem[]>([]);
const toastSequenceRef = useRef(0);
const [sessionProfile, setSessionProfile] = useState<FoundrySessionProfile | null>(null);
const [platformSettingsOpen, setPlatformSettingsOpen] = useState(false);
const [foundrySettingsOpen, setFoundrySettingsOpen] = useState(false);
const [cesiumIonToken, setCesiumIonToken] = useState("");
const [cesiumIonStatus, setCesiumIonStatus] = useState<CesiumIonSecretStatus | null>(null);
const [cesiumIonSaveState, setCesiumIonSaveState] = useState<"idle" | "loading" | "saving" | "saved" | "error">("idle");
const [cesiumIonError, setCesiumIonError] = useState("");
const [mapTemplateLayout, setMapTemplateLayout] = useState<MapPageLayout | null>(null);
const [mapTemplateSaveState, setMapTemplateSaveState] = useState<"idle" | "loading" | "saving" | "saved" | "error">("loading");
const mapTemplatePreviewRef = useRef<MapFixturePreviewHandle>(null);
@@ -527,60 +515,6 @@ export function CatalogApp() {
const isFoundryAdmin = sessionProfile?.access?.role === "admin";
const openPlatformSettings = () => {
setPlatformSettingsOpen(true);
setCesiumIonToken("");
setCesiumIonError("");
setCesiumIonSaveState("loading");
void fetch("/api/platform-settings/cesium-ion", { cache: "no-store" })
.then(async (response) => {
if (!response.ok) throw new Error("platform_settings_load_failed");
return await response.json() as CesiumIonSecretStatus;
})
.then((status) => {
setCesiumIonStatus(status);
setCesiumIonSaveState("idle");
})
.catch(() => setCesiumIonSaveState("error"));
};
const closePlatformSettings = () => {
setPlatformSettingsOpen(false);
setCesiumIonToken("");
setCesiumIonError("");
};
const saveCesiumIonToken = async () => {
const token = cesiumIonToken.trim();
if (token.length < 16) return;
setCesiumIonSaveState("saving");
setCesiumIonError("");
try {
const response = await fetch("/api/platform-settings/cesium-ion", {
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ token }),
});
if (!response.ok) {
const result = await response.json().catch(() => null) as { error?: string } | null;
throw new Error(result?.error || "platform_settings_save_failed");
}
setCesiumIonStatus(await response.json() as CesiumIonSecretStatus);
setCesiumIonToken("");
setCesiumIonSaveState("saved");
} catch (error) {
const code = error instanceof Error ? error.message : "platform_settings_save_failed";
setCesiumIonError(code === "cesium_ion_token_verification_failed"
? "Token не прошёл проверку Cesium Ion для terrain, imagery или 3D Buildings. Значение не сохранено."
: code === "map_gateway_admin_unauthorized"
? "Внутренний signing profile Gateway не совпадает с Foundry. Это исправляется только runner-managed deployment, не вводом значений в интерфейс."
: code === "map_gateway_admin_not_configured"
? "Runner-managed signing file Gateway недоступен."
: "Не удалось применить token. Проверьте права администратора и доступность Gateway.");
setCesiumIonSaveState("error");
}
};
useEffect(() => {
let active = true;
fetch("/api/layout", { cache: "no-store" })
@@ -2019,8 +1953,6 @@ export function CatalogApp() {
onClose={closeGuideline}
headerActions={studioContext === "applications" ? (
<IconButton label="Создать модуль" onClick={() => setCreateModuleOpen(true)}><Icon name="plus" /></IconButton>
) : studioContext === "pages" && isFoundryAdmin ? (
<IconButton label="Настройки Platform" onClick={openPlatformSettings}><Icon name="settings" /></IconButton>
) : undefined}
contextSlot={studioContext === "applications" ? (
<Select
@@ -2270,57 +2202,7 @@ export function CatalogApp() {
onConfirm={confirmPageRemoval}
/>
<Window
open={platformSettingsOpen}
title="Настройки Platform"
subtitle="Map Gateway · provider credentials"
size="sm"
onClose={closePlatformSettings}
footer={
<>
<Button variant="ghost" onClick={closePlatformSettings}>Отмена</Button>
<WindowFooterActions>
<Button
variant="primary"
shape="pill"
icon={<Icon name="save" />}
disabled={cesiumIonSaveState === "loading" || cesiumIonSaveState === "saving" || cesiumIonToken.trim().length < 16}
onClick={() => { void saveCesiumIonToken(); }}
>{cesiumIonSaveState === "saving" ? "Применение…" : "Применить"}</Button>
</WindowFooterActions>
</>
}
>
<div className="catalog-form">
<SettingsCard
title="Cesium Ion"
description="Master token хранится только в закрытом хранилище Platform Map Gateway. После применения он не отображается и не попадает в browser/env или artifact."
>
<ControlRow label="Состояние">
<strong>{cesiumIonSaveState === "loading" ? "Проверяем…" : cesiumIonStatus?.configured ? "Настроен" : "Не настроен"}</strong>
</ControlRow>
{cesiumIonStatus?.configured ? <ControlRow label="Проверка provider"><strong>{cesiumIonStatus.verification === "verified" ? "Пройдена: terrain и imagery доступны" : cesiumIonStatus.verification === "failed" ? "Не пройдена: проверьте token" : "Будет выполнена при следующем применении"}</strong></ControlRow> : null}
{cesiumIonStatus?.updatedAt ? <small>Последнее изменение: {new Date(cesiumIonStatus.updatedAt).toLocaleString()}</small> : null}
</SettingsCard>
<TextField
type="password"
autoComplete="new-password"
label="Cesium Ion token"
hint="обязательно"
description="Вставьте новый token. Существующее значение намеренно нельзя прочитать обратно."
value={cesiumIonToken}
onChange={(event) => {
setCesiumIonToken(event.target.value);
if (cesiumIonSaveState === "saved" || cesiumIonSaveState === "error") setCesiumIonSaveState("idle");
setCesiumIonError("");
}}
/>
{cesiumIonSaveState === "saved" ? <small className="catalog-application-draft__status">Token применён в Platform Map Gateway.</small> : null}
{cesiumIonSaveState === "error" ? <small className="catalog-application-draft__status" data-state="error">{cesiumIonError}</small> : null}
</div>
</Window>
<FoundrySettingsModal open={foundrySettingsOpen} onClose={() => setFoundrySettingsOpen(false)} />
<FoundrySettingsModal open={foundrySettingsOpen} isAdmin={isFoundryAdmin} onClose={() => setFoundrySettingsOpen(false)} />
<Window
open={createModalOpen}