@cybermem/dashboard 0.5.14 → 0.8.5

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.
@@ -1,118 +1,165 @@
1
+ "use client";
1
2
 
2
- "use client"
3
-
4
- import React, { createContext, useContext, useEffect, useState } from "react"
5
- import { DemoDataSource } from "./demo-strategy"
6
- import { ProductionDataSource } from "./production-strategy"
7
- import { DataSourceStrategy } from "./types"
3
+ import React, { createContext, useContext, useEffect, useState } from "react";
4
+ import { DemoDataSource } from "./demo-strategy";
5
+ import { ProductionDataSource } from "./production-strategy";
6
+ import { DataSourceStrategy } from "./types";
8
7
 
9
8
  interface ClientConfig {
10
- id: string
11
- name: string
12
- match: string
13
- color: string
14
- icon: string | null
15
- description: string
16
- steps: string[]
17
- configType: string
9
+ id: string;
10
+ name: string;
11
+ match: string;
12
+ color: string;
13
+ icon: string | null;
14
+ description: string;
15
+ steps: string[];
16
+ configType: string;
18
17
  }
19
18
 
20
19
  interface ServiceStatus {
21
- name: string
22
- status: 'ok' | 'error' | 'warning'
23
- message?: string
24
- latencyMs?: number
20
+ name: string;
21
+ status: "ok" | "error" | "warning";
22
+ message?: string;
23
+ latencyMs?: number;
25
24
  }
26
25
 
27
26
  interface SystemHealth {
28
- overall: 'ok' | 'degraded' | 'error'
29
- services: ServiceStatus[]
30
- timestamp: string
27
+ overall: "ok" | "degraded" | "error";
28
+ services: ServiceStatus[];
29
+ timestamp: string;
31
30
  }
32
31
 
33
32
  interface DashboardContextType {
34
- strategy: DataSourceStrategy
35
- isDemo: boolean
36
- toggleDemo: () => void
37
- refreshSignal: number
38
- clientConfigs: ClientConfig[]
39
- systemHealth: SystemHealth | null
33
+ strategy: DataSourceStrategy;
34
+ isDemo: boolean;
35
+ toggleDemo: () => void;
36
+ refreshSignal: number;
37
+ clientConfigs: ClientConfig[];
38
+ systemHealth: SystemHealth | null;
39
+ isAuthenticated: boolean;
40
+ login: () => void;
40
41
  }
41
42
 
42
- const DashboardContext = createContext<DashboardContextType | undefined>(undefined)
43
+ const DashboardContext = createContext<DashboardContextType | undefined>(
44
+ undefined,
45
+ );
43
46
 
44
- export function DashboardProvider({ children }: { children: React.ReactNode }) {
45
- const [isDemo, setIsDemo] = useState(false)
46
- const [strategy, setStrategy] = useState<DataSourceStrategy>(new ProductionDataSource())
47
- const [refreshSignal, setRefreshSignal] = useState(0)
48
- const [clientConfigs, setClientConfigs] = useState<ClientConfig[]>([])
49
- const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null)
47
+ export function DashboardProvider({
48
+ children,
49
+ initialAuth = false,
50
+ }: {
51
+ children: React.ReactNode;
52
+ initialAuth?: boolean;
53
+ }) {
54
+ const [isDemo, setIsDemo] = useState(false);
55
+ const [strategy, setStrategy] = useState<DataSourceStrategy>(
56
+ new ProductionDataSource(),
57
+ );
58
+ const [refreshSignal, setRefreshSignal] = useState(0);
59
+ const [clientConfigs, setClientConfigs] = useState<ClientConfig[]>([]);
60
+ const [systemHealth, setSystemHealth] = useState<SystemHealth | null>(null);
61
+ const [isAuthenticated, setIsAuthenticated] = useState(initialAuth);
50
62
 
51
63
  // Load configuration on mount
52
64
  useEffect(() => {
53
65
  // Load client config
54
66
  fetch("/clients.json")
55
- .then(res => res.json())
56
- .then(data => setClientConfigs(data))
57
- .catch(err => console.error("Failed to load client configs:", err))
58
- }, [])
67
+ .then((res) => res.json())
68
+ .then((data) => setClientConfigs(data))
69
+ .catch((err) => console.error("Failed to load client configs:", err));
70
+
71
+ // Check session storage
72
+ if (sessionStorage.getItem("authenticated") === "true") {
73
+ setIsAuthenticated(true);
74
+ }
75
+ }, []);
59
76
 
