diff --git a/package-lock.json b/package-lock.json index 87d8acc..c84d668 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,7 @@ "papaparse": "^5.5.3", "pdfmake": "^0.2.20", "pg": "^8.16.3", + "pidusage": "^4.0.1", "react": "^18.3.1", "react-contenteditable": "^3.3.7", "react-diff-viewer-continued": "^3.4.0", @@ -84,6 +85,7 @@ }, "devDependencies": { "@types/node": "^20.11.0", + "@types/pidusage": "^2.0.5", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.18", @@ -8105,6 +8107,12 @@ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, + "node_modules/@types/pidusage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/pidusage/-/pidusage-2.0.5.tgz", + "integrity": "sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==", + "dev": true + }, "node_modules/@types/prismjs": { "version": "1.26.5", "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", @@ -14689,6 +14697,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidusage": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-4.0.1.tgz", + "integrity": "sha512-yCH2dtLHfEBnzlHUJymR/Z1nN2ePG3m392Mv8TFlTP1B0xkpMQNHAnfkY0n2tAi6ceKO6YWhxYfZ96V4vVkh/g==", + "dependencies": { + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", diff --git a/package.json b/package.json index f32c9ea..80885bb 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "papaparse": "^5.5.3", "pdfmake": "^0.2.20", "pg": "^8.16.3", + "pidusage": "^4.0.1", "react": "^18.3.1", "react-contenteditable": "^3.3.7", "react-diff-viewer-continued": "^3.4.0", @@ -86,6 +87,7 @@ }, "devDependencies": { "@types/node": "^20.11.0", + "@types/pidusage": "^2.0.5", "@types/react": "^18.2.48", "@types/react-dom": "^18.2.18", "autoprefixer": "^10.4.18", @@ -96,4 +98,4 @@ "vite-plugin-compression": "^0.5.1", "vite-plugin-inspect": "^11.3.3" } -} \ No newline at end of file +} diff --git a/src/components/admin/SystemControl.tsx b/src/components/admin/SystemControl.tsx new file mode 100644 index 0000000..42444cf --- /dev/null +++ b/src/components/admin/SystemControl.tsx @@ -0,0 +1,131 @@ +import React, { useEffect, useState } from 'react'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Activity, Power, Cpu, Server } from 'lucide-react'; +import { Badge } from '@/components/ui/badge'; + +interface SystemMetrics { + cpu: number; + memoryMB: number; + uptime: number; + state: 'active' | 'standby'; +} + +export default function SystemControl() { + const [metrics, setMetrics] = useState(null); + const [loading, setLoading] = useState(false); + + const fetchMetrics = async () => { + try { + const res = await fetch('/api/god/system/control'); + if (res.ok) { + const data = await res.json(); + setMetrics(data); + } + } catch (e) { + console.error("Failed to fetch metrics", e); + } + }; + + const toggleSystem = async () => { + setLoading(true); + try { + const res = await fetch('/api/god/system/control', { + method: 'POST', + body: JSON.stringify({ action: 'toggle' }), + headers: { 'Content-Type': 'application/json' } + }); + if (res.ok) { + await fetchMetrics(); // Refresh immediately + } + } catch (e) { + console.error("Failed to toggle", e); + } finally { + setLoading(false); + } + }; + + // Poll every 2 seconds + useEffect(() => { + fetchMetrics(); + const interval = setInterval(fetchMetrics, 2000); + return () => clearInterval(interval); + }, []); + + const isActive = metrics?.state === 'active'; + + return ( + + +
+ + + Core System Control + + + Monitor resource usage and toggle processing engine. + +
+
+ + {isActive ? 'ONLINE' : 'STANDBY'} + +
+
+ +
+ {/* CPU Monitor */} +
+
+ CPU Usage + +
+
+ {metrics?.cpu || 0}% +
+
+
+
+
+ + {/* RAM Monitor */} +
+
+ RAM Usage + +
+
+ {metrics?.memoryMB || 0} MB +
+
+ {/* Assumes 2GB typical limit for visualization, though actual is 16GB */} +
+
+
+ + {/* Master Switch */} +
+ +
+
+ +

+ * Activating the engine will enable heavy resource consumption (`npm` processes). Deactivating puts the system in standby, reducing CPU/RAM usage. +

+ + + ); +} diff --git a/src/lib/queue/BatchProcessor.ts b/src/lib/queue/BatchProcessor.ts index c0527d8..57d7998 100644 --- a/src/lib/queue/BatchProcessor.ts +++ b/src/lib/queue/BatchProcessor.ts @@ -1,3 +1,5 @@ +import { system } from '@/lib/system/SystemController'; + interface BatchConfig { batchSize: number; // How many items to grab at once (e.g. 100) concurrency: number; // How many to process in parallel (e.g. 5) @@ -17,6 +19,18 @@ export class BatchProcessor { const chunk = items.slice(i, i + this.config.batchSize); console.log(`Processing Batch ${(i / this.config.batchSize) + 1}...`); + // Check System State (Standby Mode) + if (!system.isActive()) { + console.log('[God Mode] System in STANDBY. Pausing Batch Processor...'); + // Wait until active again (check every 2s) + while (!system.isActive()) { + await new Promise(r => setTimeout(r, 2000)); + } + console.log('[God Mode] System RESUMED.'); + } + + // Within each chunk, limit concurrency + // Within each chunk, limit concurrency const chunkResults = await this.runWithConcurrency(chunk, workerFunction); results.push(...chunkResults); diff --git a/src/lib/system/SystemController.ts b/src/lib/system/SystemController.ts new file mode 100644 index 0000000..20301e6 --- /dev/null +++ b/src/lib/system/SystemController.ts @@ -0,0 +1,68 @@ +import pidusage from 'pidusage'; + +export type SystemState = 'active' | 'standby'; + +export interface SystemMetrics { + cpu: number; + memory: number; // in bytes + memoryMB: number; + uptime: number; // seconds + state: SystemState; + timestamp: number; +} + +class SystemController { + private state: SystemState = 'active'; // Default to active + private lastMetrics: SystemMetrics | null = null; + + // Toggle System State + toggle(): SystemState { + this.state = this.state === 'active' ? 'standby' : 'active'; + console.log(`[God Mode] System State Toggled: ${this.state.toUpperCase()}`); + return this.state; + } + + // Set conform state + setState(newState: SystemState) { + this.state = newState; + } + + getState(): SystemState { + return this.state; + } + + isActive(): boolean { + return this.state === 'active'; + } + + // Get Live Resource Usage + async getMetrics(): Promise { + try { + const stats = await pidusage(process.pid); + + this.lastMetrics = { + cpu: parseFloat(stats.cpu.toFixed(1)), + memory: stats.memory, + memoryMB: Math.round(stats.memory / 1024 / 1024), + uptime: stats.elapsed, + state: this.state, + timestamp: Date.now() + }; + + return this.lastMetrics; + } catch (e) { + console.error("Failed to get pidusage", e); + // Return cached or empty if fail + return this.lastMetrics || { + cpu: 0, + memory: 0, + memoryMB: 0, + uptime: 0, + state: this.state, + timestamp: Date.now() + }; + } + } +} + +export const system = new SystemController(); diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index 69956e9..0cf9664 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -1,6 +1,7 @@ --- import AdminLayout from '../../layouts/AdminLayout.astro'; import SystemMonitor from '../../components/admin/dashboard/SystemMonitor'; +import SystemControl from '../../components/admin/SystemControl'; --- @@ -9,6 +10,10 @@ import SystemMonitor from '../../components/admin/dashboard/SystemMonitor';

Command Station

System Monitoring, Sub-Station Status, and Content Integrity.

+ +
+ +
diff --git a/src/pages/api/god/system/control.ts b/src/pages/api/god/system/control.ts new file mode 100644 index 0000000..c2ab278 --- /dev/null +++ b/src/pages/api/god/system/control.ts @@ -0,0 +1,33 @@ +import type { APIRoute } from 'astro'; +import { system } from '@/lib/system/SystemController'; + +// GET: Retrieve current metrics and state +export const GET: APIRoute = async () => { + const metrics = await system.getMetrics(); + + return new Response(JSON.stringify(metrics), { + status: 200, + headers: { 'Content-Type': 'application/json' } + }); +}; + +// POST: Toggle state +export const POST: APIRoute = async ({ request }) => { + try { + const body = await request.json(); + + if (body.action === 'toggle') { + const newState = system.toggle(); + return new Response(JSON.stringify({ + success: true, + state: newState, + message: `System is now ${newState.toUpperCase()}` + })); + } + + return new Response(JSON.stringify({ error: 'Invalid action' }), { status: 400 }); + + } catch (e: any) { + return new Response(JSON.stringify({ error: e.message }), { status: 500 }); + } +}; diff --git a/tsconfig.json b/tsconfig.json index 3c12a86..faf0d4a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,6 +2,7 @@ "extends": "astro/tsconfigs/strict", "compilerOptions": { "moduleResolution": "node", + "esModuleInterop": true, "jsx": "react-jsx", "jsxImportSource": "react", "baseUrl": ".",