oversee 0.0.1 → 0.1.0

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