60
77
  // Check system health
61
78
  useEffect(() => {
62
79
  const checkHealth = async () => {
63
80
  try {
64
- const res = await fetch("/api/health", { signal: AbortSignal.timeout(5000) })
81
+ const res = await fetch("/api/health", {
82
+ signal: AbortSignal.timeout(5000),
83
+ });
65
84
  if (res.ok) {
66
- const data = await res.json()
67
- setSystemHealth(data)
85
+ const data = await res.json();
86
+ setSystemHealth(data);
68
87
  } else {
69
88
  setSystemHealth({
70
- overall: 'error',
71
- services: [{ name: 'Dashboard API', status: 'error', message: `HTTP ${res.status}` }],
72
- timestamp: new Date().toISOString()
73
- })
89
+ overall: "error",
90
+ services: [
91
+ {
92
+ name: "Dashboard API",
93
+ status: "error",
94
+ message: `HTTP ${res.status}`,
95
+ },
96
+ ],
97
+ timestamp: new Date().toISOString(),
98
+ });
74
99
  }
75
100
  } catch (error: any) {
76
101
  setSystemHealth({
77
- overall: 'error',
78
- services: [{ name: 'Dashboard API', status: 'error', message: error.message || 'Connection failed' }],
79
- timestamp: new Date().toISOString()
80
- })
102
+ overall: "error",
103
+ services: [
104
+ {
105
+ name: "Dashboard API",
106
+ status: "error",
107
+ message: error.message || "Connection failed",
108
+ },
109
+ ],
110
+ timestamp: new Date().toISOString(),
111
+ });
81
112
  }
82
- }
83
- checkHealth()
84
- const interval = setInterval(checkHealth, 30000) // Check every 30s
85
- return () => clearInterval(interval)
86
- }, [])
113
+ };
114
+ checkHealth();
115
+ const interval = setInterval(checkHealth, 30000); // Check every 30s
116
+ return () => clearInterval(interval);
117
+ }, []);
87
118
 
88
119
  const toggleDemo = () => {
89
- const newState = !isDemo
90
- setIsDemo(newState)
91
- setStrategy(newState ? new DemoDataSource() : new ProductionDataSource())
92
- setRefreshSignal(prev => prev + 1)
93
- }
120
+ const newState = !isDemo;
121
+ setIsDemo(newState);
122
+ setStrategy(newState ? new DemoDataSource() : new ProductionDataSource());
123
+ setRefreshSignal((prev) => prev + 1);
124
+ };
125
+
126
+ const login = () => {
127
+ setIsAuthenticated(true);
128
+ sessionStorage.setItem("authenticated", "true");
129
+ };
94
130
 
95
131
  // Refresh data periodically (centralized trigger)
96
132
  useEffect(() => {
97
- if (isDemo) return // No auto-refresh in Demo Mode (static data)
133
+ if (isDemo) return; // No auto-refresh in Demo Mode (static data)
98
134
 
99
- const interval = setInterval(() => {
100
- setRefreshSignal(prev => prev + 1)
101
- }, 5000)
102
- return () => clearInterval(interval)
103
- }, [isDemo])
135
+ const interval = setInterval(() => {
136
+ setRefreshSignal((prev) => prev + 1);
137
+ }, 5000);
138
+ return () => clearInterval(interval);
139
+ }, [isDemo]);
104
140
 
