fastapi-voyager 0.7.2__tar.gz → 0.7.4__tar.gz

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 (49) hide show
  1. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/PKG-INFO +6 -1
  2. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/README.md +5 -0
  3. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/render.py +38 -28
  4. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/version.py +1 -1
  5. fastapi_voyager-0.7.4/src/fastapi_voyager/web/index.html +383 -0
  6. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/vue-main.js +52 -1
  7. fastapi_voyager-0.7.2/src/fastapi_voyager/web/index.html +0 -288
  8. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/.gitignore +0 -0
  9. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/.python-version +0 -0
  10. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/LICENSE +0 -0
  11. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/pyproject.toml +0 -0
  12. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/__init__.py +0 -0
  13. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/cli.py +0 -0
  14. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/filter.py +0 -0
  15. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/module.py +0 -0
  16. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/server.py +0 -0
  17. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/type.py +0 -0
  18. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/type_helper.py +0 -0
  19. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/voyager.py +0 -0
  20. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/component/render-graph.js +0 -0
  21. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/component/route-code-display.js +0 -0
  22. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/component/schema-code-display.js +0 -0
  23. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/component/schema-field-filter.js +0 -0
  24. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/graph-ui.js +0 -0
  25. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/graphviz.svg.css +0 -0
  26. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/graphviz.svg.js +0 -0
  27. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/android-chrome-192x192.png +0 -0
  28. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/android-chrome-512x512.png +0 -0
  29. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/apple-touch-icon.png +0 -0
  30. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/favicon-16x16.png +0 -0
  31. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/favicon-32x32.png +0 -0
  32. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/favicon.ico +0 -0
  33. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/icon/site.webmanifest +0 -0
  34. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/quasar.min.css +0 -0
  35. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/src/fastapi_voyager/web/quasar.min.js +0 -0
  36. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/__init__.py +0 -0
  37. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/demo.py +0 -0
  38. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/demo_anno.py +0 -0
  39. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/programatic.py +0 -0
  40. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/service/__init__.py +0 -0
  41. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/service/schema.py +0 -0
  42. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/test_analysis.py +0 -0
  43. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/test_filter.py +0 -0
  44. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/test_generic.py +0 -0
  45. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/test_import.py +0 -0
  46. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/test_module.py +0 -0
  47. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/tests/test_type_helper.py +0 -0
  48. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/uv.lock +0 -0
  49. {fastapi_voyager-0.7.2 → fastapi_voyager-0.7.4}/voyager.jpg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-voyager
3
- Version: 0.7.2
3
+ Version: 0.7.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
@@ -204,6 +204,11 @@ TODO: ...
204
204
  ## Changelog
205
205
 
206
206
  - 0.7:
207
+ - 0.7.4
208
+ - optimize tag/route, move to left.
209
+ - fresh on change, no need to click generate any more.
210
+ - 0.7.3
211
+ - fix `module_color` failure
207
212
  - 0.7.2
208
213
  - keep links inside filtered nodes.
209
214
  - 0.7.1
@@ -177,6 +177,11 @@ TODO: ...
177
177
  ## Changelog
178
178
 
179
179
  - 0.7:
180
+ - 0.7.4
181
+ - optimize tag/route, move to left.
182
+ - fresh on change, no need to click generate any more.
183
+ - 0.7.3
184
+ - fix `module_color` failure
180
185
  - 0.7.2
181
186
  - keep links inside filtered nodes.
182
187
  - 0.7.1
@@ -1,3 +1,4 @@
1
+ from typing import Optional
1
2
  from fastapi_voyager.type import SchemaNode, ModuleNode, Link, Tag, Route, FieldType, PK, ModuleRoute
2
3
  from fastapi_voyager.module import build_module_schema_tree, build_module_route_tree
3
4
 
@@ -61,33 +62,44 @@ class Renderer:
61
62
  else:
62
63
  raise ValueError(f'Unknown link type: {link.type}')
63
64
 
