dbdoc_engine 0.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 (198) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +331 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/builds/dbdoc_engine/application.css +5 -0
  5. data/app/assets/images/dbdoc_engine/arrowdown.svg +3 -0
  6. data/app/assets/images/dbdoc_engine/arrowhorizontal.svg +3 -0
  7. data/app/assets/images/dbdoc_engine/arrowleft.svg +3 -0
  8. data/app/assets/images/dbdoc_engine/changelog.svg +3 -0
  9. data/app/assets/images/dbdoc_engine/column_stats_dbdocs.svg +23 -0
  10. data/app/assets/images/dbdoc_engine/diagram.svg +3 -0
  11. data/app/assets/images/dbdoc_engine/double_arrow.svg +4 -0
  12. data/app/assets/images/dbdoc_engine/group_bu.svg +3 -0
  13. data/app/assets/images/dbdoc_engine/japan_circle.png +0 -0
  14. data/app/assets/images/dbdoc_engine/log_in_image.png +0 -0
  15. data/app/assets/images/dbdoc_engine/logo.svg +12 -0
  16. data/app/assets/images/dbdoc_engine/orange_changelog.svg +3 -0
  17. data/app/assets/images/dbdoc_engine/orange_fields.svg +23 -0
  18. data/app/assets/images/dbdoc_engine/orange_logo.svg +12 -0
  19. data/app/assets/images/dbdoc_engine/orange_table.svg +21 -0
  20. data/app/assets/images/dbdoc_engine/orange_updates.svg +43 -0
  21. data/app/assets/images/dbdoc_engine/orange_wiki.svg +3 -0
  22. data/app/assets/images/dbdoc_engine/search.svg +3 -0
  23. data/app/assets/images/dbdoc_engine/setting.svg +3 -0
  24. data/app/assets/images/dbdoc_engine/table_dbdocs.svg +21 -0
  25. data/app/assets/images/dbdoc_engine/uk_circle_transparent.png +0 -0
  26. data/app/assets/images/dbdoc_engine/update_stats_dbdocs.svg +43 -0
  27. data/app/assets/images/dbdoc_engine/wiki.svg +3 -0
  28. data/app/assets/stylesheets/dbdoc_engine/admin.css +176 -0
  29. data/app/assets/stylesheets/dbdoc_engine/admin_header.css +179 -0
  30. data/app/assets/stylesheets/dbdoc_engine/application.scss +1 -0
  31. data/app/assets/stylesheets/dbdoc_engine/changelog.css +173 -0
  32. data/app/assets/stylesheets/dbdoc_engine/dashboard.css +513 -0
  33. data/app/assets/stylesheets/dbdoc_engine/dbdoc_application.css +117 -0
  34. data/app/assets/stylesheets/dbdoc_engine/ecommerce.css +253 -0
  35. data/app/assets/stylesheets/dbdoc_engine/group_details.css +178 -0
  36. data/app/assets/stylesheets/dbdoc_engine/header.css +212 -0
  37. data/app/assets/stylesheets/dbdoc_engine/loading_spinner.css +127 -0
  38. data/app/assets/stylesheets/dbdoc_engine/login.css +213 -0
  39. data/app/assets/stylesheets/dbdoc_engine/schema_diagram.css +149 -0
  40. data/app/assets/stylesheets/dbdoc_engine/sidebar.css +296 -0
  41. data/app/assets/stylesheets/dbdoc_engine/table_details.css +417 -0
  42. data/app/controllers/dbdoc_engine/admin/base_controller.rb +23 -0
  43. data/app/controllers/dbdoc_engine/admin/dashboard_controller.rb +16 -0
  44. data/app/controllers/dbdoc_engine/admin/data_transfer_controller.rb +63 -0
  45. data/app/controllers/dbdoc_engine/admin/db_design_dynamic_tables_controller.rb +198 -0
  46. data/app/controllers/dbdoc_engine/admin/db_design_table_groups_controller.rb +107 -0
  47. data/app/controllers/dbdoc_engine/application_controller.rb +65 -0
  48. data/app/controllers/dbdoc_engine/concerns/internationalization.rb +57 -0
  49. data/app/controllers/dbdoc_engine/db_doc_sessions_controller.rb +33 -0
  50. data/app/controllers/dbdoc_engine/home_controller.rb +79 -0
  51. data/app/controllers/dbdoc_engine/schema_diagram_controller.rb +293 -0
  52. data/app/helper/dbdoc_engine/application_helper.rb +35 -0
  53. data/app/helpers/dbdoc_engine/application_helper.rb +4 -0
  54. data/app/helpers/dbdoc_engine/changelogs_helper.rb +27 -0
  55. data/app/helpers/dbdoc_engine/column_helper.rb +30 -0
  56. data/app/helpers/dbdoc_engine/db_design_dynamic_tables_helper.rb +15 -0
  57. data/app/helpers/dbdoc_engine/home_helper.rb +75 -0
  58. data/app/javascript/dbdoc_engine/application.js +12 -0
  59. data/app/javascript/dbdoc_engine/controllers/application.js +29 -0
  60. data/app/javascript/dbdoc_engine/controllers/auto_submit_controller.js +17 -0
  61. data/app/javascript/dbdoc_engine/controllers/chart_controller.js +58 -0
  62. data/app/javascript/dbdoc_engine/controllers/column-type_controller.js +149 -0
  63. data/app/javascript/dbdoc_engine/controllers/column_controller.js +362 -0
  64. data/app/javascript/dbdoc_engine/controllers/column_search_controller.js +42 -0
  65. data/app/javascript/dbdoc_engine/controllers/dbdoc_accordion_controller.js +42 -0
  66. data/app/javascript/dbdoc_engine/controllers/ecommerce_controller.js +73 -0
  67. data/app/javascript/dbdoc_engine/controllers/group_details_controller.js +88 -0
  68. data/app/javascript/dbdoc_engine/controllers/import_export_controller.js +200 -0
  69. data/app/javascript/dbdoc_engine/controllers/index.js +9 -0
  70. data/app/javascript/dbdoc_engine/controllers/language_controller.js +100 -0
  71. data/app/javascript/dbdoc_engine/controllers/loading_spinner_controller.js +48 -0
  72. data/app/javascript/dbdoc_engine/controllers/login_controller.js +75 -0
  73. data/app/javascript/dbdoc_engine/controllers/notification_controller.js +15 -0
  74. data/app/javascript/dbdoc_engine/controllers/schema_diagram_controller.js +1129 -0
  75. data/app/javascript/dbdoc_engine/controllers/select2_controller.js +67 -0
  76. data/app/javascript/dbdoc_engine/controllers/sidebar_controller.js +943 -0
  77. data/app/javascript/dbdoc_engine/controllers/table_details_controller.js +245 -0
  78. data/app/javascript/dbdoc_engine/controllers/table_group_validation_controller.js +148 -0
  79. data/app/javascript/dbdoc_engine/controllers/table_validation_controller.js +423 -0
  80. data/app/jobs/dbdoc_engine/application_job.rb +4 -0
  81. data/app/mailers/dbdoc_engine/application_mailer.rb +6 -0
  82. data/app/models/dbdoc_engine/application_record.rb +6 -0
  83. data/app/models/dbdoc_engine/concerns/soft_deletable.rb +30 -0
  84. data/app/models/dbdoc_engine/db_design_changelog.rb +44 -0
  85. data/app/models/dbdoc_engine/db_design_dynamic_column.rb +211 -0
  86. data/app/models/dbdoc_engine/db_design_dynamic_table.rb +124 -0
  87. data/app/models/dbdoc_engine/db_design_table_group.rb +88 -0
  88. data/app/models/dbdoc_engine/user.rb +21 -0
  89. data/app/queries/dbdoc_engine/admin_dashboard_queries.rb +71 -0
  90. data/app/queries/dbdoc_engine/db_design_changelog_queries.rb +68 -0
  91. data/app/queries/dbdoc_engine/db_design_dynamic_column_queries.rb +37 -0
  92. data/app/queries/dbdoc_engine/db_design_dynamic_table_commands.rb +106 -0
  93. data/app/queries/dbdoc_engine/db_design_dynamic_table_queries.rb +194 -0
  94. data/app/queries/dbdoc_engine/db_design_table_group_queries.rb +154 -0
  95. data/app/services/dbdoc_engine/db_design_dynamic_table_export_service.rb +38 -0
  96. data/app/services/dbdoc_engine/db_design_dynamic_table_handler_service.rb +49 -0
  97. data/app/services/dbdoc_engine/db_design_dynamic_tables_service.rb +21 -0
  98. data/app/services/dbdoc_engine/error_handler_service.rb +43 -0
  99. data/app/services/dbdoc_engine/schema_rb_import_service.rb +194 -0
  100. data/app/services/dbdoc_engine/schema_rb_parser_service.rb +339 -0
  101. data/app/services/dbdoc_engine/table_filter_service.rb +35 -0
  102. data/app/services/dbdoc_engine/table_groups_service.rb +199 -0
  103. data/app/services/dbdoc_engine/table_management_service.rb +192 -0
  104. data/app/views/dbdoc_engine/admin/dashboard/_action_badge.html.erb +11 -0
  105. data/app/views/dbdoc_engine/admin/dashboard/_changelog_rows.html.erb +22 -0
  106. data/app/views/dbdoc_engine/admin/dashboard/_changelog_table_headers.html.erb +8 -0
  107. data/app/views/dbdoc_engine/admin/dashboard/_filter_fields.html.erb +43 -0
  108. data/app/views/dbdoc_engine/admin/dashboard/index.html.erb +159 -0
  109. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_column_fields.html.erb +225 -0
  110. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_deleted_table_index.html.erb +110 -0
  111. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_foreign_key_fields.html.erb +51 -0
  112. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_form.html.erb +75 -0
  113. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_recent_activity.html.erb +39 -0
  114. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_columns.html.erb +127 -0
  115. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_index.html.erb +109 -0
  116. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/_table_information.html.erb +99 -0
  117. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/deleted_tables.html.erb +95 -0
  118. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/edit.html.erb +23 -0
  119. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_all_to_excel.xlsx.axlsx +240 -0
  120. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/export_to_excel.xlsx.axlsx +135 -0
  121. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/index.html.erb +109 -0
  122. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/new.html.erb +25 -0
  123. data/app/views/dbdoc_engine/admin/db_design_dynamic_tables/show_table_info.html.erb +125 -0
  124. data/app/views/dbdoc_engine/admin/db_design_table_groups/_deleted_table_groups_list.html.erb +75 -0
  125. data/app/views/dbdoc_engine/admin/db_design_table_groups/_form.html.erb +88 -0
  126. data/app/views/dbdoc_engine/admin/db_design_table_groups/_table_groups_list.html.erb +82 -0
  127. data/app/views/dbdoc_engine/admin/db_design_table_groups/deleted_groups.html.erb +60 -0
  128. data/app/views/dbdoc_engine/admin/db_design_table_groups/edit.html.erb +25 -0
  129. data/app/views/dbdoc_engine/admin/db_design_table_groups/index.html.erb +85 -0
  130. data/app/views/dbdoc_engine/admin/db_design_table_groups/new.html.erb +26 -0
  131. data/app/views/dbdoc_engine/db_doc_sessions/new.html.erb +59 -0
  132. data/app/views/dbdoc_engine/home/changelog_details.html.erb +80 -0
  133. data/app/views/dbdoc_engine/home/changelogs.html.erb +20 -0
  134. data/app/views/dbdoc_engine/home/group_details.html.erb +94 -0
  135. data/app/views/dbdoc_engine/home/index.html.erb +11 -0
  136. data/app/views/dbdoc_engine/home/partials/_action_badge.html.erb +11 -0
  137. data/app/views/dbdoc_engine/home/partials/_breadcrumb_navigation.html.erb +30 -0
  138. data/app/views/dbdoc_engine/home/partials/_changelog_rows.html.erb +35 -0
  139. data/app/views/dbdoc_engine/home/partials/_changelog_table_headers.html.erb +16 -0
  140. data/app/views/dbdoc_engine/home/partials/_column_headers.html.erb +23 -0
  141. data/app/views/dbdoc_engine/home/partials/_column_row.html.erb +157 -0
  142. data/app/views/dbdoc_engine/home/partials/_filter_form.html.erb +47 -0
  143. data/app/views/dbdoc_engine/home/partials/_group_section.html.erb +84 -0
  144. data/app/views/dbdoc_engine/home/partials/_pagination.html.erb +5 -0
  145. data/app/views/dbdoc_engine/home/partials/_stats_container.html.erb +46 -0
  146. data/app/views/dbdoc_engine/home/partials/_table_groups.html.erb +7 -0
  147. data/app/views/dbdoc_engine/home/partials/_table_information_section.html.erb +50 -0
  148. data/app/views/dbdoc_engine/home/partials/_table_section.html.erb +48 -0
  149. data/app/views/dbdoc_engine/home/table_details.html.erb +9 -0
  150. data/app/views/dbdoc_engine/schema_diagram/index.html.erb +102 -0
  151. data/app/views/dbdoc_engine/shared/_admin_header.html.erb +78 -0
  152. data/app/views/dbdoc_engine/shared/_header.html.erb +94 -0
  153. data/app/views/dbdoc_engine/shared/_js_translations.html.erb +3 -0
  154. data/app/views/dbdoc_engine/shared/_language_button.html.erb +14 -0
  155. data/app/views/dbdoc_engine/shared/_sidebar.html.erb +128 -0
  156. data/app/views/kaminari/dbdoc_engine/_first_page.html.erb +3 -0
  157. data/app/views/kaminari/dbdoc_engine/_gap.html.erb +3 -0
  158. data/app/views/kaminari/dbdoc_engine/_last_page.html.erb +3 -0
  159. data/app/views/kaminari/dbdoc_engine/_next_page.html.erb +3 -0
  160. data/app/views/kaminari/dbdoc_engine/_page.html.erb +9 -0
  161. data/app/views/kaminari/dbdoc_engine/_paginator.html.erb +17 -0
  162. data/app/views/kaminari/dbdoc_engine/_prev_page.html.erb +3 -0
  163. data/app/views/layouts/dbdoc_engine/application.html.erb +107 -0
  164. data/app/views/layouts/dbdoc_engine/header.html.erb +108 -0
  165. data/config/importmap.rb +11 -0
  166. data/config/locales/en.yml +307 -0
  167. data/config/locales/ja.yml +306 -0
  168. data/config/routes.rb +73 -0
  169. data/db/migrate/rails7/20250227060610_create_db_design_table_groups.rb +15 -0
  170. data/db/migrate/rails7/20250227094626_create_db_design_dynamic_tables.rb +19 -0
  171. data/db/migrate/rails7/20250228022732_create_db_design_dynamic_columns.rb +34 -0
  172. data/db/migrate/rails7/20250401051453_create_db_design_changelogs.rb +26 -0
  173. data/db/migrate/rails7/20250411040822_create_users.rb +14 -0
  174. data/db/migrate/rails7/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
  175. data/db/migrate/rails8/20250227060610_create_db_design_table_groups.rb +15 -0
  176. data/db/migrate/rails8/20250227094626_create_db_design_dynamic_tables.rb +19 -0
  177. data/db/migrate/rails8/20250228022732_create_db_design_dynamic_columns.rb +34 -0
  178. data/db/migrate/rails8/20250401051453_create_db_design_changelogs.rb +26 -0
  179. data/db/migrate/rails8/20250411040822_create_users.rb +14 -0
  180. data/db/migrate/rails8/20250421080851_add_missing_indexes_to_dbdoc_tables.rb +23 -0
  181. data/db/seeds.rb +28 -0
  182. data/lib/dbdoc_engine/engine.rb +57 -0
  183. data/lib/dbdoc_engine/version.rb +3 -0
  184. data/lib/dbdoc_engine.rb +9 -0
  185. data/lib/generators/dbdoc_engine/install/install_generator.rb +245 -0
  186. data/lib/generators/dbdoc_engine/uninstall/uninstall_generator.rb +196 -0
  187. data/lib/tasks/dbdoc_engine_tasks.rake +44 -0
  188. data/public/dbdoc_engine_assets/images/camel_chess_head.png +0 -0
  189. data/public/dbdoc_engine_assets/images/dblogo.svg +4 -0
  190. data/public/dbdoc_engine_assets/images/japan_circle.png +0 -0
  191. data/public/dbdoc_engine_assets/images/king_chess_head.png +0 -0
  192. data/public/dbdoc_engine_assets/images/login-bg.svg +44 -0
  193. data/public/dbdoc_engine_assets/images/logo.png +0 -0
  194. data/public/dbdoc_engine_assets/images/logo.svg +12 -0
  195. data/public/dbdoc_engine_assets/images/queen_chess_head.png +0 -0
  196. data/public/dbdoc_engine_assets/images/soldier_chess_headd.png +0 -0
  197. data/public/dbdoc_engine_assets/images/uk_circle_transparent.png +0 -0
  198. metadata +415 -0
