feat(node): configure system environment through the desktop workflow

This commit is contained in:
DCCONSTRUCTIONS
2026-09-05 19:46:52 +03:00
parent 15bb793e5e
commit 59e14c5bc0
29 changed files with 929 additions and 81 deletions
+50
View File
@@ -0,0 +1,50 @@
import { useCallback, useEffect, useState } from "react";
import { desktopAction, request } from "./api";
export interface EnvironmentStep { id: string; state: string; detail: string }
export interface EnvironmentRun { schema: string; profile_revision: string; run_id: string; state: string; updated_at: number; steps: EnvironmentStep[] }
export interface EnvironmentStatus { available: boolean; profile: {revision: string; steps:{id:string;label:string;description:string}[]}; run: EnvironmentRun | null }
export function useEnvironment(authorized: boolean, failure: (error: unknown) => void) {
const [value, setValue] = useState<EnvironmentStatus | null>(null);
const [pending, setPending] = useState(false);
const [loading, setLoading] = useState(false);
const refresh = useCallback(async () => {
setLoading(true);
try {setValue(await request<EnvironmentStatus>("/api/environment"));}
catch(error) {failure(error);}
finally {setLoading(false);}
},[failure]);
// Native progress survives a service restart and never depends on a stale
// HTTP session. Reopening an existing run uses the read-only status API.
useEffect(() => {
function progress(event:Event) {
const run=(event as CustomEvent<EnvironmentRun>).detail;
if(run?.schema==="missioncore.node.environment/v1") setValue(current=>current?{...current,available:true,run}:current);
}
function complete(event:Event) {
const result=(event as CustomEvent<{ok:boolean;error?:string;reloading?:boolean}>).detail;
if(!result?.reloading) setPending(false);
if(result?.error) failure(new Error(result.error));
// The native launcher renews login after service changes. No immediate
// request with the expired cookie is made here.
}
const sessionReady=()=>setPending(false);
window.addEventListener("mission-core-session-ready",sessionReady);
window.addEventListener("mission-core-environment-progress",progress);
window.addEventListener("mission-core-environment-result",complete);
return()=>{window.removeEventListener("mission-core-session-ready",sessionReady);window.removeEventListener("mission-core-environment-progress",progress);window.removeEventListener("mission-core-environment-result",complete);};
},[failure]);
useEffect(()=>{if(authorized&&!pending) void refresh();},[authorized,pending,refresh]);
useEffect(()=>{
if(!authorized||pending||value?.run?.state!=="running") return;
const timer=setTimeout(()=>void refresh(),2000);return()=>clearTimeout(timer);
},[authorized,pending,value,refresh]);
function start() {
if(pending||value?.run?.state==="running") return;
if(!desktopAction("configure-system")) {failure(new Error("Откройте установленное приложение Node для настройки окружения."));return;}
setValue(current=>current?{...current,run:null}:current);
setPending(true);
}
return {value,pending,loading,refresh,start,running:pending||value?.run?.state==="running"};
}