fastapi-voyager 0.15.2__py3-none-any.whl → 0.15.4__py3-none-any.whl
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.
- fastapi_voyager/server.py +4 -1
- fastapi_voyager/version.py +1 -1
- fastapi_voyager/web/graph-ui.js +38 -3
- fastapi_voyager/web/index.html +21 -9
- fastapi_voyager/web/store.js +441 -8
- fastapi_voyager/web/vue-main.js +35 -327
- {fastapi_voyager-0.15.2.dist-info → fastapi_voyager-0.15.4.dist-info}/METADATA +1 -1
- {fastapi_voyager-0.15.2.dist-info → fastapi_voyager-0.15.4.dist-info}/RECORD +11 -11
- {fastapi_voyager-0.15.2.dist-info → fastapi_voyager-0.15.4.dist-info}/WHEEL +0 -0
- {fastapi_voyager-0.15.2.dist-info → fastapi_voyager-0.15.4.dist-info}/entry_points.txt +0 -0
- {fastapi_voyager-0.15.2.dist-info → fastapi_voyager-0.15.4.dist-info}/licenses/LICENSE +0 -0
fastapi_voyager/server.py
CHANGED
|
@@ -19,6 +19,7 @@ WEB_DIR = Path(__file__).parent / "web"
|
|
|
19
19
|
WEB_DIR.mkdir(exist_ok=True)
|
|
20
20
|
|
|
21
21
|
GA_PLACEHOLDER = "<!-- GA_SNIPPET -->"
|
|
22
|
+
VERSION_PLACEHOLDER = "<!-- VERSION_PLACEHOLDER -->"
|
|
22
23
|
|
|
23
24
|
def _build_ga_snippet(ga_id: str | None) -> str:
|
|
24
25
|
if not ga_id:
|
|
@@ -197,7 +198,9 @@ def create_voyager(
|
|
|
197
198
|
index_file = WEB_DIR / "index.html"
|
|
198
199
|
if index_file.exists():
|
|
199
200
|
content = index_file.read_text(encoding="utf-8")
|
|
200
|
-
|
|
201
|
+
content = content.replace(GA_PLACEHOLDER, _build_ga_snippet(ga_id))
|
|
202
|
+
content = content.replace(VERSION_PLACEHOLDER, f"?v={__version__}")
|
|
203
|
+
return content
|
|
201
204
|
# fallback simple page if index.html missing
|
|
202
205
|
return """
|
|
203
206
|
<!doctype html>
|
fastapi_voyager/version.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
__all__ = ["__version__"]
|
|
2
|
-
__version__ = "0.15.
|
|
2
|
+
__version__ = "0.15.4"
|
fastapi_voyager/web/graph-ui.js
CHANGED
|
@@ -69,10 +69,45 @@ export class GraphUI {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
highlightSchemaBanner(node) {
|
|
72
|
+
// Get all polygons in the node
|
|
72
73
|
const polygons = node.querySelectorAll("polygon")
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
|
|
75
|
+
// The first polygon is typically the outer frame of the entire node
|
|
76
|
+
const outerFrame = polygons[0]
|
|
77
|
+
// The second polygon is typically the title background
|
|
78
|
+
const titleBg = polygons[1]
|
|
79
|
+
|
|
80
|
+
if (outerFrame) {
|
|
81
|
+
// Save original attributes for potential restoration
|
|
82
|
+
if (!outerFrame.hasAttribute("data-original-stroke")) {
|
|
83
|
+
outerFrame.setAttribute("data-original-stroke", outerFrame.getAttribute("stroke") || "")
|
|
84
|
+
outerFrame.setAttribute(
|
|
85
|
+
"data-original-stroke-width",
|
|
86
|
+
outerFrame.getAttribute("stroke-width") || "1"
|
|
87
|
+
)
|
|
88
|
+
outerFrame.setAttribute("data-original-fill", outerFrame.getAttribute("fill") || "")
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Apply bold purple border to the outer frame
|
|
92
|
+
outerFrame.setAttribute("stroke", "#822dba")
|
|
93
|
+
outerFrame.setAttribute("stroke-width", "3.0")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (titleBg) {
|
|
97
|
+
// Save original attributes
|
|
98
|
+
if (!titleBg.hasAttribute("data-original-stroke")) {
|
|
99
|
+
titleBg.setAttribute("data-original-stroke", titleBg.getAttribute("stroke") || "")
|
|
100
|
+
titleBg.setAttribute(
|
|
101
|
+
"data-original-stroke-width",
|
|
102
|
+
titleBg.getAttribute("stroke-width") || "1"
|
|
103
|
+
)
|
|
104
|
+
titleBg.setAttribute("data-original-fill", titleBg.getAttribute("fill") || "")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Apply purple background to title
|
|
108
|
+
titleBg.setAttribute("fill", "#822dba")
|
|
109
|
+
// Also update the stroke to match
|
|
110
|
+
titleBg.setAttribute("stroke", "#822dba")
|
|
76
111
|
}
|
|
77
112
|
}
|
|
78
113
|
|
fastapi_voyager/web/index.html
CHANGED
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
<head>
|
|
3
3
|
<title>FastAPI Voyager</title>
|
|
4
4
|
<meta name="theme-color" content="#ffffff" />
|
|
5
|
-
<link
|
|
6
|
-
|
|
5
|
+
<link
|
|
6
|
+
rel="stylesheet"
|
|
7
|
+
href="fastapi-voyager-static/graphviz.svg.css<!-- VERSION_PLACEHOLDER -->"
|
|
8
|
+
/>
|
|
9
|
+
<link
|
|
10
|
+
rel="stylesheet"
|
|
11
|
+
href="fastapi-voyager-static/quasar.min.css<!-- VERSION_PLACEHOLDER -->"
|
|
12
|
+
/>
|
|
7
13
|
<!-- App Icons / Favicons -->
|
|
8
14
|
<link
|
|
9
15
|
rel="apple-touch-icon"
|
|
@@ -111,8 +117,6 @@
|
|
|
111
117
|
border-radius: 50%;
|
|
112
118
|
background: #009485;
|
|
113
119
|
color: white;
|
|
114
|
-
border: 2px solid white;
|
|
115
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
|
116
120
|
cursor: pointer;
|
|
117
121
|
display: flex;
|
|
118
122
|
align-items: center;
|
|
@@ -284,10 +288,15 @@
|
|
|
284
288
|
aria-label="Help"
|
|
285
289
|
style="margin-right: 50px; margin-left: 20px"
|
|
286
290
|
>
|
|
287
|
-
<q-tooltip
|
|
291
|
+
<q-tooltip
|
|
292
|
+
anchor="bottom middle"
|
|
293
|
+
self="top middle"
|
|
294
|
+
:offset="[0,8]"
|
|
295
|
+
style="background-color: #f5f5f5; color: black; border: 1px solid #666"
|
|
296
|
+
>
|
|
288
297
|
<div
|
|
289
298
|
class="column items-start text-left"
|
|
290
|
-
style="line-height: 1.4; font-size:
|
|
299
|
+
style="line-height: 1.4; font-size: 14px"
|
|
291
300
|
>
|
|
292
301
|
<ul>
|
|
293
302
|
<li>scroll to zoom in/out</li>
|
|
@@ -571,7 +580,7 @@
|
|
|
571
580
|
</q-dialog>
|
|
572
581
|
</div>
|
|
573
582
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
574
|
-
<script src="fastapi-voyager-static/quasar.min.js"></script>
|
|
583
|
+
<script src="fastapi-voyager-static/quasar.min.js<!-- VERSION_PLACEHOLDER -->"></script>
|
|
575
584
|
<script
|
|
576
585
|
src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"
|
|
577
586
|
integrity="sha512-egJ/Y+22P9NQ9aIyVCh0VCOsfydyn8eNmqBy+y2CnJG+fpRIxXMS6jbWP8tVKp0jp+NO5n8WtMUAnNnGoJKi4w=="
|
|
@@ -593,7 +602,7 @@
|
|
|
593
602
|
></script>
|
|
594
603
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
|
|
595
604
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-color/2.1.2/jquery.color.min.js"></script>
|
|
596
|
-
<script src="fastapi-voyager-static/graphviz.svg.js"></script>
|
|
605
|
+
<script src="fastapi-voyager-static/graphviz.svg.js<!-- VERSION_PLACEHOLDER -->"></script>
|
|
597
606
|
<!-- highlight.js minimal ES module load (python only) -->
|
|
598
607
|
<link
|
|
599
608
|
rel="stylesheet"
|
|
@@ -615,7 +624,10 @@
|
|
|
615
624
|
}
|
|
616
625
|
})
|
|
617
626
|
</script>
|
|
618
|
-
<script
|
|
627
|
+
<script
|
|
628
|
+
type="module"
|
|
629
|
+
src="fastapi-voyager-static/vue-main.js<!-- VERSION_PLACEHOLDER -->"
|
|
630
|
+
></script>
|
|
619
631
|
|
|
620
632
|
<!-- GA_SNIPPET -->
|
|
621
633
|
</body>
|
fastapi_voyager/web/store.js
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
const { reactive } = window.Vue
|
|
2
2
|
|
|
3
3
|
const state = reactive({
|
|
4
|
-
item: {
|
|
5
|
-
count: 0,
|
|
6
|
-
},
|
|
7
|
-
|
|
8
4
|
version: "",
|
|
9
5
|
config: {
|
|
10
6
|
initial_page_policy: "first",
|
|
@@ -15,7 +11,8 @@ const state = reactive({
|
|
|
15
11
|
mode: "voyager", // voyager / er-diagram
|
|
16
12
|
|
|
17
13
|
previousTagRoute: {
|
|
18
|
-
//
|
|
14
|
+
// Store the last non-search tag/route selection for restoration when clearing search
|
|
15
|
+
// Used by resetSearch to return to the state before entering search mode
|
|
19
16
|
hasValue: false,
|
|
20
17
|
tag: null,
|
|
21
18
|
routeId: null,
|
|
@@ -65,6 +62,9 @@ const state = reactive({
|
|
|
65
62
|
fieldOptions: [],
|
|
66
63
|
},
|
|
67
64
|
|
|
65
|
+
// cache all schema options for filtering
|
|
66
|
+
allSchemaOptions: [],
|
|
67
|
+
|
|
68
68
|
// route information
|
|
69
69
|
routeDetail: {
|
|
70
70
|
show: false,
|
|
@@ -105,13 +105,446 @@ const state = reactive({
|
|
|
105
105
|
},
|
|
106
106
|
})
|
|
107
107
|
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
const getters = {
|
|
109
|
+
/**
|
|
110
|
+
* Find tag name by route ID
|
|
111
|
+
* Used to determine which tag a route belongs to
|
|
112
|
+
*/
|
|
113
|
+
findTagByRoute(routeId) {
|
|
114
|
+
return (
|
|
115
|
+
state.leftPanel.tags.find((tag) => (tag.routes || []).some((route) => route.id === routeId))
|
|
116
|
+
?.name || null
|
|
117
|
+
)
|
|
118
|
+
},
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const actions = {
|
|
122
|
+
/**
|
|
123
|
+
* Read tag, route and mode from URL query parameters
|
|
124
|
+
* @returns {{ tag: string|null, route: string|null, mode: string|null }}
|
|
125
|
+
*/
|
|
126
|
+
readQuerySelection() {
|
|
127
|
+
if (typeof window === "undefined") {
|
|
128
|
+
return { tag: null, route: null, mode: null }
|
|
129
|
+
}
|
|
130
|
+
const params = new URLSearchParams(window.location.search)
|
|
131
|
+
return {
|
|
132
|
+
tag: params.get("tag") || null,
|
|
133
|
+
route: params.get("route") || null,
|
|
134
|
+
mode: params.get("mode") || null,
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Sync current tag, route and mode selection to URL
|
|
140
|
+
* Updates browser URL without reloading the page
|
|
141
|
+
*/
|
|
142
|
+
syncSelectionToUrl() {
|
|
143
|
+
if (typeof window === "undefined") {
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
const params = new URLSearchParams(window.location.search)
|
|
147
|
+
if (state.leftPanel.tag) {
|
|
148
|
+
params.set("tag", state.leftPanel.tag)
|
|
149
|
+
} else {
|
|
150
|
+
params.delete("tag")
|
|
151
|
+
}
|
|
152
|
+
if (state.leftPanel.routeId) {
|
|
153
|
+
params.set("route", state.leftPanel.routeId)
|
|
154
|
+
} else {
|
|
155
|
+
params.delete("route")
|
|
156
|
+
}
|
|
157
|
+
// Always sync mode to URL for consistency
|
|
158
|
+
if (state.mode) {
|
|
159
|
+
params.set("mode", state.mode)
|
|
160
|
+
} else {
|
|
161
|
+
params.delete("mode")
|
|
162
|
+
}
|
|
163
|
+
const hash = window.location.hash || ""
|
|
164
|
+
const search = params.toString()
|
|
165
|
+
const base = window.location.pathname
|
|
166
|
+
const newUrl = search ? `${base}?${search}${hash}` : `${base}${hash}`
|
|
167
|
+
window.history.replaceState({}, "", newUrl)
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Apply selection from URL query parameters to state
|
|
172
|
+
* @param {{ tag: string|null, route: string|null, mode: string|null }} selection
|
|
173
|
+
* @returns {boolean} - true if any selection was applied
|
|
174
|
+
*/
|
|
175
|
+
applySelectionFromQuery(selection) {
|
|
176
|
+
let applied = false
|
|
177
|
+
if (selection.tag && state.leftPanel.tags.some((tag) => tag.name === selection.tag)) {
|
|
178
|
+
state.leftPanel.tag = selection.tag
|
|
179
|
+
state.leftPanel._tag = selection.tag
|
|
180
|
+
applied = true
|
|
181
|
+
}
|
|
182
|
+
if (selection.route && state.graph.routeItems?.[selection.route]) {
|
|
183
|
+
state.leftPanel.routeId = selection.route
|
|
184
|
+
applied = true
|
|
185
|
+
const inferredTag = getters.findTagByRoute(selection.route)
|
|
186
|
+
if (inferredTag) {
|
|
187
|
+
state.leftPanel.tag = inferredTag
|
|
188
|
+
state.leftPanel._tag = inferredTag
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Apply mode from URL if it's valid
|
|
192
|
+
if (selection.mode === "voyager" || selection.mode === "er-diagram") {
|
|
193
|
+
state.mode = selection.mode
|
|
194
|
+
applied = true
|
|
195
|
+
}
|
|
196
|
+
return applied
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Restore full tags from cache
|
|
201
|
+
* Used when resetting search mode
|
|
202
|
+
*/
|
|
203
|
+
loadFullTags() {
|
|
204
|
+
state.leftPanel.tags = state.leftPanel.fullTagsCache
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Populate field options based on selected schema
|
|
209
|
+
* @param {string} schemaId - Schema ID
|
|
210
|
+
*/
|
|
211
|
+
populateFieldOptions(schemaId) {
|
|
212
|
+
if (!schemaId) {
|
|
213
|
+
state.search.fieldOptions = []
|
|
214
|
+
state.search.fieldName = null
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
const schema = state.graph.schemaMap?.[schemaId]
|
|
218
|
+
if (!schema) {
|
|
219
|
+
state.search.fieldOptions = []
|
|
220
|
+
state.search.fieldName = null
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
const fields = Array.isArray(schema.fields) ? schema.fields.map((f) => f.name) : []
|
|
224
|
+
state.search.fieldOptions = fields
|
|
225
|
+
if (!fields.includes(state.search.fieldName)) {
|
|
226
|
+
state.search.fieldName = null
|
|
227
|
+
}
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Rebuild schema options from schema map
|
|
232
|
+
* Should be called when schema map changes
|
|
233
|
+
*/
|
|
234
|
+
rebuildSchemaOptions() {
|
|
235
|
+
const dict = state.graph.schemaMap || {}
|
|
236
|
+
const opts = Object.values(dict).map((s) => ({
|
|
237
|
+
label: s.name,
|
|
238
|
+
desc: s.id,
|
|
239
|
+
value: s.id,
|
|
240
|
+
}))
|
|
241
|
+
state.allSchemaOptions = opts
|
|
242
|
+
state.search.schemaOptions = opts.slice()
|
|
243
|
+
this.populateFieldOptions(state.search.schemaName)
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Load tags based on search criteria
|
|
248
|
+
* @returns {Promise<void>}
|
|
249
|
+
*/
|
|
250
|
+
async loadSearchedTags() {
|
|
251
|
+
try {
|
|
252
|
+
const payload = {
|
|
253
|
+
schema_name: state.search.schemaName,
|
|
254
|
+
schema_field: state.search.fieldName || null,
|
|
255
|
+
show_fields: state.filter.showFields,
|
|
256
|
+
brief: state.filter.brief,
|
|
257
|
+
hide_primitive_route: state.filter.hidePrimitiveRoute,
|
|
258
|
+
show_module: state.filter.showModule,
|
|
259
|
+
}
|
|
260
|
+
const res = await fetch("dot-search", {
|
|
261
|
+
method: "POST",
|
|
262
|
+
headers: { "Content-Type": "application/json" },
|
|
263
|
+
body: JSON.stringify(payload),
|
|
264
|
+
})
|
|
265
|
+
if (res.ok) {
|
|
266
|
+
const data = await res.json()
|
|
267
|
+
const tags = Array.isArray(data.tags) ? data.tags : []
|
|
268
|
+
state.leftPanel.tags = tags
|
|
269
|
+
}
|
|
270
|
+
} catch (err) {
|
|
271
|
+
console.error("dot-search failed", err)
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Load initial data from API
|
|
277
|
+
* @param {Function} onGenerate - Callback to generate graph after load
|
|
278
|
+
* @param {Function} renderBasedOnInitialPolicy - Callback to render based on policy
|
|
279
|
+
* @returns {Promise<void>}
|
|
280
|
+
*/
|
|
281
|
+
async loadInitial(onGenerate, renderBasedOnInitialPolicy) {
|
|
282
|
+
state.initializing = true
|
|
283
|
+
try {
|
|
284
|
+
const res = await fetch("dot")
|
|
285
|
+
const data = await res.json()
|
|
286
|
+
const tags = Array.isArray(data.tags) ? data.tags : []
|
|
287
|
+
state.leftPanel.tags = tags
|
|
288
|
+
// Cache the full tags for later use (e.g., resetSearch)
|
|
289
|
+
state.leftPanel.fullTagsCache = tags
|
|
290
|
+
|
|
291
|
+
const schemasArr = Array.isArray(data.schemas) ? data.schemas : []
|
|
292
|
+
// Build dict keyed by id for faster lookups and simpler prop passing
|
|
293
|
+
const schemaMap = Object.fromEntries(schemasArr.map((s) => [s.id, s]))
|
|
294
|
+
state.graph.schemaMap = schemaMap
|
|
295
|
+
state.graph.schemaKeys = new Set(Object.keys(schemaMap))
|
|
296
|
+
state.graph.routeItems = data.tags
|
|
297
|
+
.map((t) => t.routes)
|
|
298
|
+
.flat()
|
|
299
|
+
.reduce((acc, r) => {
|
|
300
|
+
acc[r.id] = r
|
|
301
|
+
return acc
|
|
302
|
+
}, {})
|
|
303
|
+
state.modeControl.briefModeEnabled = data.enable_brief_mode || false
|
|
304
|
+
state.version = data.version || ""
|
|
305
|
+
state.swagger.url = data.swagger_url || null
|
|
306
|
+
state.config.has_er_diagram = data.has_er_diagram || false
|
|
307
|
+
state.config.enable_pydantic_resolve_meta = data.enable_pydantic_resolve_meta || false
|
|
308
|
+
|
|
309
|
+
this.rebuildSchemaOptions()
|
|
310
|
+
|
|
311
|
+
const querySelection = this.readQuerySelection()
|
|
312
|
+
const restoredFromQuery = this.applySelectionFromQuery(querySelection)
|
|
313
|
+
if (restoredFromQuery) {
|
|
314
|
+
this.syncSelectionToUrl()
|
|
315
|
+
onGenerate()
|
|
316
|
+
return
|
|
317
|
+
} else {
|
|
318
|
+
state.config.initial_page_policy = data.initial_page_policy
|
|
319
|
+
// Check if mode was applied from URL even if tag/route wasn't
|
|
320
|
+
if (
|
|
321
|
+
querySelection.mode &&
|
|
322
|
+
(querySelection.mode === "voyager" || querySelection.mode === "er-diagram")
|
|
323
|
+
) {
|
|
324
|
+
this.syncSelectionToUrl()
|
|
325
|
+
onGenerate()
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
renderBasedOnInitialPolicy()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// default route options placeholder
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.error("Initial load failed", e)
|
|
334
|
+
} finally {
|
|
335
|
+
state.initializing = false
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Filter schema options based on search text
|
|
341
|
+
* Used by Quasar select component's filter function
|
|
342
|
+
* @param {string} val - Search text
|
|
343
|
+
* @param {Function} update - Quasar update callback
|
|
344
|
+
*/
|
|
345
|
+
filterSearchSchemas(val, update) {
|
|
346
|
+
const needle = (val || "").toLowerCase()
|
|
347
|
+
update(() => {
|
|
348
|
+
if (!needle) {
|
|
349
|
+
state.search.schemaOptions = state.allSchemaOptions.slice()
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
state.search.schemaOptions = state.allSchemaOptions.filter((option) =>
|
|
353
|
+
option.label.toLowerCase().includes(needle)
|
|
354
|
+
)
|
|
355
|
+
})
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Handle schema selection change
|
|
360
|
+
* Updates state and triggers search if a schema is selected
|
|
361
|
+
* @param {string} val - Selected schema ID
|
|
362
|
+
* @param {Function} onSearch - Callback to trigger search
|
|
363
|
+
*/
|
|
364
|
+
onSearchSchemaChange(val, onSearch) {
|
|
365
|
+
state.search.schemaName = val
|
|
366
|
+
state.search.mode = false
|
|
367
|
+
if (!val) {
|
|
368
|
+
// Clearing the select should only run resetSearch via @clear
|
|
369
|
+
return
|
|
370
|
+
}
|
|
371
|
+
onSearch()
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Reset detail panels (right drawer and route detail)
|
|
376
|
+
*/
|
|
377
|
+
resetDetailPanels() {
|
|
378
|
+
state.rightDrawer.drawer = false
|
|
379
|
+
state.routeDetail.show = false
|
|
380
|
+
state.schemaDetail.schemaCodeName = ""
|
|
381
|
+
},
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Reset left panel selection and regenerate
|
|
385
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
386
|
+
*/
|
|
387
|
+
onReset(onGenerate) {
|
|
388
|
+
state.leftPanel.tag = null
|
|
389
|
+
state.leftPanel._tag = null
|
|
390
|
+
state.leftPanel.routeId = null
|
|
391
|
+
this.syncSelectionToUrl()
|
|
392
|
+
onGenerate()
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Toggle pydantic resolve meta visibility
|
|
397
|
+
* @param {boolean} val - New value
|
|
398
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
399
|
+
*/
|
|
400
|
+
togglePydanticResolveMeta(val, onGenerate) {
|
|
401
|
+
state.modeControl.pydanticResolveMetaEnabled = val
|
|
402
|
+
try {
|
|
403
|
+
localStorage.setItem("pydantic_resolve_meta", JSON.stringify(val))
|
|
404
|
+
} catch (e) {
|
|
405
|
+
console.warn("Failed to save pydantic_resolve_meta to localStorage", e)
|
|
406
|
+
}
|
|
407
|
+
onGenerate()
|
|
408
|
+
},
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Toggle show module clustering
|
|
412
|
+
* @param {boolean} val - New value
|
|
413
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
414
|
+
*/
|
|
415
|
+
toggleShowModule(val, onGenerate) {
|
|
416
|
+
state.filter.showModule = val
|
|
417
|
+
try {
|
|
418
|
+
localStorage.setItem("show_module_cluster", JSON.stringify(val))
|
|
419
|
+
} catch (e) {
|
|
420
|
+
console.warn("Failed to save show_module_cluster to localStorage", e)
|
|
421
|
+
}
|
|
422
|
+
onGenerate()
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Toggle show fields option
|
|
427
|
+
* @param {string} field - Field display option ("single", "object", "all")
|
|
428
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
429
|
+
*/
|
|
430
|
+
toggleShowField(field, onGenerate) {
|
|
431
|
+
state.filter.showFields = field
|
|
432
|
+
onGenerate(false)
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Toggle brief mode
|
|
437
|
+
* @param {boolean} val - New value
|
|
438
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
439
|
+
*/
|
|
440
|
+
toggleBrief(val, onGenerate) {
|
|
441
|
+
state.filter.brief = val
|
|
442
|
+
try {
|
|
443
|
+
localStorage.setItem("brief_mode", JSON.stringify(val))
|
|
444
|
+
} catch (e) {
|
|
445
|
+
console.warn("Failed to save brief_mode to localStorage", e)
|
|
446
|
+
}
|
|
447
|
+
onGenerate()
|
|
448
|
+
},
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Toggle hide primitive route
|
|
452
|
+
* @param {boolean} val - New value
|
|
453
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
454
|
+
*/
|
|
455
|
+
toggleHidePrimitiveRoute(val, onGenerate) {
|
|
456
|
+
state.filter.hidePrimitiveRoute = val
|
|
457
|
+
try {
|
|
458
|
+
localStorage.setItem("hide_primitive", JSON.stringify(val))
|
|
459
|
+
} catch (e) {
|
|
460
|
+
console.warn("Failed to save hide_primitive to localStorage", e)
|
|
461
|
+
}
|
|
462
|
+
onGenerate(false)
|
|
463
|
+
},
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Render based on initial page policy
|
|
467
|
+
* @param {Function} onGenerate - Callback to regenerate graph
|
|
468
|
+
*/
|
|
469
|
+
renderBasedOnInitialPolicy(onGenerate) {
|
|
470
|
+
switch (state.config.initial_page_policy) {
|
|
471
|
+
case "full":
|
|
472
|
+
onGenerate()
|
|
473
|
+
return
|
|
474
|
+
case "empty":
|
|
475
|
+
return
|
|
476
|
+
case "first":
|
|
477
|
+
state.leftPanel.tag = state.leftPanel.tags.length > 0 ? state.leftPanel.tags[0].name : null
|
|
478
|
+
state.leftPanel._tag = state.leftPanel.tag
|
|
479
|
+
this.syncSelectionToUrl()
|
|
480
|
+
onGenerate()
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Build payload for Voyager rendering
|
|
487
|
+
* @returns {Object} Payload for dot API
|
|
488
|
+
*/
|
|
489
|
+
buildVoyagerPayload() {
|
|
490
|
+
const activeSchema = state.search.mode ? state.search.schemaName : null
|
|
491
|
+
const activeField = state.search.mode ? state.search.fieldName : null
|
|
492
|
+
return {
|
|
493
|
+
tags: state.leftPanel.tag ? [state.leftPanel.tag] : null,
|
|
494
|
+
schema_name: activeSchema || null,
|
|
495
|
+
schema_field: activeField || null,
|
|
496
|
+
route_name: state.leftPanel.routeId || null,
|
|
497
|
+
show_fields: state.filter.showFields,
|
|
498
|
+
brief: state.filter.brief,
|
|
499
|
+
hide_primitive_route: state.filter.hidePrimitiveRoute,
|
|
500
|
+
show_module: state.filter.showModule,
|
|
501
|
+
show_pydantic_resolve_meta: state.modeControl.pydanticResolveMetaEnabled,
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Build payload for ER Diagram rendering
|
|
507
|
+
* @returns {Object} Payload for er-diagram API
|
|
508
|
+
*/
|
|
509
|
+
buildErDiagramPayload() {
|
|
510
|
+
return {
|
|
511
|
+
show_fields: state.filter.showFields,
|
|
512
|
+
show_module: state.filter.showModule,
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Restore search state and return whether to regenerate
|
|
518
|
+
* @returns {boolean} - true if should regenerate with previous selection
|
|
519
|
+
*/
|
|
520
|
+
resetSearchState() {
|
|
521
|
+
state.search.mode = false
|
|
522
|
+
const hadPreviousValue = state.previousTagRoute.hasValue
|
|
523
|
+
|
|
524
|
+
if (hadPreviousValue) {
|
|
525
|
+
state.leftPanel.tag = state.previousTagRoute.tag
|
|
526
|
+
state.leftPanel._tag = state.previousTagRoute.tag
|
|
527
|
+
state.leftPanel.routeId = state.previousTagRoute.routeId
|
|
528
|
+
// Clear the saved state
|
|
529
|
+
state.previousTagRoute.hasValue = false
|
|
530
|
+
} else {
|
|
531
|
+
state.leftPanel.tag = null
|
|
532
|
+
state.leftPanel._tag = null
|
|
533
|
+
state.leftPanel.routeId = null
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
this.syncSelectionToUrl()
|
|
537
|
+
this.loadFullTags()
|
|
538
|
+
|
|
539
|
+
return hadPreviousValue
|
|
111
540
|
},
|
|
112
541
|
}
|
|
113
542
|
|
|
543
|
+
const mutations = {}
|
|
544
|
+
|
|
114
545
|
export const store = {
|
|
115
546
|
state,
|
|
547
|
+
getters,
|
|
548
|
+
actions,
|
|
116
549
|
mutations,
|
|
117
550
|
}
|
fastapi_voyager/web/vue-main.js
CHANGED
|
@@ -22,7 +22,6 @@ function loadToggleState(key, defaultValue = false) {
|
|
|
22
22
|
const app = createApp({
|
|
23
23
|
setup() {
|
|
24
24
|
let graphUI = null
|
|
25
|
-
const allSchemaOptions = ref([])
|
|
26
25
|
const erDiagramLoading = ref(false)
|
|
27
26
|
const erDiagramCache = ref("")
|
|
28
27
|
|
|
@@ -42,20 +41,13 @@ const app = createApp({
|
|
|
42
41
|
graphUI = new GraphUI("#graph", {
|
|
43
42
|
onSchemaShiftClick: (id) => {
|
|
44
43
|
if (store.state.graph.schemaKeys.has(id)) {
|
|
45
|
-
// Only save current tag/route if we're not already in search mode
|
|
46
|
-
// This prevents overwriting the saved state with null values
|
|
47
|
-
if (!store.state.previousTagRoute.hasValue && !store.state.search.mode) {
|
|
48
|
-
store.state.previousTagRoute.tag = store.state.leftPanel.tag
|
|
49
|
-
store.state.previousTagRoute.routeId = store.state.leftPanel.routeId
|
|
50
|
-
store.state.previousTagRoute.hasValue = true
|
|
51
|
-
}
|
|
52
44
|
store.state.search.mode = true
|
|
53
45
|
store.state.search.schemaName = id
|
|
54
46
|
onSearch()
|
|
55
47
|
}
|
|
56
48
|
},
|
|
57
49
|
onSchemaClick: (id) => {
|
|
58
|
-
resetDetailPanels()
|
|
50
|
+
store.actions.resetDetailPanels()
|
|
59
51
|
if (store.state.graph.schemaKeys.has(id)) {
|
|
60
52
|
store.state.schemaDetail.schemaCodeName = id
|
|
61
53
|
store.state.rightDrawer.drawer = true
|
|
@@ -66,257 +58,44 @@ const app = createApp({
|
|
|
66
58
|
}
|
|
67
59
|
},
|
|
68
60
|
resetCb: () => {
|
|
69
|
-
resetDetailPanels()
|
|
61
|
+
store.actions.resetDetailPanels()
|
|
70
62
|
},
|
|
71
63
|
})
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
function rebuildSchemaOptions() {
|
|
75
|
-
const dict = store.state.graph.schemaMap || {}
|
|
76
|
-
const opts = Object.values(dict).map((s) => ({
|
|
77
|
-
label: s.name,
|
|
78
|
-
desc: s.id,
|
|
79
|
-
value: s.id,
|
|
80
|
-
}))
|
|
81
|
-
allSchemaOptions.value = opts
|
|
82
|
-
store.state.search.schemaOptions = opts.slice()
|
|
83
|
-
populateFieldOptions(store.state.search.schemaName)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function populateFieldOptions(schemaId) {
|
|
87
|
-
if (!schemaId) {
|
|
88
|
-
store.state.search.fieldOptions = []
|
|
89
|
-
store.state.search.fieldName = null
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
const schema = store.state.graph.schemaMap?.[schemaId]
|
|
93
|
-
if (!schema) {
|
|
94
|
-
store.state.search.fieldOptions = []
|
|
95
|
-
store.state.search.fieldName = null
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
const fields = Array.isArray(schema.fields) ? schema.fields.map((f) => f.name) : []
|
|
99
|
-
store.state.search.fieldOptions = fields
|
|
100
|
-
if (!fields.includes(store.state.search.fieldName)) {
|
|
101
|
-
store.state.search.fieldName = null
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function filterSearchSchemas(val, update) {
|
|
106
|
-
const needle = (val || "").toLowerCase()
|
|
107
|
-
update(() => {
|
|
108
|
-
if (!needle) {
|
|
109
|
-
store.state.search.schemaOptions = allSchemaOptions.value.slice()
|
|
110
|
-
return
|
|
111
|
-
}
|
|
112
|
-
store.state.search.schemaOptions = allSchemaOptions.value.filter((option) =>
|
|
113
|
-
option.label.toLowerCase().includes(needle)
|
|
114
|
-
)
|
|
115
|
-
})
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function onSearchSchemaChange(val) {
|
|
119
|
-
store.state.search.schemaName = val
|
|
120
|
-
store.state.search.mode = false
|
|
121
|
-
if (!val) {
|
|
122
|
-
// Clearing the select should only run resetSearch via @clear
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
onSearch()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function readQuerySelection() {
|
|
129
|
-
if (typeof window === "undefined") {
|
|
130
|
-
return { tag: null, route: null }
|
|
131
|
-
}
|
|
132
|
-
const params = new URLSearchParams(window.location.search)
|
|
133
|
-
return {
|
|
134
|
-
tag: params.get("tag") || null,
|
|
135
|
-
route: params.get("route") || null,
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function findTagByRoute(routeId) {
|
|
140
|
-
return (
|
|
141
|
-
store.state.leftPanel.tags.find((tag) =>
|
|
142
|
-
(tag.routes || []).some((route) => route.id === routeId)
|
|
143
|
-
)?.name || null
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function syncSelectionToUrl() {
|
|
148
|
-
if (typeof window === "undefined") {
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
const params = new URLSearchParams(window.location.search)
|
|
152
|
-
if (store.state.leftPanel.tag) {
|
|
153
|
-
params.set("tag", store.state.leftPanel.tag)
|
|
154
|
-
} else {
|
|
155
|
-
params.delete("tag")
|
|
156
|
-
}
|
|
157
|
-
if (store.state.leftPanel.routeId) {
|
|
158
|
-
params.set("route", store.state.leftPanel.routeId)
|
|
159
|
-
} else {
|
|
160
|
-
params.delete("route")
|
|
161
|
-
}
|
|
162
|
-
const hash = window.location.hash || ""
|
|
163
|
-
const search = params.toString()
|
|
164
|
-
const base = window.location.pathname
|
|
165
|
-
const newUrl = search ? `${base}?${search}${hash}` : `${base}${hash}`
|
|
166
|
-
window.history.replaceState({}, "", newUrl)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function applySelectionFromQuery(selection) {
|
|
170
|
-
let applied = false
|
|
171
|
-
if (selection.tag && store.state.leftPanel.tags.some((tag) => tag.name === selection.tag)) {
|
|
172
|
-
store.state.leftPanel.tag = selection.tag
|
|
173
|
-
store.state.leftPanel._tag = selection.tag
|
|
174
|
-
applied = true
|
|
175
|
-
}
|
|
176
|
-
if (selection.route && store.state.graph.routeItems?.[selection.route]) {
|
|
177
|
-
store.state.leftPanel.routeId = selection.route
|
|
178
|
-
applied = true
|
|
179
|
-
const inferredTag = findTagByRoute(selection.route)
|
|
180
|
-
if (inferredTag) {
|
|
181
|
-
store.state.leftPanel.tag = inferredTag
|
|
182
|
-
store.state.leftPanel._tag = inferredTag
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return applied
|
|
186
|
-
}
|
|
187
|
-
|
|
188
66
|
async function resetSearch() {
|
|
189
|
-
store.
|
|
190
|
-
const hadPreviousValue = store.state.previousTagRoute.hasValue
|
|
191
|
-
|
|
192
|
-
if (hadPreviousValue) {
|
|
193
|
-
store.state.leftPanel.tag = store.state.previousTagRoute.tag
|
|
194
|
-
store.state.leftPanel._tag = store.state.previousTagRoute.tag
|
|
195
|
-
store.state.leftPanel.routeId = store.state.previousTagRoute.routeId
|
|
196
|
-
store.state.previousTagRoute.hasValue = false
|
|
197
|
-
} else {
|
|
198
|
-
store.state.leftPanel.tag = null
|
|
199
|
-
store.state.leftPanel._tag = null
|
|
200
|
-
store.state.leftPanel.routeId = null
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
syncSelectionToUrl()
|
|
204
|
-
|
|
205
|
-
// Load the full tags from cache (not search results) since we're resetting search
|
|
206
|
-
loadFullTags()
|
|
67
|
+
const hadPreviousValue = store.actions.resetSearchState()
|
|
207
68
|
|
|
208
69
|
// If we restored a previous tag/route, generate with it
|
|
209
70
|
// Otherwise, fall back to initial policy
|
|
210
71
|
if (hadPreviousValue) {
|
|
211
72
|
onGenerate()
|
|
212
73
|
} else {
|
|
213
|
-
renderBasedOnInitialPolicy()
|
|
74
|
+
store.actions.renderBasedOnInitialPolicy(onGenerate)
|
|
214
75
|
}
|
|
215
76
|
}
|
|
216
77
|
|
|
217
|
-
function loadFullTags() {
|
|
218
|
-
// Restore from cache (set by loadInitial)
|
|
219
|
-
store.state.leftPanel.tags = store.state.leftPanel.fullTagsCache
|
|
220
|
-
}
|
|
221
|
-
|
|
222
78
|
async function onSearch() {
|
|
79
|
+
// Save current state before entering search mode (only if not already saved)
|
|
80
|
+
if (!store.state.previousTagRoute.hasValue) {
|
|
81
|
+
store.state.previousTagRoute.tag = store.state.leftPanel.tag
|
|
82
|
+
store.state.previousTagRoute.routeId = store.state.leftPanel.routeId
|
|
83
|
+
store.state.previousTagRoute.hasValue = true
|
|
84
|
+
}
|
|
85
|
+
|
|
223
86
|
store.state.search.mode = true
|
|
224
87
|
store.state.leftPanel.tag = null
|
|
225
88
|
store.state.leftPanel._tag = null
|
|
226
89
|
store.state.leftPanel.routeId = null
|
|
227
|
-
syncSelectionToUrl()
|
|
228
|
-
await loadSearchedTags()
|
|
90
|
+
store.actions.syncSelectionToUrl()
|
|
91
|
+
await store.actions.loadSearchedTags()
|
|
229
92
|
await onGenerate()
|
|
230
93
|
}
|
|
231
|
-
async function loadSearchedTags() {
|
|
232
|
-
try {
|
|
233
|
-
const payload = {
|
|
234
|
-
schema_name: store.state.search.schemaName,
|
|
235
|
-
schema_field: store.state.search.fieldName || null,
|
|
236
|
-
show_fields: store.state.filter.showFields,
|
|
237
|
-
brief: store.state.filter.brief,
|
|
238
|
-
hide_primitive_route: store.state.filter.hidePrimitiveRoute,
|
|
239
|
-
show_module: store.state.filter.showModule,
|
|
240
|
-
}
|
|
241
|
-
const res = await fetch("dot-search", {
|
|
242
|
-
method: "POST",
|
|
243
|
-
headers: { "Content-Type": "application/json" },
|
|
244
|
-
body: JSON.stringify(payload),
|
|
245
|
-
})
|
|
246
|
-
if (res.ok) {
|
|
247
|
-
const data = await res.json()
|
|
248
|
-
const tags = Array.isArray(data.tags) ? data.tags : []
|
|
249
|
-
store.state.leftPanel.tags = tags
|
|
250
|
-
}
|
|
251
|
-
} catch (err) {
|
|
252
|
-
console.error("dot-search failed", err)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
94
|
|
|
256
95
|
async function loadInitial() {
|
|
257
|
-
store.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const data = await res.json()
|
|
261
|
-
const tags = Array.isArray(data.tags) ? data.tags : []
|
|
262
|
-
store.state.leftPanel.tags = tags
|
|
263
|
-
// Cache the full tags for later use (e.g., resetSearch)
|
|
264
|
-
store.state.leftPanel.fullTagsCache = tags
|
|
265
|
-
|
|
266
|
-
const schemasArr = Array.isArray(data.schemas) ? data.schemas : []
|
|
267
|
-
// Build dict keyed by id for faster lookups and simpler prop passing
|
|
268
|
-
const schemaMap = Object.fromEntries(schemasArr.map((s) => [s.id, s]))
|
|
269
|
-
store.state.graph.schemaMap = schemaMap
|
|
270
|
-
store.state.graph.schemaKeys = new Set(Object.keys(schemaMap))
|
|
271
|
-
store.state.graph.routeItems = data.tags
|
|
272
|
-
.map((t) => t.routes)
|
|
273
|
-
.flat()
|
|
274
|
-
.reduce((acc, r) => {
|
|
275
|
-
acc[r.id] = r
|
|
276
|
-
return acc
|
|
277
|
-
}, {})
|
|
278
|
-
store.state.modeControl.briefModeEnabled = data.enable_brief_mode || false
|
|
279
|
-
store.state.version = data.version || ""
|
|
280
|
-
store.state.swagger.url = data.swagger_url || null
|
|
281
|
-
store.state.config.has_er_diagram = data.has_er_diagram || false
|
|
282
|
-
store.state.config.enable_pydantic_resolve_meta = data.enable_pydantic_resolve_meta || false
|
|
283
|
-
|
|
284
|
-
rebuildSchemaOptions()
|
|
285
|
-
|
|
286
|
-
const querySelection = readQuerySelection()
|
|
287
|
-
const restoredFromQuery = applySelectionFromQuery(querySelection)
|
|
288
|
-
if (restoredFromQuery) {
|
|
289
|
-
syncSelectionToUrl()
|
|
290
|
-
onGenerate()
|
|
291
|
-
return
|
|
292
|
-
} else {
|
|
293
|
-
store.state.config.initial_page_policy = data.initial_page_policy
|
|
294
|
-
renderBasedOnInitialPolicy()
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// default route options placeholder
|
|
298
|
-
} catch (e) {
|
|
299
|
-
console.error("Initial load failed", e)
|
|
300
|
-
} finally {
|
|
301
|
-
store.state.initializing = false
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
async function renderBasedOnInitialPolicy() {
|
|
306
|
-
switch (store.state.config.initial_page_policy) {
|
|
307
|
-
case "full":
|
|
308
|
-
onGenerate()
|
|
309
|
-
return
|
|
310
|
-
case "empty":
|
|
311
|
-
return
|
|
312
|
-
case "first":
|
|
313
|
-
store.state.leftPanel.tag =
|
|
314
|
-
store.state.leftPanel.tags.length > 0 ? store.state.leftPanel.tags[0].name : null
|
|
315
|
-
store.state.leftPanel._tag = store.state.leftPanel.tag
|
|
316
|
-
syncSelectionToUrl()
|
|
317
|
-
onGenerate()
|
|
318
|
-
return
|
|
319
|
-
}
|
|
96
|
+
await store.actions.loadInitial(onGenerate, (cb) =>
|
|
97
|
+
store.actions.renderBasedOnInitialPolicy(cb)
|
|
98
|
+
)
|
|
320
99
|
}
|
|
321
100
|
|
|
322
101
|
async function onGenerate(resetZoom = true) {
|
|
@@ -331,21 +110,9 @@ const app = createApp({
|
|
|
331
110
|
}
|
|
332
111
|
|
|
333
112
|
async function renderVoyager(resetZoom = true) {
|
|
334
|
-
const activeSchema = store.state.search.mode ? store.state.search.schemaName : null
|
|
335
|
-
const activeField = store.state.search.mode ? store.state.search.fieldName : null
|
|
336
113
|
store.state.generating = true
|
|
337
114
|
try {
|
|
338
|
-
const payload =
|
|
339
|
-
tags: store.state.leftPanel.tag ? [store.state.leftPanel.tag] : null,
|
|
340
|
-
schema_name: activeSchema || null,
|
|
341
|
-
schema_field: activeField || null,
|
|
342
|
-
route_name: store.state.leftPanel.routeId || null,
|
|
343
|
-
show_fields: store.state.filter.showFields,
|
|
344
|
-
brief: store.state.filter.brief,
|
|
345
|
-
hide_primitive_route: store.state.filter.hidePrimitiveRoute,
|
|
346
|
-
show_module: store.state.filter.showModule,
|
|
347
|
-
show_pydantic_resolve_meta: store.state.modeControl.pydanticResolveMetaEnabled,
|
|
348
|
-
}
|
|
115
|
+
const payload = store.actions.buildVoyagerPayload()
|
|
349
116
|
initGraphUI()
|
|
350
117
|
const res = await fetch("dot", {
|
|
351
118
|
method: "POST",
|
|
@@ -362,37 +129,10 @@ const app = createApp({
|
|
|
362
129
|
}
|
|
363
130
|
}
|
|
364
131
|
|
|
365
|
-
function resetDetailPanels() {
|
|
366
|
-
store.state.rightDrawer.drawer = false
|
|
367
|
-
store.state.routeDetail.show = false
|
|
368
|
-
store.state.schemaDetail.schemaCodeName = ""
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function onReset() {
|
|
372
|
-
store.state.leftPanel.tag = null
|
|
373
|
-
store.state.leftPanel._tag = null
|
|
374
|
-
store.state.leftPanel.routeId = null
|
|
375
|
-
syncSelectionToUrl()
|
|
376
|
-
onGenerate()
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
async function togglePydanticResolveMeta(val) {
|
|
380
|
-
store.state.modeControl.pydanticResolveMetaEnabled = val
|
|
381
|
-
try {
|
|
382
|
-
localStorage.setItem("pydantic_resolve_meta", JSON.stringify(val))
|
|
383
|
-
} catch (e) {
|
|
384
|
-
console.warn("Failed to save pydantic_resolve_meta to localStorage", e)
|
|
385
|
-
}
|
|
386
|
-
onGenerate()
|
|
387
|
-
}
|
|
388
|
-
|
|
389
132
|
async function renderErDiagram(resetZoom = true) {
|
|
390
133
|
initGraphUI()
|
|
391
134
|
erDiagramLoading.value = true
|
|
392
|
-
const payload =
|
|
393
|
-
show_fields: store.state.filter.showFields,
|
|
394
|
-
show_module: store.state.filter.showModule,
|
|
395
|
-
}
|
|
135
|
+
const payload = store.actions.buildErDiagramPayload()
|
|
396
136
|
try {
|
|
397
137
|
const res = await fetch("er-diagram", {
|
|
398
138
|
method: "POST",
|
|
@@ -423,12 +163,14 @@ const app = createApp({
|
|
|
423
163
|
store.state.leftPanel.previousWidth = store.state.leftPanel.width
|
|
424
164
|
}
|
|
425
165
|
store.state.leftPanel.width = 0
|
|
166
|
+
store.actions.syncSelectionToUrl()
|
|
426
167
|
await renderErDiagram()
|
|
427
168
|
} else {
|
|
428
169
|
store.state.search.invisible = false
|
|
429
170
|
|
|
430
171
|
const fallbackWidth = store.state.leftPanel.previousWidth || 300
|
|
431
172
|
store.state.leftPanel.width = fallbackWidth
|
|
173
|
+
store.actions.syncSelectionToUrl()
|
|
432
174
|
await onGenerate()
|
|
433
175
|
}
|
|
434
176
|
}
|
|
@@ -447,7 +189,7 @@ const app = createApp({
|
|
|
447
189
|
|
|
448
190
|
store.state.rightDrawer.drawer = false
|
|
449
191
|
store.state.routeDetail.show = false
|
|
450
|
-
syncSelectionToUrl()
|
|
192
|
+
store.actions.syncSelectionToUrl()
|
|
451
193
|
}
|
|
452
194
|
|
|
453
195
|
function toggleTagNavigatorCollapse() {
|
|
@@ -468,7 +210,7 @@ const app = createApp({
|
|
|
468
210
|
|
|
469
211
|
function selectRoute(routeId) {
|
|
470
212
|
// find belonging tag
|
|
471
|
-
const belongingTag = findTagByRoute(routeId)
|
|
213
|
+
const belongingTag = store.getters.findTagByRoute(routeId)
|
|
472
214
|
if (belongingTag) {
|
|
473
215
|
store.state.leftPanel.tag = belongingTag
|
|
474
216
|
store.state.leftPanel._tag = belongingTag
|
|
@@ -483,45 +225,10 @@ const app = createApp({
|
|
|
483
225
|
store.state.rightDrawer.drawer = false
|
|
484
226
|
store.state.routeDetail.show = false
|
|
485
227
|
store.state.schemaDetail.schemaCodeName = ""
|
|
486
|
-
syncSelectionToUrl()
|
|
487
|
-
onGenerate()
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
function toggleShowModule(val) {
|
|
491
|
-
store.state.filter.showModule = val
|
|
492
|
-
try {
|
|
493
|
-
localStorage.setItem("show_module_cluster", JSON.stringify(val))
|
|
494
|
-
} catch (e) {
|
|
495
|
-
console.warn("Failed to save show_module_cluster to localStorage", e)
|
|
496
|
-
}
|
|
228
|
+
store.actions.syncSelectionToUrl()
|
|
497
229
|
onGenerate()
|
|
498
230
|
}
|
|
499
231
|
|
|
500
|
-
function toggleShowField(field) {
|
|
501
|
-
store.state.filter.showFields = field
|
|
502
|
-
onGenerate(false)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
function toggleBrief(val) {
|
|
506
|
-
store.state.filter.brief = val
|
|
507
|
-
try {
|
|
508
|
-
localStorage.setItem("brief_mode", JSON.stringify(val))
|
|
509
|
-
} catch (e) {
|
|
510
|
-
console.warn("Failed to save brief_mode to localStorage", e)
|
|
511
|
-
}
|
|
512
|
-
onGenerate()
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
function toggleHidePrimitiveRoute(val) {
|
|
516
|
-
store.state.filter.hidePrimitiveRoute = val
|
|
517
|
-
try {
|
|
518
|
-
localStorage.setItem("hide_primitive", JSON.stringify(val))
|
|
519
|
-
} catch (e) {
|
|
520
|
-
console.warn("Failed to save hide_primitive to localStorage", e)
|
|
521
|
-
}
|
|
522
|
-
onGenerate(false)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
232
|
function startDragDrawer(e) {
|
|
526
233
|
const startX = e.clientX
|
|
527
234
|
const startWidth = store.state.rightDrawer.width
|
|
@@ -549,7 +256,7 @@ const app = createApp({
|
|
|
549
256
|
watch(
|
|
550
257
|
() => store.state.graph.schemaMap,
|
|
551
258
|
() => {
|
|
552
|
-
rebuildSchemaOptions()
|
|
259
|
+
store.actions.rebuildSchemaOptions()
|
|
553
260
|
},
|
|
554
261
|
{ deep: false }
|
|
555
262
|
)
|
|
@@ -573,8 +280,8 @@ const app = createApp({
|
|
|
573
280
|
watch(
|
|
574
281
|
() => store.state.search.schemaName,
|
|
575
282
|
(schemaId) => {
|
|
576
|
-
store.state.search.schemaOptions = allSchemaOptions.
|
|
577
|
-
populateFieldOptions(schemaId)
|
|
283
|
+
store.state.search.schemaOptions = store.state.allSchemaOptions.slice()
|
|
284
|
+
store.actions.populateFieldOptions(schemaId)
|
|
578
285
|
if (!schemaId) {
|
|
579
286
|
store.state.search.mode = false
|
|
580
287
|
}
|
|
@@ -591,21 +298,22 @@ const app = createApp({
|
|
|
591
298
|
store,
|
|
592
299
|
onSearch,
|
|
593
300
|
resetSearch,
|
|
594
|
-
filterSearchSchemas,
|
|
595
|
-
onSearchSchemaChange,
|
|
301
|
+
filterSearchSchemas: (val, update) => store.actions.filterSearchSchemas(val, update),
|
|
302
|
+
onSearchSchemaChange: (val) => store.actions.onSearchSchemaChange(val, onSearch),
|
|
596
303
|
toggleTag,
|
|
597
304
|
toggleTagNavigatorCollapse,
|
|
598
|
-
toggleBrief,
|
|
599
|
-
toggleHidePrimitiveRoute,
|
|
305
|
+
toggleBrief: (val) => store.actions.toggleBrief(val, onGenerate),
|
|
306
|
+
toggleHidePrimitiveRoute: (val) => store.actions.toggleHidePrimitiveRoute(val, onGenerate),
|
|
600
307
|
selectRoute,
|
|
601
308
|
onGenerate,
|
|
602
|
-
onReset,
|
|
603
|
-
toggleShowField,
|
|
309
|
+
onReset: () => store.actions.onReset(onGenerate),
|
|
310
|
+
toggleShowField: (field) => store.actions.toggleShowField(field, onGenerate),
|
|
604
311
|
startDragDrawer,
|
|
605
|
-
toggleShowModule,
|
|
312
|
+
toggleShowModule: (val) => store.actions.toggleShowModule(val, onGenerate),
|
|
606
313
|
onModeChange,
|
|
607
314
|
renderErDiagram,
|
|
608
|
-
togglePydanticResolveMeta,
|
|
315
|
+
togglePydanticResolveMeta: (val) => store.actions.togglePydanticResolveMeta(val, onGenerate),
|
|
316
|
+
resetDetailPanels: () => store.actions.resetDetailPanels(),
|
|
609
317
|
}
|
|
610
318
|
},
|
|
611
319
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-voyager
|
|
3
|
-
Version: 0.15.
|
|
3
|
+
Version: 0.15.4
|
|
4
4
|
Summary: Visualize FastAPI application's routing tree and dependencies
|
|
5
5
|
Project-URL: Homepage, https://github.com/allmonday/fastapi-voyager
|
|
6
6
|
Project-URL: Source, https://github.com/allmonday/fastapi-voyager
|
|
@@ -6,10 +6,10 @@ fastapi_voyager/module.py,sha256=h9YR3BpS-CAcJW9WCdVkF4opqwY32w9T67g9GfdLytk,342
|
|
|
6
6
|
fastapi_voyager/pydantic_resolve_util.py,sha256=r4Rq7BtBcFOMV7O2Ab9TwLyRNL1yNDiQlGUVybf-sXs,3524
|
|
7
7
|
fastapi_voyager/render.py,sha256=5tTuvvCCUwFCq3WJGT1rfTSW41mSDoVyJwMyQGrmhiQ,17271
|
|
8
8
|
fastapi_voyager/render_style.py,sha256=mPOuChEl71-3agCbPwkMt2sFmax2AEKDI6dK90eFPRc,2552
|
|
9
|
-
fastapi_voyager/server.py,sha256=
|
|
9
|
+
fastapi_voyager/server.py,sha256=E0gGU7D1pBvnV7gFLBUvnkwtiBFbU1PbxEXHHSIA1oA,9115
|
|
10
10
|
fastapi_voyager/type.py,sha256=zluWvh5vpnjXJ9aAmyNJTSmXZPjAHCvgRT5oQRAjHrg,2104
|
|
11
11
|
fastapi_voyager/type_helper.py,sha256=FmfrZAI3Z4uDdh3sH_kH7UGoY6yNVPapneSN86qY_wo,10209
|
|
12
|
-
fastapi_voyager/version.py,sha256=
|
|
12
|
+
fastapi_voyager/version.py,sha256=AgJGrnhFIPGdn_tW4I4oEPuyTOgMXEkK0I5qVj2kag0,49
|
|
13
13
|
fastapi_voyager/voyager.py,sha256=4vonmL-xt54C5San-DRBq4mjoV8Q96eoWRy68MJ1IJw,14169
|
|
14
14
|
fastapi_voyager/templates/dot/cluster.j2,sha256=I2z9KkfCzmAtqXe0gXBnxnOfBXUSpdlATs3uf-O8_B8,307
|
|
15
15
|
fastapi_voyager/templates/dot/cluster_container.j2,sha256=2tH1mOJvPoVKE_aHVMR3t06TfH_dYa9OeH6DBqSHt_A,204
|
|
@@ -24,14 +24,14 @@ fastapi_voyager/templates/html/pydantic_meta.j2,sha256=_tsSqjucs_QrAlPIVRy9u6I2-
|
|
|
24
24
|
fastapi_voyager/templates/html/schema_field_row.j2,sha256=KfKexHO_QJV-OIJS0eiY_7fqA8031fWpD2g2wTv4BuE,111
|
|
25
25
|
fastapi_voyager/templates/html/schema_header.j2,sha256=9WpuHLy3Zbv5GHG08qqaj5Xf-gaR-79ErBYuANZp7iA,179
|
|
26
26
|
fastapi_voyager/templates/html/schema_table.j2,sha256=rzphiGk1il7uv4Gr2p_HLPHqyLZk63vLrGAmIduTdSE,117
|
|
27
|
-
fastapi_voyager/web/graph-ui.js,sha256=
|
|
27
|
+
fastapi_voyager/web/graph-ui.js,sha256=4gEkXTgbA6CouD4IDMW5yKYfJTxHN2vL9G0CAr6w4qA,7662
|
|
28
28
|
fastapi_voyager/web/graphviz.svg.css,sha256=K218ov_mdSe3ga4KwhiBB92ynVvm5zaAk9_D9a3d8hE,1546
|
|
29
29
|
fastapi_voyager/web/graphviz.svg.js,sha256=deI815RgxpZ3_MpELeV-TBYy2MVuUvZtQOHfS3aeXHY,18203
|
|
30
|
-
fastapi_voyager/web/index.html,sha256=
|
|
30
|
+
fastapi_voyager/web/index.html,sha256=wM9vJ_UfHR8p98F6SEMCKKjJcBEl0EyosWuPqVZYXvA,23496
|
|
31
31
|
fastapi_voyager/web/quasar.min.css,sha256=F5jQe7X2XT54VlvAaa2V3GsBFdVD-vxDZeaPLf6U9CU,203145
|
|
32
32
|
fastapi_voyager/web/quasar.min.js,sha256=h0ftyPMW_CRiyzeVfQqiup0vrVt4_QWojpqmpnpn07E,502974
|
|
33
|
-
fastapi_voyager/web/store.js,sha256=
|
|
34
|
-
fastapi_voyager/web/vue-main.js,sha256=
|
|
33
|
+
fastapi_voyager/web/store.js,sha256=7anY7HGF7aEx1xpXWz58Ca5WbnYCRnzdj7kqNjcaT_M,15103
|
|
34
|
+
fastapi_voyager/web/vue-main.js,sha256=4lJi6ADrcaOzjXcQePkp7CyiCkAnvhnO-nkF3E6G3s4,10650
|
|
35
35
|
fastapi_voyager/web/component/demo.js,sha256=sAklFGhKGmMy9-ofgOw2oPIidAoIOgHu6yvV51L_MAA,350
|
|
36
36
|
fastapi_voyager/web/component/render-graph.js,sha256=9wnO70n3eyPKTpa744idgs5PSwgvzbfv4InZ68eEOKs,2454
|
|
37
37
|
fastapi_voyager/web/component/route-code-display.js,sha256=a823nBz3EEjutW2pfi73rcF3hodCBmgYNmuZi94sXE4,3615
|
|
@@ -43,8 +43,8 @@ fastapi_voyager/web/icon/favicon-16x16.png,sha256=JC07jEzfIYxBIoQn_FHXvyHuxESdhW
|
|
|
43
43
|
fastapi_voyager/web/icon/favicon-32x32.png,sha256=C7v1h58cfWOsiLp9yOIZtlx-dLasBcq3NqpHVGRmpt4,1859
|
|
44
44
|
fastapi_voyager/web/icon/favicon.ico,sha256=tZolYIXkkBcFiYl1A8ksaXN2VjGamzcSdes838dLvNc,15406
|
|
45
45
|
fastapi_voyager/web/icon/site.webmanifest,sha256=GRozZ5suTykYcPMap1QhjrAB8PLW0mbT_phhzw_utvQ,316
|
|
46
|
-
fastapi_voyager-0.15.
|
|
47
|
-
fastapi_voyager-0.15.
|
|
48
|
-
fastapi_voyager-0.15.
|
|
49
|
-
fastapi_voyager-0.15.
|
|
50
|
-
fastapi_voyager-0.15.
|
|
46
|
+
fastapi_voyager-0.15.4.dist-info/METADATA,sha256=6BftnkJpr0w8cKiAubLyJ6ZiOeGmvlkOwE9jZLZ5UMM,8513
|
|
47
|
+
fastapi_voyager-0.15.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
48
|
+
fastapi_voyager-0.15.4.dist-info/entry_points.txt,sha256=pEIKoUnIDXEtdMBq8EmXm70m16vELIu1VPz9-TBUFWM,53
|
|
49
|
+
fastapi_voyager-0.15.4.dist-info/licenses/LICENSE,sha256=lNVRR3y_bFVoFKuK2JM8N4sFaj3m-7j29kvL3olFi5Y,1067
|
|
50
|
+
fastapi_voyager-0.15.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|