databasium 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +32 -0
- data/MIT-LICENSE +20 -0
- data/README.md +109 -0
- data/Rakefile +6 -0
- data/app/assets/builds/application.js +9045 -0
- data/app/assets/builds/application.js.map +7 -0
- data/app/assets/builds/databasium.css +2 -0
- data/app/assets/config/databasium_manifest.js +1 -0
- data/app/assets/javascript/databasium/application.js +2 -0
- data/app/assets/javascript/databasium/controllers/attribute_controller.js +27 -0
- data/app/assets/javascript/databasium/controllers/collapse_controller.js +18 -0
- data/app/assets/javascript/databasium/controllers/error_controller.js +15 -0
- data/app/assets/javascript/databasium/controllers/filter_controller.js +224 -0
- data/app/assets/javascript/databasium/controllers/flash_controller.js +18 -0
- data/app/assets/javascript/databasium/controllers/graph_controller.js +193 -0
- data/app/assets/javascript/databasium/controllers/index.js +7 -0
- data/app/assets/javascript/databasium/controllers/layout_controller.js +13 -0
- data/app/assets/javascript/databasium/controllers/model_controller.js +32 -0
- data/app/assets/javascript/databasium/controllers/new_migration_controller.js +107 -0
- data/app/assets/javascript/databasium/controllers/relation_controller.js +10 -0
- data/app/assets/javascript/databasium/controllers/search_controller.js +23 -0
- data/app/assets/javascript/databasium/controllers/table_controller.js +283 -0
- data/app/assets/javascript/databasium/controllers/table_select_controller.js +19 -0
- data/app/assets/javascript/databasium/controllers/toggle_controller.js +28 -0
- data/app/assets/javascript/databasium/controllers/validation_controller.js +78 -0
- data/app/assets/javascript/databasium/shapes/erd_table_shape.js +54 -0
- data/app/assets/stylesheets/databasium/application.css +15 -0
- data/app/assets/stylesheets/databasium/colors.css +55 -0
- data/app/assets/stylesheets/databasium/custom.css +36 -0
- data/app/assets/stylesheets/databasium/databasium_engine.css +6 -0
- data/app/assets/stylesheets/databasium/pagy-tailwind.css +66 -0
- data/app/components/base.rb +50 -0
- data/app/components/databasium/collapsable.rb +62 -0
- data/app/components/databasium/forms/model.rb +147 -0
- data/app/components/databasium/forms/search.rb +31 -0
- data/app/components/databasium/global/error.rb +60 -0
- data/app/components/databasium/global/flash.rb +73 -0
- data/app/components/databasium/global/header_actions.rb +36 -0
- data/app/components/databasium/global/sidebar.rb +45 -0
- data/app/components/databasium/global/suggestion.rb +25 -0
- data/app/components/databasium/migrations/action.rb +39 -0
- data/app/components/databasium/migrations/file.rb +58 -0
- data/app/components/databasium/migrations/form.rb +222 -0
- data/app/components/databasium/migrations/header_actions.rb +87 -0
- data/app/components/databasium/migrations/migration_status.rb +22 -0
- data/app/components/databasium/migrations/preview.rb +29 -0
- data/app/components/databasium/migrations/show_turbo_stream.rb +19 -0
- data/app/components/databasium/migrations/sidebar.rb +28 -0
- data/app/components/databasium/models/attributes.rb +49 -0
- data/app/components/databasium/models/form.rb +100 -0
- data/app/components/databasium/models/header_actions.rb +51 -0
- data/app/components/databasium/models/model_preview.rb +31 -0
- data/app/components/databasium/models/sidebar.rb +25 -0
- data/app/components/databasium/models/templates/attribute.rb +99 -0
- data/app/components/databasium/models/templates/base.rb +6 -0
- data/app/components/databasium/models/templates/relation.rb +56 -0
- data/app/components/databasium/models/templates/validation.rb +285 -0
- data/app/components/databasium/navigation/base_icon.rb +32 -0
- data/app/components/databasium/navigation/frontend_icon.rb +17 -0
- data/app/components/databasium/navigation/get_icon.rb +26 -0
- data/app/components/databasium/navigation/icon.rb +28 -0
- data/app/components/databasium/navigation/icon_panel.rb +26 -0
- data/app/components/databasium/navigation/post_icon.rb +25 -0
- data/app/components/databasium/navigation/put_icon.rb +18 -0
- data/app/components/databasium/records/filter.rb +73 -0
- data/app/components/databasium/records/foreign_records.rb +84 -0
- data/app/components/databasium/records/header_actions.rb +110 -0
- data/app/components/databasium/records/show_turbo_stream.rb +75 -0
- data/app/components/databasium/records/sidebar.rb +23 -0
- data/app/components/databasium/records/table/record_panel.rb +60 -0
- data/app/components/databasium/records/table/row.rb +104 -0
- data/app/components/databasium/records/table.rb +125 -0
- data/app/components/databasium/records/table_turbo_frame.rb +37 -0
- data/app/components/databasium/records/utilities.rb +25 -0
- data/app/components/databasium/schemas/header_actions.rb +99 -0
- data/app/components/databasium/schemas/sidebar.rb +25 -0
- data/app/components/databasium/search_results/migrations.rb +37 -0
- data/app/components/databasium/search_results/models.rb +36 -0
- data/app/components/databasium/search_results/schema_models.rb +37 -0
- data/app/components/databasium/search_results/tables.rb +31 -0
- data/app/components/databasium/type_select.rb +35 -0
- data/app/controllers/databasium/application_controller.rb +68 -0
- data/app/controllers/databasium/homepage_controller.rb +5 -0
- data/app/controllers/databasium/migrations_controller.rb +186 -0
- data/app/controllers/databasium/models_controller.rb +105 -0
- data/app/controllers/databasium/records_controller.rb +156 -0
- data/app/controllers/databasium/schemas_controller.rb +52 -0
- data/app/helpers/databasium/application_helper.rb +4 -0
- data/app/helpers/databasium/heroicon_helper.rb +21 -0
- data/app/helpers/databasium/models_helper.rb +4 -0
- data/app/jobs/databasium/application_job.rb +4 -0
- data/app/mailers/databasium/application_mailer.rb +6 -0
- data/app/models/databasium/application_record.rb +5 -0
- data/app/models/model.json +0 -0
- data/app/services/databasium/migration.rb +176 -0
- data/app/services/databasium/model.rb +182 -0
- data/app/services/databasium/record.rb +65 -0
- data/app/services/databasium/schema.rb +146 -0
- data/app/views/base.rb +13 -0
- data/app/views/databasium/errors/non_development.rb +21 -0
- data/app/views/databasium/homepage/index.rb +29 -0
- data/app/views/databasium/migrations/index.rb +33 -0
- data/app/views/databasium/migrations/new.rb +29 -0
- data/app/views/databasium/models/index.rb +31 -0
- data/app/views/databasium/models/new.rb +37 -0
- data/app/views/databasium/records/index.rb +24 -0
- data/app/views/databasium/schemas/index.rb +39 -0
- data/app/views/layouts/databasium/application.rb +56 -0
- data/config/importmap.rb +12 -0
- data/config/initializers/heroicon.rb +12 -0
- data/config/initializers/pagy.rb +48 -0
- data/config/initializers/phlex.rb +19 -0
- data/config/routes.rb +31 -0
- data/config/tailwind.config.js +10 -0
- data/lib/databasium/engine.rb +57 -0
- data/lib/databasium/engine_mount.rb +37 -0
- data/lib/databasium/middleware/conditional_check_pending.rb +27 -0
- data/lib/databasium/templates/create_table_migration.rb.tt +29 -0
- data/lib/databasium/templates/migration.rb.tt +48 -0
- data/lib/databasium/templates/model.rb.tt +23 -0
- data/lib/databasium/version.rb +3 -0
- data/lib/databasium.rb +11 -0
- data/lib/tasks/databasium_tasks.rake +4 -0
- metadata +272 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
class Databasium::Schema
|
|
2
|
+
attr_reader :schema, :tables
|
|
3
|
+
def initialize
|
|
4
|
+
@conn = ActiveRecord::Base.connection
|
|
5
|
+
@tables = @conn.data_sources - %w[ar_internal_metadata schema_migrations]
|
|
6
|
+
@schema = nil
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def sync!(schema: nil)
|
|
10
|
+
path = Rails.root.join("storage")
|
|
11
|
+
FileUtils.mkdir_p(path) unless Dir.exist?(path)
|
|
12
|
+
File.write(path.join("schema_graph.json"), schema || build_schema.to_json)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get_associations(table)
|
|
16
|
+
model =
|
|
17
|
+
ActiveRecord::Base.descendants.find { |m| m.table_name == table } ||
|
|
18
|
+
table.classify.safe_constantize
|
|
19
|
+
if model
|
|
20
|
+
model.reflect_on_all_associations.map do |r|
|
|
21
|
+
{
|
|
22
|
+
name: r.name.to_s.pluralize,
|
|
23
|
+
macro: r.macro,
|
|
24
|
+
class_name: r.class_name,
|
|
25
|
+
foreign_key: r.foreign_key
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
[]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def schema
|
|
34
|
+
return @schema unless @schema.nil?
|
|
35
|
+
if File.exist?(Rails.root.join("storage/schema_graph.json"))
|
|
36
|
+
@schema = JSON.parse(File.read(Rails.root.join("storage/schema_graph.json")))
|
|
37
|
+
else
|
|
38
|
+
@schema = build_schema.deep_stringify_keys
|
|
39
|
+
sync!(schema: @schema)
|
|
40
|
+
@schema
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_model_and_layers_BFS(model, layers)
|
|
45
|
+
queue = Queue.new()
|
|
46
|
+
queue.push([ table_name_for(model), 0 ])
|
|
47
|
+
result = {}
|
|
48
|
+
|
|
49
|
+
until queue.empty?
|
|
50
|
+
table, layer = queue.pop
|
|
51
|
+
next if (layers.present? && layer > layers) || result[table].present?
|
|
52
|
+
result[table] = get_schema_for_model(table)
|
|
53
|
+
|
|
54
|
+
model_associations = result[table].fetch("associations", nil)
|
|
55
|
+
model_associations&.each { |a| queue << [ a["name"], layer + 1 ] }
|
|
56
|
+
end
|
|
57
|
+
result
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def get_schema_for_model(model)
|
|
61
|
+
schema[table_name_for(model)]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_foreign_keys(table)
|
|
65
|
+
@conn
|
|
66
|
+
.foreign_keys(table)
|
|
67
|
+
.map do |fk|
|
|
68
|
+
{ from: fk.from_table, to: fk.to_table, column: fk.column, primary_key: fk.primary_key }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get_columns_from_table(table)
|
|
73
|
+
@conn
|
|
74
|
+
.columns(table)
|
|
75
|
+
.map { |c| { name: c.name, sql_type: c.sql_type, null: c.null, default: c.default } }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def get_columns_names(table)
|
|
79
|
+
@conn.columns(table_name_for(table)).map { |c| c.name }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def get_tables(search)
|
|
83
|
+
@tables = @tables.select { |table| table =~ /#{search}/i } if search
|
|
84
|
+
@tables
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def get_columns(model)
|
|
88
|
+
return if model.nil?
|
|
89
|
+
model.columns.map do |column|
|
|
90
|
+
{
|
|
91
|
+
name: column.name,
|
|
92
|
+
type: column.type.to_s,
|
|
93
|
+
used: false,
|
|
94
|
+
foreign_key: is_column_foreign_key?(model.table_name, column.name),
|
|
95
|
+
to_table: get_foreign_key_to_table(model.table_name, column.name)
|
|
96
|
+
}
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def get_model_from_table(table)
|
|
101
|
+
return nil, "Select a table to view its records." if table.nil?
|
|
102
|
+
table_name = table_name_for(table)
|
|
103
|
+
unless ActiveRecord::Base.connection.table_exists?(table_name)
|
|
104
|
+
return nil, "Table #{table} does not exist"
|
|
105
|
+
end
|
|
106
|
+
begin
|
|
107
|
+
# If there is no model for this table it will raise a NameError
|
|
108
|
+
@model =
|
|
109
|
+
ActiveRecord::Base.descendants.find { |model| model.table_name == table_name } ||
|
|
110
|
+
table_name.classify.constantize
|
|
111
|
+
@error = nil
|
|
112
|
+
rescue NameError
|
|
113
|
+
@model = nil
|
|
114
|
+
@error =
|
|
115
|
+
"No model found for this table,
|
|
116
|
+
if you would like to interact with this table, you need to create a model for it."
|
|
117
|
+
end
|
|
118
|
+
[ @model, @error ]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def build_schema
|
|
124
|
+
@schema = {}
|
|
125
|
+
@tables.each do |table|
|
|
126
|
+
@schema[table] = {
|
|
127
|
+
columns: get_columns_from_table(table),
|
|
128
|
+
foreign_keys: get_foreign_keys(table),
|
|
129
|
+
associations: get_associations(table)
|
|
130
|
+
}
|
|
131
|
+
end
|
|
132
|
+
@schema
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def table_name_for(name)
|
|
136
|
+
name.to_s.tableize
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def is_column_foreign_key?(table, column_name)
|
|
140
|
+
get_foreign_keys(table).any? { |fk| fk[:column] == column_name }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def get_foreign_key_to_table(table, column_name)
|
|
144
|
+
get_foreign_keys(table).find { |fk| fk[:column] == column_name }&.fetch(:to)
|
|
145
|
+
end
|
|
146
|
+
end
|
data/app/views/base.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Databasium
|
|
4
|
+
class Views::Base < Components::Base
|
|
5
|
+
# The `Views::Base` is an abstract class for all your views.
|
|
6
|
+
# By default, it inherits from `Components::Base`, but you
|
|
7
|
+
# can change that to `Phlex::HTML` if you want to keep views and
|
|
8
|
+
# components independent.
|
|
9
|
+
|
|
10
|
+
# More caching options at https://www.phlex.fun/components/caching
|
|
11
|
+
#
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Errors::NonDevelopment < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
7
|
+
|
|
8
|
+
def view_template
|
|
9
|
+
content_for(:title) { "Non Development Environment" }
|
|
10
|
+
div(class: "w-full h-full p-4") do
|
|
11
|
+
h1(class: "text-2xl font-bold") do
|
|
12
|
+
"You are not allowed to access this page in non development environment"
|
|
13
|
+
end
|
|
14
|
+
p(class: "text-red-500 bg-white w-fit px-4 py-2 rounded-md my-2") do
|
|
15
|
+
"DATABASIUM IS NOT MEANT TO BE USED IN PRODUCTION, ALWAYS BE SURE TO NEVER EXPOSE IT TO THE PUBLIC."
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Homepage::Index < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
7
|
+
|
|
8
|
+
def view_template
|
|
9
|
+
content_for(:title) { "Homepage" }
|
|
10
|
+
div(class: "w-full h-full p-4") do
|
|
11
|
+
h1(class: "text-2xl font-bold") { "Hello world!" }
|
|
12
|
+
section(class: "p-4 text-base text-gray-300 flex flex-col gap-1") do
|
|
13
|
+
h2(class: "text-xl font-semibold") { "Welcome to Databasium" }
|
|
14
|
+
p { "Databasium is a developer tool for managing your database." }
|
|
15
|
+
p { "Its features are:" }
|
|
16
|
+
ul(class: "list-disc list-inside") do
|
|
17
|
+
li { "Create, read, update and delete(CRUD) data in your database." }
|
|
18
|
+
li { "Generate models and migrations for your database." }
|
|
19
|
+
li { "Generate schema for your database." }
|
|
20
|
+
end
|
|
21
|
+
p(class: "text-red-500 bg-white w-fit px-4 py-2 rounded-md my-2") do
|
|
22
|
+
"DATABASIUM IS NOT MEANT TO BE USED IN PRODUCTION, ALWAYS BE SURE TO NEVER EXPOSE IT TO THE PUBLIC."
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Views
|
|
2
|
+
module Databasium
|
|
3
|
+
class Migrations::Index < Views::Base
|
|
4
|
+
include Phlex::Rails::Helpers::FormWith
|
|
5
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
6
|
+
include Phlex::Rails::Helpers::LinkTo
|
|
7
|
+
include Phlex::Rails::Helpers::ButtonTo
|
|
8
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
9
|
+
|
|
10
|
+
def initialize
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def view_template
|
|
14
|
+
content_for(:title) { "Migrations" }
|
|
15
|
+
content_for(:sidebar) { render Components::Databasium::Migrations::Sidebar.new }
|
|
16
|
+
content_for(:header_actions) do
|
|
17
|
+
render Components::Databasium::Migrations::HeaderActions.new(migration: nil)
|
|
18
|
+
end
|
|
19
|
+
render_migration_frame
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def render_migration_frame
|
|
25
|
+
turbo_frame_tag "migration", class: "flex-1" do
|
|
26
|
+
render Components::Databasium::Global::Suggestion.new(
|
|
27
|
+
suggestions: [ "Select a migration to see the file" ]
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Migrations::New < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::FormWith
|
|
7
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
8
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
9
|
+
|
|
10
|
+
def initialize(tables:)
|
|
11
|
+
@tables = tables
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def view_template
|
|
15
|
+
content_for(:title) { "New Migration" }
|
|
16
|
+
content_for(:sidebar) { render Components::Databasium::Migrations::Sidebar.new }
|
|
17
|
+
content_for(:header_actions) do
|
|
18
|
+
render Components::Databasium::Migrations::HeaderActions.new(migration: nil)
|
|
19
|
+
end
|
|
20
|
+
div(class: "flex p-4 gap-4") do
|
|
21
|
+
render Components::Databasium::Migrations::Form.new(tables: @tables, content: nil)
|
|
22
|
+
div(class: "flex-1 pe-4") do
|
|
23
|
+
render Components::Databasium::Migrations::Preview.new(content: nil)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Models::Index < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
7
|
+
|
|
8
|
+
def initialize(models:)
|
|
9
|
+
@models = models
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def view_template
|
|
13
|
+
content_for(:title) { "Models" }
|
|
14
|
+
|
|
15
|
+
div(class: "w-full h-full p-4") { render_models }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def render_models
|
|
21
|
+
div do
|
|
22
|
+
@models.each do |model|
|
|
23
|
+
div(class: "border-1 border-border rounded-xl p-4") do
|
|
24
|
+
h2(class: "text-lg font-semibold mb-2") { model.name }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Models::New < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
7
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
8
|
+
|
|
9
|
+
def initialize(content:, model: nil, attributes: nil, models: nil)
|
|
10
|
+
@model = model
|
|
11
|
+
@content = content
|
|
12
|
+
@attributes = attributes
|
|
13
|
+
@models = models
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def view_template
|
|
17
|
+
content_for(:title) { "New Model" }
|
|
18
|
+
content_for(:sidebar) { render Components::Databasium::Models::Sidebar.new }
|
|
19
|
+
content_for(:header_actions) do
|
|
20
|
+
render Components::Databasium::Models::HeaderActions.new(model: @model)
|
|
21
|
+
end
|
|
22
|
+
div(class: "flex gap-4 p-4 overflow-y-hidden flex-1") do
|
|
23
|
+
div(class: "w-1/2 overflow-y-auto") do
|
|
24
|
+
render Components::Databasium::Models::Form.new(
|
|
25
|
+
attributes: @attributes,
|
|
26
|
+
model: @model,
|
|
27
|
+
models: @models
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
div(class: "flex-1 overflow-y-auto") do
|
|
31
|
+
render Components::Databasium::Models::ModelPreview.new(content: @content)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Records::Index < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
7
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
8
|
+
include Phlex::Rails::Helpers::FormWith
|
|
9
|
+
include Phlex::Rails::Helpers::LinkTo
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def view_template
|
|
15
|
+
content_for(:title) { "Records" }
|
|
16
|
+
content_for(:sidebar) { render Components::Databasium::Records::Sidebar.new }
|
|
17
|
+
div(class: "flex min-h-0 min-w-0 flex-1") do
|
|
18
|
+
render Components::Databasium::Records::TableTurboFrame.new
|
|
19
|
+
render Components::Databasium::Records::Table::RecordPanel.new
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
module Databasium
|
|
5
|
+
class Schemas::Index < Views::Base
|
|
6
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
7
|
+
include Phlex::Rails::Helpers::LinkTo
|
|
8
|
+
|
|
9
|
+
def initialize(schema:, models:, pagy:, model:, layers:)
|
|
10
|
+
@schema = schema
|
|
11
|
+
@models = models
|
|
12
|
+
@pagy = pagy
|
|
13
|
+
@model = model
|
|
14
|
+
@layers = layers
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def view_template
|
|
18
|
+
content_for(:title) { "Schema" }
|
|
19
|
+
content_for(:sidebar) { render Components::Databasium::Schemas::Sidebar.new }
|
|
20
|
+
content_for(:header_actions) do
|
|
21
|
+
render Components::Databasium::Schemas::HeaderActions.new(model: @model, layers: @layers)
|
|
22
|
+
end
|
|
23
|
+
div(class: "w-full h-full p-4") { render_schema }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def render_schema
|
|
29
|
+
div(
|
|
30
|
+
class: "w-full h-full border-1 border-border rounded-2xl p-4 overflow-x-auto",
|
|
31
|
+
data: {
|
|
32
|
+
controller: "graph",
|
|
33
|
+
graph_tables_value: @schema.to_json
|
|
34
|
+
}
|
|
35
|
+
) { }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Views
|
|
2
|
+
module Layouts
|
|
3
|
+
class Databasium::Application < Views::Base
|
|
4
|
+
include Phlex::Rails::Layout
|
|
5
|
+
include Phlex::Rails::Helpers::ContentFor
|
|
6
|
+
include Phlex::Rails::Helpers::StylesheetLinkTag
|
|
7
|
+
include Phlex::Rails::Helpers::JavascriptImportmapTags
|
|
8
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
|
9
|
+
include Phlex::Rails::Helpers::Flash
|
|
10
|
+
|
|
11
|
+
def view_template(&block)
|
|
12
|
+
doctype
|
|
13
|
+
|
|
14
|
+
html do
|
|
15
|
+
head do
|
|
16
|
+
title { content_for?(:title) ? yield(:title) : "Databasium" }
|
|
17
|
+
meta charset: "utf-8"
|
|
18
|
+
csrf_meta_tags
|
|
19
|
+
csp_meta_tag
|
|
20
|
+
yield :head
|
|
21
|
+
stylesheet_link_tag "databasium",
|
|
22
|
+
"data-turbo-track": Rails.env.production? ? "reload" : ""
|
|
23
|
+
javascript_importmap_tags "databasium/application"
|
|
24
|
+
end
|
|
25
|
+
body(
|
|
26
|
+
class: "flex h-dvh overflow-hidden bg-background text-main-text scrollbar-thin",
|
|
27
|
+
data: {
|
|
28
|
+
controller: "layout"
|
|
29
|
+
}
|
|
30
|
+
) do
|
|
31
|
+
render Components::Databasium::Global::Error.new
|
|
32
|
+
render Components::Databasium::Global::Flash.new(
|
|
33
|
+
success: flash[:success],
|
|
34
|
+
error: flash[:error]
|
|
35
|
+
)
|
|
36
|
+
render Components::Databasium::Global::Sidebar.new(sidebar: content_for(:sidebar))
|
|
37
|
+
turbo_frame_tag(
|
|
38
|
+
"main",
|
|
39
|
+
class: "flex flex-1 flex-col overflow-hidden",
|
|
40
|
+
data: {
|
|
41
|
+
controller: "toggle model table"
|
|
42
|
+
}
|
|
43
|
+
) do
|
|
44
|
+
render Components::Databasium::Global::HeaderActions.new(
|
|
45
|
+
actions: content_for(:header_actions)
|
|
46
|
+
)
|
|
47
|
+
div(class: "flex min-h-0 min-w-0 flex-1 flex-col") do
|
|
48
|
+
yield block_given? ? block : block.call
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/config/importmap.rb
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pin "databasium/application", to: "databasium/application.js"
|
|
2
|
+
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
|
|
3
|
+
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
|
|
4
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
|
5
|
+
pin "@maxgraph/core", to: "https://esm.sh/@maxgraph/core@0.21.0"
|
|
6
|
+
|
|
7
|
+
# Pin the controllers index so `import "controllers"` resolves
|
|
8
|
+
pin "databasium/controllers", to: "databasium/controllers/index.js"
|
|
9
|
+
|
|
10
|
+
# Auto-pin all Stimulus controllers under the namespace
|
|
11
|
+
pin_all_from Databasium::Engine.root.join("app/assets/javascript/databasium/controllers"), under: "databasium/controllers"
|
|
12
|
+
pin_all_from Databasium::Engine.root.join("app/assets/javascript/databasium/shapes"), under: "databasium/shapes"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "heroicon"
|
|
4
|
+
|
|
5
|
+
Heroicon.configure do |config|
|
|
6
|
+
config.variant = :solid # Options are :solid, :outline and :mini
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# You can set a default class, which will get applied to every icon with
|
|
10
|
+
# the given variant. To do so, un-comment the line below.
|
|
11
|
+
# config.default_class = {solid: "h-5 w-5", outline: "h-6 w-6", mini: "h-4 w-4"}
|
|
12
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Pagy initializer file (43.2.4)
|
|
4
|
+
# See https://ddnexus.github.io/pagy/resources/initializer/
|
|
5
|
+
|
|
6
|
+
############ Global Options ################################################################
|
|
7
|
+
# See https://ddnexus.github.io/pagy/toolbox/options/ for details.
|
|
8
|
+
# Add your global options below. They will be applied globally.
|
|
9
|
+
# For example:
|
|
10
|
+
#
|
|
11
|
+
# Pagy.options[:limit] = 10 # Limit the items per page
|
|
12
|
+
# Pagy.options[:client_max_limit] = 100 # The client can request a limit up to 100
|
|
13
|
+
# Pagy.options[:max_pages] = 200 # Allow only 200 pages
|
|
14
|
+
# Pagy.options[:jsonapi] = true # Use JSON:API compliant URLs
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
############ JavaScript ####################################################################
|
|
18
|
+
# See https://ddnexus.github.io/pagy/resources/javascript/ for details.
|
|
19
|
+
# Examples for Rails:
|
|
20
|
+
# For apps with an assets pipeline
|
|
21
|
+
# Rails.application.config.assets.paths << Pagy::ROOT.join('javascripts')
|
|
22
|
+
#
|
|
23
|
+
# For apps with a javascript builder (e.g. esbuild, webpack, etc.)
|
|
24
|
+
# javascript_dir = Rails.root.join('app/javascript')
|
|
25
|
+
# Pagy.sync_javascript(javascript_dir, 'pagy.mjs') if Rails.env.development?
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
############# Overriding Pagy::I18n Lookup #################################################
|
|
29
|
+
# Refer to https://ddnexus.github.io/pagy/resources/i18n/ for details.
|
|
30
|
+
# Override the I18n lookup by dropping your custom dictionary in some pagy dir.
|
|
31
|
+
# Example for Rails:
|
|
32
|
+
#
|
|
33
|
+
# Pagy::I18n.pathnames << Rails.root.join('config/locales/pagy')
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
############# I18n Gem Translation #########################################################
|
|
37
|
+
# See https://ddnexus.github.io/pagy/resources/i18n/ for details.
|
|
38
|
+
#
|
|
39
|
+
# Pagy.translate_with_the_slower_i18n_gem!
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
############# Calendar Localization for non-en locales ####################################
|
|
43
|
+
# See https://ddnexus.github.io/pagy/toolbox/paginators/calendar#localization for details.
|
|
44
|
+
# Add your desired locales to the list and uncomment the following line to enable them,
|
|
45
|
+
# regardless of whether you use the I18n gem for translations or not, whether with
|
|
46
|
+
# Rails or not.
|
|
47
|
+
#
|
|
48
|
+
# Pagy::Calendar.localize_with_rails_i18n_gem(*your_locales)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Views
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
module Components
|
|
7
|
+
extend Phlex::Kit
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
engine_views_path = Databasium::Engine.root.join("app/views")
|
|
11
|
+
engine_components_path = Databasium::Engine.root.join("app/components")
|
|
12
|
+
|
|
13
|
+
if engine_views_path.directory?
|
|
14
|
+
Rails.autoloaders.main.push_dir(engine_views_path, namespace: Views)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if engine_components_path.directory?
|
|
18
|
+
Rails.autoloaders.main.push_dir(engine_components_path, namespace: Components)
|
|
19
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Databasium::Engine.routes.draw do
|
|
2
|
+
root to: "homepage#index"
|
|
3
|
+
resources :homepage, only: [ :index ]
|
|
4
|
+
|
|
5
|
+
resources :records, only: [ :index, :create, :update ] do
|
|
6
|
+
collection do
|
|
7
|
+
get :sidebar
|
|
8
|
+
delete :bulk_destroy
|
|
9
|
+
get :records
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
resources :migrations, only: [ :index, :new, :create, :show ] do
|
|
13
|
+
collection do
|
|
14
|
+
get :sidebar
|
|
15
|
+
post :run_pending_migrations
|
|
16
|
+
post :run_migration
|
|
17
|
+
post :rollback_migration
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
resources :models, only: [ :new, :create, :show ] do
|
|
21
|
+
collection do
|
|
22
|
+
get :sidebar
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
resources :schemas, only: [ :index, :new, :create ] do
|
|
26
|
+
collection do
|
|
27
|
+
get :sidebar
|
|
28
|
+
put :sync_schema
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "databasium/engine_mount"
|
|
4
|
+
require "databasium/middleware/conditional_check_pending"
|
|
5
|
+
|
|
6
|
+
module Databasium
|
|
7
|
+
class Engine < ::Rails::Engine
|
|
8
|
+
isolate_namespace Databasium
|
|
9
|
+
|
|
10
|
+
config.databasium = ActiveSupport::OrderedOptions.new
|
|
11
|
+
|
|
12
|
+
initializer "databasium.development_only" do
|
|
13
|
+
if Rails.env.production?
|
|
14
|
+
abort "
|
|
15
|
+
Databasium gem must not be loaded in production.
|
|
16
|
+
Add databasium only to the :development group.
|
|
17
|
+
It will not even allow you to load your application in production or when you run RAILS_ENV=production locally.
|
|
18
|
+
Do not add it to the default group."
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
initializer "databasium.configuration" do |app|
|
|
23
|
+
app.config.databasium ||= ActiveSupport::OrderedOptions.new
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
initializer "databasium.clear_mount_path_cache" do |app|
|
|
27
|
+
app.config.to_prepare { Databasium::EngineMount.clear_mount_path_cache! }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Prevent the default global middleware; we insert a scoped version below.
|
|
31
|
+
initializer "databasium.skip_global_pending_migration_check",
|
|
32
|
+
before: "active_record.migration_error" do |app|
|
|
33
|
+
app.config.active_record.migration_error = :ignore if Rails.env.development?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
initializer "databasium.conditional_check_pending",
|
|
37
|
+
after: "active_record.migration_error" do |app|
|
|
38
|
+
next unless Rails.env.development?
|
|
39
|
+
|
|
40
|
+
app.middleware.insert_after(
|
|
41
|
+
ActionDispatch::Callbacks,
|
|
42
|
+
Databasium::Middleware::ConditionalCheckPending,
|
|
43
|
+
file_watcher: app.config.file_watcher
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
initializer "databasium.assets" do |app|
|
|
48
|
+
app.config.assets.precompile += %w[ databasium_manifest.js ]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
initializer "databasium.importmap", before: "importmap" do |app|
|
|
52
|
+
if app.respond_to?(:config) && app.config.respond_to?(:importmap)
|
|
53
|
+
app.config.importmap.paths << root.join("config/importmap.rb")
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|