105
141
  return (
106
- <DashboardContext.Provider value={{ strategy, isDemo, toggleDemo, refreshSignal, clientConfigs, systemHealth }}>
142
+ <DashboardContext.Provider
143
+ value={{
144
+ strategy,
145
+ isDemo,
146
+ toggleDemo,
147
+ refreshSignal,
148
+ clientConfigs,
149
+ systemHealth,
150
+ isAuthenticated,
151
+ login,
152
+ }}
153
+ >
107
154
  {children}
108
155
  </DashboardContext.Provider>
109
- )
156
+ );
110
157
  }
111
158
 
112
159
  export function useDashboard() {
113
- const context = useContext(DashboardContext)
160
+ const context = useContext(DashboardContext);
114
161
  if (context === undefined) {
115
- throw new Error("useDashboard must be used within a DashboardProvider")
162
+ throw new Error("useDashboard must be used within a DashboardProvider");
116
163
  }
117
- return context
164
+ return context;
118
165
  }
@@ -1,39 +1,52 @@
1
-
2
- import { DashboardData, DataSourceStrategy, TimeSeriesData } from "./types"
1
+ import { DashboardData, DataSourceStrategy, TimeSeriesData } from "./types";
3
2
 
4
3
  export class ProductionDataSource implements DataSourceStrategy {
5
4
  async fetchGlobalStats(): Promise<DashboardData> {
6
- const res = await fetch(`/api/metrics`)
7
- if (!res.ok) throw new Error("Failed to fetch metrics")
8
- const data = await res.json()
5
+ const res = await fetch(`/api/metrics`);
6
+ if (!res.ok) throw new Error("Failed to fetch metrics");
7
+ const data = await res.json();
9
8
 
10
- const logsRes = await fetch(`/api/audit-logs`)
11
- const logsData = logsRes.ok ? await logsRes.json() : { logs: [] }
9
+ const logsRes = await fetch(`/api/audit-logs`);
10
+ const logsData = logsRes.ok ? await logsRes.json() : { logs: [] };
12
11
 
13
12
  // Helper to resolve logs
14
13
  const resolveOperation = (log: any) => {
15
- const normalizedOp = (log.operation || "").toString().toLowerCase()
16
- if (normalizedOp === "read") return "Read"
17
- if (normalizedOp === "write" || normalizedOp === "create") return "Write"
18
- if (normalizedOp === "update") return "Update"
19
- if (normalizedOp === "delete") return "Delete"
20
- const method = (log.method || "").toString().toUpperCase()
21
- if (method === "GET") return "Read"
22
- if (method === "DELETE") return "Delete"
23
- if (method === "PATCH" || method === "PUT") return "Update"
24
- return "Write"
25
- }
14
+ const normalizedOp = (log.operation || "").toString().toLowerCase();
15
+ if (normalizedOp === "read") return "Read";
16
+ if (normalizedOp === "write" || normalizedOp === "create") return "Write";
17
+ if (normalizedOp === "update") return "Update";
18
+ if (normalizedOp === "delete") return "Delete";
19
+ const method = (log.method || "").toString().toUpperCase();
20
+ if (method === "GET") return "Read";
21
+ if (method === "DELETE") return "Delete";
22
+ if (method === "PATCH" || method === "PUT") return "Update";
23
+ return "Write";
24
+ };
26
25
 
27
26
  const mappedLogs = (logsData.logs || []).map((log: any, index: number) => {
28
- const operation = resolveOperation(log)
27
+ const operation = resolveOperation(log);
29
28
  // Use rawStatus if available (from our API), otherwise fallback to status
30
- const statusCode = parseInt(log.rawStatus || log.status)
31
- let status = "Success"
32
- let description = ""
33
- if (statusCode >= 500) { status = "Error"; description = "Server error" }
34
- else if (statusCode >= 400) { status = "Error"; description = statusCode === 401 ? "Unauthorized" : statusCode === 403 ? "Forbidden" : "Client error" }
35
- else if (statusCode >= 300) { status = "Warning"; description = "Redirect" }
36
- else if (log.status === "Error") { status = "Error"; description = "Error" }
29
+ const statusCode = parseInt(log.rawStatus || log.status);
30
+ let status = "Success";
31
+ let description = "";
32
+ if (statusCode >= 500) {
33
+ status = "Error";
34
+ description = "Server error";
35
+ } else if (statusCode >= 400) {
36
+ status = "Error";
37
+ description =
38
+ statusCode === 401
39
+ ? "Unauthorized"
40
+ : statusCode === 403
41
+ ? "Forbidden"
42
+ : "Client error";
43
+ } else if (statusCode >= 300) {
44
+ status = "Warning";
45
+ description = "Redirect";
46
+ } else if (log.status === "Error") {
47
+ status = "Error";
48
+ description = "Error";
49
+ }
37
50
 
38
51
  return {
39
52
  id: index,
@@ -42,69 +55,95 @@ export class ProductionDataSource implements DataSourceStrategy {
42
55
  operation,
43
56
  status,
44
57
  description,
45
- timestamp: new Date(log.timestamp).getTime()
46
- }
47
- })
58
+ timestamp: new Date(log.timestamp).getTime(),
59
+ };
60
+ });
48
61
 
