🔱 VALHALLA: Added standalone God Mode app (independent container)
This commit is contained in:
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