64
- def render_module_schema(self, mod: ModuleNode) -> str:
65
- color = self.module_color.get(mod.fullname)
66
- inner_nodes = [
67
- f'''
68
- "{node.id}" [
69
- label = {self.render_schema_label(node)}
70
- shape = "plain"
71
- margin="0.5,0.1"
72
- ];''' for node in mod.schema_nodes
73
- ]
74
- inner_nodes_str = '\n'.join(inner_nodes)
75
- child_str = '\n'.join(self.render_module_schema(m) for m in mod.modules)
76
- return f'''
77
- subgraph cluster_module_{mod.fullname.replace('.', '_')} {{
78
- tooltip="{mod.fullname}"
79
- color = "#666"
80
- style="rounded"
81
- label = " {mod.name}"
82
- labeljust = "l"
83
- {(f'pencolor = "{color}"' if color else 'pencolor="#ccc"')}
84
- {(f'penwidth = 3' if color else 'penwidth=""')}
85
- {inner_nodes_str}
86
- {child_str}
87
- }}'''
65
+ def render_module_schema_wrapper(self, mods: list[ModuleNode]) -> str:
66
+ module_color_flag = set(self.module_color.keys())
67
+ print(module_color_flag)
68
+
69
+ def render_module_schema(mod: ModuleNode) -> str:
70
+ color: Optional[str] = None
71
+
72
+ for k in module_color_flag:
73
+ if mod.fullname.startswith(k):
74
+ module_color_flag.remove(k)
75
+ color = self.module_color[k]
76
+ break
77
+
78
+ inner_nodes = [
79
+ f'''
80
+ "{node.id}" [
81
+ label = {self.render_schema_label(node)}
82
+ shape = "plain"
83
+ margin="0.5,0.1"
84
+ ];''' for node in mod.schema_nodes
85
+ ]
86
+ inner_nodes_str = '\n'.join(inner_nodes)
87
+ child_str = '\n'.join(render_module_schema(m) for m in mod.modules)
88
+ return f'''
89
+ subgraph cluster_module_{mod.fullname.replace('.', '_')} {{
90
+ tooltip="{mod.fullname}"
91
+ color = "#666"
92
+ style="rounded"
93
+ label = " {mod.name}"
94
+ labeljust = "l"
95
+ {(f'pencolor = "{color}"' if color else 'pencolor="#ccc"')}
96
+ {(f'penwidth = 3' if color else 'penwidth=""')}
97
+ {inner_nodes_str}
98
+ {child_str}
99
+ }}'''
100
+ return '\n'.join(render_module_schema(m) for m in mods)
88
101
 
89
102
  def render_module_route(self, mod: ModuleRoute) -> str:
90
- color = self.module_color.get(mod.fullname)
91
103
  # Inner route nodes, same style as flat route_str
92
104
  inner_nodes = [
93
105
  f'''
@@ -106,8 +118,6 @@ class Renderer:
106
118
  style="rounded"
107
119
  label = " {mod.name}"
108
120
  labeljust = "l"
109
- {(f'pencolor = "{color}"' if color else 'pencolor="#ccc"')}
110
- {(f'penwidth = 3' if color else 'penwidth=""')}
111
121
  {inner_nodes_str}
112
122
  {child_str}
113
123
  }}'''
@@ -126,7 +136,7 @@ class Renderer:
126
136
  ])
127
137
 
128
138
 
129
- module_schemas_str = '\n'.join(self.render_module_schema(m) for m in module_schemas)
139
+ module_schemas_str = self.render_module_schema_wrapper(module_schemas)
130
140
  module_routes_str = '\n'.join(self.render_module_route(m) for m in module_routes)
131
141
  link_str = '\n'.join(self.render_link(link) for link in links)
132
142
 
@@ -1,2 +1,2 @@
1
1
  __all__ = ["__version__"]