49
62
  // Calculate Latest & Tops from logs if available
50
- const sortedByDate = [...mappedLogs].sort((a, b) => b.timestamp - a.timestamp)
63
+ const sortedByDate = [...mappedLogs].sort(
64
+ (a, b) => b.timestamp - a.timestamp,
65
+ );
51
66
 
52
67
  // Writers
53
- const wLog = sortedByDate.find(l => ["Write", "Update", "Delete", "Create"].includes(l.operation))
54
- const lastWriter = wLog ? { name: wLog.client, timestamp: wLog.timestamp } : { name: "N/A", timestamp: 0 }
68
+ const wLog = sortedByDate.find((l) =>
69
+ ["Write", "Update", "Delete", "Create"].includes(l.operation),
70
+ );
71
+ const lastWriter = wLog
72
+ ? { name: wLog.client, timestamp: wLog.timestamp }
73
+ : { name: "N/A", timestamp: 0 };
55
74
 
56
75
  // Readers
57
- const rLog = sortedByDate.find(l => l.operation === "Read")
58
- const lastReader = rLog ? { name: rLog.client, timestamp: rLog.timestamp } : { name: "N/A", timestamp: 0 }
76
+ const rLog = sortedByDate.find((l) => l.operation === "Read");
77
+ const lastReader = rLog
78
+ ? { name: rLog.client, timestamp: rLog.timestamp }
79
+ : { name: "N/A", timestamp: 0 };
59
80
 
60
81
  // Tops
61
- const writerCounts: Record<string, number> = {}
62
- const readerCounts: Record<string, number> = {}
82
+ const writerCounts: Record<string, number> = {};
83
+ const readerCounts: Record<string, number> = {};
63
84
  mappedLogs.forEach((log: any) => {
64
85
  if (["Write", "Update", "Delete", "Create"].includes(log.operation)) {
65
- writerCounts[log.client] = (writerCounts[log.client] || 0) + 1
86
+ writerCounts[log.client] = (writerCounts[log.client] || 0) + 1;
66
87
  } else if (log.operation === "Read") {
67
- readerCounts[log.client] = (readerCounts[log.client] || 0) + 1
88
+ readerCounts[log.client] = (readerCounts[log.client] || 0) + 1;
68
89
  }
69
- })
90
+ });
70
91
  const getTop = (counts: Record<string, number>) => {
71
- const entries = Object.entries(counts)
72
- if (entries.length === 0) return { name: "N/A", count: 0 }
73
- entries.sort((a, b) => b[1] - a[1])
74
- return { name: entries[0][0], count: entries[0][1] }
75
- }
76
-
77
- const topWriter = logsRes.ok ? getTop(writerCounts) : (data.stats.topWriter ?? { name: "N/A", count: 0 })
78
- const topReader = logsRes.ok ? getTop(readerCounts) : (data.stats.topReader ?? { name: "N/A", count: 0 })
92
+ const entries = Object.entries(counts);
93
+ if (entries.length === 0) return { name: "N/A", count: 0 };
94
+ entries.sort((a, b) => b[1] - a[1]);
95
+ return { name: entries[0][0], count: entries[0][1] };
96
+ };
97
+
98
+ const topWriter = logsRes.ok
99
+ ? getTop(writerCounts)
100
+ : (data.stats.topWriter ?? { name: "N/A", count: 0 });
101
+ const topReader = logsRes.ok
102
+ ? getTop(readerCounts)
103
+ : (data.stats.topReader ?? { name: "N/A", count: 0 });
79
104
 
