dbviewer 0.7.10 → 0.7.11

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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/assets/images/dbviewer/emoji-favicon.txt +1 -0
  4. data/app/assets/images/dbviewer/favicon.ico +4 -0
  5. data/app/assets/images/dbviewer/favicon.png +4 -0
  6. data/app/assets/images/dbviewer/favicon.svg +10 -0
  7. data/app/assets/javascripts/dbviewer/entity_relationship_diagram.js +38 -42
  8. data/app/assets/javascripts/dbviewer/error_handler.js +58 -0
  9. data/app/assets/javascripts/dbviewer/home.js +25 -34
  10. data/app/assets/javascripts/dbviewer/layout.js +100 -129
  11. data/app/assets/javascripts/dbviewer/query.js +309 -246
  12. data/app/assets/javascripts/dbviewer/sidebar.js +170 -183
  13. data/app/assets/javascripts/dbviewer/utility.js +124 -0
  14. data/app/assets/stylesheets/dbviewer/application.css +8 -146
  15. data/app/assets/stylesheets/dbviewer/entity_relationship_diagram.css +0 -34
  16. data/app/assets/stylesheets/dbviewer/logs.css +0 -11
  17. data/app/assets/stylesheets/dbviewer/query.css +21 -9
  18. data/app/assets/stylesheets/dbviewer/table.css +49 -131
  19. data/app/controllers/concerns/dbviewer/database_operations/connection_management.rb +90 -0
  20. data/app/controllers/concerns/dbviewer/database_operations/data_export.rb +31 -0
  21. data/app/controllers/concerns/dbviewer/database_operations/database_information.rb +54 -0
  22. data/app/controllers/concerns/dbviewer/database_operations/datatable_operations.rb +37 -0
  23. data/app/controllers/concerns/dbviewer/database_operations/query_operations.rb +37 -0
  24. data/app/controllers/concerns/dbviewer/database_operations/relationship_management.rb +175 -0
  25. data/app/controllers/concerns/dbviewer/database_operations/table_operations.rb +46 -0
  26. data/app/controllers/concerns/dbviewer/database_operations.rb +4 -9
  27. data/app/controllers/dbviewer/api/tables_controller.rb +12 -0
  28. data/app/controllers/dbviewer/entity_relationship_diagrams_controller.rb +0 -15
  29. data/app/controllers/dbviewer/tables_controller.rb +4 -33
  30. data/app/views/dbviewer/entity_relationship_diagrams/index.html.erb +1 -1
  31. data/app/views/dbviewer/tables/query.html.erb +21 -6
  32. data/app/views/dbviewer/tables/show.html.erb +2 -2
  33. data/app/views/layouts/dbviewer/application.html.erb +12 -3
  34. data/config/routes.rb +2 -2
  35. data/lib/dbviewer/database/manager.rb +2 -2
  36. data/lib/dbviewer/datatable/query_operations.rb +1 -17
  37. data/lib/dbviewer/engine.rb +29 -0
  38. data/lib/dbviewer/version.rb +1 -1
  39. metadata +15 -10
  40. data/app/controllers/concerns/dbviewer/connection_management.rb +0 -88
  41. data/app/controllers/concerns/dbviewer/data_export.rb +0 -32
  42. data/app/controllers/concerns/dbviewer/database_information.rb +0 -62
  43. data/app/controllers/concerns/dbviewer/datatable_support.rb +0 -47
  44. data/app/controllers/concerns/dbviewer/pagination_concern.rb +0 -34
  45. data/app/controllers/concerns/dbviewer/query_operations.rb +0 -28
  46. data/app/controllers/concerns/dbviewer/relationship_management.rb +0 -173
  47. data/app/controllers/concerns/dbviewer/table_operations.rb +0 -56
