solid_litequeen 0.11.1 → 0.13.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/app/assets/images/solid_litequeen/icons/circle-elipsis.svg +1 -0
- data/app/assets/images/solid_litequeen/icons/spline.svg +1 -0
- data/app/controllers/solid_litequeen/databases_controller.rb +40 -1
- data/app/javascript/solid_litequeen/controllers/dialog_controller.js +37 -0
- data/app/javascript/solid_litequeen/controllers/table_controller.js +32 -0
- data/app/views/layouts/solid_litequeen/application.html.erb +6 -0
- data/app/views/solid_litequeen/databases/_foreign-key-data.html.erb +38 -0
- data/app/views/solid_litequeen/databases/_table-data-context-dialog.html.erb +15 -0
- data/app/views/solid_litequeen/databases/_table-relationships-dialog.html.erb +1 -1
- data/app/views/solid_litequeen/databases/table_rows.html.erb +57 -6
- data/config/routes.rb +1 -0
- data/lib/solid_litequeen/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 133668d3a4340adb9111ea7e7e9aeea464173c2e87839e4dad82a2b5751e4058
|
4
|
+
data.tar.gz: 2d84c8ffef4315afcc3ac17bfa06f086efc8923c358d5ebf358daeb3438b057a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ae9e8e0f6b912011d11d857318d005dfe6f70850997ece81a48c529d277f9ac7c3832b7e1144f39fb51c964d0788a00fa8e9e7adf1e54c0b1912dc525b2e12a6
|
7
|
+
data.tar.gz: 73d39ca407117d57fea0ab2c753342fada254c6eda13816ef5d3806b84ee60f861f4d2f13257d8e885d2c3f8b7f824dfe03c3ca5cd0d62cfff4fa9a0e44e32db
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-ellipsis"><circle cx="12" cy="12" r="10"></circle><path d="M17 12h.01"></path><path d="M12 12h.01"></path><path d="M7 12h.01"></path></svg>
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-spline"><circle cx="19" cy="5" r="2"></circle><circle cx="5" cy="19" r="2"></circle><path d="M5 17A12 12 0 0 1 17 5"></path></svg>
|
@@ -93,8 +93,17 @@ module SolidLitequeen
|
|
93
93
|
|
94
94
|
table_columns = DynamicDatabase.connection.columns(@table_name)
|
95
95
|
|
96
|
+
foreign_keys = DynamicDatabase.connection.foreign_keys(@table_name)
|
97
|
+
|
98
|
+
# Build a mapping from column name to its foreign key details
|
99
|
+
fk_info = {}
|
100
|
+
foreign_keys.each do |fk|
|
101
|
+
# Depending on your Rails version, you might access these properties as below:
|
102
|
+
fk_info[fk.column] = { to_table: fk.to_table, primary_key: fk.primary_key }
|
103
|
+
end
|
104
|
+
|
96
105
|
@columns_info = table_columns.each_with_object({}) do |column, hash|
|
97
|
-
|
106
|
+
info = {
|
98
107
|
sql_type: column.sql_type_metadata.sql_type,
|
99
108
|
type: column.sql_type_metadata.type,
|
100
109
|
limit: column.sql_type_metadata.limit,
|
@@ -103,6 +112,12 @@ module SolidLitequeen
|
|
103
112
|
null: column.null,
|
104
113
|
default: column.default
|
105
114
|
}
|
115
|
+
|
116
|
+
# Append foreign key info if available for this column
|
117
|
+
if fk_info[column.name]
|
118
|
+
info[:foreign_key] = fk_info[column.name]
|
119
|
+
end
|
120
|
+
hash[column.name] = info
|
106
121
|
end
|
107
122
|
|
108
123
|
# Verify the sort column exists in the table to prevent SQL injection
|
@@ -170,5 +185,29 @@ module SolidLitequeen
|
|
170
185
|
|
171
186
|
head :ok
|
172
187
|
end
|
188
|
+
|
189
|
+
def get_foreign_key_data
|
190
|
+
@database_id = params.expect(:database_id)
|
191
|
+
@table_name = params.expect(:table)
|
192
|
+
@target_table = params.expect(:target_table)
|
193
|
+
@target_field = params.expect(:target_field)
|
194
|
+
@target_field_value = params.expect(:target_field_value)
|
195
|
+
|
196
|
+
@database_location = Base64.urlsafe_decode64(@database_id)
|
197
|
+
|
198
|
+
DynamicDatabase.establish_connection(
|
199
|
+
adapter: "sqlite3",
|
200
|
+
database: @database_location
|
201
|
+
)
|
202
|
+
|
203
|
+
|
204
|
+
# Query the target table for the record matching the foreign key value
|
205
|
+
query = "SELECT * FROM #{@target_table} WHERE #{@target_field} = ? LIMIT 1"
|
206
|
+
@result = DynamicDatabase.connection.exec_query(query, "SQL", [@target_field_value])
|
207
|
+
|
208
|
+
|
209
|
+
render partial: "foreign-key-data"
|
210
|
+
|
211
|
+
end
|
173
212
|
end
|
174
213
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
// Connects to data-controller="dialog"
|
4
|
+
export default class extends Controller {
|
5
|
+
connect() {
|
6
|
+
// this.element === dialog
|
7
|
+
this.element.addEventListener(
|
8
|
+
"click",
|
9
|
+
this.handleClickOusideDialog.bind(this),
|
10
|
+
);
|
11
|
+
}
|
12
|
+
|
13
|
+
disconnect() {
|
14
|
+
this.element.removeEventListener(
|
15
|
+
"click",
|
16
|
+
this.handleClickOusideDialog.bind(this),
|
17
|
+
);
|
18
|
+
|
19
|
+
this.element?.close();
|
20
|
+
}
|
21
|
+
|
22
|
+
/**
|
23
|
+
* Handles mouse events for the search dialog.
|
24
|
+
* @param {MouseEvent} event - The mouse event triggered by user interaction.
|
25
|
+
*/
|
26
|
+
handleClickOusideDialog(event) {
|
27
|
+
const rect = this.element.getBoundingClientRect();
|
28
|
+
const isInDialog =
|
29
|
+
rect.top <= event.clientY &&
|
30
|
+
event.clientY <= rect.top + rect.height &&
|
31
|
+
rect.left <= event.clientX &&
|
32
|
+
event.clientX <= rect.left + rect.width;
|
33
|
+
if (!isInDialog) {
|
34
|
+
this.element?.close();
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
@@ -161,6 +161,38 @@ export default class extends Controller {
|
|
161
161
|
targetTh.removeAttribute("data-column-order-about-to-be-swapped");
|
162
162
|
}
|
163
163
|
|
164
|
+
load_foreign_key_data(e) {
|
165
|
+
const foreign_key_data_dialog = document.querySelector(
|
166
|
+
"dialog#foreign-key-data",
|
167
|
+
);
|
168
|
+
|
169
|
+
const foreign_key_data_frame = document.querySelector(
|
170
|
+
"#foreign-key-data-frame",
|
171
|
+
);
|
172
|
+
|
173
|
+
const fk_data_dialog_button = e.currentTarget;
|
174
|
+
const fk_target_table = fk_data_dialog_button.dataset.fk_target_table;
|
175
|
+
const fk_target_field = fk_data_dialog_button.dataset.fk_target_field;
|
176
|
+
const fk_target_field_value =
|
177
|
+
fk_data_dialog_button.dataset.fk_target_field_value;
|
178
|
+
|
179
|
+
const new_frame_src = `${this.element.dataset.database_table_path}/foreign-key-data/${fk_target_table}/${fk_target_field}/${fk_target_field_value}`;
|
180
|
+
|
181
|
+
// if the src didn't change let's just show what we have
|
182
|
+
if (foreign_key_data_frame.src) {
|
183
|
+
const frame_src_current_path = new URL(foreign_key_data_frame.src)
|
184
|
+
?.pathname;
|
185
|
+
|
186
|
+
if (new_frame_src === frame_src_current_path) {
|
187
|
+
foreign_key_data_dialog.showModal();
|
188
|
+
return;
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
foreign_key_data_frame.src = new_frame_src;
|
193
|
+
foreign_key_data_dialog.showModal();
|
194
|
+
}
|
195
|
+
|
164
196
|
#load_datetime_local_title() {
|
165
197
|
setTimeout(() => {
|
166
198
|
const datetime_items = this.element.querySelectorAll(
|
@@ -0,0 +1,38 @@
|
|
1
|
+
<turbo-frame id="foreign-key-data-frame">
|
2
|
+
|
3
|
+
|
4
|
+
<h1 class="text-lg text-center">
|
5
|
+
<span> <%= @table_name %></span>
|
6
|
+
<span> ></span>
|
7
|
+
<span class="font-semibold"><%= @target_table %></span>
|
8
|
+
|
9
|
+
</h1>
|
10
|
+
|
11
|
+
<div class="mx-auto my-4 p-4 max-w-[90%]">
|
12
|
+
<table class="min-w-full divide-y divide-gray-200 border border-gray-200">
|
13
|
+
<thead class="bg-gray-50">
|
14
|
+
<tr>
|
15
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
16
|
+
Column Name
|
17
|
+
</th>
|
18
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
19
|
+
Value
|
20
|
+
</th>
|
21
|
+
</tr>
|
22
|
+
</thead>
|
23
|
+
<tbody class="bg-white divide-y divide-gray-200">
|
24
|
+
<% @result.rows.each do |row| %>
|
25
|
+
<% @result.columns.zip(row).each do |column, value| %>
|
26
|
+
<tr>
|
27
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800 font-semibold"><%= column %></td>
|
28
|
+
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-800"><%= value&.truncate(80) %></td>
|
29
|
+
</tr>
|
30
|
+
<% end %>
|
31
|
+
<% end %>
|
32
|
+
|
33
|
+
</tbody>
|
34
|
+
</table>
|
35
|
+
</div>
|
36
|
+
|
37
|
+
|
38
|
+
</turbo-frame>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<dialog id="<%= dialog_id %>" data-controller="dialog" class="w-[800px] m-auto overscroll-y-contain">
|
2
|
+
<form method="submit" class="flex flex-row-reverse">
|
3
|
+
<button formmethod="dialog" class="cursor-pointer mr-4 mt-2 outline-none">
|
4
|
+
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5" %>
|
5
|
+
</button>
|
6
|
+
</form>
|
7
|
+
|
8
|
+
<h1 class="text-lg font-semibold text-center"><%= column_name %></h1>
|
9
|
+
|
10
|
+
<div class="flex items-center max-w-[90%] h-80 max-h-80 mx-auto my-4 p-2 bg-gray-100 rounded ">
|
11
|
+
<p class="text-wrap overflow-auto w-[inherit] h-[inherit] p-2">
|
12
|
+
<%= data %>
|
13
|
+
</p>
|
14
|
+
</div>
|
15
|
+
</dialog>
|
@@ -1,4 +1,4 @@
|
|
1
|
-
<dialog id="table_relationships" data-controller="table-relations" data-relations="<%= @table_relations.to_json %>" class="w-[1000px] h-full m-auto overscroll-y-contain">
|
1
|
+
<dialog id="table_relationships" data-controller="dialog table-relations" data-relations="<%= @table_relations.to_json %>" class="w-[1000px] h-full m-auto overscroll-y-contain">
|
2
2
|
<form method="submit" class="flex flex-row-reverse">
|
3
3
|
<button formmethod="dialog" class="cursor-pointer mr-4 mt-2 outline-none">
|
4
4
|
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5" %>
|
@@ -18,7 +18,12 @@
|
|
18
18
|
|
19
19
|
<div class="bg-white rounded-lg shadow overflow-x-auto">
|
20
20
|
<div class="min-w-full inline-block align-middle">
|
21
|
-
<table
|
21
|
+
<table
|
22
|
+
data-controller="table"
|
23
|
+
data-database_table_path="<%= database_table_rows_path %>"
|
24
|
+
data-set-table-order-path="<%= database_set_table_column_order_path(params[:database_id], @table_name) %>"
|
25
|
+
class="min-w-full relative"
|
26
|
+
>
|
22
27
|
<thead class="">
|
23
28
|
<tr class="bg-gray-100 border-b border-gray-200">
|
24
29
|
<% @data.columns.each_with_index do |column, index| %>
|
@@ -43,35 +48,81 @@
|
|
43
48
|
<% end %>
|
44
49
|
</tr>
|
45
50
|
</thead>
|
46
|
-
|
51
|
+
|
47
52
|
<tbody class="divide-y divide-gray-200">
|
48
53
|
<% @data.rows.each do |row| %>
|
49
54
|
<tr class="hover:bg-gray-50" >
|
50
55
|
<% row.each_with_index do |item, index| %>
|
51
56
|
<% truncated_item = item&.truncate(80) %>
|
52
57
|
<% column_name = @data.columns[index] %>
|
58
|
+
<% foreign_key = @columns_info[column_name]&.dig(:foreign_key) %>
|
53
59
|
<td
|
54
60
|
data-column="<%= column_name %>"
|
55
61
|
data-data_type="<%= @columns_info.dig(column_name).dig(:type) %>"
|
56
62
|
class="px-6 py-4 text-sm text-gray-800 whitespace-nowrap"
|
57
63
|
>
|
58
64
|
|
59
|
-
<div class="flex justify-between">
|
65
|
+
<div class="flex justify-between gap-1">
|
60
66
|
|
61
67
|
<span data-column_item><%= truncated_item %></span>
|
68
|
+
|
69
|
+
<% if item.present? and foreign_key.present? %>
|
70
|
+
<button
|
71
|
+
data-action="click->table#load_foreign_key_data"
|
72
|
+
data-fk_target_table="<%= foreign_key[:to_table] %>"
|
73
|
+
data-fk_target_field="<%= foreign_key[:primary_key]%>"
|
74
|
+
data-fk_target_field_value="<%= truncated_item %>"
|
75
|
+
class="size-4 mt-0.5 hover:cursor-pointer flex-grow outline-none"
|
76
|
+
>
|
77
|
+
<%= image_tag "solid_litequeen/icons/spline.svg", class: "size-4 filter-blue" %>
|
78
|
+
</button>
|
79
|
+
|
80
|
+
<% end %>
|
81
|
+
|
62
82
|
|
83
|
+
<%# TODO: we can use one dynamic modal instead and fetch the data. for now this works and is (s)crapy! %>
|
63
84
|
<% if truncated_item&.to_s&.ends_with?("...") %>
|
64
|
-
|
65
|
-
|
66
|
-
|
85
|
+
<% dialog_id = "#{column_name}_#{SecureRandom.hex(8)}_context_dialog" %>
|
86
|
+
|
87
|
+
<%= render "table-data-context-dialog", dialog_id: dialog_id, column_name: column_name, data: item %>
|
88
|
+
|
89
|
+
<button onclick="document.getElementById('<%= dialog_id %>').showModal()" class="cursor-pointer size-4 outline-none">
|
90
|
+
<%= image_tag "solid_litequeen/icons/circle-elipsis.svg", class: "size-4" %>
|
91
|
+
|
92
|
+
</button>
|
93
|
+
|
94
|
+
|
67
95
|
<% end %>
|
96
|
+
|
97
|
+
|
68
98
|
</div>
|
69
99
|
</td>
|
70
100
|
<% end %>
|
71
101
|
</tr>
|
72
102
|
<% end %>
|
73
103
|
</tbody>
|
104
|
+
|
105
|
+
<dialog id="foreign-key-data" data-controller="dialog" class="w-[900px] m-auto overscroll-y-contain">
|
106
|
+
<form method="submit" class="">
|
107
|
+
<div class="flex flex-row-reverse">
|
108
|
+
<button formmethod="dialog" class="cursor-pointer mr-4 mt-2 outline-none">
|
109
|
+
<%= image_tag "solid_litequeen/icons/x.svg", class: "size-5" %>
|
110
|
+
</button>
|
111
|
+
|
112
|
+
</div>
|
113
|
+
</form>
|
114
|
+
|
115
|
+
<div class="overflow-x-auto">
|
116
|
+
<turbo-frame id="foreign-key-data-frame" >
|
117
|
+
<h1>loading...</h1>
|
118
|
+
</turbo-frame>
|
119
|
+
</div>
|
120
|
+
|
121
|
+
|
122
|
+
</dialog>
|
74
123
|
</table>
|
124
|
+
|
125
|
+
|
75
126
|
</div>
|
76
127
|
</div>
|
77
128
|
</div>
|
data/config/routes.rb
CHANGED
@@ -2,6 +2,7 @@ SolidLitequeen::Engine.routes.draw do
|
|
2
2
|
resources :databases, only: [ :index, :show ] do
|
3
3
|
get "/tables/:table", to: "databases#table_rows", as: :table_rows
|
4
4
|
post "/tables/:table/set-column-order", to: "databases#set_column_order", as: :set_table_column_order
|
5
|
+
get "/tables/:table/foreign-key-data/:target_table/:target_field/:target_field_value", to: "databases#get_foreign_key_data", as: :get_table_foreign_key_data
|
5
6
|
get "download", to: "databases#download", as: "download"
|
6
7
|
end
|
7
8
|
root to: "databases#index"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solid_litequeen
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.13.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vik Borges
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-03-
|
11
|
+
date: 2025-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -164,8 +164,10 @@ files:
|
|
164
164
|
- app/assets/config/solid_litequeen_manifest.js
|
165
165
|
- app/assets/images/solid_litequeen/icons/chevron-left.svg
|
166
166
|
- app/assets/images/solid_litequeen/icons/chevron-right.svg
|
167
|
+
- app/assets/images/solid_litequeen/icons/circle-elipsis.svg
|
167
168
|
- app/assets/images/solid_litequeen/icons/database.svg
|
168
169
|
- app/assets/images/solid_litequeen/icons/info.svg
|
170
|
+
- app/assets/images/solid_litequeen/icons/spline.svg
|
169
171
|
- app/assets/images/solid_litequeen/icons/workflow.svg
|
170
172
|
- app/assets/images/solid_litequeen/icons/x.svg
|
171
173
|
- app/assets/stylesheets/solid_litequeen/application.css
|
@@ -175,6 +177,7 @@ files:
|
|
175
177
|
- app/helpers/solid_litequeen/databases_helper.rb
|
176
178
|
- app/javascript/solid_litequeen/application.js
|
177
179
|
- app/javascript/solid_litequeen/controllers/application.js
|
180
|
+
- app/javascript/solid_litequeen/controllers/dialog_controller.js
|
178
181
|
- app/javascript/solid_litequeen/controllers/index.js
|
179
182
|
- app/javascript/solid_litequeen/controllers/table_controller.js
|
180
183
|
- app/javascript/solid_litequeen/controllers/table_relations_controller.js
|
@@ -183,6 +186,8 @@ files:
|
|
183
186
|
- app/models/solid_litequeen/application_record.rb
|
184
187
|
- app/views/layouts/solid_litequeen/application.html.erb
|
185
188
|
- app/views/solid_litequeen/_database-selector.html.erb
|
189
|
+
- app/views/solid_litequeen/databases/_foreign-key-data.html.erb
|
190
|
+
- app/views/solid_litequeen/databases/_table-data-context-dialog.html.erb
|
186
191
|
- app/views/solid_litequeen/databases/_table-relationships-dialog.html.erb
|
187
192
|
- app/views/solid_litequeen/databases/index.html.erb
|
188
193
|
- app/views/solid_litequeen/databases/show.html.erb
|