toolnet-memory 0.2.1 → 0.2.3

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.
Files changed (342) hide show
  1. package/README.md +55 -28
  2. package/bin/toolnet-memory +55 -55
  3. package/bundle/agy.js +88 -0
  4. package/bundle/codex.js +92 -0
  5. package/bundle/context-runtime.js +56 -0
  6. package/bundle/context.js +62 -0
  7. package/bundle/doctor.js +3 -0
  8. package/bundle/full-index.js +8 -0
  9. package/bundle/learner.js +8 -0
  10. package/bundle/mcp.js +7 -0
  11. package/bundle/opencode.js +314 -0
  12. package/bundle/project-manual.js +54 -0
  13. package/bundle/setup.js +31 -0
  14. package/bundle/snapshot.js +4 -0
  15. package/bundle/work-continuity.js +12 -0
  16. package/package.json +9 -6
  17. package/dist/packages/cli/commands.js +0 -1
  18. package/dist/packages/cli/index.js +0 -1
  19. package/dist/packages/sdk/client.js +0 -78
  20. package/dist/packages/sdk/index.js +0 -1
  21. package/dist/packages/sdk/types.js +0 -1
  22. package/dist/src/api/index.js +0 -1
  23. package/dist/src/api/routes/health.js +0 -1
  24. package/dist/src/api/routes/index.js +0 -1
  25. package/dist/src/api/routes/memory.js +0 -1
  26. package/dist/src/api/routes/projects.js +0 -1
  27. package/dist/src/api/routes/search.js +0 -1
  28. package/dist/src/api/server.js +0 -1
  29. package/dist/src/capture/activity-capture.js +0 -21
  30. package/dist/src/capture/command-capture.js +0 -12
  31. package/dist/src/capture/error-capture.js +0 -14
  32. package/dist/src/capture/event-deduplicator.js +0 -28
  33. package/dist/src/capture/event-queue.js +0 -20
  34. package/dist/src/capture/file-capture.js +0 -23
  35. package/dist/src/capture/index.js +0 -8
  36. package/dist/src/capture/session-capture.js +0 -18
  37. package/dist/src/capture/tool-capture.js +0 -13
  38. package/dist/src/code-intelligence/analysis/analysis-engine.js +0 -26
  39. package/dist/src/code-intelligence/analysis/dead-code-analyzer.js +0 -127
  40. package/dist/src/code-intelligence/analysis/dependency-analyzer.js +0 -66
  41. package/dist/src/code-intelligence/analysis/index.js +0 -4
  42. package/dist/src/code-intelligence/analysis/types.js +0 -1
  43. package/dist/src/code-intelligence/architecture/architecture-engine.js +0 -48
  44. package/dist/src/code-intelligence/architecture/clusters.js +0 -322
  45. package/dist/src/code-intelligence/architecture/entry-points.js +0 -150
  46. package/dist/src/code-intelligence/architecture/hotspots.js +0 -136
  47. package/dist/src/code-intelligence/architecture/index.js +0 -6
  48. package/dist/src/code-intelligence/architecture/layers.js +0 -197
  49. package/dist/src/code-intelligence/architecture/types.js +0 -1
  50. package/dist/src/code-intelligence/chunks/index.js +0 -2
  51. package/dist/src/code-intelligence/chunks/smart-chunker.js +0 -125
  52. package/dist/src/code-intelligence/chunks/types.js +0 -1
  53. package/dist/src/code-intelligence/git/git-diff.js +0 -109
  54. package/dist/src/code-intelligence/git/index.js +0 -1
  55. package/dist/src/code-intelligence/graph/architecture.js +0 -26
  56. package/dist/src/code-intelligence/graph/graph-builder.js +0 -213
  57. package/dist/src/code-intelligence/graph/graph-store.js +0 -57
  58. package/dist/src/code-intelligence/graph/index.js +0 -1
  59. package/dist/src/code-intelligence/graph/relationships.js +0 -1
  60. package/dist/src/code-intelligence/graph/trace.js +0 -65
  61. package/dist/src/code-intelligence/impact/blast-radius.js +0 -160
  62. package/dist/src/code-intelligence/impact/change-mapper.js +0 -69
  63. package/dist/src/code-intelligence/impact/edit-tool-detector.js +0 -92
  64. package/dist/src/code-intelligence/impact/impact-analyzer.js +0 -60
  65. package/dist/src/code-intelligence/impact/impact-guard.js +0 -33
  66. package/dist/src/code-intelligence/impact/index.js +0 -5
  67. package/dist/src/code-intelligence/incremental/file-hash.js +0 -8
  68. package/dist/src/code-intelligence/incremental/incremental-indexer.js +0 -106
  69. package/dist/src/code-intelligence/incremental/manifest-builder.js +0 -19
  70. package/dist/src/code-intelligence/incremental/manifest.js +0 -31
  71. package/dist/src/code-intelligence/index.js +0 -23
  72. package/dist/src/code-intelligence/indexer/file-watcher.js +0 -1
  73. package/dist/src/code-intelligence/indexer/incremental-indexer.js +0 -1
  74. package/dist/src/code-intelligence/indexer/index.js +0 -1
  75. package/dist/src/code-intelligence/indexer/repository-indexer.js +0 -31
  76. package/dist/src/code-intelligence/indexer/repository-scanner.js +0 -50
  77. package/dist/src/code-intelligence/parsers/index.js +0 -1
  78. package/dist/src/code-intelligence/parsers/tree-sitter/index.js +0 -1
  79. package/dist/src/code-intelligence/parsers/tree-sitter/languages.js +0 -1
  80. package/dist/src/code-intelligence/parsers/tree-sitter/parser.js +0 -1
  81. package/dist/src/code-intelligence/parsers/typescript-parser.js +0 -190
  82. package/dist/src/code-intelligence/query/graph-query-engine.js +0 -275
  83. package/dist/src/code-intelligence/query/index.js +0 -2
  84. package/dist/src/code-intelligence/query/types.js +0 -1
  85. package/dist/src/code-intelligence/resolution/index.js +0 -2
  86. package/dist/src/code-intelligence/resolution/types.js +0 -1
  87. package/dist/src/code-intelligence/resolution/typescript-resolver.js +0 -316
  88. package/dist/src/code-intelligence/rich/index.js +0 -1
  89. package/dist/src/code-intelligence/rich/rich-graph-enricher.js +0 -463
  90. package/dist/src/code-intelligence/semantic/index.js +0 -2
  91. package/dist/src/code-intelligence/semantic/semantic-code-engine.js +0 -213
  92. package/dist/src/code-intelligence/semantic/source-priority.js +0 -22
  93. package/dist/src/code-intelligence/symbols/index.js +0 -1
  94. package/dist/src/code-intelligence/symbols/reference-resolver.js +0 -21
  95. package/dist/src/code-intelligence/symbols/symbol-extractor.js +0 -1
  96. package/dist/src/code-intelligence/test-analysis.js +0 -72
  97. package/dist/src/code-intelligence/test-architecture.js +0 -67
  98. package/dist/src/code-intelligence/test-impact.js +0 -47
  99. package/dist/src/code-intelligence/test-incremental.js +0 -24
  100. package/dist/src/code-intelligence/test-index.js +0 -39
  101. package/dist/src/code-intelligence/test-resolution.js +0 -54
  102. package/dist/src/code-intelligence/test-rich-graph.js +0 -48
  103. package/dist/src/code-intelligence/test-semantic.js +0 -57
  104. package/dist/src/code-intelligence/test-visualization.js +0 -36
  105. package/dist/src/code-intelligence/types.js +0 -1
  106. package/dist/src/code-intelligence/visualization/index.js +0 -2
  107. package/dist/src/code-intelligence/visualization/types.js +0 -1
  108. package/dist/src/code-intelligence/visualization/visualization-builder.js +0 -104
  109. package/dist/src/core/config.js +0 -66
  110. package/dist/src/core/global-env.js +0 -70
  111. package/dist/src/core/importance.js +0 -30
  112. package/dist/src/core/index.js +0 -5
  113. package/dist/src/core/memory-engine.js +0 -183
  114. package/dist/src/core/project-manager.js +0 -243
  115. package/dist/src/core/types.js +0 -1
  116. package/dist/src/embeddings/batch-embedder.js +0 -1
  117. package/dist/src/embeddings/cache.js +0 -1
  118. package/dist/src/embeddings/embedder.js +0 -51
  119. package/dist/src/embeddings/index.js +0 -4
  120. package/dist/src/embeddings/local.js +0 -37
  121. package/dist/src/embeddings/provider.js +0 -1
  122. package/dist/src/embeddings/remote.js +0 -76
  123. package/dist/src/embeddings/test-connection.js +0 -16
  124. package/dist/src/hooks/after-edit.js +0 -3
  125. package/dist/src/hooks/after-tool.js +0 -3
  126. package/dist/src/hooks/auto-context.js +0 -68
  127. package/dist/src/hooks/auto-impact-guard.js +0 -94
  128. package/dist/src/hooks/auto-retrieval.js +0 -27
  129. package/dist/src/hooks/before-agent.js +0 -18
  130. package/dist/src/hooks/before-compact.js +0 -3
  131. package/dist/src/hooks/before-edit.js +0 -20
  132. package/dist/src/hooks/index.js +0 -11
  133. package/dist/src/hooks/runtime.js +0 -110
  134. package/dist/src/hooks/session-end.js +0 -3
  135. package/dist/src/hooks/session-start.js +0 -3
  136. package/dist/src/index.js +0 -22
  137. package/dist/src/mcp/bootstrap.js +0 -68
  138. package/dist/src/mcp/context.js +0 -1
  139. package/dist/src/mcp/index.js +0 -3
  140. package/dist/src/mcp/server.js +0 -45
  141. package/dist/src/mcp/tools/analyze-impact.js +0 -41
  142. package/dist/src/mcp/tools/dead-code.js +0 -55
  143. package/dist/src/mcp/tools/find-callers.js +0 -24
  144. package/dist/src/mcp/tools/find-dependencies.js +0 -57
  145. package/dist/src/mcp/tools/find-symbol.js +0 -7
  146. package/dist/src/mcp/tools/get-architecture.js +0 -11
  147. package/dist/src/mcp/tools/graph-dependents.js +0 -24
  148. package/dist/src/mcp/tools/graph-neighborhood.js +0 -58
  149. package/dist/src/mcp/tools/graph-path.js +0 -43
  150. package/dist/src/mcp/tools/graph-query-utils.js +0 -32
  151. package/dist/src/mcp/tools/impact-guard.js +0 -34
  152. package/dist/src/mcp/tools/index-repository.js +0 -1
  153. package/dist/src/mcp/tools/index.js +0 -20
  154. package/dist/src/mcp/tools/memory-decisions.js +0 -1
  155. package/dist/src/mcp/tools/memory-forget.js +0 -14
  156. package/dist/src/mcp/tools/memory-recent.js +0 -1
  157. package/dist/src/mcp/tools/memory-remember.js +0 -33
  158. package/dist/src/mcp/tools/memory-rules.js +0 -1
  159. package/dist/src/mcp/tools/memory-search.js +0 -18
  160. package/dist/src/mcp/tools/memory-todos.js +0 -1
  161. package/dist/src/mcp/tools/project-context.js +0 -25
  162. package/dist/src/mcp/tools/search-code.js +0 -21
  163. package/dist/src/mcp/tools/semantic-code-search.js +0 -33
  164. package/dist/src/mcp/tools/snapshot-create.js +0 -13
  165. package/dist/src/mcp/tools/snapshot-list.js +0 -7
  166. package/dist/src/mcp/tools/snapshot-restore.js +0 -13
  167. package/dist/src/mcp/tools/trace-calls.js +0 -37
  168. package/dist/src/memory/activity.js +0 -5
  169. package/dist/src/memory/conflict-detector.js +0 -105
  170. package/dist/src/memory/consolidator.js +0 -104
  171. package/dist/src/memory/decay.js +0 -59
  172. package/dist/src/memory/decisions.js +0 -3
  173. package/dist/src/memory/deduplicate.js +0 -25
  174. package/dist/src/memory/index.js +0 -11
  175. package/dist/src/memory/maintenance.js +0 -16
  176. package/dist/src/memory/remember.js +0 -3
  177. package/dist/src/memory/rules.js +0 -3
  178. package/dist/src/memory/search.js +0 -3
  179. package/dist/src/memory/summaries.js +0 -1
  180. package/dist/src/memory/todos.js +0 -3
  181. package/dist/src/processor/decision-extractor.js +0 -13
  182. package/dist/src/processor/importance-scorer.js +0 -22
  183. package/dist/src/processor/index.js +0 -7
  184. package/dist/src/processor/memory-extractor.js +0 -59
  185. package/dist/src/processor/memory-processor.js +0 -105
  186. package/dist/src/processor/rule-extractor.js +0 -13
  187. package/dist/src/processor/summarizer.js +0 -71
  188. package/dist/src/processor/todo-extractor.js +0 -12
  189. package/dist/src/production/config-check.js +0 -34
  190. package/dist/src/production/doctor.js +0 -58
  191. package/dist/src/production/full-index.js +0 -114
  192. package/dist/src/production/health.js +0 -58
  193. package/dist/src/production/index-pipeline.js +0 -151
  194. package/dist/src/production/index.js +0 -4
  195. package/dist/src/production/project-lock.js +0 -95
  196. package/dist/src/production/project-status.js +0 -16
  197. package/dist/src/production/recovery.js +0 -15
  198. package/dist/src/production/setup.js +0 -219
  199. package/dist/src/production/snapshot-cli.js +0 -58
  200. package/dist/src/project-manual/cli.js +0 -77
  201. package/dist/src/project-manual/index.js +0 -2
  202. package/dist/src/project-manual/manager.js +0 -147
  203. package/dist/src/project-manual/types.js +0 -1
  204. package/dist/src/retrieval/bm25/index.js +0 -1
  205. package/dist/src/retrieval/bm25/search.js +0 -1
  206. package/dist/src/retrieval/bm25.js +0 -30
  207. package/dist/src/retrieval/context-budget.js +0 -1
  208. package/dist/src/retrieval/context-builder.js +0 -22
  209. package/dist/src/retrieval/graph/index.js +0 -1
  210. package/dist/src/retrieval/graph/search.js +0 -1
  211. package/dist/src/retrieval/hybrid/hybrid-search.js +0 -1
  212. package/dist/src/retrieval/hybrid/index.js +0 -1
  213. package/dist/src/retrieval/hybrid-search.js +0 -68
  214. package/dist/src/retrieval/importance.js +0 -16
  215. package/dist/src/retrieval/index.js +0 -12
  216. package/dist/src/retrieval/query-analyzer.js +0 -32
  217. package/dist/src/retrieval/query-classifier.js +0 -1
  218. package/dist/src/retrieval/query-expansion.js +0 -1
  219. package/dist/src/retrieval/recency.js +0 -12
  220. package/dist/src/retrieval/reranker/index.js +0 -1
  221. package/dist/src/retrieval/reranker/reranker.js +0 -1
  222. package/dist/src/retrieval/retrieval-engine.js +0 -17
  223. package/dist/src/retrieval/tokenizer.js +0 -9
  224. package/dist/src/retrieval/type-priority.js +0 -10
  225. package/dist/src/retrieval/types.js +0 -1
  226. package/dist/src/retrieval/vector/index.js +0 -4
  227. package/dist/src/retrieval/vector/metadata-filter.js +0 -1
  228. package/dist/src/retrieval/vector/persistence.js +0 -67
  229. package/dist/src/retrieval/vector/search.js +0 -12
  230. package/dist/src/retrieval/vector/test-persistence.js +0 -36
  231. package/dist/src/retrieval/vector/upsert.js +0 -40
  232. package/dist/src/retrieval/vector/vector-store.js +0 -89
  233. package/dist/src/retrieval/vector-hybrid-engine.js +0 -91
  234. package/dist/src/runtime/bootstrap.js +0 -28
  235. package/dist/src/runtime/graceful.js +0 -17
  236. package/dist/src/runtime/index.js +0 -3
  237. package/dist/src/runtime/toolnet-memory-runtime.js +0 -156
  238. package/dist/src/security/encryption.js +0 -1
  239. package/dist/src/security/file-filter.js +0 -30
  240. package/dist/src/security/index.js +0 -3
  241. package/dist/src/security/sanitizer.js +0 -49
  242. package/dist/src/security/secret-scanner.js +0 -56
  243. package/dist/src/session/agy/adapter.js +0 -159
  244. package/dist/src/session/agy/cli.js +0 -87
  245. package/dist/src/session/agy/hook-installer.js +0 -62
  246. package/dist/src/session/agy/hook.js +0 -167
  247. package/dist/src/session/agy/index.js +0 -5
  248. package/dist/src/session/agy/project-resolver.js +0 -27
  249. package/dist/src/session/agy/recovery.js +0 -112
  250. package/dist/src/session/agy/transcript.js +0 -191
  251. package/dist/src/session/codex/adapter.js +0 -138
  252. package/dist/src/session/codex/cli.js +0 -236
  253. package/dist/src/session/codex/context-hook-installer.js +0 -70
  254. package/dist/src/session/codex/context-hook.js +0 -69
  255. package/dist/src/session/codex/discovery.js +0 -138
  256. package/dist/src/session/codex/index.js +0 -6
  257. package/dist/src/session/codex/notify-installer.js +0 -135
  258. package/dist/src/session/codex/project-resolver.js +0 -25
  259. package/dist/src/session/codex/recovery.js +0 -27
  260. package/dist/src/session/codex/rollout.js +0 -186
  261. package/dist/src/session/core.js +0 -239
  262. package/dist/src/session/identity.js +0 -57
  263. package/dist/src/session/index.js +0 -9
  264. package/dist/src/session/learner/cli.js +0 -62
  265. package/dist/src/session/learner/extractor.js +0 -461
  266. package/dist/src/session/learner/index.js +0 -4
  267. package/dist/src/session/learner/journal.js +0 -159
  268. package/dist/src/session/learner/learner.js +0 -124
  269. package/dist/src/session/learner/types.js +0 -1
  270. package/dist/src/session/opencode/adapter.js +0 -553
  271. package/dist/src/session/opencode/cli.js +0 -121
  272. package/dist/src/session/opencode/index.js +0 -2
  273. package/dist/src/session/opencode/plugin-installer.js +0 -230
  274. package/dist/src/session/store.js +0 -242
  275. package/dist/src/session/types.js +0 -1
  276. package/dist/src/session/utils.js +0 -47
  277. package/dist/src/session/wal.js +0 -303
  278. package/dist/src/snapshot/index.js +0 -2
  279. package/dist/src/snapshot/manager.js +0 -130
  280. package/dist/src/snapshot/test-snapshot.js +0 -28
  281. package/dist/src/snapshot/types.js +0 -1
  282. package/dist/src/storage/architecture-store.js +0 -37
  283. package/dist/src/storage/cleanup-health.js +0 -20
  284. package/dist/src/storage/code-analysis-store.js +0 -35
  285. package/dist/src/storage/code-chunk-store.js +0 -25
  286. package/dist/src/storage/code-graph-store.js +0 -24
  287. package/dist/src/storage/code-manifest-store.js +0 -24
  288. package/dist/src/storage/code-vector-store.js +0 -25
  289. package/dist/src/storage/github/client.js +0 -1
  290. package/dist/src/storage/github/index.js +0 -1
  291. package/dist/src/storage/google-drive/client.js +0 -1
  292. package/dist/src/storage/google-drive/index.js +0 -1
  293. package/dist/src/storage/huggingface/client.js +0 -116
  294. package/dist/src/storage/huggingface/index.js +0 -1
  295. package/dist/src/storage/index.js +0 -16
  296. package/dist/src/storage/list-bucket-raw.js +0 -14
  297. package/dist/src/storage/list-bucket-tree.js +0 -38
  298. package/dist/src/storage/local/client.js +0 -99
  299. package/dist/src/storage/local/index.js +0 -1
  300. package/dist/src/storage/memory-store.js +0 -30
  301. package/dist/src/storage/project/cleanup-layout-v1.js +0 -72
  302. package/dist/src/storage/project/folder.js +0 -15
  303. package/dist/src/storage/project/index.js +0 -3
  304. package/dist/src/storage/project/layout.js +0 -74
  305. package/dist/src/storage/project/migrate-layout-v2.js +0 -101
  306. package/dist/src/storage/project/migrate.js +0 -73
  307. package/dist/src/storage/project/scoped-provider.js +0 -165
  308. package/dist/src/storage/provider.js +0 -26
  309. package/dist/src/storage/retrying-provider.js +0 -23
  310. package/dist/src/storage/test-connection.js +0 -32
  311. package/dist/src/storage/type-resolution-store.js +0 -26
  312. package/dist/src/storage/types.js +0 -1
  313. package/dist/src/storage/vector-store.js +0 -30
  314. package/dist/src/storage/visualization-store.js +0 -25
  315. package/dist/src/sync/compressor.js +0 -1
  316. package/dist/src/sync/conflict-resolver.js +0 -1
  317. package/dist/src/sync/downloader.js +0 -1
  318. package/dist/src/sync/index.js +0 -1
  319. package/dist/src/sync/sync-engine.js +0 -1
  320. package/dist/src/sync/upload-queue.js +0 -1
  321. package/dist/src/utils/retry.js +0 -29
  322. package/dist/src/visualization/server.js +0 -270
  323. package/dist/src/work-continuity/brief-cache.js +0 -107
  324. package/dist/src/work-continuity/brief.js +0 -349
  325. package/dist/src/work-continuity/cli.js +0 -96
  326. package/dist/src/work-continuity/context-cli.js +0 -106
  327. package/dist/src/work-continuity/context-runtime-cli.js +0 -88
  328. package/dist/src/work-continuity/extractor.js +0 -339
  329. package/dist/src/work-continuity/handoff.js +0 -185
  330. package/dist/src/work-continuity/index.js +0 -14
  331. package/dist/src/work-continuity/injection.js +0 -84
  332. package/dist/src/work-continuity/journal.js +0 -49
  333. package/dist/src/work-continuity/learner.js +0 -127
  334. package/dist/src/work-continuity/reducer.js +0 -316
  335. package/dist/src/work-continuity/semantic-extractor.js +0 -360
  336. package/dist/src/work-continuity/semantic-journal.js +0 -55
  337. package/dist/src/work-continuity/semantic-learner.js +0 -125
  338. package/dist/src/work-continuity/semantic-reducer.js +0 -268
  339. package/dist/src/work-continuity/semantic-types.js +0 -1
  340. package/dist/src/work-continuity/types.js +0 -1
  341. package/dist/visualization/public/index.html +0 -2896
  342. package/dist/visualization/public/vendor/3d-force-graph.min.js +0 -5
