🔱 VALHALLA: Added standalone God Mode app (independent container)
This commit is contained in:
90
GOD_MODE_IMPLEMENTATION_PLAN.md
Normal file
90
GOD_MODE_IMPLEMENTATION_PLAN.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# God Mode (Valhalla) Implementation Plan
|
||||
|
||||
## 1. Overview
|
||||
We are extracting the "God Mode" diagnostics console into a completely standalone application ("Valhalla"). This ensures that even if the main Spark Platform crashes (e.g., Directus API failure, Container exhaustion), the diagnostics tools remain available to troubleshoot and fix the system.
|
||||
|
||||
## 2. Architecture
|
||||
- **Repo:** Monorepo strategy (`/god-mode` folder in `jumpstartscaling/net`).
|
||||
- **Framework:** Astro + React (matching the main frontend stack).
|
||||
- **Runtime:** Node.js 20 on Alpine Linux.
|
||||
- **Database:** DIRECT connection to PostgreSQL (bypassing Directus).
|
||||
- **Deployment:** Separate Coolify Application pointing to `/god-mode` base directory.
|
||||
|
||||
## 3. Dependencies
|
||||
To ensure full compatibility and future-proofing, we are including the **Standard Spark Feature Set** in the dependencies. This allows us to port *any* component from the main app to God Mode without missing libraries.
|
||||
|
||||
**Core Stack:**
|
||||
- `astro`, `@astrojs/node`, `@astrojs/react`
|
||||
- `react`, `react-dom`
|
||||
- `tailwindcss`, `shadcn` (radix-ui)
|
||||
|
||||
**Data Layer:**
|
||||
- `pg` (Postgres Client) - **CRITICAL**
|
||||
- `ioredis` (Redis Client) - **CRITICAL**
|
||||
- `@directus/sdk` (For future API repairs)
|
||||
- `@tanstack/react-query` (Data fetching)
|
||||
|
||||
**UI/Visualization:**
|
||||
- `@tremor/react` (Dashboards)
|
||||
- `recharts` (Metrics)
|
||||
- `lucide-react` (Icons)
|
||||
- `framer-motion` (Animations)
|
||||
|
||||
## 4. File Structure
|
||||
```
|
||||
/god-mode
|
||||
├── Dockerfile (Standard Node 20 build)
|
||||
├── package.json (Full dependency list)
|
||||
├── astro.config.mjs (Node adapter config)
|
||||
├── tsconfig.json (TypeScript config)
|
||||
├── tailwind.config.cjs (Shared design system)
|
||||
└── src
|
||||
├── lib
|
||||
│ └── godMode.ts (Core logic: DB connection)
|
||||
├── pages
|
||||
│ ├── index.astro (Main Dashboard)
|
||||
│ └── api
|
||||
│ └── god
|
||||
│ └── [...action].ts (API Endpoints)
|
||||
└── components
|
||||
└── ... (Ported from frontend)
|
||||
```
|
||||
|
||||
## 5. Implementation Steps
|
||||
|
||||
### Step 1: Initialize Workspace
|
||||
- Create `/god-mode` directory.
|
||||
- Create `package.json` with the full dependency list.
|
||||
- Create `Dockerfile` optimized for `npm install`.
|
||||
|
||||
### Step 2: Configuration
|
||||
- Copy `tailwind.config.mjs` (or cjs) from frontend to ensure design parity.
|
||||
- Configure `astro.config.mjs` for Node.js SSR.
|
||||
|
||||
### Step 3: Logic Porting
|
||||
- Copy `/frontend/src/lib/godMode.ts` -> Update to use `process.env` directly.
|
||||
- Copy `/frontend/src/pages/god.astro` -> `/god-mode/src/pages/index.astro`.
|
||||
- Copy `/frontend/src/pages/api/god/[...action].ts` -> `/god-mode/src/pages/api/god/[...action].ts`.
|
||||
|
||||
### Step 4: Verification
|
||||
- Build locally (`npm run build`).
|
||||
- Verify DB connection with explicit connection string.
|
||||
|
||||
### Step 5: Deployment
|
||||
- User creates new App in Coolify:
|
||||
- **Repo:** `jumpstartscaling/net`
|
||||
- **Base Directory:** `/god-mode`
|
||||
- **Env Vars:** `DATABASE_URL`, `GOD_MODE_TOKEN`
|
||||
|
||||
## 6. Coolify Env Vars
|
||||
```bash
|
||||
# Internal Connection String (from Coolify PostgreSQL)
|
||||
DATABASE_URL=postgres://postgres:PASSWORD@host:5432/postgres
|
||||
|
||||
# Security Token
|
||||
GOD_MODE_TOKEN=jmQXoeyxWoBsB7eHzG7FmnH90f22JtaYBxXHoorhfZ-v4tT3VNEr9vvmwHqYHCDoWXHSU4DeZXApCP-Gha-YdA
|
||||
|
||||
# Server Port
|
||||
PORT=4321
|
||||
HOST=0.0.0.0
|
||||
```
|
||||
42
god-mode/Dockerfile
Normal file
42
god-mode/Dockerfile
Normal file
@@ -0,0 +1,42 @@
|
||||
# God Mode (Valhalla) Dockerfile
|
||||
# Optimized for reliable builds with full dependencies
|
||||
|
||||
# 1. Base Image
|
||||
FROM node:20-alpine AS base
|
||||
WORKDIR /app
|
||||
# Install libc6-compat for sharp/performance
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
# 2. Dependencies
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
# Use npm install for robustness (npm ci can fail if lockfile is out of sync)
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# 3. Builder
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
# 4. Runner
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
ENV NODE_ENV=production
|
||||
ENV HOST=0.0.0.0
|
||||
ENV PORT=4321
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 astro
|
||||
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY package.json ./
|
||||
|
||||
USER astro
|
||||
EXPOSE 4321
|
||||
|
||||
CMD ["node", "./dist/server/entry.mjs"]
|
||||
23
god-mode/astro.config.mjs
Normal file
23
god-mode/astro.config.mjs
Normal file
@@ -0,0 +1,23 @@
|
||||
import { defineConfig } from 'astro/config';
|
||||
import node from '@astrojs/node';
|
||||
import react from '@astrojs/react';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
output: 'server',
|
||||
adapter: node({
|
||||
mode: 'standalone'
|
||||
}),
|
||||
integrations: [
|
||||
react(),
|
||||
tailwind({
|
||||
applyBaseStyles: false,
|
||||
}),
|
||||
],
|
||||
vite: {
|
||||
ssr: {
|
||||
noExternal: ['path-to-regexp']
|
||||
}
|
||||
}
|
||||
});
|
||||
99
god-mode/package.json
Normal file
99
god-mode/package.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"name": "spark-god-mode",
|
||||
"type": "module",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "node ./dist/server/entry.mjs",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/node": "^8.2.6",
|
||||
"@astrojs/partytown": "^2.1.4",
|
||||
"@astrojs/react": "^3.2.0",
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@astrojs/tailwind": "^5.1.0",
|
||||
"@bull-board/api": "^6.15.0",
|
||||
"@bull-board/express": "^6.15.0",
|
||||
"@craftjs/core": "^0.2.12",
|
||||
"@craftjs/utils": "^0.2.5",
|
||||
"@directus/sdk": "^17.0.0",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@nanostores/react": "^1.0.0",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@tanstack/react-query": "^5.90.12",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@tanstack/react-virtual": "^3.13.13",
|
||||
"@tiptap/extension-placeholder": "^3.13.0",
|
||||
"@tiptap/react": "^3.13.0",
|
||||
"@tiptap/starter-kit": "^3.13.0",
|
||||
"@tremor/react": "^3.18.7",
|
||||
"@turf/turf": "^7.3.1",
|
||||
"@types/leaflet": "^1.9.21",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@vite-pwa/astro": "^1.2.0",
|
||||
"astro": "^4.7.0",
|
||||
"astro-imagetools": "^0.9.0",
|
||||
"bullmq": "^5.66.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"framer-motion": "^12.23.26",
|
||||
"html-to-image": "^1.11.13",
|
||||
"immer": "^11.0.1",
|
||||
"ioredis": "^5.8.2",
|
||||
"leaflet": "^1.9.4",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lucide-react": "^0.346.0",
|
||||
"lzutf8": "^0.6.3",
|
||||
"nanoid": "^5.0.5",
|
||||
"nanostores": "^1.1.0",
|
||||
"papaparse": "^5.5.3",
|
||||
"pdfmake": "^0.2.20",
|
||||
"pg": "^8.16.3",
|
||||
"react": "^18.3.1",
|
||||
"react-contenteditable": "^3.3.7",
|
||||
"react-diff-viewer-continued": "^3.4.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-flow-renderer": "^10.3.17",
|
||||
"react-hook-form": "^7.68.0",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^16.1.0",
|
||||
"reactflow": "^11.11.4",
|
||||
"recharts": "^3.5.1",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwindcss": "^3.4.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "^3.25.76",
|
||||
"zustand": "^5.0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/react": "^18.2.48",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"autoprefixer": "^10.4.18",
|
||||
"postcss": "^8.4.35",
|
||||
"rollup-plugin-visualizer": "^6.0.5",
|
||||
"sharp": "^0.33.3",
|
||||
"typescript": "^5.4.0",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-inspect": "^11.3.3"
|
||||
}
|
||||
}
|
||||
187
god-mode/src/pages/api/god/[...action].ts
Normal file
187
god-mode/src/pages/api/god/[...action].ts
Normal file
@@ -0,0 +1,187 @@
|
||||
/**
|
||||
* 🔱 GOD MODE BACKDOOR - Direct PostgreSQL Access
|
||||
* Standalone Version (Valhalla)
|
||||
*/
|
||||
|
||||
import type { APIRoute } from 'astro';
|
||||
import pg from 'pg'; // Default import for some environments
|
||||
const { Pool } = pg;
|
||||
import Redis from 'ioredis';
|
||||
|
||||
// Database connection
|
||||
// Coolify provides DATABASE_URL in format: postgres://user:pass@host:5432/db
|
||||
const pool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL || process.env.DB_CONNECTION_STRING,
|
||||
max: 3,
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 5000,
|
||||
ssl: process.env.DATABASE_URL?.includes('sslmode=require') ? { rejectUnauthorized: false } : undefined
|
||||
});
|
||||
|
||||
// Redis connection
|
||||
const REDIS_URL = process.env.REDIS_URL || 'redis://redis:6379';
|
||||
|
||||
// Service URLs
|
||||
const DIRECTUS_URL = process.env.PUBLIC_DIRECTUS_URL || 'http://directus:8055';
|
||||
|
||||
// Token validation
|
||||
function validateGodToken(request: Request): boolean {
|
||||
const token = request.headers.get('X-God-Token') ||
|
||||
request.headers.get('Authorization')?.replace('Bearer ', '') ||
|
||||
new URL(request.url).searchParams.get('token');
|
||||
|
||||
const godToken = process.env.GOD_MODE_TOKEN;
|
||||
|
||||
if (!godToken) {
|
||||
// In standalone mode, we force a token for security
|
||||
console.warn('⚠️ GOD_MODE_TOKEN not set in env!');
|
||||
return false;
|
||||
}
|
||||
|
||||
return token === godToken;
|
||||
}
|
||||
|
||||
function json(data: object, status = 200) {
|
||||
return new Response(JSON.stringify(data, null, 2), {
|
||||
status,
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
}
|
||||
|
||||
// GET Handler
|
||||
export const GET: APIRoute = async ({ request, url }) => {
|
||||
// Quick health check (no auth needed for load balancers)
|
||||
if (url.pathname.endsWith('/healthz')) {
|
||||
return new Response('OK', { status: 200 });
|
||||
}
|
||||
|
||||
if (!validateGodToken(request)) {
|
||||
return json({ error: 'Unauthorized - Invalid God Mode Token' }, 401);
|
||||
}
|
||||
|
||||
const action = url.pathname.split('/').pop();
|
||||
|
||||
try {
|
||||
switch (action) {
|
||||
case 'health': return await getHealth();
|
||||
case 'services': return await getServices();
|
||||
case 'db-status': return await getDbStatus();
|
||||
case 'tables': return await getTables();
|
||||
case 'logs': return await getLogs();
|
||||
default: return json({
|
||||
message: '🔱 Valhalla Active',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
return json({ error: error.message, stack: error.stack }, 500);
|
||||
}
|
||||
};
|
||||
|
||||
// POST Handler
|
||||
export const POST: APIRoute = async ({ request, url }) => {
|
||||
if (!validateGodToken(request)) {
|
||||
return json({ error: 'Unauthorized' }, 401);
|
||||
}
|
||||
|
||||
const action = url.pathname.split('/').pop();
|
||||
|
||||
if (action === 'sql') {
|
||||
try {
|
||||
const body = await request.json();
|
||||
if (!body.query) return json({ error: 'Missing query' }, 400);
|
||||
|
||||
const result = await pool.query(body.query);
|
||||
return json({
|
||||
success: true,
|
||||
rowCount: result.rowCount,
|
||||
rows: result.rows,
|
||||
fields: result.fields?.map(f => f.name)
|
||||
});
|
||||
} catch (error: any) {
|
||||
return json({ error: error.message }, 500);
|
||||
}
|
||||
}
|
||||
|
||||
return json({ error: 'Method not allowed' }, 405);
|
||||
};
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
async function getServices() {
|
||||
const services: any = { timestamp: new Date().toISOString() };
|
||||
|
||||
// DB Check
|
||||
try {
|
||||
const start = Date.now();
|
||||
await pool.query('SELECT 1');
|
||||
services.postgresql = { status: '✅ RUNNING', latency: Date.now() - start };
|
||||
} catch (e: any) {
|
||||
services.postgresql = { status: '❌ DOWN', error: e.message };
|
||||
}
|
||||
|
||||
// Redis Check
|
||||
try {
|
||||
const redis = new Redis(REDIS_URL, { maxRetriesPerRequest: 1, connectTimeout: 2000 });
|
||||
const start = Date.now();
|
||||
await redis.ping();
|
||||
services.redis = { status: '✅ RUNNING', latency: Date.now() - start };
|
||||
redis.disconnect();
|
||||
} catch (e: any) {
|
||||
services.redis = { status: '❌ DOWN', error: e.message };
|
||||
}
|
||||
|
||||
// Directus Check
|
||||
try {
|
||||
const start = Date.now();
|
||||
const res = await fetch(`${DIRECTUS_URL}/server/health`, { signal: AbortSignal.timeout(5000) });
|
||||
services.directus = {
|
||||
status: res.ok ? '✅ RUNNING' : `⚠️ HTTP ${res.status}`,
|
||||
latency: Date.now() - start
|
||||
};
|
||||
} catch (e: any) {
|
||||
services.directus = { status: '❌ DOWN', error: e.message };
|
||||
}
|
||||
|
||||
return json(services);
|
||||
}
|
||||
|
||||
async function getHealth() {
|
||||
// Full system health (similar to getServices but more detail)
|
||||
return getServices();
|
||||
}
|
||||
|
||||
async function getDbStatus() {
|
||||
try {
|
||||
const res = await pool.query(`
|
||||
SELECT current_database() as db, version(),
|
||||
(SELECT count(*) FROM pg_stat_activity) as connections
|
||||
`);
|
||||
return json({ status: 'connected', ...res.rows[0] });
|
||||
} catch (e: any) {
|
||||
return json({ status: 'error', error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function getTables() {
|
||||
try {
|
||||
const res = await pool.query(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`);
|
||||
return json({ total: res.rows.length, tables: res.rows });
|
||||
} catch (e: any) {
|
||||
return json({ error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
async function getLogs() {
|
||||
try {
|
||||
const res = await pool.query('SELECT * FROM work_log ORDER BY timestamp DESC LIMIT 50');
|
||||
return json({ logs: res.rows });
|
||||
} catch (e: any) {
|
||||
return json({ error: e.message });
|
||||
}
|
||||
}
|
||||
367
god-mode/src/pages/index.astro
Normal file
367
god-mode/src/pages/index.astro
Normal file
@@ -0,0 +1,367 @@
|
||||
---
|
||||
/**
|
||||
* 🔱 GOD PANEL - System Diagnostics Dashboard
|
||||
*
|
||||
* This page is COMPLETELY STANDALONE:
|
||||
* - No middleware
|
||||
* - No Directus dependency
|
||||
* - No redirects
|
||||
* - Works even when everything else is broken
|
||||
*/
|
||||
export const prerender = false;
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>🔱 🔱 Valhalla - Spark God Mode</title>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
god: {
|
||||
gold: '#FFD700',
|
||||
dark: '#0a0a0a',
|
||||
card: '#111111',
|
||||
border: '#333333'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
@keyframes pulse-gold {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 215, 0, 0.4); }
|
||||
50% { box-shadow: 0 0 20px 10px rgba(255, 215, 0, 0.1); }
|
||||
}
|
||||
.pulse-gold { animation: pulse-gold 2s infinite; }
|
||||
.status-healthy { color: #22c55e; }
|
||||
.status-unhealthy { color: #ef4444; }
|
||||
.status-warning { color: #eab308; }
|
||||
pre { white-space: pre-wrap; word-wrap: break-word; }
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-god-dark text-white min-h-screen">
|
||||
<div id="god-panel"></div>
|
||||
|
||||
<script type="module">
|
||||
import React from 'https://esm.sh/react@18';
|
||||
import ReactDOM from 'https://esm.sh/react-dom@18/client';
|
||||
|
||||
const { useState, useEffect, useCallback } = React;
|
||||
const h = React.createElement;
|
||||
|
||||
// API Helper
|
||||
const api = {
|
||||
async get(endpoint) {
|
||||
const token = localStorage.getItem('godToken') || '';
|
||||
const res = await fetch(`/api/god/${endpoint}`, {
|
||||
headers: { 'X-God-Token': token }
|
||||
});
|
||||
return res.json();
|
||||
},
|
||||
async post(endpoint, data) {
|
||||
const token = localStorage.getItem('godToken') || '';
|
||||
const res = await fetch(`/api/god/${endpoint}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-God-Token': token
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
return res.json();
|
||||
}
|
||||
};
|
||||
|
||||
// Status Badge Component
|
||||
function StatusBadge({ status }) {
|
||||
const isHealthy = status?.includes('✅') || status === 'healthy' || status === 'connected';
|
||||
const isWarning = status?.includes('⚠️');
|
||||
const className = isHealthy ? 'status-healthy' : isWarning ? 'status-warning' : 'status-unhealthy';
|
||||
return h('span', { className: `font-bold ${className}` }, status || 'Unknown');
|
||||
}
|
||||
|
||||
// Service Card Component
|
||||
function ServiceCard({ name, data, icon }) {
|
||||
const status = data?.status || 'Unknown';
|
||||
const isHealthy = status?.includes('✅') || status?.includes('healthy');
|
||||
|
||||
return h('div', {
|
||||
className: `bg-god-card border border-god-border rounded-xl p-4 ${isHealthy ? '' : 'border-red-500/50'}`
|
||||
}, [
|
||||
h('div', { className: 'flex items-center justify-between mb-2' }, [
|
||||
h('div', { className: 'flex items-center gap-2' }, [
|
||||
h('span', { className: 'text-2xl' }, icon),
|
||||
h('span', { className: 'font-semibold text-lg' }, name)
|
||||
]),
|
||||
h(StatusBadge, { status })
|
||||
]),
|
||||
data?.latency_ms && h('div', { className: 'text-sm text-gray-400' },
|
||||
`Latency: ${data.latency_ms}ms`
|
||||
),
|
||||
data?.error && h('div', { className: 'text-sm text-red-400 mt-1' },
|
||||
`Error: ${data.error}`
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
// SQL Console Component
|
||||
function SQLConsole() {
|
||||
const [query, setQuery] = useState('SELECT * FROM sites LIMIT 5;');
|
||||
const [result, setResult] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const execute = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await api.post('sql', { query });
|
||||
setResult(data);
|
||||
} catch (err) {
|
||||
setResult({ error: err.message });
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return h('div', { className: 'bg-god-card border border-god-border rounded-xl p-4' }, [
|
||||
h('h3', { className: 'text-lg font-semibold mb-3 flex items-center gap-2' }, [
|
||||
'🗄️ SQL Console'
|
||||
]),
|
||||
h('textarea', {
|
||||
className: 'w-full bg-black border border-god-border rounded-lg p-3 font-mono text-sm text-green-400 mb-3',
|
||||
rows: 4,
|
||||
value: query,
|
||||
onChange: e => setQuery(e.target.value),
|
||||
placeholder: 'Enter SQL query...'
|
||||
}),
|
||||
h('button', {
|
||||
className: 'bg-god-gold text-black font-bold px-4 py-2 rounded-lg hover:bg-yellow-400 disabled:opacity-50',
|
||||
onClick: execute,
|
||||
disabled: loading
|
||||
}, loading ? 'Executing...' : 'Execute SQL'),
|
||||
result && h('div', { className: 'mt-4' }, [
|
||||
result.error ?
|
||||
h('div', { className: 'text-red-400 font-mono text-sm' }, `Error: ${result.error}`) :
|
||||
h('div', {}, [
|
||||
h('div', { className: 'text-sm text-gray-400 mb-2' },
|
||||
`${result.rowCount || 0} rows returned`
|
||||
),
|
||||
h('pre', {
|
||||
className: 'bg-black rounded-lg p-3 overflow-auto max-h-64 text-xs font-mono text-gray-300'
|
||||
}, JSON.stringify(result.rows, null, 2))
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
// Tables List Component
|
||||
function TablesList({ tables }) {
|
||||
if (!tables?.tables) return null;
|
||||
|
||||
const customTables = tables.tables.filter(t => !t.name.startsWith('directus_'));
|
||||
const systemTables = tables.tables.filter(t => t.name.startsWith('directus_'));
|
||||
|
||||
return h('div', { className: 'bg-god-card border border-god-border rounded-xl p-4' }, [
|
||||
h('h3', { className: 'text-lg font-semibold mb-3' },
|
||||
`📊 Database Tables (${tables.total})`
|
||||
),
|
||||
h('div', { className: 'grid grid-cols-2 gap-4' }, [
|
||||
h('div', {}, [
|
||||
h('h4', { className: 'text-sm font-semibold text-god-gold mb-2' },
|
||||
`Custom Tables (${customTables.length})`
|
||||
),
|
||||
h('div', { className: 'space-y-1 max-h-48 overflow-auto' },
|
||||
customTables.map(t =>
|
||||
h('div', {
|
||||
key: t.name,
|
||||
className: 'text-xs font-mono flex justify-between bg-black/50 px-2 py-1 rounded'
|
||||
}, [
|
||||
h('span', {}, t.name),
|
||||
h('span', { className: 'text-gray-500' }, `${t.rows} rows`)
|
||||
])
|
||||
)
|
||||
)
|
||||
]),
|
||||
h('div', {}, [
|
||||
h('h4', { className: 'text-sm font-semibold text-gray-400 mb-2' },
|
||||
`Directus System (${systemTables.length})`
|
||||
),
|
||||
h('div', { className: 'text-xs text-gray-500' },
|
||||
systemTables.length + ' system tables'
|
||||
)
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
// Quick Actions Component
|
||||
function QuickActions() {
|
||||
const actions = [
|
||||
{ label: 'Check Sites', query: 'SELECT id, name, url, status FROM sites LIMIT 10' },
|
||||
{ label: 'Count Articles', query: 'SELECT COUNT(*) as count FROM generated_articles' },
|
||||
{ label: 'Active Connections', query: 'SELECT count(*) FROM pg_stat_activity' },
|
||||
{ label: 'DB Size', query: "SELECT pg_size_pretty(pg_database_size(current_database())) as size" },
|
||||
];
|
||||
|
||||
const [result, setResult] = useState(null);
|
||||
|
||||
const run = async (query) => {
|
||||
const data = await api.post('sql', { query });
|
||||
setResult(data);
|
||||
};
|
||||
|
||||
return h('div', { className: 'bg-god-card border border-god-border rounded-xl p-4' }, [
|
||||
h('h3', { className: 'text-lg font-semibold mb-3' }, '⚡ Quick Actions'),
|
||||
h('div', { className: 'flex flex-wrap gap-2' },
|
||||
actions.map(a =>
|
||||
h('button', {
|
||||
key: a.label,
|
||||
className: 'bg-god-border hover:bg-god-gold hover:text-black px-3 py-1 rounded text-sm transition-colors',
|
||||
onClick: () => run(a.query)
|
||||
}, a.label)
|
||||
)
|
||||
),
|
||||
result && h('pre', {
|
||||
className: 'mt-3 bg-black rounded-lg p-3 text-xs font-mono text-gray-300 overflow-auto max-h-32'
|
||||
}, JSON.stringify(result.rows || result, null, 2))
|
||||
]);
|
||||
}
|
||||
|
||||
// Main God Panel Component
|
||||
function GodPanel() {
|
||||
const [services, setServices] = useState(null);
|
||||
const [health, setHealth] = useState(null);
|
||||
const [tables, setTables] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [autoRefresh, setAutoRefresh] = useState(false);
|
||||
const [lastUpdate, setLastUpdate] = useState(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
try {
|
||||
const [svc, hlth, tbl] = await Promise.all([
|
||||
api.get('services'),
|
||||
api.get('health'),
|
||||
api.get('tables')
|
||||
]);
|
||||
setServices(svc);
|
||||
setHealth(hlth);
|
||||
setTables(tbl);
|
||||
setLastUpdate(new Date().toLocaleTimeString());
|
||||
} catch (err) {
|
||||
console.error('Refresh failed:', err);
|
||||
}
|
||||
setLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
if (autoRefresh) {
|
||||
const interval = setInterval(refresh, 5000);
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
}, [refresh, autoRefresh]);
|
||||
|
||||
return h('div', { className: 'max-w-6xl mx-auto p-6' }, [
|
||||
// Header
|
||||
h('div', { className: 'flex items-center justify-between mb-8' }, [
|
||||
h('div', {}, [
|
||||
h('h1', { className: 'text-3xl font-bold flex items-center gap-3' }, [
|
||||
h('span', { className: 'text-god-gold pulse-gold inline-block' }, '🔱'),
|
||||
'God Panel'
|
||||
]),
|
||||
h('p', { className: 'text-gray-400 mt-1' },
|
||||
'System Diagnostics & Emergency Access'
|
||||
)
|
||||
]),
|
||||
h('div', { className: 'flex items-center gap-4' }, [
|
||||
h('label', { className: 'flex items-center gap-2 text-sm' }, [
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
checked: autoRefresh,
|
||||
onChange: e => setAutoRefresh(e.target.checked),
|
||||
className: 'rounded'
|
||||
}),
|
||||
'Auto-refresh (5s)'
|
||||
]),
|
||||
h('button', {
|
||||
className: 'bg-god-gold text-black font-bold px-4 py-2 rounded-lg hover:bg-yellow-400',
|
||||
onClick: refresh
|
||||
}, '🔄 Refresh'),
|
||||
lastUpdate && h('span', { className: 'text-xs text-gray-500' },
|
||||
`Last: ${lastUpdate}`
|
||||
)
|
||||
])
|
||||
]),
|
||||
|
||||
// Summary Banner
|
||||
services?.summary && h('div', {
|
||||
className: `rounded-xl p-4 mb-6 text-center font-bold text-lg ${
|
||||
services.summary.includes('✅') ? 'bg-green-900/30 border border-green-500/50' :
|
||||
'bg-red-900/30 border border-red-500/50'
|
||||
}`
|
||||
}, services.summary),
|
||||
|
||||
// Service Grid
|
||||
h('div', { className: 'grid grid-cols-2 md:grid-cols-4 gap-4 mb-6' }, [
|
||||
h(ServiceCard, { name: 'Frontend', data: services?.frontend, icon: '🌐' }),
|
||||
h(ServiceCard, { name: 'PostgreSQL', data: services?.postgresql, icon: '🐘' }),
|
||||
h(ServiceCard, { name: 'Redis', data: services?.redis, icon: '🔴' }),
|
||||
h(ServiceCard, { name: 'Directus', data: services?.directus, icon: '📦' }),
|
||||
]),
|
||||
|
||||
// Memory & Performance
|
||||
health && h('div', { className: 'grid grid-cols-3 gap-4 mb-6' }, [
|
||||
h('div', { className: 'bg-god-card border border-god-border rounded-xl p-4 text-center' }, [
|
||||
h('div', { className: 'text-3xl font-bold text-god-gold' },
|
||||
health.uptime_seconds ? Math.round(health.uptime_seconds / 60) + 'm' : '-'
|
||||
),
|
||||
h('div', { className: 'text-sm text-gray-400' }, 'Uptime')
|
||||
]),
|
||||
h('div', { className: 'bg-god-card border border-god-border rounded-xl p-4 text-center' }, [
|
||||
h('div', { className: 'text-3xl font-bold text-god-gold' },
|
||||
health.memory?.heap_used_mb ? health.memory.heap_used_mb + 'MB' : '-'
|
||||
),
|
||||
h('div', { className: 'text-sm text-gray-400' }, 'Memory Used')
|
||||
]),
|
||||
h('div', { className: 'bg-god-card border border-god-border rounded-xl p-4 text-center' }, [
|
||||
h('div', { className: 'text-3xl font-bold text-god-gold' },
|
||||
health.total_latency_ms ? health.total_latency_ms + 'ms' : '-'
|
||||
),
|
||||
h('div', { className: 'text-sm text-gray-400' }, 'Health Check')
|
||||
])
|
||||
]),
|
||||
|
||||
// Main Content Grid
|
||||
h('div', { className: 'grid md:grid-cols-2 gap-6' }, [
|
||||
h(SQLConsole, {}),
|
||||
h('div', { className: 'space-y-6' }, [
|
||||
h(QuickActions, {}),
|
||||
h(TablesList, { tables })
|
||||
])
|
||||
]),
|
||||
|
||||
// Raw Health Data
|
||||
health && h('details', { className: 'mt-6' }, [
|
||||
h('summary', { className: 'cursor-pointer text-gray-400 hover:text-white' },
|
||||
'📋 Raw Health Data'
|
||||
),
|
||||
h('pre', {
|
||||
className: 'mt-2 bg-god-card border border-god-border rounded-xl p-4 text-xs font-mono overflow-auto max-h-64'
|
||||
}, JSON.stringify(health, null, 2))
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
// Render
|
||||
const root = ReactDOM.createRoot(document.getElementById('god-panel'));
|
||||
root.render(h(GodPanel));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
116
god-mode/tailwind.config.mjs
Normal file
116
god-mode/tailwind.config.mjs
Normal file
@@ -0,0 +1,116 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
import defaultTheme from 'tailwindcss/defaultTheme'
|
||||
|
||||
export default {
|
||||
darkMode: ['class'],
|
||||
content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// === THE TITANIUM PRO SYSTEM ===
|
||||
// The Void (Base Layer - Pure Black)
|
||||
void: '#000000',
|
||||
|
||||
// Surface Staircase (Hard-Edge Layers)
|
||||
titanium: '#121212', // Level 1: Main panels
|
||||
graphite: '#1C1C1E', // Level 2: Inputs/Secondary
|
||||
jet: '#27272A', // Level 3: Popups/Modals
|
||||
|
||||
// Edge System (Borders & Separators)
|
||||
edge: {
|
||||
subtle: 'rgba(255, 255, 255, 0.10)', // Standard borders
|
||||
normal: 'rgba(255, 255, 255, 0.15)', // Card borders
|
||||
bright: 'rgba(255, 255, 255, 0.30)', // Active borders
|
||||
gold: '#D4AF37', // Selected state
|
||||
},
|
||||
|
||||
// The Luxury (Gold Accent System)
|
||||
gold: {
|
||||
100: '#FEF3C7', // Lightest
|
||||
200: '#FDE68A',
|
||||
300: '#FDE047', // Glowing text/data
|
||||
400: '#EAB308', // Button highlight
|
||||
500: '#D4AF37', // Antique Brass (primary)
|
||||
600: '#B49428', // Button dark
|
||||
700: '#A16207',
|
||||
800: '#854D0E',
|
||||
900: '#422006', // Deep shadow
|
||||
},
|
||||
|
||||
// The Tech (Electric Blue - for live indicators)
|
||||
electric: {
|
||||
400: '#38BDF8',
|
||||
500: '#0EA5E9',
|
||||
},
|
||||
|
||||
// Text System (High Contrast Only)
|
||||
silver: '#D1D5DB', // text-silver (darkest allowed)
|
||||
|
||||
// shadcn/ui compatibility
|
||||
border: 'rgba(255, 255, 255, 0.15)',
|
||||
input: 'rgba(255, 255, 255, 0.10)',
|
||||
ring: '#D4AF37',
|
||||
background: '#000000',
|
||||
foreground: '#FFFFFF',
|
||||
primary: {
|
||||
DEFAULT: '#D4AF37',
|
||||
foreground: '#000000',
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: '#1C1C1E',
|
||||
foreground: '#FFFFFF',
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: '#EF4444',
|
||||
foreground: '#FFFFFF',
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: '#121212',
|
||||
foreground: '#D1D5DB',
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: '#27272A',
|
||||
foreground: '#FFFFFF',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: '#27272A',
|
||||
foreground: '#FFFFFF',
|
||||
},
|
||||
card: {
|
||||
DEFAULT: '#121212',
|
||||
foreground: '#FFFFFF',
|
||||
},
|
||||
},
|
||||
|
||||
fontFamily: {
|
||||
sans: ['Inter', ...defaultTheme.fontFamily.sans],
|
||||
mono: ['JetBrains Mono', 'Consolas', ...defaultTheme.fontFamily.mono],
|
||||
},
|
||||
|
||||
backgroundImage: {
|
||||
'gold-gradient': 'linear-gradient(to bottom, #EAB308, #CA8A04)',
|
||||
'gold-gradient-r': 'linear-gradient(to right, #EAB308, #CA8A04)',
|
||||
'metal-shine': 'linear-gradient(45deg, transparent 25%, rgba(255,255,255,0.05) 50%, transparent 75%)',
|
||||
'dot-grid': 'radial-gradient(rgba(255,255,255,0.1) 1px, transparent 1px)',
|
||||
},
|
||||
|
||||
boxShadow: {
|
||||
'glow-gold': '0 0 20px -5px rgba(212, 175, 55, 0.3)',
|
||||
'glow-blue': '0 0 20px -5px rgba(56, 189, 248, 0.3)',
|
||||
'hard': '0 4px 0 0 #1c1c1e', // Block shadow for cards
|
||||
'hard-gold': '0 4px 0 0 rgba(212, 175, 55, 0.3)',
|
||||
},
|
||||
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(--radius) - 4px)',
|
||||
},
|
||||
|
||||
backgroundSize: {
|
||||
'dot-size': '20px 20px',
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require('tailwindcss-animate')],
|
||||
}
|
||||
14
god-mode/tsconfig.json
Normal file
14
god-mode/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "react",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user