@@ -0,0 +1,31 @@
1
+ require "csv"
2
+
3
+ module Dbviewer
4
+ module DatabaseOperations
5
+ module DataExport
6
+ extend ActiveSupport::Concern
7
+
8
+ # Export table data to CSV
9
+ def export_table_to_csv(table_name, query_params = nil, include_headers = true)
10
+ records = database_manager.table_query_operations.table_records(table_name, query_params)
11
+
12
+ CSV.generate do |csv|
13
+ csv << records.columns if include_headers
14
+
15
+ record_body = records.rows.map do |row|
16
+ row.map { |cell| format_csv_value(cell) }
17
+ end
18
+ csv.concat(record_body)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ # Format cell values for CSV export to handle nil values and special characters
25
+ def format_csv_value(value)
26
+ return "" if value.nil?
27
+ value.to_s
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ module Dbviewer
2
+ module DatabaseOperations
3
+ module DatabaseInformation
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ helper_method :get_database_name, :get_adapter_name if respond_to?(:helper_method)
8
+ end
9
+
10
+ # Get the name of the current database
11
+ def get_database_name
12
+ current_conn_config = Dbviewer.configuration.database_connections[current_connection_key]
13
+ return current_conn_config[:name] if current_conn_config && current_conn_config[:name].present?
14
+
15
+ adapter = database_manager.connection.adapter_name.downcase
16
+ fetch_database_name(adapter)
17
+ end
18
+
19
+ # Get the name of the current database adapter
20
+ def get_adapter_name
21
+ adapter_name = database_manager.connection.adapter_name.downcase
22
+ adapter_mappings = {
23
+ /mysql/i => "MySQL",
24
+ /postgres/i => "PostgreSQL",
25
+ /sqlite/i => "SQLite",
26
+ /oracle/i => "Oracle",
27
+ /sqlserver|mssql/i => "SQL Server"
28
+ }
29
+ adapter_mappings.find { |pattern, _| adapter_name =~ pattern }&.last || adapter_name.titleize
30
+ rescue
31
+ "Unknown"
32
+ end
33
+
34
+ def fetch_database_name(adapter)
35
+ case adapter
36
+ when /mysql/
37
+ query = "SELECT DATABASE() as db_name"
38
+ result = database_manager.execute_query(query).first
39
+ result ? result["db_name"] : "Database"
40
+ when /postgres/
41
+ query = "SELECT current_database() as db_name"
42
+ result = database_manager.execute_query(query).first
43
+ result ? result["db_name"] : "Database"
44
+ when /sqlite/
45
+ # For SQLite, extract the database name from the connection_config
46
+ database_path = database_manager.connection.pool.spec.config[:database] || ""
47
+ File.basename(database_path, ".*") || "SQLite Database"
48
+ else
49
+ "Database" # Default fallback
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ module Dbviewer
2
+ module DatabaseOperations
3
+ module DatatableOperations
4
+ extend ActiveSupport::Concern
5
+
6
+ # Consolidated method to fetch all datatable-related data in one call
7
+ # Returns a hash containing all necessary datatable information
8
+ def fetch_datatable_data(table_name, query_params)
9
+ columns = fetch_table_columns(table_name)
10
+
11
+ total_count = fetch_table_record_count(table_name, query_params.column_filters)
12
+ records = fetch_table_records(table_name, query_params)
13
+ metadata = fetch_table_metadata(table_name)
14
+
15
+ {
16
+ columns: columns,
17
+ records: records,
18
+ total_count: total_count,
19
+ total_pages: total_count > 0 ? (total_count.to_f / query_params.per_page).ceil : 0,
20
+ metadata: metadata,
21
+ current_page: query_params.page,
22
+ per_page: query_params.per_page,
23
+ order_by: query_params.order_by,
24
+ direction: query_params.direction
25
+ }
26
+ end
27
+
28
+ def fetch_table_stats(table_name)
29
+ {
30
+ table_name: table_name,
31
+ columns: fetch_table_columns(table_name),
32
+ metadata: fetch_table_metadata(table_name)
33
+ }
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,37 @@
1
+ module Dbviewer
2
+ module DatabaseOperations
3
+ module QueryOperations
4
+ extend ActiveSupport::Concern
5
+
6
+ # Prepare the SQL query - either from params or default
7
+ def prepare_query(table_name, query)
8
+ query = query.present? ? query.to_s : default_query(table_name)
9
+
10
+ # Validate query for security
11
+ unless ::Dbviewer::Validator::Sql.safe_query?(query)
12
+ query = default_query(table_name)
13
+ flash.now[:warning] = "Only SELECT queries are allowed. Your query contained potentially unsafe operations. Using default query instead."
14
+ end
15
+
16
+ query
17
+ end
18
+
19
+ # Execute the prepared SQL query
20
+ def execute_query(query)
21
+ database_manager.execute_query(@query)
22
+ end
23
+
24
+ def default_query(table_name)
25
+ quoted_table = safe_quote_table_name(table_name)
26
+ "SELECT * FROM #{quoted_table} LIMIT 100"
27
+ end
28
+
29
+ private
30
+
31
+ # Safely quote a table name, with fallback
32
+ def safe_quote_table_name(table_name)
33
+ database_manager.connection.quote_table_name(table_name) rescue table_name.to_s
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,175 @@
1
+ module Dbviewer
2
+ module DatabaseOperations
3
+ module RelationshipManagement
4
+ extend ActiveSupport::Concern
5
+
6
+ # Fetch relationships between tables for ERD visualization
7
+ def fetch_table_relationships(tables)
8
+ tables.flat_map { |table| extract_table_relationships_from_metadata(table[:name]) }
9
+ end
10
+
11
+ # Get mini ERD data for a specific table and its relationships
12
+ def fetch_mini_erd_for_table(table_name)
13
+ outgoing_data = collect_outgoing_relationships(table_name)
14
+ incoming_data = collect_incoming_relationships(table_name)
15
+
16
+ initial_tables = [ { name: table_name } ]
17
+ all_relationships = outgoing_data[:relationships] + incoming_data[:relationships]
18
+ all_tables = (initial_tables + outgoing_data[:tables] + incoming_data[:tables]).uniq { |t| t[:name] }
19
+
20
+ {
21
+ tables: all_tables,
22
+ relationships: all_relationships,
23
+ timestamp: Time.now.to_i
24
+ }
25
+ end
26
+
27
+ private
28
+
29
+ # Extract relationships for a single table from its metadata
30
+ # @param table_name [String] The name of the table to process
31
+ # @return [Array<Hash>] Array of relationship hashes for this table
32
+ def extract_table_relationships_from_metadata(table_name)
33
+ metadata = database_manager.table_metadata(table_name)
34
+ return [] unless metadata&.dig(:foreign_keys)&.present?
35
+
36
+ metadata[:foreign_keys].map do |fk|
37
+ {
38
+ from_table: table_name,
39
+ to_table: fk[:to_table],
40
+ from_column: fk[:column],
41
+ to_column: fk[:primary_key],
42
+ name: fk[:name]
43
+ }
44
+ end
45
+ end
46
+
47
+ # Collect outgoing relationships from the specified table to other tables
48
+ # @param table_name [String] The source table name
49
+ # @return [Hash] Hash containing :tables and :relationships arrays
50
+ def collect_outgoing_relationships(table_name)
51
+ tables = []
52
+ relationships = []
53
+
54
+ metadata = fetch_table_metadata(table_name)
55
+ return { tables: tables, relationships: relationships } unless metadata&.dig(:foreign_keys)&.present?
56
+
57
+ metadata[:foreign_keys].each do |fk|
58
+ result = process_outgoing_foreign_key(table_name, fk)
59
+ if result
60
+ relationships << result[:relationship]
61
+ tables << result[:table]
62
+ end
63
+ end
64
+
65
+ {
66
+ tables: tables,
67
+ relationships: relationships
68
+ }
69
+ end
70
+
71
+ # Process a single outgoing foreign key relationship
72
+ # @param table_name [String] The source table name
73
+ # @param fk [Hash] Foreign key metadata
74
+ # @return [Hash, nil] Hash containing :relationship and :table, or nil if invalid
75
+ def process_outgoing_foreign_key(table_name, fk)
76
+ return nil unless fk[:to_table].present? && fk[:column].present?
77
+
78
+ relationship = build_relationship_hash(
79
+ from_table: table_name.to_s,
80
+ to_table: fk[:to_table].to_s,
81
+ from_column: fk[:column].to_s,
82
+ to_column: fk[:primary_key].to_s.presence || "id",
83
+ name: fk[:name].to_s.presence || "#{table_name}_to_#{fk[:to_table]}",
84
+ direction: "outgoing"
85
+ )
86
+
87
+ {
88
+ relationship: relationship,
89
+ table: {
90
+ name: fk[:to_table].to_s
91
+ }
92
+ }
93
+ end
94
+
95
+ # Collect incoming relationships from other tables to the specified table
96
+ # @param table_name [String] The target table name
97
+ # @return [Hash] Hash containing :tables and :relationships arrays
98
+ def collect_incoming_relationships(table_name)
99
+ results = database_manager.tables
100
+ .reject { |other_table_name| other_table_name == table_name }
101
+ .map { |other_table_name| process_table_for_incoming_relationships(table_name, other_table_name) }
102
+ .compact
103
+
104
+ {
105
+ tables: results.flat_map { |result| result[:tables] },
106
+ relationships: results.flat_map { |result| result[:relationships] }
107
+ }
108
+ end
109
+
110
+ # Process a single table to find incoming relationships to the target table
111
+ # @param target_table [String] The target table name
112
+ # @param source_table [String] The source table name to check
113
+ # @return [Hash, nil] Hash containing :tables and :relationships arrays, or nil if no relationships
114
+ def process_table_for_incoming_relationships(target_table, source_table)
115
+ other_metadata = fetch_table_metadata(source_table)
116
+ return nil unless other_metadata&.dig(:foreign_keys)&.present?
117
+
118
+ results = other_metadata[:foreign_keys]
119
+ .map { |fk| process_incoming_foreign_key(target_table, source_table, fk) }
120
+ .compact
121
+
122
+ return nil if results.empty?
123
+
124
+ {
125
+ tables: results.map { |result| result[:table] },
126
+ relationships: results.map { |result| result[:relationship] }
127
+ }
128
+ end
129
+
130
+ # Process a single incoming foreign key relationship
131
+ # @param target_table [String] The target table name
132
+ # @param source_table [String] The source table name
133
+ # @param fk [Hash] Foreign key metadata
134
+ # @return [Hash, nil] Hash containing :relationship and :table, or nil if invalid
135
+ def process_incoming_foreign_key(target_table, source_table, fk)
136
+ return nil unless fk[:to_table] == target_table && fk[:column].present?
137
+
138
+ relationship = build_relationship_hash(
139
+ from_table: source_table.to_s,
140
+ to_table: target_table.to_s,
141
+ from_column: fk[:column].to_s,
142
+ to_column: fk[:primary_key].to_s.presence || "id",
143
+ name: fk[:name].to_s.presence || "#{source_table}_to_#{target_table}",
144
+ direction: "incoming"
145
+ )
146
+
147
+ {
148
+ relationship: relationship,
149
+ table: {
150
+ name: source_table.to_s
151
+ }
152
+ }
153
+ end
154
+
155
+ # Build a standardized relationship hash
156
+ # @param from_table [String] Source table name
157
+ # @param to_table [String] Target table name
158
+ # @param from_column [String] Source column name
159
+ # @param to_column [String] Target column name
160
+ # @param name [String] Relationship name
161
+ # @param direction [String] Relationship direction
162
+ # @return [Hash] Standardized relationship hash
163
+ def build_relationship_hash(from_table:, to_table:, from_column:, to_column:, name:, direction:)
164
+ {
165
+ from_table: from_table,
166
+ to_table: to_table,
167
+ from_column: from_column,
168
+ to_column: to_column,
169
+ name: name,
170
+ direction: direction
171
+ }
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,46 @@
1
+ module Dbviewer
2
+ module DatabaseOperations
3
+ module TableOperations
4
+ extend ActiveSupport::Concern
5
+
6
+ # Fetch all tables with their stats
7
+ # By default, don't include record counts for better performance on sidebar
8
+ def fetch_tables(include_record_counts = false)
9
+ database_manager.tables.map do |table_name|
10
+ table_stats = { name: table_name }
11
+ table_stats[:record_count] = fetch_table_record_count(table_name) if include_record_counts
12
+ table_stats
13
+ end
14
+ end
15
+
16
+ # Get column information for a specific table
17
+ def fetch_table_columns(table_name)
18
+ database_manager.table_columns(table_name)
19
+ end
20
+
21
+ # Fetch records for a table with pagination and sorting
22
+ def fetch_table_records(table_name, query_params)
23
+ database_manager.table_records(table_name, query_params)
24
+ end
25
+
26
+ # Get filtered record count for a table
27
+ def fetch_table_record_count(table_name, column_filters = {})
28
+ database_manager.table_record_count(table_name, column_filters)
29
+ end
30
+
31
+ # Get table metadata for display (e.g., primary key, foreign keys, indexes)
32
+ def fetch_table_metadata(table_name)
33
+ database_manager.table_metadata(table_name)
34
+ end
35
+
36
+ private
37
+
38
+ # Check if a table has a created_at column for timestamp visualization
39
+ def has_timestamp_column?(table_name)
40
+ fetch_table_columns(table_name).any? do |column|
41
+ column[:name] == "created_at" && [ :datetime, :timestamp ].include?(column[:type])
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -3,22 +3,17 @@ module Dbviewer
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  include ConnectionManagement
6
+ include DataExport
6
7
  include DatabaseInformation
