@cybermem/dashboard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.dockerignore +11 -0
  2. package/.eslintrc.json +3 -0
  3. package/Dockerfile +48 -0
  4. package/app/api/audit-logs/route.ts +60 -0
  5. package/app/api/metrics/route.ts +141 -0
  6. package/app/api/prometheus/route.ts +65 -0
  7. package/app/api/settings/regenerate/route.ts +20 -0
  8. package/app/api/settings/route.ts +25 -0
  9. package/app/api/system/restart/route.ts +18 -0
  10. package/app/globals.css +148 -0
  11. package/app/layout.tsx +37 -0
  12. package/app/page.tsx +150 -0
  13. package/components/dashboard/audit-log-table.tsx +195 -0
  14. package/components/dashboard/chart-card.tsx +196 -0
  15. package/components/dashboard/charts-section.tsx +16 -0
  16. package/components/dashboard/header.tsx +82 -0
  17. package/components/dashboard/login-modal.tsx +87 -0
  18. package/components/dashboard/mcp-config-modal.tsx +397 -0
  19. package/components/dashboard/metric-card.tsx +23 -0
  20. package/components/dashboard/metrics-chart.tsx +134 -0
  21. package/components/dashboard/metrics-grid.tsx +136 -0
  22. package/components/dashboard/settings-modal.tsx +345 -0
  23. package/components/theme-provider.tsx +11 -0
  24. package/components/ui/accordion.tsx +66 -0
  25. package/components/ui/alert-dialog.tsx +157 -0
  26. package/components/ui/alert.tsx +66 -0
  27. package/components/ui/aspect-ratio.tsx +11 -0
  28. package/components/ui/avatar.tsx +53 -0
  29. package/components/ui/badge.tsx +46 -0
  30. package/components/ui/breadcrumb.tsx +109 -0
  31. package/components/ui/button-group.tsx +83 -0
  32. package/components/ui/button.tsx +60 -0
  33. package/components/ui/calendar.tsx +213 -0
  34. package/components/ui/card.tsx +92 -0
  35. package/components/ui/carousel.tsx +241 -0
  36. package/components/ui/chart.tsx +353 -0
  37. package/components/ui/checkbox.tsx +32 -0
  38. package/components/ui/collapsible.tsx +33 -0
  39. package/components/ui/command.tsx +184 -0
  40. package/components/ui/context-menu.tsx +252 -0
  41. package/components/ui/dialog.tsx +143 -0
  42. package/components/ui/drawer.tsx +135 -0
  43. package/components/ui/dropdown-menu.tsx +257 -0
  44. package/components/ui/empty.tsx +104 -0
  45. package/components/ui/field.tsx +244 -0
  46. package/components/ui/form.tsx +167 -0
  47. package/components/ui/hover-card.tsx +44 -0
  48. package/components/ui/input-group.tsx +169 -0
  49. package/components/ui/input-otp.tsx +77 -0
  50. package/components/ui/input.tsx +21 -0
  51. package/components/ui/item.tsx +193 -0
  52. package/components/ui/kbd.tsx +28 -0
  53. package/components/ui/label.tsx +24 -0
  54. package/components/ui/menubar.tsx +276 -0
  55. package/components/ui/navigation-menu.tsx +166 -0
  56. package/components/ui/pagination.tsx +127 -0
  57. package/components/ui/popover.tsx +48 -0
  58. package/components/ui/progress.tsx +31 -0
  59. package/components/ui/radio-group.tsx +45 -0
  60. package/components/ui/resizable.tsx +56 -0
  61. package/components/ui/scroll-area.tsx +58 -0
  62. package/components/ui/select.tsx +185 -0
  63. package/components/ui/separator.tsx +28 -0
  64. package/components/ui/sheet.tsx +139 -0
  65. package/components/ui/sidebar.tsx +726 -0
  66. package/components/ui/skeleton.tsx +13 -0
  67. package/components/ui/slider.tsx +63 -0
  68. package/components/ui/sonner.tsx +25 -0
  69. package/components/ui/spinner.tsx +16 -0
  70. package/components/ui/switch.tsx +31 -0
  71. package/components/ui/table.tsx +116 -0
  72. package/components/ui/tabs.tsx +66 -0
  73. package/components/ui/textarea.tsx +18 -0
  74. package/components/ui/toast.tsx +129 -0
  75. package/components/ui/toaster.tsx +35 -0
  76. package/components/ui/toggle-group.tsx +73 -0
  77. package/components/ui/toggle.tsx +47 -0
  78. package/components/ui/tooltip.tsx +61 -0
  79. package/components/ui/use-mobile.tsx +19 -0
  80. package/components/ui/use-toast.ts +191 -0
  81. package/components.json +21 -0
  82. package/hooks/use-mobile.ts +19 -0
  83. package/hooks/use-toast.ts +191 -0
  84. package/lib/data/dashboard-context.tsx +75 -0
  85. package/lib/data/demo-strategy.ts +110 -0
  86. package/lib/data/production-strategy.ts +152 -0
  87. package/lib/data/types.ts +52 -0
  88. package/lib/prometheus/client.ts +58 -0
  89. package/lib/prometheus/index.ts +6 -0
  90. package/lib/prometheus/metrics.ts +234 -0
  91. package/lib/prometheus/sparklines.ts +71 -0
  92. package/lib/prometheus/timeseries.ts +305 -0
  93. package/lib/prometheus/utils.ts +176 -0
  94. package/lib/utils.ts +6 -0
  95. package/next.config.mjs +36 -0
  96. package/package.json +91 -0
  97. package/postcss.config.mjs +8 -0
  98. package/public/clients.json +165 -0
  99. package/public/favicon-dark.svg +1 -0
  100. package/public/favicon-light.svg +1 -0
  101. package/public/icons/antigravity.png +0 -0
  102. package/public/icons/chatgpt.png +0 -0
  103. package/public/icons/claude-code.png +0 -0
  104. package/public/icons/claude.png +0 -0
  105. package/public/icons/codex.png +0 -0
  106. package/public/icons/cursor.png +0 -0
  107. package/public/icons/gemini.png +0 -0
  108. package/public/icons/images.jpeg +0 -0
  109. package/public/icons/mcp.png +0 -0
  110. package/public/icons/mono.png +0 -0
  111. package/public/icons/perplexity.png +0 -0
  112. package/public/icons/vscode.png +0 -0
  113. package/public/icons/warp.png +0 -0
  114. package/public/icons/windsurf.png +0 -0
  115. package/public/logo.png +0 -0
  116. package/public/logo.svg +7 -0
  117. package/public/manifest.json +21 -0
  118. package/public/site.webmanifest +21 -0
  119. package/public/web-app-manifest-192x192.png +0 -0
  120. package/public/web-app-manifest-512x512.png +0 -0
  121. package/shared.env +0 -0
  122. package/styles/globals.css +125 -0
  123. package/tsconfig.json +41 -0
