Skip to content

Commit 28da9cd

Browse files
committed
feat: add vue-flow for configuration layers
1 parent c83b5e4 commit 28da9cd

File tree

9 files changed

+702
-30
lines changed

9 files changed

+702
-30
lines changed

app/assets/css/main.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1+
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/[email protected]/dist/style.css';
2+
@import 'https://cdn.jsdelivr.net/npm/@vue-flow/[email protected]/dist/theme-default.css';
3+
/* import the necessary styles for Vue Flow to work */
4+
@import '@vue-flow/core/dist/style.css';
5+
6+
/* import the default theme, this is optional but generally recommended */
7+
@import '@vue-flow/core/dist/theme-default.css';
18
@import 'tailwindcss';
29
@import '@nuxt/ui';
10+
311
/* @import "@nuxt/ui-pro"; */
412

513
@theme static {
Lines changed: 367 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,367 @@
1+
<script setup lang="ts">
2+
import { ref } from 'vue'
3+
import type { Node, Edge } from '@vue-flow/core'
4+
import { VueFlow, MarkerType, Position } from '@vue-flow/core'
5+
import { Background } from '@vue-flow/background'
6+
import { NodeToolbar } from '@vue-flow/node-toolbar'
7+
import ContainerNode from './nodes/ContainerNode.vue'
8+
import ToolbarNode from './nodes/ToolbarNode.vue'
9+
import ConfigNode from './nodes/ConfigNode.vue'
10+
11+
// Litestar configuration layers
12+
const nodes = ref<Node[]>([
13+
// Main box (blue)
14+
{
15+
id: 'application-layer-box',
16+
type: 'container',
17+
data: { label: 'Application layer' },
18+
position: { x: 0, y: 20 },
19+
height: 640,
20+
width: 500,
21+
style: {
22+
backgroundColor: '#EFF6FF',
23+
border: '1px solid #d1d5db',
24+
borderRadius: '8px',
25+
zIndex: -1,
26+
pointerEvents: 'none'
27+
},
28+
draggable: false,
29+
},
30+
// Router/Controller layer (purple) inside main box
31+
{
32+
id: 'router-controller-layer-box',
33+
type: 'container',
34+
data: { label: 'Router/Controller layer' },
35+
parentNode: 'application-layer-box',
36+
position: { x: 20, y: 200 },
37+
width: 460,
38+
height: 420,
39+
style: {
40+
backgroundColor: '#fae8ff',
41+
border: '1px solid #d1d5db',
42+
borderRadius: '8px',
43+
zIndex: -1,
44+
pointerEvents: 'none'
45+
},
46+
draggable: false,
47+
},
48+
// Handler layers (green) inside Router/Controller layer
49+
{
50+
id: 'handler-layer-box-1',
51+
type: 'container',
52+
data: { label: 'Handler layer' },
53+
parentNode: 'router-controller-layer-box',
54+
position: { x: 20, y: 200 },
55+
width: 200,
56+
height: 200,
57+
style: {
58+
backgroundColor: '#D9E7D6',
59+
border: '1px solid #d1d5db',
60+
borderRadius: '8px',
61+
zIndex: -1,
62+
pointerEvents: 'none'
63+
},
64+
draggable: false,
65+
},
66+
// Second handler layer (green) inside Router/Controller layer
67+
{
68+
id: 'handler-layer-box-2',
69+
type: 'container',
70+
data: { label: 'Handler layer', labelPosition: 'right' },
71+
parentNode: 'router-controller-layer-box',
72+
position: { x: 240, y: 200 },
73+
width: 200,
74+
height: 200,
75+
style: {
76+
backgroundColor: '#D9E7D6',
77+
border: '1px solid #d1d5db',
78+
borderRadius: '8px',
79+
zIndex: -1,
80+
pointerEvents: 'none'
81+
},
82+
draggable: false,
83+
},
84+
// Production configuration (top)
85+
{
86+
id: 'production',
87+
type: 'config',
88+
data: { label: 'Application configuration', backgroundColor: '#DBEAFE' },
89+
position: { x: 150, y: 70 },
90+
width: 200,
91+
height: 50,
92+
},
93+
// Icon node in application layer
94+
{
95+
id: 'application-layer-config',
96+
type: 'icon',
97+
data: {
98+
label: '⎇+',
99+
tooltip: '<span style="color: #3b82f6;">Anonymize PII data</span>',
100+
toolbarPosition: Position.Right,
101+
},
102+
position: { x: 230, y: 155 },
103+
style: {
104+
backgroundColor: '#ffffff',
105+
border: '1px solid #d1d5db',
106+
borderRadius: '50%',
107+
width: '40px',
108+
height: '40px',
109+
display: 'flex',
110+
alignItems: 'center',
111+
justifyContent: 'center',
112+
fontSize: '14px',
113+
cursor: 'pointer',
114+
},
115+
},
116+
// Router controller configuration (middle)
117+
{
118+
id: 'staging',
119+
type: 'config',
120+
data: { label: 'Router/Controller configuration', backgroundColor: '#f3e8ff' },
121+
position: { x: 150, y: 270 },
122+
width: 200,
123+
height: 50,
124+
},
125+
// Icon node router controller configuration
126+
{
127+
id: 'branchPlus',
128+
type: 'icon',
129+
data: {
130+
label: '⎇+',
131+
tooltip: 'Router configuration',
132+
toolbarPosition: Position.Right,
133+
},
134+
position: { x: 230, y: 335 },
135+
style: {
136+
backgroundColor: '#ffffff',
137+
border: '1px solid #d1d5db',
138+
borderRadius: '50%',
139+
width: '40px',
140+
height: '40px',
141+
display: 'flex',
142+
alignItems: 'center',
143+
justifyContent: 'center',
144+
fontSize: '14px',
145+
cursor: 'pointer',
146+
},
147+
},
148+
// Get users
149+
{
150+
id: 'get-users',
151+
type: 'config',
152+
data: { label: 'GET /users', backgroundColor: '#C1D9BC' },
153+
position: { x: 20, y: 60 },
154+
parentNode: 'handler-layer-box-1',
155+
width: 160,
156+
height: 40,
157+
},
158+
// Post users
159+
{
160+
id: 'post-users',
161+
type: 'config',
162+
data: { label: 'POST /users', backgroundColor: '#C1D9BC' },
163+
position: { x: 20, y: 60 },
164+
parentNode: 'handler-layer-box-2',
165+
width: 160,
166+
height: 40,
167+
},
168+
// Icon nodes in handler layers
169+
{
170+
id: 'get-users-icon',
171+
type: 'icon',
172+
data: {
173+
label: '⎇+',
174+
tooltip: 'Handler configuration',
175+
toolbarPosition: Position.Right,
176+
},
177+
position: { x: 80, y: 130 },
178+
parentNode: 'handler-layer-box-1',
179+
style: {
180+
backgroundColor: '#ffffff',
181+
border: '1px solid #d1d5db',
182+
borderRadius: '50%',
183+
width: '40px',
184+
height: '40px',
185+
display: 'flex',
186+
alignItems: 'center',
187+
justifyContent: 'center',
188+
fontSize: '14px',
189+
cursor: 'pointer',
190+
},
191+
},
192+
{
193+
id: 'post-users-icon',
194+
type: 'icon',
195+
data: {
196+
label: '⎇+',
197+
tooltip: `<strong>operation_id:</strong> ListUsers
198+
<strong>name:</strong> users:list
199+
<strong>summary:</strong> List Users
200+
<strong>description:</strong> Retrieve the users.
201+
<strong>path:</strong> /users
202+
<strong>cache:</strong> None`,
203+
toolbarPosition: Position.Right,
204+
},
205+
position: { x: 80, y: 130 },
206+
parentNode: 'handler-layer-box-2',
207+
style: {
208+
backgroundColor: '#ffffff',
209+
border: '1px solid #d1d5db',
210+
borderRadius: '50%',
211+
width: '40px',
212+
height: '40px',
213+
display: 'flex',
214+
alignItems: 'center',
215+
justifyContent: 'center',
216+
fontSize: '14px',
217+
cursor: 'pointer',
218+
},
219+
},
220+
])
221+
222+
const edges = ref<Edge[]>([
223+
{
224+
id: 'e-prod-staging',
225+
source: 'production',
226+
target: 'application-layer-config',
227+
animated: true,
228+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
229+
markerEnd: MarkerType.ArrowClosed,
230+
},
231+
{
232+
id: 'e-application-layer-config-staging',
233+
source: 'application-layer-config',
234+
target: 'staging',
235+
animated: true,
236+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
237+
markerEnd: MarkerType.ArrowClosed,
238+
},
239+
{
240+
id: 'e-staging-branchPlus',
241+
source: 'staging',
242+
target: 'branchPlus',
243+
animated: true,
244+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
245+
markerEnd: MarkerType.ArrowClosed,
246+
},
247+
{
248+
id: 'e-branchPlus-get-users',
249+
source: 'branchPlus',
250+
target: 'get-users',
251+
animated: true,
252+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
253+
markerEnd: MarkerType.ArrowClosed,
254+
},
255+
{
256+
id: 'e-branchPlus-post-users',
257+
source: 'branchPlus',
258+
target: 'post-users',
259+
animated: true,
260+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
261+
markerEnd: MarkerType.ArrowClosed,
262+
},
263+
{
264+
id: 'e-get-users-icon',
265+
source: 'get-users',
266+
target: 'get-users-icon',
267+
animated: true,
268+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
269+
markerEnd: MarkerType.ArrowClosed,
270+
},
271+
{
272+
id: 'e-post-users-icon',
273+
source: 'post-users',
274+
target: 'post-users-icon',
275+
animated: true,
276+
style: { stroke: '#71717a', strokeWidth: 1, strokeDasharray: '5,5' },
277+
markerEnd: MarkerType.ArrowClosed,
278+
},
279+
])
280+
</script>
281+
282+
<template>
283+
<VueFlow
284+
:nodes="nodes"
285+
:edges="edges"
286+
fit-view-on-init
287+
:zoom-on-scroll="false"
288+
:zoom-on-pinch="false"
289+
:zoom-on-double-click="false"
290+
:nodes-draggable="false"
291+
:nodes-connectable="false"
292+
:pan-on-drag="false"
293+
:pan-on-scroll="false"
294+
>
295+
<template #node-container="{ data }">
296+
<ContainerNode :data="data" />
297+
</template>
298+
299+
<template #node-config="{ data }">
300+
<ConfigNode :data="data" />
301+
</template>
302+
303+
<template #node-icon="props">
304+
<ToolbarNode :id="props.id" :data="props.data" />
305+
</template>
306+
307+
<template #node-default="{ data, id }">
308+
<div class="custom-node">
309+
{{ data.label }}
310+
<NodeToolbar v-if="data.tooltip" :node-id="id">
311+
<div class="toolbar-content">{{ data.tooltip }}</div>
312+
</NodeToolbar>
313+
</div>
314+
</template>
315+
316+
<Background />
317+
</VueFlow>
318+
</template>
319+
320+
<style scoped>
321+
322+
.vue-flow {
323+
flex: 1;
324+
background: #ffffff;
325+
}
326+
327+
.vue-flow__node {
328+
cursor: default;
329+
}
330+
331+
.vue-flow__node:hover {
332+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
333+
}
334+
335+
/* Container boxes shouldn't respond to hover */
336+
.vue-flow__node[data-id="your-cloud-box"]:hover,
337+
.vue-flow__node[data-id="xata-cloud-box"]:hover {
338+
box-shadow: none;
339+
}
340+
341+
/* Icon nodes get hover effect */
342+
.vue-flow__node[data-id="application-layer-config"]:hover,
343+
.vue-flow__node[data-id="branchPlus"]:hover,
344+
.vue-flow__node[data-id="merge"]:hover {
345+
transform: scale(1.05);
346+
transition: transform 0.2s ease;
347+
cursor: help;
348+
}
349+
350+
.custom-node {
351+
width: 100%;
352+
height: 100%;
353+
display: flex;
354+
align-items: center;
355+
justify-content: center;
356+
}
357+
358+
.toolbar-content {
359+
background: #18181b;
360+
color: white;
361+
padding: 8px 12px;
362+
border-radius: 6px;
363+
font-size: 12px;
364+
white-space: nowrap;
365+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
366+
}
367+
</style>

0 commit comments

Comments
 (0)