← back to Omega Watches 2
src/app/(dashboard)/settings/page.tsx
164 lines
"use client";
import { useEffect, useState } from "react";
import { api } from "@/lib/api-client";
import { formatDateTime } from "@/lib/utils";
export default function SettingsPage() {
const [health, setHealth] = useState<any>(null);
const [sources, setSources] = useState<any[]>([]);
useEffect(() => {
api.health().then(setHealth).catch(() => {});
api.getSources().then(setSources).catch(() => {});
}, []);
const activeSources = sources.filter((s) => s.is_active).length;
const tosBlocked = sources.filter((s) => s.tos_reviewed && s.tos_allows_collection === false).length;
return (
<div className="space-y-6">
<h1 className="text-xl font-bold text-gray-100">Access Control & Settings</h1>
{/* System Status */}
<div className="grid gap-4 md:grid-cols-4">
<div className="card border-navy-700 text-center">
<div className={`text-2xl font-bold ${health?.status === "healthy" ? "text-green-400" : "text-red-400"}`}>
{health?.status || "..."}
</div>
<div className="text-xs text-gray-500">System Status</div>
</div>
<div className="card border-navy-700 text-center">
<div className="text-2xl font-bold text-gray-200">{health?.version || "..."}</div>
<div className="text-xs text-gray-500">Version</div>
</div>
<div className="card border-navy-700 text-center">
<div className="text-2xl font-bold text-blue-400">{activeSources}</div>
<div className="text-xs text-gray-500">Active Sources</div>
</div>
<div className="card border-navy-700 text-center">
<div className={`text-2xl font-bold ${health?.database?.ok ? "text-green-400" : "text-red-400"}`}>
{health?.database?.ok ? `${health.database.latency}ms` : "Down"}
</div>
<div className="text-xs text-gray-500">DB Latency</div>
</div>
</div>
{/* RBAC Roles */}
<div className="card border-navy-700">
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
RBAC Roles
</h2>
<div className="space-y-3">
{[
{ role: "admin", perms: "Full access: run/backfill/resolve/edit parsers/manage users", users: "admin", color: "text-red-400" },
{ role: "operator", perms: "Run jobs, resolve quarantine, view all data", users: "-", color: "text-orange-400" },
{ role: "auditor", perms: "View audit logs, source health, read-only access", users: "-", color: "text-blue-400" },
{ role: "viewer", perms: "Read-only dashboard access", users: "-", color: "text-gray-400" },
].map((r) => (
<div key={r.role} className="flex items-center gap-4 rounded-lg bg-navy-900 p-3">
<span className={`w-20 rounded bg-navy-800 px-2 py-1 text-center text-xs font-bold ${r.color}`}>
{r.role}
</span>
<div className="flex-1">
<div className="text-sm text-gray-300">{r.perms}</div>
</div>
<div className="text-xs text-gray-500">Users: {r.users}</div>
</div>
))}
</div>
</div>
{/* Collection Schedule */}
<div className="card border-navy-700">
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
Automated Collection Schedule
</h2>
<div className="space-y-2">
{[
{ time: "06:00 UTC", source: "Omega Official", type: "MSRP Snapshot", script: "daily-msrp.sh" },
{ time: "08:00 UTC", source: "Chrono24", type: "Market Listings", script: "daily-chrono24.sh" },
].map((s) => (
<div key={s.script} className="flex items-center justify-between rounded bg-navy-900 p-3">
<div className="flex items-center gap-3">
<span className="badge badge-success text-xs">Active</span>
<div>
<div className="text-sm text-gray-200">{s.source} — {s.type}</div>
<div className="text-xs text-gray-500">Cron: {s.time} daily</div>
</div>
</div>
<span className="font-mono text-xs text-gray-500">{s.script}</span>
</div>
))}
<div className="mt-2 text-xs text-gray-500">
Collectors run as cron jobs. Results appear in the Jobs page. Materialized views refresh after each run.
</div>
</div>
</div>
{/* System Configuration */}
<div className="card border-navy-700">
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
System Configuration
</h2>
<div className="grid gap-4 md:grid-cols-2">
<div>
<label className="mb-1 block text-xs text-gray-500">Database</label>
<div className="input bg-navy-900 text-gray-400">omega_watches_v2 @ 127.0.0.1:5432</div>
</div>
<div>
<label className="mb-1 block text-xs text-gray-500">Port</label>
<div className="input bg-navy-900 text-gray-400">7600</div>
</div>
<div>
<label className="mb-1 block text-xs text-gray-500">Environment</label>
<div className="input bg-navy-900 text-gray-400">production</div>
</div>
<div>
<label className="mb-1 block text-xs text-gray-500">Process Manager</label>
<div className="input bg-navy-900 text-gray-400">PM2 (fork mode)</div>
</div>
<div>
<label className="mb-1 block text-xs text-gray-500">Architecture</label>
<div className="input bg-navy-900 text-gray-400">Dual-Ledger (MSRP + Market Events)</div>
</div>
<div>
<label className="mb-1 block text-xs text-gray-500">DB Connections</label>
<div className="input bg-navy-900 text-gray-400">
{health ? `${health.database.connections.total} total, ${health.database.connections.idle} idle` : "..."}
</div>
</div>
</div>
</div>
{/* ToS Compliance Dashboard */}
<div className="card border-navy-700">
<h2 className="mb-4 text-sm font-semibold uppercase tracking-wider text-gray-400">
Compliance & Legal
</h2>
<div className="mb-4 flex gap-4">
<div className="rounded bg-navy-900 px-4 py-2 text-center">
<div className="text-xl font-bold text-green-400">{sources.filter((s) => s.tos_reviewed && s.tos_allows_collection !== false).length}</div>
<div className="text-xs text-gray-500">ToS Approved</div>
</div>
<div className="rounded bg-navy-900 px-4 py-2 text-center">
<div className="text-xl font-bold text-yellow-400">{sources.filter((s) => !s.tos_reviewed).length}</div>
<div className="text-xs text-gray-500">ToS Pending</div>
</div>
<div className="rounded bg-navy-900 px-4 py-2 text-center">
<div className={`text-xl font-bold ${tosBlocked > 0 ? "text-red-400" : "text-gray-400"}`}>{tosBlocked}</div>
<div className="text-xs text-gray-500">ToS Blocked</div>
</div>
</div>
<div className="space-y-2 text-sm text-gray-400">
<p>• All collection respects robots.txt and ToS directives</p>
<p>• Personal data minimization enforced (GDPR/CCPA compliant)</p>
<p>• Raw snapshots retained for auditability</p>
<p>• Parser versions tracked for reproducibility</p>
<p>• Sources with tos_allows_collection = false are auto-blocked from automated runs</p>
</div>
</div>
</div>
);
}