80
105
  // Trends calculation
81
106
  const calculateTrend = (series: number[]) => {
82
- if (!series || series.length < 2) return { change: "0", trend: "neutral" as const, hasData: false, data: [] }
83
- const first = series[0]
84
- const last = series[series.length - 1]
85
- const diff = last - first
86
- const prefix = diff > 0 ? "+" : ""
87
- return {
88
- change: `${prefix}${diff.toLocaleString()}`,
89
- trend: (diff > 0 ? "up" : diff < 0 ? "down" : "neutral") as "up" | "down" | "neutral",
90
- hasData: true,
91
- data: series
92
- }
93
- }
107
+ if (!series || series.length < 2)
108
+ return {
109
+ change: "0",
110
+ trend: "neutral" as const,
111
+ hasData: false,
112
+ data: [],
113
+ };
114
+ const first = series[0];
115
+ const last = series[series.length - 1];
116
+ const diff = last - first;
117
+ const prefix = diff > 0 ? "+" : "";
118
+ return {
119
+ change: `${prefix}${diff.toLocaleString()}`,
120
+ trend: (diff > 0 ? "up" : diff < 0 ? "down" : "neutral") as
121
+ | "up"
122
+ | "down"
123
+ | "neutral",
124
+ hasData: true,
125
+ data: series,
126
+ };
127
+ };
94
128
 
95
129
  // Success Rate Trend
96
- let successTrend = { change: "0%", trend: "neutral" as "neutral" | "up" | "down", hasData: false, data: [] as number[] }
130
+ let successTrend = {
131
+ change: "0%",
132
+ trend: "neutral" as "neutral" | "up" | "down",
133
+ hasData: false,
134
+ data: [] as number[],
135
+ };
97
136
  if (data.sparklines?.successRate) {
98
- const sData = data.sparklines.successRate
99
- const sFirst = sData[0] || 0
100
- const sLast = sData[sData.length - 1] || 0
101
- const sDiff = sLast - sFirst
102
- successTrend = {
103
- change: `${sDiff > 0 ? "+" : ""}${sDiff.toFixed(1)}%`,
104
- trend: (sDiff >= 0 ? "up" : "down"),
105
- hasData: true,
106
- data: sData
107
- }
137
+ const sData = data.sparklines.successRate;
138
+ const sFirst = sData[0] || 0;
139
+ const sLast = sData[sData.length - 1] || 0;
140
+ const sDiff = sLast - sFirst;
141
+ successTrend = {
142
+ change: `${sDiff > 0 ? "+" : ""}${sDiff.toFixed(1)}%`,
143
+ trend: sDiff >= 0 ? "up" : "down",
144
+ hasData: true,
145
+ data: sData,
146
+ };
108
147
  }
109
148
 
