oversee 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|