'use client' import { Check, Clock, CornerDownLeft, ExternalLink, File, Plus } from 'lucide-react' import Link from 'next/link' import { useCallback, useMemo, useState } from 'react' import { Badge, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, Button, Card, CardContent, cn, flattenTree, NavMenu, NavMenuItem, Tooltip, TooltipContent, TooltipTrigger, TreeView, TreeViewItem, type ChartConfig, type INodeRendererProps, } from 'ui' import { Chart, ChartActions, ChartCard, ChartContent, ChartHeader, ChartLine, ChartLoadingState, ChartMetric, } from 'ui-patterns/Chart' import { LogsBarChart } from 'ui-patterns/LogsBarChart' import { PageBreadcrumbs, PageBreadcrumbsActions } from 'ui-patterns/PageBreadcrumbs' import { PageContainer } from 'ui-patterns/PageContainer' import { PageHeader, PageHeaderDescription, PageHeaderMeta, PageHeaderSummary, PageHeaderTitle, } from 'ui-patterns/PageHeader' import { PageNav } from 'ui-patterns/PageNav' import { PageSection, PageSectionAside, PageSectionContent, PageSectionDescription, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import { PageLayoutLogsContent } from './page-layout-logs-content' type PageId = 'overview' | 'logs' | 'code' | 'settings' const pages: { id: PageId; label: string }[] = [ { id: 'overview', label: 'Overview' }, { id: 'logs', label: 'Logs' }, { id: 'code', label: 'Code' }, { id: 'settings', label: 'Settings' }, ] const CHART_INTERVALS = [ { key: '15min', label: '15 min', format: 'MMM D, h:mm:ssa', minutes: 15 }, { key: '1hr', label: '1 hour', format: 'MMM D, h:mma', minutes: 60 }, { key: '3hr', label: '3 hours', format: 'MMM D, h:mma', minutes: 180 }, { key: '1day', label: '1 day', format: 'MMM D, h:mma', minutes: 24 * 60 }, ] as const const FUNCTION_URL = 'https://demo.supabase.co/functions/v1/hello-world' type MockInvocationDatum = { timestamp: string ok_count: number warning_count: number error_count: number } type MockMetricsDatum = MockInvocationDatum & { avg_execution_time: number max_execution_time: number avg_cpu_time_used: number max_cpu_time_used: number avg_memory_used: number avg_heap_memory_used: number avg_external_memory_used: number } const pseudoNoise = (seed: number, amplitude = 1) => { const value = Math.sin(seed * 12.9898) * 43758.5453 return (value - Math.floor(value)) * amplitude } const getIntervalMinutes = (intervalKey: string) => CHART_INTERVALS.find((item) => item.key === intervalKey)?.minutes ?? 60 const buildMockChartData = (intervalKey: string) => { const minutes = getIntervalMinutes(intervalKey) const end = new Date() end.setSeconds(0, 0) const invocation: MockInvocationDatum[] = Array.from({ length: minutes }, (_, index) => { const timestamp = new Date(end) timestamp.setMinutes(end.getMinutes() - (minutes - 1 - index)) const progress = minutes <= 1 ? 0 : index / (minutes - 1) const baseLoad = 12 + Math.round(progress * 28) const noise = Math.round(pseudoNoise(index + minutes * 0.1, 8)) const ok_count = baseLoad + noise const warning_count = index % 17 === 0 ? 2 + (index % 3) : index % 11 === 0 ? 1 : 0 const error_count = index % 43 === 0 ? 3 + (index % 2) : 0 return { timestamp: timestamp.toISOString(), ok_count, warning_count, error_count, } }) const metrics: MockMetricsDatum[] = invocation.map((datum, index) => { const avg_execution_time = Math.round(68 + pseudoNoise(index + 2, 24) + index * 0.15) const max_execution_time = Math.round( avg_execution_time * (1.25 + pseudoNoise(index + minutes, 0.35)) ) return { ...datum, avg_execution_time, max_execution_time, avg_cpu_time_used: Math.round(8 + pseudoNoise(index + 4, 10) + index * 0.05), max_cpu_time_used: Math.round(16 + pseudoNoise(index + 6, 18) + index * 0.1), avg_memory_used: Number((40 + pseudoNoise(index + 8, 12) + index * 0.08).toFixed(1)), avg_heap_memory_used: Number((24 + pseudoNoise(index + 10, 8) + index * 0.05).toFixed(1)), avg_external_memory_used: Number((14 + pseudoNoise(index + 12, 6) + index * 0.04).toFixed(1)), } }) return { invocation, metrics } } const EXECUTION_TIME_CHART_CONFIG = { avg_execution_time: { label: 'Average Execution Time', color: 'var(--foreground-default)', }, max_execution_time: { label: 'Max Execution Time', color: 'hsl(var(--brand-default))', }, } satisfies ChartConfig const CPU_TIME_CHART_CONFIG = { max_cpu_time_used: { label: 'Max CPU Time', color: 'hsl(var(--brand-default))', }, } satisfies ChartConfig const MEMORY_CHART_CONFIG = { avg_memory_used: { label: 'Memory Usage', color: 'hsl(var(--brand-default))', }, } satisfies ChartConfig type MockFileData = { id: number name: string content: string state: 'new' | 'modified' | 'unchanged' } const MOCK_CODE_FILES: MockFileData[] = [ { id: 1, name: 'index.ts', state: 'modified', content: `import { serve } from 'https://deno.land/std/http/server.ts' serve(async (req) => { const { name = 'World' } = await req.json().catch(() => ({})) return new Response( JSON.stringify({ message: \`Hello \${name}\` }), { headers: { 'content-type': 'application/json' } } ) })`, }, { id: 2, name: 'deno.json', state: 'unchanged', content: JSON.stringify({ imports: {} }, null, '\t'), }, ] const formatRate = (count: number, total: number) => new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 1, }).format(total === 0 ? 0 : count / total) const formatMetric = (value: number, unit?: string) => { const formatted = unit === 'MB' ? value.toFixed(1) : Math.round(value).toLocaleString('en-US') return unit ? `${formatted}${unit}` : formatted } const getSegmentedButtonClassName = (index: number, total: number) => { if (index === 0) return 'rounded-tr-none rounded-br-none' if (index === total - 1) return 'rounded-tl-none rounded-bl-none' return 'rounded-none' } const sumBy = (items: T[], getValue: (item: T) => number) => items.reduce((total, item) => total + getValue(item), 0) const meanBy = (items: T[], getValue: (item: T) => number) => items.length === 0 ? 0 : sumBy(items, getValue) / items.length export default function PageLayoutEdgeFunction() { const [activePage, setActivePage] = useState('overview') return (
} > Edge Functions hello-world {pages.map((page) => ( ))} {activePage === 'overview' && } {activePage === 'logs' && ( )} {activePage === 'code' && ( )} {activePage === 'settings' && }
) } function OverviewPage() { const [interval, setInterval] = useState('1hr') const selectedInterval = CHART_INTERVALS.find((item) => item.key === interval) ?? CHART_INTERVALS[1] const dateTimeFormat = selectedInterval.format const { invocation: mockInvocationChartData, metrics: mockMetricsChartData } = useMemo( () => buildMockChartData(interval), [interval] ) const { totalInvocationCount, totalWarningCount, totalErrorCount } = useMemo(() => { const totalInvocationCount = sumBy( mockInvocationChartData, (datum) => datum.ok_count + datum.warning_count + datum.error_count ) return { totalInvocationCount, totalWarningCount: sumBy(mockInvocationChartData, (datum) => datum.warning_count), totalErrorCount: sumBy(mockInvocationChartData, (datum) => datum.error_count), } }, [mockInvocationChartData]) const { averageExecutionTime, maxExecutionTime } = useMemo( () => ({ averageExecutionTime: meanBy(mockMetricsChartData, (datum) => datum.avg_execution_time), maxExecutionTime: Math.max(...mockMetricsChartData.map((datum) => datum.max_execution_time)), }), [mockMetricsChartData] ) const { averageCpuTime, maxCpuTime, averageMemoryUsage, totalHeapMemory, totalExternalMemory } = useMemo(() => { const totalHeapMemory = sumBy(mockMetricsChartData, (datum) => datum.avg_heap_memory_used) const totalExternalMemory = sumBy( mockMetricsChartData, (datum) => datum.avg_external_memory_used ) return { averageCpuTime: meanBy(mockMetricsChartData, (datum) => datum.avg_cpu_time_used), maxCpuTime: Math.max(...mockMetricsChartData.map((datum) => datum.max_cpu_time_used)), averageMemoryUsage: meanBy(mockMetricsChartData, (datum) => datum.avg_memory_used), totalHeapMemory, totalExternalMemory, } }, [mockMetricsChartData]) const invocationActions = [ { label: 'Open logs', href: '#', icon: , }, ] return ( <> hello-world {FUNCTION_URL} Last deployed 2 hours ago
{CHART_INTERVALS.map((item, index) => ( ))}
Errors since last deploy
Performance
}>
`${Math.round(value)}ms`, }} />
Usage
}>
`${Math.round(value)}ms`, }} />
}>
`${Number(value).toFixed(1)}MB`, }} />
) } function CodePage() { const initialFiles = useMemo(() => MOCK_CODE_FILES, []) const [files, setFiles] = useState(initialFiles) const [selectedFileId, setSelectedFileId] = useState(initialFiles[0]?.id ?? 1) const selectedFile = files.find((file) => file.id === selectedFileId) const treeData = useMemo( () => ({ name: '', children: files.map((file) => ({ id: file.id.toString(), name: file.name, metadata: { originalId: file.id, state: file.state }, })), }), [files] ) const handleContentChange = (value: string) => { setFiles((currentFiles) => currentFiles.map((file) => { if (file.id !== selectedFileId) return file const originalFile = initialFiles.find((item) => item.id === file.id) if (!originalFile) return { ...file, content: value, state: 'new' } if (originalFile.content !== value) return { ...file, content: value, state: 'modified' } return { ...file, content: value, state: 'unchanged' } }) ) } const addNewFile = () => { const newId = Math.max(0, ...files.map((file) => file.id)) + 1 const newFile: MockFileData = { id: newId, name: `file-${newId}.ts`, content: '', state: 'new', } setFiles((currentFiles) => [...currentFiles, newFile]) setSelectedFileId(newId) } const renderFileNode = useCallback( ({ element, isBranch, isExpanded, getNodeProps, level }: INodeRendererProps) => { const originalId = typeof element.metadata?.originalId === 'number' ? element.metadata.originalId : null const state = element.metadata?.state as MockFileData['state'] return ( } onClick={() => { if (originalId !== null) setSelectedFileId(originalId) }} actions={ state !== 'unchanged' && (
{state === 'new' ? 'U' : 'M'} {state === 'new' ? 'Unsaved' : 'Modified'}
) } /> ) }, [selectedFileId] ) return (

Files

{selectedFile ? (