@neat.is/web 0.2.10
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/app/api/events/route.ts +54 -0
- package/app/api/graph/blast-radius/[id]/route.ts +19 -0
- package/app/api/graph/node/[id]/route.ts +17 -0
- package/app/api/graph/root-cause/[id]/route.ts +17 -0
- package/app/api/graph/route.ts +12 -0
- package/app/api/health/route.ts +13 -0
- package/app/api/incidents/route.ts +16 -0
- package/app/api/policies/violations/route.ts +11 -0
- package/app/api/projects/route.ts +11 -0
- package/app/api/search/route.ts +17 -0
- package/app/api/stale-events/route.ts +15 -0
- package/app/claude-design/Neat Graph View.html +925 -0
- package/app/claude-design/app.js +604 -0
- package/app/components/AppShell.tsx +109 -0
- package/app/components/GraphCanvas.tsx +607 -0
- package/app/components/Inspector.tsx +329 -0
- package/app/components/Rail.tsx +124 -0
- package/app/components/StatusBar.tsx +72 -0
- package/app/components/TopBar.tsx +211 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +891 -0
- package/app/incidents/page.tsx +145 -0
- package/app/layout.tsx +27 -0
- package/app/page.tsx +5 -0
- package/lib/fixtures.ts +94 -0
- package/lib/proxy.ts +16 -0
- package/package.json +53 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { TopBar } from './TopBar'
|
|
5
|
+
import { Rail } from './Rail'
|
|
6
|
+
import { GraphCanvas } from './GraphCanvas'
|
|
7
|
+
import { Inspector } from './Inspector'
|
|
8
|
+
import { StatusBar } from './StatusBar'
|
|
9
|
+
import type { GraphNode, GraphEdge } from '@neat.is/types'
|
|
10
|
+
|
|
11
|
+
export interface GraphData {
|
|
12
|
+
nodes: GraphNode[]
|
|
13
|
+
edges: GraphEdge[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface ProjectEntry { name: string }
|
|
17
|
+
|
|
18
|
+
// ADR-057 #2 — resolution chain. URL → localStorage → first /projects → 'default'.
|
|
19
|
+
function readUrlProject(): string | null {
|
|
20
|
+
if (typeof window === 'undefined') return null
|
|
21
|
+
const v = new URLSearchParams(window.location.search).get('project')
|
|
22
|
+
return v && v.length > 0 ? v : null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readStoredProject(): string | null {
|
|
26
|
+
if (typeof window === 'undefined') return null
|
|
27
|
+
try {
|
|
28
|
+
const v = window.localStorage.getItem('neat:lastProject')
|
|
29
|
+
return v && v.length > 0 ? v : null
|
|
30
|
+
} catch {
|
|
31
|
+
return null
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function AppShell() {
|
|
36
|
+
const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null)
|
|
37
|
+
const [graphData, setGraphData] = useState<GraphData | null>(null)
|
|
38
|
+
// ADR-057 #2 — start with URL or localStorage (synchronous), then resolve
|
|
39
|
+
// against /projects on mount if neither was set.
|
|
40
|
+
const [project, setProjectState] = useState<string>(() => {
|
|
41
|
+
return readUrlProject() ?? readStoredProject() ?? 'default'
|
|
42
|
+
})
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
44
|
+
const cyRef = useRef<any>(null)
|
|
45
|
+
const resolvedRef = useRef(readUrlProject() !== null || readStoredProject() !== null)
|
|
46
|
+
|
|
47
|
+
// ADR-057 #1, #4 — single source of truth + URL sync.
|
|
48
|
+
function setProject(name: string): void {
|
|
49
|
+
setProjectState(name)
|
|
50
|
+
if (typeof window === 'undefined') return
|
|
51
|
+
try {
|
|
52
|
+
window.localStorage.setItem('neat:lastProject', name)
|
|
53
|
+
} catch {
|
|
54
|
+
/* ignore quota errors */
|
|
55
|
+
}
|
|
56
|
+
const url = new URL(window.location.href)
|
|
57
|
+
url.searchParams.set('project', name)
|
|
58
|
+
window.history.replaceState({}, '', url)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ADR-057 #2.3, #2.4 — if neither URL nor localStorage gave us a project,
|
|
62
|
+
// fetch /projects and use the first entry; fall back to 'default' if empty.
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
if (resolvedRef.current) return
|
|
65
|
+
resolvedRef.current = true
|
|
66
|
+
fetch('/api/projects')
|
|
67
|
+
.then((r) => (r.ok ? r.json() : []))
|
|
68
|
+
.then((data: ProjectEntry[] | { projects?: ProjectEntry[] }) => {
|
|
69
|
+
const list = Array.isArray(data) ? data : Array.isArray(data?.projects) ? data.projects : []
|
|
70
|
+
if (list.length > 0 && list[0]?.name) {
|
|
71
|
+
setProject(list[0].name)
|
|
72
|
+
} else {
|
|
73
|
+
setProject('default')
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
.catch(() => {
|
|
77
|
+
/* registry unreachable — keep 'default' fallback */
|
|
78
|
+
})
|
|
79
|
+
}, [])
|
|
80
|
+
|
|
81
|
+
// Pre-select a node from the URL ?node= query param (e.g. from incidents back-link)
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const params = new URLSearchParams(window.location.search)
|
|
84
|
+
const nodeId = params.get('node')
|
|
85
|
+
if (nodeId) setSelectedNodeId(nodeId)
|
|
86
|
+
}, [])
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<div className="app">
|
|
90
|
+
<TopBar
|
|
91
|
+
project={project}
|
|
92
|
+
onProjectChange={setProject}
|
|
93
|
+
onNodeSelect={setSelectedNodeId}
|
|
94
|
+
onRelayout={() => cyRef.current?.layout({ name: 'cose', animate: true, randomize: false, idealEdgeLength: 90, nodeRepulsion: 9000, edgeElasticity: 80, gravity: 0.4, numIter: 1200 }).run()}
|
|
95
|
+
onToggleLock={() => { if (cyRef.current) cyRef.current.autoungrabify(!cyRef.current.autoungrabify()) }}
|
|
96
|
+
/>
|
|
97
|
+
<Rail project={project} />
|
|
98
|
+
<GraphCanvas
|
|
99
|
+
project={project}
|
|
100
|
+
selectedNodeId={selectedNodeId}
|
|
101
|
+
onNodeSelect={setSelectedNodeId}
|
|
102
|
+
onGraphLoaded={setGraphData}
|
|
103
|
+
onCyReady={(cy) => { cyRef.current = cy }}
|
|
104
|
+
/>
|
|
105
|
+
<Inspector project={project} selectedNodeId={selectedNodeId} graphData={graphData} />
|
|
106
|
+
<StatusBar project={project} graphData={graphData} />
|
|
107
|
+
</div>
|
|
108
|
+
)
|
|
109
|
+
}
|