@djangocfg/devtools 2.1.459
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.
- package/README.md +41 -0
- package/package.json +66 -0
- package/src/Devtools.tsx +48 -0
- package/src/capture.ts +175 -0
- package/src/index.ts +44 -0
- package/src/ingest.ts +27 -0
- package/src/instance.ts +185 -0
- package/src/internal.ts +85 -0
- package/src/panel/DevtoolsButton.tsx +129 -0
- package/src/panel/DevtoolsPanel.tsx +142 -0
- package/src/panel/LogsTab.tsx +165 -0
- package/src/server.ts +86 -0
- package/src/store.ts +170 -0
- package/src/styles.css +6 -0
- package/src/types.ts +113 -0
- package/src/useDebugMode.ts +58 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @djangocfg/devtools — shared types.
|
|
3
|
+
*
|
|
4
|
+
* `DevtoolsEvent` is the hand-written contract of `POST /cfg/monitor/ingest/`
|
|
5
|
+
* (django-cfg's django_monitor extension). The endpoint is a stable, additive
|
|
6
|
+
* API — a generated OpenAPI client here would be 2k+ lines to describe one
|
|
7
|
+
* POST, so we don't. If the backend contract changes shape (it shouldn't),
|
|
8
|
+
* this file is the single place to update.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ─── Backend event contract ──────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export const EventType = {
|
|
14
|
+
JS_ERROR: 'JS_ERROR',
|
|
15
|
+
NETWORK_ERROR: 'NETWORK_ERROR',
|
|
16
|
+
ERROR: 'ERROR',
|
|
17
|
+
WARNING: 'WARNING',
|
|
18
|
+
CONSOLE: 'CONSOLE',
|
|
19
|
+
} as const
|
|
20
|
+
export type EventType = (typeof EventType)[keyof typeof EventType]
|
|
21
|
+
|
|
22
|
+
export const EventLevel = {
|
|
23
|
+
ERROR: 'error',
|
|
24
|
+
WARNING: 'warning',
|
|
25
|
+
INFO: 'info',
|
|
26
|
+
DEBUG: 'debug',
|
|
27
|
+
} as const
|
|
28
|
+
export type EventLevel = (typeof EventLevel)[keyof typeof EventLevel]
|
|
29
|
+
|
|
30
|
+
/** One ingest event. Field limits mirror the backend serializer. */
|
|
31
|
+
export interface DevtoolsEvent {
|
|
32
|
+
event_type: EventType
|
|
33
|
+
message: string
|
|
34
|
+
level?: EventLevel
|
|
35
|
+
stack_trace?: string
|
|
36
|
+
url?: string
|
|
37
|
+
fingerprint?: string
|
|
38
|
+
http_status?: number | null
|
|
39
|
+
http_method?: string
|
|
40
|
+
http_url?: string
|
|
41
|
+
session_id?: string
|
|
42
|
+
user_agent?: string
|
|
43
|
+
build_id?: string
|
|
44
|
+
environment?: string
|
|
45
|
+
extra?: unknown
|
|
46
|
+
project_name?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
export interface DevtoolsConfig {
|
|
52
|
+
/** Base URL for the django-cfg backend. Default: NEXT_PUBLIC_API_URL or same origin */
|
|
53
|
+
baseUrl?: string
|
|
54
|
+
/** Project name sent with every event */
|
|
55
|
+
project?: string
|
|
56
|
+
/** Environment tag: production / staging / development */
|
|
57
|
+
environment?: string
|
|
58
|
+
/** Next.js BUILD_ID for server-side source map deminification */
|
|
59
|
+
buildId?: string
|
|
60
|
+
/** Send captured errors/warnings to the backend ingest endpoint. Default: true */
|
|
61
|
+
ingest?: boolean
|
|
62
|
+
/** Capture window.onerror + unhandledrejection. Default: true */
|
|
63
|
+
captureJsErrors?: boolean
|
|
64
|
+
/** Capture console.warn / console.error. Default: true */
|
|
65
|
+
captureConsole?: boolean
|
|
66
|
+
/** Ingest flush interval in ms. Default: 5000 */
|
|
67
|
+
flushInterval?: number
|
|
68
|
+
/** Max events in the outbox before immediate flush. Default: 20 */
|
|
69
|
+
maxBufferSize?: number
|
|
70
|
+
/** Deduplication TTL in ms (same event captured twice). Default: 30000 */
|
|
71
|
+
dedupeTtl?: number
|
|
72
|
+
/** Log devtools internals to console. Default: false */
|
|
73
|
+
debug?: boolean
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ServerDevtoolsConfig {
|
|
77
|
+
/** Base URL for the django-cfg backend (absolute URL required on server) */
|
|
78
|
+
baseUrl?: string
|
|
79
|
+
project?: string
|
|
80
|
+
environment?: string
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Panel log entries (local, never sent anywhere) ─────────────────────────
|
|
84
|
+
|
|
85
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'success'
|
|
86
|
+
|
|
87
|
+
export interface LogEntry {
|
|
88
|
+
id: string
|
|
89
|
+
timestamp: Date
|
|
90
|
+
level: LogLevel
|
|
91
|
+
/** Where the entry came from: a component name, or `capture:<event_type>` */
|
|
92
|
+
source: string
|
|
93
|
+
message: string
|
|
94
|
+
data?: Record<string, unknown>
|
|
95
|
+
stack?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface Logger {
|
|
99
|
+
debug: (message: string, data?: Record<string, unknown>) => void
|
|
100
|
+
info: (message: string, data?: Record<string, unknown>) => void
|
|
101
|
+
warn: (message: string, data?: Record<string, unknown>) => void
|
|
102
|
+
error: (message: string, data?: Record<string, unknown>) => void
|
|
103
|
+
success: (message: string, data?: Record<string, unknown>) => void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Panel customization ─────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
export interface CustomDebugTab {
|
|
109
|
+
id: string
|
|
110
|
+
label: string
|
|
111
|
+
icon: React.ElementType
|
|
112
|
+
panel: React.ComponentType<{ isActive: boolean }>
|
|
113
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useDebugMode — debug mode detection.
|
|
5
|
+
*
|
|
6
|
+
* - development: always true
|
|
7
|
+
* - production: `?debug=1` persists to localStorage (param removed from URL),
|
|
8
|
+
* `?debug=0` clears it; otherwise the stored flag decides.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from 'react'
|
|
12
|
+
|
|
13
|
+
import { isDevelopment } from './internal'
|
|
14
|
+
|
|
15
|
+
const LS_KEY = '__debug_mode__'
|
|
16
|
+
|
|
17
|
+
function readStored(): boolean {
|
|
18
|
+
try {
|
|
19
|
+
return localStorage.getItem(LS_KEY) === '1'
|
|
20
|
+
} catch {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function consumeDebugParam(): boolean | null {
|
|
26
|
+
if (typeof window === 'undefined') return null
|
|
27
|
+
const params = new URLSearchParams(window.location.search)
|
|
28
|
+
const value = params.get('debug')
|
|
29
|
+
if (value === null) return null
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
if (value === '1') localStorage.setItem(LS_KEY, '1')
|
|
33
|
+
else localStorage.removeItem(LS_KEY)
|
|
34
|
+
} catch {
|
|
35
|
+
/* private browsing */
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
params.delete('debug')
|
|
39
|
+
const qs = params.toString()
|
|
40
|
+
window.history.replaceState(
|
|
41
|
+
null,
|
|
42
|
+
'',
|
|
43
|
+
`${window.location.pathname}${qs ? `?${qs}` : ''}${window.location.hash}`,
|
|
44
|
+
)
|
|
45
|
+
return value === '1'
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useDebugMode(): boolean {
|
|
49
|
+
const [isDebug, setIsDebug] = useState(isDevelopment)
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (isDevelopment) return
|
|
53
|
+
const fromUrl = consumeDebugParam()
|
|
54
|
+
setIsDebug(fromUrl ?? readStored())
|
|
55
|
+
}, [])
|
|
56
|
+
|
|
57
|
+
return isDebug
|
|
58
|
+
}
|