package/.dockerignore ADDED
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ .next
3
+ .git
4
+ .gitignore
5
+ README.md
6
+ npm-debug.log
7
+ yarn-debug.log
8
+ yarn-error.log
9
+ .DS_Store
10
+ *.pem
11
+ .env*.local
package/.eslintrc.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
package/Dockerfile ADDED
@@ -0,0 +1,48 @@
1
+ # Base stage for both dev and prod
2
+ FROM node:20-alpine AS base
3
+ WORKDIR /app
4
+
5
+ # Use corepack to get the pnpm version from the lockfile and speed up installs via cache
6
+ RUN corepack enable
7
+
8
+ # Copy package files
9
+ COPY package*.json ./
10
+ COPY pnpm-lock.yaml* ./
11
+
12
+ # Install dependencies (cache pnpm store). Lock is not frozen in repo, so allow updates.
13
+ RUN --mount=type=cache,target=/root/.pnpm-store pnpm install --no-frozen-lockfile
14
+
15
+ # Copy source code
16
+ COPY . .
17
+
18
+ # Development stage - skips build, runs dev server
19
+ FROM base AS dev
20
+ ENV NODE_ENV=development
21
+ CMD ["pnpm", "dev"]
22
+
23
+ # Builder stage - runs build (slow)
24
+ FROM base AS builder
25
+ RUN --mount=type=cache,target=/root/.pnpm-store \
26
+ --mount=type=cache,target=/app/.next/cache \
27
+ pnpm build
28
+
29
+ # Production stage
30
+ FROM node:20-alpine AS production
31
+ WORKDIR /app
32
+
33
+ ENV NODE_ENV=production
34
+ ENV NEXT_TELEMETRY_DISABLED=1
35
+
36
+ # Copy necessary files from builder
37
+ COPY --from=builder /app/public ./public
38
+ COPY --from=builder /app/.next/standalone ./
39
+ COPY --from=builder /app/.next/static ./.next/static
40
+
41
+ # Expose port
42
+ EXPOSE 3000
43
+
44
+ ENV PORT=3000
45
+ ENV HOSTNAME="0.0.0.0"
46
+
47
+ # Start the application
48
+ CMD ["node", "server.js"]
@@ -0,0 +1,60 @@
1
+ import { NextResponse } from 'next/server'
2
+
3
+ export const dynamic = 'force-dynamic'
4
+
5
+ const CLIENTS = ["Claude Code", "v0", "Cursor", "GitHub Copilot", "Windsurf"]
6
+ const OPERATIONS = ["Read", "Write", "Update", "Delete", "Create"]
7
+ const STATUSES = ["Success", "Success", "Success", "Warning", "Error"]
8
+ const DESCRIPTIONS = {
9
+ "Success": ["Operation completed successfully", "Resource accessed", "Data synchronized"],
10
+ "Warning": ["High latency detected", "Rate limit approaching", "Deprecation warning"],
11
+ "Error": ["Unauthorized access", "Internal server error", "Timeout exceeded", "Validation failed"]
12
+ }
13
+
14
+ export async function GET(request: Request) {
15
+ try {
16
+ // Fetch logs from db-exporter service
17
+ // timeout 2s to not hang
18
+ const controller = new AbortController()
19
+ const timeoutId = setTimeout(() => controller.abort(), 2000)
20
+
21
+ const res = await fetch('http://db-exporter:8000/api/logs?limit=100', {
22
+ signal: controller.signal,
23
+ cache: 'no-store'
24
+ })
25
+ clearTimeout(timeoutId)
26
+
27
+ if (!res.ok) {
28
+ throw new Error(`Failed to fetch logs: ${res.statusText}`)
29
+ }
30
+
31
+ const data = await res.json()
32
+ const rawLogs = data.logs || []
33
+
34
+ const logs = rawLogs.map((log: any) => {
35
+ const statusCode = parseInt(log.status) || 0
36
+ let status = "Success"
37
+ if (statusCode === 0 || statusCode >= 400) status = "Error"
38
+ else if (statusCode >= 300) status = "Warning"
39
+
40
+ // Capitalize operation
41
+ const operation = log.operation.charAt(0).toUpperCase() + log.operation.slice(1)
42
+
43
+ return {
44
+ timestamp: log.timestamp,
45
+ client: log.client_name || "Unknown",
46
+ operation: operation,
47
+ status: status,
48
+ method: log.method,
49
+ description: log.endpoint,
50
+ rawStatus: log.status
51
+ }
52
+ })
53
+
54
+ return NextResponse.json({ logs })
55
+ } catch (error) {
56
+ console.error("Error fetching audit logs:", error)
57
+ // Return empty list on error to avoid breaking UI with 500
58
+ return NextResponse.json({ logs: [] })
59
+ }
60
+ }
@@ -0,0 +1,141 @@
1
+ import {
2
+ getClientCount,
3
+ getCreatesByClient,
4
+ getDeletesByClient,
5
+ getErrorsByClient,
6
+ getLastReader,
7
+ getLastWriter,
8
+ getMemoryRecordsCount,
9
+ getMemoryRecordsSparkline,
10
+ getReadsByClient,
11
+ getRequestsByClient,
12
+ getRequestsByMethod,
13
+ getRequestsTimeSeries,
14
+ getRequestsTimeSeriesByMethod,
15
+ getResponseTimeTimeSeries,
16
+ getSuccessRate,
17
+ getSuccessRateByClient,
18
+ getSuccessRateSparkline,
19
+ getSuccessRateTimeSeries,
20
+ getSuccessRateTimeSeriesByClient,
21
+ getTopReader,
22
+ getTopWriter,
23
+ getTotalClientsSparkline,
24
+ getTotalRequests,
25
+ getTotalRequestsSparkline,
26
+ getUpdatesByClient
27
+ } from '@/lib/prometheus'
28
+ import { NextResponse } from 'next/server'
29
+
30
+ export const dynamic = 'force-dynamic'
31
+ export const revalidate = 0
32
+
33
+ export async function GET(request: Request) {
34
+ try {
35
+ const { searchParams } = new URL(request.url)
36
+ const period = searchParams.get('period') || '15m'
37
+
38
+ const [
39
+ totalRequests,
40
+ requestsByClient,
41
+ requestsByMethod,
42
+ successRate,
43
+ requestsTimeSeries,
44
+ responseTimeTimeSeries,
45
+ successRateTimeSeries,
46
+ writesTimeSeries,
47
+ memoryRecords,
48
+ clientCount,
49
+ memoryRecordsSparkline,
50
+ totalRequestsSparkline,
51
+ totalClientsSparkline,
52
+ successRateSparkline,
53
+ successRateByClient,
54
+ successRateTimeSeriesByClient,
55
+ topWriter,
56
+ topReader,
57
+ lastWriter,
58
+ lastReader,
59
+ createsTimeSeries,
60
+ readsTimeSeries,
61
+ updatesTimeSeries,
62
+ deletesTimeSeries,
63
+ errorsTimeSeries
64
+ ] = await Promise.all([
65
+ getTotalRequests(period),
66
+ getRequestsByClient(period),
67
+ getRequestsByMethod(period),
68
+ getSuccessRate(),
69
+ getRequestsTimeSeries(period),
70
+ getResponseTimeTimeSeries(period),
71
+ getSuccessRateTimeSeries(period),
72
+ getRequestsTimeSeriesByMethod('POST', period),
73
+ getMemoryRecordsCount(),
74
+ getClientCount(),
75
+ getMemoryRecordsSparkline(period),
76
+ getTotalRequestsSparkline(period),
77
+ getTotalClientsSparkline(period),
78
+ getSuccessRateSparkline(period),
79
+ getSuccessRateByClient(),
80
+ getSuccessRateTimeSeriesByClient(period),
81
+ getTopWriter(),
82
+ getTopReader(),
83
+ getLastWriter(),
84
+ getLastReader(),
85
+ getCreatesByClient(period),
86
+ getReadsByClient(period),
87
+ getUpdatesByClient(period),
88
+ getDeletesByClient(period),
89
+ getErrorsByClient(period)
90
+ ])
91
+
92
+ // Sort clients by total requests
93
+ const clientStats = Object.entries(requestsByClient)
94
+ .map(([client, total]) => ({
95
+ client,
96
+ total,
97
+ reads: requestsByMethod.reads[client] || 0,
98
+ writes: requestsByMethod.writes[client] || 0
99
+ }))
100
+ .sort((a, b) => b.total - a.total)
101
+
102
+ return NextResponse.json({
103
+ stats: {
104
+ memoryRecords,
105
+ totalClients: clientCount,
106
+ successRate,
107
+ totalRequests,
108
+ topWriter,
109
+ topReader,
110
+ lastWriter,
111
+ lastReader
112
+ },
113
+ timeSeries: {
114
+ requests: requestsTimeSeries,
115
+ responseTime: responseTimeTimeSeries,
116
+ successRate: successRateTimeSeries,
117
+ successRateByClient: successRateTimeSeriesByClient,
118
+ writes: writesTimeSeries,
119
+ creates: createsTimeSeries,
120
+ reads: readsTimeSeries,
121
+ updates: updatesTimeSeries,
122
+ deletes: deletesTimeSeries,
123
+ errors: errorsTimeSeries
124
+ },
125
+ clientStats: {
126
+ reads: requestsByMethod.reads,
127
+ writes: requestsByMethod.writes,
128
+ successRate: successRateByClient
129
+ },
130
+ sparklines: {
131
+ memoryRecords: memoryRecordsSparkline,
132
+ totalRequests: totalRequestsSparkline,
133
+ totalClients: totalClientsSparkline,
134
+ successRate: successRateSparkline
135
+ }
136
+ })
137
+ } catch (error) {
138
+ console.error('Failed to fetch metrics:', error)
139
+ return NextResponse.json({ error: 'Failed to fetch metrics' }, { status: 500 })
140
+ }
141
+ }
@@ -0,0 +1,65 @@
1
+ import { NextRequest, NextResponse } from 'next/server'
2
+
3
+ const PROMETHEUS_URL = process.env.PROMETHEUS_URL || 'http://localhost:9090'
4
+
5
+ export async function GET(request: NextRequest) {
6
+ const searchParams = request.nextUrl.searchParams
7
+ const query = searchParams.get('query')
8
+
9
+ if (!query) {
10
+ return NextResponse.json({ error: 'Query parameter is required' }, { status: 400 })
11
+ }
12
+
13
+ try {
14
+ const prometheusResponse = await fetch(
15
+ `${PROMETHEUS_URL}/api/v1/query?query=${encodeURIComponent(query)}`
16
+ )
17
+
18
+ if (!prometheusResponse.ok) {
19
+ throw new Error(`Prometheus returned ${prometheusResponse.status}`)
20
+ }
21
+
22
+ const data = await prometheusResponse.json()
23
+ return NextResponse.json(data)
24
+ } catch (error) {
25
+ console.error('Error fetching from Prometheus:', error)
26
+ return NextResponse.json(
27
+ { error: 'Failed to fetch data from Prometheus' },
28
+ { status: 500 }
29
+ )
30
+ }
31
+ }
32
+
33
+ export async function POST(request: NextRequest) {
34
+ try {
35
+ const body = await request.json()
36
+ const { query, start, end, step } = body
37
+
38
+ if (!query) {
39
+ return NextResponse.json({ error: 'Query parameter is required' }, { status: 400 })
40
+ }
41
+
42
+ const params = new URLSearchParams({
43
+ query,
44
+ ...(start && { start: start.toString() }),
45
+ ...(end && { end: end.toString() }),
46
+ ...(step && { step: step.toString() }),
47
+ })
48
+
49
+ const endpoint = start && end ? 'query_range' : 'query'
50
+ const prometheusResponse = await fetch(`${PROMETHEUS_URL}/api/v1/${endpoint}?${params}`)
51
+
52
+ if (!prometheusResponse.ok) {
53
+ throw new Error(`Prometheus returned ${prometheusResponse.status}`)
54
+ }
55
+
56
+ const data = await prometheusResponse.json()
57
+ return NextResponse.json(data)
58
+ } catch (error) {
59
+ console.error('Error fetching from Prometheus:', error)
60
+ return NextResponse.json(
61
+ { error: 'Failed to fetch data from Prometheus' },
62
+ { status: 500 }
63
+ )
64
+ }
65
+ }
@@ -0,0 +1,20 @@
1
+ import crypto from 'crypto'
2
+ import fs from 'fs'
3
+ import { NextResponse } from 'next/server'
4
+
5
+ export const dynamic = 'force-dynamic'
6
+
7
+ export async function POST(req: Request) {
8
+ try {
9
+ const apiKey = `sk-${crypto.randomBytes(16).toString('hex')}`
10
+ const sharedEnvPath = '/app/shared.env'
11
+
12
+ // Write OM_API_KEY=... to shared file mounted at /.env in OpenMemory
13
+ fs.writeFileSync(sharedEnvPath, `OM_API_KEY=${apiKey}\n`)
14
+
15
+ return NextResponse.json({ success: true, apiKey })
16
+ } catch (error) {
17
+ console.error('[Settings] Failed to regenerate API Key:', error)
18
+ return NextResponse.json({ error: 'Failed to regenerate API Key' }, { status: 500 })
19
+ }
20
+ }
@@ -0,0 +1,25 @@
1
+ import fs from 'fs'
2
+ import { NextResponse } from 'next/server'
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ const CONFIG_PATH = '/data/config.json'
7
+
8
+ export async function GET() {
9
+ let apiKey = process.env.OM_API_KEY || 'not-set'
10
+
11
+ try {
12
+ if (fs.existsSync(CONFIG_PATH)) {
13
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8')
14
+ const conf = JSON.parse(raw)
15
+ if (conf.api_key) apiKey = conf.api_key
16
+ }
17
+ } catch (e) {
18
+ // ignore
19
+ }
20
+
21
+ return NextResponse.json({
22
+ apiKey: apiKey,
23
+ endpoint: process.env.CYBERMEM_URL || 'http://localhost:8080'
24
+ })
25
+ }
@@ -0,0 +1,18 @@
1
+ const Docker = require('dockerode');
2
+ import { NextResponse } from 'next/server';
3
+
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function POST(req: Request) {
7
+ try {
8
+ const docker = new Docker({ socketPath: '/var/run/docker.sock' });
9
+ const container = docker.getContainer('cybermem-openmemory');
10
+
11
+ await container.restart();
12
+
13
+ return NextResponse.json({ success: true, message: 'Container restarting...' })
14
+ } catch (error) {
15
+ console.error('[Dashboard] Failed to restart server:', error)
16
+ return NextResponse.json({ error: 'Failed to restart server' }, { status: 500 })
17
+ }
18
+ }
@@ -0,0 +1,148 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+
4
+ @custom-variant dark (&:is(.dark *));
5
+
6
+ :root {
7
+ --background: oklch(0.1 0.02 240);
8
+ --foreground: oklch(0.95 0 0);
9
+ --card: oklch(0.15 0.02 240);
10
+ --card-foreground: oklch(0.95 0 0);
11
+ --popover: oklch(0.15 0.02 240);
12
+ --popover-foreground: oklch(0.95 0 0);
13
+ --primary: oklch(0.6 0.18 150);
14
+ --primary-foreground: oklch(0.08 0 0);
15
+ --secondary: oklch(0.2 0.02 240);
16
+ --secondary-foreground: oklch(0.95 0 0);
17
+ --muted: oklch(0.3 0.02 240);
18
+ --muted-foreground: oklch(0.7 0 0);
19
+ --accent: oklch(0.6 0.18 150);
20
+ --accent-foreground: oklch(0.08 0 0);
21
+ --destructive: oklch(0.577 0.245 27.325);
22
+ --destructive-foreground: oklch(0.977 0.008 71.098);
23
+ --border: oklch(0.2 0.02 240);
24
+ --input: oklch(0.2 0.02 240);
25
+ --ring: oklch(0.6 0.18 150);
26
+ --chart-1: oklch(0.6 0.18 150);
27
+ --chart-2: oklch(0.62 0.18 210);
28
+ --chart-3: oklch(0.65 0.14 45);
29
+ --chart-4: oklch(0.45 0.18 300);
30
+ --chart-5: oklch(0.58 0.15 190);
31
+ --radius: 0.625rem;
32
+ --sidebar: oklch(0.15 0.02 240);
33
+ --sidebar-foreground: oklch(0.95 0 0);
34
+ --sidebar-primary: oklch(0.6 0.18 150);
35
+ --sidebar-primary-foreground: oklch(0.08 0 0);
36
+ --sidebar-accent: oklch(0.3 0.02 240);
37
+ --sidebar-accent-foreground: oklch(0.95 0 0);
38
+ --sidebar-border: oklch(0.2 0.02 240);
39
+ --sidebar-ring: oklch(0.6 0.18 150);
40
+ }
41
+
42
+ .dark {
43
+ --background: oklch(0.1 0.02 240);
44
+ --foreground: oklch(0.95 0 0);
45
+ --card: oklch(0.15 0.02 240);
46
+ --card-foreground: oklch(0.95 0 0);
47
+ --popover: oklch(0.15 0.02 240);
48
+ --popover-foreground: oklch(0.95 0 0);
49
+ --primary: oklch(0.6 0.18 150);
50
+ --primary-foreground: oklch(0.08 0 0);
51
+ --secondary: oklch(0.2 0.02 240);
52
+ --secondary-foreground: oklch(0.95 0 0);
53
+ --muted: oklch(0.3 0.02 240);
54
+ --muted-foreground: oklch(0.7 0 0);
55
+ --accent: oklch(0.6 0.18 150);
56
+ --accent-foreground: oklch(0.08 0 0);
57
+ --destructive: oklch(0.396 0.141 25.723);
58
+ --destructive-foreground: oklch(0.637 0.237 25.331);
59
+ --border: oklch(0.2 0.02 240);
60
+ --input: oklch(0.2 0.02 240);
61
+ --ring: oklch(0.6 0.18 150);
62
+ --chart-1: oklch(0.6 0.18 150);
63
+ --chart-2: oklch(0.48 0.16 255);
64
+ --chart-3: oklch(0.65 0.14 45);
65
+ --chart-4: oklch(0.45 0.18 300);
66
+ --chart-5: oklch(0.58 0.15 190);
67
+ --sidebar: oklch(0.15 0.02 240);
68
+ --sidebar-foreground: oklch(0.95 0 0);
69
+ --sidebar-primary: oklch(0.6 0.18 150);
70
+ --sidebar-primary-foreground: oklch(0.08 0 0);
71
+ --sidebar-accent: oklch(0.3 0.02 240);
72
+ --sidebar-accent-foreground: oklch(0.95 0 0);
73
+ --sidebar-border: oklch(0.2 0.02 240);
74
+ --sidebar-ring: oklch(0.6 0.18 150);
75
+ }
76
+
77
+ @theme inline {
78
+ --font-sans: "Geist", "Geist Fallback";
79
+ --font-mono: "Geist Mono", "Geist Mono Fallback";
80
+ --font-exo: var(--font-exo2);
81
+ --color-background: var(--background);
82
+ --color-foreground: var(--foreground);
83
+ --color-card: var(--card);
84
+ --color-card-foreground: var(--card-foreground);
85
+ --color-popover: var(--popover);
86
+ --color-popover-foreground: var(--popover-foreground);
87
+ --color-primary: var(--primary);
88
+ --color-primary-foreground: var(--primary-foreground);
89
+ --color-secondary: var(--secondary);
90
+ --color-secondary-foreground: var(--secondary-foreground);
91
+ --color-muted: var(--muted);
92
+ --color-muted-foreground: var(--muted-foreground);
93
+ --color-accent: var(--accent);
94
+ --color-accent-foreground: var(--accent-foreground);
95
+ --color-destructive: var(--destructive);
96
+ --color-destructive-foreground: var(--destructive-foreground);
97
+ --color-border: var(--border);
98
+ --color-input: var(--input);
99
+ --color-ring: var(--ring);
100
+ --color-chart-1: var(--chart-1);
101
+ --color-chart-2: var(--chart-2);
102
+ --color-chart-3: var(--chart-3);
103
+ --color-chart-4: var(--chart-4);
104
+ --color-chart-5: var(--chart-5);
105
+ --radius-sm: calc(var(--radius) - 4px);
106
+ --radius-md: calc(var(--radius) - 2px);
107
+ --radius-lg: var(--radius);
108
+ --radius-xl: calc(var(--radius) + 4px);
109
+ --color-sidebar: var(--sidebar);
110
+ --color-sidebar-foreground: var(--sidebar-foreground);
111
+ --color-sidebar-primary: var(--sidebar-primary);
112
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
113
+ --color-sidebar-accent: var(--sidebar-accent);
114
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
115
+ --color-sidebar-border: var(--sidebar-border);
116
+ --color-sidebar-ring: var(--sidebar-ring);
117
+ }
118
+
119
+ @layer base {
120
+ * {
121
+ @apply border-border outline-ring/50;
122
+ }
123
+ }
124
+
125
+ html {
126
+ background-color: #000000;
127
+ min-height: 100%;
128
+
129
+ background-image:
130
+ radial-gradient(circle at 0% 50%, oklch(72.399% 0.12193 187.358 / 0.02) 0%, transparent 70%),
131
+ radial-gradient(circle at 100% 50%, oklch(0.75 0.1257 215.22 / 0.02) 0%, transparent 70%),
132
+ radial-gradient(circle at 25% 25%, oklch(68.073% 0.11257 209.377 / 0.06) 0%, transparent 60%),
133
+ radial-gradient(circle at 75% 25%, oklch(0.71 0.1491 162.48 / 0.04) 0%, transparent 60%),
134
+ radial-gradient(circle at 65% 65%, oklch(59.83% 0.10619 177.492 / 0.03) 0%, transparent 60%),
135
+ radial-gradient(circle at 50% 55%, oklch(0.75 0.1257 215.22 / 0.02) 0%, transparent 65%),
136
+ radial-gradient(circle at 25% 85%, oklch(0.77 0.09845 196.726 / 0.05) 0%, transparent 60%),
137
+ radial-gradient(circle at 95% 95%, oklch(66.101% 0.11198 215.999 / 0.03) 0%, transparent 60%);
138
+
139
+ background-size: 100% 100%;
140
+ background-attachment: fixed;
141
+ overscroll-behavior: none;
142
+ }
143
+
144
+ body {
145
+ background-color: transparent;
146
+ color: #ffffff;
147
+ min-height: 100vh;
148
+ }
package/app/layout.tsx ADDED
@@ -0,0 +1,37 @@
1
+ import { DashboardProvider } from "@/lib/data/dashboard-context"
2
+ import { Analytics } from "@vercel/analytics/next"
3
+ import type { Metadata } from "next"
4
+ import { Exo_2, Geist, Geist_Mono } from "next/font/google"
5
+ import type React from "react"
6
+ import "./globals.css"
7
+
8
+ const _geist = Geist({ subsets: ["latin"] })
9
+ const _geistMono = Geist_Mono({ subsets: ["latin"] })
10
+ const exo2 = Exo_2({ subsets: ["latin"], variable: "--font-exo2" })
11
+
12
+ export const metadata: Metadata = {
13
+ title: "CyberMem",
14
+ description: "Real-time memory operations dashboard",
15
+ icons: {
16
+ icon: '/favicon-dark.svg',
17
+ shortcut: '/favicon-dark.svg',
18
+ apple: '/favicon-dark.svg',
19
+ },
20
+ }
21
+
22
+ export default function RootLayout({
23
+ children,
24
+ }: Readonly<{
25
+ children: React.ReactNode
26
+ }>) {
27
+ return (
28
+ <html lang="en" suppressHydrationWarning className="relative">
29
+ <body className={`font-sans antialiased relative z-10 ${exo2.variable}`}>
30
+ <DashboardProvider>
31
+ {children}
32
+ </DashboardProvider>
33
+ <Analytics />
34
+ </body>
35
+ </html>
36
+ )
37
+ }