eva4j 1.0.13 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +314 -10
- package/COMMAND_EVALUATION.md +15 -16
- package/DOMAIN_YAML_GUIDE.md +576 -10
- package/FUTURE_FEATURES.md +1627 -1168
- package/README.md +318 -13
- package/bin/eva4j.js +34 -0
- package/config/defaults.json +1 -0
- package/design-system.md +797 -0
- package/docs/commands/EVALUATE_SYSTEM.md +994 -0
- package/docs/commands/GENERATE_ENTITIES.md +795 -6
- package/docs/commands/INDEX.md +10 -1
- package/examples/domain-endpoints-relations.yaml +353 -0
- package/examples/domain-endpoints-versioned.yaml +144 -0
- package/examples/domain-endpoints.yaml +135 -0
- package/examples/domain-events.yaml +166 -20
- package/examples/domain-listeners.yaml +212 -0
- package/examples/domain-one-to-many.yaml +1 -0
- package/examples/domain-one-to-one.yaml +1 -0
- package/examples/domain-ports.yaml +414 -0
- package/examples/domain-soft-delete.yaml +47 -44
- package/examples/system/notification.yaml +147 -0
- package/examples/system/product.yaml +185 -0
- package/examples/system/system.yaml +112 -0
- package/examples/system-report.html +971 -0
- package/examples/system.yaml +332 -0
- package/package.json +2 -1
- package/src/commands/build.js +714 -0
- package/src/commands/create.js +7 -3
- package/src/commands/detach.js +1 -0
- package/src/commands/evaluate-system.js +610 -0
- package/src/commands/generate-entities.js +1331 -49
- package/src/commands/generate-http-exchange.js +2 -0
- package/src/commands/generate-kafka-event.js +98 -11
- package/src/generators/base-generator.js +8 -1
- package/src/generators/postman-generator.js +188 -0
- package/src/generators/shared-generator.js +10 -0
- package/src/utils/config-manager.js +54 -0
- package/src/utils/context-builder.js +1 -0
- package/src/utils/domain-diagram.js +192 -0
- package/src/utils/domain-validator.js +970 -0
- package/src/utils/fake-data.js +376 -0
- package/src/utils/naming.js +3 -2
- package/src/utils/system-validator.js +434 -0
- package/src/utils/yaml-to-entity.js +302 -8
- package/templates/aggregate/AggregateMapper.java.ejs +3 -2
- package/templates/aggregate/AggregateRepository.java.ejs +8 -2
- package/templates/aggregate/AggregateRepositoryImpl.java.ejs +13 -3
- package/templates/aggregate/AggregateRoot.java.ejs +60 -2
- package/templates/aggregate/DomainEventHandler.java.ejs +27 -20
- package/templates/aggregate/DomainEventRecord.java.ejs +24 -8
- package/templates/aggregate/DomainEventSnapshot.java.ejs +46 -0
- package/templates/aggregate/JpaAggregateRoot.java.ejs +6 -0
- package/templates/aggregate/JpaRepository.java.ejs +5 -0
- package/templates/base/gradle/build.gradle.ejs +3 -2
- package/templates/base/root/AGENTS.md.ejs +306 -45
- package/templates/base/root/skill-build-domain-yaml-references-generate-entities.md.ejs +1663 -0
- package/templates/base/root/skill-build-system-yaml.ejs +1446 -0
- package/templates/base/root/system.yaml.ejs +97 -0
- package/templates/crud/ApplicationMapper.java.ejs +4 -0
- package/templates/crud/Controller.java.ejs +4 -4
- package/templates/crud/CreateCommand.java.ejs +4 -0
- package/templates/crud/CreateItemDto.java.ejs +4 -0
- package/templates/crud/CreateValueObjectDto.java.ejs +4 -0
- package/templates/crud/DeleteCommandHandler.java.ejs +10 -2
- package/templates/crud/EndpointsController.java.ejs +178 -0
- package/templates/crud/FindByQuery.java.ejs +17 -0
- package/templates/crud/FindByQueryHandler.java.ejs +57 -0
- package/templates/crud/ListQuery.java.ejs +1 -1
- package/templates/crud/ListQueryHandler.java.ejs +8 -8
- package/templates/crud/ScaffoldCommand.java.ejs +12 -0
- package/templates/crud/ScaffoldCommandHandler.java.ejs +43 -0
- package/templates/crud/ScaffoldQuery.java.ejs +13 -0
- package/templates/crud/ScaffoldQueryHandler.java.ejs +41 -0
- package/templates/crud/SubEntityAddCommand.java.ejs +21 -0
- package/templates/crud/SubEntityAddCommandHandler.java.ejs +43 -0
- package/templates/crud/SubEntityRemoveCommand.java.ejs +9 -0
- package/templates/crud/SubEntityRemoveCommandHandler.java.ejs +42 -0
- package/templates/crud/TransitionCommand.java.ejs +9 -0
- package/templates/crud/TransitionCommandHandler.java.ejs +39 -0
- package/templates/crud/UpdateCommand.java.ejs +4 -0
- package/templates/evaluate/report.html.ejs +1363 -0
- package/templates/kafka-event/DomainEventHandlerMethod.ejs +3 -1
- package/templates/kafka-event/Event.java.ejs +16 -0
- package/templates/kafka-listener/KafkaController.java.ejs +1 -1
- package/templates/kafka-listener/KafkaListenerClass.java.ejs +1 -1
- package/templates/kafka-listener/ListenerClass.java.ejs +65 -0
- package/templates/kafka-listener/ListenerCommand.java.ejs +31 -0
- package/templates/kafka-listener/ListenerCommandHandler.java.ejs +23 -0
- package/templates/kafka-listener/ListenerIntegrationEvent.java.ejs +37 -0
- package/templates/kafka-listener/ListenerMethod.java.ejs +1 -1
- package/templates/kafka-listener/ListenerNestedType.java.ejs +28 -0
- package/templates/mock/MockEvent.java.ejs +10 -0
- package/templates/mock/MockMessageBrokerImpl.java.ejs +35 -0
- package/templates/mock/MockMessageBrokerImplMethod.java.ejs +6 -0
- package/templates/mock/SpringEventListener.java.ejs +61 -0
- package/templates/ports/PortDomainModel.java.ejs +35 -0
- package/templates/ports/PortFeignAdapter.java.ejs +67 -0
- package/templates/ports/PortFeignClient.java.ejs +45 -0
- package/templates/ports/PortFeignConfig.java.ejs +24 -0
- package/templates/ports/PortInterface.java.ejs +45 -0
- package/templates/ports/PortNestedType.java.ejs +28 -0
- package/templates/ports/PortRequestDto.java.ejs +30 -0
- package/templates/ports/PortResponseDto.java.ejs +28 -0
- package/templates/postman/Collection.json.ejs +1 -1
- package/templates/postman/UnifiedCollection.json.ejs +185 -0
- package/templates/shared/configurations/eventPublicationConfig/EventPublicationSchemaConfig.java.ejs +109 -0
|
@@ -0,0 +1,971 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="es">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>test-eva — eva4j Architecture Validator</title>
|
|
7
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet" />
|
|
10
|
+
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
|
|
11
|
+
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
|
|
12
|
+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
13
|
+
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
|
|
14
|
+
<style>
|
|
15
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
16
|
+
body { background: #0a0a0f; color: #e8e8f0; font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif; }
|
|
17
|
+
::-webkit-scrollbar { width: 6px; }
|
|
18
|
+
::-webkit-scrollbar-track { background: #0a0a0f; }
|
|
19
|
+
::-webkit-scrollbar-thumb { background: #2e2e50; border-radius: 3px; }
|
|
20
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
|
|
21
|
+
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }
|
|
22
|
+
@keyframes shimmer { 0% { box-shadow: 0 0 0 0 rgba(230,57,80,0.4); } 70% { box-shadow: 0 0 0 8px rgba(230,57,80,0); } 100% { box-shadow: 0 0 0 0 rgba(230,57,80,0); } }
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
<div id="root"></div>
|
|
27
|
+
|
|
28
|
+
<script>
|
|
29
|
+
(function () {
|
|
30
|
+
var _d = 'eyJzeXN0ZW1OYW1lIjoidGVzdC1ldmEiLCJtb2R1bGVzIjpbeyJpZCI6Im1vdmllcyIsImxhYmVsIjoiTW92aWVzIiwiaWNvbiI6IvCfjqwiLCJjb2xvciI6IiM0YTllZmYiLCJkZXNjIjoiQ2F0w6Fsb2dvIGRlIHBlbMOtY3VsYXM6IHTDrXR1bG9zLCBnw6luZXJvcywgY2xhc2lmaWNhY2lvbmVzLCBzaW5vcHNpcyB5IGR1cmFjacOzbiJ9LHsiaWQiOiJ0aGVhdGVycyIsImxhYmVsIjoiVGhlYXRlcnMiLCJpY29uIjoi8J+Pm++4jyIsImNvbG9yIjoiIzliNmRmZiIsImRlc2MiOiJTYWxhcyBkZSBjaW5lOiBjb25maWd1cmFjacOzbiBkZSBjYXBhY2lkYWQsIHRpcG8gZGUgc2FsYSAoMkQsIDNELCBJTUFYKSB5IG1hcGEgZGUgYXNpZW50b3MifSx7ImlkIjoic2NyZWVuaW5ncyIsImxhYmVsIjoiU2NyZWVuaW5ncyIsImljb24iOiLwn4+b77iPIiwiY29sb3IiOiIjZjVjODQyIiwiZGVzYyI6IkZ1bmNpb25lcyBwcm9ncmFtYWRhczogYXNvY2lhIHBlbMOtY3VsYSArIHNhbGEgKyBob3JhcmlvLCBnZXN0aW9uYSBkaXNwb25pYmlsaWRhZCwgcHJlY2lvcyB5IGJsb3F1ZW8gZXhjbHVzaXZvIHBhcmEgZXZlbnRvcyBwcml2YWRvcyJ9LHsiaWQiOiJjdXN0b21lcnMiLCJsYWJlbCI6IkN1c3RvbWVycyIsImljb24iOiLwn5GkIiwiY29sb3IiOiIjMmRjYzhmIiwiZGVzYyI6IlJlZ2lzdHJvIHkgcGVyZmlsIGRlIGNsaWVudGVzOiBkYXRvcyBwZXJzb25hbGVzLCBoaXN0b3JpYWwgZGUgY29tcHJhcyBlIGhpc3RvcmlhbCBkZSBwdW50b3MifSx7ImlkIjoicmVzZXJ2YXRpb25zIiwibGFiZWwiOiJSZXNlcnZhdGlvbnMiLCJpY29uIjoi8J+On++4jyIsImNvbG9yIjoiI2ZmOGM0MiIsImRlc2MiOiJDaWNsbyBkZSB2aWRhIGRlIGxhIHJlc2VydmE6IHNlbGVjY2nDs24geSBibG9xdWVvIGRlIGFzaWVudG9zLCBjb25maXJtYWNpw7NuLCBjYW5jZWxhY2nDs24geSByZXNlcnZhIGV4Y2x1c2l2YSBkZSBzYWxhIGNvbXBsZXRhIHBhcmEgZXZlbnRvcyBwcml2YWRvcyJ9LHsiaWQiOiJwYXltZW50cyIsImxhYmVsIjoiUGF5bWVudHMiLCJpY29uIjoi8J+SsyIsImNvbG9yIjoiI2U2Mzk1MCIsImRlc2MiOiJQcm9jZXNhbWllbnRvIGRlIHBhZ29zOiBpbnRlZ3JhY2nDs24gY29uIHBhc2FyZWxhIGV4dGVybmEsIGFwcm9iYWNpb25lcyB5IHJlZW1ib2xzb3MifSx7ImlkIjoibm90aWZpY2F0aW9ucyIsImxhYmVsIjoiTm90aWZpY2F0aW9ucyIsImljb24iOiLwn5SUIiwiY29sb3IiOiIjNDBjNGQwIiwiZGVzYyI6IkVudsOtbyBkZSBub3RpZmljYWNpb25lcyBwb3IgZW1haWwgeSBTTVM6IGNvbmZpcm1hY2lvbmVzLCB0aWNrZXRzIFFSLCByZWNvcmRhdG9yaW9zIHkgYWxlcnRhcyBkZSBjYW5jZWxhY2nDs24ifV0sImV2ZW50cyI6W3siZXZlbnQiOiJSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsInByb2R1Y2VyIjoicmVzZXJ2YXRpb25zIiwidG9waWMiOiJSRVNFUlZBVElPTl9DUkVBVEVEIiwiY29uc3VtZXJzIjpbInBheW1lbnRzIiwibm90aWZpY2F0aW9ucyJdfSx7ImV2ZW50IjoiUGF5bWVudEFwcHJvdmVkRXZlbnQiLCJwcm9kdWNlciI6InBheW1lbnRzIiwidG9waWMiOiJQQVlNRU5UX0FQUFJPVkVEIiwiY29uc3VtZXJzIjpbInJlc2VydmF0aW9ucyIsIm5vdGlmaWNhdGlvbnMiXX0seyJldmVudCI6IlBheW1lbnRSZWplY3RlZEV2ZW50IiwicHJvZHVjZXIiOiJwYXltZW50cyIsInRvcGljIjoiUEFZTUVOVF9SRUpFQ1RFRCIsImNvbnN1bWVycyI6WyJyZXNlcnZhdGlvbnMiLCJub3RpZmljYXRpb25zIl19LHsiZXZlbnQiOiJSZXNlcnZhdGlvbkNvbmZpcm1lZEV2ZW50IiwicHJvZHVjZXIiOiJyZXNlcnZhdGlvbnMiLCJ0b3BpYyI6IlJFU0VSVkFUSU9OX0NPTkZJUk1FRCIsImNvbnN1bWVycyI6WyJub3RpZmljYXRpb25zIiwiY3VzdG9tZXJzIl19LHsiZXZlbnQiOiJSZXNlcnZhdGlvbkNhbmNlbGxlZEV2ZW50IiwicHJvZHVjZXIiOiJyZXNlcnZhdGlvbnMiLCJ0b3BpYyI6IlJFU0VSVkFUSU9OX0NBTkNFTExFRCIsImNvbnN1bWVycyI6WyJwYXltZW50cyIsInNjcmVlbmluZ3MiLCJub3RpZmljYXRpb25zIl19LHsiZXZlbnQiOiJSZXNlcnZhdGlvbkV4cGlyZWRFdmVudCIsInByb2R1Y2VyIjoicmVzZXJ2YXRpb25zIiwidG9waWMiOiJSRVNFUlZBVElPTl9FWFBJUkVEIiwiY29uc3VtZXJzIjpbInNjcmVlbmluZ3MiLCJub3RpZmljYXRpb25zIl19LHsiZXZlbnQiOiJTY3JlZW5pbmdDYW5jZWxsZWRFdmVudCIsInByb2R1Y2VyIjoic2NyZWVuaW5ncyIsInRvcGljIjoiU0NSRUVOSU5HX0NBTkNFTExFRCIsImNvbnN1bWVycyI6WyJyZXNlcnZhdGlvbnMiLCJub3RpZmljYXRpb25zIl19LHsiZXZlbnQiOiJQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsInByb2R1Y2VyIjoicmVzZXJ2YXRpb25zIiwidG9waWMiOiJQUklWQVRFX0VWRU5UX1JFU0VSVkFUSU9OX0NSRUFURUQiLCJjb25zdW1lcnMiOlsic2NyZWVuaW5ncyIsInBheW1lbnRzIiwibm90aWZpY2F0aW9ucyJdfSx7ImV2ZW50IjoiVGhlYXRlckxvY2tlZEZvclByaXZhdGVFdmVudEV2ZW50IiwicHJvZHVjZXIiOiJzY3JlZW5pbmdzIiwidG9waWMiOiJUSEVBVEVSX0xPQ0tFRF9GT1JfUFJJVkFURV9FVkVOVCIsImNvbnN1bWVycyI6WyJub3RpZmljYXRpb25zIl19XSwic3luY0ludGVncmF0aW9ucyI6W3siY2FsbGVyIjoic2NyZWVuaW5ncyIsImNhbGxzIjoibW92aWVzIiwicG9ydCI6Ik1vdmllU2VydmljZSIsImVuZHBvaW50cyI6WyJHRVQgL21vdmllcy97aWR9Il19LHsiY2FsbGVyIjoic2NyZWVuaW5ncyIsImNhbGxzIjoidGhlYXRlcnMiLCJwb3J0IjoiVGhlYXRlclNlcnZpY2UiLCJlbmRwb2ludHMiOlsiR0VUIC90aGVhdGVycy97aWR9Il19LHsiY2FsbGVyIjoicmVzZXJ2YXRpb25zIiwiY2FsbHMiOiJzY3JlZW5pbmdzIiwicG9ydCI6IlNjcmVlbmluZ1NlcnZpY2UiLCJlbmRwb2ludHMiOlsiR0VUIC9zY3JlZW5pbmdzL3tpZH0iLCJHRVQgL3NjcmVlbmluZ3Mve2lkfS9zZWF0cyIsIkdFVCAvc2NyZWVuaW5ncy97aWR9L3ByaXZhdGUtZXZlbnQtYXZhaWxhYmlsaXR5Il19LHsiY2FsbGVyIjoicmVzZXJ2YXRpb25zIiwiY2FsbHMiOiJjdXN0b21lcnMiLCJwb3J0IjoiQ3VzdG9tZXJTZXJ2aWNlIiwiZW5kcG9pbnRzIjpbIkdFVCAvY3VzdG9tZXJzL3tpZH0iXX0seyJjYWxsZXIiOiJwYXltZW50cyIsImNhbGxzIjoicmVzZXJ2YXRpb25zIiwicG9ydCI6IlJlc2VydmF0aW9uU2VydmljZSIsImVuZHBvaW50cyI6WyJHRVQgL3Jlc2VydmF0aW9ucy97aWR9Il19XSwiZW5kcG9pbnRzIjp7Im1vdmllcyI6WyJHRVQgL21vdmllcyIsIkdFVCAvbW92aWVzL3tpZH0iLCJQT1NUIC9tb3ZpZXMiLCJQVVQgL21vdmllcy97aWR9IiwiREVMRVRFIC9tb3ZpZXMve2lkfSJdLCJ0aGVhdGVycyI6WyJHRVQgL3RoZWF0ZXJzIiwiR0VUIC90aGVhdGVycy97aWR9IiwiUE9TVCAvdGhlYXRlcnMiLCJQVVQgL3RoZWF0ZXJzL3tpZH0iXSwic2NyZWVuaW5ncyI6WyJHRVQgL3NjcmVlbmluZ3MiLCJHRVQgL3NjcmVlbmluZ3Mve2lkfSIsIkdFVCAvc2NyZWVuaW5ncy97aWR9L3NlYXRzIiwiUE9TVCAvc2NyZWVuaW5ncyIsIlBVVCAvc2NyZWVuaW5ncy97aWR9L2NhbmNlbCIsIkdFVCAvc2NyZWVuaW5ncy97aWR9L3ByaXZhdGUtZXZlbnQtYXZhaWxhYmlsaXR5IiwiUFVUIC9zY3JlZW5pbmdzL3tpZH0vbG9jayJdLCJjdXN0b21lcnMiOlsiUE9TVCAvY3VzdG9tZXJzIiwiR0VUIC9jdXN0b21lcnMve2lkfSIsIkdFVCAvY3VzdG9tZXJzIiwiUFVUIC9jdXN0b21lcnMve2lkfSJdLCJyZXNlcnZhdGlvbnMiOlsiUE9TVCAvcmVzZXJ2YXRpb25zIiwiR0VUIC9yZXNlcnZhdGlvbnMve2lkfSIsIkdFVCAvcmVzZXJ2YXRpb25zIiwiUFVUIC9yZXNlcnZhdGlvbnMve2lkfS9jb25maXJtIiwiUFVUIC9yZXNlcnZhdGlvbnMve2lkfS9jYW5jZWwiLCJQVVQgL3Jlc2VydmF0aW9ucy97aWR9L2V4cGlyZSIsIlBPU1QgL3Jlc2VydmF0aW9ucy9wcml2YXRlLWV2ZW50cyJdLCJwYXltZW50cyI6WyJQT1NUIC9wYXltZW50cyIsIkdFVCAvcGF5bWVudHMve2lkfSIsIkdFVCAvcGF5bWVudHMiLCJQVVQgL3BheW1lbnRzL3tpZH0vYXBwcm92ZSIsIlBVVCAvcGF5bWVudHMve2lkfS9yZWplY3QiLCJQVVQgL3BheW1lbnRzL3tpZH0vcmVmdW5kIl0sIm5vdGlmaWNhdGlvbnMiOltdfSwiZmxvd3MiOlt7ImlkIjoiUmVzZXJ2YXRpb25DcmVhdGVkRXZlbnQiLCJsYWJlbCI6IlJlc2VydmF0aW9uIENyZWF0ZWQiLCJpY29uIjoi8J+On++4jyIsImRlc2NyaXB0aW9uIjoicmVzZXJ2YXRpb25zIOKGkiBbcGF5bWVudHMsIG5vdGlmaWNhdGlvbnNdIHbDrWEgdG9waWMgUkVTRVJWQVRJT05fQ1JFQVRFRCIsImNvbG9yIjoiI2ZmOGM0MiIsInN0ZXBzIjpbeyJpZCI6MSwidHlwZSI6Imh0dHAiLCJmcm9tIjoiY2xpZW50IiwidG8iOiJyZXNlcnZhdGlvbnMiLCJsYWJlbCI6IlBPU1QgL3Jlc2VydmF0aW9ucyIsImRlc2MiOiJJbmljaWFyIHJlc2VydmE6IHNlbGVjY2lvbmFyIGZ1bmNpw7NuIHkgYXNpZW50b3MsIGJsb3F1ZW8gdGVtcG9yYWwgKDE1IG1pbikiLCJzeW5jQ2FsbHMiOlt7InRvIjoic2NyZWVuaW5ncyIsImxhYmVsIjoiR0VUIC9zY3JlZW5pbmdzL3tpZH0iLCJwb3J0IjoiU2NyZWVuaW5nU2VydmljZSJ9LHsidG8iOiJjdXN0b21lcnMiLCJsYWJlbCI6IkdFVCAvY3VzdG9tZXJzL3tpZH0iLCJwb3J0IjoiQ3VzdG9tZXJTZXJ2aWNlIn1dfSx7ImlkIjoyLCJ0eXBlIjoiZXZlbnQiLCJmcm9tIjoicmVzZXJ2YXRpb25zIiwiZXZlbnQiOiJSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsInRvcGljIjoiUkVTRVJWQVRJT05fQ1JFQVRFRCIsInRvIjpbInBheW1lbnRzIiwibm90aWZpY2F0aW9ucyJdLCJkZXNjIjoiUmVzZXJ2YXRpb25DcmVhdGVkRXZlbnQgcHVibGljYWRvIGVuIEthZmthICh0b3BpYzogUkVTRVJWQVRJT05fQ1JFQVRFRCkifSx7ImlkIjozLCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6InBheW1lbnRzIiwidG8iOiJwYXltZW50cyIsImxhYmVsIjoiUHJvY2VzYSBSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsImRlc2MiOiJwYXltZW50cyByZWFjY2lvbmEgYWwgZXZlbnRvIFJlc2VydmF0aW9uQ3JlYXRlZEV2ZW50In0seyJpZCI6NCwidHlwZSI6ImFjdGlvbiIsImZyb20iOiJub3RpZmljYXRpb25zIiwidG8iOiJub3RpZmljYXRpb25zIiwibGFiZWwiOiJQcm9jZXNhIFJlc2VydmF0aW9uQ3JlYXRlZEV2ZW50IiwiZGVzYyI6Im5vdGlmaWNhdGlvbnMgcmVhY2Npb25hIGFsIGV2ZW50byBSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCJ9XX0seyJpZCI6IlBheW1lbnRBcHByb3ZlZEV2ZW50IiwibGFiZWwiOiJQYXltZW50IEFwcHJvdmVkIiwiaWNvbiI6IvCfkrMiLCJkZXNjcmlwdGlvbiI6InBheW1lbnRzIOKGkiBbcmVzZXJ2YXRpb25zLCBub3RpZmljYXRpb25zXSB2w61hIHRvcGljIFBBWU1FTlRfQVBQUk9WRUQiLCJjb2xvciI6IiNlNjM5NTAiLCJzdGVwcyI6W3siaWQiOjEsInR5cGUiOiJodHRwIiwiZnJvbSI6ImNsaWVudCIsInRvIjoicGF5bWVudHMiLCJsYWJlbCI6IlBVVCAvcGF5bWVudHMve2lkfS9hcHByb3ZlIiwiZGVzYyI6IlJlZ2lzdHJhciBhcHJvYmFjacOzbiBkZSBwYWdvIHJlY2liaWRhIGRlc2RlIGxhIHBhc2FyZWxhIiwic3luY0NhbGxzIjpbeyJ0byI6InJlc2VydmF0aW9ucyIsImxhYmVsIjoiR0VUIC9yZXNlcnZhdGlvbnMve2lkfSIsInBvcnQiOiJSZXNlcnZhdGlvblNlcnZpY2UifV19LHsiaWQiOjIsInR5cGUiOiJldmVudCIsImZyb20iOiJwYXltZW50cyIsImV2ZW50IjoiUGF5bWVudEFwcHJvdmVkRXZlbnQiLCJ0b3BpYyI6IlBBWU1FTlRfQVBQUk9WRUQiLCJ0byI6WyJyZXNlcnZhdGlvbnMiLCJub3RpZmljYXRpb25zIl0sImRlc2MiOiJQYXltZW50QXBwcm92ZWRFdmVudCBwdWJsaWNhZG8gZW4gS2Fma2EgKHRvcGljOiBQQVlNRU5UX0FQUFJPVkVEKSJ9LHsiaWQiOjMsInR5cGUiOiJhY3Rpb24iLCJmcm9tIjoicmVzZXJ2YXRpb25zIiwidG8iOiJyZXNlcnZhdGlvbnMiLCJsYWJlbCI6IlByb2Nlc2EgUGF5bWVudEFwcHJvdmVkRXZlbnQiLCJkZXNjIjoicmVzZXJ2YXRpb25zIHJlYWNjaW9uYSBhbCBldmVudG8gUGF5bWVudEFwcHJvdmVkRXZlbnQifSx7ImlkIjo0LCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6Im5vdGlmaWNhdGlvbnMiLCJ0byI6Im5vdGlmaWNhdGlvbnMiLCJsYWJlbCI6IlByb2Nlc2EgUGF5bWVudEFwcHJvdmVkRXZlbnQiLCJkZXNjIjoibm90aWZpY2F0aW9ucyByZWFjY2lvbmEgYWwgZXZlbnRvIFBheW1lbnRBcHByb3ZlZEV2ZW50In1dfSx7ImlkIjoiUGF5bWVudFJlamVjdGVkRXZlbnQiLCJsYWJlbCI6IlBheW1lbnQgUmVqZWN0ZWQiLCJpY29uIjoi8J+SsyIsImRlc2NyaXB0aW9uIjoicGF5bWVudHMg4oaSIFtyZXNlcnZhdGlvbnMsIG5vdGlmaWNhdGlvbnNdIHbDrWEgdG9waWMgUEFZTUVOVF9SRUpFQ1RFRCIsImNvbG9yIjoiI2U2Mzk1MCIsInN0ZXBzIjpbeyJpZCI6MSwidHlwZSI6Imh0dHAiLCJmcm9tIjoiY2xpZW50IiwidG8iOiJwYXltZW50cyIsImxhYmVsIjoiUFVUIC9wYXltZW50cy97aWR9L3JlamVjdCIsImRlc2MiOiJSZWdpc3RyYXIgcmVjaGF6byBkZSBwYWdvIHJlY2liaWRvIGRlc2RlIGxhIHBhc2FyZWxhIiwic3luY0NhbGxzIjpbeyJ0byI6InJlc2VydmF0aW9ucyIsImxhYmVsIjoiR0VUIC9yZXNlcnZhdGlvbnMve2lkfSIsInBvcnQiOiJSZXNlcnZhdGlvblNlcnZpY2UifV19LHsiaWQiOjIsInR5cGUiOiJldmVudCIsImZyb20iOiJwYXltZW50cyIsImV2ZW50IjoiUGF5bWVudFJlamVjdGVkRXZlbnQiLCJ0b3BpYyI6IlBBWU1FTlRfUkVKRUNURUQiLCJ0byI6WyJyZXNlcnZhdGlvbnMiLCJub3RpZmljYXRpb25zIl0sImRlc2MiOiJQYXltZW50UmVqZWN0ZWRFdmVudCBwdWJsaWNhZG8gZW4gS2Fma2EgKHRvcGljOiBQQVlNRU5UX1JFSkVDVEVEKSJ9LHsiaWQiOjMsInR5cGUiOiJhY3Rpb24iLCJmcm9tIjoicmVzZXJ2YXRpb25zIiwidG8iOiJyZXNlcnZhdGlvbnMiLCJsYWJlbCI6IlByb2Nlc2EgUGF5bWVudFJlamVjdGVkRXZlbnQiLCJkZXNjIjoicmVzZXJ2YXRpb25zIHJlYWNjaW9uYSBhbCBldmVudG8gUGF5bWVudFJlamVjdGVkRXZlbnQifSx7ImlkIjo0LCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6Im5vdGlmaWNhdGlvbnMiLCJ0byI6Im5vdGlmaWNhdGlvbnMiLCJsYWJlbCI6IlByb2Nlc2EgUGF5bWVudFJlamVjdGVkRXZlbnQiLCJkZXNjIjoibm90aWZpY2F0aW9ucyByZWFjY2lvbmEgYWwgZXZlbnRvIFBheW1lbnRSZWplY3RlZEV2ZW50In1dfSx7ImlkIjoiUmVzZXJ2YXRpb25Db25maXJtZWRFdmVudCIsImxhYmVsIjoiUmVzZXJ2YXRpb24gQ29uZmlybWVkIiwiaWNvbiI6IvCfjp/vuI8iLCJkZXNjcmlwdGlvbiI6InJlc2VydmF0aW9ucyDihpIgW25vdGlmaWNhdGlvbnMsIGN1c3RvbWVyc10gdsOtYSB0b3BpYyBSRVNFUlZBVElPTl9DT05GSVJNRUQiLCJjb2xvciI6IiNmZjhjNDIiLCJzdGVwcyI6W3siaWQiOjEsInR5cGUiOiJodHRwIiwiZnJvbSI6ImNsaWVudCIsInRvIjoicmVzZXJ2YXRpb25zIiwibGFiZWwiOiJQVVQgL3Jlc2VydmF0aW9ucy97aWR9L2NvbmZpcm0iLCJkZXNjIjoiQ29uZmlybWFyIGxhIHJlc2VydmEgZGVzcHXDqXMgZGUgcmVjaWJpciBwYWdvIGFwcm9iYWRvIiwic3luY0NhbGxzIjpbeyJ0byI6InNjcmVlbmluZ3MiLCJsYWJlbCI6IkdFVCAvc2NyZWVuaW5ncy97aWR9IiwicG9ydCI6IlNjcmVlbmluZ1NlcnZpY2UifSx7InRvIjoiY3VzdG9tZXJzIiwibGFiZWwiOiJHRVQgL2N1c3RvbWVycy97aWR9IiwicG9ydCI6IkN1c3RvbWVyU2VydmljZSJ9XX0seyJpZCI6MiwidHlwZSI6ImV2ZW50IiwiZnJvbSI6InJlc2VydmF0aW9ucyIsImV2ZW50IjoiUmVzZXJ2YXRpb25Db25maXJtZWRFdmVudCIsInRvcGljIjoiUkVTRVJWQVRJT05fQ09ORklSTUVEIiwidG8iOlsibm90aWZpY2F0aW9ucyIsImN1c3RvbWVycyJdLCJkZXNjIjoiUmVzZXJ2YXRpb25Db25maXJtZWRFdmVudCBwdWJsaWNhZG8gZW4gS2Fma2EgKHRvcGljOiBSRVNFUlZBVElPTl9DT05GSVJNRUQpIn0seyJpZCI6MywidHlwZSI6ImFjdGlvbiIsImZyb20iOiJub3RpZmljYXRpb25zIiwidG8iOiJub3RpZmljYXRpb25zIiwibGFiZWwiOiJQcm9jZXNhIFJlc2VydmF0aW9uQ29uZmlybWVkRXZlbnQiLCJkZXNjIjoibm90aWZpY2F0aW9ucyByZWFjY2lvbmEgYWwgZXZlbnRvIFJlc2VydmF0aW9uQ29uZmlybWVkRXZlbnQifSx7ImlkIjo0LCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6ImN1c3RvbWVycyIsInRvIjoiY3VzdG9tZXJzIiwibGFiZWwiOiJQcm9jZXNhIFJlc2VydmF0aW9uQ29uZmlybWVkRXZlbnQiLCJkZXNjIjoiY3VzdG9tZXJzIHJlYWNjaW9uYSBhbCBldmVudG8gUmVzZXJ2YXRpb25Db25maXJtZWRFdmVudCJ9XX0seyJpZCI6IlJlc2VydmF0aW9uQ2FuY2VsbGVkRXZlbnQiLCJsYWJlbCI6IlJlc2VydmF0aW9uIENhbmNlbGxlZCIsImljb24iOiLwn46f77iPIiwiZGVzY3JpcHRpb24iOiJyZXNlcnZhdGlvbnMg4oaSIFtwYXltZW50cywgc2NyZWVuaW5ncywgbm90aWZpY2F0aW9uc10gdsOtYSB0b3BpYyBSRVNFUlZBVElPTl9DQU5DRUxMRUQiLCJjb2xvciI6IiNmZjhjNDIiLCJzdGVwcyI6W3siaWQiOjEsInR5cGUiOiJodHRwIiwiZnJvbSI6ImNsaWVudCIsInRvIjoicmVzZXJ2YXRpb25zIiwibGFiZWwiOiJQVVQgL3Jlc2VydmF0aW9ucy97aWR9L2NhbmNlbCIsImRlc2MiOiJDYW5jZWxhciBsYSByZXNlcnZhIHkgbGliZXJhciBsb3MgYXNpZW50b3Mgc2VsZWNjaW9uYWRvcyIsInN5bmNDYWxscyI6W3sidG8iOiJzY3JlZW5pbmdzIiwibGFiZWwiOiJHRVQgL3NjcmVlbmluZ3Mve2lkfSIsInBvcnQiOiJTY3JlZW5pbmdTZXJ2aWNlIn0seyJ0byI6ImN1c3RvbWVycyIsImxhYmVsIjoiR0VUIC9jdXN0b21lcnMve2lkfSIsInBvcnQiOiJDdXN0b21lclNlcnZpY2UifV19LHsiaWQiOjIsInR5cGUiOiJldmVudCIsImZyb20iOiJyZXNlcnZhdGlvbnMiLCJldmVudCI6IlJlc2VydmF0aW9uQ2FuY2VsbGVkRXZlbnQiLCJ0b3BpYyI6IlJFU0VSVkFUSU9OX0NBTkNFTExFRCIsInRvIjpbInBheW1lbnRzIiwic2NyZWVuaW5ncyIsIm5vdGlmaWNhdGlvbnMiXSwiZGVzYyI6IlJlc2VydmF0aW9uQ2FuY2VsbGVkRXZlbnQgcHVibGljYWRvIGVuIEthZmthICh0b3BpYzogUkVTRVJWQVRJT05fQ0FOQ0VMTEVEKSJ9LHsiaWQiOjMsInR5cGUiOiJhY3Rpb24iLCJmcm9tIjoicGF5bWVudHMiLCJ0byI6InBheW1lbnRzIiwibGFiZWwiOiJQcm9jZXNhIFJlc2VydmF0aW9uQ2FuY2VsbGVkRXZlbnQiLCJkZXNjIjoicGF5bWVudHMgcmVhY2Npb25hIGFsIGV2ZW50byBSZXNlcnZhdGlvbkNhbmNlbGxlZEV2ZW50In0seyJpZCI6NCwidHlwZSI6ImFjdGlvbiIsImZyb20iOiJzY3JlZW5pbmdzIiwidG8iOiJzY3JlZW5pbmdzIiwibGFiZWwiOiJDYW5jZWxTY3JlZW5pbmcgKFBVVCAvc2NyZWVuaW5ncy97aWR9L2NhbmNlbCkiLCJkZXNjIjoic2NyZWVuaW5ncyByZWFjY2lvbmEgYWwgZXZlbnRvIFJlc2VydmF0aW9uQ2FuY2VsbGVkRXZlbnQifSx7ImlkIjo1LCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6Im5vdGlmaWNhdGlvbnMiLCJ0byI6Im5vdGlmaWNhdGlvbnMiLCJsYWJlbCI6IlByb2Nlc2EgUmVzZXJ2YXRpb25DYW5jZWxsZWRFdmVudCIsImRlc2MiOiJub3RpZmljYXRpb25zIHJlYWNjaW9uYSBhbCBldmVudG8gUmVzZXJ2YXRpb25DYW5jZWxsZWRFdmVudCJ9XX0seyJpZCI6IlJlc2VydmF0aW9uRXhwaXJlZEV2ZW50IiwibGFiZWwiOiJSZXNlcnZhdGlvbiBFeHBpcmVkIiwiaWNvbiI6IvCfjp/vuI8iLCJkZXNjcmlwdGlvbiI6InJlc2VydmF0aW9ucyDihpIgW3NjcmVlbmluZ3MsIG5vdGlmaWNhdGlvbnNdIHbDrWEgdG9waWMgUkVTRVJWQVRJT05fRVhQSVJFRCIsImNvbG9yIjoiI2ZmOGM0MiIsInN0ZXBzIjpbeyJpZCI6MSwidHlwZSI6Imh0dHAiLCJmcm9tIjoiY2xpZW50IiwidG8iOiJyZXNlcnZhdGlvbnMiLCJsYWJlbCI6IlBVVCAvcmVzZXJ2YXRpb25zL3tpZH0vZXhwaXJlIiwiZGVzYyI6IkV4cGlyYXIgcmVzZXJ2YSBubyBwYWdhZGEgZGVudHJvIGRlbCB0aWVtcG8gbMOtbWl0ZSBkZSBibG9xdWVvIiwic3luY0NhbGxzIjpbeyJ0byI6InNjcmVlbmluZ3MiLCJsYWJlbCI6IkdFVCAvc2NyZWVuaW5ncy97aWR9IiwicG9ydCI6IlNjcmVlbmluZ1NlcnZpY2UifSx7InRvIjoiY3VzdG9tZXJzIiwibGFiZWwiOiJHRVQgL2N1c3RvbWVycy97aWR9IiwicG9ydCI6IkN1c3RvbWVyU2VydmljZSJ9XX0seyJpZCI6MiwidHlwZSI6ImV2ZW50IiwiZnJvbSI6InJlc2VydmF0aW9ucyIsImV2ZW50IjoiUmVzZXJ2YXRpb25FeHBpcmVkRXZlbnQiLCJ0b3BpYyI6IlJFU0VSVkFUSU9OX0VYUElSRUQiLCJ0byI6WyJzY3JlZW5pbmdzIiwibm90aWZpY2F0aW9ucyJdLCJkZXNjIjoiUmVzZXJ2YXRpb25FeHBpcmVkRXZlbnQgcHVibGljYWRvIGVuIEthZmthICh0b3BpYzogUkVTRVJWQVRJT05fRVhQSVJFRCkifSx7ImlkIjozLCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6InNjcmVlbmluZ3MiLCJ0byI6InNjcmVlbmluZ3MiLCJsYWJlbCI6IlByb2Nlc2EgUmVzZXJ2YXRpb25FeHBpcmVkRXZlbnQiLCJkZXNjIjoic2NyZWVuaW5ncyByZWFjY2lvbmEgYWwgZXZlbnRvIFJlc2VydmF0aW9uRXhwaXJlZEV2ZW50In0seyJpZCI6NCwidHlwZSI6ImFjdGlvbiIsImZyb20iOiJub3RpZmljYXRpb25zIiwidG8iOiJub3RpZmljYXRpb25zIiwibGFiZWwiOiJQcm9jZXNhIFJlc2VydmF0aW9uRXhwaXJlZEV2ZW50IiwiZGVzYyI6Im5vdGlmaWNhdGlvbnMgcmVhY2Npb25hIGFsIGV2ZW50byBSZXNlcnZhdGlvbkV4cGlyZWRFdmVudCJ9XX0seyJpZCI6IlNjcmVlbmluZ0NhbmNlbGxlZEV2ZW50IiwibGFiZWwiOiJTY3JlZW5pbmcgQ2FuY2VsbGVkIiwiaWNvbiI6IvCfj5vvuI8iLCJkZXNjcmlwdGlvbiI6InNjcmVlbmluZ3Mg4oaSIFtyZXNlcnZhdGlvbnMsIG5vdGlmaWNhdGlvbnNdIHbDrWEgdG9waWMgU0NSRUVOSU5HX0NBTkNFTExFRCIsImNvbG9yIjoiI2Y1Yzg0MiIsInN0ZXBzIjpbeyJpZCI6MSwidHlwZSI6Imh0dHAiLCJmcm9tIjoiY2xpZW50IiwidG8iOiJzY3JlZW5pbmdzIiwibGFiZWwiOiJQVVQgL3NjcmVlbmluZ3Mve2lkfS9jYW5jZWwiLCJkZXNjIjoiQ2FuY2VsYXIgdW5hIGZ1bmNpw7NuIHByb2dyYW1hZGEgeSBsaWJlcmFyIHRvZGFzIGxhcyByZXNlcnZhcyIsInN5bmNDYWxscyI6W3sidG8iOiJtb3ZpZXMiLCJsYWJlbCI6IkdFVCAvbW92aWVzL3tpZH0iLCJwb3J0IjoiTW92aWVTZXJ2aWNlIn0seyJ0byI6InRoZWF0ZXJzIiwibGFiZWwiOiJHRVQgL3RoZWF0ZXJzL3tpZH0iLCJwb3J0IjoiVGhlYXRlclNlcnZpY2UifV19LHsiaWQiOjIsInR5cGUiOiJldmVudCIsImZyb20iOiJzY3JlZW5pbmdzIiwiZXZlbnQiOiJTY3JlZW5pbmdDYW5jZWxsZWRFdmVudCIsInRvcGljIjoiU0NSRUVOSU5HX0NBTkNFTExFRCIsInRvIjpbInJlc2VydmF0aW9ucyIsIm5vdGlmaWNhdGlvbnMiXSwiZGVzYyI6IlNjcmVlbmluZ0NhbmNlbGxlZEV2ZW50IHB1YmxpY2FkbyBlbiBLYWZrYSAodG9waWM6IFNDUkVFTklOR19DQU5DRUxMRUQpIn0seyJpZCI6MywidHlwZSI6ImFjdGlvbiIsImZyb20iOiJyZXNlcnZhdGlvbnMiLCJ0byI6InJlc2VydmF0aW9ucyIsImxhYmVsIjoiQ2FuY2VsUmVzZXJ2YXRpb24gKFBVVCAvcmVzZXJ2YXRpb25zL3tpZH0vY2FuY2VsKSIsImRlc2MiOiJyZXNlcnZhdGlvbnMgcmVhY2Npb25hIGFsIGV2ZW50byBTY3JlZW5pbmdDYW5jZWxsZWRFdmVudCJ9LHsiaWQiOjQsInR5cGUiOiJhY3Rpb24iLCJmcm9tIjoibm90aWZpY2F0aW9ucyIsInRvIjoibm90aWZpY2F0aW9ucyIsImxhYmVsIjoiUHJvY2VzYSBTY3JlZW5pbmdDYW5jZWxsZWRFdmVudCIsImRlc2MiOiJub3RpZmljYXRpb25zIHJlYWNjaW9uYSBhbCBldmVudG8gU2NyZWVuaW5nQ2FuY2VsbGVkRXZlbnQifV19LHsiaWQiOiJQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsImxhYmVsIjoiUHJpdmF0ZSBFdmVudCBSZXNlcnZhdGlvbiBDcmVhdGVkIiwiaWNvbiI6IvCfjp/vuI8iLCJkZXNjcmlwdGlvbiI6InJlc2VydmF0aW9ucyDihpIgW3NjcmVlbmluZ3MsIHBheW1lbnRzLCBub3RpZmljYXRpb25zXSB2w61hIHRvcGljIFBSSVZBVEVfRVZFTlRfUkVTRVJWQVRJT05fQ1JFQVRFRCIsImNvbG9yIjoiI2ZmOGM0MiIsInN0ZXBzIjpbeyJpZCI6MSwidHlwZSI6Imh0dHAiLCJmcm9tIjoiY2xpZW50IiwidG8iOiJyZXNlcnZhdGlvbnMiLCJsYWJlbCI6IlBPU1QgL3Jlc2VydmF0aW9ucyIsImRlc2MiOiJJbmljaWFyIHJlc2VydmE6IHNlbGVjY2lvbmFyIGZ1bmNpw7NuIHkgYXNpZW50b3MsIGJsb3F1ZW8gdGVtcG9yYWwgKDE1IG1pbikiLCJzeW5jQ2FsbHMiOlt7InRvIjoic2NyZWVuaW5ncyIsImxhYmVsIjoiR0VUIC9zY3JlZW5pbmdzL3tpZH0iLCJwb3J0IjoiU2NyZWVuaW5nU2VydmljZSJ9LHsidG8iOiJjdXN0b21lcnMiLCJsYWJlbCI6IkdFVCAvY3VzdG9tZXJzL3tpZH0iLCJwb3J0IjoiQ3VzdG9tZXJTZXJ2aWNlIn1dfSx7ImlkIjoyLCJ0eXBlIjoiZXZlbnQiLCJmcm9tIjoicmVzZXJ2YXRpb25zIiwiZXZlbnQiOiJQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsInRvcGljIjoiUFJJVkFURV9FVkVOVF9SRVNFUlZBVElPTl9DUkVBVEVEIiwidG8iOlsic2NyZWVuaW5ncyIsInBheW1lbnRzIiwibm90aWZpY2F0aW9ucyJdLCJkZXNjIjoiUHJpdmF0ZUV2ZW50UmVzZXJ2YXRpb25DcmVhdGVkRXZlbnQgcHVibGljYWRvIGVuIEthZmthICh0b3BpYzogUFJJVkFURV9FVkVOVF9SRVNFUlZBVElPTl9DUkVBVEVEKSJ9LHsiaWQiOjMsInR5cGUiOiJhY3Rpb24iLCJmcm9tIjoic2NyZWVuaW5ncyIsInRvIjoic2NyZWVuaW5ncyIsImxhYmVsIjoiUHJvY2VzYSBQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsImRlc2MiOiJzY3JlZW5pbmdzIHJlYWNjaW9uYSBhbCBldmVudG8gUHJpdmF0ZUV2ZW50UmVzZXJ2YXRpb25DcmVhdGVkRXZlbnQifSx7ImlkIjo0LCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6InBheW1lbnRzIiwidG8iOiJwYXltZW50cyIsImxhYmVsIjoiUHJvY2VzYSBQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCIsImRlc2MiOiJwYXltZW50cyByZWFjY2lvbmEgYWwgZXZlbnRvIFByaXZhdGVFdmVudFJlc2VydmF0aW9uQ3JlYXRlZEV2ZW50In0seyJpZCI6NSwidHlwZSI6ImFjdGlvbiIsImZyb20iOiJub3RpZmljYXRpb25zIiwidG8iOiJub3RpZmljYXRpb25zIiwibGFiZWwiOiJQcm9jZXNhIFByaXZhdGVFdmVudFJlc2VydmF0aW9uQ3JlYXRlZEV2ZW50IiwiZGVzYyI6Im5vdGlmaWNhdGlvbnMgcmVhY2Npb25hIGFsIGV2ZW50byBQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCJ9XX0seyJpZCI6IlRoZWF0ZXJMb2NrZWRGb3JQcml2YXRlRXZlbnRFdmVudCIsImxhYmVsIjoiVGhlYXRlciBMb2NrZWQgRm9yIFByaXZhdGUgRXZlbnQiLCJpY29uIjoi8J+Pm++4jyIsImRlc2NyaXB0aW9uIjoic2NyZWVuaW5ncyDihpIgW25vdGlmaWNhdGlvbnNdIHbDrWEgdG9waWMgVEhFQVRFUl9MT0NLRURfRk9SX1BSSVZBVEVfRVZFTlQiLCJjb2xvciI6IiNmNWM4NDIiLCJzdGVwcyI6W3siaWQiOjEsInR5cGUiOiJodHRwIiwiZnJvbSI6ImNsaWVudCIsInRvIjoic2NyZWVuaW5ncyIsImxhYmVsIjoidHJpZ2dlciAvdGhlYXRlcmxvY2tlZGZvcnByaXZhdGVldmVudCIsImRlc2MiOiJBY2Npw7NuIHF1ZSBkZXNlbmNhZGVuYSBlbCBldmVudG8ifSx7ImlkIjoyLCJ0eXBlIjoiZXZlbnQiLCJmcm9tIjoic2NyZWVuaW5ncyIsImV2ZW50IjoiVGhlYXRlckxvY2tlZEZvclByaXZhdGVFdmVudEV2ZW50IiwidG9waWMiOiJUSEVBVEVSX0xPQ0tFRF9GT1JfUFJJVkFURV9FVkVOVCIsInRvIjpbIm5vdGlmaWNhdGlvbnMiXSwiZGVzYyI6IlRoZWF0ZXJMb2NrZWRGb3JQcml2YXRlRXZlbnRFdmVudCBwdWJsaWNhZG8gZW4gS2Fma2EgKHRvcGljOiBUSEVBVEVSX0xPQ0tFRF9GT1JfUFJJVkFURV9FVkVOVCkifSx7ImlkIjozLCJ0eXBlIjoiYWN0aW9uIiwiZnJvbSI6Im5vdGlmaWNhdGlvbnMiLCJ0byI6Im5vdGlmaWNhdGlvbnMiLCJsYWJlbCI6IlByb2Nlc2EgVGhlYXRlckxvY2tlZEZvclByaXZhdGVFdmVudEV2ZW50IiwiZGVzYyI6Im5vdGlmaWNhdGlvbnMgcmVhY2Npb25hIGFsIGV2ZW50byBUaGVhdGVyTG9ja2VkRm9yUHJpdmF0ZUV2ZW50RXZlbnQifV19XSwidmFsaWRhdGlvbiI6eyJlcnJvcnMiOltdLCJ3YXJuaW5ncyI6WyInbm90aWZpY2F0aW9ucycgbm8gdGllbmUgZW5kcG9pbnRzIGV4cHVlc3RvcyAoZXhwb3Nlc1tdIHZhY8OtbyBvIGF1c2VudGUpIiwiR2FwIGRlIGNvbXBvcnRhbWllbnRvOiAnRXhwaXJlUmVzZXJ2YXRpb24nIChQVVQgL3Jlc2VydmF0aW9ucy97aWR9L2V4cGlyZSkgZW4gJ3Jlc2VydmF0aW9ucycgbm8gdGllbmUgbmluZ8O6biBldmVudG8gbmkgbGxhbWFkYSBzw61uY3JvbmEgcXVlIGxvIGFjdGl2ZS4gUHVlZGUgbmVjZXNpdGFyIHVuIHNjaGVkdWxlciBvIGpvYiBwZXJpw7NkaWNvLiIsIkdhcCBkZSBjb21wb3J0YW1pZW50bzogJ1Byb2Nlc3NSZWZ1bmQnIChQVVQgL3BheW1lbnRzL3tpZH0vcmVmdW5kKSBlbiAncGF5bWVudHMnIG5vIHRpZW5lIG5pbmfDum4gZXZlbnRvIG5pIGxsYW1hZGEgc8OtbmNyb25hIHF1ZSBsbyBhY3RpdmUuIFB1ZWRlIG5lY2VzaXRhciB1biBzY2hlZHVsZXIgbyBqb2IgcGVyacOzZGljby4iLCJBY29wbGFtaWVudG8gYXNpbcOpdHJpY286ICdyZXNlcnZhdGlvbnMnIGxsYW1hIHPDrW5jcm9uYW1lbnRlIGEgJ3NjcmVlbmluZ3MnLCBtaWVudHJhcyAnc2NyZWVuaW5ncycgcmVzcG9uZGUgdsOtYSBldmVudG9zIGFzw61uY3Jvbm9zIChTY3JlZW5pbmdDYW5jZWxsZWRFdmVudCkuIENvbnNpZGVyYXIgcGFzYXIgbG9zIGRhdG9zIG5lY2VzYXJpb3MgZGlyZWN0YW1lbnRlIGVuIGVsIGV2ZW50byBwYXJhIGVsaW1pbmFyIGxhIGxsYW1hZGEgc8OtbmNyb25hLiIsIkFjb3BsYW1pZW50byBhc2ltw6l0cmljbzogJ3BheW1lbnRzJyBsbGFtYSBzw61uY3JvbmFtZW50ZSBhICdyZXNlcnZhdGlvbnMnLCBtaWVudHJhcyAncmVzZXJ2YXRpb25zJyByZXNwb25kZSB2w61hIGV2ZW50b3MgYXPDrW5jcm9ub3MgKFJlc2VydmF0aW9uQ3JlYXRlZEV2ZW50LCBSZXNlcnZhdGlvbkNhbmNlbGxlZEV2ZW50LCBQcml2YXRlRXZlbnRSZXNlcnZhdGlvbkNyZWF0ZWRFdmVudCkuIENvbnNpZGVyYXIgcGFzYXIgbG9zIGRhdG9zIG5lY2VzYXJpb3MgZGlyZWN0YW1lbnRlIGVuIGVsIGV2ZW50byBwYXJhIGVsaW1pbmFyIGxhIGxsYW1hZGEgc8OtbmNyb25hLiJdLCJvayI6WyJQcm9kdWN0b3IgJ3Jlc2VydmF0aW9ucycgZGVsIGV2ZW50byAnUmVzZXJ2YXRpb25DcmVhdGVkRXZlbnQnIGV4aXN0ZSDinJMiLCJQcm9kdWN0b3IgJ3BheW1lbnRzJyBkZWwgZXZlbnRvICdQYXltZW50QXBwcm92ZWRFdmVudCcgZXhpc3RlIOKckyIsIlByb2R1Y3RvciAncGF5bWVudHMnIGRlbCBldmVudG8gJ1BheW1lbnRSZWplY3RlZEV2ZW50JyBleGlzdGUg4pyTIiwiUHJvZHVjdG9yICdyZXNlcnZhdGlvbnMnIGRlbCBldmVudG8gJ1Jlc2VydmF0aW9uQ29uZmlybWVkRXZlbnQnIGV4aXN0ZSDinJMiLCJQcm9kdWN0b3IgJ3Jlc2VydmF0aW9ucycgZGVsIGV2ZW50byAnUmVzZXJ2YXRpb25DYW5jZWxsZWRFdmVudCcgZXhpc3RlIOKckyIsIlByb2R1Y3RvciAncmVzZXJ2YXRpb25zJyBkZWwgZXZlbnRvICdSZXNlcnZhdGlvbkV4cGlyZWRFdmVudCcgZXhpc3RlIOKckyIsIlByb2R1Y3RvciAnc2NyZWVuaW5ncycgZGVsIGV2ZW50byAnU2NyZWVuaW5nQ2FuY2VsbGVkRXZlbnQnIGV4aXN0ZSDinJMiLCJQcm9kdWN0b3IgJ3Jlc2VydmF0aW9ucycgZGVsIGV2ZW50byAnUHJpdmF0ZUV2ZW50UmVzZXJ2YXRpb25DcmVhdGVkRXZlbnQnIGV4aXN0ZSDinJMiLCJQcm9kdWN0b3IgJ3NjcmVlbmluZ3MnIGRlbCBldmVudG8gJ1RoZWF0ZXJMb2NrZWRGb3JQcml2YXRlRXZlbnRFdmVudCcgZXhpc3RlIOKckyIsIlRvZG9zIGxvcyBjb25zdW1pZG9yZXMgZGUgZXZlbnRvcyBlc3TDoW4gZGVjbGFyYWRvcyBjb21vIG3Ds2R1bG9zIOKckyIsIlRvZG9zIGxvcyBlbmRwb2ludHMgdXNhZG9zIGVuIGludGVncmFjaW9uZXMgc8OtbmNyb25hcyBlc3TDoW4gZGVjbGFyYWRvcyBlbiBsb3MgbcOzZHVsb3MgZGVzdGlubyDinJMiLCJObyBzZSBkZXRlY3Rhcm9uIGNpY2xvcyBuaSBhY29wbGFtaWVudG8gc8OtbmNyb25vIGJpZGlyZWNjaW9uYWwg4pyTIiwiJ2N1c3RvbWVycycgZXMgY29uc3VtaWRvciBwdXJvIGRlIGV2ZW50b3MgKGNvcnJlY3RvOiBubyBwcm9kdWNlIGV2ZW50b3MgcHJvcGlvcykiLCInbm90aWZpY2F0aW9ucycgZXMgY29uc3VtaWRvciBwdXJvIGRlIGV2ZW50b3MgKGNvcnJlY3RvOiBubyBwcm9kdWNlIGV2ZW50b3MgcHJvcGlvcykiLCInbm90aWZpY2F0aW9ucycgbm8gZXhwb25lIGVuZHBvaW50cyBSRVNUIGRpcmVjdGFtZW50ZSAobcOzZHVsbyBkZSBpbnRlZ3JhY2nDs24pIiwiJ3NjcmVlbmluZ3MnIHRpZW5lIGVuZHBvaW50cyBhY2Nlc2libGVzIHRhbnRvIHPDrW5jcm9uYW1lbnRlIGNvbW8gdsOtYSBldmVudG9zIChkaXNlw7FvIGR1YWwg4oCUIGludGVuY2lvbmFsKSIsIidjdXN0b21lcnMnIHRpZW5lIGVuZHBvaW50cyBhY2Nlc2libGVzIHRhbnRvIHPDrW5jcm9uYW1lbnRlIGNvbW8gdsOtYSBldmVudG9zIChkaXNlw7FvIGR1YWwg4oCUIGludGVuY2lvbmFsKSIsIidyZXNlcnZhdGlvbnMnIHRpZW5lIGVuZHBvaW50cyBhY2Nlc2libGVzIHRhbnRvIHPDrW5jcm9uYW1lbnRlIGNvbW8gdsOtYSBldmVudG9zIChkaXNlw7FvIGR1YWwg4oCUIGludGVuY2lvbmFsKSJdLCJzY29yZSI6ODh9LCJnZW5lcmF0ZWRBdCI6IjIwMjYtMDMtMTFUMjM6MTk6NTEuNTcxWiJ9';
|
|
31
|
+
var bytes = Uint8Array.from(atob(_d), function(c) { return c.charCodeAt(0); });
|
|
32
|
+
window.__EVA_DATA__ = JSON.parse(new TextDecoder('utf-8').decode(bytes));
|
|
33
|
+
})();
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<script type="text/babel">
|
|
37
|
+
const { useState, useRef, useEffect } = React;
|
|
38
|
+
|
|
39
|
+
// ─── Injected data ──────────────────────────────────────────────────────
|
|
40
|
+
const {
|
|
41
|
+
systemName,
|
|
42
|
+
modules: MODULES_LIST,
|
|
43
|
+
events: EVENTS,
|
|
44
|
+
syncIntegrations: SYNC_INTEGRATIONS,
|
|
45
|
+
endpoints: ENDPOINTS,
|
|
46
|
+
flows: FLOWS_LIST,
|
|
47
|
+
validation: VALIDATION,
|
|
48
|
+
generatedAt,
|
|
49
|
+
} = window.__EVA_DATA__;
|
|
50
|
+
|
|
51
|
+
// Convert arrays to maps for fast lookup
|
|
52
|
+
const MODULES = Object.fromEntries(MODULES_LIST.map(m => [m.id, m]));
|
|
53
|
+
const FLOWS = Object.fromEntries(FLOWS_LIST.map(f => [f.id, f]));
|
|
54
|
+
|
|
55
|
+
// ─── Design system ──────────────────────────────────────────────────────
|
|
56
|
+
const C = {
|
|
57
|
+
bg: "#0a0a0f",
|
|
58
|
+
surface: "#12121a",
|
|
59
|
+
surfaceHover: "#1a1a28",
|
|
60
|
+
border: "#1e1e30",
|
|
61
|
+
borderBright: "#2e2e50",
|
|
62
|
+
accent: "#e63950",
|
|
63
|
+
gold: "#f5c842",
|
|
64
|
+
green: "#2dcc8f",
|
|
65
|
+
blue: "#4a9eff",
|
|
66
|
+
purple: "#9b6dff",
|
|
67
|
+
orange: "#ff8c42",
|
|
68
|
+
text: "#e8e8f0",
|
|
69
|
+
textMuted: "#8c8caa",
|
|
70
|
+
textDim: "#b4b4cc",
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ─── Primitive components ───────────────────────────────────────────────
|
|
74
|
+
const Tag = ({ color, children }) => (
|
|
75
|
+
<span style={{
|
|
76
|
+
background: color + "22", color, border: `1px solid ${color}44`,
|
|
77
|
+
borderRadius: 4, padding: "1px 8px", fontSize: 11, fontWeight: 600,
|
|
78
|
+
fontFamily: "'JetBrains Mono', 'Courier New', monospace", letterSpacing: 0.3, display: "inline-block",
|
|
79
|
+
}}>
|
|
80
|
+
{children}
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const Badge = ({ color, children }) => (
|
|
85
|
+
<span style={{
|
|
86
|
+
background: color + "33", color, borderRadius: 20,
|
|
87
|
+
padding: "2px 10px", fontSize: 11, fontWeight: 700,
|
|
88
|
+
}}>
|
|
89
|
+
{children}
|
|
90
|
+
</span>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// ─── ModuleCard ─────────────────────────────────────────────────────────
|
|
94
|
+
function ModuleCard({ id, selected, onClick }) {
|
|
95
|
+
const mod = MODULES[id];
|
|
96
|
+
if (!mod) return null;
|
|
97
|
+
return (
|
|
98
|
+
<div onClick={onClick} style={{
|
|
99
|
+
background: selected ? mod.color + "22" : C.surface,
|
|
100
|
+
border: `1px solid ${selected ? mod.color : C.border}`,
|
|
101
|
+
borderRadius: 10, padding: "10px 14px", cursor: "pointer",
|
|
102
|
+
transition: "all 0.18s", display: "flex", alignItems: "center", gap: 10,
|
|
103
|
+
boxShadow: selected ? `0 0 14px ${mod.color}33` : "none",
|
|
104
|
+
}}>
|
|
105
|
+
<span style={{ fontSize: 20 }}>{mod.icon}</span>
|
|
106
|
+
<div>
|
|
107
|
+
<div style={{ fontWeight: 700, color: selected ? mod.color : C.text, fontSize: 13 }}>{mod.label}</div>
|
|
108
|
+
<div style={{ color: C.textMuted, fontSize: 10, marginTop: 1 }}>{mod.desc}</div>
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── ValidationTab ──────────────────────────────────────────────────────
|
|
115
|
+
function ValidationTab() {
|
|
116
|
+
const [expanded, setExpanded] = useState({ errors: true, warnings: true, ok: false });
|
|
117
|
+
|
|
118
|
+
const score = VALIDATION.score;
|
|
119
|
+
const scoreColor = score > 80 ? C.green : score > 60 ? C.gold : C.accent;
|
|
120
|
+
|
|
121
|
+
function Section({ title, items, color, icon, sectionKey }) {
|
|
122
|
+
const isOpen = expanded[sectionKey];
|
|
123
|
+
return (
|
|
124
|
+
<div style={{ marginBottom: 16 }}>
|
|
125
|
+
<div
|
|
126
|
+
onClick={() => setExpanded(e => ({ ...e, [sectionKey]: !e[sectionKey] }))}
|
|
127
|
+
style={{
|
|
128
|
+
display: "flex", alignItems: "center", gap: 10, cursor: "pointer",
|
|
129
|
+
padding: "10px 16px", background: C.surface, border: `1px solid ${C.border}`,
|
|
130
|
+
borderRadius: isOpen ? "8px 8px 0 0" : 8, userSelect: "none",
|
|
131
|
+
}}
|
|
132
|
+
>
|
|
133
|
+
<span style={{ fontSize: 18 }}>{icon}</span>
|
|
134
|
+
<span style={{ fontWeight: 700, color, flex: 1 }}>{title}</span>
|
|
135
|
+
<Badge color={color}>{items.length}</Badge>
|
|
136
|
+
<span style={{ color: C.textMuted, fontSize: 12 }}>{isOpen ? "▲" : "▼"}</span>
|
|
137
|
+
</div>
|
|
138
|
+
{isOpen && items.length > 0 && (
|
|
139
|
+
<div style={{ border: `1px solid ${C.border}`, borderTop: "none", borderRadius: "0 0 8px 8px", overflow: "hidden" }}>
|
|
140
|
+
{items.map((item, i) => (
|
|
141
|
+
<div key={i} style={{
|
|
142
|
+
padding: "10px 16px",
|
|
143
|
+
borderBottom: i < items.length - 1 ? `1px solid ${C.border}` : "none",
|
|
144
|
+
display: "flex", alignItems: "flex-start", gap: 10,
|
|
145
|
+
background: i % 2 === 0 ? C.bg : C.surface,
|
|
146
|
+
}}>
|
|
147
|
+
<span style={{ color, marginTop: 1, flexShrink: 0 }}>•</span>
|
|
148
|
+
<span style={{ color: C.textDim, fontSize: 13, lineHeight: 1.6 }}>
|
|
149
|
+
{typeof item === "string" ? item : item.msg}
|
|
150
|
+
</span>
|
|
151
|
+
</div>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
{isOpen && items.length === 0 && (
|
|
156
|
+
<div style={{ border: `1px solid ${C.border}`, borderTop: "none", borderRadius: "0 0 8px 8px", padding: "12px 16px", background: C.bg }}>
|
|
157
|
+
<span style={{ color: C.textMuted, fontSize: 12 }}>— ninguno —</span>
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div>
|
|
166
|
+
{/* Score cards */}
|
|
167
|
+
<div style={{ display: "flex", gap: 16, marginBottom: 24, flexWrap: "wrap" }}>
|
|
168
|
+
<div style={{
|
|
169
|
+
flex: 1, minWidth: 160, background: C.surface, border: `1px solid ${scoreColor}44`,
|
|
170
|
+
borderRadius: 12, padding: 20, textAlign: "center",
|
|
171
|
+
boxShadow: `0 0 24px ${scoreColor}22`,
|
|
172
|
+
}}>
|
|
173
|
+
<div style={{ fontSize: 52, fontWeight: 900, color: scoreColor, fontFamily: "monospace", lineHeight: 1 }}>
|
|
174
|
+
{score}%
|
|
175
|
+
</div>
|
|
176
|
+
<div style={{ color: C.textMuted, fontSize: 11, marginTop: 6, letterSpacing: 1 }}>SCORE DE CALIDAD</div>
|
|
177
|
+
</div>
|
|
178
|
+
{[
|
|
179
|
+
{ label: "Errores", count: VALIDATION.errors.length, color: C.accent, icon: "🔴" },
|
|
180
|
+
{ label: "Advertencias", count: VALIDATION.warnings.length, color: C.gold, icon: "🟡" },
|
|
181
|
+
{ label: "Validados", count: VALIDATION.ok.length, color: C.green, icon: "🟢" },
|
|
182
|
+
].map(s => (
|
|
183
|
+
<div key={s.label} style={{
|
|
184
|
+
flex: 1, minWidth: 130, background: C.surface, border: `1px solid ${C.border}`,
|
|
185
|
+
borderRadius: 12, padding: 20, textAlign: "center",
|
|
186
|
+
}}>
|
|
187
|
+
<div style={{ fontSize: 38, fontWeight: 900, color: s.color, fontFamily: "monospace" }}>{s.count}</div>
|
|
188
|
+
<div style={{ color: C.textMuted, fontSize: 11, marginTop: 6, letterSpacing: 0.5 }}>
|
|
189
|
+
{s.icon} {s.label.toUpperCase()}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<Section title="Errores críticos" items={VALIDATION.errors} color={C.accent} icon="🔴" sectionKey="errors" />
|
|
196
|
+
<Section title="Advertencias" items={VALIDATION.warnings} color={C.gold} icon="🟡" sectionKey="warnings" />
|
|
197
|
+
<Section title="Validaciones pasadas" items={VALIDATION.ok} color={C.green} icon="🟢" sectionKey="ok" />
|
|
198
|
+
</div>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ─── FlowSimulator ──────────────────────────────────────────────────────
|
|
203
|
+
function FlowSimulator() {
|
|
204
|
+
const defaultFlow = FLOWS_LIST[0]?.id || null;
|
|
205
|
+
const [selectedFlowId, setSelectedFlowId] = useState(defaultFlow);
|
|
206
|
+
const [currentStep, setCurrentStep] = useState(-1);
|
|
207
|
+
const [running, setRunning] = useState(false);
|
|
208
|
+
const [completed, setCompleted] = useState(false);
|
|
209
|
+
const intervalRef = useRef(null);
|
|
210
|
+
|
|
211
|
+
const flow = FLOWS[selectedFlowId] || FLOWS_LIST[0];
|
|
212
|
+
|
|
213
|
+
if (!flow) {
|
|
214
|
+
return (
|
|
215
|
+
<div style={{ color: C.textMuted, padding: 40, textAlign: "center" }}>
|
|
216
|
+
No hay flujos de eventos declarados en integrations.async
|
|
217
|
+
</div>
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const reset = () => {
|
|
222
|
+
clearInterval(intervalRef.current);
|
|
223
|
+
setCurrentStep(-1);
|
|
224
|
+
setRunning(false);
|
|
225
|
+
setCompleted(false);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const selectFlow = (id) => {
|
|
229
|
+
reset();
|
|
230
|
+
setSelectedFlowId(id);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const run = () => {
|
|
234
|
+
if (running) return;
|
|
235
|
+
reset();
|
|
236
|
+
setRunning(true);
|
|
237
|
+
let step = 0;
|
|
238
|
+
setCurrentStep(0);
|
|
239
|
+
intervalRef.current = setInterval(() => {
|
|
240
|
+
step++;
|
|
241
|
+
if (step >= flow.steps.length) {
|
|
242
|
+
clearInterval(intervalRef.current);
|
|
243
|
+
setRunning(false);
|
|
244
|
+
setCompleted(true);
|
|
245
|
+
setCurrentStep(flow.steps.length);
|
|
246
|
+
} else {
|
|
247
|
+
setCurrentStep(step);
|
|
248
|
+
}
|
|
249
|
+
}, 1100);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
useEffect(() => () => clearInterval(intervalRef.current), []);
|
|
253
|
+
|
|
254
|
+
const getStepIcon = (step) => {
|
|
255
|
+
if (step.type === "event") return "⚡";
|
|
256
|
+
if (step.type === "external") return "🌐";
|
|
257
|
+
if (step.type === "action") return "📤";
|
|
258
|
+
return "→";
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const getStepColor = (step) => {
|
|
262
|
+
if (step.type === "event") return C.gold;
|
|
263
|
+
if (step.type === "external") return C.purple;
|
|
264
|
+
if (step.type === "action") return C.green;
|
|
265
|
+
return C.blue;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const getActorColor = (actor) => {
|
|
269
|
+
if (MODULES[actor]) return MODULES[actor].color;
|
|
270
|
+
if (actor === "client" || actor === "organizer") return C.green;
|
|
271
|
+
if (actor === "admin") return C.purple;
|
|
272
|
+
if (actor === "gateway") return C.textDim;
|
|
273
|
+
return C.textMuted;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<div>
|
|
278
|
+
{/* Flow selector tabs */}
|
|
279
|
+
<div style={{ display: "flex", gap: 8, marginBottom: 20, flexWrap: "wrap" }}>
|
|
280
|
+
{FLOWS_LIST.map(f => (
|
|
281
|
+
<button key={f.id} onClick={() => selectFlow(f.id)} style={{
|
|
282
|
+
background: selectedFlowId === f.id ? f.color + "22" : C.surface,
|
|
283
|
+
border: `1px solid ${selectedFlowId === f.id ? f.color : C.border}`,
|
|
284
|
+
color: selectedFlowId === f.id ? f.color : C.textMuted,
|
|
285
|
+
borderRadius: 8, padding: "7px 12px", cursor: "pointer",
|
|
286
|
+
fontWeight: selectedFlowId === f.id ? 700 : 400, fontSize: 12,
|
|
287
|
+
transition: "all 0.15s", display: "flex", alignItems: "center", gap: 6,
|
|
288
|
+
boxShadow: selectedFlowId === f.id ? `0 0 12px ${f.color}33` : "none",
|
|
289
|
+
fontFamily: "inherit",
|
|
290
|
+
}}>
|
|
291
|
+
<span>{f.icon}</span>{f.label}
|
|
292
|
+
</button>
|
|
293
|
+
))}
|
|
294
|
+
</div>
|
|
295
|
+
|
|
296
|
+
{/* Flow header */}
|
|
297
|
+
<div style={{
|
|
298
|
+
background: C.surface, border: `1px solid ${flow.color}44`, borderRadius: 10,
|
|
299
|
+
padding: 16, marginBottom: 20, display: "flex", alignItems: "center", gap: 16,
|
|
300
|
+
}}>
|
|
301
|
+
<span style={{ fontSize: 32 }}>{flow.icon}</span>
|
|
302
|
+
<div style={{ flex: 1 }}>
|
|
303
|
+
<div style={{ fontWeight: 700, color: flow.color, fontSize: 16 }}>{flow.label}</div>
|
|
304
|
+
<div style={{ color: C.textDim, fontSize: 12, marginTop: 3 }}>{flow.description}</div>
|
|
305
|
+
</div>
|
|
306
|
+
<div style={{ display: "flex", gap: 8 }}>
|
|
307
|
+
<button onClick={run} disabled={running} style={{
|
|
308
|
+
background: running ? C.border : flow.color, color: "#fff",
|
|
309
|
+
border: "none", borderRadius: 8, padding: "10px 20px",
|
|
310
|
+
cursor: running ? "not-allowed" : "pointer", fontWeight: 700, fontSize: 14,
|
|
311
|
+
transition: "all 0.15s", opacity: running ? 0.6 : 1, fontFamily: "inherit",
|
|
312
|
+
}}>
|
|
313
|
+
{running ? "⏳ Ejecutando..." : completed ? "▶ Re-ejecutar" : "▶ Simular"}
|
|
314
|
+
</button>
|
|
315
|
+
<button onClick={reset} style={{
|
|
316
|
+
background: "transparent", color: C.textMuted,
|
|
317
|
+
border: `1px solid ${C.border}`, borderRadius: 8,
|
|
318
|
+
padding: "10px 14px", cursor: "pointer", fontFamily: "inherit",
|
|
319
|
+
}}>⟳</button>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Steps timeline */}
|
|
324
|
+
<div style={{ position: "relative" }}>
|
|
325
|
+
{flow.steps.map((step, i) => {
|
|
326
|
+
const isActive = currentStep === i;
|
|
327
|
+
const isDone = currentStep > i;
|
|
328
|
+
const stepColor = getStepColor(step);
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<div key={step.id} style={{ display: "flex", gap: 16, marginBottom: 8, alignItems: "flex-start" }}>
|
|
332
|
+
{/* Timeline dot */}
|
|
333
|
+
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: 36, flexShrink: 0 }}>
|
|
334
|
+
<div style={{
|
|
335
|
+
width: 36, height: 36, borderRadius: "50%",
|
|
336
|
+
display: "flex", alignItems: "center", justifyContent: "center",
|
|
337
|
+
background: isDone ? C.green + "22" : isActive ? stepColor + "33" : C.surface,
|
|
338
|
+
border: `2px solid ${isDone ? C.green : isActive ? stepColor : C.border}`,
|
|
339
|
+
color: isDone ? C.green : isActive ? stepColor : C.textMuted,
|
|
340
|
+
fontWeight: 700, fontSize: 13, transition: "all 0.3s",
|
|
341
|
+
boxShadow: isActive ? `0 0 16px ${stepColor}44` : "none",
|
|
342
|
+
}}>
|
|
343
|
+
{isDone ? "✓" : step.id}
|
|
344
|
+
</div>
|
|
345
|
+
{i < flow.steps.length - 1 && (
|
|
346
|
+
<div style={{
|
|
347
|
+
width: 2, flex: 1, minHeight: 24,
|
|
348
|
+
background: isDone ? C.green + "44" : C.border,
|
|
349
|
+
marginTop: 4,
|
|
350
|
+
}} />
|
|
351
|
+
)}
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
{/* Step card */}
|
|
355
|
+
<div style={{
|
|
356
|
+
flex: 1,
|
|
357
|
+
background: isActive ? stepColor + "11" : isDone ? C.green + "08" : C.surface,
|
|
358
|
+
border: `1px solid ${isActive ? stepColor + "66" : isDone ? C.green + "33" : C.border}`,
|
|
359
|
+
borderRadius: 10, padding: "12px 16px", marginBottom: 4,
|
|
360
|
+
transition: "all 0.3s",
|
|
361
|
+
opacity: currentStep === -1 ? 0.55 : isDone || isActive ? 1 : 0.4,
|
|
362
|
+
}}>
|
|
363
|
+
{/* Step header */}
|
|
364
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
|
|
365
|
+
<span style={{ fontSize: 16 }}>{getStepIcon(step)}</span>
|
|
366
|
+
{step.from && (
|
|
367
|
+
<>
|
|
368
|
+
<Tag color={getActorColor(step.from)}>{step.from}</Tag>
|
|
369
|
+
<span style={{ color: C.textMuted, fontSize: 12 }}>→</span>
|
|
370
|
+
</>
|
|
371
|
+
)}
|
|
372
|
+
{step.to && !Array.isArray(step.to) && (
|
|
373
|
+
<Tag color={getActorColor(step.to)}>{step.to}</Tag>
|
|
374
|
+
)}
|
|
375
|
+
{Array.isArray(step.to) && step.to.map(t => (
|
|
376
|
+
<Tag key={t} color={getActorColor(t)}>{t}</Tag>
|
|
377
|
+
))}
|
|
378
|
+
{step.label && (
|
|
379
|
+
<code style={{
|
|
380
|
+
background: C.bg, color: stepColor,
|
|
381
|
+
padding: "2px 8px", borderRadius: 4, fontSize: 12,
|
|
382
|
+
border: `1px solid ${C.border}`,
|
|
383
|
+
}}>
|
|
384
|
+
{step.label}
|
|
385
|
+
</code>
|
|
386
|
+
)}
|
|
387
|
+
{step.event && <Tag color={C.gold}>⚡ {step.event}</Tag>}
|
|
388
|
+
{step.topic && <Tag color={C.purple}>kafka:{step.topic}</Tag>}
|
|
389
|
+
</div>
|
|
390
|
+
|
|
391
|
+
<div style={{ color: C.textDim, fontSize: 12, marginTop: 6, lineHeight: 1.6 }}>
|
|
392
|
+
{step.desc}
|
|
393
|
+
</div>
|
|
394
|
+
|
|
395
|
+
{/* Sync sub-calls (shown when step is active) */}
|
|
396
|
+
{step.syncCalls && step.syncCalls.length > 0 && isActive && (
|
|
397
|
+
<div style={{ marginTop: 10, paddingTop: 10, borderTop: `1px dashed ${C.border}` }}>
|
|
398
|
+
<div style={{ color: C.textMuted, fontSize: 10, marginBottom: 6, fontWeight: 700, letterSpacing: 1 }}>
|
|
399
|
+
LLAMADAS SÍNCRONAS:
|
|
400
|
+
</div>
|
|
401
|
+
{step.syncCalls.map((sc, j) => (
|
|
402
|
+
<div key={j} style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
|
|
403
|
+
<span style={{ color: C.blue, fontSize: 11 }}>⟶</span>
|
|
404
|
+
<Tag color={getActorColor(sc.to)}>{sc.to}</Tag>
|
|
405
|
+
<code style={{
|
|
406
|
+
background: C.bg, color: C.blue,
|
|
407
|
+
padding: "1px 6px", borderRadius: 3, fontSize: 11,
|
|
408
|
+
}}>{sc.label}</code>
|
|
409
|
+
<span style={{ color: C.textMuted, fontSize: 10 }}>via {sc.port}</span>
|
|
410
|
+
</div>
|
|
411
|
+
))}
|
|
412
|
+
</div>
|
|
413
|
+
)}
|
|
414
|
+
</div>
|
|
415
|
+
</div>
|
|
416
|
+
);
|
|
417
|
+
})}
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
{completed && (
|
|
421
|
+
<div style={{
|
|
422
|
+
background: C.green + "15", border: `1px solid ${C.green}44`,
|
|
423
|
+
borderRadius: 10, padding: 16, textAlign: "center", marginTop: 8,
|
|
424
|
+
animation: "fadeIn 0.3s",
|
|
425
|
+
}}>
|
|
426
|
+
<span style={{ fontSize: 24 }}>✅</span>
|
|
427
|
+
<div style={{ color: C.green, fontWeight: 700, marginTop: 4 }}>Flujo completado exitosamente</div>
|
|
428
|
+
<div style={{ color: C.textMuted, fontSize: 12, marginTop: 2 }}>
|
|
429
|
+
{flow.steps.length} pasos · {flow.label}
|
|
430
|
+
</div>
|
|
431
|
+
</div>
|
|
432
|
+
)}
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ─── ArchitectureTab ────────────────────────────────────────────────────
|
|
438
|
+
function ArchitectureTab() {
|
|
439
|
+
const [selected, setSelected] = useState(null);
|
|
440
|
+
|
|
441
|
+
const moduleInfo = (id) => ({
|
|
442
|
+
produces: EVENTS.filter(e => e.producer === id),
|
|
443
|
+
consumes: EVENTS.filter(e => (e.consumers || []).includes(id)),
|
|
444
|
+
callsSync: SYNC_INTEGRATIONS.filter(s => s.caller === id),
|
|
445
|
+
calledBy: SYNC_INTEGRATIONS.filter(s => s.calls === id),
|
|
446
|
+
endpoints: ENDPOINTS[id] || [],
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
return (
|
|
450
|
+
<div>
|
|
451
|
+
{/* Module grid */}
|
|
452
|
+
<div style={{ marginBottom: 20 }}>
|
|
453
|
+
<div style={{ color: C.textMuted, fontSize: 11, marginBottom: 10, fontWeight: 700, letterSpacing: 1 }}>
|
|
454
|
+
MÓDULOS — clic para explorar dependencias
|
|
455
|
+
</div>
|
|
456
|
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))", gap: 10 }}>
|
|
457
|
+
{MODULES_LIST.map(m => (
|
|
458
|
+
<ModuleCard
|
|
459
|
+
key={m.id}
|
|
460
|
+
id={m.id}
|
|
461
|
+
selected={selected === m.id}
|
|
462
|
+
onClick={() => setSelected(selected === m.id ? null : m.id)}
|
|
463
|
+
/>
|
|
464
|
+
))}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
{/* Selected module detail */}
|
|
469
|
+
{selected && (() => {
|
|
470
|
+
const mod = MODULES[selected];
|
|
471
|
+
const info = moduleInfo(selected);
|
|
472
|
+
if (!mod) return null;
|
|
473
|
+
return (
|
|
474
|
+
<div style={{
|
|
475
|
+
background: C.surface, border: `1px solid ${mod.color}44`,
|
|
476
|
+
borderRadius: 12, padding: 20, marginBottom: 20,
|
|
477
|
+
animation: "fadeIn 0.2s",
|
|
478
|
+
}}>
|
|
479
|
+
<div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
|
|
480
|
+
<span style={{ fontSize: 28 }}>{mod.icon}</span>
|
|
481
|
+
<div>
|
|
482
|
+
<div style={{ fontWeight: 800, color: mod.color, fontSize: 18 }}>{mod.label}</div>
|
|
483
|
+
<div style={{ color: C.textMuted, fontSize: 12 }}>{mod.desc}</div>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
|
|
487
|
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))", gap: 16 }}>
|
|
488
|
+
{[
|
|
489
|
+
{ title: "Produce eventos", items: info.produces, color: C.gold, icon: "📤", render: e => e.event },
|
|
490
|
+
{ title: "Consume eventos", items: info.consumes, color: C.blue, icon: "📥", render: e => e.event },
|
|
491
|
+
{ title: "Llama síncronamente", items: info.callsSync, color: C.purple, icon: "⟶", render: s => `→ ${s.calls} (${s.port})` },
|
|
492
|
+
{ title: "Es llamado por", items: info.calledBy, color: C.green, icon: "⟵", render: s => `← ${s.caller} (${s.port})` },
|
|
493
|
+
{ title: "Endpoints expuestos", items: info.endpoints, color: mod.color, icon: "🔌", render: e => e },
|
|
494
|
+
].filter(sec => sec.items.length > 0).map(section => (
|
|
495
|
+
<div key={section.title}>
|
|
496
|
+
<div style={{ color: section.color, fontSize: 10, fontWeight: 700, marginBottom: 8, letterSpacing: 0.5 }}>
|
|
497
|
+
{section.icon} {section.title.toUpperCase()}
|
|
498
|
+
</div>
|
|
499
|
+
{section.items.map((item, i) => (
|
|
500
|
+
<div key={i} style={{
|
|
501
|
+
color: C.textDim, fontSize: 12, padding: "5px 0",
|
|
502
|
+
borderBottom: `1px solid ${C.border}`, fontFamily: "monospace",
|
|
503
|
+
}}>
|
|
504
|
+
{section.render(item)}
|
|
505
|
+
</div>
|
|
506
|
+
))}
|
|
507
|
+
</div>
|
|
508
|
+
))}
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
);
|
|
512
|
+
})()}
|
|
513
|
+
|
|
514
|
+
{/* Sync integrations graph */}
|
|
515
|
+
{SYNC_INTEGRATIONS.length > 0 && (
|
|
516
|
+
<div style={{ marginBottom: 24 }}>
|
|
517
|
+
<div style={{ color: C.textMuted, fontSize: 11, marginBottom: 10, fontWeight: 700, letterSpacing: 1 }}>
|
|
518
|
+
DEPENDENCIAS SÍNCRONAS ({SYNC_INTEGRATIONS.length} puertos)
|
|
519
|
+
</div>
|
|
520
|
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))", gap: 10 }}>
|
|
521
|
+
{SYNC_INTEGRATIONS.map((s, i) => {
|
|
522
|
+
const callerMod = MODULES[s.caller];
|
|
523
|
+
const calleeMod = MODULES[s.calls];
|
|
524
|
+
return (
|
|
525
|
+
<div key={i} style={{
|
|
526
|
+
background: C.surface, border: `1px solid ${C.border}`,
|
|
527
|
+
borderRadius: 10, padding: "12px 16px",
|
|
528
|
+
}}>
|
|
529
|
+
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 6, marginBottom: 8 }}>
|
|
530
|
+
<Tag color={callerMod?.color || C.textMuted}>{s.caller}</Tag>
|
|
531
|
+
<span style={{ color: C.textMuted, fontSize: 14, fontWeight: 700 }}>→</span>
|
|
532
|
+
<Tag color={calleeMod?.color || C.textMuted}>{s.calls}</Tag>
|
|
533
|
+
<Tag color={C.purple}>{s.port}</Tag>
|
|
534
|
+
</div>
|
|
535
|
+
<div style={{ marginLeft: 4 }}>
|
|
536
|
+
{(s.endpoints || []).map((ep, j) => (
|
|
537
|
+
<div key={j} style={{ color: C.textDim, fontSize: 11, fontFamily: "monospace", padding: "2px 0" }}>
|
|
538
|
+
· {ep}
|
|
539
|
+
</div>
|
|
540
|
+
))}
|
|
541
|
+
</div>
|
|
542
|
+
</div>
|
|
543
|
+
);
|
|
544
|
+
})}
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
)}
|
|
548
|
+
|
|
549
|
+
{/* Kafka topics */}
|
|
550
|
+
<div>
|
|
551
|
+
<div style={{ color: C.textMuted, fontSize: 11, marginBottom: 10, fontWeight: 700, letterSpacing: 1 }}>
|
|
552
|
+
KAFKA TOPICS ({EVENTS.length} eventos)
|
|
553
|
+
</div>
|
|
554
|
+
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: 8 }}>
|
|
555
|
+
{EVENTS.map(e => (
|
|
556
|
+
<div key={e.event} style={{
|
|
557
|
+
background: C.surface, border: `1px solid ${C.border}`,
|
|
558
|
+
borderRadius: 8, padding: "10px 14px",
|
|
559
|
+
}}>
|
|
560
|
+
<div style={{ display: "flex", alignItems: "center", gap: 6, marginBottom: 6 }}>
|
|
561
|
+
<Tag color={C.gold}>{e.topic}</Tag>
|
|
562
|
+
</div>
|
|
563
|
+
<div style={{ display: "flex", alignItems: "center", gap: 6, flexWrap: "wrap" }}>
|
|
564
|
+
<Tag color={MODULES[e.producer]?.color || C.textMuted}>{e.producer}</Tag>
|
|
565
|
+
<span style={{ color: C.textMuted, fontSize: 11 }}>→</span>
|
|
566
|
+
{(e.consumers || []).map(c => (
|
|
567
|
+
<Tag key={c} color={MODULES[c]?.color || C.textMuted}>{c}</Tag>
|
|
568
|
+
))}
|
|
569
|
+
</div>
|
|
570
|
+
</div>
|
|
571
|
+
))}
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ─── DiagramTab ──────────────────────────────────────────────────────────
|
|
579
|
+
function DiagramTab() {
|
|
580
|
+
const containerRef = useRef(null);
|
|
581
|
+
const networkRef = useRef(null);
|
|
582
|
+
const edgesDataRef = useRef(null);
|
|
583
|
+
const edgeGroupMapRef = useRef({});
|
|
584
|
+
const eventEdgesMapRef = useRef({});
|
|
585
|
+
const edgeLabelMapRef = useRef({});
|
|
586
|
+
const [physicsOn, setPhysicsOn] = useState(true);
|
|
587
|
+
const [hoveredNode, setHoveredNode] = useState(null);
|
|
588
|
+
const [hoveredEvent, setHoveredEvent] = useState(null); // { name, producer, consumers[] }
|
|
589
|
+
|
|
590
|
+
// Build vis datasets from injected data
|
|
591
|
+
function buildDatasets() {
|
|
592
|
+
const visNodes = MODULES_LIST.map(mod => ({
|
|
593
|
+
id: mod.id,
|
|
594
|
+
label: mod.icon + "\n" + mod.label,
|
|
595
|
+
title: mod.desc,
|
|
596
|
+
color: {
|
|
597
|
+
background: mod.color + "22",
|
|
598
|
+
border: mod.color,
|
|
599
|
+
highlight: { background: mod.color + "44", border: mod.color },
|
|
600
|
+
hover: { background: mod.color + "33", border: mod.color },
|
|
601
|
+
},
|
|
602
|
+
font: { color: C.text, size: 13, face: "'Plus Jakarta Sans', sans-serif", multi: false },
|
|
603
|
+
shape: "box",
|
|
604
|
+
borderWidth: 2,
|
|
605
|
+
borderWidthSelected: 3,
|
|
606
|
+
margin: 10,
|
|
607
|
+
}));
|
|
608
|
+
|
|
609
|
+
const visEdges = [];
|
|
610
|
+
|
|
611
|
+
// Sync edges — solid blue
|
|
612
|
+
SYNC_INTEGRATIONS.forEach((s, i) => {
|
|
613
|
+
visEdges.push({
|
|
614
|
+
id: "sync-" + i,
|
|
615
|
+
from: s.caller,
|
|
616
|
+
to: s.calls,
|
|
617
|
+
label: s.port,
|
|
618
|
+
dashes: false,
|
|
619
|
+
color: { color: C.blue, highlight: C.blue, hover: C.blue },
|
|
620
|
+
font: { color: C.blue, size: 10, face: "'JetBrains Mono', monospace",
|
|
621
|
+
background: C.bg, strokeWidth: 0, align: "middle" },
|
|
622
|
+
arrows: { to: { enabled: true, scaleFactor: 0.7 } },
|
|
623
|
+
width: 2,
|
|
624
|
+
smooth: { enabled: true, type: "curvedCW", roundness: 0.15 },
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Broker node — shown only if there are async events
|
|
629
|
+
if (EVENTS.length > 0) {
|
|
630
|
+
const brokerLabel = ((window.__EVA_DATA__.brokerName || "Kafka") + "\nBroker");
|
|
631
|
+
visNodes.push({
|
|
632
|
+
id: "__broker__",
|
|
633
|
+
label: "⚡ " + brokerLabel,
|
|
634
|
+
title: "Message broker — retransmite eventos asíncronos entre módulos",
|
|
635
|
+
color: {
|
|
636
|
+
background: C.gold, border: "#c49a00",
|
|
637
|
+
highlight: { background: "#ffd84d", border: "#c49a00" },
|
|
638
|
+
hover: { background: "#ffd84d", border: "#c49a00" },
|
|
639
|
+
},
|
|
640
|
+
font: { color: "#0d1f3c", size: 13, face: "'Plus Jakarta Sans', sans-serif", bold: true },
|
|
641
|
+
shape: "box", borderWidth: 2, borderWidthSelected: 3, margin: 12,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Async edges — routed through broker + group maps for hover highlighting
|
|
646
|
+
const edgeGroupMap = {}; // edgeId → eventIndex
|
|
647
|
+
const eventEdgesMap = {}; // eventIndex → [edgeIds]
|
|
648
|
+
const edgeLabelMap = {}; // edgeId → original label
|
|
649
|
+
EVENTS.forEach((ev, i) => {
|
|
650
|
+
const shortLabel = ev.event.replace(/Event$/, "");
|
|
651
|
+
eventEdgesMap[i] = [];
|
|
652
|
+
// producer → broker
|
|
653
|
+
const pubId = "async-pub-" + i;
|
|
654
|
+
edgeGroupMap[pubId] = i;
|
|
655
|
+
eventEdgesMap[i].push(pubId);
|
|
656
|
+
edgeLabelMap[pubId] = shortLabel;
|
|
657
|
+
visEdges.push({
|
|
658
|
+
id: pubId,
|
|
659
|
+
from: ev.producer,
|
|
660
|
+
to: "__broker__",
|
|
661
|
+
label: shortLabel,
|
|
662
|
+
dashes: [6, 4],
|
|
663
|
+
color: { color: C.gold + "cc", highlight: C.gold, hover: C.gold },
|
|
664
|
+
font: { color: C.gold, size: 10, face: "'JetBrains Mono', monospace",
|
|
665
|
+
background: C.bg, strokeWidth: 0, align: "middle" },
|
|
666
|
+
arrows: { to: { enabled: true, scaleFactor: 0.6 } },
|
|
667
|
+
width: 1.5,
|
|
668
|
+
smooth: { enabled: true, type: "dynamic" },
|
|
669
|
+
});
|
|
670
|
+
// broker → each consumer
|
|
671
|
+
(ev.consumers || []).forEach((consumer, j) => {
|
|
672
|
+
const subId = "async-sub-" + i + "-" + j;
|
|
673
|
+
edgeGroupMap[subId] = i;
|
|
674
|
+
eventEdgesMap[i].push(subId);
|
|
675
|
+
edgeLabelMap[subId] = "";
|
|
676
|
+
visEdges.push({
|
|
677
|
+
id: subId,
|
|
678
|
+
from: "__broker__",
|
|
679
|
+
to: consumer,
|
|
680
|
+
label: "",
|
|
681
|
+
dashes: [4, 4],
|
|
682
|
+
color: { color: C.gold + "77", highlight: C.gold, hover: C.gold },
|
|
683
|
+
font: { color: "rgba(0,0,0,0)", strokeWidth: 0 },
|
|
684
|
+
arrows: { to: { enabled: true, scaleFactor: 0.5 } },
|
|
685
|
+
width: 1.5,
|
|
686
|
+
smooth: { enabled: true, type: "dynamic" },
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
return { visNodes, visEdges, edgeGroupMap, eventEdgesMap, edgeLabelMap };
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function initNetwork(phys) {
|
|
695
|
+
if (!containerRef.current) return;
|
|
696
|
+
if (networkRef.current) { networkRef.current.destroy(); }
|
|
697
|
+
|
|
698
|
+
const { visNodes, visEdges, edgeGroupMap, eventEdgesMap, edgeLabelMap } = buildDatasets();
|
|
699
|
+
edgeGroupMapRef.current = edgeGroupMap;
|
|
700
|
+
eventEdgesMapRef.current = eventEdgesMap;
|
|
701
|
+
edgeLabelMapRef.current = edgeLabelMap;
|
|
702
|
+
const edgesDS = new vis.DataSet(visEdges);
|
|
703
|
+
edgesDataRef.current = edgesDS;
|
|
704
|
+
const data = {
|
|
705
|
+
nodes: new vis.DataSet(visNodes),
|
|
706
|
+
edges: edgesDS,
|
|
707
|
+
};
|
|
708
|
+
const options = {
|
|
709
|
+
physics: {
|
|
710
|
+
enabled: phys,
|
|
711
|
+
solver: "forceAtlas2Based",
|
|
712
|
+
forceAtlas2Based: { gravitationalConstant: -60, springLength: 160, springConstant: 0.05, damping: 0.5 },
|
|
713
|
+
stabilization: { iterations: 400, updateInterval: 25 },
|
|
714
|
+
},
|
|
715
|
+
interaction: { dragNodes: true, zoomView: true, hover: true, tooltipDelay: 150 },
|
|
716
|
+
edges: { selectionWidth: 2 },
|
|
717
|
+
nodes: { chosen: true },
|
|
718
|
+
layout: { randomSeed: 42 },
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
const net = new vis.Network(containerRef.current, data, options);
|
|
722
|
+
net.once("stabilizationIterationsDone", () => {
|
|
723
|
+
net.setOptions({ physics: { enabled: false } });
|
|
724
|
+
setPhysicsOn(false);
|
|
725
|
+
});
|
|
726
|
+
net.on("hoverNode", (p) => setHoveredNode(p.node));
|
|
727
|
+
net.on("blurNode", () => setHoveredNode(null));
|
|
728
|
+
net.on("hoverEdge", (p) => {
|
|
729
|
+
const groupIdx = edgeGroupMapRef.current[p.edge];
|
|
730
|
+
if (groupIdx === undefined || !edgesDataRef.current) return;
|
|
731
|
+
const groupIds = eventEdgesMapRef.current[groupIdx] || [];
|
|
732
|
+
const allIds = Object.keys(edgeGroupMapRef.current);
|
|
733
|
+
const RED = "#ff6b6b";
|
|
734
|
+
const GREEN = "#4ade80";
|
|
735
|
+
const FONT_PUB = { color: C.gold, size: 10, face: "'JetBrains Mono', monospace", background: C.bg, strokeWidth: 0, align: "middle" };
|
|
736
|
+
const FONT_HID = { color: "rgba(0,0,0,0)", background: "rgba(0,0,0,0)", strokeWidth: 0 };
|
|
737
|
+
edgesDataRef.current.update(allIds.map(id => {
|
|
738
|
+
const inGroup = groupIds.includes(id);
|
|
739
|
+
const isPub = id.startsWith("async-pub-");
|
|
740
|
+
return inGroup
|
|
741
|
+
? { id,
|
|
742
|
+
color: { color: RED, highlight: RED, hover: RED },
|
|
743
|
+
font: isPub ? { ...FONT_PUB, color: GREEN, background: "rgba(0,0,0,0)", vadjust: -14 } : FONT_HID,
|
|
744
|
+
label: edgeLabelMapRef.current[id],
|
|
745
|
+
width: 1.5 }
|
|
746
|
+
: { id,
|
|
747
|
+
color: { color: C.gold + "18", highlight: C.gold + "22", hover: C.gold + "22" },
|
|
748
|
+
font: FONT_HID,
|
|
749
|
+
label: "",
|
|
750
|
+
width: 1.5 };
|
|
751
|
+
}));
|
|
752
|
+
// update event tooltip
|
|
753
|
+
const ev = EVENTS[groupIdx];
|
|
754
|
+
if (ev) setHoveredEvent({ name: ev.event, producer: ev.producer, consumers: ev.consumers || [] });
|
|
755
|
+
});
|
|
756
|
+
net.on("blurEdge", () => {
|
|
757
|
+
setHoveredEvent(null);
|
|
758
|
+
if (!edgesDataRef.current) return;
|
|
759
|
+
const allIds = Object.keys(edgeGroupMapRef.current);
|
|
760
|
+
const FONT_PUB = { color: C.gold, size: 10, face: "'JetBrains Mono', monospace", background: C.bg, strokeWidth: 0, align: "middle" };
|
|
761
|
+
const FONT_SUB = { color: "rgba(0,0,0,0)", strokeWidth: 0 };
|
|
762
|
+
edgesDataRef.current.update(allIds.map(id => {
|
|
763
|
+
const isPub = id.startsWith("async-pub-");
|
|
764
|
+
return {
|
|
765
|
+
id,
|
|
766
|
+
color: isPub
|
|
767
|
+
? { color: C.gold + "cc", highlight: C.gold, hover: C.gold }
|
|
768
|
+
: { color: C.gold + "77", highlight: C.gold, hover: C.gold },
|
|
769
|
+
font: isPub ? FONT_PUB : FONT_SUB,
|
|
770
|
+
label: edgeLabelMapRef.current[id],
|
|
771
|
+
width: 1.5,
|
|
772
|
+
};
|
|
773
|
+
}));
|
|
774
|
+
});
|
|
775
|
+
networkRef.current = net;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
useEffect(() => {
|
|
779
|
+
initNetwork(true);
|
|
780
|
+
return () => { if (networkRef.current) networkRef.current.destroy(); };
|
|
781
|
+
}, []);
|
|
782
|
+
|
|
783
|
+
const togglePhysics = () => {
|
|
784
|
+
const next = !physicsOn;
|
|
785
|
+
setPhysicsOn(next);
|
|
786
|
+
if (networkRef.current) networkRef.current.setOptions({ physics: { enabled: next } });
|
|
787
|
+
};
|
|
788
|
+
|
|
789
|
+
const fitView = () => networkRef.current && networkRef.current.fit({ animation: { duration: 400, easingFunction: "easeInOutQuad" } });
|
|
790
|
+
const resetNet = () => initNetwork(true);
|
|
791
|
+
|
|
792
|
+
const hoveredMod = (hoveredNode && hoveredNode !== "__broker__") ? MODULES[hoveredNode] : null;
|
|
793
|
+
const hoveredBroker = hoveredNode === "__broker__";
|
|
794
|
+
// overlay priority: hoveredEvent > node tooltips
|
|
795
|
+
const showOverlay = hoveredEvent || hoveredMod || hoveredBroker;
|
|
796
|
+
|
|
797
|
+
return (
|
|
798
|
+
<div>
|
|
799
|
+
{/* Toolbar */}
|
|
800
|
+
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 14, flexWrap: "wrap" }}>
|
|
801
|
+
<button onClick={togglePhysics} style={{
|
|
802
|
+
background: physicsOn ? C.blue + "22" : C.surface,
|
|
803
|
+
border: `1px solid ${physicsOn ? C.blue : C.border}`,
|
|
804
|
+
color: physicsOn ? C.blue : C.textMuted,
|
|
805
|
+
borderRadius: 8, padding: "7px 14px", cursor: "pointer",
|
|
806
|
+
fontFamily: "inherit", fontSize: 12, fontWeight: 600,
|
|
807
|
+
transition: "all 0.15s",
|
|
808
|
+
}}>
|
|
809
|
+
{physicsOn ? "⏸ Detener física" : "▶ Activar física"}
|
|
810
|
+
</button>
|
|
811
|
+
<button onClick={fitView} style={{
|
|
812
|
+
background: C.surface, border: `1px solid ${C.border}`,
|
|
813
|
+
color: C.textMuted, borderRadius: 8, padding: "7px 14px",
|
|
814
|
+
cursor: "pointer", fontFamily: "inherit", fontSize: 12,
|
|
815
|
+
}}>⊞ Ajustar vista</button>
|
|
816
|
+
<button onClick={resetNet} style={{
|
|
817
|
+
background: C.surface, border: `1px solid ${C.border}`,
|
|
818
|
+
color: C.textMuted, borderRadius: 8, padding: "7px 14px",
|
|
819
|
+
cursor: "pointer", fontFamily: "inherit", fontSize: 12,
|
|
820
|
+
}}>⟳ Reiniciar</button>
|
|
821
|
+
<div style={{ flex: 1 }} />
|
|
822
|
+
</div>
|
|
823
|
+
|
|
824
|
+
{/* Canvas */}
|
|
825
|
+
<div style={{
|
|
826
|
+
position: "relative",
|
|
827
|
+
background: C.surface, border: `1px solid ${C.border}`,
|
|
828
|
+
borderRadius: 12, overflow: "hidden",
|
|
829
|
+
}}>
|
|
830
|
+
<div ref={containerRef} style={{ width: "100%", height: 720 }} />
|
|
831
|
+
{showOverlay && (
|
|
832
|
+
<div style={{
|
|
833
|
+
position: "absolute", bottom: 12, left: 12,
|
|
834
|
+
background: C.bg + "ee",
|
|
835
|
+
border: `1px solid ${
|
|
836
|
+
hoveredEvent ? "#4ade80"
|
|
837
|
+
: hoveredBroker ? C.gold
|
|
838
|
+
: hoveredMod.color}66`,
|
|
839
|
+
borderRadius: 8, padding: "6px 12px", backdropFilter: "blur(4px)",
|
|
840
|
+
fontSize: 12, fontWeight: 600,
|
|
841
|
+
animation: "fadeIn 0.15s", pointerEvents: "none",
|
|
842
|
+
maxWidth: "80%",
|
|
843
|
+
}}>
|
|
844
|
+
{hoveredEvent
|
|
845
|
+
? <span style={{ color: "#4ade80" }}>
|
|
846
|
+
⬡ {hoveredEvent.name}
|
|
847
|
+
<span style={{ color: C.textMuted, fontWeight: 400 }}> — </span>
|
|
848
|
+
<span style={{ color: C.textDim, fontWeight: 400 }}>
|
|
849
|
+
{hoveredEvent.producer} → [{hoveredEvent.consumers.join(", ")}]
|
|
850
|
+
</span>
|
|
851
|
+
</span>
|
|
852
|
+
: hoveredBroker
|
|
853
|
+
? <span style={{ color: C.gold }}>⚡ Kafka Broker — <span style={{ color: C.textDim, fontWeight: 400 }}>Retransmite eventos asíncronos entre módulos</span></span>
|
|
854
|
+
: <span style={{ color: hoveredMod.color }}>{hoveredMod.icon} {hoveredMod.label} — <span style={{ color: C.textDim, fontWeight: 400 }}>{hoveredMod.desc}</span></span>
|
|
855
|
+
}
|
|
856
|
+
</div>
|
|
857
|
+
)}
|
|
858
|
+
</div>
|
|
859
|
+
|
|
860
|
+
{/* Legend */}
|
|
861
|
+
<div style={{
|
|
862
|
+
display: "flex", gap: 24, marginTop: 14, flexWrap: "wrap",
|
|
863
|
+
alignItems: "center", paddingLeft: 4,
|
|
864
|
+
}}>
|
|
865
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
866
|
+
<svg width="36" height="12">
|
|
867
|
+
<line x1="0" y1="6" x2="36" y2="6"
|
|
868
|
+
stroke={C.blue} strokeWidth="2" strokeLinecap="round" />
|
|
869
|
+
<polygon points="32,3 36,6 32,9" fill={C.blue} />
|
|
870
|
+
</svg>
|
|
871
|
+
<span style={{ color: C.textMuted, fontSize: 12 }}>Síncrono (HTTP)</span>
|
|
872
|
+
</div>
|
|
873
|
+
<div style={{ display: "flex", alignItems: "center", gap: 8 }}>
|
|
874
|
+
<svg width="36" height="12">
|
|
875
|
+
<line x1="0" y1="6" x2="36" y2="6"
|
|
876
|
+
stroke={C.gold} strokeWidth="1.5" strokeDasharray="6 4" strokeLinecap="round" />
|
|
877
|
+
<polygon points="32,3 36,6 32,9" fill={C.gold} />
|
|
878
|
+
</svg>
|
|
879
|
+
<span style={{ color: C.textMuted, fontSize: 12 }}>Asíncrono (Kafka)</span>
|
|
880
|
+
</div>
|
|
881
|
+
<div style={{ color: C.textMuted, fontSize: 11, marginLeft: "auto" }}>
|
|
882
|
+
{MODULES_LIST.length} módulos · {SYNC_INTEGRATIONS.length} puertos sync · {EVENTS.length} eventos
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
</div>
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// ─── App root ───────────────────────────────────────────────────────────
|
|
890
|
+
function App() {
|
|
891
|
+
const [tab, setTab] = useState("validation");
|
|
892
|
+
|
|
893
|
+
const tabs = [
|
|
894
|
+
{ id: "validation", label: "Validación", icon: "🔍" },
|
|
895
|
+
{ id: "flows", label: "Simulador de flujos", icon: "▶" },
|
|
896
|
+
{ id: "architecture", label: "Arquitectura", icon: "🗺️" },
|
|
897
|
+
{ id: "diagram", label: "Diagrama", icon: "◈" },
|
|
898
|
+
];
|
|
899
|
+
|
|
900
|
+
const sys = window.__EVA_DATA__;
|
|
901
|
+
const tech = [];
|
|
902
|
+
// Detect tech from systemConfig embedded in data
|
|
903
|
+
if (sys.events && sys.events.length > 0) tech.push({ label: "Kafka", color: C.gold });
|
|
904
|
+
|
|
905
|
+
return (
|
|
906
|
+
<div style={{ background: C.bg, minHeight: "100vh", color: C.text, fontFamily: "'Plus Jakarta Sans', system-ui, -apple-system, sans-serif" }}>
|
|
907
|
+
|
|
908
|
+
{/* Header */}
|
|
909
|
+
<div style={{ borderBottom: `1px solid ${C.border}`, padding: "0 24px" }}>
|
|
910
|
+
<div style={{ maxWidth: 1100, margin: "0 auto", display: "flex", alignItems: "center", gap: 16, height: 64 }}>
|
|
911
|
+
<div>
|
|
912
|
+
<span style={{ fontWeight: 900, fontSize: 18, color: C.accent, letterSpacing: -0.5 }}>eva4j</span>
|
|
913
|
+
<span style={{ fontWeight: 400, fontSize: 14, color: C.textMuted, marginLeft: 8 }}>/ architecture validator</span>
|
|
914
|
+
</div>
|
|
915
|
+
<div style={{ height: 20, width: 1, background: C.border }} />
|
|
916
|
+
<div style={{ fontWeight: 700, fontSize: 16, color: C.text }}>{systemName}</div>
|
|
917
|
+
<div style={{ flex: 1 }} />
|
|
918
|
+
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
|
919
|
+
{sys.events && sys.events.length > 0 && (
|
|
920
|
+
<Tag color={C.gold}>Kafka · {sys.events.length} events</Tag>
|
|
921
|
+
)}
|
|
922
|
+
{sys.syncIntegrations && sys.syncIntegrations.length > 0 && (
|
|
923
|
+
<Tag color={C.blue}>Sync · {sys.syncIntegrations.length} ports</Tag>
|
|
924
|
+
)}
|
|
925
|
+
<Tag color={C.purple}>{sys.modules.length} modules</Tag>
|
|
926
|
+
</div>
|
|
927
|
+
</div>
|
|
928
|
+
</div>
|
|
929
|
+
|
|
930
|
+
{/* Tabs */}
|
|
931
|
+
<div style={{ borderBottom: `1px solid ${C.border}`, padding: "0 24px" }}>
|
|
932
|
+
<div style={{ maxWidth: 1100, margin: "0 auto", display: "flex", gap: 4 }}>
|
|
933
|
+
{tabs.map(t => (
|
|
934
|
+
<button key={t.id} onClick={() => setTab(t.id)} style={{
|
|
935
|
+
background: "transparent", border: "none",
|
|
936
|
+
color: tab === t.id ? C.text : C.textMuted,
|
|
937
|
+
padding: "16px 20px", cursor: "pointer",
|
|
938
|
+
fontWeight: tab === t.id ? 700 : 400,
|
|
939
|
+
borderBottom: `2px solid ${tab === t.id ? C.accent : "transparent"}`,
|
|
940
|
+
fontSize: 13, transition: "all 0.15s", fontFamily: "inherit",
|
|
941
|
+
display: "flex", alignItems: "center", gap: 8,
|
|
942
|
+
}}>
|
|
943
|
+
<span>{t.icon}</span> {t.label}
|
|
944
|
+
</button>
|
|
945
|
+
))}
|
|
946
|
+
<div style={{ flex: 1 }} />
|
|
947
|
+
<div style={{ display: "flex", alignItems: "center" }}>
|
|
948
|
+
<span style={{ color: C.textMuted, fontSize: 10 }}>
|
|
949
|
+
generated {new Date(generatedAt).toLocaleString()}
|
|
950
|
+
</span>
|
|
951
|
+
</div>
|
|
952
|
+
</div>
|
|
953
|
+
</div>
|
|
954
|
+
|
|
955
|
+
{/* Tab content */}
|
|
956
|
+
<div style={{ maxWidth: 1100, margin: "0 auto", padding: "28px 24px" }}>
|
|
957
|
+
{tab === "validation" && <ValidationTab />}
|
|
958
|
+
{tab === "flows" && <FlowSimulator />}
|
|
959
|
+
{tab === "architecture" && <ArchitectureTab />}
|
|
960
|
+
{tab === "diagram" && <DiagramTab />}
|
|
961
|
+
</div>
|
|
962
|
+
</div>
|
|
963
|
+
);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// Mount
|
|
967
|
+
const root = ReactDOM.createRoot(document.getElementById("root"));
|
|
968
|
+
root.render(React.createElement(App, null));
|
|
969
|
+
</script>
|
|
970
|
+
</body>
|
|
971
|
+
</html>
|