110
149
  return {
@@ -119,34 +158,34 @@ export class ProductionDataSource implements DataSourceStrategy {
119
158
  lastReader,
120
159
  },
121
160
  trends: {
122
- memory: calculateTrend(data.sparklines?.memoryRecords),
123
- clients: calculateTrend(data.sparklines?.totalClients),
161
+ memory: calculateTrend(data.sparklines?.memoryRecords || []),
162
+ clients: calculateTrend(data.sparklines?.totalClients || []),
124
163
  success: successTrend,
125
- requests: calculateTrend(data.sparklines?.totalRequests),
164
+ requests: calculateTrend(data.sparklines?.totalRequests || []),
126
165
  },
127
166
  logs: mappedLogs,
128
- }
167
+ };
129
168
  }
130
169
 
131
170
  async getChartData(period: string): Promise<TimeSeriesData> {
132
- const res = await fetch(`/api/metrics?period=${period}`)
133
- if (!res.ok) throw new Error("Failed to fetch chart data")
134
- const apiData = await res.json()
171
+ const res = await fetch(`/api/metrics?period=${period}`);
172
+ if (!res.ok) throw new Error("Failed to fetch chart data");
173
+ const apiData = await res.json();
135
174
 
136
175
  // Fetch clients metadata separately or use what's in apiData
137
176
  // Ideally we merge them here
138
- let metadata = {}
177
+ let metadata = {};
139
178
  if (apiData.metadata) {
140
- metadata = apiData.metadata
179
+ metadata = apiData.metadata;
141
180
  }
142
181
 
143
182
  // apiData.timeSeries needs to be returned.
144
183
  return {
145
- creates: apiData.timeSeries?.creates || [],
146
- reads: apiData.timeSeries?.reads || [],
147
- updates: apiData.timeSeries?.updates || [],
148
- deletes: apiData.timeSeries?.deletes || [],
149
- metadata
150
- }
184
+ creates: apiData.timeSeries?.creates || [],
185
+ reads: apiData.timeSeries?.reads || [],
186
+ updates: apiData.timeSeries?.updates || [],
187
+ deletes: apiData.timeSeries?.deletes || [],
188
+ metadata,
189
+ };
151
190
  }
152
191
  }
