dbwatcher 0.1.5 → 1.1.0

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -210
  3. data/app/assets/config/dbwatcher_manifest.js +15 -0
  4. data/app/assets/javascripts/dbwatcher/alpine_registrations.js +39 -0
  5. data/app/assets/javascripts/dbwatcher/auto_init.js +23 -0
  6. data/app/assets/javascripts/dbwatcher/components/base.js +141 -0
  7. data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +1008 -0
  8. data/app/assets/javascripts/dbwatcher/components/diagrams.js +449 -0
  9. data/app/assets/javascripts/dbwatcher/components/summary.js +234 -0
  10. data/app/assets/javascripts/dbwatcher/core/alpine_store.js +138 -0
  11. data/app/assets/javascripts/dbwatcher/core/api_client.js +162 -0
  12. data/app/assets/javascripts/dbwatcher/core/component_loader.js +70 -0
  13. data/app/assets/javascripts/dbwatcher/core/component_registry.js +94 -0
  14. data/app/assets/javascripts/dbwatcher/dbwatcher.js +120 -0
  15. data/app/assets/javascripts/dbwatcher/services/mermaid.js +315 -0
  16. data/app/assets/javascripts/dbwatcher/services/mermaid_service.js +199 -0
  17. data/app/assets/javascripts/dbwatcher/vendor/date-fns-browser.js +99 -0
  18. data/app/assets/javascripts/dbwatcher/vendor/lodash.min.js +140 -0
  19. data/app/assets/javascripts/dbwatcher/vendor/tabulator.min.js +3 -0
  20. data/app/assets/stylesheets/dbwatcher/application.css +423 -0
  21. data/app/assets/stylesheets/dbwatcher/application.scss +15 -0
  22. data/app/assets/stylesheets/dbwatcher/components/_badges.scss +38 -0
  23. data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +162 -0
  24. data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +51 -0
  25. data/app/assets/stylesheets/dbwatcher/components/_forms.scss +27 -0
  26. data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +55 -0
  27. data/app/assets/stylesheets/dbwatcher/core/_base.scss +34 -0
  28. data/app/assets/stylesheets/dbwatcher/core/_variables.scss +47 -0
  29. data/app/assets/stylesheets/dbwatcher/vendor/tabulator.min.css +2 -0
  30. data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +64 -0
  31. data/app/controllers/dbwatcher/base_controller.rb +101 -0
  32. data/app/controllers/dbwatcher/dashboard_controller.rb +20 -0
  33. data/app/controllers/dbwatcher/queries_controller.rb +24 -0
  34. data/app/controllers/dbwatcher/sessions_controller.rb +30 -20
  35. data/app/controllers/dbwatcher/tables_controller.rb +38 -0
  36. data/app/helpers/dbwatcher/application_helper.rb +103 -0
  37. data/app/helpers/dbwatcher/component_helper.rb +29 -0
  38. data/app/helpers/dbwatcher/diagram_helper.rb +110 -0
  39. data/app/helpers/dbwatcher/formatting_helper.rb +108 -0
  40. data/app/helpers/dbwatcher/session_helper.rb +28 -0
  41. data/app/views/dbwatcher/dashboard/index.html.erb +177 -0
  42. data/app/views/dbwatcher/queries/index.html.erb +240 -0
  43. data/app/views/dbwatcher/sessions/_changes_tab.html.erb +265 -0
  44. data/app/views/dbwatcher/sessions/_diagrams_tab.html.erb +166 -0
  45. data/app/views/dbwatcher/sessions/_session_header.html.erb +11 -0
  46. data/app/views/dbwatcher/sessions/_summary_tab.html.erb +88 -0
  47. data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +12 -0
  48. data/app/views/dbwatcher/sessions/changes.html.erb +21 -0
  49. data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +44 -0
  50. data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +96 -0
  51. data/app/views/dbwatcher/sessions/diagrams.html.erb +21 -0
  52. data/app/views/dbwatcher/sessions/index.html.erb +124 -27
  53. data/app/views/dbwatcher/sessions/shared/_layout.html.erb +8 -0
  54. data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +35 -0
  55. data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +25 -0
  56. data/app/views/dbwatcher/sessions/show.html.erb +3 -149
  57. data/app/views/dbwatcher/sessions/summary.html.erb +21 -0
  58. data/app/views/dbwatcher/shared/_badge.html.erb +4 -0
  59. data/app/views/dbwatcher/shared/_data_table.html.erb +20 -0
  60. data/app/views/dbwatcher/shared/_header.html.erb +7 -0
  61. data/app/views/dbwatcher/shared/_page_layout.html.erb +20 -0
  62. data/app/views/dbwatcher/shared/_section_panel.html.erb +9 -0
  63. data/app/views/dbwatcher/shared/_stats_card.html.erb +11 -0
  64. data/app/views/dbwatcher/shared/_tab_bar.html.erb +6 -0
  65. data/app/views/dbwatcher/tables/changes.html.erb +225 -0
  66. data/app/views/dbwatcher/tables/index.html.erb +123 -0
  67. data/app/views/dbwatcher/tables/show.html.erb +86 -0
  68. data/app/views/layouts/dbwatcher/application.html.erb +252 -25
  69. data/bin/compile_scss +49 -0
  70. data/config/routes.rb +43 -3
  71. data/lib/dbwatcher/configuration.rb +103 -1
  72. data/lib/dbwatcher/engine.rb +28 -13
  73. data/lib/dbwatcher/logging.rb +72 -0
  74. data/lib/dbwatcher/services/analyzers/session_data_processor.rb +98 -0
  75. data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +202 -0
  76. data/lib/dbwatcher/services/api/base_api_service.rb +100 -0
  77. data/lib/dbwatcher/services/api/changes_data_service.rb +112 -0
  78. data/lib/dbwatcher/services/api/diagram_data_service.rb +145 -0
  79. data/lib/dbwatcher/services/api/summary_data_service.rb +158 -0
  80. data/lib/dbwatcher/services/base_service.rb +64 -0
  81. data/lib/dbwatcher/services/dashboard_data_aggregator.rb +121 -0
  82. data/lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb +162 -0
  83. data/lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb +354 -0
  84. data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +502 -0
  85. data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +564 -0
  86. data/lib/dbwatcher/services/diagram_data/attribute.rb +154 -0
  87. data/lib/dbwatcher/services/diagram_data/dataset.rb +278 -0
  88. data/lib/dbwatcher/services/diagram_data/entity.rb +180 -0
  89. data/lib/dbwatcher/services/diagram_data/relationship.rb +188 -0
  90. data/lib/dbwatcher/services/diagram_data/relationship_params.rb +55 -0
  91. data/lib/dbwatcher/services/diagram_data.rb +65 -0
  92. data/lib/dbwatcher/services/diagram_error_handler.rb +239 -0
  93. data/lib/dbwatcher/services/diagram_generator.rb +154 -0
  94. data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +149 -0
  95. data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +49 -0
  96. data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +52 -0
  97. data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +52 -0
  98. data/lib/dbwatcher/services/diagram_system.rb +69 -0
  99. data/lib/dbwatcher/services/diagram_type_registry.rb +164 -0
  100. data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +127 -0
  101. data/lib/dbwatcher/services/mermaid_syntax/cardinality_mapper.rb +90 -0
  102. data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +136 -0
  103. data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +46 -0
  104. data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +116 -0
  105. data/lib/dbwatcher/services/mermaid_syntax/flowchart_builder.rb +109 -0
  106. data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +102 -0
  107. data/lib/dbwatcher/services/mermaid_syntax_builder.rb +155 -0
  108. data/lib/dbwatcher/services/query_filter_processor.rb +114 -0
  109. data/lib/dbwatcher/services/table_statistics_collector.rb +119 -0
  110. data/lib/dbwatcher/sql_logger.rb +107 -0
  111. data/lib/dbwatcher/storage/api/base_api.rb +134 -0
  112. data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +59 -0
  113. data/lib/dbwatcher/storage/api/query_api.rb +95 -0
  114. data/lib/dbwatcher/storage/api/session_api.rb +181 -0
  115. data/lib/dbwatcher/storage/api/table_api.rb +86 -0
  116. data/lib/dbwatcher/storage/base_storage.rb +120 -0
  117. data/lib/dbwatcher/storage/change_processor.rb +65 -0
  118. data/lib/dbwatcher/storage/concerns/data_normalizer.rb +134 -0
  119. data/lib/dbwatcher/storage/concerns/error_handler.rb +75 -0
  120. data/lib/dbwatcher/storage/concerns/timestampable.rb +74 -0
  121. data/lib/dbwatcher/storage/concerns/validatable.rb +117 -0
  122. data/lib/dbwatcher/storage/date_helper.rb +21 -0
  123. data/lib/dbwatcher/storage/errors.rb +86 -0
  124. data/lib/dbwatcher/storage/file_manager.rb +122 -0
  125. data/lib/dbwatcher/storage/null_session.rb +39 -0
  126. data/lib/dbwatcher/storage/query_storage.rb +338 -0
  127. data/lib/dbwatcher/storage/query_validator.rb +24 -0
  128. data/lib/dbwatcher/storage/session.rb +58 -0
  129. data/lib/dbwatcher/storage/session_operations.rb +37 -0
  130. data/lib/dbwatcher/storage/session_query.rb +71 -0
  131. data/lib/dbwatcher/storage/session_storage.rb +322 -0
  132. data/lib/dbwatcher/storage/table_storage.rb +237 -0
  133. data/lib/dbwatcher/storage.rb +112 -85
  134. data/lib/dbwatcher/tracker.rb +4 -55
  135. data/lib/dbwatcher/version.rb +1 -1
  136. data/lib/dbwatcher.rb +70 -3
  137. metadata +140 -2
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Navigation Styles
3
+ * Sidebar, tabs, and navigation controls
4
+ */
5
+
6
+ /* Sidebar styles */
7
+ .sidebar-item {
8
+ font-size: 13px;
9
+ padding: 6px 12px;
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 8px;
13
+ border-radius: 3px;
14
+ transition: all 0.15s;
15
+ }
16
+
17
+ .sidebar-item:hover {
18
+ background: rgba(108, 173, 223, 0.1);
19
+ color: var(--blue-medium);
20
+ }
21
+
22
+ .sidebar-item.active {
23
+ background: var(--navy-dark);
24
+ color: white;
25
+ }
26
+
27
+ /* Tab styles */
28
+ .tab-bar {
29
+ background: var(--gray-100);
30
+ border-bottom: 1px solid var(--border-medium);
31
+ display: flex;
32
+ align-items: center;
33
+ height: 32px;
34
+ font-size: 12px;
35
+ }
36
+
37
+ .tab-item {
38
+ padding: 0 16px;
39
+ height: 100%;
40
+ display: flex;
41
+ align-items: center;
42
+ border-right: 1px solid var(--border-medium);
43
+ cursor: pointer;
44
+ transition: all 0.15s;
45
+ }
46
+
47
+ .tab-item:hover {
48
+ background: var(--border-medium);
49
+ }
50
+
51
+ .tab-item.active {
52
+ background: white;
53
+ color: var(--navy-dark);
54
+ font-weight: 500;
55
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Base Styles
3
+ * Scrollbars, splitters, and utility styles
4
+ */
5
+
6
+ /* Splitter */
7
+ .splitter {
8
+ width: 4px;
9
+ background: var(--border-medium);
10
+ cursor: col-resize;
11
+ }
12
+
13
+ .splitter:hover {
14
+ background: var(--blue-medium);
15
+ }
16
+
17
+ /* Scrollbar styling */
18
+ ::-webkit-scrollbar {
19
+ width: 8px;
20
+ height: 8px;
21
+ }
22
+
23
+ ::-webkit-scrollbar-track {
24
+ background: var(--gray-100);
25
+ }
26
+
27
+ ::-webkit-scrollbar-thumb {
28
+ background: var(--gray-400);
29
+ border-radius: 4px;
30
+ }
31
+
32
+ ::-webkit-scrollbar-thumb:hover {
33
+ background: var(--blue-medium);
34
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * DBWatcher CSS Variables
3
+ * Core color scheme and layout dimensions
4
+ */
5
+
6
+ :root {
7
+ /* Brand colors */
8
+ --navy-dark: #00285D;
9
+ --blue-light: #96C1E7;
10
+ --blue-medium: #6CADDF;
11
+ --gold-dark: #D4A11E;
12
+ --gold-light: #FFC758;
13
+
14
+ /* Layout dimensions for diagram container */
15
+ --header-height: 64px;
16
+ --tab-bar-height: 32px;
17
+ --toolbar-height: 56px;
18
+ --footer-height: 0px;
19
+
20
+ /* Gray scale */
21
+ --gray-50: #f9fafb;
22
+ --gray-100: #f3f3f3;
23
+ --gray-200: #e8e8e8;
24
+ --gray-300: #d1d5db;
25
+ --gray-400: #c8c8c8;
26
+ --gray-500: #6b7280;
27
+ --gray-600: #4b5563;
28
+ --gray-700: #374151;
29
+ --gray-800: #1f2937;
30
+ --gray-900: #111827;
31
+
32
+ /* Border colors */
33
+ --border-light: #f0f0f0;
34
+ --border-medium: #e8e8e8;
35
+ --border-dark: #d1d5db;
36
+
37
+ /* Status colors */
38
+ --status-insert: #10b981;
39
+ --status-update: #6CADDF;
40
+ --status-delete: #ef4444;
41
+ --status-select: #6b7280;
42
+
43
+ /* Highlight colors */
44
+ --highlight-change: rgba(255, 199, 88, 0.3);
45
+ --highlight-new: rgba(16, 185, 129, 0.2);
46
+ --highlight-deleted: rgba(239, 68, 68, 0.2);
47
+ }
@@ -0,0 +1,2 @@
1
+ .tabulator{background-color:#888;border:1px solid #999;font-size:14px;overflow:hidden;position:relative;text-align:left;-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-ms-transform:translateZ(0);-o-transform:translateZ(0);transform:translateZ(0)}.tabulator[tabulator-layout=fitDataFill] .tabulator-tableholder .tabulator-table{min-width:100%}.tabulator[tabulator-layout=fitDataTable]{display:inline-block}.tabulator.tabulator-block-select,.tabulator.tabulator-ranges .tabulator-cell:not(.tabulator-editing){user-select:none}.tabulator .tabulator-header{background-color:#e6e6e6;border-bottom:1px solid #999;box-sizing:border-box;color:#555;font-weight:700;outline:none;overflow:hidden;position:relative;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap;width:100%}.tabulator .tabulator-header.tabulator-header-hidden{display:none}.tabulator .tabulator-header .tabulator-header-contents{overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-header-contents .tabulator-headers{display:inline-block}.tabulator .tabulator-header .tabulator-col{background:#e6e6e6;border-right:1px solid #aaa;box-sizing:border-box;display:inline-flex;flex-direction:column;justify-content:flex-start;overflow:hidden;position:relative;text-align:left;vertical-align:bottom}.tabulator .tabulator-header .tabulator-col.tabulator-moving{background:#cdcdcd;border:1px solid #999;pointer-events:none;position:absolute}.tabulator .tabulator-header .tabulator-col.tabulator-range-highlight{background-color:#d6d6d6;color:#000}.tabulator .tabulator-header .tabulator-col.tabulator-range-selected{background-color:#3876ca;color:#fff}.tabulator .tabulator-header .tabulator-col .tabulator-col-content{box-sizing:border-box;padding:4px;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button{padding:0 8px}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-header-popup-button:hover{cursor:pointer;opacity:.6}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title-holder{position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title{box-sizing:border-box;overflow:hidden;text-overflow:ellipsis;vertical-align:bottom;white-space:nowrap;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title.tabulator-col-title-wrap{text-overflow:clip;white-space:normal}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-title-editor{background:#fff;border:1px solid #999;box-sizing:border-box;padding:1px;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-title .tabulator-header-popup-button+.tabulator-title-editor{width:calc(100% - 22px)}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{align-items:center;bottom:0;display:flex;position:absolute;right:4px;top:0}.tabulator .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-left:6px solid transparent;border-right:6px solid transparent;height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{border-top:1px solid #aaa;display:flex;margin-right:-1px;overflow:hidden;position:relative}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter{box-sizing:border-box;margin-top:2px;position:relative;text-align:center;width:100%}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter textarea{height:auto!important}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter svg{margin-top:3px}.tabulator .tabulator-header .tabulator-col .tabulator-header-filter input::-ms-clear{height:0;width:0}.tabulator .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-right:25px}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable.tabulator-col-sorter-element:hover{background-color:#cdcdcd;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter{color:#bbb}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=none] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #bbb;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-bottom:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=ascending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:6px solid #666;border-top:none}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter{color:#666}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter.tabulator-col-sorter-element .tabulator-arrow:hover{border-top:6px solid #555;cursor:pointer}}.tabulator .tabulator-header .tabulator-col.tabulator-sortable[aria-sort=descending] .tabulator-col-content .tabulator-col-sorter .tabulator-arrow{border-bottom:none;border-top:6px solid #666;color:#666}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical .tabulator-col-content .tabulator-col-title{align-items:center;display:flex;justify-content:center;text-orientation:mixed;writing-mode:vertical-rl}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-col-vertical-flip .tabulator-col-title{transform:rotate(180deg)}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-title{padding-right:0;padding-top:20px}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable.tabulator-col-vertical-flip .tabulator-col-title{padding-bottom:20px;padding-right:0}.tabulator .tabulator-header .tabulator-col.tabulator-col-vertical.tabulator-sortable .tabulator-col-sorter{bottom:auto;justify-content:center;left:0;right:0;top:4px}.tabulator .tabulator-header .tabulator-frozen{left:0;position:sticky;z-index:11}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator .tabulator-header .tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator .tabulator-header .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;display:inline-block}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important}.tabulator .tabulator-header .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-header .tabulator-frozen-rows-holder{display:inline-block}.tabulator .tabulator-header .tabulator-frozen-rows-holder:empty{display:none}.tabulator .tabulator-tableholder{-webkit-overflow-scrolling:touch;overflow:auto;position:relative;white-space:nowrap;width:100%}.tabulator .tabulator-tableholder:focus{outline:none}.tabulator .tabulator-tableholder .tabulator-placeholder{align-items:center;box-sizing:border-box;display:flex;justify-content:center;min-width:100%;width:100%}.tabulator .tabulator-tableholder .tabulator-placeholder[tabulator-render-mode=virtual]{min-height:100%}.tabulator .tabulator-tableholder .tabulator-placeholder .tabulator-placeholder-contents{color:#ccc;display:inline-block;font-size:20px;font-weight:700;padding:10px;text-align:center;white-space:normal}.tabulator .tabulator-tableholder .tabulator-table{background-color:#fff;color:#333;display:inline-block;overflow:visible;position:relative;white-space:nowrap}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs{background:#e2e2e2!important;font-weight:700}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-top{border-bottom:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-table .tabulator-row.tabulator-calcs.tabulator-calcs-bottom{border-top:2px solid #aaa}.tabulator .tabulator-tableholder .tabulator-range-overlay{inset:0;pointer-events:none;position:absolute;z-index:10}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range{border:1px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;position:absolute;right:-3px;width:6px}.tabulator .tabulator-tableholder .tabulator-range-overlay .tabulator-range-cell-active{border:2px solid #2975dd;box-sizing:border-box;position:absolute}.tabulator .tabulator-footer{background-color:#e6e6e6;border-top:1px solid #999;color:#555;font-weight:700;user-select:none;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;white-space:nowrap}.tabulator .tabulator-footer .tabulator-footer-contents{align-items:center;display:flex;flex-direction:row;justify-content:space-between;padding:5px 10px}.tabulator .tabulator-footer .tabulator-footer-contents:empty{display:none}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs{margin-top:-5px;overflow-x:auto}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab{border:1px solid #999;border-bottom-left-radius:5px;border-bottom-right-radius:5px;border-top:none;display:inline-block;font-size:.9em;padding:5px}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab:hover{cursor:pointer;opacity:.7}.tabulator .tabulator-footer .tabulator-spreadsheet-tabs .tabulator-spreadsheet-tab.tabulator-spreadsheet-tab-active{background:#fff}.tabulator .tabulator-footer .tabulator-calcs-holder{background:#f3f3f3!important;border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;overflow:hidden;text-align:left;width:100%}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row{background:#f3f3f3!important;display:inline-block}.tabulator .tabulator-footer .tabulator-calcs-holder .tabulator-row .tabulator-col-resize-handle{display:none}.tabulator .tabulator-footer .tabulator-calcs-holder:only-child{border-bottom:none;margin-bottom:-5px}.tabulator .tabulator-footer>*+.tabulator-page-counter{margin-left:10px}.tabulator .tabulator-footer .tabulator-page-counter{font-weight:400}.tabulator .tabulator-footer .tabulator-paginator{color:#555;flex:1;font-family:inherit;font-size:inherit;font-weight:inherit;text-align:right}.tabulator .tabulator-footer .tabulator-page-size{border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 5px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-pages{margin:0 7px}.tabulator .tabulator-footer .tabulator-page{background:hsla(0,0%,100%,.2);border:1px solid #aaa;border-radius:3px;display:inline-block;margin:0 2px;padding:2px 5px}.tabulator .tabulator-footer .tabulator-page.active{color:#d00}.tabulator .tabulator-footer .tabulator-page:disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-footer .tabulator-page:not(disabled):hover{background:rgba(0,0,0,.2);color:#fff;cursor:pointer}}.tabulator .tabulator-col-resize-handle{display:inline-block;margin-left:-3px;margin-right:-3px;position:relative;vertical-align:middle;width:6px;z-index:11}@media (hover:hover) and (pointer:fine){.tabulator .tabulator-col-resize-handle:hover{cursor:ew-resize}}.tabulator .tabulator-col-resize-handle:last-of-type{margin-right:0;width:3px}.tabulator .tabulator-col-resize-guide{background-color:#999;height:100%;margin-left:-.5px;opacity:.5;position:absolute;top:0;width:4px}.tabulator .tabulator-row-resize-guide{background-color:#999;height:4px;left:0;margin-top:-.5px;opacity:.5;position:absolute;width:100%}.tabulator .tabulator-alert{align-items:center;background:rgba(0,0,0,.4);display:flex;height:100%;left:0;position:absolute;text-align:center;top:0;width:100%;z-index:100}.tabulator .tabulator-alert .tabulator-alert-msg{background:#fff;border-radius:10px;display:inline-block;font-size:16px;font-weight:700;margin:0 auto;padding:10px 20px}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-msg{border:4px solid #333;color:#000}.tabulator .tabulator-alert .tabulator-alert-msg.tabulator-alert-state-error{border:4px solid #d00;color:#590000}.tabulator-row{background-color:#fff;box-sizing:border-box;min-height:22px;position:relative}.tabulator-row.tabulator-row-even{background-color:#efefef}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selectable:hover{background-color:#bbb;cursor:pointer}}.tabulator-row.tabulator-selected{background-color:#9abcea}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-selected:hover{background-color:#769bcc;cursor:pointer}}.tabulator-row.tabulator-row-moving{background:#fff;border:1px solid #000}.tabulator-row.tabulator-moving{border-bottom:1px solid #aaa;border-top:1px solid #aaa;pointer-events:none;position:absolute;z-index:15}.tabulator-row.tabulator-range-highlight .tabulator-cell.tabulator-range-row-header{background-color:#d6d6d6;color:#000}.tabulator-row.tabulator-range-highlight.tabulator-range-selected .tabulator-cell.tabulator-range-row-header,.tabulator-row.tabulator-range-selected .tabulator-cell.tabulator-range-row-header{background-color:#3876ca;color:#fff}.tabulator-row .tabulator-row-resize-handle{bottom:0;height:5px;left:0;position:absolute;right:0}.tabulator-row .tabulator-row-resize-handle.prev{bottom:auto;top:0}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-row-resize-handle:hover{cursor:ns-resize}}.tabulator-row .tabulator-responsive-collapse{border-bottom:1px solid #aaa;border-top:1px solid #aaa;box-sizing:border-box;padding:5px}.tabulator-row .tabulator-responsive-collapse:empty{display:none}.tabulator-row .tabulator-responsive-collapse table{font-size:14px}.tabulator-row .tabulator-responsive-collapse table tr td{position:relative}.tabulator-row .tabulator-responsive-collapse table tr td:first-of-type{padding-right:10px}.tabulator-row .tabulator-cell{border-right:1px solid #aaa;box-sizing:border-box;display:inline-block;outline:none;overflow:hidden;padding:4px;position:relative;text-overflow:ellipsis;vertical-align:middle;white-space:nowrap}.tabulator-row .tabulator-cell.tabulator-row-header{background:#e6e6e6;border-bottom:1px solid #aaa;border-right:1px solid #999}.tabulator-row .tabulator-cell.tabulator-frozen{background-color:inherit;display:inline-block;left:0;position:sticky;z-index:11}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-right:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-left:2px solid #aaa}.tabulator-row .tabulator-cell.tabulator-editing{border:1px solid #1d68cd;outline:none;padding:0}.tabulator-row .tabulator-cell.tabulator-editing input,.tabulator-row .tabulator-cell.tabulator-editing select{background:transparent;border:1px;outline:none}.tabulator-row .tabulator-cell.tabulator-validation-fail{border:1px solid #d00}.tabulator-row .tabulator-cell.tabulator-validation-fail input,.tabulator-row .tabulator-cell.tabulator-validation-fail select{background:transparent;border:1px;color:#d00}.tabulator-row .tabulator-cell.tabulator-row-handle{align-items:center;display:inline-flex;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box{width:80%}.tabulator-row .tabulator-cell.tabulator-row-handle .tabulator-row-handle-box .tabulator-row-handle-bar{background:#666;height:3px;margin-top:2px;width:100%}.tabulator-row .tabulator-cell.tabulator-range-selected:not(.tabulator-range-only-cell-selected):not(.tabulator-range-row-header){background-color:#9abcea}.tabulator-row .tabulator-cell .tabulator-data-tree-branch-empty{display:inline-block;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-row .tabulator-cell .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle{align-items:center;background:#666;border-radius:20px;color:#fff;display:inline-flex;font-size:1.1em;font-weight:700;height:15px;justify-content:center;-moz-user-select:none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;width:15px}@media (hover:hover) and (pointer:fine){.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle:hover{cursor:pointer;opacity:.7}}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-close{display:initial}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle.open .tabulator-responsive-collapse-toggle-open{display:none}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle svg{stroke:#fff}.tabulator-row .tabulator-cell .tabulator-responsive-collapse-toggle .tabulator-responsive-collapse-toggle-close{display:none}.tabulator-row .tabulator-cell .tabulator-traffic-light{border-radius:14px;display:inline-block;height:14px;width:14px}.tabulator-row.tabulator-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-row.tabulator-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-row.tabulator-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-row.tabulator-group.tabulator-group-level-1{padding-left:30px}.tabulator-row.tabulator-group.tabulator-group-level-2{padding-left:50px}.tabulator-row.tabulator-group.tabulator-group-level-3{padding-left:70px}.tabulator-row.tabulator-group.tabulator-group-level-4{padding-left:90px}.tabulator-row.tabulator-group.tabulator-group-level-5{padding-left:110px}.tabulator-row.tabulator-group .tabulator-group-toggle{display:inline-block}.tabulator-row.tabulator-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-row.tabulator-group span{color:#d00;margin-left:10px}.tabulator-toggle{background:#dcdcdc;border:1px solid #ccc;box-sizing:border-box;display:flex;flex-direction:row}.tabulator-toggle.tabulator-toggle-on{background:#1c6cc2}.tabulator-toggle .tabulator-toggle-switch{background:#fff;border:1px solid #ccc;box-sizing:border-box}.tabulator-popup-container{-webkit-overflow-scrolling:touch;background:#fff;border:1px solid #aaa;box-shadow:0 0 5px 0 rgba(0,0,0,.2);box-sizing:border-box;display:inline-block;font-size:14px;overflow-y:auto;position:absolute;z-index:10000}.tabulator-popup{border-radius:3px;padding:5px}.tabulator-tooltip{border-radius:2px;box-shadow:none;font-size:12px;max-width:Min(500px,100%);padding:3px 5px;pointer-events:none}.tabulator-menu .tabulator-menu-item{box-sizing:border-box;padding:5px 10px;position:relative;user-select:none}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-disabled{opacity:.5}@media (hover:hover) and (pointer:fine){.tabulator-menu .tabulator-menu-item:not(.tabulator-menu-item-disabled):hover{background:#efefef;cursor:pointer}}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu{padding-right:25px}.tabulator-menu .tabulator-menu-item.tabulator-menu-item-submenu:after{border-color:#aaa;border-style:solid;border-width:1px 1px 0 0;content:"";display:inline-block;height:7px;position:absolute;right:10px;top:calc(5px + .4em);transform:rotate(45deg);vertical-align:top;width:7px}.tabulator-menu .tabulator-menu-separator{border-top:1px solid #aaa}.tabulator-edit-list{-webkit-overflow-scrolling:touch;font-size:14px;max-height:200px;overflow-y:auto}.tabulator-edit-list .tabulator-edit-list-item{color:#333;outline:none;padding:4px}.tabulator-edit-list .tabulator-edit-list-item.active{background:#1d68cd;color:#fff}.tabulator-edit-list .tabulator-edit-list-item.active.focused{outline:1px solid hsla(0,0%,100%,.5)}.tabulator-edit-list .tabulator-edit-list-item.focused{outline:1px solid #1d68cd}@media (hover:hover) and (pointer:fine){.tabulator-edit-list .tabulator-edit-list-item:hover{background:#1d68cd;color:#fff;cursor:pointer}}.tabulator-edit-list .tabulator-edit-list-placeholder{color:#333;padding:4px;text-align:center}.tabulator-edit-list .tabulator-edit-list-group{border-bottom:1px solid #aaa;color:#333;font-weight:700;padding:6px 4px 4px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-2,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-2{padding-left:12px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-3,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-3{padding-left:20px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-4,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-4{padding-left:28px}.tabulator-edit-list .tabulator-edit-list-group.tabulator-edit-list-group-level-5,.tabulator-edit-list .tabulator-edit-list-item.tabulator-edit-list-group-level-5{padding-left:36px}.tabulator.tabulator-ltr{direction:ltr}.tabulator.tabulator-rtl{direction:rtl;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col{border-left:1px solid #aaa;border-right:initial;text-align:initial}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-col-group .tabulator-col-group-cols{margin-left:-1px;margin-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col.tabulator-sortable .tabulator-col-title{padding-left:25px;padding-right:0}.tabulator.tabulator-rtl .tabulator-header .tabulator-col .tabulator-col-content .tabulator-col-sorter{left:8px;right:auto}.tabulator.tabulator-rtl .tabulator-tableholder .tabulator-range-overlay .tabulator-range.tabulator-range-active:after{background-color:#2975dd;border-radius:999px;bottom:-3px;content:"";height:6px;left:-3px;position:absolute;right:auto;width:6px}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell{border-left:1px solid #aaa;border-right:initial}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-branch{border-bottom-left-radius:0;border-bottom-right-radius:1px;border-left:initial;border-right:2px solid #aaa;margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell .tabulator-data-tree-control{margin-left:5px;margin-right:0}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-left{border-left:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-cell.tabulator-frozen.tabulator-frozen-right{border-right:2px solid #aaa}.tabulator.tabulator-rtl .tabulator-row .tabulator-col-resize-handle:last-of-type{margin-left:0;margin-right:-3px;width:3px}.tabulator.tabulator-rtl .tabulator-footer .tabulator-calcs-holder{text-align:initial}.tabulator-print-fullscreen{bottom:0;left:0;position:absolute;right:0;top:0;z-index:10000}body.tabulator-print-fullscreen-hide>:not(.tabulator-print-fullscreen){display:none!important}.tabulator-print-table{border-collapse:collapse}.tabulator-print-table .tabulator-data-tree-branch{border-bottom:2px solid #aaa;border-bottom-left-radius:1px;border-left:2px solid #aaa;display:inline-block;height:9px;margin-right:5px;margin-top:-9px;vertical-align:middle;width:7px}.tabulator-print-table .tabulator-print-table-group{background:#ccc;border-bottom:1px solid #999;border-right:1px solid #aaa;border-top:1px solid #999;box-sizing:border-box;font-weight:700;min-width:100%;padding:5px 5px 5px 10px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-print-table-group:hover{background-color:rgba(0,0,0,.1);cursor:pointer}}.tabulator-print-table .tabulator-print-table-group.tabulator-group-visible .tabulator-arrow{border-bottom:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:6px solid #666;margin-right:10px}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-1 td{padding-left:30px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-2 td{padding-left:50px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-3 td{padding-left:70px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-4 td{padding-left:90px!important}.tabulator-print-table .tabulator-print-table-group.tabulator-group-level-5 td{padding-left:110px!important}.tabulator-print-table .tabulator-print-table-group .tabulator-group-toggle{display:inline-block}.tabulator-print-table .tabulator-print-table-group .tabulator-arrow{border-bottom:6px solid transparent;border-left:6px solid #666;border-right:0;border-top:6px solid transparent;display:inline-block;height:0;margin-right:16px;vertical-align:middle;width:0}.tabulator-print-table .tabulator-print-table-group span{color:#d00;margin-left:10px}.tabulator-print-table .tabulator-data-tree-control{align-items:center;background:rgba(0,0,0,.1);border:1px solid #333;border-radius:2px;display:inline-flex;height:11px;justify-content:center;margin-right:5px;overflow:hidden;vertical-align:middle;width:11px}@media (hover:hover) and (pointer:fine){.tabulator-print-table .tabulator-data-tree-control:hover{background:rgba(0,0,0,.2);cursor:pointer}}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse{background:transparent;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-collapse:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand{background:#333;display:inline-block;height:7px;position:relative;width:1px}.tabulator-print-table .tabulator-data-tree-control .tabulator-data-tree-control-expand:after{background:#333;content:"";height:1px;left:-3px;position:absolute;top:3px;width:7px}
2
+ /*# sourceMappingURL=tabulator.min.css.map */
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module Api
5
+ module V1
6
+ class SessionsController < BaseController
7
+ before_action :find_session, except: [:diagram_types]
8
+
9
+ def changes_data
10
+ Rails.logger.info "API::V1::SessionsController#changes_data: Getting changes for session #{@session.id}"
11
+
12
+ # Paginated, filtered changes data
13
+ # Convert ActionController::Parameters to a hash before passing to service
14
+ service = Dbwatcher::Services::Api::ChangesDataService.new(@session, filter_params.to_h)
15
+ render json: service.call
16
+ end
17
+
18
+ def summary_data
19
+ Rails.logger.info "API::V1::SessionsController#summary_data: Getting summary for session #{@session.id}"
20
+
21
+ # Aggregated summary statistics
22
+ service = Dbwatcher::Services::Api::SummaryDataService.new(@session)
23
+ render json: service.call
24
+ end
25
+
26
+ def diagram_data
27
+ Rails.logger.info "API::V1::SessionsController#diagram_data: Getting diagram for session #{@session.id}"
28
+
29
+ # Generated diagram content with caching
30
+ # Convert ActionController::Parameters to a hash before passing to service
31
+ diagram_params = params.to_unsafe_h
32
+ service = Dbwatcher::Services::Api::DiagramDataService.new(@session, params[:type], diagram_params)
33
+ result = service.call
34
+
35
+ if result[:error]
36
+ render json: { error: result[:error] }, status: :unprocessable_entity
37
+ else
38
+ render json: result
39
+ end
40
+ end
41
+
42
+ def diagram_types
43
+ Rails.logger.info "API::V1::SessionsController#diagram_types: Getting available diagram types"
44
+
45
+ render json: {
46
+ types: Dbwatcher::Services::Api::DiagramDataService.available_types_with_metadata,
47
+ default_type: "database_tables"
48
+ }
49
+ end
50
+
51
+ private
52
+
53
+ def find_session
54
+ @session = Storage.sessions.find(params[:id])
55
+ render json: { error: "Session not found" }, status: :not_found unless @session
56
+ end
57
+
58
+ def filter_params
59
+ params.permit(:table, :operation, :page, :per_page)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ # Base controller for all Dbwatcher controllers
5
+ # Provides common functionality and configuration for the entire engine
6
+ class BaseController < ActionController::Base
7
+ protect_from_forgery with: :exception
8
+ layout "dbwatcher/application"
9
+
10
+ before_action :set_current_time
11
+ before_action :log_request_info
12
+
13
+ # Common error handling
14
+ rescue_from StandardError, with: :handle_error
15
+
16
+ # Include helpers
17
+ helper Dbwatcher::ApplicationHelper
18
+ helper Dbwatcher::FormattingHelper
19
+ helper Dbwatcher::DiagramHelper if defined?(Dbwatcher::DiagramHelper)
20
+
21
+ protected
22
+
23
+ # Set current time for consistent timestamp usage across views
24
+ def set_current_time
25
+ @current_time = Time.current
26
+ end
27
+
28
+ # Log request information for debugging
29
+ def log_request_info
30
+ return unless Rails.logger
31
+
32
+ Rails.logger.info "DBWatcher Request: #{request.method} #{request.path} - Controller: #{self.class.name}"
33
+ end
34
+
35
+ # Common error handling for all controllers
36
+ def handle_error(exception)
37
+ Rails.logger.error "DBWatcher Error in #{self.class.name}##{action_name}: #{exception.message}"
38
+ Rails.logger.error exception.backtrace.join("\n") if Rails.env.development?
39
+
40
+ respond_to do |format|
41
+ format.html do
42
+ flash[:error] = "An error occurred while processing your request."
43
+ # Avoid infinite redirect by using main app root or request referer
44
+ redirect_to(request.referer || main_app.root_path)
45
+ end
46
+ format.json do
47
+ render json: { error: "Internal server error" }, status: :internal_server_error
48
+ end
49
+ end
50
+ end
51
+
52
+ # Helper method for safely extracting data from hashes with symbol/string keys
53
+ def safe_extract(data, key)
54
+ return nil unless data.is_a?(Hash)
55
+
56
+ data[key] || data[key.to_s]
57
+ end
58
+
59
+ # Helper method for formatting timestamps consistently
60
+ def format_timestamp(timestamp_str)
61
+ return "N/A" unless timestamp_str
62
+
63
+ Time.parse(timestamp_str).strftime("%Y-%m-%d %H:%M:%S")
64
+ rescue ArgumentError
65
+ "N/A"
66
+ end
67
+
68
+ # Make helper methods available to views
69
+ helper_method :format_timestamp, :safe_extract
70
+
71
+ # Helper method for rendering JSON responses with consistent structure
72
+ def render_json_response(data, status: :ok)
73
+ render json: {
74
+ status: status,
75
+ data: data,
76
+ timestamp: @current_time.iso8601
77
+ }, status: status
78
+ end
79
+
80
+ # Helper method for handling not found resources
81
+ def handle_not_found(resource_name, redirect_path)
82
+ Rails.logger.warn "#{self.class.name}##{action_name}: #{resource_name} not found for ID: #{params[:id]}"
83
+
84
+ respond_to do |format|
85
+ format.html do
86
+ redirect_to redirect_path, alert: "#{resource_name} not found"
87
+ end
88
+ format.json do
89
+ render json: { error: "#{resource_name} not found" }, status: :not_found
90
+ end
91
+ end
92
+ end
93
+
94
+ # Helper method for clearing storage with consistent messaging
95
+ def clear_storage_with_message(storage_method, resource_name, redirect_path)
96
+ cleared_count = storage_method.call
97
+ redirect_to redirect_path,
98
+ notice: "#{resource_name} cleared (#{cleared_count} files removed)"
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ class DashboardController < BaseController
5
+ def index
6
+ dashboard_data = Dbwatcher::Services::DashboardDataAggregator.call
7
+ @recent_sessions = dashboard_data[:recent_sessions]
8
+ @active_tables = dashboard_data[:active_tables]
9
+ @query_stats = dashboard_data[:query_stats]
10
+ end
11
+
12
+ def clear_all
13
+ clear_storage_with_message(
14
+ -> { Storage.clear_all },
15
+ "All sessions and SQL logs",
16
+ root_path
17
+ )
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ class QueriesController < BaseController
5
+ def index
6
+ @date = params[:date] || Date.current.strftime("%Y-%m-%d")
7
+ queries = Storage.queries.for_date(@date).all
8
+ @queries = Dbwatcher::Services::QueryFilterProcessor.call(queries, params)
9
+
10
+ respond_to do |format|
11
+ format.html
12
+ format.json { render json: @queries }
13
+ end
14
+ end
15
+
16
+ def clear
17
+ clear_storage_with_message(
18
+ -> { Storage.query_storage.clear_all },
19
+ "SQL query logs",
20
+ queries_path
21
+ )
22
+ end
23
+ end
24
+ end
@@ -1,38 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dbwatcher
4
- class SessionsController < ActionController::Base
5
- protect_from_forgery with: :exception
6
- layout "dbwatcher/application"
4
+ class SessionsController < BaseController
5
+ before_action :find_session, except: [:index]
7
6
 
8
7
  def index
9
- @sessions = Storage.all_sessions
8
+ @sessions = Storage.sessions.all
10
9
  end
11
10
 
12
11
  def show
13
- @session = Storage.load_session(params[:id])
12
+ redirect_to changes_session_path(@session.id)
13
+ end
14
14
 
15
- respond_to do |format|
16
- format.html
17
- format.json { render json: @session.to_h }
18
- end
15
+ def changes
16
+ Rails.logger.info "SessionsController#changes: Loading changes for session #{@session.id}"
17
+ # No server-side data processing - API-first architecture
19
18
  end
20
19
 
21
- def destroy_all
22
- Dbwatcher.reset!
23
- redirect_to root_path, notice: "All sessions cleared"
20
+ def summary
21
+ Rails.logger.info "SessionsController#summary: Loading summary for session #{@session.id}"
22
+ # No server-side data processing - API-first architecture
24
23
  end
25
24
 
26
- private
25
+ def diagrams
26
+ Rails.logger.info "SessionsController#diagrams: Loading diagrams for session #{@session.id}"
27
+ # No server-side data processing - API-first architecture
28
+ end
27
29
 
28
- # Helper method to safely get the sessions path
29
- def sessions_index_path
30
- if respond_to?(:sessions_path)
30
+ def clear
31
+ clear_storage_with_message(
32
+ -> { Storage.session_storage.clear_all },
33
+ "All sessions",
31
34
  sessions_path
32
- else
33
- "/dbwatcher"
34
- end
35
+ )
36
+ end
37
+
38
+ private
39
+
40
+ def find_session
41
+ @session = Storage.sessions.find(params[:id])
42
+ handle_not_found("Session", sessions_path) unless @session
35
43
  end
36
- helper_method :sessions_index_path
44
+
45
+ # No longer needed with API-first architecture
46
+ # All data processing happens in API services and is loaded via JavaScript
37
47
  end
38
48
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ class TablesController < BaseController
5
+ def index
6
+ @tables = Dbwatcher::Services::TableStatisticsCollector.call
7
+ end
8
+
9
+ def show
10
+ @table_name = params[:id]
11
+ @changes = Storage.tables.changes_for(@table_name).all
12
+ @sessions = @changes.map { |c| c[:session_id] }.uniq
13
+
14
+ respond_to do |format|
15
+ format.html
16
+ format.json { render json: @changes }
17
+ end
18
+ end
19
+
20
+ def changes
21
+ @table_name = params[:id]
22
+ @changes = Storage.tables.changes_for(@table_name).all
23
+ @sessions = extract_session_ids(@changes)
24
+ @records = group_changes_by_record(@changes)
25
+ end
26
+
27
+ private
28
+
29
+ # These remain as they are simple data extraction helpers
30
+ def extract_session_ids(changes)
31
+ changes.map { |c| c[:session_id] }.uniq
32
+ end
33
+
34
+ def group_changes_by_record(changes)
35
+ changes.group_by { |c| c[:record_id] }
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module ApplicationHelper
5
+ include FormattingHelper
6
+ include SessionHelper
7
+
8
+ # Common view helpers for templates
9
+
10
+ # Create icon HTML for stats cards
11
+ def stats_icon(type)
12
+ icon_html = icon_definitions[type.to_sym]
13
+ icon_html&.html_safe
14
+ end
15
+
16
+ private
17
+
18
+ # Icon definitions for stats cards
19
+ def icon_definitions
20
+ {
21
+ sessions: sessions_icon_svg,
22
+ tables: tables_icon_svg,
23
+ queries: queries_icon_svg,
24
+ performance: performance_icon_svg
25
+ }
26
+ end
27
+
28
+ def sessions_icon_svg
29
+ <<~SVG
30
+ <svg class="w-4 h-4 text-blue-medium" fill="currentColor" viewBox="0 0 20 20">
31
+ <path d="M10 12a2 2 0 100-4 2 2 0 000 4z"/>
32
+ <path fill-rule="evenodd" d="M.458 10C1.732 5.943 5.522 3 10 3s8.268 2.943 9.542 7c-1.274 4.057-5.064 7-9.542 7S1.732 14.057.458 10zM14 10a4 4 0 11-8 0 4 4 0 018 0z" clip-rule="evenodd"/>
33
+ </svg>
34
+ SVG
35
+ end
36
+
37
+ def tables_icon_svg
38
+ <<~SVG
39
+ <svg class="w-4 h-4 text-gold-dark" fill="currentColor" viewBox="0 0 20 20">
40
+ <path fill-rule="evenodd" d="M3 3a1 1 0 000 2v8a2 2 0 002 2h2.586l-1.293 1.293a1 1 0 101.414 1.414L10 15.414l2.293 2.293a1 1 0 001.414-1.414L12.414 15H15a2 2 0 002-2V5a1 1 0 100-2H3zm11 4a1 1 0 10-2 0v4a1 1 0 102 0V7zm-3 1a1 1 0 10-2 0v3a1 1 0 102 0V8zM8 9a1 1 0 00-2 0v2a1 1 0 102 0V9z" clip-rule="evenodd"/>
41
+ </svg>
42
+ SVG
43
+ end
44
+
45
+ def queries_icon_svg
46
+ <<~SVG
47
+ <svg class="w-4 h-4 text-blue-light" fill="currentColor" viewBox="0 0 20 20">
48
+ <path fill-rule="evenodd" d="M2 5a2 2 0 012-2h12a2 2 0 012 2v10a2 2 0 01-2 2H4a2 2 0 01-2-2V5zm3.293 1.293a1 1 0 011.414 0l3 3a1 1 0 010 1.414l-3 3a1 1 0 01-1.414-1.414L7.586 10 5.293 7.707a1 1 0 010-1.414zM11 12a1 1 0 100 2h3a1 1 0 100-2h-3z" clip-rule="evenodd"/>
49
+ </svg>
50
+ SVG
51
+ end
52
+
53
+ def performance_icon_svg
54
+ <<~SVG
55
+ <svg class="w-4 h-4 text-gold-light" fill="currentColor" viewBox="0 0 20 20">
56
+ <path fill-rule="evenodd"
57
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z"
58
+ clip-rule="evenodd"/>
59
+ </svg>
60
+ SVG
61
+ end
62
+
63
+ public
64
+
65
+ # Generate operation badges for tables
66
+ def operation_badges
67
+ content_tag(:div, class: "flex gap-1 justify-center") do
68
+ [
69
+ content_tag(:span, "I", class: "badge badge-insert", title: "Inserts"),
70
+ content_tag(:span, "U", class: "badge badge-update", title: "Updates"),
71
+ content_tag(:span, "D", class: "badge badge-delete", title: "Deletes")
72
+ ].join.html_safe
73
+ end
74
+ end
75
+
76
+ # Create action buttons with consistent styling
77
+ def action_button(text, path, color: "blue")
78
+ link_to text, path, class: "px-3 py-1 text-xs bg-#{color}-600 text-white rounded hover:bg-#{color}-700"
79
+ end
80
+
81
+ # Safe value extraction with fallback
82
+ def safe_value(data, key, fallback = "N/A")
83
+ return fallback unless data.is_a?(Hash)
84
+
85
+ data[key] || data[key.to_s] || fallback
86
+ end
87
+
88
+ # Format count with proper fallback
89
+ def format_count(value)
90
+ value.to_i.to_s
91
+ end
92
+
93
+ # Create empty state message
94
+ def empty_state(message, icon: nil)
95
+ content_tag(:div, class: "text-center py-8 text-gray-500") do
96
+ content = []
97
+ content << icon if icon
98
+ content << content_tag(:p, message, class: "text-xs")
99
+ content.join.html_safe
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dbwatcher
4
+ module ComponentHelper
5
+ # Removed as part of API-first migration (Story 6.8)
6
+ # All data is now loaded directly from API endpoints
7
+
8
+ # Removed as part of API-first migration (Story 6.8)
9
+ # All data is now loaded directly from API endpoints
10
+
11
+ # Removed as part of API-first migration (Story 6.8)
12
+ # All data is now loaded directly from API endpoints
13
+
14
+ # Generate data attributes for component binding
15
+ def dbwatcher_component(component_name, config)
16
+ {
17
+ "data-dbwatcher" => component_name,
18
+ "data-config" => config.to_json
19
+ }
20
+ end
21
+
22
+ # Helper to render a DBWatcher component
23
+ def render_dbwatcher_component(component_name, config, html_options = {})
24
+ content_tag :div, dbwatcher_component(component_name, config).merge(html_options) do
25
+ yield if block_given?
26
+ end
27
+ end
28
+ end
29
+ end