oversee 0.0.1 → 0.1.1

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