oversee 0.0.1 → 0.1.1

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 (52) 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 +39 -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/boolean_component.rb +26 -0
  14. data/app/components/oversee/fields/input/datetime_component.rb +27 -0
  15. data/app/components/oversee/fields/input/integer_component.rb +26 -0
  16. data/app/components/oversee/fields/input/string_component.rb +26 -0
  17. data/app/components/oversee/fields/value/boolean_component.rb +44 -0
  18. data/app/components/oversee/fields/value/datetime_component.rb +16 -0
  19. data/app/components/oversee/fields/value/enum_component.rb +16 -0
  20. data/app/components/oversee/fields/value/integer_component.rb +16 -0
  21. data/app/components/oversee/fields/value/string_component.rb +23 -0
  22. data/app/components/oversee/fields/value/text_component.rb +16 -0
  23. data/app/controllers/oversee/application_controller.rb +4 -0
  24. data/app/controllers/oversee/base_controller.rb +6 -0
  25. data/app/controllers/oversee/dashboard_controller.rb +21 -0
  26. data/app/controllers/oversee/resources/fields_controller.rb +45 -0
  27. data/app/controllers/oversee/resources_controller.rb +111 -0
  28. data/app/helpers/oversee/application_helper.rb +5 -0
  29. data/app/jobs/oversee/application_job.rb +4 -0
  30. data/app/mailers/oversee/application_mailer.rb +6 -0
  31. data/app/models/oversee/application_record.rb +5 -0
  32. data/app/oversee/cards.rb +2 -0
  33. data/app/oversee/resource.rb +10 -0
  34. data/app/views/layouts/oversee/application.html.erb +26 -0
  35. data/app/views/oversee/application/_javascript.html.erb +17 -0
  36. data/app/views/oversee/dashboard/show.html.erb +18 -0
  37. data/app/views/oversee/resources/_form.html.erb +10 -0
  38. data/app/views/oversee/resources/_input_field.html.erb +20 -0
  39. data/app/views/oversee/resources/edit.html.erb +30 -0
  40. data/app/views/oversee/resources/index.html.erb +59 -0
  41. data/app/views/oversee/resources/input_field.html.erb +1 -0
  42. data/app/views/oversee/resources/new.html.erb +28 -0
  43. data/app/views/oversee/resources/show.html.erb +51 -0
  44. data/app/views/oversee/resources/update.turbo_stream.erb +3 -0
  45. data/app/views/shared/_sidebar.html.erb +32 -0
  46. data/config/routes.rb +16 -0
  47. data/lib/oversee/configuration.rb +27 -0
  48. data/lib/oversee/engine.rb +10 -0
  49. data/lib/oversee/version.rb +3 -0
  50. data/lib/oversee.rb +31 -4
  51. data/lib/tasks/oversee_tasks.rake +4 -0
  52. metadata +126 -5
