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.
- checksums.yaml +4 -4
- data/README.md +81 -210
- data/app/assets/config/dbwatcher_manifest.js +15 -0
- data/app/assets/javascripts/dbwatcher/alpine_registrations.js +39 -0
- data/app/assets/javascripts/dbwatcher/auto_init.js +23 -0
- data/app/assets/javascripts/dbwatcher/components/base.js +141 -0
- data/app/assets/javascripts/dbwatcher/components/changes_table_hybrid.js +1008 -0
- data/app/assets/javascripts/dbwatcher/components/diagrams.js +449 -0
- data/app/assets/javascripts/dbwatcher/components/summary.js +234 -0
- data/app/assets/javascripts/dbwatcher/core/alpine_store.js +138 -0
- data/app/assets/javascripts/dbwatcher/core/api_client.js +162 -0
- data/app/assets/javascripts/dbwatcher/core/component_loader.js +70 -0
- data/app/assets/javascripts/dbwatcher/core/component_registry.js +94 -0
- data/app/assets/javascripts/dbwatcher/dbwatcher.js +120 -0
- data/app/assets/javascripts/dbwatcher/services/mermaid.js +315 -0
- data/app/assets/javascripts/dbwatcher/services/mermaid_service.js +199 -0
- data/app/assets/javascripts/dbwatcher/vendor/date-fns-browser.js +99 -0
- data/app/assets/javascripts/dbwatcher/vendor/lodash.min.js +140 -0
- data/app/assets/javascripts/dbwatcher/vendor/tabulator.min.js +3 -0
- data/app/assets/stylesheets/dbwatcher/application.css +423 -0
- data/app/assets/stylesheets/dbwatcher/application.scss +15 -0
- data/app/assets/stylesheets/dbwatcher/components/_badges.scss +38 -0
- data/app/assets/stylesheets/dbwatcher/components/_compact_table.scss +162 -0
- data/app/assets/stylesheets/dbwatcher/components/_diagrams.scss +51 -0
- data/app/assets/stylesheets/dbwatcher/components/_forms.scss +27 -0
- data/app/assets/stylesheets/dbwatcher/components/_navigation.scss +55 -0
- data/app/assets/stylesheets/dbwatcher/core/_base.scss +34 -0
- data/app/assets/stylesheets/dbwatcher/core/_variables.scss +47 -0
- data/app/assets/stylesheets/dbwatcher/vendor/tabulator.min.css +2 -0
- data/app/controllers/dbwatcher/api/v1/sessions_controller.rb +64 -0
- data/app/controllers/dbwatcher/base_controller.rb +101 -0
- data/app/controllers/dbwatcher/dashboard_controller.rb +20 -0
- data/app/controllers/dbwatcher/queries_controller.rb +24 -0
- data/app/controllers/dbwatcher/sessions_controller.rb +30 -20
- data/app/controllers/dbwatcher/tables_controller.rb +38 -0
- data/app/helpers/dbwatcher/application_helper.rb +103 -0
- data/app/helpers/dbwatcher/component_helper.rb +29 -0
- data/app/helpers/dbwatcher/diagram_helper.rb +110 -0
- data/app/helpers/dbwatcher/formatting_helper.rb +108 -0
- data/app/helpers/dbwatcher/session_helper.rb +28 -0
- data/app/views/dbwatcher/dashboard/index.html.erb +177 -0
- data/app/views/dbwatcher/queries/index.html.erb +240 -0
- data/app/views/dbwatcher/sessions/_changes_tab.html.erb +265 -0
- data/app/views/dbwatcher/sessions/_diagrams_tab.html.erb +166 -0
- data/app/views/dbwatcher/sessions/_session_header.html.erb +11 -0
- data/app/views/dbwatcher/sessions/_summary_tab.html.erb +88 -0
- data/app/views/dbwatcher/sessions/_tab_navigation.html.erb +12 -0
- data/app/views/dbwatcher/sessions/changes.html.erb +21 -0
- data/app/views/dbwatcher/sessions/components/changes/_filters.html.erb +44 -0
- data/app/views/dbwatcher/sessions/components/changes/_table_list.html.erb +96 -0
- data/app/views/dbwatcher/sessions/diagrams.html.erb +21 -0
- data/app/views/dbwatcher/sessions/index.html.erb +124 -27
- data/app/views/dbwatcher/sessions/shared/_layout.html.erb +8 -0
- data/app/views/dbwatcher/sessions/shared/_navigation.html.erb +35 -0
- data/app/views/dbwatcher/sessions/shared/_session_header.html.erb +25 -0
- data/app/views/dbwatcher/sessions/show.html.erb +3 -149
- data/app/views/dbwatcher/sessions/summary.html.erb +21 -0
- data/app/views/dbwatcher/shared/_badge.html.erb +4 -0
- data/app/views/dbwatcher/shared/_data_table.html.erb +20 -0
- data/app/views/dbwatcher/shared/_header.html.erb +7 -0
- data/app/views/dbwatcher/shared/_page_layout.html.erb +20 -0
- data/app/views/dbwatcher/shared/_section_panel.html.erb +9 -0
- data/app/views/dbwatcher/shared/_stats_card.html.erb +11 -0
- data/app/views/dbwatcher/shared/_tab_bar.html.erb +6 -0
- data/app/views/dbwatcher/tables/changes.html.erb +225 -0
- data/app/views/dbwatcher/tables/index.html.erb +123 -0
- data/app/views/dbwatcher/tables/show.html.erb +86 -0
- data/app/views/layouts/dbwatcher/application.html.erb +252 -25
- data/bin/compile_scss +49 -0
- data/config/routes.rb +43 -3
- data/lib/dbwatcher/configuration.rb +103 -1
- data/lib/dbwatcher/engine.rb +28 -13
- data/lib/dbwatcher/logging.rb +72 -0
- data/lib/dbwatcher/services/analyzers/session_data_processor.rb +98 -0
- data/lib/dbwatcher/services/analyzers/table_summary_builder.rb +202 -0
- data/lib/dbwatcher/services/api/base_api_service.rb +100 -0
- data/lib/dbwatcher/services/api/changes_data_service.rb +112 -0
- data/lib/dbwatcher/services/api/diagram_data_service.rb +145 -0
- data/lib/dbwatcher/services/api/summary_data_service.rb +158 -0
- data/lib/dbwatcher/services/base_service.rb +64 -0
- data/lib/dbwatcher/services/dashboard_data_aggregator.rb +121 -0
- data/lib/dbwatcher/services/diagram_analyzers/base_analyzer.rb +162 -0
- data/lib/dbwatcher/services/diagram_analyzers/foreign_key_analyzer.rb +354 -0
- data/lib/dbwatcher/services/diagram_analyzers/inferred_relationship_analyzer.rb +502 -0
- data/lib/dbwatcher/services/diagram_analyzers/model_association_analyzer.rb +564 -0
- data/lib/dbwatcher/services/diagram_data/attribute.rb +154 -0
- data/lib/dbwatcher/services/diagram_data/dataset.rb +278 -0
- data/lib/dbwatcher/services/diagram_data/entity.rb +180 -0
- data/lib/dbwatcher/services/diagram_data/relationship.rb +188 -0
- data/lib/dbwatcher/services/diagram_data/relationship_params.rb +55 -0
- data/lib/dbwatcher/services/diagram_data.rb +65 -0
- data/lib/dbwatcher/services/diagram_error_handler.rb +239 -0
- data/lib/dbwatcher/services/diagram_generator.rb +154 -0
- data/lib/dbwatcher/services/diagram_strategies/base_diagram_strategy.rb +149 -0
- data/lib/dbwatcher/services/diagram_strategies/class_diagram_strategy.rb +49 -0
- data/lib/dbwatcher/services/diagram_strategies/erd_diagram_strategy.rb +52 -0
- data/lib/dbwatcher/services/diagram_strategies/flowchart_diagram_strategy.rb +52 -0
- data/lib/dbwatcher/services/diagram_system.rb +69 -0
- data/lib/dbwatcher/services/diagram_type_registry.rb +164 -0
- data/lib/dbwatcher/services/mermaid_syntax/base_builder.rb +127 -0
- data/lib/dbwatcher/services/mermaid_syntax/cardinality_mapper.rb +90 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_builder.rb +136 -0
- data/lib/dbwatcher/services/mermaid_syntax/class_diagram_helper.rb +46 -0
- data/lib/dbwatcher/services/mermaid_syntax/erd_builder.rb +116 -0
- data/lib/dbwatcher/services/mermaid_syntax/flowchart_builder.rb +109 -0
- data/lib/dbwatcher/services/mermaid_syntax/sanitizer.rb +102 -0
- data/lib/dbwatcher/services/mermaid_syntax_builder.rb +155 -0
- data/lib/dbwatcher/services/query_filter_processor.rb +114 -0
- data/lib/dbwatcher/services/table_statistics_collector.rb +119 -0
- data/lib/dbwatcher/sql_logger.rb +107 -0
- data/lib/dbwatcher/storage/api/base_api.rb +134 -0
- data/lib/dbwatcher/storage/api/concerns/table_analyzer.rb +59 -0
- data/lib/dbwatcher/storage/api/query_api.rb +95 -0
- data/lib/dbwatcher/storage/api/session_api.rb +181 -0
- data/lib/dbwatcher/storage/api/table_api.rb +86 -0
- data/lib/dbwatcher/storage/base_storage.rb +120 -0
- data/lib/dbwatcher/storage/change_processor.rb +65 -0
- data/lib/dbwatcher/storage/concerns/data_normalizer.rb +134 -0
- data/lib/dbwatcher/storage/concerns/error_handler.rb +75 -0
- data/lib/dbwatcher/storage/concerns/timestampable.rb +74 -0
- data/lib/dbwatcher/storage/concerns/validatable.rb +117 -0
- data/lib/dbwatcher/storage/date_helper.rb +21 -0
- data/lib/dbwatcher/storage/errors.rb +86 -0
- data/lib/dbwatcher/storage/file_manager.rb +122 -0
- data/lib/dbwatcher/storage/null_session.rb +39 -0
- data/lib/dbwatcher/storage/query_storage.rb +338 -0
- data/lib/dbwatcher/storage/query_validator.rb +24 -0
- data/lib/dbwatcher/storage/session.rb +58 -0
- data/lib/dbwatcher/storage/session_operations.rb +37 -0
- data/lib/dbwatcher/storage/session_query.rb +71 -0
- data/lib/dbwatcher/storage/session_storage.rb +322 -0
- data/lib/dbwatcher/storage/table_storage.rb +237 -0
- data/lib/dbwatcher/storage.rb +112 -85
- data/lib/dbwatcher/tracker.rb +4 -55
- data/lib/dbwatcher/version.rb +1 -1
- data/lib/dbwatcher.rb +70 -3
- 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 <
|
5
|
-
|
6
|
-
layout "dbwatcher/application"
|
4
|
+
class SessionsController < BaseController
|
5
|
+
before_action :find_session, except: [:index]
|
7
6
|
|
8
7
|
def index
|
9
|
-
@sessions = Storage.
|
8
|
+
@sessions = Storage.sessions.all
|
10
9
|
end
|
11
10
|
|
12
11
|
def show
|
13
|
-
@session
|
12
|
+
redirect_to changes_session_path(@session.id)
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
30
|
+
def clear
|
31
|
+
clear_storage_with_message(
|
32
|
+
-> { Storage.session_storage.clear_all },
|
33
|
+
"All sessions",
|
31
34
|
sessions_path
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
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
|