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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/LICENCE +21 -0
  3. data/README.md +40 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/oversee_manifest.js +1 -0
  6. data/app/assets/stylesheets/oversee/application.css +15 -0
  7. data/app/components/oversee/application_component.rb +5 -0
  8. data/app/components/oversee/card_component.rb +15 -0
  9. data/app/components/oversee/dashboard/sidebar_component.rb +127 -0
  10. data/app/components/oversee/field_component.rb +36 -0
  11. data/app/components/oversee/field_label_component.rb +155 -0
  12. data/app/components/oversee/fields/display_row_component.rb +58 -0
  13. data/app/components/oversee/fields/input/datetime_component.rb +27 -0
  14. data/app/components/oversee/fields/input/integer_component.rb +26 -0
  15. data/app/components/oversee/fields/input/string_component.rb +26 -0
  16. data/app/components/oversee/fields/value/boolean_component.rb +44 -0
  17. data/app/components/oversee/fields/value/datetime_component.rb +16 -0
  18. data/app/components/oversee/fields/value/enum_component.rb +16 -0
  19. data/app/components/oversee/fields/value/integer_component.rb +16 -0
  20. data/app/components/oversee/fields/value/string_component.rb +23 -0
  21. data/app/components/oversee/fields/value/text_component.rb +16 -0
  22. data/app/controllers/oversee/application_controller.rb +4 -0
  23. data/app/controllers/oversee/base_controller.rb +6 -0
  24. data/app/controllers/oversee/dashboard_controller.rb +21 -0
  25. data/app/controllers/oversee/resources/fields_controller.rb +45 -0
  26. data/app/controllers/oversee/resources_controller.rb +92 -0
  27. data/app/helpers/oversee/application_helper.rb +5 -0
  28. data/app/jobs/oversee/application_job.rb +4 -0
  29. data/app/mailers/oversee/application_mailer.rb +6 -0
  30. data/app/models/oversee/application_record.rb +5 -0
  31. data/app/oversee/cards.rb +2 -0
  32. data/app/views/layouts/oversee/application.html.erb +26 -0
  33. data/app/views/oversee/application/_javascript.html.erb +17 -0
  34. data/app/views/oversee/dashboard/show.html.erb +18 -0
  35. data/app/views/oversee/resources/_form.html.erb +10 -0
  36. data/app/views/oversee/resources/_input_field.html.erb +20 -0
  37. data/app/views/oversee/resources/edit.html.erb +31 -0
  38. data/app/views/oversee/resources/index.html.erb +59 -0
  39. data/app/views/oversee/resources/input_field.html.erb +1 -0
  40. data/app/views/oversee/resources/input_field.turbo_stream.erb +3 -0
  41. data/app/views/oversee/resources/show.html.erb +51 -0
  42. data/app/views/oversee/resources/update.turbo_stream.erb +3 -0
  43. data/app/views/shared/_sidebar.html.erb +32 -0
  44. data/config/routes.rb +10 -0
  45. data/lib/oversee/configuration.rb +27 -0
  46. data/lib/oversee/engine.rb +10 -0
  47. data/lib/oversee/version.rb +3 -0
  48. data/lib/oversee.rb +26 -4
  49. data/lib/tasks/oversee_tasks.rake +4 -0
  50. 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,4 @@
1
+ module Oversee
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Oversee
2
+ class BaseController < ActionController::Base
3
+ include Pagy::Backend
4
+ layout "oversee/application"
5
+ end
6
+ 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,5 @@
1
+ module Oversee
2
+ module ApplicationHelper
3
+ include Pagy::Frontend
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Oversee
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Oversee
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Oversee
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,2 @@
1
+ class Cards
2
+ 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,3 @@
1
+ <%= turbo_stream.replace dom_id(@resource, @key) do %>
2
+ <%= render partial: "oversee/resources/input_field", locals: { datatype: @datatype, key: @key, value: @value } %>
3
+ <% end %>
@@ -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,3 @@
1
+ <%= turbo_stream.replace(dom_id(@resource, @key)) do %>
2
+ <%= render Oversee::Fields::DisplayRowComponent.new(key: @key, resource: @resource, datatype: @datatype, value: @resource.send(@key)) %>
3
+ <% end %>
@@ -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,10 @@
1
+ Oversee::Engine.routes.draw do
2
+
3
+ # Resources
4
+ resources :resources do
5
+ resources :fields, only: [:show, :edit, :update]
6
+ get :input_field, on: :member
7
+ end
8
+
9
+ root to: "dashboard#show"
10
+ end
@@ -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
@@ -0,0 +1,10 @@
1
+ module Oversee
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Oversee
4
+
5
+ config.to_prepare do
6
+ # Eager-loading to fetch all resources
7
+ Rails.application.eager_load!
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module Oversee
2
+ VERSION = "0.1.0"
3
+ end
data/lib/oversee.rb CHANGED
@@ -1,5 +1,27 @@
1
- class Oversee
2
- def self.lulwut
3
- "LULWUT"
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
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :oversee do
3
+ # # Task goes here
4
+ # end