feat(map): add reference layers and stabilize workspace controls
This commit is contained in:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user