2
- __version__ = "0.7.2"
2
+ __version__ = "0.7.4"
@@ -0,0 +1,383 @@
1
+ <html>
2
+ <head>
3
+ <title>FastAPI Voyager</title>
4
+ <meta name="theme-color" content="#ffffff" />
5
+ <link rel="stylesheet" href="fastapi-voyager-static/graphviz.svg.css" />
6
+ <link rel="stylesheet" href="fastapi-voyager-static/quasar.min.css" />
7
+ <!-- App Icons / Favicons -->
8
+ <link
9
+ rel="apple-touch-icon"
10
+ sizes="180x180"
11
+ href="fastapi-voyager-static/icon/apple-touch-icon.png"
12
+ />
13
+ <link
14
+ rel="icon"
15
+ type="image/png"
16
+ sizes="32x32"
17
+ href="fastapi-voyager-static/icon/favicon-32x32.png"
18
+ />
19
+ <link
20
+ rel="icon"
21
+ type="image/png"
22
+ sizes="16x16"
23
+ href="fastapi-voyager-static/icon/favicon-16x16.png"
24
+ />
25
+ <link
26
+ rel="icon"
27
+ href="fastapi-voyager-static/icon/favicon.ico"
28
+ sizes="any"
29
+ />
30
+ <link rel="manifest" href="fastapi-voyager-static/icon/site.webmanifest" />
31
+ <link
32
+ href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
33
+ rel="stylesheet"
34
+ type="text/css"
35
+ />
36
+ </head>
37
+
38
+ <style>
39
+ html,
40
+ body {
41
+ height: 100%;
42
+ margin: 0;
43
+ }
44
+ body {
45
+ display: flex;
46
+ flex-direction: column;
47
+ }
48
+ /* Quasar select menu max-height for scroll */
49
+ .select-popup {
50
+ max-height: 400px;
51
+ overflow-y: auto;
52
+ overflow-x: hidden;
53
+ }
54
+ #graph {
55
+ flex: 1 1 auto;
56
+ overflow: auto;
57
+ }
58
+ </style>
59
+ <body>
60
+ <div id="q-app" class="column" style="height: 100%">
61
+ <div
62
+ v-if="state.initializing"
63
+ style="
64
+ position: fixed;
65
+ inset: 0;
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ z-index: 2000;
70
+ background: rgba(255, 255, 255, 0.85);
71
+ font-size: 18px;
72
+ font-family: Roboto, sans-serif;
73
+ color: #444;
74
+ "
75
+ >
76
+ <div style="text-align: center">
77
+ <div class="q-mb-sm">Initializing...</div>
78
+ <q-spinner color="primary" size="32px" />
79
+ </div>
80
+ </div>
81
+ <div
82
+ style="
83
+ padding: 8px;
84
+ background-color: #fff;
85
+ border-bottom: 1px solid #ccc;
86
+ color: #211d1d;
87
+ flex: 0 0 auto;
88
+ "
89
+ >
90
+ <div class="row items-center q-col-gutter-md">
91
+ <div class="col-auto">
92
+ <div class="column">
93
+ <q-option-group
94
+ v-model="state.showFields"
95
+ :options="state.fieldOptions"
96
+ color="primary"
97
+ type="radio"
98
+ inline
99
+ dense
100
+ />
101
+ </div>
102
+ </div>
103
+
104
+ <div class="col-auto">
105
+ <q-toggle v-model="state.brief" label="Brief" dense />
106
+ </div>
107
+
108
+ <!-- <div class="col-auto">
109
+ <q-btn-dropdown
110
+ class="q-ml-md"
111
+ split
112
+ :loading="state.generating"
113
+ @click="onGenerate"
114
+ label="Generate"
115
+ outline
116
+ >
117
+ <q-list>
118
+ <q-item clickable v-close-popup @click="onDumpData">
119
+ <q-item-section>Dump data</q-item-section>
120
+ </q-item>
121
+ <q-item clickable v-close-popup @click="openImportDialog">
122
+ <q-item-section>Import data</q-item-section>
123
+ </q-item>
124
+ </q-list>
125
+ </q-btn-dropdown>
126
+ </div> -->
127
+
128
+ <div class="col-auto q-ml-auto">
129
+ <q-btn outline @click="onReset" title="may be very slow" label="Show All" />
130
+ </div>
131
+ <div class="col-auto">
132
+ <q-btn outline icon="search" label="Search" @click="showDialog()" />
133
+ </div>
134
+ <div class="col-auto">
135
+ <q-btn
136
+ dense
137
+ round
138
+ flat
139
+ icon="help_outline"
140
+ aria-label="Help"
141
+ class="q-mr-md"
142
+ >
143
+ <q-tooltip
144
+ anchor="bottom middle"
145
+ self="top middle"
146
+ :offset="[0,8]"
147
+ >
148
+ <div
149
+ class="column items-start text-left"
150
+ style="line-height: 1.4; font-size: 12px"
151
+ >
152
+ <ul>
153
+ <li>scroll to zoom in/out</li>
154
+ <li>
155
+ click node to check highlight related nodes on the chart,
156
+ esc to unselect
157
+ </li>
158
+ <li>
159
+ shift + click to see schema's dependencies without
160
+ unrelated nodes
161
+ </li>
162
+ <li>alt + click to see schema details</li>
163
+ </ul>
164
+ </div>
165
+ </q-tooltip>
166
+ </q-btn>
167
+ </div>
168
+ </div>
169
+ </div>
170
+ <div class="row no-wrap" style="flex: 1 1 auto; min-height: 0">
171
+ <div
172
+ class="column no-wrap"
173
+ :style="{
174
+ minWidth: '300px',
175
+ width: '300px',
176
+ borderRight: '1px solid #e0e0e0',
177
+ backgroundColor: '#fff',
178
+ minHeight: 0,
179
+ }"
180
+ >
181
+ <q-scroll-area class="fit">
182
+ <q-list dense separator>
183
+ <q-expansion-item
184
+ v-for="tag in state.rawTags"
185
+ :key="tag.name"
186
+ expand-separator
187
+ switch-toggle-side
188
+ :model-value="state.tag === tag.name"
189
+ @update:model-value="(val) => toggleTag(tag.name, val)"
190
+ :header-class="state.tag === tag.name ? 'bg-primary text-white text-weight-medium' : 'text-weight-medium'"
191
+ content-class="q-pa-none"
192
+ >
193
+ <template #header>
194
+ <div class="row items-center no-wrap" style="width: 100%">
195
+ <div class="col text-body2">{{ tag.name }}</div>
196
+ </div>
197
+ </template>
198
+
199
+ <q-list separator>
200
+ <q-item
201
+ v-for="route in (tag.routes || [])"
202
+ :key="route.id"
203
+ clickable
204
+ v-ripple
205
+ :active="state.routeId === route.id"
206
+ active-class=""
207
+ @click="selectRoute(route.id)"
208
+ >
209
+ <q-item-section>{{ route.name }}</q-item-section>
210
+ </q-item>
211
+ <q-item v-if="!tag.routes || tag.routes.length === 0" dense>
212
+ <q-item-section class="text-grey-6">No routes</q-item-section>
213
+ </q-item>
214
+ </q-list>
215
+ </q-expansion-item>
216
+ </q-list>
217
+ </q-scroll-area>
218
+ </div>
219
+ <div id="graph"></div>
220
+ </div>
221
+
222
+ <!-- Detail Dialog -->
223
+ <q-dialog v-model="showDetail" :persistent="true" :maximized="true">
224
+ <detail-dialog
225
+ :schema-name="schemaName"
226
+ :show-fields="state.showFields"
227
+ :model-value="showDetail"
228
+ @close="closeDetail"
229
+ />
230
+ </q-dialog>
231
+
232
+ <!-- Schema Field Filter Dialog -->
233
+ <q-dialog
234
+ v-model="showSchemaFieldFilter"
235
+ :persistent="true"
236
+ :maximized="true"
237
+ >
238
+ <schema-field-filter
239
+ :schemas="state.rawSchemasFull"
240
+ :schema-name="schemaFieldFilterSchema"
241
+ @close="showSchemaFieldFilter = false"
242
+ />
243
+ </q-dialog>
244
+
245
+ <!-- Schema Source Code Dialog (Ctrl + Click) -->
246
+ <q-dialog
247
+ v-model="showSchemaCode"
248
+ :maximized="true"
249
+ :persistent="false"
250
+ position="left"
251
+ :seamless="false"
252
+ >
253
+ <schema-code-display
254
+ :schema-name="schemaCodeName"
255
+ :model-value="showSchemaCode"
256
+ :schemas="state.rawSchemasFull"
257
+ @close="showSchemaCode = false"
258
+ />
259
+ </q-dialog>
260
+
261
+ <!-- Route Source Code Dialog (Alt + Click on route) -->
262
+ <q-dialog
263
+ v-model="showRouteCode"
264
+ :maximized="true"
265
+ :persistent="false"
266
+ position="right"
267
+ :seamless="false"
268
+ >
269
+ <route-code-display
270
+ :route-id="routeCodeId"
271
+ :model-value="showRouteCode"
272
+ :routes="state.routeItems"
273
+ @close="showRouteCode = false"
274
+ />
275
+ </q-dialog>
276
+
277
+ <!-- Dump Core Data Dialog -->
278
+ <q-dialog v-model="showDumpDialog" :maximized="true" :persistent="false">
279
+ <div style="height: 100%; position: relative; background: #fff">
280
+ <q-btn
281
+ flat
282
+ dense
283
+ round
284
+ icon="content_copy"
285
+ aria-label="Copy"
286
+ @click="copyDumpJson"
287
+ style="
288
+ position: absolute;
289
+ top: 6px;
290
+ right: 62px;
291
+ z-index: 11;
292
+ background: rgba(255, 255, 255, 0.85);
293
+ "
294
+ ></q-btn>
295
+ <q-btn
296
+ flat
297
+ dense
298
+ round
299
+ icon="close"
300
+ aria-label="Close"
301
+ @click="showDumpDialog = false"
302
+ style="
303
+ position: absolute;
304
+ top: 6px;
305
+ right: 6px;
306
+ z-index: 11;
307
+ background: rgba(255, 255, 255, 0.85);
308
+ "
309
+ ></q-btn>
310
+ <div>
311
+ <pre
312
+ style="padding: 20px; overflow: auto"
313
+ ><code>{{ dumpJson }}</code></pre>
314
+ </div>
315
+ </div>
316
+ </q-dialog>
317
+
318
+ <!-- Import Core Data Dialog -->
319
+ <q-dialog v-model="showImportDialog" :persistent="true">
320
+ <q-card style="min-width: 70vw; max-width: 90vw">
321
+ <q-card-section class="text-h6">Import core data JSON</q-card-section>
322
+ <q-card-section>
323
+ <q-btn color="primary" label="Render" @click="onImportConfirm" />
324
+ </q-card-section>
325
+ <q-card-section>
326
+ <q-input
327
+ v-model="importJsonText"
328
+ type="textarea"
329
+ autogrow
330
+ filled
331
+ label="Paste JSON here"
332
+ />
333
+ </q-card-section>
334
+ </q-card>
335
+ </q-dialog>
336
+
337
+ <!-- Render Graph Dialog (from imported core data) -->
338
+ <q-dialog v-model="showRenderGraph" :maximized="true" :persistent="false">
339
+ <render-graph
340
+ :core-data="renderCoreData"
341
+ @close="showRenderGraph = false"
342
+ />
343
+ </q-dialog>
344
+ </div>
345
+
346
+ <script
347
+ type="text/javascript"
348
+ src="https://code.jquery.com/jquery-2.1.3.min.js"
349
+ ></script>
350
+ <script
351
+ src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"
352
+ integrity="sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A=="
353
+ crossorigin="anonymous"
354
+ referrerpolicy="no-referrer"
355
+ ></script>
356
+ <script src="https://unpkg.com/@hpcc-js/wasm@2.20.0/dist/graphviz.umd.js"></script>
357
+ <script src="https://unpkg.com/d3-graphviz@5.6.0/build/d3-graphviz.js"></script>
358
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
359
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-color/2.1.2/jquery.color.min.js"></script>
360
+
361
+ <!-- Add the following at the end of your body tag -->
362
+ <script
363
+ src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.22/vue.global.prod.min.js"
364
+ integrity="sha512-Y9sKU0AwzWRxKSLd2i35LuDpUdHY/E9tJrKG0mxw0qYQ75VVgGYazIUQPwKhFK9vGO3jIgAtxLiSq8GQ7PDfUg=="
365
+ crossorigin="anonymous"
366
+ referrerpolicy="no-referrer"
367
+ ></script>
368
+ <script src="fastapi-voyager-static/quasar.min.js"></script>
369
+ <script src="fastapi-voyager-static/graphviz.svg.js"></script>
370
+ <!-- highlight.js minimal ES module load (python only) -->
371
+ <link
372
+ rel="stylesheet"
373
+ href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css"
374
+ />
375
+ <script type="module">
376
+ import hljs from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/highlight.min.js";
377
+ import python from "https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/languages/python.min.js";
378
+ hljs.registerLanguage("python", python);
379
+ window.hljs = hljs;
380
+ </script>
381
+ <script type="module" src="fastapi-voyager-static/vue-main.js"></script>
382
+ </body>
383
+ </html>
@@ -51,6 +51,8 @@ const app = createApp({
51
51
  showDetail.value = false;
52
52
  }
53
53
 
54
+ const skipNextRouteGenerate = ref(false);
55
+
54
56
  function applyRoutesForTag(tagName) {
55
57
  const tag = state.rawTags.find((t) => t.name === tagName);
56
58
  state.routeOptions = [];
@@ -59,6 +61,7 @@ const app = createApp({
59
61
  ...tag.routes.map((r) => ({ label: r.name, value: r.id }))
60
62
  );
61
63
  }
64
+ skipNextRouteGenerate.value = true;
62
65
  state.routeId = "";
63
66
  }
64
67
 
@@ -242,7 +245,19 @@ const app = createApp({
242
245
  state.schemaFullname = null;
243
246
  state.showFields = "object";
244
247
  state.brief = false;
245
- await loadInitial();
248
+ onGenerate()
249
+ // await loadInitial();
250
+ }
251
+
252
+ function toggleTag(tagName, expanded = null) {
253
+ if (expanded === true) {
254
+ state.tag = tagName;
255
+ return;
256
+ }
257
+ }
258
+
259
+ function selectRoute(routeId) {
260
+ state.routeId = state.routeId === routeId ? "" : routeId;
246
261
  }
247
262
 
248
263
  // react to tag changes to rebuild routes
@@ -250,6 +265,40 @@ const app = createApp({
250
265
  () => state.tag,
251
266
  (val) => {
252
267
  applyRoutesForTag(val);
268
+ if (!state.initializing) {
269
+ onGenerate();
270
+ }
271
+ }
272
+ );
273
+
274
+ watch(
275
+ () => state.routeId,
276
+ () => {
277
+ if (skipNextRouteGenerate.value) {
278
+ skipNextRouteGenerate.value = false;
279
+ return;
280
+ }
281
+ if (!state.initializing) {
282
+ onGenerate();
283
+ }
284
+ }
285
+ );
286
+
287
+ watch(
288
+ () => state.showFields,
289
+ () => {
290
+ if (!state.initializing) {
291
+ onGenerate();
292
+ }
293
+ }
294
+ )
295
+
296
+ watch(
297
+ () => state.brief,
298
+ () => {
299
+ if (!state.initializing) {
300
+ onGenerate();
301
+ }
253
302
  }
254
303
  );
255
304
 
@@ -259,6 +308,8 @@ const app = createApp({
259
308
 
260
309
  return {
261
310
  state,
311
+ toggleTag,
312
+ selectRoute,
262
313
  onFilterTags,
263
314
  onFilterSchemas,
264
315
  onGenerate,
@@ -1,288 +0,0 @@
1
- <html>
2
- <head>
3
- <title>FastAPI Voyager</title>
4
- <meta name="theme-color" content="#ffffff" />
5
- <link rel="stylesheet" href="fastapi-voyager-static/graphviz.svg.css" />
6
- <link rel="stylesheet" href="fastapi-voyager-static/quasar.min.css" />
7
- <!-- App Icons / Favicons -->
8
- <link rel="apple-touch-icon" sizes="180x180" href="fastapi-voyager-static/icon/apple-touch-icon.png" />
9
- <link rel="icon" type="image/png" sizes="32x32" href="fastapi-voyager-static/icon/favicon-32x32.png" />
10
- <link rel="icon" type="image/png" sizes="16x16" href="fastapi-voyager-static/icon/favicon-16x16.png" />
11
- <link rel="icon" href="fastapi-voyager-static/icon/favicon.ico" sizes="any" />
12
- <link rel="manifest" href="fastapi-voyager-static/icon/site.webmanifest" />
13
- <link
14
- href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons"
15
- rel="stylesheet"
16
- type="text/css"
17
- />
18
- </head>
19
-
20
- <style>
21
- html,
22
- body {
23
- height: 100%;
24
- margin: 0;
25
- }
26
- body {
27
- display: flex;
28
- flex-direction: column;
29
- }
30
- /* Quasar select menu max-height for scroll */
31
- .select-popup {
32
- max-height: 400px;
33
- overflow-y: auto;
34
- overflow-x: hidden;
35
- }
36
- #graph {
37
- flex: 1 1 auto;
38
- overflow: auto;
39
- }
40
- </style>
41
- <body>
42
- <div id="q-app" class="column" style="height: 100%">
43
- <div v-if="state.initializing" style="position:fixed; inset:0; display:flex; align-items:center; justify-content:center; z-index:2000; background:rgba(255,255,255,0.85); font-size:18px; font-family:Roboto, sans-serif; color:#444;">
44
- <div style="text-align:center;">
45
- <div class="q-mb-sm">Initializing...</div>
46
- <q-spinner color="primary" size="32px" />
47
- </div>
48
- </div>
49
- <div
50
- style="
51
- padding-top: 8px;
52
- padding-left: 8px;
53
- padding-bottom: 8px;
54
- position: absolute;
55
- width: 100%;
56
- top: 0;
57
- background-color: #fff;
58
- z-index: 1;
59
- border-bottom: 1px solid #ccc;
60
- color: #211d1d;
61
- "
62
- >
63
- <div class="row items-center q-col-gutter-md">
64
- <div class="col-auto">
65
- <q-select
66
- v-model="state.tag"
67
- :options="state.tagOptions"
68
- use-input
69
- input-debounce="0"
70
- behavior="menu"
71
- dense
72
- outlined
73
- style="min-width: 320px"
74
- popup-content-class="select-popup"
75
- clearable
76
- label="Tags"
77
- @filter="onFilterTags"
78
- />
79
- </div>
80
-
81
- <div class="col-auto">
82
- <q-select
83
- v-model="state.routeId"
84
- :options="state.routeOptions"
85
- option-label="label"
86
- option-value="value"
87
- emit-value
88
- map-options
89
- dense
90
- outlined
91
- style="min-width: 320px"
92
- popup-content-class="select-popup"
93
- clearable
94
- placeholder="All routes"
95
- label="Routes"
96
- />
97
- </div>
98
-
99
- <div class="col-auto">
100
- <div class="column">
101
- <q-option-group
102
- v-model="state.showFields"
103
- :options="state.fieldOptions"
104
- color="primary"
105
- type="radio"
106
- inline
107
- dense
108
- />
109
- </div>
110
- </div>
111
-
112
- <div class="col-auto">
113
- <q-checkbox
114
- v-model="state.brief"
115
- label="Brief"
116
- dense
117
- />
118
- </div>
119
-
120
- <div class="col-auto">
121
- <q-btn-dropdown
122
- class="q-ml-md"
123
- split
124
- :loading="state.generating"
125
- @click="onGenerate"
126
- label="Generate"
127
- outline
128
- >
129
- <q-list>
130
- <q-item clickable v-close-popup @click="onDumpData">
131
- <q-item-section>Dump data</q-item-section>
132
- </q-item>
133
- <q-item clickable v-close-popup @click="openImportDialog">
134
- <q-item-section>Import data</q-item-section>
135
- </q-item>
136
- </q-list>
137
- </q-btn-dropdown>
138
- </div>
139
- <div class="col-auto">
140
- <q-btn flat @click="onReset" label="Reset" />
141
- </div>
142
- <div class="col-auto q-ml-auto">
143
- <q-btn
144
- outline
145
- icon="search"
146
- label="Search"
147
- @click="showDialog()"
148
- />
149
- </div>
150
- <div class="col-auto">
151
- <q-btn
152
- dense
153
- round
154
- flat
155
- icon="help_outline"
156
- aria-label="Help"
157
- class="q-mr-md"
158
- >
159
- <q-tooltip
160
- anchor="bottom middle"
161
- self="top middle"
162
- :offset="[0,8]"
163
- >
164
- <div
165
- class="column items-start text-left"
166
- style="line-height: 1.4; font-size: 12px"
167
- >
168
- <ul>
169
- <li>scroll to zoom in/out</li>
170
- <li>
171
- click node to check highlight related nodes on the chart,
172
- esc to unselect
173
- </li>
174
- <li>
175
- shift + click to see schema's dependencies without
176
- unrelated nodes
177
- </li>
178
- <li>alt + click to see schema details</li>
179
- </ul>
180
- </div>
181
- </q-tooltip>
182
- </q-btn>
183
- </div>
184
- </div>
185
- </div>
186
- <!-- Detail Dialog -->
187
- <q-dialog v-model="showDetail" :persistent="true" :maximized="true">
188
- <detail-dialog
189
- :schema-name="schemaName"
190
- :show-fields="state.showFields"
191
- :model-value="showDetail"
192
- @close="closeDetail"
193
- />
194
- </q-dialog>
195
-
196
- <!-- Schema Field Filter Dialog -->
197
- <q-dialog
198
- v-model="showSchemaFieldFilter"
199
- :persistent="true"
200
- :maximized="true"
201
- >
202
- <schema-field-filter
203
- :schemas="state.rawSchemasFull"
204
- :schema-name="schemaFieldFilterSchema" @close="showSchemaFieldFilter = false" />
205
- </q-dialog>
206
-
207
- <!-- Schema Source Code Dialog (Ctrl + Click) -->
208
- <q-dialog v-model="showSchemaCode" :maximized="true" :persistent="false" position="left" :seamless="false">
209
- <schema-code-display
210
- :schema-name="schemaCodeName"
211
- :model-value="showSchemaCode"
212
- :schemas="state.rawSchemasFull"
213
- @close="showSchemaCode = false" />
214
- </q-dialog>
215
-
216
- <!-- Route Source Code Dialog (Alt + Click on route) -->
217
- <q-dialog v-model="showRouteCode" :maximized="true" :persistent="false" position="right" :seamless="false">
218
- <route-code-display
219
- :route-id="routeCodeId"
220
- :model-value="showRouteCode"
221
- :routes="state.routeItems"
222
- @close="showRouteCode = false" />
223
- </q-dialog>
224
-
225
- <!-- Dump Core Data Dialog -->
226
- <q-dialog v-model="showDumpDialog" :maximized="true" :persistent="false">
227
- <div style="height:100%; position:relative; background:#fff;">
228
- <q-btn
229
- flat dense round icon="content_copy"
230
- aria-label="Copy"
231
- @click="copyDumpJson"
232
- style="position:absolute; top:6px; right:62px; z-index:11; background:rgba(255,255,255,0.85);"
233
- ></q-btn>
234
- <q-btn
235
- flat dense round icon="close"
236
- aria-label="Close"
237
- @click="showDumpDialog = false"
238
- style="position:absolute; top:6px; right:6px; z-index:11; background:rgba(255,255,255,0.85);"
239
- ></q-btn>
240
- <div>
241
- <pre style="padding:20px; overflow: auto;"><code>{{ dumpJson }}</code></pre>
242
- </div>
243
- </div>
244
- </q-dialog>
245
-
246
- <!-- Import Core Data Dialog -->
247
- <q-dialog v-model="showImportDialog" :persistent="true">
248
- <q-card style="min-width:70vw; max-width:90vw;">
249
- <q-card-section class="text-h6">Import core data JSON</q-card-section>
250
- <q-card-section >
251
- <q-btn color="primary" label="Render" @click="onImportConfirm" />
252
- </q-card-section>
253
- <q-card-section>
254
- <q-input v-model="importJsonText" type="textarea" autogrow filled label="Paste JSON here" />
255
- </q-card-section>
256
- </q-card>
257
- </q-dialog>
258
-
259
- <!-- Render Graph Dialog (from imported core data) -->
260
- <q-dialog v-model="showRenderGraph" :maximized="true" :persistent="false">
261
- <render-graph :core-data="renderCoreData" @close="showRenderGraph = false" />
262
- </q-dialog>
263
-
264
- <div id="graph" style="width: 100%; flex: 1 1 auto; overflow: auto"></div>
265
- </div>
266
-
267
- <script type="text/javascript" src="https://code.jquery.com/jquery-2.1.3.min.js" ></script>
268
- <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js" integrity="sha512-vc58qvvBdrDR4etbxMdlTt4GBQk1qjvyORR2nrsPsFPyrs+/u5c3+1Ct6upOgdZoIl7eq6k3a1UPDSNAQi/32A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
269
- <script src="https://unpkg.com/@hpcc-js/wasm@2.20.0/dist/graphviz.umd.js"></script>
270
- <script src="https://unpkg.com/d3-graphviz@5.6.0/build/d3-graphviz.js"></script>
271
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mousewheel/3.1.13/jquery.mousewheel.min.js"></script>
272
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-color/2.1.2/jquery.color.min.js"></script>
273
-
274
- <!-- Add the following at the end of your body tag -->
275
- <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.5.22/vue.global.prod.min.js" integrity="sha512-Y9sKU0AwzWRxKSLd2i35LuDpUdHY/E9tJrKG0mxw0qYQ75VVgGYazIUQPwKhFK9vGO3jIgAtxLiSq8GQ7PDfUg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
276
- <script src="fastapi-voyager-static/quasar.min.js"></script>
277
- <script src="fastapi-voyager-static/graphviz.svg.js"></script>
278
- <!-- highlight.js minimal ES module load (python only) -->
279
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" />
280
- <script type="module">
281
- import hljs from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/highlight.min.js';
282
- import python from 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/es/languages/python.min.js';
283
- hljs.registerLanguage('python', python);
284
- window.hljs = hljs;
285
- </script>
286
- <script type="module" src="fastapi-voyager-static/vue-main.js"></script>
287
- </body>
288
- </html>
File without changes
File without changes