7
- include TableOperations
8
- include RelationshipManagement
8
+ include DatatableOperations
9
9
  include QueryOperations
10
- include DataExport
11
- include DatatableSupport
12
-
13
- # -- Database Managers --
10
+ include RelationshipManagement
11
+ include TableOperations
14
12
 
15
- # Initialize the database manager with the current connection
16
13
  def database_manager
17
14
  @database_manager = ::Dbviewer::Database::Manager.new(current_connection_key)
18
15
  end
19
16
 
20
- # Initialize the table query operations manager
21
- # This gives direct access to table query operations when needed
22
17
  def table_query_operations
23
18
  @table_query_operations ||= database_manager.table_query_operations
24
19
  end
@@ -6,6 +6,11 @@ module Dbviewer
6
6
  render_success(total_tables: tables_count)
7
7
  end
8
8
 
9
+ def show
10
+ table_stats = fetch_table_stats(params[:id])
11
+ render_success(**table_stats)
12
+ end
13
+
9
14
  def records
10
15
  tables_stats = fetch_tables_stats
11
16
  render_success(tables_stats)
@@ -36,6 +41,13 @@ module Dbviewer
36
41
  })
37
42
  end
38
43
 
44
+ def mini_erd
45
+ table_name = params[:id]
46
+ erd_data = fetch_mini_erd_for_table(table_name)
47
+
48
+ render_success(erd_data)
49
+ end
50
+
39
51
  private
