oversee 0.0.1 → 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 +4 -4
- data/LICENCE +21 -0
- data/README.md +40 -0
- data/Rakefile +8 -0
- data/app/assets/config/oversee_manifest.js +1 -0
- data/app/assets/stylesheets/oversee/application.css +15 -0
- data/app/components/oversee/application_component.rb +5 -0
- data/app/components/oversee/card_component.rb +15 -0
- data/app/components/oversee/dashboard/sidebar_component.rb +127 -0
- data/app/components/oversee/field_component.rb +36 -0
- data/app/components/oversee/field_label_component.rb +155 -0
- data/app/components/oversee/fields/display_row_component.rb +58 -0
- data/app/components/oversee/fields/input/datetime_component.rb +27 -0
- data/app/components/oversee/fields/input/integer_component.rb +26 -0
- data/app/components/oversee/fields/input/string_component.rb +26 -0
- data/app/components/oversee/fields/value/boolean_component.rb +44 -0
- data/app/components/oversee/fields/value/datetime_component.rb +16 -0
- data/app/components/oversee/fields/value/enum_component.rb +16 -0
- data/app/components/oversee/fields/value/integer_component.rb +16 -0
- data/app/components/oversee/fields/value/string_component.rb +23 -0
- data/app/components/oversee/fields/value/text_component.rb +16 -0
- data/app/controllers/oversee/application_controller.rb +4 -0
- data/app/controllers/oversee/base_controller.rb +6 -0
- data/app/controllers/oversee/dashboard_controller.rb +21 -0
- data/app/controllers/oversee/resources/fields_controller.rb +45 -0
- data/app/controllers/oversee/resources_controller.rb +92 -0
- data/app/helpers/oversee/application_helper.rb +5 -0
- data/app/jobs/oversee/application_job.rb +4 -0
- data/app/mailers/oversee/application_mailer.rb +6 -0
- data/app/models/oversee/application_record.rb +5 -0
- data/app/oversee/cards.rb +2 -0
- data/app/views/layouts/oversee/application.html.erb +26 -0
- data/app/views/oversee/application/_javascript.html.erb +17 -0
- data/app/views/oversee/dashboard/show.html.erb +18 -0
- data/app/views/oversee/resources/_form.html.erb +10 -0
- data/app/views/oversee/resources/_input_field.html.erb +20 -0
- data/app/views/oversee/resources/edit.html.erb +31 -0
- data/app/views/oversee/resources/index.html.erb +59 -0
- data/app/views/oversee/resources/input_field.html.erb +1 -0
- data/app/views/oversee/resources/input_field.turbo_stream.erb +3 -0
- data/app/views/oversee/resources/show.html.erb +51 -0
- data/app/views/oversee/resources/update.turbo_stream.erb +3 -0
- data/app/views/shared/_sidebar.html.erb +32 -0
- data/config/routes.rb +10 -0
- data/lib/oversee/configuration.rb +27 -0
- data/lib/oversee/engine.rb +10 -0
- data/lib/oversee/version.rb +3 -0
- data/lib/oversee.rb +26 -4
- data/lib/tasks/oversee_tasks.rake +4 -0
- metadata +124 -5
@@ -0,0 +1,23 @@
|
|
1
|
+
module Oversee
|
2
|
+
module Fields
|
3
|
+
module Value
|
4
|
+
class StringComponent < Phlex::HTML
|
5
|
+
def initialize(key: nil, value: nil, kind: :value)
|
6
|
+
@key = key
|
7
|
+
@value = value
|
8
|
+
@kind = kind
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
return p(class: "text-gray-500 text-xs"){ "—" } if @value.blank?
|
13
|
+
|
14
|
+
if @key&.downcase&.include?("password") || @key&.downcase&.include?("token")
|
15
|
+
p { "[REDACTED]" }
|
16
|
+
else
|
17
|
+
p(class: "truncate") { @value }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Oversee
|
2
|
+
module Fields
|
3
|
+
module Value
|
4
|
+
class TextComponent < Phlex::HTML
|
5
|
+
def initialize(datatype: :string, key: nil, value: nil, kind: :value)
|
6
|
+
@value = value
|
7
|
+
end
|
8
|
+
|
9
|
+
def view_template
|
10
|
+
return p(class: "text-gray-500 text-xs"){ "—" } if @value.blank?
|
11
|
+
p(class: "truncate") { @value }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Oversee
|
2
|
+
class DashboardController < BaseController
|
3
|
+
def show
|
4
|
+
fetch_metrics
|
5
|
+
|
6
|
+
root = Rails.root.join("app/oversee/cards/")
|
7
|
+
files = Dir.glob(root.join("**/*.rb"))
|
8
|
+
@class_strings = files.sort!.map! { |f| f.split(root.to_s).last.delete_suffix(".rb").classify }
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def fetch_metrics
|
14
|
+
# @metrics = [{
|
15
|
+
# label: "Total Users",
|
16
|
+
# value: User.count,
|
17
|
+
# }]
|
18
|
+
@metrics = []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Oversee
|
2
|
+
module Resources
|
3
|
+
class FieldsController < BaseController
|
4
|
+
before_action :set_resource_class
|
5
|
+
|
6
|
+
def update
|
7
|
+
@resource_class = params[:resource_class].constantize
|
8
|
+
@resource = @resource_class.find(params[:id])
|
9
|
+
# @datatype = @resource_class.columns_hash[@key.to_s].type
|
10
|
+
|
11
|
+
respond_to do |format|
|
12
|
+
if @resource.update(resource_params)
|
13
|
+
format.html { redirect_to resource_path(@resource, resource: @resource_class) }
|
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 "---"
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def set_resource_class
|
37
|
+
@resource_class = params[:resource_class].constantize
|
38
|
+
end
|
39
|
+
|
40
|
+
def resource_params
|
41
|
+
params.require(:resource).permit!
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Oversee
|
2
|
+
class ResourcesController < BaseController
|
3
|
+
include ActionView::RecordIdentifier
|
4
|
+
|
5
|
+
before_action :set_resource_class, except: [:update]
|
6
|
+
before_action :set_resource, only: %i[show edit destroy]
|
7
|
+
|
8
|
+
def index
|
9
|
+
set_sorting_rules
|
10
|
+
|
11
|
+
@resources = @resource_class.order(@sort_attribute.to_sym => sort_direction)
|
12
|
+
@pagy, @resources = pagy(@resources)
|
13
|
+
end
|
14
|
+
|
15
|
+
def show
|
16
|
+
resource_associations
|
17
|
+
end
|
18
|
+
|
19
|
+
def edit
|
20
|
+
end
|
21
|
+
|
22
|
+
def update
|
23
|
+
@resource_class = params[:resource_class].constantize
|
24
|
+
set_resource
|
25
|
+
|
26
|
+
@key = params[:resource][:oversee_key]
|
27
|
+
@datatype = params[:resource][:oversee_datatype]
|
28
|
+
|
29
|
+
respond_to do |format|
|
30
|
+
if @resource.update(resource_params)
|
31
|
+
format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
|
32
|
+
format.turbo_stream
|
33
|
+
else
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def destroy
|
39
|
+
@resource.destroy
|
40
|
+
redirect_to resources_path(resource: @resource_class)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Non-standard actions
|
44
|
+
def input_field
|
45
|
+
set_resource
|
46
|
+
|
47
|
+
@key = params[:key].to_sym
|
48
|
+
@value = @resource.send(@key)
|
49
|
+
@datatype = @resource.class.columns_hash[@key.to_s].type
|
50
|
+
|
51
|
+
puts "---"
|
52
|
+
puts "key: #{@key}"
|
53
|
+
puts "value: #{@value}"
|
54
|
+
puts "datatype: #{@datatype}"
|
55
|
+
puts "---"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def set_resource_class
|
61
|
+
@resource_class = params[:resource].constantize
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_resource
|
65
|
+
@resource = @resource_class.find(params[:id])
|
66
|
+
end
|
67
|
+
|
68
|
+
def resource_associations
|
69
|
+
@resource_associations ||= @resource_class.reflect_on_all_associations
|
70
|
+
end
|
71
|
+
|
72
|
+
def sort_attribute
|
73
|
+
@sort_attribute ||= @resource_class.column_names.include?(params[:sort_attribute]) ? params[:sort_attribute] : "created_at"
|
74
|
+
end
|
75
|
+
|
76
|
+
def sort_direction
|
77
|
+
@sort_direction ||= ["asc", "desc"].include?(params[:sort_direction]) ? params[:sort_direction] : "desc"
|
78
|
+
end
|
79
|
+
|
80
|
+
def set_sorting_rules
|
81
|
+
@sort_attribute = params[:sort_attribute] || "created_at"
|
82
|
+
@sort_direction = params[:sort_direction] || "desc"
|
83
|
+
end
|
84
|
+
|
85
|
+
def resource_params
|
86
|
+
params[:resource].delete(:oversee_key)
|
87
|
+
params[:resource].delete(:oversee_datatype)
|
88
|
+
|
89
|
+
params.require(:resource).permit!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
5
|
+
<meta name="ROBOTS" content="NOODP">
|
6
|
+
|
7
|
+
<title>Oversee | <%= Rails.application.class %></title>
|
8
|
+
<%= csrf_meta_tags %>
|
9
|
+
<%= csp_meta_tag %>
|
10
|
+
|
11
|
+
<%# stylesheet_link_tag "oversee/application", media: "all" %>
|
12
|
+
<%= render "oversee/application/javascript" %>
|
13
|
+
</head>
|
14
|
+
<body class="min-h-screen bg-gray-100 p-8">
|
15
|
+
<div class="flex gap-4 w-full">
|
16
|
+
<div class="w-72 shrink-0">
|
17
|
+
<%= render Oversee::Dashboard::SidebarComponent.new %>
|
18
|
+
</div>
|
19
|
+
<div class="w-full overflow-scroll">
|
20
|
+
<div class="bg-white rounded-lg border overflow-clip">
|
21
|
+
<%= yield %>
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</body>
|
26
|
+
</html>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
2
|
+
|
3
|
+
<script type="importmap" data-turbo-track="reload">
|
4
|
+
{
|
5
|
+
"imports": {
|
6
|
+
"@hotwired/stimulus": "https://unpkg.com/@hotwired/stimulus/dist/stimulus.js",
|
7
|
+
"@hotwired/turbo": "https://unpkg.com/@hotwired/turbo",
|
8
|
+
"@hotwired/turbo-rails": "https://unpkg.com/@hotwired/turbo-rails"
|
9
|
+
}
|
10
|
+
}
|
11
|
+
</script>
|
12
|
+
|
13
|
+
<script async src="https://unpkg.com/es-module-shims/dist/es-module-shims.js"></script>
|
14
|
+
|
15
|
+
<script type="module">
|
16
|
+
import * as Turbo from "@hotwired/turbo-rails"
|
17
|
+
</script>
|
@@ -0,0 +1,18 @@
|
|
1
|
+
<div class="p-8">
|
2
|
+
<div class="flex items-center justify-between">
|
3
|
+
<div>
|
4
|
+
<p class="text-xs uppercase font-medium text-gray-400">Dashboard</p>
|
5
|
+
<h1 class="text-xl">Welcome</h1>
|
6
|
+
</div>
|
7
|
+
</div>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<% if Oversee.card_class_names.present? %>
|
11
|
+
<div class="p-8">
|
12
|
+
<div class="grid grid-cols-4 gap-4">
|
13
|
+
<% Oversee.card_class_names.each do |card_name| %>
|
14
|
+
<%= render Oversee::CardComponent.new(card_name: card_name) %>
|
15
|
+
<% end %>
|
16
|
+
</div>
|
17
|
+
</div>
|
18
|
+
<% end %>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= form_with model: @resource, scope: :resource, url: resource_path do |form| %>
|
2
|
+
<% @resource_class.columns_hash.each do |key, metadata| %>
|
3
|
+
<div class="mb-4">
|
4
|
+
<%= form.label key, class: "uppercase text-xs" %>
|
5
|
+
<%= form.text_field key, class: "block border" %>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
8
|
+
<%= hidden_field_tag :resource_class, @resource_class %>
|
9
|
+
<%= form.submit %>
|
10
|
+
<% end %>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<div id="<%= dom_id(@resource, key) %>" class="py-4 px-8 hover:bg-gray-50 group flex items-center justify-between">
|
2
|
+
<div class="hidden space-y-2 animate-pulse">
|
3
|
+
<div class="h-4 w-10 bg-gray-100 rounded-md"></div>
|
4
|
+
<div class="h-8 w-32 bg-gray-100 rounded-md"></div>
|
5
|
+
</div>
|
6
|
+
<div>
|
7
|
+
<%= render Oversee::FieldLabelComponent.new(key: key, datatype: datatype) %>
|
8
|
+
<div class="mt-2">
|
9
|
+
<%= form_with url: resource_path(@resource.id, resource_class: @resource_class), scope: :resource, model: @resource do |f| %>
|
10
|
+
<%= f.hidden_field :oversee_key, value: key %>
|
11
|
+
<%= f.hidden_field :oversee_datatype, value: datatype %>
|
12
|
+
|
13
|
+
<% input_datatype = [:string, :integer, :datetime].include?(datatype) ? datatype : :string %>
|
14
|
+
<%= render Oversee::FieldComponent.new(kind: :input, datatype: input_datatype.to_sym, key: key, value: value)%>
|
15
|
+
|
16
|
+
<%= f.submit "Save", class: "bg-blue-500 px-4 py-2 rounded-md text-white text-sm hover:bg-blue-600 hover:cursor-pointer" %>
|
17
|
+
<% end %>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</div>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<div class="p-8">
|
2
|
+
<div class="flex items-center justify-between">
|
3
|
+
<div>
|
4
|
+
<p class="text-xs uppercase font-medium text-gray-400">Resource</p>
|
5
|
+
<h1 class="text-xl"><%= params[:resource] %></h1>
|
6
|
+
</div>
|
7
|
+
|
8
|
+
<%= link_to 'Edit', edit_resource_path(@resource, resource: params[:resource]), class: "inline-flex items-center justify-center py-2 px-6 rounded bg-blue-500 text-white text-sm font-medium" %>
|
9
|
+
</div>
|
10
|
+
</div>
|
11
|
+
<div class="p-8">
|
12
|
+
<%= render "form" %>
|
13
|
+
</div>
|
14
|
+
<div class="p-8">
|
15
|
+
<div class="bg-white px-8 border shadow-sm rounded">
|
16
|
+
<div class="divide-y divide-gray-100 -mx-8">
|
17
|
+
<% @resource.attributes.each do |attribute, value| %>
|
18
|
+
<div class="py-4 px-8">
|
19
|
+
<p class="text-xs font-medium text-gray-500 uppercase"><%= attribute.humanize %></p>
|
20
|
+
<p class="mt-1 text-lg"><%= render Oversee::FieldComponent.new(datatype: :string, value: value) %></p>
|
21
|
+
</div>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</div>
|
26
|
+
|
27
|
+
<div class="p-8">
|
28
|
+
<pre>
|
29
|
+
<%= debug @resource.attributes %>
|
30
|
+
</pre>
|
31
|
+
</div>
|
@@ -0,0 +1,59 @@
|
|
1
|
+
<div class="p-8">
|
2
|
+
<div class="flex items-center justify-between">
|
3
|
+
<div>
|
4
|
+
<p class="text-xs font-medium text-gray-500">Index</p>
|
5
|
+
<h1 class="text-2xl"><%= params[:resource] %></h1>
|
6
|
+
</div>
|
7
|
+
<%= link_to 'Add new', "", class: "inline-flex items-center justify-center py-2 px-6 rounded bg-blue-500 text-white text-sm font-medium" %>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
<div class="">
|
11
|
+
<div class="bg-white border-t overflow-x-hidden">
|
12
|
+
<div class="-mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
13
|
+
<div class="inline-block min-w-full align-middle sm:px-6 lg:px-8">
|
14
|
+
<table class="min-w-full divide-y divide-gray-300">
|
15
|
+
<thead>
|
16
|
+
<tr class="divide-x divide-gray-200">
|
17
|
+
<th class="hidden"><input type="checkbox" name="" id="" class="mx-4"></th>
|
18
|
+
<th scope="col" class="px-4 py-3.5 text-left text-xs font-semibold text-gray-900 uppercase"></th>
|
19
|
+
<% @resource_class.columns_hash.each do |key, metadata| %>
|
20
|
+
<th scope="col" class="px-4 py-3.5 text-left text-xs font-semibold text-gray-900 uppercase whitespace-nowrap hover:bg-gray-50 transition">
|
21
|
+
<a href="<%= resources_path(resource: params[:resource], sort_attribute: key, sort_direction: params[:sort_direction] == "asc" ? :desc : :asc) %>" class="hover:underline">
|
22
|
+
<%= render Oversee::FieldLabelComponent.new(key: key, datatype: metadata.sql_type_metadata.type) %>
|
23
|
+
</a>
|
24
|
+
</th>
|
25
|
+
<% end %>
|
26
|
+
</tr>
|
27
|
+
</thead>
|
28
|
+
<tbody class="divide-y divide-gray-100 bg-white">
|
29
|
+
<% @resources.each do |resource| %>
|
30
|
+
<tr class="divide-x divide-gray-100">
|
31
|
+
<td class="hidden">
|
32
|
+
<input type="checkbox" name="" id="" class="mx-4">
|
33
|
+
</td>
|
34
|
+
<td>
|
35
|
+
<div class="flex space-x-2 mx-4">
|
36
|
+
<a href="<%= resource_path(resource.id, resource: params[:resource]) %>" data-turbo-stream="true">
|
37
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" data-slot="icon" class="w-4 h-4 text-gray-500 hover:text-blue-500">
|
38
|
+
<path d="M8 9.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" />
|
39
|
+
<path fill-rule="evenodd" d="M1.38 8.28a.87.87 0 0 1 0-.566 7.003 7.003 0 0 1 13.238.006.87.87 0 0 1 0 .566A7.003 7.003 0 0 1 1.379 8.28ZM11 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" clip-rule="evenodd" />
|
40
|
+
</svg>
|
41
|
+
</a>
|
42
|
+
</div>
|
43
|
+
</td>
|
44
|
+
<% @resource_class.columns_hash.each do |key, metadata| %>
|
45
|
+
<td class="whitespace-nowrap p-4 text-sm text-gray-500">
|
46
|
+
<div class="max-w-96">
|
47
|
+
<%= render Oversee::FieldComponent.new(datatype: metadata.sql_type_metadata.type, value: resource.send(key), key: key) %>
|
48
|
+
</div>
|
49
|
+
</td>
|
50
|
+
<% end %>
|
51
|
+
</tr>
|
52
|
+
<% end %>
|
53
|
+
</tbody>
|
54
|
+
</table>
|
55
|
+
</div>
|
56
|
+
</div>
|
57
|
+
</div>
|
58
|
+
<%= raw(pagy_nav(@pagy)) if @pagy.pages > 1 %>
|
59
|
+
</div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<%= render partial: "oversee/resources/input_field" %>
|
@@ -0,0 +1,51 @@
|
|
1
|
+
<div class="p-8">
|
2
|
+
<div class="flex items-center justify-between">
|
3
|
+
<div>
|
4
|
+
<p class="text-xs font-medium text-gray-500">Resource</p>
|
5
|
+
<h1 class="text-xl"><%= params[:resource] %></h1>
|
6
|
+
<div class="mt-2 inline-flex py-1 px-3 rounded-full text-xs bg-white border"># <%= @resource.to_param %></div>
|
7
|
+
</div>
|
8
|
+
<%= button_to resource_path(resource: params[:resource]), method: :delete, data: { turbo_confirm: "Are you sure?" }, class: "py-2 px-6 inline-flex border rounded text-red-500 shadow-sm hover:bg-gray-50 text-sm font-medium" do %>
|
9
|
+
Delete
|
10
|
+
<% end %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="pb-8">
|
15
|
+
<div class="px-8">
|
16
|
+
<h3 class="text-xl mb-8">Associations</h3>
|
17
|
+
<div class="border-y divide-y -mx-8">
|
18
|
+
<% @resource_associations.each do |association| %>
|
19
|
+
<% associated_resources = @resource.send(association.name.to_sym) %>
|
20
|
+
<% next if associated_resources.blank? %>
|
21
|
+
<% associated_resources = [associated_resources] unless associated_resources.respond_to?(:each) %>
|
22
|
+
|
23
|
+
<div class="px-8 py-6">
|
24
|
+
<div class="inline-flex items-center gap-2 mb-4">
|
25
|
+
<div class="inline-flex items-center justify-center h-6 w-6 bg-gray-50">
|
26
|
+
<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="h-4 w-4"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 5m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M5 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M19 19m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0" /><path d="M6.5 17.5l5.5 -4.5l5.5 4.5" /><path d="M12 7l0 6" /></svg>
|
27
|
+
</div>
|
28
|
+
<%= association.name.to_s.titleize %>
|
29
|
+
</div>
|
30
|
+
|
31
|
+
<div class="flex gap-2 flex-wrap">
|
32
|
+
<% associated_resources.each do |ar| %>
|
33
|
+
<a href="<%= resource_path(ar.id, resource: ar.class) %>" class="inline-flex text-xs border border-transparent rounded-full bg-gray-100 hover:bg-gray-200 px-3 py-1.5"><%= ar.class %> - <%= ar.to_param %></a>
|
34
|
+
<% end %>
|
35
|
+
</div>
|
36
|
+
</div>
|
37
|
+
<% end %>
|
38
|
+
</div>
|
39
|
+
</div>
|
40
|
+
</div>
|
41
|
+
|
42
|
+
<div class="pb-8">
|
43
|
+
<div class="px-8">
|
44
|
+
<h3 class="text-xl mb-8">Attributes</h3>
|
45
|
+
<div class="border-y divide-y -mx-8">
|
46
|
+
<% @resource_class.columns_hash.each do |key, metadata| %>
|
47
|
+
<%= render Oversee::Fields::DisplayRowComponent.new(key: key, resource: @resource, datatype: metadata.sql_type_metadata.type, value: @resource.send(key)) %>
|
48
|
+
<% end %>
|
49
|
+
</div>
|
50
|
+
</div>
|
51
|
+
</div>
|
@@ -0,0 +1,32 @@
|
|
1
|
+
<div class="bg-gray-50 h-full relative p-4">
|
2
|
+
<div class="overflow-y-scroll mb-4">
|
3
|
+
<ul class="mb-4">
|
4
|
+
<li class="flex">
|
5
|
+
<a href="<%= root_path %>" class="group flex items-center space-x-1.5 rounded-md hover:bg-gray-100 p-2">
|
6
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="group-hover:text-gray-500 text-gray-400 w-4 h-4 transition-colors duration-75" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v1a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M4 13m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v3a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /><path d="M14 4m0 2a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2z" /></svg>
|
7
|
+
<p class="text-sm text-gray-800 group-hover:text-gray-900">Dashboard</p>
|
8
|
+
</a>
|
9
|
+
</li>
|
10
|
+
<li class="flex">
|
11
|
+
<a href="<%= main_app.root_path %>" class="group flex items-center space-x-1.5 rounded-md hover:bg-gray-100 p-2">
|
12
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="group-hover:text-gray-500 text-gray-400 w-4 h-4 transition-colors duration-75" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M14 8v-2a2 2 0 0 0 -2 -2h-7a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h7a2 2 0 0 0 2 -2v-2" /><path d="M9 12h12l-3 -3" /><path d="M18 15l3 -3" /></svg>
|
13
|
+
<p class="text-sm text-gray-800 group-hover:text-gray-900">Return to app</p>
|
14
|
+
</a>
|
15
|
+
</li>
|
16
|
+
</ul>
|
17
|
+
<p class="font-medium mb-2 text-xs text-gray-500">Resources</p>
|
18
|
+
<ul>
|
19
|
+
<% ApplicationRecord.descendants.map(&:to_s).sort.each do |resource_name| %>
|
20
|
+
<li class="flex">
|
21
|
+
<a href="<%= resources_path(resource: resource_name) %>" class="group flex items-center space-x-1.5 rounded-md hover:bg-gray-100 p-2">
|
22
|
+
<svg xmlns="http://www.w3.org/2000/svg" class="group-hover:text-gray-500 text-gray-400 w-4 h-4 transition-colors duration-75" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 4h4l3 3h7a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2v-11a2 2 0 0 1 2 -2" /></svg>
|
23
|
+
<p class="text-sm text-gray-800 group-hover:text-gray-900"><%= resource_name %></p>
|
24
|
+
</a>
|
25
|
+
</li>
|
26
|
+
<% end %>
|
27
|
+
</ul>
|
28
|
+
</div>
|
29
|
+
<div class="absolute bottom-0 w-full text-xs flex items-center justify-center py-3 bg-gray-50 -mx-4">
|
30
|
+
<p>Powered by <a href="https://github.com/primevise/oversee" class="text-blue-500 hover:underline" target="_blank">Oversee</a></p>
|
31
|
+
</div>
|
32
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Oversee
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :excluded_resources
|
6
|
+
attr_accessor :filter_sensitive_columns
|
7
|
+
attr_accessor :sensitive_column_titles
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@excluded_resources = []
|
11
|
+
@filter_sensitive_columns = true
|
12
|
+
@sensitive_column_titles = %w[password password_digest access_token]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.configuration
|
17
|
+
@configuration ||= Configuration.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.configuration=(config)
|
21
|
+
@configuration = config
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.configure
|
25
|
+
yield configuration
|
26
|
+
end
|
27
|
+
end
|
data/lib/oversee.rb
CHANGED
@@ -1,5 +1,27 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
1
|
+
require "zeitwerk"
|
2
|
+
|
3
|
+
require "oversee/version"
|
4
|
+
require "oversee/engine"
|
5
|
+
require "oversee/configuration"
|
6
|
+
|
7
|
+
require "phlex-rails"
|
8
|
+
require "pagy"
|
9
|
+
|
10
|
+
# Zeitwerk
|
11
|
+
loader = Zeitwerk::Loader.for_gem
|
12
|
+
loader.ignore("#{__dir__}/generators")
|
13
|
+
loader.setup
|
14
|
+
|
15
|
+
module Oversee
|
16
|
+
class << self
|
17
|
+
def allowed_resource_list
|
18
|
+
ApplicationRecord.descendants
|
19
|
+
end
|
20
|
+
|
21
|
+
def card_class_names
|
22
|
+
root = Rails.root.join("app/oversee/cards/")
|
23
|
+
files = Dir.glob(root.join("**/*.rb"))
|
24
|
+
files.map! { |f| f.split(root.to_s).last.delete_suffix(".rb").classify.prepend("Cards::") }
|
25
|
+
end
|
4
26
|
end
|
5
|
-
end
|
27
|
+
end
|