@@ -0,0 +1,88 @@
1
+ import { Controller } from '@hotwired/stimulus';
2
+
3
+ export default class extends Controller {
4
+ static targets = ['groupContent', 'viewButton'];
5
+
6
+ connect() {
7
+ // Initialize clickable rows when the controller connects
8
+ this.initializeClickableRows();
9
+ }
10
+
11
+ /**
12
+ * Toggles the visibility of the group content when the view button is clicked.
13
+ * @param {Event} event - The click event triggered by the user.
14
+ */
15
+ toggleGroupContent(event) {
16
+ // Get the button that was clicked
17
+ const viewButton = event.currentTarget;
18
+
19
+ // Find the closest group section to the clicked button
20
+ const groupSection = viewButton.closest('.group-section');
21
+
22
+ // Find the associated group content using the Stimulus target
23
+ const groupContent = groupSection.querySelector('[data-group-details-target="groupContent"]');
24
+
25
+ // Toggle the 'active' class on the view button
26
+ viewButton.classList.toggle('active');
27
+
28
+ // Toggle the visibility of the group content based on the button state
29
+ if (viewButton.classList.contains('active')) {
30
+ groupContent.classList.add('active');
31
+ } else {
32
+ groupContent.classList.remove('active');
33
+ }
34
+
35
+ // Prevent the event from bubbling up to the header
36
+ event.stopPropagation();
37
+ }
38
+
39
+ /**
40
+ * Toggles the group content when the header is clicked
41
+ * @param {Event} event - The click event triggered by the user.
42
+ */
43
+ toggleGroupContentHeader(event) {
44
+ // Find the closest group section to the clicked header
45
+ const groupSection = event.currentTarget.closest('.group-section');
46
+
47
+ // Find the view button and group content
48
+ const viewButton = groupSection.querySelector('[data-group-details-target="viewButton"]');
49
+ const groupContent = groupSection.querySelector('[data-group-details-target="groupContent"]');
50
+
51
+ // Toggle the 'active' class on the view button
52
+ viewButton.classList.toggle('active');
53
+
54
+ // Toggle the visibility of the group content based on the button state
55
+ if (viewButton.classList.contains('active')) {
56
+ groupContent.classList.add('active');
57
+ } else {
58
+ groupContent.classList.remove('active');
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Initialize clickable table rows
64
+ */
65
+ initializeClickableRows() {
66
+ document.addEventListener('click', (event) => {
67
+ // Find the closest table row to the clicked element
68
+ const row = event.target.closest('.group-table-row');
69
+
70
+ // If a row was found and the click wasn't on an existing link
71
+ if (row && !event.target.closest('.table-link')) {
72
+ // Find the link within the row
73
+ const link = row.querySelector('.table-link');
74
+ if (link) {
75
+ // Navigate to the link's href
76
+ window.location.href = link.getAttribute('href');
77
+ }
78
+ }
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Navigates the user to the ecommerce homepage.
84
+ */
85
+ navigateToEcommerce() {
86
+ window.location.href = '/dbdoc/';
87
+ }
88
+ }
@@ -0,0 +1,200 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["dropZone", "fileInput", "schemaDropZone", "schemaFileInput"];
5
+ static values = { basePath: String };
6
+
7
+ get adminPath() {
8
+ return `${this.basePathValue.split("?")[0].replace(/\/+$/, "")}/admin`;
9
+ }
10
+
11
+ export(event) {
12
+ event.preventDefault();
13
+ fetch(`${this.adminPath}/export_data.json`)
14
+ .then(r => r.json())
15
+ .then(data => {
16
+ const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
17
+ const url = URL.createObjectURL(blob);
18
+ const a = document.createElement("a");
19
+ a.href = url;
20
+ a.download = "dbdoc_engine_export.json";
21
+ document.body.appendChild(a);
22
+ a.click();
23
+ document.body.removeChild(a);
24
+ })
25
+ .catch(() => this.showError("Export failed!"));
26
+ }
27
+
28
+ showImport(event) {
29
+ event.preventDefault();
30
+ if (!this.hasDropZoneTarget) return;
31
+ this.dropZoneTarget.style.display = "block";
32
+ document.addEventListener("click", this.handleDocumentClick);
33
+ }
34
+
35
+ handleDocumentClick = (e) => {
36
+ if (this.hasDropZoneTarget && this.dropZoneTarget.contains(e.target)) return;
37
+ if (e.target.closest('[data-action="click->import-export#showImport"]')) return;
38
+ if (this.hasDropZoneTarget) this.dropZoneTarget.style.display = "none";
39
+ document.removeEventListener("click", this.handleDocumentClick);
40
+ }
41
+
42
+ handleFileChange(event) {
43
+ if (event.target.files.length) this.handleImportFile(event.target.files[0]);
44
+ }
45
+
46
+ handleDrop(event) {
47
+ event.preventDefault();
48
+ if (this.hasDropZoneTarget) this.dropZoneTarget.style.borderColor = "#ccc";
49
+ if (event.dataTransfer.files.length) this.handleImportFile(event.dataTransfer.files[0]);
50
+ }
51
+
52
+ handleDragOver(event) {
53
+ event.preventDefault();
54
+ const zone = event.currentTarget;
55
+ zone.style.borderColor = "#0d6efd";
56
+ }
57
+
58
+ handleDragLeave(event) {
59
+ event.preventDefault();
60
+ const zone = event.currentTarget;
61
+ zone.style.borderColor = "#ccc";
62
+ }
63
+
64
+ handleImportFile(file) {
65
+ if (!file.name.endsWith(".json")) {
66
+ this.showWarning("Please select a valid JSON file.");
67
+ return;
68
+ }
69
+ const reader = new FileReader();
70
+ reader.onload = (e) => {
71
+ fetch(`${this.adminPath}/import_data`, {
72
+ method: "POST",
73
+ headers: { "Content-Type": "application/json", "X-CSRF-Token": this.csrfToken },
74
+ body: e.target.result
75
+ })
76
+ .then(r => r.json())
77
+ .then(data => {
78
+ if (data.status === "ok") {
79
+ this.showSuccess("Import successful!").then(() => window.location.reload());
80
+ } else {
81
+ this.showError("Import failed: " + (data.error || "Unknown error"));
82
+ }
83
+ })
84
+ .catch(() => this.showError("Import failed!"));
85
+ };
86
+ reader.readAsText(file);
87
+ }
88
+
89
+ async importProjectSchema(event) {
90
+ event.preventDefault();
91
+
92
+ const confirmed = await this.showConfirm(
93
+ "Import Project Schema",
94
+ "This will replace all existing documentation with tables from this project's schema.rb. Continue?"
95
+ );
96
+ if (!confirmed) return;
97
+
98
+ fetch(`${this.adminPath}/import_schema?source=project`, {
99
+ method: "POST",
100
+ headers: { "Content-Type": "application/json", "X-CSRF-Token": this.csrfToken }
101
+ })
102
+ .then(r => r.json())
103
+ .then(data => {
104
+ if (data.status === "ok") {
105
+ this.showSuccess(`Schema import successful! ${data.tables_count} tables imported.`)
106
+ .then(() => window.location.reload());
107
+ } else {
108
+ this.showError("Schema import failed: " + (data.error || "Unknown error"));
109
+ }
110
+ })
111
+ .catch(() => this.showError("Schema import failed!"));
112
+ }
113
+
114
+ showSchemaUpload(event) {
115
+ event.preventDefault();
116
+ if (!this.hasSchemaDropZoneTarget) return;
117
+ this.schemaDropZoneTarget.style.display = "block";
118
+ document.addEventListener("click", this.handleSchemaDocumentClick);
119
+ }
120
+
121
+ handleSchemaDocumentClick = (e) => {
122
+ if (this.hasSchemaDropZoneTarget && this.schemaDropZoneTarget.contains(e.target)) return;
123
+ if (e.target.closest('[data-action="click->import-export#showSchemaUpload"]')) return;
124
+ if (this.hasSchemaDropZoneTarget) this.schemaDropZoneTarget.style.display = "none";
125
+ document.removeEventListener("click", this.handleSchemaDocumentClick);
126
+ }
127
+
128
+ handleSchemaFileChange(event) {
129
+ if (event.target.files.length) this.handleSchemaImportFile(event.target.files[0]);
130
+ }
131
+
132
+ handleSchemaDrop(event) {
133
+ event.preventDefault();
134
+ if (this.hasSchemaDropZoneTarget) this.schemaDropZoneTarget.style.borderColor = "#ccc";
135
+ if (event.dataTransfer.files.length) this.handleSchemaImportFile(event.dataTransfer.files[0]);
136
+ }
137
+
138
+ async handleSchemaImportFile(file) {
139
+ if (!file.name.endsWith(".rb")) {
140
+ this.showWarning("Please select a valid Ruby schema file (.rb).");
141
+ return;
142
+ }
143
+
144
+ const confirmed = await this.showConfirm(
145
+ "Import Schema File",
146
+ "This will replace all existing documentation with tables from the uploaded schema.rb. Continue?"
147
+ );
148
+ if (!confirmed) return;
149
+
150
+ const reader = new FileReader();
151
+ reader.onload = (e) => {
152
+ fetch(`${this.adminPath}/import_schema`, {
153
+ method: "POST",
154
+ headers: { "Content-Type": "text/plain", "X-CSRF-Token": this.csrfToken },
155
+ body: e.target.result
156
+ })
157
+ .then(r => r.json())
158
+ .then(data => {
159
+ if (data.status === "ok") {
160
+ this.showSuccess(`Schema import successful! ${data.tables_count} tables imported.`)
161
+ .then(() => window.location.reload());
162
+ } else {
163
+ this.showError("Schema import failed: " + (data.error || "Unknown error"));
164
+ }
165
+ })
166
+ .catch(() => this.showError("Schema import failed!"));
167
+ };
168
+ reader.readAsText(file);
169
+ }
170
+
171
+ get csrfToken() {
172
+ const meta = document.querySelector('meta[name="csrf-token"]');
173
+ return meta ? meta.content : "";
174
+ }
175
+
176
+ showSuccess(message) {
177
+ return Swal.fire({ icon: "success", title: "Success", text: message, confirmButtonColor: "#364380" });
178
+ }
179
+
180
+ showError(message) {
181
+ return Swal.fire({ icon: "error", title: "Error", text: message, confirmButtonColor: "#364380" });
182
+ }
183
+
184
+ showWarning(message) {
185
+ return Swal.fire({ icon: "warning", title: "Warning", text: message, confirmButtonColor: "#364380" });
186
+ }
187
+
188
+ showConfirm(title, text) {
189
+ return Swal.fire({
190
+ icon: "warning",
191
+ title: title,
192
+ text: text,
193
+ showCancelButton: true,
194
+ confirmButtonColor: "#364380",
195
+ cancelButtonColor: "#6c757d",
196
+ confirmButtonText: "Yes, proceed",
197
+ cancelButtonText: "Cancel"
198
+ }).then(result => result.isConfirmed);
199
+ }
200
+ }
@@ -0,0 +1,9 @@
1
+ import { dbdoc_engine } from "controllers/application";
2
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
3
+ import ChartController from "./chart_controller"
4
+ import DbdocAccordionController from "./dbdoc_accordion_controller"
5
+
6
+ dbdoc_engine.register("chart", ChartController)
7
+ dbdoc_engine.register("dbdoc-accordion", DbdocAccordionController)
8
+
9
+ eagerLoadControllersFrom("controllers", dbdoc_engine);
@@ -0,0 +1,100 @@
1
+ // app/javascript/controllers/language_controller.js
2
+ import { Controller } from '@hotwired/stimulus';
3
+
4
+ export default class extends Controller {
5
+ static targets = ['button'];
6
+
7
+ connect() {
8
+ this.updateButtonDisplay();
9
+ this.checkActiveButton();
10
+ }
11
+
12
+ toggleLanguage(event) {
13
+ event.preventDefault();
14
+
15
+ // Get current locale from HTML lang attribute or body data attribute
16
+ const currentLocale = document.documentElement.lang ||
17
+ document.body.dataset.locale ||
18
+ 'en';
19
+
20
+ // Toggle between English and Japanese
21
+ const newLocale = currentLocale === 'ja' ? 'en' : 'ja';
22
+
23
+ // Add locale to URL and redirect
24
+ const url = new URL(window.location.href);
25
+ url.searchParams.set('locale', newLocale);
26
+
27
+ // Store in localStorage for immediate visual feedback before page reloads
28
+ localStorage.setItem('userLocale', newLocale);
29
+
30
+ // Navigate to the new URL
31
+ window.location.href = url.toString();
32
+ }
33
+
34
+ /**
35
+ * Updates the language button display based on the current locale.
36
+ */
37
+ updateButtonDisplay() {
38
+ if (!this.hasButtonTarget) return;
39
+
40
+ const currentLocale = document.documentElement.lang ||
41
+ document.body.dataset.locale ||
42
+ localStorage.getItem('userLocale') ||
43
+ 'en';
44
+
45
+ // Set the flag image based on the current locale
46
+ if (currentLocale === 'ja') {
47
+ this.buttonTarget.innerHTML = '<img src="/dbdoc_engine_assets/images/japan_circle.png" alt="日本語" class="language-japan-flag">';
48
+ this.buttonTarget.title = '日本語';
49
+ } else {
50
+ this.buttonTarget.innerHTML = '<img src="/dbdoc_engine_assets/images/uk_circle_transparent.png" alt="English" class="language-flag">';
51
+ this.buttonTarget.title = 'English';
52
+ }
53
+ }
54
+
55
+ checkActiveButton() {
56
+ const navButtons = document.querySelectorAll('.nav-button');
57
+
58
+ // Remove 'active' class from all navigation buttons
59
+ navButtons.forEach(button => {
60
+ button.classList.remove('active');
61
+ });
62
+
63
+ // Get the current page path (without query parameters)
64
+ const currentPath = window.location.pathname;
65
+
66
+ // Find the navigation buttons
67
+ const wikiButton = document.querySelector('.wiki-button');
68
+ const changelogButton = document.querySelector('.changelog-button');
69
+ const diagramButton = document.querySelector('.diagram-button');
70
+
71
+ // Define routes where each button should be active
72
+ const wikiActiveRoutes = ['/dbdoc/', '/dbdoc/group_details', '/dbdoc/table_details'];
73
+ const changelogRoutes = ['/dbdoc/changelogs'];
74
+ const diagramRoutes = ['/dbdoc/schema_diagram'];
75
+
76
+ // Check if the current path matches diagram routes first (highest priority)
77
+ const isDiagramActivePath = diagramRoutes.some(route =>
78
+ currentPath === route || currentPath.startsWith(route + '/')
79
+ );
80
+
81
+ // Check if the current path matches changelog routes
82
+ const isChangelogActivePath = changelogRoutes.some(route =>
83
+ currentPath === route || currentPath.startsWith(route + '/')
84
+ );
85
+
86
+ // Check if the current path matches wiki routes
87
+ const isWikiActivePath = wikiActiveRoutes.some(route =>
88
+ currentPath === route || currentPath.startsWith(route + '/')
89
+ );
90
+
91
+ // Set active class based on path, with proper prioritization
92
+ if (isDiagramActivePath && diagramButton) {
93
+ diagramButton.classList.add('active');
94
+ } else if (isChangelogActivePath && changelogButton) {
95
+ changelogButton.classList.add('active');
96
+ } else if (isWikiActivePath && wikiButton) {
97
+ wikiButton.classList.add('active');
98
+ }
99
+ }
100
+ }
@@ -0,0 +1,48 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.boundShow = this.show.bind(this);
6
+ this.boundHide = this.hide.bind(this);
7
+
8
+ document.addEventListener("turbo:before-visit", this.boundShow);
9
+ document.addEventListener("turbo:before-fetch-request", this.boundShow);
10
+ document.addEventListener("turbo:load", this.boundHide);
11
+ document.addEventListener("turbo:frame-load", this.boundHide);
12
+ document.addEventListener("turbo:fetch-request-error", this.boundHide);
13
+
14
+ if (document.readyState !== "complete") {
15
+ this.show();
16
+ window.addEventListener("load", this.boundHide, { once: true });
17
+ }
18
+ }
19
+
20
+ disconnect() {
21
+ document.removeEventListener("turbo:before-visit", this.boundShow);
22
+ document.removeEventListener("turbo:before-fetch-request", this.boundShow);
23
+ document.removeEventListener("turbo:load", this.boundHide);
24
+ document.removeEventListener("turbo:frame-load", this.boundHide);
25
+ document.removeEventListener("turbo:fetch-request-error", this.boundHide);
26
+ window.removeEventListener("load", this.boundHide);
27
+ this.clearSafetyTimeout();
28
+ }
29
+
30
+ show() {
31
+ this.clearSafetyTimeout();
32
+ this.element.setAttribute("data-loading-spinner-visible", "true");
33
+
34
+ this.safetyTimeout = setTimeout(() => this.hide(), 8000);
35
+ }
36
+
37
+ hide() {
38
+ this.clearSafetyTimeout();
39
+ this.element.setAttribute("data-loading-spinner-visible", "false");
40
+ }
41
+
42
+ clearSafetyTimeout() {
43
+ if (this.safetyTimeout) {
44
+ clearTimeout(this.safetyTimeout);
45
+ this.safetyTimeout = null;
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,75 @@
1
+ import { Controller } from "@hotwired/stimulus" // Import the Stimulus Controller base class
2
+
3
+ export default class extends Controller {
4
+ static targets = ["username", "password"] // Define the form fields that we want to target
5
+
6
+ connect() {
7
+ // Initialize the controller when it connects to the DOM
8
+ this.clearErrors() // Clear any existing error messages when the controller connects
9
+ }
10
+
11
+ validate(event) {
12
+ event.preventDefault() // Stop the form from submitting normally so we can validate first
13
+
14
+ let isValid = true // Assume the form is valid until we find errors
15
+ this.clearErrors() // Remove any previous error messages
16
+
17
+ // Validate username
18
+ const username = this.usernameTarget.value.trim() // Get the trimmed username value
19
+ if (!username) {
20
+ this.showError(this.usernameTarget, "username_required") // Display username error message
21
+ isValid = false // Mark the form as invalid
22
+ }
23
+
24
+ // Validate password
25
+ const password = this.passwordTarget.value.trim() // Get the trimmed password value
26
+ if (!password) {
27
+ this.showError(this.passwordTarget, "password_required") // Display password error message
28
+ isValid = false // Mark the form as invalid
29
+ }
30
+
31
+ if (isValid) {
32
+ event.target.submit() // Submit the form if all validations pass
33
+ }
34
+ }
35
+
36
+ showError(element, messageKey) {
37
+ if (!element) return // Guard clause if element is undefined
38
+
39
+ // Add 'is-invalid' class to highlight the input field in red
40
+ element.classList.add('is-invalid')
41
+
42
+ // Get translated error message from translations
43
+ const translatedMessage = this.getTranslation(messageKey)
44
+
45
+ // Check if error message element already exists
46
+ let errorElement = element.nextElementSibling
47
+ if (!errorElement || !errorElement.classList.contains('invalid-feedback')) {
48
+ errorElement = document.createElement('div') // Create a new error message container
49
+ errorElement.classList.add('invalid-feedback', 'd-block') // Style it as Bootstrap error message
50
+ element.parentNode.appendChild(errorElement) // Add it after the input field
51
+ }
52
+
53
+ // Set the error message text
54
+ errorElement.textContent = translatedMessage
55
+ }
56
+
57
+ getTranslation(key) {
58
+ // Look for the error message in the global Translations object
59
+ if (window.Translations && window.Translations[key]) {
60
+ return window.Translations[key] // Return the translated message if found
61
+ }
62
+ return key // Fall back to the key itself if translation not found
63
+ }
64
+
65
+ clearErrors() {
66
+ // Find all inputs with error styling
67
+ this.element.querySelectorAll('.is-invalid').forEach(el => {
68
+ el.classList.remove('is-invalid') // Remove the error styling class
69
+ const errorEl = el.nextElementSibling // Get the error message element
70
+ if (errorEl && errorEl.classList.contains('invalid-feedback')) {
71
+ errorEl.remove() // Remove the error message element from the DOM
72
+ }
73
+ })
74
+ }
75
+ }
@@ -0,0 +1,15 @@
1
+ import { Controller } from "@hotwired/stimulus";
2
+
3
+ export default class extends Controller {
4
+ static targets = ["sidebar"];
5
+
6
+ openSidebar() {
7
+ this.sidebarTarget.classList.add("open");
8
+ document.body.classList.add("notification-sidebar-open");
9
+ }
10
+
11
+ closeSidebar() {
12
+ this.sidebarTarget.classList.remove("open");
13
+ document.body.classList.remove("notification-sidebar-open");
14
+ }
15
+ }