40
52
 
41
53
  def fetch_tables_count
@@ -1,21 +1,6 @@
1
1
  module Dbviewer
2
2
  class EntityRelationshipDiagramsController < ApplicationController
3
3
  def index
4
- # Only show warning if no tables exist, but don't fetch relationships on initial load
5
- if @tables.blank?
6
- flash.now[:warning] = "No tables found in database to generate ERD."
7
- end
8
-
9
- respond_to do |format|
10
- format.html # Just render the HTML without relationships
11
- format.json do
12
- # For JSON requests, return just tables initially
13
- render json: {
14
- tables: @tables,
15
- relationships: []
16
- }
17
- end
18
- end
19
4
  end
20
5
  end
21
6
  end
@@ -1,13 +1,11 @@
1
1
  module Dbviewer
2
2
  class TablesController < ApplicationController
3
- include Dbviewer::PaginationConcern
4
-
5
3
  before_action :set_table_name, except: [ :index ]
6
4
  before_action :set_query_filters, only: [ :show, :export_csv ]
7
5
  before_action :set_global_filters, only: [ :show, :export_csv ]
8
6
 
9
7
  def index
10
- @tables = fetch_tables(include_record_counts: true)
8
+ @tables = fetch_tables(:include_record_counts)
11
9
  end
12
10
 