package/middleware.ts CHANGED
@@ -1,43 +1,65 @@
1
- import { NextResponse } from 'next/server'
2
- import type { NextRequest } from 'next/server'
1
+ import type { NextRequest } from "next/server";
2
+ import { NextResponse } from "next/server";
3
+
4
+ /**
5
+ * Dashboard Middleware
6
+ *
7
+ * 1. LOCAL BYPASS: localhost, 127.0.0.1, raspberrypi.local skip auth
8
+ * 2. REMOTE: Check cybermem_token cookie
9
+ * 3. CSRF Protection for mutations
10
+ */
11
+ export async function middleware(request: NextRequest) {
12
+ const host = request.headers.get("host") || "";
13
+
14
+ // LOCAL BYPASS: Skip auth for local development and trusted networks
15
+ const isLocal =
16
+ host.includes("localhost") ||
17
+ host.includes("127.0.0.1") ||
18
+ host.includes("raspberrypi.local");
19
+
20
+ if (!isLocal) {
21
+ // REMOTE: Check cookie token
22
+ const token = request.cookies.get("cybermem_token")?.value;
23
+
24
+ if (!token) {
25
+ // Redirect to token auth page with error
26
+ const authUrl = new URL("/api/auth/token", request.url);
27
+ authUrl.searchParams.set("error", "no_token");
28
+ return NextResponse.redirect(authUrl);
29
+ }
30
+
31
+ // Note: Full token verification happens in the token endpoint
32
+ // Cookie existence is enough for middleware (token was validated when set)
33
+ }
3
34
 
4
- export function middleware(request: NextRequest) {
5
35
  // CSRF Protection for mutating requests
6
- if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method)) {
7
- const origin = request.headers.get('origin')
8
- const referer = request.headers.get('referer')
9
- const host = request.headers.get('host')
10
-
11
- // If no origin/referer, or they don't match the host, block it.
12
- // NOTE: This is a strict check. For local dev, internal API calls might need bypass if no origin set.
13
- // Browsers ALWAYS send Origin for cross-origin POSTs.
14
- // For same-origin, they usually send it too, but we can fall back to Referer.
15
-
16
- // Allow server-side calls (no origin/referer) ONLY if coming from trusted internal network?
17
- // Actually, for a dashboard, we expect browser interaction.
18
- // If strict compliance is needed: logic below.
19
-
36
+ if (["POST", "PUT", "DELETE", "PATCH"].includes(request.method)) {
37
+ const origin = request.headers.get("origin");
38
+ const referer = request.headers.get("referer");
39
+
20
40
  if (origin) {
21
- const originHost = origin.replace(/^https?:\/\//, '')
41
+ const originHost = origin.replace(/^https?:\/\//, "");
22
42
  if (originHost !== host) {
23
- return new NextResponse('CSRF Validation Failed', { status: 403 })
43
+ return new NextResponse("CSRF Validation Failed", { status: 403 });
24
44
  }
25
45
  } else if (referer) {
26
- const refererHost = new URL(referer).host
27
- if (refererHost !== host) {
28
- return new NextResponse('CSRF Validation Failed', { status: 403 })
29
- }
30
- } else {
31
- // Ideally we block requests without Origin/Referer in modern browsers for mutations
32
- // But to be safe for non-browser tooling (if used): header check
33
- // We'll enforce that the request must have come from our UI
34
- // return new NextResponse('Missing Origin/Referer', { status: 403 })
46
+ const refererHost = new URL(referer).host;
47
+ if (refererHost !== host) {
48
+ return new NextResponse("CSRF Validation Failed", { status: 403 });
49
+ }
35
50
  }
36
51
  }
37
52
 
38
- return NextResponse.next()
53
+ return NextResponse.next();
39
54
  }
40
55
 
41
56
  export const config = {
42
- matcher: '/api/:path*',
43
- }
57
+ matcher: [
58
+ // Match all routes except:
59
+ // - API auth routes
60
+ // - Next.js internals
61
+ // - Static files
62
+ // - Health check (for monitoring)
63
+ "/((?!api/auth|api/health|_next|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
64
+ ],
65
+ };
package/next.config.mjs CHANGED
@@ -1,36 +1,28 @@
1
-
2
1
  /** @type {import('next').NextConfig} */
3
2
  const nextConfig = {
4
- typescript: {
5
- ignoreBuildErrors: true,
6
- },
7
- eslint: {
8
- ignoreDuringBuilds: true,
9
- },
10
3
  images: {
11
4
  unoptimized: true,
12
5
  },
13
- output: 'standalone',
14
- transpilePackages: ['recharts'],
15
- serverExternalPackages: ['dockerode', 'ssh2'],
16
- experimental: {
17
- },
6
+ output: "standalone",
7
+ transpilePackages: ["recharts"],
8
+ serverExternalPackages: ["dockerode", "ssh2"],
9
+ experimental: {},
18
10
  webpack: (config) => {
19
- config.externals = [...(config.externals || []), 'ssh2', 'dockerode'];
11
+ config.externals = [...(config.externals || []), "ssh2", "dockerode"];
20
12
  return config;
21
13
  },
22
14
  async rewrites() {
23
15
  return [
24
16
  {
25
- source: '/docs',
26
- destination: '/docs/index.html',
17
+ source: "/docs",
18
+ destination: "/docs/index.html",
27
19
  },
28
20
  {
29
- source: '/docs/:slug',
30
- destination: '/docs/:slug.html',
21
+ source: "/docs/:slug",
22
+ destination: "/docs/:slug.html",
31
23
  },
32
- ]
24
+ ];
33
25
  },
34
- }
26
+ };
35
27
 
36
- export default nextConfig
28
+ export default nextConfig;