chore: start log preview renderers

This commit is contained in:
Jonathan Summers-Muir authored and Copple committed 2022-03-30 17:07:39 +02:00
1 parent 6c2a86ee1f
commit 3d8bc63f26
15 files changed
+913 -130

No files matched your search

@@ -0,0 +1,22 @@
import dayjs from 'dayjs'
import { ResponseCodeFormatter } from '../LogsFormatters'
export default [
{
formatter: (data: any) => (
<div className="flex h-full w-full items-center justify-between gap-3">
<div className="flex h-full w-full items-center gap-4">
<ResponseCodeFormatter row={data} value={data.row.metadata[0].response[0].status_code} />
<span className="text-xs w-14">{data.row.metadata[0].request[0].method}</span>
<span className="font-mono text-xs">{data.row.metadata[0].request[0].path}</span>
</div>
<div>
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs">{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}</span>
<span className="text-xs">{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}</span>
</span>
</div>
</div>
),
},
]
@@ -0,0 +1,23 @@
import dayjs from 'dayjs'
import { SeverityFormatter } from '../LogsFormatters'
export default [
{
formatter: (data: any) => (
<div className="flex w-full items-center gap-2">
<div className="flex items-center gap-2">
<span className="w-20 flex items-center gap-1">
<span className="text-xs">{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}</span>
<span className="text-xs">{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}</span>
</span>
<div className="w-16 flex justify-center">
<SeverityFormatter value={data?.row?.metadata[0].parsed[0].error_severity} />
</div>
</div>
<div className="flex truncate">
<span className="font-mono text-xs truncate">{data.row.event_message}</span>
</div>
</div>
),
},
]
@@ -2,67 +2,158 @@ import dayjs from 'dayjs'
import { FC } from 'react'
import { Typography, IconX, Input } from '@supabase/ui'
import { LogData } from './Logs.types'
import { LogData, QueryType } from './Logs.types'
import JsonEditor from 'components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonCodeEditor'
import { jsonSyntaxHighlight } from './LogsFormatters'
import DatabaseApiSelectionRender, {
DatabaseApiSelectionHeaderRender,
} from './LogSelectionRenderers/DatabaseApiSelectionRender'
import DatabasePostgresSelectionRender from './LogSelectionRenderers/DatabasePostgresSelectionRender'
interface Props {
log: LogData
onClose: () => void
queryType: QueryType
}
/**
* Log selection display
*/
const LogSelection: FC<Props> = ({ log, onClose }) => {
const LogSelection: FC<Props> = ({ log, onClose, queryType }) => {
// console.log('log in selecion', log)
console.log('log selection in component', log)
// @ts-ignore
console.log('log response', log?.response)
// @ts-ignore
console.log('log request', log?.request)
// response highlight
// @ts-ignore
const response = log?.response ? jsonSyntaxHighlight(log.response) : ''
console.log('output', response)
const Formatter = () => {
switch (queryType) {
case 'api':
return <DatabaseApiSelectionRender log={log} />
break
case 'database':
return <DatabasePostgresSelectionRender log={log} />
break
default:
return null
break
}
}
function header() {
switch (queryType) {
case 'api':
return DatabaseApiSelectionHeaderRender(log)
break
default:
return null
break
}
}
return (
<div
className={[
'h-full flex flex-col flex-grow border border-l-0',
'h-full flex flex-col flex-grow border border-l',
'border-panel-border-light dark:border-panel-border-dark',
'bg-gray-200',
].join(' ')}
>
<div
className={[
'bg-panel-header-light dark:bg-panel-header-dark',
'border-b border-panel-border-interior-light',
'dark:border-panel-border-interior-dark',
].join(' ')}
>
<div className="px-6 py-4 flex items-center">
<div className="flex flex-row justify-between items-center w-full">
<Typography.Title level={5}>{dayjs(log.timestamp / 1000).toString()}</Typography.Title>
<div className="cursor-pointer" onClick={onClose}>
<IconX size={14} />
{log ? (
<>
<div
className={[
'bg-panel-header-light dark:bg-panel-header-dark',
'border-b border-panel-border-interior-light',
'dark:border-panel-border-interior-dark',
].join(' ')}
>
<div className="px-6 py-4 flex items-center">
<div className="flex flex-row justify-between items-center w-full">
{/* <Typography.Title level={5}>
{dayjs(log.timestamp / 1000).toString()}
</Typography.Title> */}
<h3 className="text-xl font-semibold">{header()}</h3>
<div className="cursor-pointer" onClick={onClose}>
<IconX size={14} />
</div>
</div>
</div>
</div>
</div>
</div>
<div className="flex-grow overflow-y-auto bg-panel-body-light dark:bg-panel-body-dark">
<div className="p-4 flex-col space-y-4">
<div className="space-y-2">
<p className="text-sm">Event message</p>
<Input
copy
readOnly
size="small"
layout="vertical"
className="text-sm input-mono"
value={log.event_message}
/>
<div
className="
flex-grow overflow-y-auto
bg-panel-body-light dark:bg-panel-body-dark
space-y-6
py-8
"
>
{/* <div className="p-4 flex-col space-y-4">
<div className="space-y-2">
<p className="text-sm">Event message</p>
<Input
copy
readOnly
size="small"
layout="vertical"
className="text-sm input-mono"
value={log.event_message}
/>
</div>
<div className="space-y-2">
<p className="text-sm">Metadata</p>
<Input.TextArea
copy
readOnly
size="small"
layout="vertical"
rows={20}
className="input-mono"
value={JSON.stringify(log.metadata, null, 2)}
/>
</div>
</div> */}
<Formatter />
{/* <pre className="text-sm px-5">
<h3 className="text-xl text-scale-1200">Request</h3>
<div
dangerouslySetInnerHTML={{
__html: log.request ? jsonSyntaxHighlight(log.request) : '',
}}
/>
</pre>
<pre className="text-sm px-5">
<h3 className="text-xl text-scale-1200">Request</h3>
<div
dangerouslySetInnerHTML={{
__html: log.response ? jsonSyntaxHighlight(log.response) : '',
}}
/>
</pre> */}
</div>
<div className="space-y-2">
<p className="text-sm">Metadata</p>
<Input.TextArea
copy
readOnly
size="small"
layout="vertical"
rows={20}
className="input-mono"
value={JSON.stringify(log.metadata, null, 2)}
/>
</>
) : (
<div className="flex h-full w-full items-center justify-center">
<div className="w-1/2 text-center flex flex-col gap-2">
<h3 className="text-sm text-scale-1200">Select an Event</h3>
<p className="text-xs text-scale-1100">
Select an Event to view the code snippet (pretty view) or complete JSON payload (raw
view).
</p>
</div>
</div>
</div>
)}
</div>
)
}
@@ -0,0 +1,13 @@
import { QueryType } from './Logs.types'
const SelectionRenderHandler = ({ type }: { type: QueryType }) => {
switch (type) {
case 'api':
break
default:
break
}
}
export default SelectionRenderHandler
@@ -0,0 +1,68 @@
import React from 'react'
import { jsonSyntaxHighlight, ResponseCodeFormatter } from '../LogsFormatters'
const DatabaseApiSelectionRender = ({ log }: any) => {
const request = log?.metadata[0]?.request[0]
const response = log?.metadata[0]?.response[0]
const method = log?.metadata[0]?.request[0]?.method
const status = log?.metadata[0]?.response[0].status_code
const ipAddress = log?.metadata[0]?.request[0].headers[0]?.cf_connecting_ip
const countryOrigin = log?.metadata[0]?.request[0].headers[0]?.cf_ipcountry
const clientInfo = log?.metadata[0]?.request[0].headers[0]?.x_client_info
const referer = log?.metadata[0]?.request[0].headers[0]?.referer
const DetailedRow = ({ label, value }: { label: string; value: string | React.ReactNode }) => {
return (
<div className="grid grid-cols-12">
<span className="text-scale-900 text-sm col-span-4">{label}</span>
<span className="text-scale-1200 text-base col-span-8">{value}</span>
</div>
)
}
return (
<>
<div className="px-5 space-y-2">
<DetailedRow label="Status" value={<ResponseCodeFormatter value={status} />} />
<DetailedRow label="Method" value={method} />
<DetailedRow label="Timestamp" value={log.timestamp} />
<DetailedRow label="IP Address" value={ipAddress} />
<DetailedRow label="Origin Country" value={countryOrigin} />
{clientInfo && <DetailedRow label="Client" value={clientInfo} />}
{referer && <DetailedRow label="Referer" value={referer} />}
</div>
<div className="px-5">
<h3 className="text-lg text-scale-1200 mb-4">Request body</h3>
<pre className="text-sm syntax-highlight overflow-x-auto">
<div
className="text-wrap"
dangerouslySetInnerHTML={{
__html: request ? jsonSyntaxHighlight(log.metadata[0].request[0]) : '',
}}
/>
</pre>
</div>
<div className="px-5">
<h3 className="text-lg text-scale-1200 mb-4">
Response{method ? ` ${method}` : null} body
</h3>
<pre className="text-sm syntax-highlight overflow-x-auto">
<div
dangerouslySetInnerHTML={{
__html: response ? jsonSyntaxHighlight(log.metadata[0].response[0]) : '',
}}
/>
</pre>
</div>
</>
)
}
export const DatabaseApiSelectionHeaderRender = (log: any) => {
const method = log?.metadata[0]?.request[0]?.method
const path = log?.metadata[0]?.request[0]?.path
return `${method} ${path}`
}
export default DatabaseApiSelectionRender
@@ -0,0 +1,67 @@
import { Alert } from '@supabase/ui'
import React from 'react'
import { jsonSyntaxHighlight, SeverityFormatter } from '../LogsFormatters'
const DatabasePostgresSelectionRender = ({ log }: any) => {
const postgresUsername = log?.metadata[0]?.parsed[0]?.user_name
const sessionId = log?.metadata[0]?.parsed[0]?.session_id
const hint = log?.metadata[0]?.parsed[0]?.hint
const DetailedRow = ({ label, value }: { label: string; value: string | React.ReactNode }) => {
return (
<div className="grid grid-cols-12">
<span className="text-scale-900 text-sm col-span-4">{label}</span>
<span className="text-scale-1200 text-base col-span-8">{value}</span>
</div>
)
}
return (
<>
<div className="px-5">
<span className="text-scale-900 text-sm col-span-4">Event message</span>
<div
className="text-wrap font-mono text-scale-1200 mt-2"
dangerouslySetInnerHTML={{
__html: log.event_message,
}}
/>
</div>
<div className="h-px w-full bg-panel-border-interior-light dark:bg-panel-border-interior-dark"></div>
<div className="px-5 space-y-2">
<DetailedRow label="Severity" value={<SeverityFormatter value={log.error_severity} />} />
<DetailedRow label="Postgres Username" value={postgresUsername} />
<DetailedRow label="Session ID" value={sessionId} />
</div>
{hint && (
<div className="mt-4 px-5">
<Alert variant="warning" withIcon title={log?.metadata[0]?.parsed[0]?.hint} />
</div>
)}
<div className="h-px w-full bg-panel-border-interior-light dark:bg-panel-border-interior-dark"></div>
<div className="px-5">
<h3 className="text-lg text-scale-1200 mb-4">Metadata</h3>
<pre className="text-sm syntax-highlight overflow-x-auto">
<div
className="text-wrap"
dangerouslySetInnerHTML={{
__html: log.metadata ? jsonSyntaxHighlight(log.metadata[0]) : '',
}}
/>
</pre>
</div>
</>
)
}
export const DatabaseApiSelectionHeaderRender = (log: any) => {
const method = log?.metadata[0]?.request[0]?.method
const path = log?.metadata[0]?.request[0]?.path
return `${method} ${path}`
}
export default DatabasePostgresSelectionRender
@@ -0,0 +1,26 @@
import { jsonSyntaxHighlight } from '../LogsFormatters'
const FunctionInvocationSelectionRender = ({ log }: any) => {
return (
<>
<pre className="text-sm px-5">
<h3 className="text-xl text-scale-1200">Request</h3>
<div
dangerouslySetInnerHTML={{
__html: log.request ? jsonSyntaxHighlight(log.request) : '',
}}
/>
</pre>
<pre className="text-sm px-5">
<h3 className="text-xl text-scale-1200">Response</h3>
<div
dangerouslySetInnerHTML={{
__html: log.response ? jsonSyntaxHighlight(log.response) : '',
}}
/>
</pre>
</>
)
}
export default FunctionInvocationSelectionRender
@@ -1,15 +1,21 @@
import dayjs from 'dayjs'
import { useEffect, useState, useMemo } from 'react'
import { Typography } from '@supabase/ui'
import DataGrid from '@supabase/react-data-grid'
import { Badge, Button, IconDownload, IconDownloadCloud, IconEye, Typography } from '@supabase/ui'
import DataGrid, { Row } from '@supabase/react-data-grid'
import LogSelection from './LogSelection'
import { LogData } from './Logs.types'
import { LogData, Mode, QueryType } from './Logs.types'
import { isNil } from 'lodash'
import { SeverityFormatter, ResponseCodeFormatter, HeaderFormmater } from './LogsFormatters'
// column renders
import DatabaseApiColumnRender from './LogColumnRenderers/DatabaseApiColumnRender'
import DatabasePostgresColumnRender from './LogColumnRenderers/DatabasePostgresColumnRender'
interface Props {
isCustomQuery: boolean
data?: Array<LogData | Object>
queryType: QueryType
}
type LogMap = { [id: string]: LogData }
@@ -18,43 +24,180 @@ type LogMap = { [id: string]: LogData }
*
* When in custom data display mode, the side panel will not open when focusing on logs.
*/
const LogTable = ({ isCustomQuery, data = [] }: Props) => {
const LogTable = ({ isCustomQuery, data = [], queryType }: Props) => {
const [focusedLog, setFocusedLog] = useState<LogData | null>(null)
const columnNames = Object.keys(data[0] || {})
// whether the data structure is LogData format.
const hasLogDataFormat =
columnNames.includes('timestamp') &&
columnNames.includes('event_message') &&
columnNames.length === 4
const columns = (hasLogDataFormat ? ['timestamp', 'event_message'] : columnNames).map((v) => ({
key: v,
name: v,
width: hasLogDataFormat && v === 'timestamp' ? 210 : undefined,
resizable: true,
headerRenderer: () => {
return <div className="flex items-center text-xs font-mono h-full">{v}</div>
},
formatter: ({ row }: any) => {
let value = row[v]
if (hasLogDataFormat && v === 'timestamp') {
value = dayjs(Number(row['timestamp']) / 1000).toISOString()
console.log('queryType', queryType)
// console.log('focusedLog', focusedLog)
isCustomQuery = false
// whether the data structure is LogData format.
// const hasLogDataFormat =
// columnNames.includes('timestamp') &&
// columnNames.includes('event_message') &&
// columnNames.length === 4
const hasLogDataFormat = !isCustomQuery
const DEFAULT_COLUMNS = (hasLogDataFormat ? ['timestamp', 'event_message'] : columnNames).map(
(v) => ({
key: v,
name: v,
width: hasLogDataFormat && v === 'timestamp' ? 210 : undefined,
resizable: true,
headerRenderer: () => {
return <div className="flex items-center text-xs font-mono h-full">{v}</div>
},
formatter: ({ row }: any) => {
let value = row[v]
if (hasLogDataFormat && v === 'timestamp') {
value = dayjs(Number(row['timestamp']) / 1000).toISOString()
}
return (
<p
className={[
'block whitespace-wrap font-mono',
`${hasLogDataFormat && row.id === focusedLog?.id ? 'font-bold' : ''}`,
`${hasLogDataFormat && v === 'timestamp' ? 'text-green-900' : ''}`,
].join(' ')}
>
{value}
</p>
)
},
})
)
let columns
console.log('QUERY TYPE', queryType)
switch (queryType) {
case 'api':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
break
}
return (
<p
className={[
'block whitespace-wrap font-mono',
`${hasLogDataFormat && row.id === focusedLog?.id ? 'font-bold' : ''}`,
`${hasLogDataFormat && v === 'timestamp' ? 'text-green-900' : ''}`,
].join(' ')}
>
{value}
</p>
)
},
}))
const stringData = JSON.stringify(data)
console.log('is api, running column')
columns = DatabaseApiColumnRender
break
case 'database':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
break
}
console.log('is api, running column')
columns = DatabasePostgresColumnRender
break
case 'fn_edge':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
break
}
columns = [
{
key: 'timestamp',
headerRenderer: () => (
<div className="flex w-full justify-end h-full">
<HeaderFormmater value={'timestamp'} />
</div>
),
name: 'timestamp',
formatter: (data: any) => (
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs">{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}</span>
<span className="text-xs">
{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}
</span>
{/* {data?.row?.timestamp} */}
</span>
),
width: 128,
},
{
key: 'status_code',
headerRenderer: () => <HeaderFormmater value={'Status'} />,
name: 'status_code',
formatter: (data: any) => (
<ResponseCodeFormatter row={data} value={data.row.status_code} />
),
width: 0,
resizable: true,
},
{
key: 'method',
headerRenderer: () => <HeaderFormmater value={'method'} />,
width: 0,
resizable: true,
},
{
key: 'id',
headerRenderer: () => <HeaderFormmater value={'id'} />,
name: 'id',
resizable: true,
},
]
break
case 'functions':
if (isCustomQuery) {
columns = DEFAULT_COLUMNS
break
}
columns = [
{
key: 'timestamp',
headerRenderer: () => (
<div className="flex w-full justify-end h-full">
<HeaderFormmater value={'timestamp'} />
</div>
),
name: 'timestamp',
formatter: (data: any) => (
<span className="flex w-full h-full items-center gap-1">
<span className="text-xs !text-scale-1100">
{dayjs(data?.row?.timestamp / 1000).format('DD MMM')}
</span>
<span className="text-xs !text-scale-1100">
{dayjs(data?.row?.timestamp / 1000).format('HH:mm:ss')}
</span>
{/* {data?.row?.timestamp} */}
</span>
),
width: 128,
resizable: true,
},
{
key: 'level',
headerRenderer: () => <HeaderFormmater value={'Level'} />,
name: 'level',
formatter: (data: any) => <SeverityFormatter value={data.row.level} />,
width: 24,
resizable: true,
},
{
key: 'event_message',
headerRenderer: () => <HeaderFormmater value={'Event message'} />,
resizable: true,
},
]
break
default:
columns = DEFAULT_COLUMNS
break
}
const logMap = useMemo(() => {
if (!hasLogDataFormat) return {} as LogMap
const logData = data as LogData[]
@@ -62,17 +205,23 @@ const LogTable = ({ isCustomQuery, data = [] }: Props) => {
acc[d.id] = d
return acc
}, {}) as LogMap
}, [stringData])
}, [JSON.stringify(data)])
const stringData = JSON.stringify(data)
useEffect(() => {
if (!hasLogDataFormat) return
if (isNil(data)) return
if (focusedLog && !(focusedLog.id in logMap)) {
setFocusedLog(null)
}
}
}, [stringData])
if (!data) return null
// console.log(data)
// console.log('logMap', logMap)
// console.log(columns)
// [Joshen] Hmm quite hacky now, but will do
const maxHeight = isCustomQuery ? 'calc(100vh - 42px - 10rem)' : 'calc(100vh - 42px - 3rem)'
@@ -81,45 +230,103 @@ const LogTable = ({ isCustomQuery, data = [] }: Props) => {
return Object.values(logMap).sort((a, b) => b.timestamp - a.timestamp)
}, [stringData])
return (
<section className="flex flex-1 flex-row" style={{ maxHeight }}>
<DataGrid
style={{ height: '100%' }}
className="flex-grow flex-1"
onSelectedCellChange={({ idx, rowIdx }) => {
if (!hasLogDataFormat) return
setFocusedLog(data[rowIdx] as LogData)
}}
noRowsFallback={
<div className="p-4">
<Typography.Text type="secondary" small className="font-mono">
No data returned from query
</Typography.Text>
<>
{isCustomQuery && (
<div
className="
w-full bg-scale-300 rounded
flex items-center justify-between
px-5 py-2
"
>
<div className="flex items-center gap-2">
{data && data.length ? (
<>
<span className="text-sm text-scale-1200">Query results</span>
<span className="text-sm text-scale-1100">{data && data.length}</span>
</>
) : (
<span className="text-xs text-scale-1200">Results will be shown below</span>
)}
</div>
<div className="flex items-center gap-2">
<Button type="default" icon={<IconEye />}>
Histogram
</Button>
<Button type="default" icon={<IconDownloadCloud />}>
Download
</Button>
</div>
}
columns={columns as any}
rowClass={(r) => {
if (!hasLogDataFormat) return 'cursor-pointer'
const row = r as LogData
return `${row.id === focusedLog?.id ? 'bg-green-800' : 'cursor-pointer'}`
}}
rows={logDataRows}
rowKeyGetter={(r) => {
if (!hasLogDataFormat) return Object.keys(r)[0]
const row = r as LogData
return row.id
}}
onRowClick={(r) => {
if (!hasLogDataFormat) return
const row = r as LogData
setFocusedLog(logMap[row.id])
}}
/>
{focusedLog && (
<div className="w-2/5 flex flex-col">
<LogSelection onClose={() => setFocusedLog(null)} log={focusedLog} />
</div>
)}
</section>
<section className="flex flex-1 flex-row" style={{ maxHeight }}>
<DataGrid
style={{ height: '100%' }}
className={`
flex-grow flex-1
${isCustomQuery ? '' : ' data-grid--simple-logs'}
`}
rowHeight={40}
headerRowHeight={0}
onSelectedCellChange={({ idx, rowIdx }) => {
if (!hasLogDataFormat) return
setFocusedLog(data[rowIdx] as LogData)
}}
noRowsFallback={
<div className="p-4">
<Typography.Text type="secondary" small className="font-mono">
No data returned from query
</Typography.Text>
</div>
}
columns={columns as any}
rowClass={(r) => {
// if (!hasLogDataFormat) return 'cursor-pointer'
const row = r as LogData
let classes = []
//@ts-ignore
// if (row.status_code) {
// const response = parseResponseCode(row.status_code)
// if (response == 'warning') {
// classes.push('!bg-yellow-200 !dark:bg-yellow-100')
// }
// if (response == 'error') {
// classes.push('!bg-red-200')
// }
// }
classes.push(
`${row.id === focusedLog?.id ? '!bg-scale-400 rdg-row--focussed' : 'cursor-pointer'}`
)
return classes.join(' ')
}}
rows={logDataRows}
rowKeyGetter={(r) => {
if (!hasLogDataFormat) return Object.keys(r)[0]
const row = r as LogData
return row.id
}}
onRowClick={(r) => {
// if (!hasLogDataFormat) return
const row = r as LogData
setFocusedLog(logMap[row.id])
}}
/>
{/* {hasLogDataFormat ||
(focusedLog && ( */}
<div className="w-1/2 flex flex-col">
<LogSelection
onClose={() => setFocusedLog(null)}
log={focusedLog}
queryType={queryType}
/>
</div>
{/* ))} */}
</section>
</>
)
}
export default LogTable
@@ -172,28 +172,30 @@ LIMIT 100
]
export const LOG_TYPE_LABEL_MAPPING: { [k: string]: string } = {
explorer: "Explorer",
explorer: 'Explorer',
api: 'API',
database: 'Database',
}
export const genDefaultQuery = (table: string, where: string = ''): string => `SELECT
id, timestamp, event_message, metadata
FROM
${table}${where ? ' WHERE\n ' + where : ''}
export const genDefaultQuery = (table: LogsTableName, where: string = '') => {
console.log('genDefaultQuery table', table)
switch (table) {
case 'edge_logs':
return `SELECT id, timestamp, event_message, metadata
FROM ${table}
${where ? ' WHERE\n ' + where : ''}
LIMIT 100
`
export const cleanQuery = (str: string) => str.replace(/\n/g, ' ')
// .replace(/\n.*\-\-.*(\n)?$?/, "")
export enum LogsTableName {
EDGE = 'edge_logs',
POSTGRES = 'postgres_logs',
}
export const genCountQuery = (table: string): string => `SELECT count(*) as count FROM ${table}`
export const genQueryParams = (params: { [k: string ]: string }) => {
export const genQueryParams = (params: { [k: string]: string }) => {
// remove keys which are empty strings, null, or undefined
for (const k in params) {
const v = params[k]
@@ -3,6 +3,7 @@ interface Metadata {
}
export type LogSearchCallback = (filters: { query: string; to?: string; from?: string; fromMicro?: number, toMicro?: number }) => void
export type LogSearchCallback = (filters: { query: string; to?: string; toMicro?: number }) => void
export interface LogsEndpointParams {
// project ref
project: string
@@ -44,3 +45,9 @@ type LFResponse<T> = {
export type Count = LFResponse<CountData>
export type Logs = LFResponse<LogData>
export type QueryType = 'api' | 'database' | 'functions' | 'fn_edge' | 'sql'
export type Mode = 'simple' | 'custom'
export type Table = 'edge_logs' | 'postgres_logs'
@@ -0,0 +1,207 @@
/*
* Response Code
*
* for http response codes
*/
import { IconAlertCircle, IconInfo } from '@supabase/ui'
export const ResponseCodeFormatter = ({ value }: any) => {
if (!value) {
return (
<div>
<label className="text-xs text-scale-800">No data</label>
</div>
)
}
const split = value.toString().split('')[0]
// console.log(split)
switch (split) {
// 2XX || 1XX responses
case '1':
case '2':
return (
<div className="flex items-center h-full">
<div
className="relative rounded px-2 py-1 text-center h-6 flex justify-center items-center
bg-scale-300 dark:bg-scale-600
"
>
<label className="block font-mono text-sm text-scale-900">{value}</label>
</div>
</div>
)
break
// 5XX responses
case '5':
return (
<div className="flex items-center h-full">
<div
className="relative rounded px-2 py-1 text-center h-6 flex justify-center items-center
bg-red-400
"
>
<label className="block font-mono text-sm text-red-1100">{value}</label>
</div>
</div>
)
break
// 4XX || 3XX responses
case '4':
case '3':
return (
<div className="flex items-center h-full">
<div
className="relative rounded px-2 py-1 text-center h-6 flex justify-center items-center
bg-amber-400
"
>
<label className="block font-mono text-sm text-amber-1100 dark:text-amber-900">
{value}
</label>
</div>
</div>
)
break
// All other responses
default:
return (
<div className="flex items-center h-full">
<div
className="relative rounded px-2 py-1 text-center h-6 flex justify-center items-center
bg-scale-300
"
>
<label className="block font-mono text-sm text-scale-900">{value}</label>
</div>
</div>
)
break
}
}
/*
* Response Code
*
* for http response codes
*/
export const SeverityFormatter = ({ value }: { value: string }) => {
if (!value) {
return (
<div>
<label className="text-xs text-scale-800">No data</label>
</div>
)
}
value = value.toLowerCase()
switch (value) {
case 'error':
return (
<div className="flex items-center h-full gap-1">
<div className=" p-0.5 rounded !text-red-900">
<IconAlertCircle size={14} strokeWidth={2} />
</div>
<span className="!text-red-900 !block titlecase">{value}</span>
</div>
)
break
case 'info':
case 'debug':
return (
<div className="flex items-center h-full gap-1">
<div className=" p-0.5 rounded !text-blue-900">
<IconAlertCircle size={14} strokeWidth={2} />
</div>
<span className="!text-blue-900 !block titlecase">{value}</span>
</div>
)
break
case 'log':
return (
<div className="flex items-center h-full gap-1">
<div className=" p-0.5 rounded !text-blue-900">
<IconInfo size={14} strokeWidth={2} />
</div>
<span className="!text-blue-900 !block titlecase">{value}</span>
</div>
)
break
// All other responses
default:
return (
<div className="flex items-center h-full">
<div
className="relative rounded px-2 py-1 text-center h-6 flex justify-center items-center
bg-scale-300
"
>
<label className="block font-mono text-sm text-scale-900">{value}</label>
</div>
</div>
)
break
}
}
/*
* Header Formatter
*
* for http response codes
*/
export const HeaderFormmater = ({ value }: any) => {
return <div className="text-scale-900 font-normal flex items-center text-xs h-full">{value}</div>
}
/*
* JSON Syntax Highlighter
*
* for http response codes
*/
export function jsonSyntaxHighlight(json: string) {
json = JSON.stringify(json, null, 2)
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
console.log('json', json)
const newJson = json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
function (match) {
var cls = 'number text-tomato-900'
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key text-scale-1200'
} else {
cls = 'string text-brand-1100'
}
} else if (/true|false/.test(match)) {
cls = 'boolean text-blue-900'
} else if (/null/.test(match)) {
cls = 'null text-amber-1100'
}
return '<span class="' + cls + '">' + match + '</span>'
}
)
const jsonWithLineWraps = newJson.split(`\n`).map((x) => {
return `<span class="line">${x}</span>`
})
return jsonWithLineWraps.join('\n')
}
+1
View File
@@ -7,6 +7,7 @@ import 'styles/toast.scss'
import 'styles/code.scss'
import 'styles/monaco.scss'
import 'styles/contextMenu.scss'
import 'styles/react-data-grid-logs.scss'
import Head from 'next/head'
import type { AppProps } from 'next/app'
@@ -125,7 +125,11 @@ export const LogPage: NextPage = () => {
return (
<SettingsLayout title={title}>
<div className="h-full flex flex-col flex-grow px-5 md:px-5 xl:px-16 py-8">
<div
className="h-full flex flex-col flex-grow px-5 md:px-5 xl:px-16 py-8
space-y-4
"
>
<PreviewFilterPanel
isShowingEventChart={showChart}
onToggleEventChart={() => setShowChart(!showChart)}
@@ -153,7 +157,7 @@ export const LogPage: NextPage = () => {
}}
onSelectTemplate={onSelectTemplate}
/>
{showChart && (
{/* {showChart && (
<div>
<LogEventChart
data={!isLoading ? logData : undefined}
@@ -162,7 +166,7 @@ export const LogPage: NextPage = () => {
}}
/>
</div>
)}
)} */}
<div className="flex flex-col flex-grow relative">
{isLoading && (
<div
@@ -175,7 +179,7 @@ export const LogPage: NextPage = () => {
</div>
)}
<LogTable data={logData} isCustomQuery={false} />
<LogTable data={logData} isCustomQuery={false} queryType={type} />
<div className="p-2">
<Button onClick={() => loadOlder()} icon={<IconRewind />} type="default">
Load older
+45
View File
@@ -0,0 +1,45 @@
.data-grid--simple-logs {
.rdg-cell,
.rdg-cell span {
@apply text-scale-1100 font-normal #{!important};
border-left: none;
border-right: none;
}
.rdg-cell:first-child {
@apply pl-5;
}
.rdg-cell:last-child {
@apply pr-5;
}
.rdg-row {
&.rdg-row--focussed {
@apply border-r border-brand-900;
border-right-width: 4px;
}
&.rdg-row--focussed .rdg-cell,
&.rdg-row--focussed .rdg-cell span {
@apply text-scale-1200 font-semibold #{!important};
}
}
}
.syntax-highlight {
counter-reset: line-numbering;
font-family: Menlo, Monaco, monospace;
color: var(--colors-scale10);
word-break: break-word;
}
.syntax-highlight .line::before {
content: counter(line-numbering);
counter-increment: line-numbering;
padding-right: 0px;
display: inline-block;
color: var(--colors-scale8);
/* space after numbers */
width: 2rem;
text-align: left;
}
+4 -4
View File
@@ -77,7 +77,7 @@ module.exports = ui({
}),
divideColor: (theme) => ({
...theme('colors'),
DEFAULT: 'var(--colors-scale3)',
DEFAULT: 'var(--colors-scale6)',
dark: 'var(--colors-scale2)',
}),
extend: {
@@ -222,7 +222,7 @@ module.exports = ui({
dark: 'var(--colors-scale4)', // '#1a1a1a',
},
'table-border': {
light: 'var(--colors-scale4)', // gray[100],
light: 'var(--colors-scale5)', // gray[100],
dark: 'var(--colors-scale4)', // '#1a1a1a',
},
@@ -240,11 +240,11 @@ module.exports = ui({
dark: 'var(--colors-scale3)',
},
'panel-border': {
light: 'var(--colors-scale4)',
light: 'var(--colors-scale5)',
dark: 'var(--colors-scale4)',
},
'panel-border-interior': {
light: 'var(--colors-scale2)',
light: 'var(--colors-scale4)',
dark: 'var(--colors-scale4)',
},
'panel-border-hover': {