13
11
  def show
@@ -18,44 +16,17 @@ module Dbviewer
18
16
  direction: @order_direction,
19
17
  column_filters: @column_filters.reject { |_, v| v.blank? }
20
18
  )
21
-
22
- # Get all datatable data in one method call
23
19
  datatable_data = fetch_datatable_data(@table_name, query_params)
24
20
 
25
- # Assign to instance variables for view access
26
21
  @total_count = datatable_data[:total_count]
27
22
  @records = datatable_data[:records]
28
23
  @total_pages = datatable_data[:total_pages]
29
24
  @columns = datatable_data[:columns]
30
25
  @metadata = datatable_data[:metadata]
31
-
32
- respond_to do |format|
33
- format.html # Default HTML response
34
- format.json do
35
- render json: {
36
- table_name: @table_name,
37
- columns: @columns,
38
- metadata: @metadata,
39
- record_count: @total_count
40
- }
41
- end
42
- end
43
- end
44
-
45
- def mini_erd
46
- @erd_data = fetch_mini_erd_for_table(@table_name)
47
-
48
- respond_to do |format|
49
- format.json { render json: @erd_data }
50
- format.html { render layout: false }
51
- end
52
26
  end
53
27
 
54
28
  def query
55
- @read_only_mode = true # Flag to indicate we're in read-only mode
56
29
  @columns = fetch_table_columns(@table_name)