@@ -1,2896 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
-
6
- <meta
7
- name="viewport"
8
- content="width=device-width, initial-scale=1, viewport-fit=cover"
9
- />
10
-
11
- <title>
12
- ToolNet Memory · Graph
13
- </title>
14
-
15
- <style>
16
- * {
17
- box-sizing: border-box;
18
- }
19
-
20
- html,
21
- body {
22
- width: 100%;
23
- height: 100%;
24
- margin: 0;
25
- overflow: hidden;
26
-
27
- background:
28
- #050608;
29
-
30
- color:
31
- #f6f7f9;
32
-
33
- font-family:
34
- Inter,
35
- ui-sans-serif,
36
- system-ui,
37
- -apple-system,
38
- BlinkMacSystemFont,
39
- "Segoe UI",
40
- sans-serif;
41
- }
42
-
43
- #graph {
44
- position: fixed;
45
- inset: 0;
46
- }
47
-
48
- .panel {
49
- position: fixed;
50
- z-index: 20;
51
-
52
- border:
53
- 1px solid
54
- rgba(255,255,255,.12);
55
-
56
- background:
57
- rgba(10,12,17,.88);
58
-
59
- backdrop-filter:
60
- blur(20px);
61
-
62
- -webkit-backdrop-filter:
63
- blur(20px);
64
-
65
- box-shadow:
66
- 0 22px 60px
67
- rgba(0,0,0,.42);
68
-
69
- border-radius:
70
- 18px;
71
- }
72
-
73
- #toolbar {
74
- top:
75
- max(
76
- 14px,
77
- env(safe-area-inset-top)
78
- );
79
-
80
- left:
81
- 14px;
82
-
83
- right:
84
- 14px;
85
-
86
- padding:
87
- 14px;
88
-
89
- display:
90
- grid;
91
-
92
- grid-template-columns:
93
- auto minmax(170px, 1fr)
94
- minmax(170px, 1fr)
95
- minmax(150px, .7fr)
96
- minmax(150px, .7fr)
97
- auto;
98
-
99
- gap:
100
- 10px;
101
-
102
- align-items:
103
- center;
104
- }
105
-
106
- .brand {
107
- padding:
108
- 0 8px;
109
-
110
- white-space:
111
- nowrap;
112
-
113
- font-weight:
114
- 750;
115
-
116
- font-size:
117
- 18px;
118
- }
119
-
120
- .brand span {
121
- opacity:
122
- .45;
123
-
124
- font-weight:
125
- 500;
126
- }
127
-
128
- input,
129
- select,
130
- button {
131
- height:
132
- 42px;
133
-
134
- min-width:
135
- 0;
136
-
137
- border:
138
- 1px solid
139
- rgba(255,255,255,.12);
140
-
141
- border-radius:
142
- 11px;
143
-
144
- background:
145
- rgba(255,255,255,.06);
146
-
147
- color:
148
- white;
149
-
150
- padding:
151
- 0 12px;
152
-
153
- outline:
154
- none;
155
-
156
- font:
157
- inherit;
158
- }
159
-
160
- button {
161
- cursor:
162
- pointer;
163
- }
164
-
165
- #project {
166
- font-weight:
167
- 650;
168
- }
169
-
170
- #stats {
171
- left:
172
- 14px;
173
-
174
- bottom:
175
- max(
176
- 14px,
177
- env(safe-area-inset-bottom)
178
- );
179
-
180
- min-width:
181
- 220px;
182
-
183
- padding:
184
- 14px 16px;
185
-
186
- font-size:
187
- 13px;
188
-
189
- line-height:
190
- 1.7;
191
- }
192
-
193
- .status {
194
- margin-top:
195
- 5px;
196
-
197
- opacity:
198
- .55;
199
-
200
- font-size:
201
- 11px;
202
- }
203
-
204
- #details {
205
- top:
206
- 96px;
207
-
208
- right:
209
- 14px;
210
-
211
- bottom:
212
- 14px;
213
-
214
- width:
215
- min(
216
- 410px,
217
- calc(100vw - 28px)
218
- );
219
-
220
- padding:
221
- 68px 20px 20px;
222
-
223
- overflow-y:
224
- auto;
225
-
226
- transform:
227
- translateX(
228
- calc(100% + 30px)
229
- );
230
-
231
- transition:
232
- transform .2s ease;
233
- }
234
-
235
- #details.open {
236
- transform:
237
- translateX(0);
238
- }
239
-
240
- .details-close {
241
- position:
242
- absolute;
243
-
244
- top:
245
- 14px;
246
-
247
- right:
248
- 14px;
249
-
250
- width:
251
- 44px;
252
-
253
- height:
254
- 44px;
255
-
256
- display:
257
- grid;
258
-
259
- place-items:
260
- center;
261
-
262
- padding:
263
- 0;
264
-
265
- border-radius:
266
- 50%;
267
-
268
- font-size:
269
- 28px;
270
-
271
- line-height:
272
- 1;
273
- }
274
-
275
- .detail-title {
276
- font-size:
277
- 21px;
278
-
279
- font-weight:
280
- 750;
281
-
282
- line-height:
283
- 1.25;
284
-
285
- overflow-wrap:
286
- anywhere;
287
- }
288
-
289
- .path {
290
- margin-top:
291
- 8px;
292
-
293
- opacity:
294
- .55;
295
-
296
- font-family:
297
- ui-monospace,
298
- SFMono-Regular,
299
- Menlo,
300
- monospace;
301
-
302
- font-size:
303
- 12px;
304
-
305
- overflow-wrap:
306
- anywhere;
307
- }
308
-
309
- .tag {
310
- display:
311
- inline-flex;
312
-
313
- margin:
314
- 10px 5px 0 0;
315
-
316
- padding:
317
- 5px 9px;
318
-
319
- border:
320
- 1px solid
321
- rgba(255,255,255,.12);
322
-
323
- border-radius:
324
- 999px;
325
-
326
- background:
327
- rgba(255,255,255,.06);
328
-
329
- font-size:
330
- 11px;
331
- }
332
-
333
- .section {
334
- margin-top:
335
- 24px;
336
- }
337
-
338
- .section-title {
339
- margin-bottom:
340
- 8px;
341
-
342
- opacity:
343
- .5;
344
-
345
- font-size:
346
- 11px;
347
-
348
- letter-spacing:
349
- .1em;
350
-
351
- text-transform:
352
- uppercase;
353
- }
354
-
355
- .row {
356
- display:
357
- flex;
358
-
359
- justify-content:
360
- space-between;
361
-
362
- gap:
363
- 12px;
364
-
365
- padding:
366
- 9px 0;
367
-
368
- border-bottom:
369
- 1px solid
370
- rgba(255,255,255,.06);
371
-
372
- font-size:
373
- 13px;
374
- }
375
-
376
- #loading {
377
- position:
378
- fixed;
379
-
380
- inset:
381
- 0;
382
-
383
- z-index:
384
- 100;
385
-
386
- display:
387
- grid;
388
-
389
- place-items:
390
- center;
391
-
392
- background:
393
- #050608;
394
-
395
- padding:
396
- 24px;
397
-
398
- text-align:
399
- center;
400
- }
401
-
402
- @media (
403
- max-width: 1000px
404
- ) {
405
- #toolbar {
406
- grid-template-columns:
407
- 1fr 1fr;
408
-
409
- top:
410
- max(
411
- 10px,
412
- env(safe-area-inset-top)
413
- );
414
-
415
- left:
416
- 10px;
417
-
418
- right:
419
- 10px;
420
- }
421
-
422
- .brand {
423
- grid-column:
424
- 1 / -1;
425
-
426
- font-size:
427
- 17px;
428
- }
429
-
430
- #search {
431
- grid-column:
432
- 1 / -1;
433
- }
434
-
435
- #details {
436
- top:
437
- 10px;
438
-
439
- right:
440
- 10px;
441
-
442
- bottom:
443
- 10px;
444
- }
445
-
446
- #stats {
447
- left:
448
- 10px;
449
-
450
- bottom:
451
- max(
452
- 10px,
453
- env(safe-area-inset-bottom)
454
- );
455
- }
456
- }
457
-
458
- @media (
459
- max-width: 1000px
460
- ) {
461
- #toolbar {
462
- grid-template-columns:
463
- repeat(12, minmax(0, 1fr));
464
- }
465
-
466
- .brand {
467
- grid-column:
468
- 1 / -1;
469
- }
470
-
471
- #project {
472
- grid-column:
473
- 1 / 10;
474
-
475
- width:
476
- 100%;
477
- }
478
-
479
- #reset {
480
- grid-column:
481
- 10 / 13;
482
-
483
- width:
484
- 100%;
485
-
486
- padding-left:
487
- 8px;
488
-
489
- padding-right:
490
- 8px;
491
- }
492
-
493
- #search {
494
- grid-column:
495
- 1 / -1;
496
-
497
- width:
498
- 100%;
499
- }
500
-
501
- #cluster {
502
- grid-column:
503
- 1 / 7;
504
-
505
- width:
506
- 100%;
507
- }
508
-
509
- #edge {
510
- grid-column:
511
- 7 / 13;
512
-
513
- width:
514
- 100%;
515
- }
516
- }
517
-
518
-
519
- .intel-summary {
520
- display:
521
- grid;
522
-
523
- grid-template-columns:
524
- 1fr 1fr;
525
-
526
- gap:
527
- 8px;
528
-
529
- margin-top:
530
- 18px;
531
- }
532
-
533
- .intel-card {
534
- padding:
535
- 12px;
536
-
537
- border:
538
- 1px solid
539
- rgba(255,255,255,.08);
540
-
541
- border-radius:
542
- 12px;
543
-
544
- background:
545
- rgba(255,255,255,.035);
546
- }
547
-
548
- .intel-card strong {
549
- display:
550
- block;
551
-
552
- font-size:
553
- 18px;
554
-
555
- margin-top:
556
- 3px;
557
- }
558
-
559
- .intel-card span {
560
- opacity:
561
- .55;
562
-
563
- font-size:
564
- 11px;
565
- }
566
-
567
- .relation-list {
568
- display:
569
- flex;
570
-
571
- flex-direction:
572
- column;
573
-
574
- gap:
575
- 6px;
576
- }
577
-
578
- .relation-item {
579
- width:
580
- 100%;
581
-
582
- height:
583
- auto;
584
-
585
- min-height:
586
- 44px;
587
-
588
- padding:
589
- 9px 11px;
590
-
591
- display:
592
- block;
593
-
594
- text-align:
595
- left;
596
-
597
- border-radius:
598
- 10px;
599
-
600
- cursor:
601
- pointer;
602
- }
603
-
604
- .relation-name {
605
- display:
606
- block;
607
-
608
- font-size:
609
- 12px;
610
-
611
- font-weight:
612
- 650;
613
-
614
- overflow-wrap:
615
- anywhere;
616
- }
617
-
618
- .relation-meta {
619
- display:
620
- block;
621
-
622
- margin-top:
623
- 4px;
624
-
625
- opacity:
626
- .45;
627
-
628
- font-size:
629
- 10px;
630
-
631
- font-family:
632
- ui-monospace,
633
- SFMono-Regular,
634
- Menlo,
635
- monospace;
636
-
637
- overflow-wrap:
638
- anywhere;
639
- }
640
-
641
- .relation-more {
642
- margin-top:
643
- 7px;
644
-
645
- opacity:
646
- .45;
647
-
648
- font-size:
649
- 11px;
650
- }
651
-
652
- .risk {
653
- display:
654
- inline-flex;
655
-
656
- padding:
657
- 5px 9px;
658
-
659
- margin-top:
660
- 8px;
661
-
662
- border:
663
- 1px solid
664
- rgba(255,255,255,.12);
665
-
666
- border-radius:
667
- 999px;
668
-
669
- font-size:
670
- 11px;
671
-
672
- font-weight:
673
- 700;
674
- }
675
-
676
- .path-actions {
677
- display:
678
- flex;
679
-
680
- gap:
681
- 8px;
682
-
683
- margin-top:
684
- 10px;
685
- }
686
-
687
- .path-actions button {
688
- flex:
689
- 1;
690
-
691
- height:
692
- auto;
693
-
694
- min-height:
695
- 40px;
696
-
697
- white-space:
698
- normal;
699
- }
700
-
701
- .path-chain {
702
- display:
703
- flex;
704
-
705
- flex-direction:
706
- column;
707
-
708
- gap:
709
- 5px;
710
-
711
- margin-top:
712
- 9px;
713
- }
714
-
715
- .path-node {
716
- height:
717
- auto;
718
-
719
- min-height:
720
- 36px;
721
-
722
- padding:
723
- 7px 10px;
724
-
725
- text-align:
726
- left;
727
-
728
- font-size:
729
- 11px;
730
- }
731
-
732
- </style>
733
- </head>
734
-
735
- <body>
736
- <div id="loading">
737
- Loading ToolNet projects…
738
- </div>
739
-
740
- <div id="graph"></div>
741
-
742
- <div
743
- id="toolbar"
744
- class="panel"
745
- >
746
- <div class="brand">
747
- ToolNet Memory
748
- <span>/ Graph</span>
749
- </div>
750
-
751
- <select
752
- id="project"
753
- aria-label="Project"
754
- >
755
- <option>
756
- Loading…
757
- </option>
758
- </select>
759
-
760
- <button id="reset">
761
- Reset
762
- </button>
763
-
764
- <input
765
- id="search"
766
- placeholder="Search symbol or file…"
767
- />
768
-
769
- <select
770
- id="cluster"
771
- aria-label="Subsystem"
772
- >
773
- <option value="">
774
- All subsystems
775
- </option>
776
- </select>
777
-
778
- <select
779
- id="edge"
780
- aria-label="Relation"
781
- >
782
- <option value="">
783
- All relations
784
- </option>
785
- </select>
786
-
787
- </div>
788
-
789
- <div
790
- id="stats"
791
- class="panel"
792
- ></div>
793
-
794
- <div
795
- id="details"
796
- class="panel"
797
- ></div>
798
-
799
- <script src="/vendor/3d-force-graph.min.js"></script>
800
-
801
- <script>
802
- const state = {
803
- raw:
804
- null,
805
-
806
- graph:
807
- null,
808
-
809
- project:
810
- "",
811
-
812
- search:
813
- "",
814
-
815
- cluster:
816
- "",
817
-
818
- edge:
819
- "",
820
-
821
- selected:
822
- null,
823
-
824
- pathStart:
825
- null,
826
-
827
- highlightedPath:
828
- [],
829
- };
830
-
831
- const graphEl =
832
- document.getElementById(
833
- "graph"
834
- );
835
-
836
- const projectEl =
837
- document.getElementById(
838
- "project"
839
- );
840
-
841
- const searchEl =
842
- document.getElementById(
843
- "search"
844
- );
845
-
846
- const clusterEl =
847
- document.getElementById(
848
- "cluster"
849
- );
850
-
851
- const edgeEl =
852
- document.getElementById(
853
- "edge"
854
- );
855
-
856
- const statsEl =
857
- document.getElementById(
858
- "stats"
859
- );
860
-
861
- const detailsEl =
862
- document.getElementById(
863
- "details"
864
- );
865
-
866
- const colors = [
867
- "#62a8ff",
868
- "#ff8f6b",
869
- "#67d49b",
870
- "#c493ff",
871
- "#ffd166",
872
- "#73d2de",
873
- "#ff6b9a",
874
- "#9fd356",
875
- "#e5989b",
876
- "#8ecae6",
877
- "#c9cba3",
878
- "#f4a261",
879
- ];
880
-
881
- function escapeHtml(
882
- value
883
- ) {
884
- return String(
885
- value ?? ""
886
- )
887
- .replaceAll(
888
- "&",
889
- "&amp;"
890
- )
891
- .replaceAll(
892
- "<",
893
- "&lt;"
894
- )
895
- .replaceAll(
896
- ">",
897
- "&gt;"
898
- )
899
- .replaceAll(
900
- '"',
901
- "&quot;"
902
- )
903
- .replaceAll(
904
- "'",
905
- "&#039;"
906
- );
907
- }
908
-
909
- function hashCode(
910
- value
911
- ) {
912
- let hash =
913
- 0;
914
-
915
- for (
916
- let i = 0;
917
- i < value.length;
918
- i++
919
- ) {
920
- hash =
921
- (
922
- (
923
- hash << 5
924
- ) -
925
- hash
926
- ) +
927
- value.charCodeAt(
928
- i
929
- );
930
-
931
- hash |=
932
- 0;
933
- }
934
-
935
- return Math.abs(
936
- hash
937
- );
938
- }
939
-
940
- function nodeColor(
941
- node
942
- ) {
943
- const value =
944
- node.clusterLabel ||
945
- node.cluster ||
946
- node.layer ||
947
- node.type ||
948
- "other";
949
-
950
- return colors[
951
- hashCode(
952
- value
953
- ) %
954
- colors.length
955
- ];
956
- }
957
-
958
- function linkColor(
959
- link
960
- ) {
961
- const map = {
962
- CALL_REFERENCE:
963
- "rgba(255,190,90,.72)",
964
-
965
- CALLS:
966
- "rgba(255,255,255,.30)",
967
-
968
- IMPORTS:
969
- "rgba(100,170,255,.46)",
970
-
971
- USES_TYPE:
972
- "rgba(190,130,255,.44)",
973
-
974
- WRITES:
975
- "rgba(255,100,100,.60)",
976
-
977
- TESTS:
978
- "rgba(100,220,150,.42)",
979
-
980
- IMPLEMENTS:
981
- "rgba(100,220,220,.48)",
982
-
983
- INHERITS:
984
- "rgba(235,140,255,.50)",
985
- };
986
-
987
- return (
988
- map[link.type] ||
989
- "rgba(255,255,255,.18)"
990
- );
991
- }
992
-
993
- function closeDetails() {
994
- state.selected =
995
- null;
996
-
997
- detailsEl
998
- .classList
999
- .remove(
1000
- "open"
1001
- );
1002
-
1003
- detailsEl.innerHTML =
1004
- "";
1005
- }
1006
-
1007
- const CALL_RELATIONS =
1008
- new Set([
1009
- "CALLS",
1010
- "CALL_REFERENCE",
1011
- ]);
1012
-
1013
- const DEPENDENCY_RELATIONS =
1014
- new Set([
1015
- "CALLS",
1016
- "CALL_REFERENCE",
1017
- "IMPORTS",
1018
- "USES_TYPE",
1019
- "WRITES",
1020
- "INHERITS",
1021
- "IMPLEMENTS",
1022
- "ROUTE",
1023
- ]);
1024
-
1025
- const IMPACT_WEIGHTS = {
1026
- CALLS: 4,
1027
- CALL_REFERENCE: 6,
1028
- USES_TYPE: 4,
1029
- TESTS: 2,
1030
- ROUTE: 4,
1031
- WRITES: 3,
1032
- IMPORTS: 3,
1033
- INHERITS: 5,
1034
- IMPLEMENTS: 5,
1035
- DEFINES: 1,
1036
- };
1037
-
1038
- function endpointId(
1039
- link,
1040
- side
1041
- ) {
1042
- const value =
1043
- link[side];
1044
-
1045
- return typeof value ===
1046
- "object"
1047
- ? value.id
1048
- : value;
1049
- }
1050
-
1051
- function rawNode(
1052
- id
1053
- ) {
1054
- return state.raw
1055
- ?.nodes
1056
- ?.find(
1057
- node =>
1058
- node.id === id
1059
- );
1060
- }
1061
-
1062
- function uniqueNodes(
1063
- values
1064
- ) {
1065
- const map =
1066
- new Map();
1067
-
1068
- for (
1069
- const node
1070
- of values
1071
- ) {
1072
- if (
1073
- node &&
1074
- !map.has(
1075
- node.id
1076
- )
1077
- ) {
1078
- map.set(
1079
- node.id,
1080
- node
1081
- );
1082
- }
1083
- }
1084
-
1085
- return [
1086
- ...map.values()
1087
- ];
1088
- }
1089
-
1090
- function relatedNodes(
1091
- nodeId,
1092
- direction,
1093
- allowed
1094
- ) {
1095
- const result =
1096
- [];
1097
-
1098
- for (
1099
- const link
1100
- of state.raw.links
1101
- ) {
1102
- if (
1103
- allowed &&
1104
- !allowed.has(
1105
- link.type
1106
- )
1107
- ) {
1108
- continue;
1109
- }
1110
-
1111
- const source =
1112
- endpointId(
1113
- link,
1114
- "source"
1115
- );
1116
-
1117
- const target =
1118
- endpointId(
1119
- link,
1120
- "target"
1121
- );
1122
-
1123
- if (
1124
- direction ===
1125
- "incoming" &&
1126
- target ===
1127
- nodeId
1128
- ) {
1129
- const node =
1130
- rawNode(
1131
- source
1132
- );
1133
-
1134
- if (node) {
1135
- result.push(
1136
- node
1137
- );
1138
- }
1139
- }
1140
-
1141
- if (
1142
- direction ===
1143
- "outgoing" &&
1144
- source ===
1145
- nodeId
1146
- ) {
1147
- const node =
1148
- rawNode(
1149
- target
1150
- );
1151
-
1152
- if (node) {
1153
- result.push(
1154
- node
1155
- );
1156
- }
1157
- }
1158
- }
1159
-
1160
- return uniqueNodes(
1161
- result
1162
- );
1163
- }
1164
-
1165
- function blastRadius(
1166
- startId,
1167
- maxDepth = 3
1168
- ) {
1169
- const visited =
1170
- new Map();
1171
-
1172
- const queue = [
1173
- {
1174
- id:
1175
- startId,
1176
-
1177
- depth:
1178
- 0,
1179
-
1180
- score:
1181
- 0,
1182
- }
1183
- ];
1184
-
1185
- visited.set(
1186
- startId,
1187
- {
1188
- depth:
1189
- 0,
1190
-
1191
- score:
1192
- 0,
1193
- }
1194
- );
1195
-
1196
- while (
1197
- queue.length
1198
- ) {
1199
- const current =
1200
- queue.shift();
1201
-
1202
- if (
1203
- current.depth >=
1204
- maxDepth
1205
- ) {
1206
- continue;
1207
- }
1208
-
1209
- for (
1210
- const link
1211
- of state.raw.links
1212
- ) {
1213
- const weight =
1214
- IMPACT_WEIGHTS[
1215
- link.type
1216
- ];
1217
-
1218
- if (!weight) {
1219
- continue;
1220
- }
1221
-
1222
- const source =
1223
- endpointId(
1224
- link,
1225
- "source"
1226
- );
1227
-
1228
- const target =
1229
- endpointId(
1230
- link,
1231
- "target"
1232
- );
1233
-
1234
- /*
1235
- * Reverse traversal:
1236
- * if source depends on target,
1237
- * changing target may affect source.
1238
- */
1239
- if (
1240
- target !==
1241
- current.id
1242
- ) {
1243
- continue;
1244
- }
1245
-
1246
- const nextDepth =
1247
- current.depth +
1248
- 1;
1249
-
1250
- const nextScore =
1251
- current.score +
1252
- weight *
1253
- (
1254
- maxDepth -
1255
- current.depth
1256
- );
1257
-
1258
- const previous =
1259
- visited.get(
1260
- source
1261
- );
1262
-
1263
- if (
1264
- !previous ||
1265
- nextDepth <
1266
- previous.depth ||
1267
- nextScore >
1268
- previous.score
1269
- ) {
1270
- visited.set(
1271
- source,
1272
- {
1273
- depth:
1274
- nextDepth,
1275
-
1276
- score:
1277
- nextScore,
1278
- }
1279
- );
1280
-
1281
- queue.push({
1282
- id:
1283
- source,
1284
-
1285
- depth:
1286
- nextDepth,
1287
-
1288
- score:
1289
- nextScore,
1290
- });
1291
- }
1292
- }
1293
- }
1294
-
1295
- visited.delete(
1296
- startId
1297
- );
1298
-
1299
- const impacted =
1300
- [
1301
- ...visited.entries()
1302
- ]
1303
- .map(
1304
- ([id, info]) => ({
1305
- node:
1306
- rawNode(
1307
- id
1308
- ),
1309
-
1310
- ...info,
1311
- })
1312
- )
1313
- .filter(
1314
- item =>
1315
- item.node
1316
- )
1317
- .sort(
1318
- (a, b) =>
1319
- b.score -
1320
- a.score
1321
- );
1322
-
1323
- const score =
1324
- impacted.reduce(
1325
- (
1326
- total,
1327
- item
1328
- ) =>
1329
- total +
1330
- item.score,
1331
- 0
1332
- );
1333
-
1334
- let risk =
1335
- "LOW";
1336
-
1337
- if (
1338
- score >= 250 ||
1339
- impacted.length >= 40
1340
- ) {
1341
- risk =
1342
- "CRITICAL";
1343
- } else if (
1344
- score >= 120 ||
1345
- impacted.length >= 20
1346
- ) {
1347
- risk =
1348
- "HIGH";
1349
- } else if (
1350
- score >= 40 ||
1351
- impacted.length >= 8
1352
- ) {
1353
- risk =
1354
- "MEDIUM";
1355
- }
1356
-
1357
- return {
1358
- impacted,
1359
- score,
1360
- risk,
1361
- };
1362
- }
1363
-
1364
- function shortestPath(
1365
- fromId,
1366
- toId,
1367
- maxDepth = 10
1368
- ) {
1369
- if (
1370
- fromId ===
1371
- toId
1372
- ) {
1373
- return [
1374
- fromId
1375
- ];
1376
- }
1377
-
1378
- const queue = [
1379
- {
1380
- id:
1381
- fromId,
1382
-
1383
- path: [
1384
- fromId
1385
- ],
1386
- }
1387
- ];
1388
-
1389
- const visited =
1390
- new Set([
1391
- fromId
1392
- ]);
1393
-
1394
- while (
1395
- queue.length
1396
- ) {
1397
- const current =
1398
- queue.shift();
1399
-
1400
- if (
1401
- current.path.length >
1402
- maxDepth
1403
- ) {
1404
- continue;
1405
- }
1406
-
1407
- for (
1408
- const link
1409
- of state.raw.links
1410
- ) {
1411
- if (
1412
- !DEPENDENCY_RELATIONS
1413
- .has(
1414
- link.type
1415
- )
1416
- ) {
1417
- continue;
1418
- }
1419
-
1420
- if (
1421
- endpointId(
1422
- link,
1423
- "source"
1424
- ) !==
1425
- current.id
1426
- ) {
1427
- continue;
1428
- }
1429
-
1430
- const next =
1431
- endpointId(
1432
- link,
1433
- "target"
1434
- );
1435
-
1436
- if (
1437
- visited.has(
1438
- next
1439
- )
1440
- ) {
1441
- continue;
1442
- }
1443
-
1444
- const path = [
1445
- ...current.path,
1446
- next,
1447
- ];
1448
-
1449
- if (
1450
- next ===
1451
- toId
1452
- ) {
1453
- return path;
1454
- }
1455
-
1456
- visited.add(
1457
- next
1458
- );
1459
-
1460
- queue.push({
1461
- id:
1462
- next,
1463
-
1464
- path,
1465
- });
1466
- }
1467
- }
1468
-
1469
- return null;
1470
- }
1471
-
1472
- function relationHtml(
1473
- title,
1474
- nodes,
1475
- limit = 8
1476
- ) {
1477
- const visible =
1478
- nodes.slice(
1479
- 0,
1480
- limit
1481
- );
1482
-
1483
- return `
1484
- <div class="section">
1485
- <div class="section-title">
1486
- ${escapeHtml(
1487
- title
1488
- )}
1489
- · ${nodes.length}
1490
- </div>
1491
-
1492
- ${
1493
- visible.length
1494
- ? `
1495
- <div class="relation-list">
1496
- ${
1497
- visible
1498
- .map(
1499
- node => `
1500
- <button
1501
- class="relation-item"
1502
- data-jump-node="${escapeHtml(
1503
- node.id
1504
- )}"
1505
- >
1506
- <span class="relation-name">
1507
- ${escapeHtml(
1508
- node.qualifiedName ||
1509
- node.name
1510
- )}
1511
- </span>
1512
-
1513
- <span class="relation-meta">
1514
- ${escapeHtml(
1515
- node.filePath
1516
- )}
1517
- </span>
1518
- </button>
1519
- `
1520
- )
1521
- .join("")
1522
- }
1523
- </div>
1524
- `
1525
- : `
1526
- <div class="muted">
1527
- None
1528
- </div>
1529
- `
1530
- }
1531
-
1532
- ${
1533
- nodes.length >
1534
- limit
1535
- ? `
1536
- <div class="relation-more">
1537
- +${
1538
- nodes.length -
1539
- limit
1540
- } more
1541
- </div>
1542
- `
1543
- : ""
1544
- }
1545
- </div>
1546
- `;
1547
- }
1548
-
1549
- function resetPathHighlight() {
1550
- state.highlightedPath =
1551
- [];
1552
-
1553
- if (
1554
- !state.graph
1555
- ) {
1556
- return;
1557
- }
1558
-
1559
- state.graph
1560
- .linkWidth(
1561
- link =>
1562
- link.type ===
1563
- "CALL_REFERENCE"
1564
- ? 1.2
1565
- : .45
1566
- )
1567
- .linkColor(
1568
- link =>
1569
- linkColor(
1570
- link
1571
- )
1572
- );
1573
- }
1574
-
1575
- function isPathLink(
1576
- link,
1577
- path
1578
- ) {
1579
- const source =
1580
- endpointId(
1581
- link,
1582
- "source"
1583
- );
1584
-
1585
- const target =
1586
- endpointId(
1587
- link,
1588
- "target"
1589
- );
1590
-
1591
- for (
1592
- let i = 0;
1593
- i <
1594
- path.length - 1;
1595
- i++
1596
- ) {
1597
- if (
1598
- path[i] ===
1599
- source &&
1600
- path[i + 1] ===
1601
- target
1602
- ) {
1603
- return true;
1604
- }
1605
- }
1606
-
1607
- return false;
1608
- }
1609
-
1610
- function highlightPath(
1611
- path
1612
- ) {
1613
- state.highlightedPath =
1614
- path || [];
1615
-
1616
- if (
1617
- !state.graph
1618
- ) {
1619
- return;
1620
- }
1621
-
1622
- state.graph
1623
- .linkWidth(
1624
- link =>
1625
- isPathLink(
1626
- link,
1627
- state.highlightedPath
1628
- )
1629
- ? 3.5
1630
- : (
1631
- link.type ===
1632
- "CALL_REFERENCE"
1633
- ? 1.2
1634
- : .35
1635
- )
1636
- )
1637
- .linkColor(
1638
- link =>
1639
- isPathLink(
1640
- link,
1641
- state.highlightedPath
1642
- )
1643
- ? "rgba(255,225,80,.98)"
1644
- : linkColor(
1645
- link
1646
- )
1647
- );
1648
- }
1649
-
1650
- function flyToNode(
1651
- node
1652
- ) {
1653
- const distance =
1654
- 70;
1655
-
1656
- const length =
1657
- Math.hypot(
1658
- node.x || 1,
1659
- node.y || 1,
1660
- node.z || 1
1661
- );
1662
-
1663
- const ratio =
1664
- 1 +
1665
- distance /
1666
- length;
1667
-
1668
- state.graph
1669
- .cameraPosition(
1670
- {
1671
- x:
1672
- node.x *
1673
- ratio,
1674
-
1675
- y:
1676
- node.y *
1677
- ratio,
1678
-
1679
- z:
1680
- node.z *
1681
- ratio,
1682
- },
1683
-
1684
- node,
1685
-
1686
- 800
1687
- );
1688
- }
1689
-
1690
- function jumpToNode(
1691
- id
1692
- ) {
1693
- /*
1694
- * Clear filters if destination is hidden.
1695
- */
1696
- searchEl.value =
1697
- "";
1698
-
1699
- clusterEl.value =
1700
- "";
1701
-
1702
- edgeEl.value =
1703
- "";
1704
-
1705
- state.search =
1706
- "";
1707
-
1708
- state.cluster =
1709
- "";
1710
-
1711
- state.edge =
1712
- "";
1713
-
1714
- applyFilters();
1715
-
1716
- setTimeout(
1717
- () => {
1718
- const node =
1719
- state.graph
1720
- .graphData()
1721
- .nodes
1722
- .find(
1723
- item =>
1724
- item.id ===
1725
- id
1726
- );
1727
-
1728
- if (!node) {
1729
- return;
1730
- }
1731
-
1732
- showDetails(
1733
- node
1734
- );
1735
-
1736
- flyToNode(
1737
- node
1738
- );
1739
- },
1740
- 100
1741
- );
1742
- }
1743
-
1744
- function showDetails(
1745
- node
1746
- ) {
1747
- state.selected =
1748
- node;
1749
-
1750
- detailsEl
1751
- .classList
1752
- .add(
1753
- "open"
1754
- );
1755
-
1756
- const callers =
1757
- relatedNodes(
1758
- node.id,
1759
- "incoming",
1760
- CALL_RELATIONS
1761
- );
1762
-
1763
- const callees =
1764
- relatedNodes(
1765
- node.id,
1766
- "outgoing",
1767
- CALL_RELATIONS
1768
- );
1769
-
1770
- const dependencies =
1771
- relatedNodes(
1772
- node.id,
1773
- "outgoing",
1774
- DEPENDENCY_RELATIONS
1775
- );
1776
-
1777
- const dependents =
1778
- relatedNodes(
1779
- node.id,
1780
- "incoming",
1781
- DEPENDENCY_RELATIONS
1782
- );
1783
-
1784
- const blast =
1785
- blastRadius(
1786
- node.id,
1787
- 3
1788
- );
1789
-
1790
- let path =
1791
- null;
1792
-
1793
- if (
1794
- state.pathStart &&
1795
- state.pathStart !==
1796
- node.id
1797
- ) {
1798
- path =
1799
- shortestPath(
1800
- state.pathStart,
1801
- node.id
1802
- );
1803
-
1804
- if (path) {
1805
- highlightPath(
1806
- path
1807
- );
1808
- } else {
1809
- resetPathHighlight();
1810
- }
1811
- }
1812
-
1813
- const pathStartNode =
1814
- state.pathStart
1815
- ? rawNode(
1816
- state.pathStart
1817
- )
1818
- : null;
1819
-
1820
- detailsEl.innerHTML = `
1821
- <button
1822
- class="details-close"
1823
- id="details-close"
1824
- type="button"
1825
- aria-label="Close"
1826
- >
1827
- &times;
1828
- </button>
1829
-
1830
- <div class="detail-title">
1831
- ${escapeHtml(
1832
- node.qualifiedName ||
1833
- node.name
1834
- )}
1835
- </div>
1836
-
1837
- <div class="path">
1838
- ${escapeHtml(
1839
- node.filePath
1840
- )}
1841
- </div>
1842
-
1843
- <div>
1844
- <span class="tag">
1845
- ${escapeHtml(
1846
- node.type
1847
- )}
1848
- </span>
1849
-
1850
- ${
1851
- node.clusterLabel
1852
- ? `
1853
- <span class="tag">
1854
- ${escapeHtml(
1855
- node.clusterLabel
1856
- )}
1857
- </span>
1858
- `
1859
- : ""
1860
- }
1861
-
1862
- ${
1863
- node.layer
1864
- ? `
1865
- <span class="tag">
1866
- ${escapeHtml(
1867
- node.layer
1868
- )}
1869
- </span>
1870
- `
1871
- : ""
1872
- }
1873
-
1874
- ${
1875
- node.deadCode
1876
- ? `
1877
- <span class="tag">
1878
- dead-code:
1879
- ${escapeHtml(
1880
- node.deadCode
1881
- .confidence
1882
- )}
1883
- </span>
1884
- `
1885
- : ""
1886
- }
1887
- </div>
1888
-
1889
- <div class="intel-summary">
1890
- <div class="intel-card">
1891
- <span>Callers</span>
1892
- <strong>
1893
- ${callers.length}
1894
- </strong>
1895
- </div>
1896
-
1897
- <div class="intel-card">
1898
- <span>Callees</span>
1899
- <strong>
1900
- ${callees.length}
1901
- </strong>
1902
- </div>
1903
-
1904
- <div class="intel-card">
1905
- <span>Dependents</span>
1906
- <strong>
1907
- ${dependents.length}
1908
- </strong>
1909
- </div>
1910
-
1911
- <div class="intel-card">
1912
- <span>Dependencies</span>
1913
- <strong>
1914
- ${dependencies.length}
1915
- </strong>
1916
- </div>
1917
- </div>
1918
-
1919
- <div class="section">
1920
- <div class="section-title">
1921
- Blast Radius
1922
- </div>
1923
-
1924
- <div class="row">
1925
- <span>Risk</span>
1926
- <strong>
1927
- ${blast.risk}
1928
- </strong>
1929
- </div>
1930
-
1931
- <div class="row">
1932
- <span>Impacted nodes</span>
1933
- <strong>
1934
- ${blast.impacted.length}
1935
- </strong>
1936
- </div>
1937
-
1938
- <div class="row">
1939
- <span>Impact score</span>
1940
- <strong>
1941
- ${blast.score}
1942
- </strong>
1943
- </div>
1944
-
1945
- <span class="risk">
1946
- ${blast.risk}
1947
- </span>
1948
- </div>
1949
-
1950
- ${
1951
- blast.impacted.length
1952
- ? relationHtml(
1953
- "Top Impacted",
1954
- blast.impacted
1955
- .slice(
1956
- 0,
1957
- 8
1958
- )
1959
- .map(
1960
- item =>
1961
- item.node
1962
- )
1963
- )
1964
- : ""
1965
- }
1966
-
1967
- ${relationHtml(
1968
- "Callers",
1969
- callers
1970
- )}
1971
-
1972
- ${relationHtml(
1973
- "Callees",
1974
- callees
1975
- )}
1976
-
1977
- ${relationHtml(
1978
- "Dependencies",
1979
- dependencies
1980
- )}
1981
-
1982
- ${relationHtml(
1983
- "Dependents",
1984
- dependents
1985
- )}
1986
-
1987
- <div class="section">
1988
- <div class="section-title">
1989
- Shortest Path
1990
- </div>
1991
-
1992
- ${
1993
- !state.pathStart
1994
- ? `
1995
- <div class="muted">
1996
- Chọn node này làm điểm bắt đầu,
1997
- sau đó click node khác.
1998
- </div>
1999
- `
2000
- : `
2001
- <div class="path">
2002
- From:
2003
- ${escapeHtml(
2004
- pathStartNode
2005
- ?.qualifiedName ||
2006
- pathStartNode
2007
- ?.name ||
2008
- state.pathStart
2009
- )}
2010
- </div>
2011
- `
2012
- }
2013
-
2014
- <div class="path-actions">
2015
- <button
2016
- id="path-start"
2017
- type="button"
2018
- >
2019
- ${
2020
- state.pathStart ===
2021
- node.id
2022
- ? "Start selected"
2023
- : "Set path start"
2024
- }
2025
- </button>
2026
-
2027
- ${
2028
- state.pathStart
2029
- ? `
2030
- <button
2031
- id="path-clear"
2032
- type="button"
2033
- >
2034
- Clear path
2035
- </button>
2036
- `
2037
- : ""
2038
- }
2039
- </div>
2040
-
2041
- ${
2042
- state.pathStart &&
2043
- state.pathStart !==
2044
- node.id
2045
- ? (
2046
- path
2047
- ? `
2048
- <div class="path-chain">
2049
- ${
2050
- path.map(
2051
- id => {
2052
- const item =
2053
- rawNode(
2054
- id
2055
- );
2056
-
2057
- return `
2058
- <button
2059
- class="path-node"
2060
- data-jump-node="${escapeHtml(
2061
- id
2062
- )}"
2063
- >
2064
- ${escapeHtml(
2065
- item?.qualifiedName ||
2066
- item?.name ||
2067
- id
2068
- )}
2069
- </button>
2070
- `;
2071
- }
2072
- ).join("")
2073
- }
2074
- </div>
2075
- `
2076
- : `
2077
- <div class="relation-more">
2078
- Không tìm thấy directed path
2079
- trong 10 bước.
2080
- </div>
2081
- `
2082
- )
2083
- : ""
2084
- }
2085
- </div>
2086
- `;
2087
-
2088
- document
2089
- .getElementById(
2090
- "details-close"
2091
- )
2092
- ?.addEventListener(
2093
- "click",
2094
- event => {
2095
- event.stopPropagation();
2096
- closeDetails();
2097
- }
2098
- );
2099
-
2100
- detailsEl
2101
- .querySelectorAll(
2102
- "[data-jump-node]"
2103
- )
2104
- .forEach(
2105
- element => {
2106
- element
2107
- .addEventListener(
2108
- "click",
2109
- event => {
2110
- event.stopPropagation();
2111
-
2112
- jumpToNode(
2113
- element.dataset
2114
- .jumpNode
2115
- );
2116
- }
2117
- );
2118
- }
2119
- );
2120
-
2121
- document
2122
- .getElementById(
2123
- "path-start"
2124
- )
2125
- ?.addEventListener(
2126
- "click",
2127
- event => {
2128
- event.stopPropagation();
2129
-
2130
- state.pathStart =
2131
- node.id;
2132
-
2133
- resetPathHighlight();
2134
-
2135
- showDetails(
2136
- node
2137
- );
2138
- }
2139
- );
2140
-
2141
- document
2142
- .getElementById(
2143
- "path-clear"
2144
- )
2145
- ?.addEventListener(
2146
- "click",
2147
- event => {
2148
- event.stopPropagation();
2149
-
2150
- state.pathStart =
2151
- null;
2152
-
2153
- resetPathHighlight();
2154
-
2155
- showDetails(
2156
- node
2157
- );
2158
- }
2159
- );
2160
- }
2161
-
2162
- function populateFilters() {
2163
- clusterEl.innerHTML =
2164
- '<option value="">All subsystems</option>';
2165
-
2166
- edgeEl.innerHTML =
2167
- '<option value="">All relations</option>';
2168
-
2169
- const clusters =
2170
- [
2171
- ...new Set(
2172
- state.raw.nodes
2173
- .map(
2174
- node =>
2175
- node.clusterLabel
2176
- )
2177
- .filter(Boolean)
2178
- )
2179
- ]
2180
- .sort();
2181
-
2182
- for (
2183
- const value
2184
- of clusters
2185
- ) {
2186
- const option =
2187
- document.createElement(
2188
- "option"
2189
- );
2190
-
2191
- option.value =
2192
- value;
2193
-
2194
- option.textContent =
2195
- value;
2196
-
2197
- clusterEl.appendChild(
2198
- option
2199
- );
2200
- }
2201
-
2202
- const edges =
2203
- [
2204
- ...new Set(
2205
- state.raw.links
2206
- .map(
2207
- link =>
2208
- link.type
2209
- )
2210
- )
2211
- ]
2212
- .sort();
2213
-
2214
- for (
2215
- const value
2216
- of edges
2217
- ) {
2218
- const option =
2219
- document.createElement(
2220
- "option"
2221
- );
2222
-
2223
- option.value =
2224
- value;
2225
-
2226
- option.textContent =
2227
- value;
2228
-
2229
- edgeEl.appendChild(
2230
- option
2231
- );
2232
- }
2233
- }
2234
-
2235
- function filteredData() {
2236
- const search =
2237
- state.search
2238
- .trim()
2239
- .toLowerCase();
2240
-
2241
- let nodes =
2242
- state.raw.nodes;
2243
-
2244
- if (
2245
- state.cluster
2246
- ) {
2247
- nodes =
2248
- nodes.filter(
2249
- node =>
2250
- node.clusterLabel ===
2251
- state.cluster
2252
- );
2253
- }
2254
-
2255
- if (
2256
- search
2257
- ) {
2258
- nodes =
2259
- nodes.filter(
2260
- node =>
2261
- node.name
2262
- ?.toLowerCase()
2263
- .includes(
2264
- search
2265
- ) ||
2266
-
2267
- node.qualifiedName
2268
- ?.toLowerCase()
2269
- .includes(
2270
- search
2271
- ) ||
2272
-
2273
- node.filePath
2274
- ?.toLowerCase()
2275
- .includes(
2276
- search
2277
- )
2278
- );
2279
- }
2280
-
2281
- const ids =
2282
- new Set(
2283
- nodes.map(
2284
- node =>
2285
- node.id
2286
- )
2287
- );
2288
-
2289
- let links =
2290
- state.raw.links
2291
- .filter(
2292
- link => {
2293
- const source =
2294
- typeof link.source ===
2295
- "object"
2296
- ? link.source.id
2297
- : link.source;
2298
-
2299
- const target =
2300
- typeof link.target ===
2301
- "object"
2302
- ? link.target.id
2303
- : link.target;
2304
-
2305
- return (
2306
- ids.has(
2307
- source
2308
- ) &&
2309
- ids.has(
2310
- target
2311
- )
2312
- );
2313
- }
2314
- );
2315
-
2316
- if (
2317
- state.edge
2318
- ) {
2319
- links =
2320
- links.filter(
2321
- link =>
2322
- link.type ===
2323
- state.edge
2324
- );
2325
- }
2326
-
2327
- return {
2328
- nodes:
2329
- nodes.map(
2330
- node => ({
2331
- ...node
2332
- })
2333
- ),
2334
-
2335
- links:
2336
- links.map(
2337
- link => ({
2338
- ...link,
2339
-
2340
- source:
2341
- typeof link.source ===
2342
- "object"
2343
- ? link.source.id
2344
- : link.source,
2345
-
2346
- target:
2347
- typeof link.target ===
2348
- "object"
2349
- ? link.target.id
2350
- : link.target,
2351
- })
2352
- ),
2353
- };
2354
- }
2355
-
2356
- function renderStats(
2357
- data
2358
- ) {
2359
- const dashboard =
2360
- state.raw.dashboard ||
2361
- {};
2362
-
2363
- statsEl.innerHTML = `
2364
- <div>
2365
- <strong>
2366
- ${data.nodes.length}
2367
- </strong>
2368
- nodes
2369
- </div>
2370
-
2371
- <div>
2372
- <strong>
2373
- ${data.links.length}
2374
- </strong>
2375
- relationships
2376
- </div>
2377
-
2378
- <div>
2379
- <strong>
2380
- ${state.raw.summary.files}
2381
- </strong>
2382
- files
2383
- </div>
2384
-
2385
- <div>
2386
- <strong>
2387
- ${state.raw.summary.clusters}
2388
- </strong>
2389
- subsystems
2390
- </div>
2391
-
2392
- <div class="status">
2393
- ${escapeHtml(
2394
- dashboard.name ||
2395
- state.project
2396
- )}
2397
-
2398
- ${
2399
- dashboard.hasArchitecture
2400
- ? " · architecture"
2401
- : ""
2402
- }
2403
-
2404
- ${
2405
- dashboard.hasAnalysis
2406
- ? " · analysis"
2407
- : ""
2408
- }
2409
- </div>
2410
- `;
2411
- }
2412
-
2413
- function applyFilters() {
2414
- const data =
2415
- filteredData();
2416
-
2417
- state.graph.graphData(
2418
- data
2419
- );
2420
-
2421
- renderStats(
2422
- data
2423
- );
2424
- }
2425
-
2426
- async function loadProject(
2427
- project
2428
- ) {
2429
- closeDetails();
2430
-
2431
- document.body
2432
- .appendChild(
2433
- (() => {
2434
- let loading =
2435
- document.getElementById(
2436
- "loading"
2437
- );
2438
-
2439
- if (!loading) {
2440
- loading =
2441
- document.createElement(
2442
- "div"
2443
- );
2444
-
2445
- loading.id =
2446
- "loading";
2447
-
2448
- document.body
2449
- .appendChild(
2450
- loading
2451
- );
2452
- }
2453
-
2454
- loading.textContent =
2455
- `Loading ${project}…`;
2456
-
2457
- return document
2458
- .createDocumentFragment();
2459
- })()
2460
- );
2461
-
2462
- const response =
2463
- await fetch(
2464
- `/api/graph?project=${
2465
- encodeURIComponent(
2466
- project
2467
- )
2468
- }`
2469
- );
2470
-
2471
- if (
2472
- !response.ok
2473
- ) {
2474
- const error =
2475
- await response
2476
- .json()
2477
- .catch(
2478
- () => null
2479
- );
2480
-
2481
- throw new Error(
2482
- error?.error ||
2483
- "Unable to load graph"
2484
- );
2485
- }
2486
-
2487
- state.project =
2488
- project;
2489
-
2490
- state.raw =
2491
- await response.json();
2492
-
2493
- state.search =
2494
- "";
2495
-
2496
- state.cluster =
2497
- "";
2498
-
2499
- state.edge =
2500
- "";
2501
-
2502
- searchEl.value =
2503
- "";
2504
-
2505
- clusterEl.value =
2506
- "";
2507
-
2508
- edgeEl.value =
2509
- "";
2510
-
2511
- populateFilters();
2512
-
2513
- const data =
2514
- filteredData();
2515
-
2516
- if (
2517
- !state.graph
2518
- ) {
2519
- state.graph =
2520
- ForceGraph3D()(
2521
- graphEl
2522
- )
2523
- .backgroundColor(
2524
- "#050608"
2525
- )
2526
-
2527
- .nodeLabel(
2528
- node =>
2529
- `${node.qualifiedName || node.name}\n${node.filePath}`
2530
- )
2531
-
2532
- .nodeColor(
2533
- node =>
2534
- nodeColor(
2535
- node
2536
- )
2537
- )
2538
-
2539
- .nodeVal(
2540
- node =>
2541
- 2 +
2542
- Math.min(
2543
- 10,
2544
- Math.log2(
2545
- 1 +
2546
- node.incoming +
2547
- node.outgoing
2548
- )
2549
- )
2550
- )
2551
-
2552
- .linkColor(
2553
- link =>
2554
- linkColor(
2555
- link
2556
- )
2557
- )
2558
-
2559
- .linkOpacity(
2560
- .5
2561
- )
2562
-
2563
- .linkWidth(
2564
- link =>
2565
- link.type ===
2566
- "CALL_REFERENCE"
2567
- ? 1.2
2568
- : .45
2569
- )
2570
-
2571
- .linkDirectionalParticles(
2572
- link =>
2573
- link.type ===
2574
- "CALL_REFERENCE"
2575
- ? 1
2576
- : 0
2577
- )
2578
-
2579
- .linkDirectionalParticleWidth(
2580
- 1.5
2581
- )
2582
-
2583
- .onBackgroundClick(
2584
- () => {
2585
- closeDetails();
2586
- }
2587
- )
2588
-
2589
- .onNodeClick(
2590
- node => {
2591
- showDetails(
2592
- node
2593
- );
2594
-
2595
- const distance =
2596
- 70;
2597
-
2598
- const length =
2599
- Math.hypot(
2600
- node.x || 1,
2601
- node.y || 1,
2602
- node.z || 1
2603
- );
2604
-
2605
- const ratio =
2606
- 1 +
2607
- distance /
2608
- length;
2609
-
2610
- state.graph
2611
- .cameraPosition(
2612
- {
2613
- x:
2614
- node.x *
2615
- ratio,
2616
-
2617
- y:
2618
- node.y *
2619
- ratio,
2620
-
2621
- z:
2622
- node.z *
2623
- ratio,
2624
- },
2625
-
2626
- node,
2627
-
2628
- 900
2629
- );
2630
- }
2631
- );
2632
- }
2633
-
2634
- state.graph.graphData(
2635
- data
2636
- );
2637
-
2638
- renderStats(
2639
- data
2640
- );
2641
-
2642
- document.title =
2643
- `${
2644
- state.raw.dashboard?.name ||
2645
- project
2646
- } · ToolNet Graph`;
2647
-
2648
- const loading =
2649
- document.getElementById(
2650
- "loading"
2651
- );
2652
-
2653
- if (
2654
- loading
2655
- ) {
2656
- loading.remove();
2657
- }
2658
-
2659
- setTimeout(
2660
- () => {
2661
- state.graph
2662
- .zoomToFit(
2663
- 700,
2664
- 60
2665
- );
2666
- },
2667
- 300
2668
- );
2669
- }
2670
-
2671
- async function main() {
2672
- const response =
2673
- await fetch(
2674
- "/api/projects"
2675
- );
2676
-
2677
- if (
2678
- !response.ok
2679
- ) {
2680
- throw new Error(
2681
- "Unable to load ToolNet projects"
2682
- );
2683
- }
2684
-
2685
- const catalog =
2686
- await response.json();
2687
-
2688
- projectEl.innerHTML =
2689
- "";
2690
-
2691
- for (
2692
- const project
2693
- of catalog.projects
2694
- ) {
2695
- const option =
2696
- document.createElement(
2697
- "option"
2698
- );
2699
-
2700
- option.value =
2701
- project.remote;
2702
-
2703
- option.textContent =
2704
- project.hasGraph
2705
- ? project.name
2706
- : `${project.name} · not indexed`;
2707
-
2708
- option.disabled =
2709
- !project.hasGraph;
2710
-
2711
- projectEl.appendChild(
2712
- option
2713
- );
2714
- }
2715
-
2716
- const requested =
2717
- new URLSearchParams(
2718
- window.location.search
2719
- ).get(
2720
- "project"
2721
- );
2722
-
2723
- const requestedValid =
2724
- catalog.projects
2725
- .some(
2726
- project =>
2727
- project.remote ===
2728
- requested &&
2729
- project.hasGraph
2730
- );
2731
-
2732
- const selected =
2733
- requestedValid
2734
- ? requested
2735
- : catalog.defaultProject;
2736
-
2737
- if (!selected) {
2738
- throw new Error(
2739
- "No indexed projects found"
2740
- );
2741
- }
2742
-
2743
- projectEl.value =
2744
- selected;
2745
-
2746
- await loadProject(
2747
- selected
2748
- );
2749
- }
2750
-
2751
- projectEl.addEventListener(
2752
- "change",
2753
- async () => {
2754
- const project =
2755
- projectEl.value;
2756
-
2757
- if (!project) {
2758
- return;
2759
- }
2760
-
2761
- const url =
2762
- new URL(
2763
- window.location.href
2764
- );
2765
-
2766
- url.searchParams.set(
2767
- "project",
2768
- project
2769
- );
2770
-
2771
- history.replaceState(
2772
- {},
2773
- "",
2774
- url
2775
- );
2776
-
2777
- try {
2778
- await loadProject(
2779
- project
2780
- );
2781
- } catch (
2782
- error
2783
- ) {
2784
- alert(
2785
- error.message
2786
- );
2787
- }
2788
- }
2789
- );
2790
-
2791
- searchEl.addEventListener(
2792
- "input",
2793
- () => {
2794
- state.search =
2795
- searchEl.value;
2796
-
2797
- applyFilters();
2798
- }
2799
- );
2800
-
2801
- clusterEl.addEventListener(
2802
- "change",
2803
- () => {
2804
- state.cluster =
2805
- clusterEl.value;
2806
-
2807
- applyFilters();
2808
- }
2809
- );
2810
-
2811
- edgeEl.addEventListener(
2812
- "change",
2813
- () => {
2814
- state.edge =
2815
- edgeEl.value;
2816
-
2817
- applyFilters();
2818
- }
2819
- );
2820
-
2821
- document
2822
- .getElementById(
2823
- "reset"
2824
- )
2825
- .addEventListener(
2826
- "click",
2827
- () => {
2828
- searchEl.value =
2829
- "";
2830
-
2831
- clusterEl.value =
2832
- "";
2833
-
2834
- edgeEl.value =
2835
- "";
2836
-
2837
- state.search =
2838
- "";
2839
-
2840
- state.cluster =
2841
- "";
2842
-
2843
- state.edge =
2844
- "";
2845
-
2846
- state.pathStart =
2847
- null;
2848
-
2849
- resetPathHighlight();
2850
-
2851
- closeDetails();
2852
-
2853
- applyFilters();
2854
-
2855
- state.graph
2856
- .zoomToFit(
2857
- 700,
2858
- 60
2859
- );
2860
- }
2861
- );
2862
-
2863
- document.addEventListener(
2864
- "keydown",
2865
- event => {
2866
- if (
2867
- event.key ===
2868
- "Escape"
2869
- ) {
2870
- closeDetails();
2871
- }
2872
- }
2873
- );
2874
-
2875
- main().catch(
2876
- error => {
2877
- const loading =
2878
- document.getElementById(
2879
- "loading"
2880
- );
2881
-
2882
- if (
2883
- loading
2884
- ) {
2885
- loading.textContent =
2886
- error.message;
2887
- }
2888
-
2889
- console.error(
2890
- error
2891
- );
2892
- }
2893
- );
2894
- </script>
2895
- </body>
2896
- </html>