oversee 0.1.1 → 0.3.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/README.md +53 -14
- data/app/assets/config/oversee_manifest.js +2 -1
- data/app/components/oversee/{application_component.rb → base.rb} +1 -1
- data/app/components/oversee/card.rb +13 -0
- data/app/components/oversee/dashboard/filters.rb +58 -0
- data/app/components/oversee/dashboard/header.rb +57 -0
- data/app/components/oversee/dashboard/index.rb +36 -0
- data/app/components/oversee/dashboard/javascript.rb +9 -0
- data/app/components/oversee/dashboard/pagination.rb +23 -0
- data/app/components/oversee/dashboard/sidebar.rb +90 -0
- data/app/components/oversee/dashboard/tailwind.rb +62 -0
- data/app/components/oversee/field/display.rb +38 -0
- data/app/components/oversee/field/form.rb +30 -0
- data/app/components/oversee/field/input/belongs_to.rb +22 -0
- data/app/components/oversee/field/input/boolean.rb +13 -0
- data/app/components/oversee/field/input/datetime.rb +10 -0
- data/app/components/oversee/field/input/integer.rb +10 -0
- data/app/components/oversee/field/input/rich_text.rb +18 -0
- data/app/components/oversee/field/input/string.rb +10 -0
- data/app/components/oversee/field/input.rb +34 -0
- data/app/components/oversee/field/label.rb +25 -0
- data/app/components/oversee/field/value/belongs_to.rb +21 -0
- data/app/components/oversee/field/value/boolean.rb +50 -0
- data/app/components/oversee/field/value/datetime.rb +9 -0
- data/app/components/oversee/field/value/enum.rb +9 -0
- data/app/components/oversee/field/value/integer.rb +9 -0
- data/app/components/oversee/field/value/rich_text.rb +9 -0
- data/app/components/oversee/field/value/string.rb +20 -0
- data/app/components/oversee/field/value/text.rb +9 -0
- data/app/components/oversee/field/value.rb +40 -0
- data/app/components/oversee/layout/application.rb +46 -0
- data/app/components/oversee/resources/base.rb +4 -0
- data/app/components/oversee/resources/errors.rb +41 -0
- data/app/components/oversee/resources/form.rb +49 -0
- data/app/components/oversee/resources/index.rb +45 -0
- data/app/components/oversee/resources/new.rb +21 -0
- data/app/components/oversee/resources/show.rb +207 -0
- data/app/components/oversee/resources/table.rb +115 -0
- data/app/components/oversee/table/body.rb +13 -0
- data/app/components/oversee/table/data.rb +5 -0
- data/app/components/oversee/table/head.rb +9 -0
- data/app/components/oversee/table/row.rb +9 -0
- data/app/components/oversee/table.rb +21 -0
- data/app/controllers/oversee/dashboard_controller.rb +4 -0
- data/app/controllers/oversee/resources/fields_controller.rb +20 -36
- data/app/controllers/oversee/resources_controller.rb +65 -26
- data/app/javascript/oversee/application.js +3 -0
- data/app/javascript/oversee/controllers/index.js +8 -0
- data/app/javascript/oversee/controllers/reveal_controller.js +7 -0
- data/app/javascript/oversee/controllers/sidebar/state_controller.js +21 -0
- data/app/models/oversee/resource.rb +62 -0
- data/app/service/oversee/filter.rb +39 -0
- data/app/service/oversee/search.rb +34 -0
- data/app/views/oversee/base.rb +5 -0
- data/config/importmap.rb +14 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/oversee.en.yml +4 -0
- data/config/routes.rb +17 -11
- data/lib/generators/oversee/install_generator.rb +7 -0
- data/lib/oversee/configuration.rb +3 -0
- data/lib/oversee/engine.rb +19 -0
- data/lib/oversee/version.rb +1 -1
- data/lib/oversee.rb +26 -2
- data/lib/tasks/oversee_tasks.rake +6 -4
- metadata +95 -48
- data/app/assets/stylesheets/oversee/application.css +0 -15
- data/app/components/oversee/card_component.rb +0 -15
- data/app/components/oversee/dashboard/sidebar_component.rb +0 -127
- data/app/components/oversee/field_component.rb +0 -39
- data/app/components/oversee/field_label_component.rb +0 -155
- data/app/components/oversee/fields/display_row_component.rb +0 -58
- data/app/components/oversee/fields/input/boolean_component.rb +0 -26
- data/app/components/oversee/fields/input/datetime_component.rb +0 -27
- data/app/components/oversee/fields/input/integer_component.rb +0 -26
- data/app/components/oversee/fields/input/string_component.rb +0 -26
- data/app/components/oversee/fields/value/boolean_component.rb +0 -44
- data/app/components/oversee/fields/value/datetime_component.rb +0 -16
- data/app/components/oversee/fields/value/enum_component.rb +0 -16
- data/app/components/oversee/fields/value/integer_component.rb +0 -16
- data/app/components/oversee/fields/value/string_component.rb +0 -23
- data/app/components/oversee/fields/value/text_component.rb +0 -16
- data/app/helpers/oversee/application_helper.rb +0 -5
- data/app/jobs/oversee/application_job.rb +0 -4
- data/app/mailers/oversee/application_mailer.rb +0 -6
- data/app/oversee/cards.rb +0 -2
- data/app/oversee/resource.rb +0 -10
- data/app/views/layouts/oversee/application.html.erb +0 -26
- data/app/views/oversee/application/_javascript.html.erb +0 -17
- data/app/views/oversee/dashboard/show.html.erb +0 -18
- data/app/views/oversee/resources/_form.html.erb +0 -10
- data/app/views/oversee/resources/_input_field.html.erb +0 -20
- data/app/views/oversee/resources/edit.html.erb +0 -30
- data/app/views/oversee/resources/index.html.erb +0 -59
- data/app/views/oversee/resources/input_field.html.erb +0 -1
- data/app/views/oversee/resources/new.html.erb +0 -28
- data/app/views/oversee/resources/show.html.erb +0 -51
- data/app/views/oversee/resources/update.turbo_stream.erb +0 -3
- data/app/views/shared/_sidebar.html.erb +0 -32
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Oversee::Resources::Table < Oversee::Base
|
4
|
+
include Phlex::Rails::Helpers::TurboFrameTag
|
5
|
+
|
6
|
+
def initialize(resources:, resource_class:, params:, **options)
|
7
|
+
@resources = resources
|
8
|
+
@resource_class = resource_class || resources.first.class
|
9
|
+
@params = params
|
10
|
+
@options = options
|
11
|
+
|
12
|
+
@oversee_resource = Oversee::Resource.new(resource_class: @resource_class)
|
13
|
+
end
|
14
|
+
|
15
|
+
def view_template
|
16
|
+
turbo_frame_tag dom_id(@resource_class, :table), target: "_top" do
|
17
|
+
render Oversee::Table.new do |table|
|
18
|
+
table.head do |head|
|
19
|
+
head.row do
|
20
|
+
th(scope: "col", class: "px-4 py-3 text-left text-xs text-gray-900 uppercase")
|
21
|
+
# Attributes
|
22
|
+
@resource_class.columns_hash.each do |key, metadata|
|
23
|
+
next if @oversee_resource.foreign_keys.include?(key.to_s)
|
24
|
+
th(scope: "col", class: "text-left text-xs text-gray-900 uppercase whitespace-nowrap hover:bg-gray-50 transition relative") do
|
25
|
+
a(
|
26
|
+
href:
|
27
|
+
(
|
28
|
+
helpers.resources_path(
|
29
|
+
resource: @params[:resource],
|
30
|
+
sort_attribute: key,
|
31
|
+
sort_direction:
|
32
|
+
@params[:sort_direction] == "asc" ? :desc : :asc
|
33
|
+
)
|
34
|
+
),
|
35
|
+
target: "_top",
|
36
|
+
class: "px-4 py-3 flex items-center justify-between gap-2 w-full h-full hover:text-gray-900 transition-colors"
|
37
|
+
) do
|
38
|
+
render Oversee::Field::Label.new(key: key, datatype: metadata.sql_type_metadata.type)
|
39
|
+
render Phlex::Icons::Iconoir::ArrowSeparateVertical.new(class: "size-3 text-gray-500", stroke_width: 2.5)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Associations
|
45
|
+
resource_associations.each do |association|
|
46
|
+
th(scope: "col", class: "text-left text-xs text-gray-900 uppercase whitespace-nowrap hover:bg-gray-50 transition relative") do
|
47
|
+
span(
|
48
|
+
class: "px-4 py-3 flex items-center justify-between gap-2 w-full h-full hover:text-gray-900 transition-colors"
|
49
|
+
) do
|
50
|
+
render Oversee::Field::Label.new(key: association.name, datatype: nil)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
table.body do |body|
|
58
|
+
@resources.each do |resource|
|
59
|
+
|
60
|
+
body.row do |row|
|
61
|
+
td do
|
62
|
+
div(class: "flex space-x-2 mx-4") do
|
63
|
+
a(
|
64
|
+
href:
|
65
|
+
(
|
66
|
+
helpers.resource_path(
|
67
|
+
resource.id,
|
68
|
+
resource_class_name: @params[:resource_class_name]
|
69
|
+
)
|
70
|
+
),
|
71
|
+
data: { turbo_stream: true },
|
72
|
+
class: "size-6 bg-gray-100 inline-flex items-center justify-center hover:bg-gray-200 group"
|
73
|
+
) { render Phlex::Icons::Iconoir::Eye.new(class: "size-4 text-gray-500") }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
@resource_class.columns_hash.each do |key, metadata|
|
78
|
+
next if @oversee_resource.foreign_keys.include?(key.to_s)
|
79
|
+
row.data do
|
80
|
+
div(class: "max-w-96") do
|
81
|
+
render Oversee::Field::Value.new(datatype: metadata.sql_type_metadata.type, value: resource.send(key), key: key)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
resource_associations.each do |association|
|
87
|
+
foreign_id = resource.send(association.foreign_key)
|
88
|
+
resource_class_name = association.class_name
|
89
|
+
|
90
|
+
path = !!foreign_id ? helpers.resource_path(id: foreign_id, resource_class_name:) : helpers.resources_path(resource_class_name: resource_class_name)
|
91
|
+
|
92
|
+
row.data do
|
93
|
+
div(class: "max-w-96") do
|
94
|
+
a(
|
95
|
+
href: path,
|
96
|
+
class:"px-2 py-1 bg-gray-100 text-gray-500 text-sm flex items-center justify-between gap-2 hover:bg-gray-200 hover:text-gray-900 min-w-20") do
|
97
|
+
span { foreign_id.presence || "N/A" }
|
98
|
+
render Phlex::Icons::Iconoir::ArrowRight.new(class: "size-3")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def resource_associations
|
113
|
+
@resource_class.reflect_on_all_associations.select { |association| association.macro == :belongs_to }
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Oversee::Table < Phlex::HTML
|
2
|
+
def view_template(&)
|
3
|
+
div(class: "overflow-x-hidden") do
|
4
|
+
div(class: "-mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8") do
|
5
|
+
div(class: "inline-block min-w-full align-middle sm:px-6 lg:px-8") do
|
6
|
+
table(class: "min-w-full divide-y divide-gray-200") do
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def head(&)
|
15
|
+
render Oversee::Table::Head.new(&)
|
16
|
+
end
|
17
|
+
|
18
|
+
def body(&)
|
19
|
+
render Oversee::Table::Body.new(&)
|
20
|
+
end
|
21
|
+
end
|
@@ -1,44 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
class Oversee::Resources::FieldsController < Oversee::ResourcesController
|
2
|
+
# Renders the display field for a resource
|
3
|
+
def show
|
4
|
+
end
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# @datatype = @resource_class.columns_hash[@key.to_s].type
|
6
|
+
# Renders the input field for a resource
|
7
|
+
def input
|
8
|
+
set_resource
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
format.turbo_stream
|
15
|
-
else
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# Non-standard actions
|
21
|
-
def edit
|
22
|
-
@resource = @resource_class.find(params[:id])
|
23
|
-
@key = params[:key].to_sym
|
24
|
-
@value = @resource.send(@key)
|
25
|
-
@datatype = @resource.class.columns_hash[@key.to_s].type
|
26
|
-
puts "---"
|
27
|
-
puts "key: #{@key}"
|
28
|
-
puts "value: #{@value}"
|
29
|
-
puts "datatype: #{@datatype}"
|
30
|
-
puts "---"
|
10
|
+
key = params[:key].to_sym
|
11
|
+
value = params[:value] || @resource.send(key)
|
12
|
+
datatype = params[:datatype] || @resource.class.columns_hash[key.to_s].type
|
31
13
|
|
32
|
-
|
14
|
+
# puts "---" * 30
|
15
|
+
# puts "key: #{key}"
|
16
|
+
# puts "value: #{value}"
|
17
|
+
# puts "datatype: #{datatype}"
|
18
|
+
# puts "---" * 30
|
33
19
|
|
34
|
-
|
35
|
-
|
36
|
-
def set_resource_class
|
37
|
-
@resource_class = params[:resource_class].constantize
|
38
|
-
end
|
20
|
+
field_dom_id = dom_id(@resource, key)
|
21
|
+
field = Oversee::Field::Form.new(resource: @resource, datatype:, key:, value:)
|
39
22
|
|
40
|
-
|
41
|
-
|
23
|
+
respond_to do |format|
|
24
|
+
format.turbo_stream do
|
25
|
+
render turbo_stream: turbo_stream.replace(field_dom_id, field)
|
42
26
|
end
|
43
27
|
end
|
44
28
|
end
|
@@ -2,51 +2,67 @@ module Oversee
|
|
2
2
|
class ResourcesController < BaseController
|
3
3
|
include ActionView::RecordIdentifier
|
4
4
|
|
5
|
-
before_action :set_resource_class
|
6
|
-
before_action :set_resource, only: %i[show edit destroy]
|
5
|
+
before_action :set_resource_class
|
6
|
+
before_action :set_resource, only: %i[show edit update destroy]
|
7
|
+
before_action :set_resources, only: %i[index]
|
7
8
|
|
8
9
|
def index
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
@pagy, @resources = pagy(@resources, limit: params[:per_page] || Oversee.configuration.per_page)
|
11
|
+
|
12
|
+
render Oversee::Resources::Index.new(
|
13
|
+
resources: @resources,
|
14
|
+
resource_class: @resource_class,
|
15
|
+
pagy: @pagy,
|
16
|
+
params: params
|
17
|
+
), layout: false
|
13
18
|
end
|
14
19
|
|
15
20
|
def new
|
16
21
|
@resource = @resource_class.new
|
22
|
+
render Oversee::Resources::New.new(
|
23
|
+
resource: @resource,
|
24
|
+
resource_class: @resource_class,
|
25
|
+
params: params
|
26
|
+
), layout: false
|
17
27
|
end
|
18
28
|
|
19
29
|
def create
|
20
|
-
@resource_class = params[:resource_class].constantize
|
21
30
|
@resource = @resource_class.new(resource_params)
|
22
31
|
|
23
32
|
respond_to do |format|
|
24
33
|
if @resource.update(resource_params)
|
25
|
-
format.html { redirect_to resource_path(@resource.id,
|
26
|
-
format.turbo_stream
|
34
|
+
format.html { redirect_to resource_path(@resource.id, resource_class_name: @resource_class) }
|
35
|
+
format.turbo_stream { redirect_to resource_path(@resource.id, resource_class_name: @resource_class), status: :see_other }
|
27
36
|
else
|
37
|
+
format.html { render :new }
|
38
|
+
format.turbo_stream { render turbo_stream: turbo_stream.replace(dom_id(@resource), Oversee::Resources::Form.new(resource: @resource)) }
|
28
39
|
end
|
29
40
|
end
|
30
41
|
end
|
31
42
|
|
32
43
|
def show
|
33
|
-
|
44
|
+
render Oversee::Resources::Show.new(
|
45
|
+
resource: @resource,
|
46
|
+
resource_class: @resource_class,
|
47
|
+
resource_associations: resource_associations,
|
48
|
+
params: params
|
49
|
+
), layout: false
|
34
50
|
end
|
35
51
|
|
36
52
|
def edit
|
37
53
|
end
|
38
54
|
|
39
55
|
def update
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
@key = params[:resource][:oversee_key]
|
44
|
-
@datatype = params[:resource][:oversee_datatype]
|
56
|
+
key = params[:oversee_key]
|
57
|
+
datatype = params[:oversee_datatype]
|
45
58
|
|
46
59
|
respond_to do |format|
|
47
60
|
if @resource.update(resource_params)
|
48
61
|
format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
|
49
|
-
format.turbo_stream
|
62
|
+
format.turbo_stream do
|
63
|
+
component = Oversee::Field::Display.new(resource: @resource, datatype:, key:, value: @resource.send(key))
|
64
|
+
render turbo_stream: turbo_stream.replace(dom_id(@resource, key), component)
|
65
|
+
end
|
50
66
|
else
|
51
67
|
end
|
52
68
|
end
|
@@ -57,19 +73,34 @@ module Oversee
|
|
57
73
|
redirect_to resources_path(resource: @resource_class)
|
58
74
|
end
|
59
75
|
|
60
|
-
|
61
|
-
|
62
|
-
|
76
|
+
def association
|
77
|
+
@resources = @resource_class.find(params[:id]).send(params[:association_name])
|
78
|
+
@pagy, @resources = pagy(@resources, limit: params[:per_page] || Oversee.configuration.per_page)
|
79
|
+
|
80
|
+
render Oversee::Resources::Index.new(
|
81
|
+
resources: @resources,
|
82
|
+
resource_class: @resource_class,
|
83
|
+
pagy: @pagy,
|
84
|
+
params: params
|
85
|
+
)
|
86
|
+
end
|
63
87
|
|
64
|
-
|
65
|
-
|
66
|
-
|
88
|
+
def table
|
89
|
+
if params[:association_name].present?
|
90
|
+
@resources = @resource_class.find(params[:id]).send(params[:association_name])
|
91
|
+
else
|
92
|
+
set_resources
|
93
|
+
end
|
94
|
+
|
95
|
+
component_id = dom_id(@resource_class, :table)
|
96
|
+
component = Oversee::Resources::Table.new(resource_class: @resource_class, resources: @resources, params: params)
|
67
97
|
|
68
98
|
respond_to do |format|
|
69
99
|
format.turbo_stream do
|
70
|
-
render turbo_stream:
|
71
|
-
|
72
|
-
|
100
|
+
render turbo_stream: turbo_stream.replace(component_id, component)
|
101
|
+
end
|
102
|
+
format.html do
|
103
|
+
render component, layout: false
|
73
104
|
end
|
74
105
|
end
|
75
106
|
end
|
@@ -77,13 +108,21 @@ module Oversee
|
|
77
108
|
private
|
78
109
|
|
79
110
|
def set_resource_class
|
80
|
-
@resource_class = params[:
|
111
|
+
@resource_class = params[:resource_class_name].constantize
|
81
112
|
end
|
82
113
|
|
83
114
|
def set_resource
|
84
115
|
@resource = @resource_class.find(params[:id])
|
85
116
|
end
|
86
117
|
|
118
|
+
def set_resources
|
119
|
+
set_sorting_rules
|
120
|
+
|
121
|
+
@resources = @resource_class.order(@sort_attribute.to_sym => sort_direction)
|
122
|
+
@resources = Oversee::Filter.new(collection: @resources, params:).apply
|
123
|
+
@resources = Oversee::Search.new(collection: @resources, resource_class: @resource_class, query: params[:query]).call
|
124
|
+
end
|
125
|
+
|
87
126
|
def resource_associations
|
88
127
|
@resource_associations ||= @resource_class.reflect_on_all_associations
|
89
128
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { Application } from "@hotwired/stimulus";
|
2
|
+
const application = Application.start();
|
3
|
+
|
4
|
+
application.debug = false;
|
5
|
+
window.Stimulus = application;
|
6
|
+
|
7
|
+
import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading";
|
8
|
+
eagerLoadControllersFrom("controllers", application);
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { Controller } from "@hotwired/stimulus";
|
2
|
+
|
3
|
+
const KEY_BASE = "oversee-sidebar";
|
4
|
+
|
5
|
+
export default class extends Controller {
|
6
|
+
initialize() {
|
7
|
+
this.key = `${KEY_BASE}--${this.element.id}`;
|
8
|
+
this.expanded = localStorage.getItem(this.key) === "true";
|
9
|
+
this.element.open = this.expanded;
|
10
|
+
}
|
11
|
+
|
12
|
+
toggle() {
|
13
|
+
this.expanded = !this.expanded;
|
14
|
+
this.element.open = this.expanded;
|
15
|
+
}
|
16
|
+
|
17
|
+
persist(event) {
|
18
|
+
this.expanded = event.newState === "open";
|
19
|
+
localStorage.setItem(this.key, this.expanded);
|
20
|
+
}
|
21
|
+
}
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Oversee::Resource
|
2
|
+
attr_reader :resource_class
|
3
|
+
attr_reader :resource_class_name
|
4
|
+
attr_reader :instance
|
5
|
+
attr_reader :rich_text_associations
|
6
|
+
|
7
|
+
attr_accessor :associations
|
8
|
+
|
9
|
+
def initialize(resource_class:, instance: nil)
|
10
|
+
@resource_class = resource_class
|
11
|
+
@resource_class_name = resource_class.to_s
|
12
|
+
@instance = instance
|
13
|
+
end
|
14
|
+
|
15
|
+
# Route helpers
|
16
|
+
def resources_path
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def resource_path
|
21
|
+
end
|
22
|
+
|
23
|
+
# Columns
|
24
|
+
def columns_for_create
|
25
|
+
excluded_columns = [resource_class.primary_key, "created_at", "updated_at"]
|
26
|
+
resource_class.columns_hash.except(*excluded_columns)
|
27
|
+
end
|
28
|
+
|
29
|
+
def columns_for_show
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
# Associations
|
34
|
+
# Structured by association macro
|
35
|
+
def associations
|
36
|
+
@associations ||= begin
|
37
|
+
map = Hash.new { |hash, key| hash[key] = [] }
|
38
|
+
|
39
|
+
@resource_class.reflect_on_all_associations.each do |association|
|
40
|
+
map[association.macro] << {
|
41
|
+
name: association.name,
|
42
|
+
class_name: association.class_name,
|
43
|
+
foreign_key: association.foreign_key,
|
44
|
+
optional: association.macro == :belongs_to ? !!association.options[:optional] : true,
|
45
|
+
through: association.options[:through],
|
46
|
+
rich_text: association.name.to_s.start_with?("rich_text_"),
|
47
|
+
}
|
48
|
+
end
|
49
|
+
map
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def foreign_keys
|
54
|
+
resource_class.reflections.map do |name, reflection|
|
55
|
+
reflection.foreign_key if reflection.belongs_to?
|
56
|
+
end.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
def rich_text_associations
|
60
|
+
@rich_text_associations ||= associations[:has_one].select { |association| association[:rich_text] }
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Oversee::Filter
|
2
|
+
ALLOWED_OPERATORS = %w[eq in gt gte lt lte].freeze
|
3
|
+
|
4
|
+
def initialize(collection:, params: nil)
|
5
|
+
@collection = collection
|
6
|
+
@params = params
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def apply
|
11
|
+
return @collection if filters.blank?
|
12
|
+
|
13
|
+
filters.each do |key, constraint|
|
14
|
+
operator = constraint.keys.first
|
15
|
+
next unless ALLOWED_OPERATORS.include?(operator)
|
16
|
+
|
17
|
+
value = constraint[operator]
|
18
|
+
chain(key:, operator:, value:)
|
19
|
+
end
|
20
|
+
return @collection
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Example params hash:
|
26
|
+
# filters[kind][eq][]=get => {"kind"=>{"is"=>["get"]}}
|
27
|
+
def filters
|
28
|
+
@filters ||= @params[:filters]
|
29
|
+
end
|
30
|
+
|
31
|
+
def chain(key:, operator:, value:)
|
32
|
+
@collection = case operator
|
33
|
+
when "eq"
|
34
|
+
@collection.then { _1.where(key => value) }
|
35
|
+
when "in"
|
36
|
+
@collection.then { _1.where(key => value) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Oversee::Search
|
2
|
+
DEFAULT_SEARCHABLE_ATTRIBUTES = %w[name title email email_address]
|
3
|
+
|
4
|
+
def initialize(collection:, resource_class:, query: nil)
|
5
|
+
@collection = collection
|
6
|
+
@resource_class = resource_class
|
7
|
+
@query = query
|
8
|
+
end
|
9
|
+
|
10
|
+
def call
|
11
|
+
return @collection if @query.blank? || @resource_class.blank?
|
12
|
+
default_searchable_by
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_searchable_attribute
|
16
|
+
DEFAULT_SEARCHABLE_ATTRIBUTES.each do |attr|
|
17
|
+
return attr if @resource_class.column_names.include?(attr)
|
18
|
+
end
|
19
|
+
|
20
|
+
return @resource_class.primary_key
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def default_searchable_by
|
26
|
+
return @collection.where(default_searchable_attribute => @query) if @resource_class.primary_key == default_searchable_attribute
|
27
|
+
|
28
|
+
@collection
|
29
|
+
.where(@resource_class.arel_table[default_searchable_attribute]
|
30
|
+
.matches("%#{@query}%"))
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
data/config/importmap.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# Application
|
2
|
+
pin "application", to: "oversee/application.js", preload: true
|
3
|
+
|
4
|
+
# Rich text editor
|
5
|
+
pin "trix", to: "https://unpkg.com/trix@2.1.8"
|
6
|
+
pin "@rails/actiontext", to: "https://unpkg.com/@rails/actiontext@8.0.0"
|
7
|
+
|
8
|
+
# Turbo
|
9
|
+
pin "@hotwired/turbo-rails", to: "https://unpkg.com/@hotwired/turbo-rails"
|
10
|
+
|
11
|
+
# Stimulus
|
12
|
+
pin "@hotwired/stimulus", to: "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js"
|
13
|
+
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
|
14
|
+
pin_all_from Oversee::Engine.root.join("app/javascript/oversee/controllers"), under: "controllers", to: "oversee/controllers"
|
data/config/routes.rb
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
Oversee::Engine.routes.draw do
|
2
|
-
|
3
2
|
# Resources
|
4
|
-
scope :
|
5
|
-
get "
|
6
|
-
|
7
|
-
|
8
|
-
get "
|
9
|
-
get "
|
10
|
-
patch "
|
11
|
-
delete "
|
12
|
-
get "
|
3
|
+
scope ":resource_class_name", controller: "resources" do
|
4
|
+
get "/", action: :index, as: :resources
|
5
|
+
post "/", action: :create, as: :create_resource
|
6
|
+
get "/new", action: :new, as: :new_resource
|
7
|
+
get "/:id", action: :show, as: :resource
|
8
|
+
get "/:id/edit", action: :edit, as: :edit_resource
|
9
|
+
patch "/:id", action: :update, as: :update_resource
|
10
|
+
delete "/:id", action: :destroy, as: :destroy_resource
|
11
|
+
get "/table", action: :table, as: :resources_table
|
12
|
+
|
13
|
+
get "/:id/input_field", action: :input_field, as: :resource_input_field
|
14
|
+
get "/:id/association", action: :association, as: :resource_association
|
15
|
+
|
16
|
+
|
17
|
+
# To reach this route using Rails' route helpers, use resource_input_path(:resource_class_name, :id)
|
18
|
+
get "/:id/input", controller: "resources/fields", action: :input, as: :resource_input
|
13
19
|
end
|
14
20
|
|
15
|
-
root to: "dashboard#
|
21
|
+
root to: "dashboard#index"
|
16
22
|
end
|