57
- @tables = fetch_tables # Fetch tables for sidebar
58
-
59
30
  @query = prepare_query(@table_name, params[:query])
60
31
  @records = execute_query(@query)
61
32
 
@@ -95,11 +66,11 @@ module Dbviewer
95
66
 
96
67
  def set_query_filters
97
68
  @current_page = [ 1, params[:page].to_i ].max
98
- @per_page = params[:per_page] ? params[:per_page].to_i : self.class.default_per_page
99
- @per_page = self.class.default_per_page unless self.class.per_page_options.include?(@per_page)
69
+ @per_page = params[:per_page] ? params[:per_page].to_i : Dbviewer.configuration.default_per_page
70
+ @per_page = Dbviewer.configuration.default_per_page unless Dbviewer.configuration.per_page_options.include?(@per_page)
100
71
  @order_by = params[:order_by].presence || determine_default_order_column
101
72
  @order_direction = params[:order_direction].upcase if params[:order_direction].present?
102
- @order_direction = "DESC" unless self.class::VALID_SORT_DIRECTIONS.include?(@order_direction)
73
+ @order_direction = "DESC" unless %w[ASC DESC].include?(@order_direction)
103
74
  @column_filters = params[:column_filters].presence ? params[:column_filters].to_enum.to_h : {}
104
75
  end
105
76
 
@@ -89,5 +89,5 @@
89
89
  </div>
90
90
 
91
91
  <input type="text" id="tables" class="d-none" value='<%= raw @tables.to_json %>'>
92
- <input type="text" id="tables_path" class="d-none" value='<%= dbviewer.tables_path %>'>
92
+ <input type="text" id="tables_path" class="d-none" value='<%= dbviewer.api_tables_path %>'>
93
93
  <input type="text" id="relationships_api_path" class="d-none" value='<%= dbviewer.relationships_api_entity_relationship_diagrams_path %>'>
@@ -5,7 +5,18 @@
5
5
  <% content_for :head do %>
6
6
  <link href="https://cdn.jsdelivr.net/npm/vscode-codicons@0.0.17/dist/codicon.min.css" rel="stylesheet">
7
7
  <%= stylesheet_link_tag "dbviewer/query", "data-turbo-track": "reload" %>