@@ -0,0 +1,16 @@
1
+ module Oversee
2
+ module Fields
3
+ module Value
4
+ class EnumComponent < Phlex::HTML
5
+ def initialize(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 { @value.to_s }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Oversee
2
+ module Fields
3
+ module Value
4
+ class IntegerComponent < Phlex::HTML
5
+ def initialize(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 { @value }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -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,111 @@
1
+ module Oversee
2
+ class ResourcesController < BaseController
3
+ include ActionView::RecordIdentifier
4
+
5
+ before_action :set_resource_class, except: [:create, :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 new
16
+ @resource = @resource_class.new
17
+ end
18
+
19
+ def create
20
+ @resource_class = params[:resource_class].constantize
21
+ @resource = @resource_class.new(resource_params)
22
+
23
+ respond_to do |format|
24
+ if @resource.update(resource_params)
25
+ format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
26
+ format.turbo_stream
27
+ else
28
+ end
29
+ end
30
+ end
31
+
32
+ def show
33
+ resource_associations
34
+ end
35
+
36
+ def edit
37
+ end
38
+
39
+ def update
40
+ @resource_class = params[:resource_class].constantize
41
+ set_resource
42
+
43
+ @key = params[:resource][:oversee_key]
44
+ @datatype = params[:resource][:oversee_datatype]
45
+
46
+ respond_to do |format|
47
+ if @resource.update(resource_params)
48
+ format.html { redirect_to resource_path(@resource.id, resource: @resource_class) }
49
+ format.turbo_stream
50
+ else
51
+ end
52
+ end
53
+ end
54
+
55
+ def destroy
56
+ @resource.destroy
57
+ redirect_to resources_path(resource: @resource_class)
58
+ end
59
+
60
+ # Non-standard actions
61
+ def input_field
62
+ set_resource
63
+
64
+ @key = params[:key].to_sym
65
+ @value = @resource.send(@key)
66
+ @datatype = @resource.class.columns_hash[@key.to_s].type
67
+
68
+ respond_to do |format|
69
+ format.turbo_stream do
70
+ render turbo_stream: [
71
+ turbo_stream.replace(dom_id(@resource, @key), partial: "oversee/resources/input_field", locals: { datatype: @datatype, key: @key, value: @value })
72
+ ]
73
+ end
74
+ end
75
+ end
76
+
77
+ private
78
+
79
+ def set_resource_class
80
+ @resource_class = params[:resource].constantize
81
+ end
82
+
83
+ def set_resource
84
+ @resource = @resource_class.find(params[:id])
85
+ end
86
+
87
+ def resource_associations
88
+ @resource_associations ||= @resource_class.reflect_on_all_associations
89
+ end
90
+
91
+ def sort_attribute
92
+ @sort_attribute ||= @resource_class.column_names.include?(params[:sort_attribute]) ? params[:sort_attribute] : "created_at"
93
+ end
94
+
95
+ def sort_direction
96
+ @sort_direction ||= ["asc", "desc"].include?(params[:sort_direction]) ? params[:sort_direction] : "desc"
97
+ end
98
+
99
+ def set_sorting_rules
100
+ @sort_attribute = params[:sort_attribute] || "created_at"
101
+ @sort_direction = params[:sort_direction] || "desc"
102
+ end
103
+
104
+ def resource_params
105
+ params[:resource].delete(:oversee_key)
106
+ params[:resource].delete(:oversee_datatype)
107
+
108
+ params.require(:resource).permit!
109
+ end
110
+ end
111
+ 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,10 @@
1
+ class Resource
2
+ def initialize(resource)
3
+ @resource = resource
4
+ end
5
+
6
+ def columns_for_create
7
+ excluded_columns = [@resource.class.primary_key, "created_at", "updated_at"]
8
+ @resource.columns_hash.except(*excluded_columns)
9
+ end
10
+ 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 | <%= Oversee.application_name %></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,30 @@
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
+ <%= 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" %>
8
+ </div>
9
+ </div>
10
+ <div class="p-8">
11
+ <%= render "form" %>
12
+ </div>
13
+ <div class="p-8">
14
+ <div class="bg-white px-8 border shadow-sm rounded">
15
+ <div class="divide-y divide-gray-100 -mx-8">
16
+ <% @resource.attributes.each do |attribute, value| %>
17
+ <div class="py-4 px-8">
18
+ <p class="text-xs font-medium text-gray-500 uppercase"><%= attribute.humanize %></p>
19
+ <p class="mt-1 text-lg"><%= render Oversee::FieldComponent.new(datatype: :string, value: value) %></p>
20
+ </div>
21
+ <% end %>
22
+ </div>
23
+ </div>
24
+ </div>
25
+
26
+ <div class="p-8">
27
+ <pre>
28
+ <%= debug @resource.attributes %>
29
+ </pre>
30
+ </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', new_resource_path(params[:resource]), 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,28 @@
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">New</p>
5
+ <h1 class="text-xl"><%= params[:resource] %></h1>
6
+ </div>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="pb-8 px-8">
11
+ <%= form_with model: @resource, scope: :resource, url: create_resource_path(resource_class: @resource.class), scope: :resource do |f| %>
12
+ <div class="border-y -mx-8 py-8">
13
+ <% @resource_class.columns_hash.each do |key, metadata| %>
14
+ <% next if [@resource_class.primary_key, "created_at", "updated_at"].include?(key) %>
15
+ <div class="px-8 py-2">
16
+ <%= render Oversee::FieldLabelComponent.new(key: key, datatype: metadata.sql_type_metadata.type) %>
17
+ <div class="mt-2 w-2/3">
18
+ <%= render Oversee::FieldComponent.new(kind: :input, datatype: metadata.sql_type_metadata.type, key: key)%>
19
+ </div>
20
+ </div>
21
+ <%#= render Oversee::Fields::DisplayRowComponent.new(key: key, resource: @resource, datatype: metadata.sql_type_metadata.type, value: @resource.send(key)) %>
22
+ <% end %>
23
+ </div>
24
+ <div class="flex justify-end mt-8">
25
+ <%= f.submit "Save", class: "bg-blue-500 px-4 py-2 rounded-md text-white text-sm hover:bg-blue-600 hover:cursor-pointer" %>
26
+ </div>
27
+ <% end %>
28
+ </div>
@@ -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">Attributes</h3>
17
+ <div class="border-y divide-y -mx-8">
18
+ <% @resource_class.columns_hash.each do |key, metadata| %>
19
+ <%= render Oversee::Fields::DisplayRowComponent.new(key: key, resource: @resource, datatype: metadata.sql_type_metadata.type, value: @resource.send(key)) %>
20
+ <% end %>
21
+ </div>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="pb-8">
26
+ <div class="px-8">
27
+ <h3 class="text-xl mb-8">Associations</h3>
28
+ <div class="border-y divide-y -mx-8">
29
+ <% @resource_associations.each do |association| %>
30
+ <% associated_resources = @resource.send(association.name.to_sym) %>
31
+ <% next if associated_resources.blank? %>
32
+ <% associated_resources = [associated_resources] unless associated_resources.respond_to?(:each) %>
33
+
34
+ <div class="px-8 py-6">
35
+ <div class="inline-flex items-center gap-2 mb-4">
36
+ <div class="inline-flex items-center justify-center h-6 w-6 bg-gray-50">
37
+ <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>
38
+ </div>
39
+ <%= association.name.to_s.titleize %>
40
+ </div>
41
+
42
+ <div class="flex gap-2 flex-wrap">
43
+ <% associated_resources.each do |ar| %>
44
+ <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>
45
+ <% end %>
46
+ </div>
47
+ </div>
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,16 @@
1
+ Oversee::Engine.routes.draw do
2
+
3
+ # Resources
4
+ scope :resources, controller: "resources" do
5
+ get ":resource/", action: :index, as: :resources
6
+ get ":resource/new", action: :new, as: :new_resource
7
+ post ":resource_class/", action: :create, as: :create_resource
8
+ get ":resource/:id", action: :show, as: :resource
9
+ get ":resource/:id/edit", action: :edit, as: :edit_resource
10
+ patch ":resource/:id", action: :update, as: :update_resource
11
+ delete ":resource/:id", action: :destroy, as: :destroy_resource
12
+ get ":resource/:id/input_field", action: :input_field, as: :input_field
13
+ end
14
+
15
+ root to: "dashboard#show"
16
+ end