Module: Dbviewer::ApplicationHelper
- Defined in:
- app/helpers/dbviewer/application_helper.rb
Instance Method Summary collapse
-
#active_nav_class(controller_name, action_name = nil) ⇒ Object
Helper method to determine if current controller and action match.
-
#code_block_bg_class ⇒ Object
Helper method for code blocks background that adapts to dark mode.
-
#column_type_from_info(column_name, columns) ⇒ Object
Extract column type from columns info.
-
#column_type_icon(column_type) ⇒ Object
Get appropriate icon for column data type.
-
#common_params(options = {}) ⇒ Object
Common parameters for pagination and filtering.
-
#current_table?(table_name) ⇒ Boolean
Determine if the current table should be active in the sidebar.
-
#dashboard_nav_class ⇒ Object
Helper for highlighting dashboard link.
-
#default_operator_for_column_type(column_type) ⇒ Object
Determine default operator based on column type.
-
#erd_nav_class ⇒ Object
Helper for highlighting ERD link.
- #format_cell_value(value) ⇒ Object
-
#format_table_name(table_name) ⇒ Object
Format table name for display - truncate if too long.
-
#get_database_manager ⇒ Object
Helper to access the database manager.
-
#has_timestamp_column?(table_name) ⇒ Boolean
Check if a table has a created_at column.
-
#logs_nav_class ⇒ Object
Helper for highlighting SQL Logs link.
-
#next_sort_direction(column_name, current_order_by, current_direction) ⇒ Object
Determine the next sort direction based on the current one.
-
#operator_options_for_column_type(column_type) ⇒ Object
Generate operator options based on column type.
-
#per_page_url_params(table_name) ⇒ Object
Generate URL parameters for per-page dropdown.
-
#render_action_cell(row_data, columns, metadata = nil) ⇒ Object
Render action buttons for a record.
-
#render_column_filter(form, column_name, columns, column_filters) ⇒ Object
Render complete filter input group for a column.
-
#render_column_filter_input(form, column_name, column_type, column_filters) ⇒ Object
Render column filter input based on column type.
-
#render_column_filters_row(form, records, columns, column_filters) ⇒ Object
Render the column filters row.
-
#render_operator_select(form, column_name, column_type, column_filters) ⇒ Object
Render operator select for column filter.
-
#render_pagination(table_name, current_page, total_pages, params = {}) ⇒ Object
Render pagination UI.
-
#render_sortable_header_row(records, order_by, order_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Render a complete table header row with sortable columns.
-
#render_table_body(records, metadata) ⇒ Object
Render the entire table body with rows.
-
#render_table_cell(cell, column_name, metadata) ⇒ Object
Render a cell that may include a foreign key link.
-
#render_table_row(row, records, metadata) ⇒ Object
Render a table row with cells.
-
#sort_icon(column_name, current_order_by, current_direction) ⇒ Object
Returns a sort icon based on the current sort direction.
-
#sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Generate a sortable column header link.
-
#stat_card_bg_class ⇒ Object
Returns the appropriate background class for stat cards that adapts to dark mode.
-
#tables_nav_class ⇒ Object
Helper for highlighting tables link.
-
#theme_toggle_icon ⇒ Object
Returns the theme toggle icon based on the current theme.
-
#theme_toggle_label ⇒ Object
Returns the aria label for the theme toggle button.
-
#time_grouping_links(table_name, current_grouping) ⇒ Object
Render time grouping links.
Instance Method Details
#active_nav_class(controller_name, action_name = nil) ⇒ Object
Helper method to determine if current controller and action match
290 291 292 293 294 295 296 297 298 299 |
# File 'app/helpers/dbviewer/application_helper.rb', line 290 def active_nav_class(controller_name, action_name = nil) current_controller = params[:controller].split("/").last active = current_controller == controller_name if action_name.present? active = active && params[:action] == action_name end active ? "active" : "" end |
#code_block_bg_class ⇒ Object
Helper method for code blocks background that adapts to dark mode
251 252 253 |
# File 'app/helpers/dbviewer/application_helper.rb', line 251 def code_block_bg_class "sql-code-block" end |
#column_type_from_info(column_name, columns) ⇒ Object
Extract column type from columns info
18 19 20 21 22 23 |
# File 'app/helpers/dbviewer/application_helper.rb', line 18 def column_type_from_info(column_name, columns) return nil unless columns.present? column_info = columns.find { |c| c[:name].to_s == column_name.to_s } column_info ? column_info[:type].to_s.downcase : nil end |
#column_type_icon(column_type) ⇒ Object
Get appropriate icon for column data type
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 |
# File 'app/helpers/dbviewer/application_helper.rb', line 270 def column_type_icon(column_type) case column_type.to_s.downcase when /int/, /serial/, /number/, /decimal/, /float/, /double/ "bi-123" when /char/, /text/, /string/, /uuid/ "bi-fonts" when /date/, /time/ "bi-calendar" when /bool/ "bi-toggle-on" when /json/, /jsonb/ "bi-braces" when /array/ "bi-list-ol" else "bi-file-earmark" end end |
#common_params(options = {}) ⇒ Object
Common parameters for pagination and filtering
149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'app/helpers/dbviewer/application_helper.rb', line 149 def common_params( = {}) params = { order_by: @order_by, order_direction: @order_direction, per_page: @per_page, column_filters: @column_filters }.merge() # Add creation filters if they exist params[:creation_filter_start] = @creation_filter_start if @creation_filter_start.present? params[:creation_filter_end] = @creation_filter_end if @creation_filter_end.present? params end |
#current_table?(table_name) ⇒ Boolean
Determine if the current table should be active in the sidebar
256 257 258 |
# File 'app/helpers/dbviewer/application_helper.rb', line 256 def current_table?(table_name) @table_name.present? && @table_name == table_name end |
#dashboard_nav_class ⇒ Object
Helper for highlighting dashboard link
302 303 304 |
# File 'app/helpers/dbviewer/application_helper.rb', line 302 def dashboard_nav_class active_nav_class("home") end |
#default_operator_for_column_type(column_type) ⇒ Object
Determine default operator based on column type
26 27 28 29 30 31 32 |
# File 'app/helpers/dbviewer/application_helper.rb', line 26 def default_operator_for_column_type(column_type) if column_type && column_type =~ /char|text|string|uuid|enum/i "contains" else "eq" end end |
#erd_nav_class ⇒ Object
Helper for highlighting ERD link
312 313 314 |
# File 'app/helpers/dbviewer/application_helper.rb', line 312 def erd_nav_class active_nav_class("entity_relationship_diagrams") end |
#format_cell_value(value) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 |
# File 'app/helpers/dbviewer/application_helper.rb', line 121 def format_cell_value(value) return "NULL" if value.nil? return value.to_s.truncate(100) unless value.is_a?(String) case value when /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/ # ISO 8601 datetime begin Time.parse(value).strftime("%Y-%m-%d %H:%M:%S") rescue value.to_s.truncate(100) end when /\A\d{4}-\d{2}-\d{2}\z/ # Date value when /\A{.+}\z/, /\A\[.+\]\z/ # JSON begin JSON.pretty_generate(JSON.parse(value)).truncate(100) rescue value.to_s.truncate(100) end else value.to_s.truncate(100) end end |
#format_table_name(table_name) ⇒ Object
Format table name for display - truncate if too long
261 262 263 264 265 266 267 |
# File 'app/helpers/dbviewer/application_helper.rb', line 261 def format_table_name(table_name) if table_name.length > 20 "#{table_name.first(17)}..." else table_name end end |
#get_database_manager ⇒ Object
Helper to access the database manager
13 14 15 |
# File 'app/helpers/dbviewer/application_helper.rb', line 13 def get_database_manager @database_manager ||= ::Dbviewer::DatabaseManager.new end |
#has_timestamp_column?(table_name) ⇒ Boolean
Check if a table has a created_at column
4 5 6 7 8 9 10 |
# File 'app/helpers/dbviewer/application_helper.rb', line 4 def (table_name) return false unless table_name.present? # Get the columns for the table directly using DatabaseManager columns = get_database_manager.table_columns(table_name) columns.any? { |col| col[:name] == "created_at" && [ :datetime, :timestamp ].include?(col[:type]) } end |
#logs_nav_class ⇒ Object
Helper for highlighting SQL Logs link
317 318 319 |
# File 'app/helpers/dbviewer/application_helper.rb', line 317 def logs_nav_class active_nav_class("logs") end |
#next_sort_direction(column_name, current_order_by, current_direction) ⇒ Object
Determine the next sort direction based on the current one
332 333 334 335 336 337 338 |
# File 'app/helpers/dbviewer/application_helper.rb', line 332 def next_sort_direction(column_name, current_order_by, current_direction) if column_name == current_order_by && current_direction == "ASC" "DESC" else "ASC" end end |
#operator_options_for_column_type(column_type) ⇒ Object
Generate operator options based on column type
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'app/helpers/dbviewer/application_helper.rb', line 35 def (column_type) if column_type && (column_type =~ /datetime/ || column_type =~ /^date$/ || column_type =~ /^time$/) # Date/Time operators [ [ "=", "eq" ], [ "≠", "neq" ], [ "<", "lt" ], [ ">", "gt" ], [ "≤", "lte" ], [ "≥", "gte" ] ] elsif column_type && column_type =~ /int|float|decimal|double|number|numeric|real|money|bigint|smallint|tinyint|mediumint|bit/i # Numeric operators [ [ "=", "eq" ], [ "≠", "neq" ], [ "<", "lt" ], [ ">", "gt" ], [ "≤", "lte" ], [ "≥", "gte" ] ] else # Text operators [ [ "contains", "contains" ], [ "not contains", "not_contains" ], [ "=", "eq" ], [ "≠", "neq" ], [ "starts with", "starts_with" ], [ "ends with", "ends_with" ] ] end end |
#per_page_url_params(table_name) ⇒ Object
Generate URL parameters for per-page dropdown
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'app/helpers/dbviewer/application_helper.rb', line 196 def per_page_url_params(table_name) # Start with the dynamic part for the select element url_params = "per_page=' + this.value + '&page=1" # Add all other common parameters except per_page and page which we already set params = common_params.except(:per_page, :page) # Convert the params hash to URL parameters params.each do |key, value| if key == :column_filters && value.is_a?(Hash) && value.reject { |_, v| v.blank? }.any? value.reject { |_, v| v.blank? }.each do |filter_key, filter_value| url_params += "&column_filters[#{filter_key}]=#{CGI.escape(filter_value.to_s)}" end elsif value.present? url_params += "&#{key}=#{CGI.escape(value.to_s)}" end end url_params end |
#render_action_cell(row_data, columns, metadata = nil) ⇒ Object
Render action buttons for a record
461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 |
# File 'app/helpers/dbviewer/application_helper.rb', line 461 def render_action_cell(row_data, columns, = nil) data_attributes = {} # Create a hash of column_name: value pairs for data attributes columns.each_with_index do |column_name, index| data_attributes[column_name] = row_data[index].to_s end content_tag(:td, class: "text-center action-column") do ( type: "button", class: "btn btn-sm btn-primary view-record-btn", title: "View Record Details", data: { bs_toggle: "modal", bs_target: "#recordDetailModal", record_data: data_attributes.to_json, foreign_keys: && [:foreign_keys] ? [:foreign_keys].to_json : "[]", reverse_foreign_keys: && [:reverse_foreign_keys] ? [:reverse_foreign_keys].to_json : "[]" } ) do content_tag(:i, "", class: "bi bi-eye") end end end |
#render_column_filter(form, column_name, columns, column_filters) ⇒ Object
Render complete filter input group for a column
112 113 114 115 116 117 118 119 |
# File 'app/helpers/dbviewer/application_helper.rb', line 112 def render_column_filter(form, column_name, columns, column_filters) column_type = column_type_from_info(column_name, columns) content_tag(:div, class: "filter-input-group") do render_operator_select(form, column_name, column_type, column_filters) + render_column_filter_input(form, column_name, column_type, column_filters) end end |
#render_column_filter_input(form, column_name, column_type, column_filters) ⇒ Object
Render column filter input based on column type
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'app/helpers/dbviewer/application_helper.rb', line 70 def render_column_filter_input(form, column_name, column_type, column_filters) if column_type && column_type =~ /datetime/ form.datetime_local_field("column_filters[#{column_name}]", value: column_filters[column_name], class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) elsif column_type && column_type =~ /^date$/ form.date_field("column_filters[#{column_name}]", value: column_filters[column_name], class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) elsif column_type && column_type =~ /^time$/ form.time_field("column_filters[#{column_name}]", value: column_filters[column_name], class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) else form.text_field("column_filters[#{column_name}]", value: column_filters[column_name], placeholder: "", class: "form-control form-control-sm column-filter rounded-0", data: { column: column_name }) end end |
#render_column_filters_row(form, records, columns, column_filters) ⇒ Object
Render the column filters row
390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'app/helpers/dbviewer/application_helper.rb', line 390 def render_column_filters_row(form, records, columns, column_filters) return content_tag(:tr) { content_tag(:th, "") } unless records&.columns content_tag(:tr, class: "column-filters") do filters = records.columns.map do |column_name| content_tag(:th, class: "p-0") do render_column_filter(form, column_name, columns, column_filters) end end filters.join.html_safe end end |
#render_operator_select(form, column_name, column_type, column_filters) ⇒ Object
Render operator select for column filter
96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'app/helpers/dbviewer/application_helper.rb', line 96 def render_operator_select(form, column_name, column_type, column_filters) # Get previously selected operator or default default_operator = default_operator_for_column_type(column_type) selected_operator = column_filters["#{column_name}_operator"] selected_operator = default_operator if selected_operator.nil? || selected_operator == "default" # Get appropriate options = (column_type) form.select("column_filters[#{column_name}_operator]", (, selected_operator), { include_blank: false }, { class: "form-select form-select-sm operator-select" }) end |
#render_pagination(table_name, current_page, total_pages, params = {}) ⇒ Object
Render pagination UI
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'app/helpers/dbviewer/application_helper.rb', line 165 def render_pagination(table_name, current_page, total_pages, params = {}) return unless total_pages && total_pages > 1 content_tag(:nav, 'aria-label': "Page navigation") do content_tag(:ul, class: "pagination justify-content-center") do prev_link = content_tag(:li, class: "page-item #{current_page == 1 ? 'disabled' : ''}") do link_to "«", table_path(table_name, params.merge(page: [ current_page - 1, 1 ].max)), class: "page-link" end # Calculate page range to display start_page = [ 1, current_page - 2 ].max end_page = [ start_page + 4, total_pages ].min start_page = [ 1, end_page - 4 ].max # Generate page links page_links = (start_page..end_page).map do |page_num| content_tag(:li, class: "page-item #{page_num == current_page ? 'active' : ''}") do link_to page_num, table_path(table_name, params.merge(page: page_num)), class: "page-link" end end.join.html_safe next_link = content_tag(:li, class: "page-item #{current_page == total_pages ? 'disabled' : ''}") do link_to "»", table_path(table_name, params.merge(page: [ current_page + 1, total_pages ].min)), class: "page-link" end prev_link + page_links + next_link end end end |
#render_sortable_header_row(records, order_by, order_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Render a complete table header row with sortable columns
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'app/helpers/dbviewer/application_helper.rb', line 366 def render_sortable_header_row(records, order_by, order_direction, table_name, current_page, per_page, column_filters) return content_tag(:tr) { content_tag(:th, "No columns available") } unless records&.columns content_tag(:tr) do # Start with action column header (sticky first column) headers = [ content_tag(:th, class: "px-3 py-2 text-center action-column action-column-header", width: "60px", rowspan: 2) do content_tag(:span, "Actions") end ] # Add all data columns headers += records.columns.map do |column_name| is_sorted = order_by == column_name content_tag(:th, class: "px-3 py-2 sortable-column #{is_sorted ? 'sorted' : ''}") do sortable_column_header(column_name, order_by, order_direction, table_name, current_page, per_page, column_filters) end end headers.join.html_safe end end |
#render_table_body(records, metadata) ⇒ Object
Render the entire table body with rows
442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# File 'app/helpers/dbviewer/application_helper.rb', line 442 def render_table_body(records, ) if records.nil? || records.rows.nil? || records.empty? content_tag(:tbody) do content_tag(:tr) do # Adding +1 to account for the action column total_columns = records&.columns&.size.to_i + 1 content_tag(:td, "No records found or table is empty.", colspan: total_columns, class: "text-center") end end else content_tag(:tbody) do records.rows.map do |row| render_table_row(row, records, ) end.join.html_safe end end end |
#render_table_cell(cell, column_name, metadata) ⇒ Object
Render a cell that may include a foreign key link
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'app/helpers/dbviewer/application_helper.rb', line 405 def render_table_cell(cell, column_name, ) cell_value = format_cell_value(cell) foreign_key = && [:foreign_keys] ? [:foreign_keys].find { |fk| fk[:column] == column_name } : nil if foreign_key && !cell.nil? fk_params = { column_filters: { foreign_key[:primary_key] => cell } } fk_params = fk_params.merge(common_params.except(:column_filters)) content_tag(:td, title: "#{cell_value} (Click to view referenced record)") do link_to(cell_value, table_path(foreign_key[:to_table], fk_params), class: "text-decoration-none foreign-key-link") + content_tag(:i, "", class: "bi bi-link-45deg text-muted small") end else content_tag(:td, cell_value, title: cell_value) end end |
#render_table_row(row, records, metadata) ⇒ Object
Render a table row with cells
426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'app/helpers/dbviewer/application_helper.rb', line 426 def render_table_row(row, records, ) content_tag(:tr) do # Start with action column (sticky first column) cells = [ render_action_cell(row, records.columns, ) ] # Add all data cells cells += row.each_with_index.map do |cell, cell_index| column_name = records.columns[cell_index] render_table_cell(cell, column_name, ) end cells.join.html_safe end end |
#sort_icon(column_name, current_order_by, current_direction) ⇒ Object
Returns a sort icon based on the current sort direction
322 323 324 325 326 327 328 329 |
# File 'app/helpers/dbviewer/application_helper.rb', line 322 def sort_icon(column_name, current_order_by, current_direction) if column_name == current_order_by direction = current_direction == "ASC" ? "up" : "down" "<i class='bi bi-sort-#{direction}'></i>".html_safe else "<i class='bi bi-filter invisible sort-icon'></i>".html_safe end end |
#sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters) ⇒ Object
Generate a sortable column header link
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 |
# File 'app/helpers/dbviewer/application_helper.rb', line 341 def sortable_column_header(column_name, current_order_by, current_direction, table_name, current_page, per_page, column_filters) is_sorted = column_name == current_order_by sort_direction = next_sort_direction(column_name, current_order_by, current_direction) aria_sort = if is_sorted current_direction.downcase == "asc" ? "ascending" : "descending" else "none" end # Use common_params helper to build parameters sort_params = common_params(order_by: column_name, order_direction: sort_direction) link_to table_path(table_name, sort_params), class: "d-flex align-items-center text-decoration-none text-reset column-sort-link", title: "Sort by #{column_name} (#{sort_direction.downcase})", "aria-sort": aria_sort, role: "button", tabindex: "0" do content_tag(:span, column_name, class: "column-name") + content_tag(:span, sort_icon(column_name, current_order_by, current_direction), class: "sort-icon-container") end end |
#stat_card_bg_class ⇒ Object
Returns the appropriate background class for stat cards that adapts to dark mode
246 247 248 |
# File 'app/helpers/dbviewer/application_helper.rb', line 246 def stat_card_bg_class "stat-card-bg" end |
#tables_nav_class ⇒ Object
Helper for highlighting tables link
307 308 309 |
# File 'app/helpers/dbviewer/application_helper.rb', line 307 def tables_nav_class active_nav_class("tables") end |
#theme_toggle_icon ⇒ Object
Returns the theme toggle icon based on the current theme
236 237 238 |
# File 'app/helpers/dbviewer/application_helper.rb', line 236 def theme_toggle_icon '<i class="bi bi-moon"></i><i class="bi bi-sun"></i>'.html_safe end |
#theme_toggle_label ⇒ Object
Returns the aria label for the theme toggle button
241 242 243 |
# File 'app/helpers/dbviewer/application_helper.rb', line 241 def theme_toggle_label "Toggle dark mode" end |
#time_grouping_links(table_name, current_grouping) ⇒ Object
Render time grouping links
218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'app/helpers/dbviewer/application_helper.rb', line 218 def time_grouping_links(table_name, current_grouping) params = common_params content_tag(:div, class: "btn-group btn-group-sm", role: "group", 'aria-label': "Time grouping") do [ link_to("Hourly", table_path(table_name, params.merge(time_group: "hourly")), class: "btn btn-outline-primary #{current_grouping == 'hourly' ? 'active' : ''}"), link_to("Daily", table_path(table_name, params.merge(time_group: "daily")), class: "btn btn-outline-primary #{current_grouping == 'daily' ? 'active' : ''}"), link_to("Weekly", table_path(table_name, params.merge(time_group: "weekly")), class: "btn btn-outline-primary #{current_grouping == 'weekly' ? 'active' : ''}") ].join.html_safe end end |