8
- <%= javascript_include_tag "dbviewer/query", "data-turbo-track": "reload", type: :module %>
8
+ <!-- Load Monaco editor first as a regular script -->
9
+ <script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/min/vs/loader.js"></script>
10
+ <script>
11
+ // Set up Monaco loader
12
+ require.config({ paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/min/vs' } });
13
+ window.MonacoEnvironment = { getWorkerUrl: () => proxy };
14
+ let proxy = URL.createObjectURL(new Blob([`
15
+ self.MonacoEnvironment = { baseUrl: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/min/' };
16
+ importScripts('https://cdn.jsdelivr.net/npm/monaco-editor@0.39.0/min/vs/base/worker/workerMain.js');
17
+ `], { type: 'text/javascript' }));
18
+ </script>
19
+ <%= javascript_include_tag "dbviewer/query", "data-turbo-track": "reload" %>
9
20
  <% end %>
10
21
 
11
22
  <div class="d-flex justify-content-between align-items-center mb-4">
@@ -26,6 +37,7 @@
26
37
  <div class="mb-3">
27
38
  <div id="monaco-editor" class="monaco-editor-container" style="min-height: 200px; border-radius: 4px; margin-bottom: 0rem;"
28
39
  data-initial-query="<%= CGI.escapeHTML(@query.to_s) %>"></div>
40
+ <div id="query-container" class="editor-error-container"></div>
29
41
  <%= form.hidden_field :query, id: "query-input", value: @query.to_s %>
30
42
  </div>
31
43
 
@@ -100,11 +112,14 @@
100
112
  </div>
101
113
  </div>
102
114
 
103
- <% if @error.present? %>
104
- <div class="alert alert-danger" role="alert">
105
- <strong>Error:</strong> <%= @error %>
106
- </div>
107
- <% end %>
115
+ <div id="query-results">
116
+ <% if @error.present? %>
117
+ <div class="alert alert-danger" role="alert">
118
+ <i class="bi bi-exclamation-triangle me-2"></i>
119
+ <strong>Error:</strong> <%= @error %>
120
+ </div>
121
+ <% end %>
122
+ </div>
108
123
 
109
124
  <% if @records.present? %>
110
125
  <div class="card">
@@ -100,7 +100,7 @@
100
100
  <div class="card-header d-flex justify-content-between align-items-center">
101
101
  <h5 class="mb-0">
102
102
  <select id="per-page-select" class="form-select form-select-sm" onchange="window.location.href='<%= table_path(@table_name) %>?<%= per_page_url_params(@table_name) %>'">
103
- <% Dbviewer::TablesController.per_page_options.each do |option| %>
103
+ <% Dbviewer.configuration.per_page_options.each do |option| %>
104
104
  <option value="<%= option %>" <%= 'selected' if @per_page == option %>><%= option %></option>
105
105
  <% end %>
106
106
  </select>
@@ -348,5 +348,5 @@
348
348
  </div>
349
349
  </div>
350
350
  <% end %>
351
- <input type="hidden" id="mini_erd_table_path" name="mini_erd_table_path" value="<%= dbviewer.mini_erd_table_path(@table_name, format: :json) %>">
351
+ <input type="hidden" id="mini_erd_table_path" name="mini_erd_table_path" value="<%= dbviewer.mini_erd_api_table_path(@table_name, format: :json) %>">
352
352
  <input type="hidden" id="table_name" name="table_name" value="<%= @table_name %>">
@@ -5,6 +5,13 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
6
6
  <%= csrf_meta_tags %>
7
7
  <%= csp_meta_tag %>
8
+
9
+ <!-- Favicon -->
10
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>👁️</text></svg>">
11
+ <link rel="icon" href="<%= asset_path('dbviewer/favicon.ico') %>" type="image/x-icon">
12
+ <link rel="shortcut icon" href="<%= asset_path('dbviewer/favicon.ico') %>" type="image/x-icon">
13
+ <link rel="icon" type="image/svg+xml" href="<%= asset_path('dbviewer/favicon.svg') %>">
14
+ <link rel="icon" type="image/png" href="<%= asset_path('dbviewer/favicon.png') %>">
8
15
 
9
16
  <!-- Prevent theme flash during page load -->
10
17
  <script>
@@ -37,6 +44,8 @@
37
44
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
38
45
  <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
39
46
 
47
+ <%= javascript_include_tag "dbviewer/utility", "data-turbo-track": "reload" %>
48
+ <%= javascript_include_tag "dbviewer/error_handler", "data-turbo-track": "reload" %>
40
49
  <%= javascript_include_tag "dbviewer/layout", "data-turbo-track": "reload" %>
41
50
  <%= javascript_include_tag "dbviewer/sidebar", "data-turbo-track": "reload" %>
42
51
  <%= stylesheet_link_tag "dbviewer/application", "data-turbo-track": "reload" %>
@@ -47,8 +56,8 @@
47
56
  <div class="dbviewer-wrapper">
48
57
  <!-- Top Navigation Bar (Fixed) -->
49
58
  <nav class="navbar navbar-expand-lg navbar-dark bg-primary dbviewer-navbar fixed-top">
50
- <div class="container-fluid">
51
- <a class="navbar-brand" href="<%= dbviewer.root_path %>"><i class="bi bi-database-fill me-1"></i>DB Viewer</a>
59
+ <div class="container-fluid px-0">
60
+ <a class="navbar-brand d-flex" href="<%= dbviewer.root_path %>"><span class="me-2">👁️</span> DB Viewer</a>
52
61
  <div class="d-flex align-items-center">
53
62
  <button class="navbar-toggler border-0 px-2 d-lg-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#navbarOffcanvas"
54
63
  aria-controls="navbarOffcanvas" aria-expanded="false" aria-label="Toggle navigation">
@@ -110,7 +119,7 @@
110
119
  <!-- Offcanvas sidebar for mobile/tablet view that slides from right -->
111
120
  <div class="offcanvas offcanvas-end d-lg-none" tabindex="-1" id="navbarOffcanvas" aria-labelledby="offcanvasNavbarLabel">
112
121
  <div class="offcanvas-header bg-light-subtle">
113
- <h5 class="offcanvas-title" id="offcanvasNavbarLabel"><i class="bi bi-database-fill me-2 text-primary"></i>DB Viewer</h5>
122
+ <h5 class="offcanvas-title" id="offcanvasNavbarLabel">👁️ DB Viewer</h5>
114
123
  <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
115
124
  </div>
116
125
  <div class="offcanvas-body bg-body-tertiary">