Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 45 additions & 26 deletions dashboard/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,43 +1,62 @@
# Stage 1: Build
FROM node:20-alpine AS builder
# ==============================================================================
# Stage 1: Dependencies - Install only production dependencies
# ==============================================================================
FROM node:22-alpine AS deps

WORKDIR /app

# Install dependencies
# Only copy package.json and package-lock.json (if available) first to leverage caching
COPY package*.json ./
RUN npm ci
# Install dependencies based on the preferred package manager
COPY package.json package-lock.json* ./

# Copy the rest of the source code
# Use npm ci for faster, more reliable installs
RUN npm ci --only=production && \
npm cache clean --force

# ==============================================================================
# Stage 2: Builder - Build the application
# ==============================================================================
FROM node:22-alpine AS builder

WORKDIR /app

# Copy dependencies from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Build the application
# Install dev dependencies for build
RUN npm ci && \
npm cache clean --force

# Build Next.js application
# This will generate the standalone output
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

RUN npm run build

# Stage 2: Production
FROM node:20-alpine AS runner
# ==============================================================================
# Stage 3: Runner - Minimal production image using distroless
# ==============================================================================
FROM gcr.io/distroless/nodejs22-debian12:nonroot

WORKDIR /app

# Set production environment
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Create a non-root user for security
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy the standalone output from the build stage
COPY --from=builder /app/.next/standalone ./
# Copy the static assets
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Copy public assets
COPY --from=builder /app/public ./public
# Copy only necessary files from builder
# The standalone output includes all necessary dependencies
COPY --from=builder --chown=nonroot:nonroot /app/.next/standalone ./
COPY --from=builder --chown=nonroot:nonroot /app/.next/static ./.next/static
COPY --from=builder --chown=nonroot:nonroot /app/public ./public


USER nextjs
# Distroless runs as nonroot user (uid 65532) by default
# No USER directive needed

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]
# Distroless doesn't have shell, use exec form
CMD ["server.js"]
5 changes: 3 additions & 2 deletions dashboard/app/dashboard/demo/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// dashboard/app/dashboard/demo/page.tsx
'use client';

import { useEffect, useState } from 'react';
import Dashboard from '@/components/dashboard/Dashboard';
import DashboardWithFilters from '@/components/dashboard/DashboardWithFilters';
import Header from '@/components/ui/Header';
import { generateTimeSeriesLogs } from '@/lib/demo';
import { TraefikLog } from '@/lib/types';
Expand Down Expand Up @@ -52,7 +53,7 @@ export default function DemoDashboardPage() {
demoMode={true}
lastUpdate={lastUpdate}
/>
<Dashboard logs={logs} demoMode={true} />
<DashboardWithFilters logs={logs} demoMode={true} />
</div>
);
}
10 changes: 3 additions & 7 deletions dashboard/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// dashboard/app/dashboard/page.tsx
'use client';

import { useEffect, useState, useRef } from 'react';
import Dashboard from '@/components/dashboard/Dashboard';
import DashboardWithFilters from '@/components/dashboard/DashboardWithFilters';
import Header from '@/components/ui/Header';
import { TraefikLog } from '@/lib/types';
import { parseTraefikLogs } from '@/lib/traefik-parser';
Expand All @@ -19,7 +20,6 @@ export default function DashboardPage() {
useEffect(() => {
const fetchLogs = async () => {
try {
// FIX: Ensure position is always a valid number, default to -1 if undefined or invalid
const position = positionRef.current ?? -1;

const response = await fetch(
Expand All @@ -44,7 +44,6 @@ export default function DashboardPage() {
});
}

// FIX: Safely update position, ensuring it's always a valid number
if (data.positions && data.positions.length > 0 && typeof data.positions[0].Position === 'number') {
positionRef.current = data.positions[0].Position;
}
Expand All @@ -61,10 +60,7 @@ export default function DashboardPage() {
}
};

// Initial fetch
fetchLogs();

// Poll every 5 seconds
const interval = setInterval(fetchLogs, 5000);

return () => clearInterval(interval);
Expand Down Expand Up @@ -139,7 +135,7 @@ export default function DashboardPage() {
</div>
</div>

<Dashboard logs={logs} demoMode={false} />
<DashboardWithFilters logs={logs} demoMode={false} />
</div>
);
}
6 changes: 3 additions & 3 deletions dashboard/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// dashboard/app/layout.tsx
import React from 'react';
import './globals.css';
import { AgentProvider } from '@/lib/contexts/AgentContext';
import Providers from '@/components/providers/Providers'; // MODIFIED LINE 4

export const metadata = {
title: 'Traefik Log Dashboard',
Expand All @@ -16,11 +16,11 @@ export default function RootLayout({
return (
<html lang="en">
<body>
<AgentProvider>
<Providers>
<div className="min-h-screen bg-gray-50">
{children}
</div>
</AgentProvider>
</Providers>
</body>
</html>
);
Expand Down
Loading