oversee 0.1.1 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +53 -14
- data/app/assets/config/oversee_manifest.js +2 -1
- data/app/components/oversee/{application_component.rb → base.rb} +1 -1
- data/app/components/oversee/card.rb +13 -0
- data/app/components/oversee/dashboard/filters.rb +58 -0
- data/app/components/oversee/dashboard/header.rb +57 -0
- data/app/components/oversee/dashboard/index.rb +36 -0
- data/app/components/oversee/dashboard/javascript.rb +9 -0
- data/app/components/oversee/dashboard/pagination.rb +23 -0
- data/app/components/oversee/dashboard/sidebar.rb +90 -0
- data/app/components/oversee/dashboard/tailwind.rb +62 -0
- data/app/components/oversee/field/display.rb +38 -0
- data/app/components/oversee/field/form.rb +30 -0
- data/app/components/oversee/field/input/belongs_to.rb +22 -0
- data/app/components/oversee/field/input/boolean.rb +13 -0
- data/app/components/oversee/field/input/datetime.rb +10 -0
- data/app/components/oversee/field/input/integer.rb +10 -0
- data/app/components/oversee/field/input/rich_text.rb +18 -0
- data/app/components/oversee/field/input/string.rb +10 -0
- data/app/components/oversee/field/input.rb +34 -0
- data/app/components/oversee/field/label.rb +25 -0
- data/app/components/oversee/field/value/belongs_to.rb +21 -0
- data/app/components/oversee/field/value/boolean.rb +50 -0
- data/app/components/oversee/field/value/datetime.rb +9 -0
- data/app/components/oversee/field/value/enum.rb +9 -0
- data/app/components/oversee/field/value/integer.rb +9 -0
- data/app/components/oversee/field/value/rich_text.rb +9 -0
- data/app/components/oversee/field/value/string.rb +20 -0
- data/app/components/oversee/field/value/text.rb +9 -0
- data/app/components/oversee/field/value.rb +40 -0
- data/app/components/oversee/layout/application.rb +46 -0
- data/app/components/oversee/resources/base.rb +4 -0
- data/app/components/oversee/resources/errors.rb +41 -0
- data/app/components/oversee/resources/form.rb +49 -0
- data/app/components/oversee/resources/index.rb +45 -0
- data/app/components/oversee/resources/new.rb +21 -0
- data/app/components/oversee/resources/show.rb +207 -0
- data/app/components/oversee/resources/table.rb +115 -0
- data/app/components/oversee/table/body.rb +13 -0
- data/app/components/oversee/table/data.rb +5 -0
- data/app/components/oversee/table/head.rb +9 -0
- data/app/components/oversee/table/row.rb +9 -0
- data/app/components/oversee/table.rb +21 -0
- data/app/controllers/oversee/dashboard_controller.rb +4 -0
- data/app/controllers/oversee/resources/fields_controller.rb +20 -36
- data/app/controllers/oversee/resources_controller.rb +65 -26
- data/app/javascript/oversee/application.js +3 -0
- data/app/javascript/oversee/controllers/index.js +8 -0
- data/app/javascript/oversee/controllers/reveal_controller.js +7 -0
- data/app/javascript/oversee/controllers/sidebar/state_controller.js +21 -0
- data/app/models/oversee/resource.rb +62 -0
- data/app/service/oversee/filter.rb +39 -0
- data/app/service/oversee/search.rb +34 -0
- data/app/views/oversee/base.rb +5 -0
- data/config/importmap.rb +14 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/oversee.en.yml +4 -0
- data/config/routes.rb +17 -11
- data/lib/generators/oversee/install_generator.rb +7 -0
- data/lib/oversee/configuration.rb +3 -0
- data/lib/oversee/engine.rb +19 -0
- data/lib/oversee/version.rb +1 -1
- data/lib/oversee.rb +26 -2
- data/lib/tasks/oversee_tasks.rake +6 -4
- metadata +95 -48
- data/app/assets/stylesheets/oversee/application.css +0 -15
- data/app/components/oversee/card_component.rb +0 -15
- data/app/components/oversee/dashboard/sidebar_component.rb +0 -127
- data/app/components/oversee/field_component.rb +0 -39
- data/app/components/oversee/field_label_component.rb +0 -155
- data/app/components/oversee/fields/display_row_component.rb +0 -58
- data/app/components/oversee/fields/input/boolean_component.rb +0 -26
- data/app/components/oversee/fields/input/datetime_component.rb +0 -27
- data/app/components/oversee/fields/input/integer_component.rb +0 -26
- data/app/components/oversee/fields/input/string_component.rb +0 -26
- data/app/components/oversee/fields/value/boolean_component.rb +0 -44
- data/app/components/oversee/fields/value/datetime_component.rb +0 -16
- data/app/components/oversee/fields/value/enum_component.rb +0 -16
- data/app/components/oversee/fields/value/integer_component.rb +0 -16
- data/app/components/oversee/fields/value/string_component.rb +0 -23
- data/app/components/oversee/fields/value/text_component.rb +0 -16
- data/app/helpers/oversee/application_helper.rb +0 -5
- data/app/jobs/oversee/application_job.rb +0 -4
- data/app/mailers/oversee/application_mailer.rb +0 -6
- data/app/oversee/cards.rb +0 -2
- data/app/oversee/resource.rb +0 -10
- data/app/views/layouts/oversee/application.html.erb +0 -26
- data/app/views/oversee/application/_javascript.html.erb +0 -17
- data/app/views/oversee/dashboard/show.html.erb +0 -18
- data/app/views/oversee/resources/_form.html.erb +0 -10
- data/app/views/oversee/resources/_input_field.html.erb +0 -20
- data/app/views/oversee/resources/edit.html.erb +0 -30
- data/app/views/oversee/resources/index.html.erb +0 -59
- data/app/views/oversee/resources/input_field.html.erb +0 -1
- data/app/views/oversee/resources/new.html.erb +0 -28
- data/app/views/oversee/resources/show.html.erb +0 -51
- data/app/views/oversee/resources/update.turbo_stream.erb +0 -3
- data/app/views/shared/_sidebar.html.erb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c31a8c277449dfaec238f66cfc8e404a64c51dff4beef1a944fee85c5935991c
|
4
|
+
data.tar.gz: f3fcd62d4ff3184f5b9f97273c2269e986b5f03736a9961440aca242b38a1292
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ad42840ab6b72667e5e16853cea4d9028fc1356fd4ff2ec7d3909b6102c2fc80dca9287245ad4ab62021181c238beae371f77b473f1f9464bf0b1162a29946c5
|
7
|
+
data.tar.gz: 229f928f44cfe6d6535cd8ef6682472a9f5ecf59e29bc65714ed71e1c6147ab48b582e5c065fc1e9233d5e536155e16d8750a1fc92f3e74bd97e3720cbc58e42
|
data/README.md
CHANGED
@@ -1,40 +1,79 @@
|
|
1
|
-
[](https://badge.fury.io/rb/oversee)
|
2
1
|
|
3
|
-
#
|
2
|
+
# Oversee
|
3
|
+
|
4
4
|
Plug & play admin dashboard for Rails applications.
|
5
5
|
|
6
|
+
|
7
|
+
[](https://badge.fury.io/rb/oversee)
|
8
|
+
|
9
|
+
---
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
- Minimal, if any, configuration needed
|
14
|
+
- Tailored and pleasant user interface
|
15
|
+
- Geared for performance
|
16
|
+
|
17
|
+
---
|
18
|
+
|
19
|
+

|
20
|
+
|
6
21
|
Developed by [Primevise](https://primevise.com)
|
7
22
|
|
8
|
-
> [!NOTE]
|
23
|
+
> [!NOTE]
|
9
24
|
> Oversee is still rather incomplete and only has the very basic features. It might significantly change and break things before a stable release.
|
10
25
|
|
11
26
|
## Installation
|
12
|
-
|
27
|
+
|
28
|
+
#### Add gem
|
29
|
+
|
30
|
+
Simply add the gem to your Gemfile by running the following command
|
13
31
|
|
14
32
|
```bash
|
15
33
|
$ bundle add oversee
|
16
34
|
```
|
17
35
|
|
36
|
+
> [!TIP]
|
37
|
+
> Currently, we don't release new gem versions too often due to the fragile nature of the gem. However, you can use the edge version of the gem by pointing directly to the git repository.
|
38
|
+
> ```ruby
|
39
|
+
> gem "oversee", git: "https://github.com/primevise/oversee", branch: :main
|
40
|
+
> ```
|
18
41
|
|
19
|
-
|
42
|
+
---
|
20
43
|
|
21
|
-
|
22
|
-
gem "oversee"
|
23
|
-
```
|
44
|
+
#### Mount it on your application
|
24
45
|
|
25
|
-
|
26
|
-
|
27
|
-
|
46
|
+
After you have the gem installed, you can then mount the engine in your `config/routes.rb`:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
Rails.application.routes.draw do
|
50
|
+
mount Oversee::Engine => "/oversee"
|
51
|
+
end
|
28
52
|
```
|
29
53
|
|
30
|
-
|
31
|
-
TBA
|
54
|
+
Ideally, you'd want to limit access to the dashboard to only authorized users.
|
32
55
|
|
33
56
|
## Notable mentions
|
34
|
-
|
57
|
+
|
58
|
+
This gem would not be possible without the work of others. A big thank you goes to these projects:
|
35
59
|
|
36
60
|
- [madmin](https://github.com/excid3/madmin)
|
37
61
|
- [Avo](https://github.com/avo-hq/avo)
|
62
|
+
- [Iconoir](https://github.com/iconoir-icons/iconoir)
|
63
|
+
|
64
|
+
## Who uses Oversee?
|
65
|
+
|
66
|
+
- [Mintis](https://mintis.app)
|
67
|
+
- [No Logo X](https://nologox.com)
|
68
|
+
- [Release Server](https://releaseserver.com)
|
69
|
+
- [College Life Work](https://work.collegelife.co)
|
70
|
+
|
71
|
+
Do you use Oversee in your project? Let us know!
|
72
|
+
|
73
|
+
## Contributing
|
74
|
+
|
75
|
+
TBA
|
38
76
|
|
39
77
|
## License
|
78
|
+
|
40
79
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -1 +1,2 @@
|
|
1
|
-
//=
|
1
|
+
//= link oversee/application.js
|
2
|
+
//= link_tree ../../javascript/oversee/controllers .js
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Oversee::Card < Phlex::HTML
|
2
|
+
def initialize(card_name:)
|
3
|
+
@card_name = card_name
|
4
|
+
@card = card_name.constantize.new
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
div(class: "bg-gray-100 rounded-md p-4") do
|
9
|
+
p(class: "text-xs uppercase text-gray-600") { @card.label }
|
10
|
+
h4(class: "mt-2 text-gray-900 text-2xl") { @card.value }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Oversee::Dashboard::Filters < Oversee::Base
|
2
|
+
def initialize(params: nil)
|
3
|
+
|
4
|
+
@params = params
|
5
|
+
|
6
|
+
puts "params: #{params}"
|
7
|
+
puts "sortless_path: #{sortless_path}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template(&)
|
11
|
+
div(class: "flex items-center justify-between") do
|
12
|
+
div(class: "flex items-center gap-2") do
|
13
|
+
if show_action_section?
|
14
|
+
button(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
|
15
|
+
render Phlex::Icons::Iconoir::FilterAlt.new(class: "size-3")
|
16
|
+
plain "Filters"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
if false
|
20
|
+
a(class:"rounded-full bg-gray-100 inline-flex gap-2 items-center text-xs px-4 py-2 font-medium hover:bg-gray-200") do
|
21
|
+
render Phlex::Icons::Iconoir::XMark.new(class: "size-4 text-gray-500")
|
22
|
+
plain "Clear sorting"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
div(class: "flex items-center gap-4") do
|
27
|
+
form(action: "", class: "flex items-center gap-2") do
|
28
|
+
input(
|
29
|
+
type: :search,
|
30
|
+
name: :query,
|
31
|
+
value: @params[:query],
|
32
|
+
placeholder: search_placeholder,
|
33
|
+
class: "flex bg-gray-100 min-w-64 w-64 focus:w-96 transition-all h-10 items-center pl-4 py-2 placeholder:text-gray-500 rounded-sm text-sm"
|
34
|
+
)
|
35
|
+
button(class: "size-10 inline-flex items-center justify-center bg-gray-100 hover:bg-gray-200 transition-colors") { render Phlex::Icons::Iconoir::Search.new(class: "size-4 text-gray-600") }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def show_action_section?
|
44
|
+
Rails.env.development? || @params[:experimental] == "true"
|
45
|
+
end
|
46
|
+
|
47
|
+
def sortless_path
|
48
|
+
sortless_query_params = @params.except(:sort_attribute, :sort_direction, :controller, :action)
|
49
|
+
# helpers.resources_path(sortless_query_params)
|
50
|
+
end
|
51
|
+
|
52
|
+
def search_placeholder
|
53
|
+
context = Oversee::Search.new(collection: nil, resource_class: @params[:resource_class_name].constantize)
|
54
|
+
attr = context.default_searchable_attribute
|
55
|
+
|
56
|
+
"Search by #{attr}"
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Oversee::Dashboard::Header < Oversee::Base
|
2
|
+
attr_reader :title
|
3
|
+
attr_reader :subtitle
|
4
|
+
attr_reader :return_path
|
5
|
+
attr_reader :show_back_button
|
6
|
+
|
7
|
+
def initialize(title: nil, subtitle: nil, return_path: nil, show_back_button: true)
|
8
|
+
@title = title || "Dashboard"
|
9
|
+
@subtitle = subtitle || "Manage your account"
|
10
|
+
@return_path = return_path
|
11
|
+
@show_back_button = show_back_button
|
12
|
+
end
|
13
|
+
|
14
|
+
def view_template(&)
|
15
|
+
|
16
|
+
div(class: "min-h-10 flex items-center justify-between") do
|
17
|
+
if block_given?
|
18
|
+
yield(self)
|
19
|
+
else
|
20
|
+
left
|
21
|
+
right
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def left(&)
|
27
|
+
div(class: "flex items-center gap-2") do
|
28
|
+
back_button if show_back_button
|
29
|
+
h3(class: "text-lg font-medium text-gray-900") { title } if title.present?
|
30
|
+
yield if block_given?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def right(&)
|
35
|
+
div(class: "flex items-center gap-4", &)
|
36
|
+
end
|
37
|
+
|
38
|
+
def separator
|
39
|
+
render Phlex::Icons::Iconoir::Slash.new(class: "size-4 text-gray-300", stroke_width: 1.75)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def back_button
|
45
|
+
a(
|
46
|
+
href: return_button_path,
|
47
|
+
class: "mr-2 size-8 inline-flex items-center justify-center bg-gray-50 text-gray-500 hover:text-gray-900 hover:bg-gray-200 transition-colors",
|
48
|
+
data: { controller: "back", action: "back#navigate", turbo_action: "replace" }
|
49
|
+
) do
|
50
|
+
render Phlex::Icons::Iconoir::ArrowLeft.new(class: "size-3", stroke_width: 2.5)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def return_button_path
|
55
|
+
@return_path || root_path
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Oversee::Dashboard::Index < Oversee::Base
|
4
|
+
|
5
|
+
def around_template
|
6
|
+
render Oversee::Layout::Application.new { super }
|
7
|
+
end
|
8
|
+
|
9
|
+
def view_template
|
10
|
+
div(class: "flex items-center justify-between") do
|
11
|
+
h1(class: "text-lg font-medium text-gray-900") { "Dashboard" }
|
12
|
+
h1(class: "text-sm font-medium text-gray-500") { Date.current.to_fs(:long) }
|
13
|
+
end
|
14
|
+
|
15
|
+
if Oversee.card_class_names.present?
|
16
|
+
div(class: "grid grid-cols-4 gap-4") do
|
17
|
+
Oversee.card_class_names.each do |card_name|
|
18
|
+
render Oversee::Card.new(card_name: card_name)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
hr(class: "my-4")
|
24
|
+
|
25
|
+
div(class: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4") do
|
26
|
+
Oversee.application_resource_names.sort.each do |resource_class_name|
|
27
|
+
a(href: helpers.resources_path(resource_class_name:), class: "w-full bg-gray-100/75 block hover:bg-gray-50 p-4 truncate") do
|
28
|
+
div(class: "flex items-center justify-center size-8 bg-white") do
|
29
|
+
render Phlex::Icons::Iconoir::Folder.new(class: "size-4 text-gray-400")
|
30
|
+
end
|
31
|
+
p(class: "mt-4 font-medium text-gray-700") { resource_class_name }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Oversee::Dashboard::Javascript < Phlex::HTML
|
2
|
+
include Phlex::Rails::Helpers::JavascriptImportmapTags
|
3
|
+
|
4
|
+
def view_template
|
5
|
+
script(async: true, src:"https://ga.jspm.io/npm:es-module-shims@1.8.2/dist/es-module-shims.js")
|
6
|
+
javascript_importmap_tags("application", importmap: Oversee.importmap)
|
7
|
+
script(type: "module") { raw %(import * as Turbo from "@hotwired/turbo-rails").html_safe }
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Oversee::Dashboard::Pagination < Oversee::Base
|
3
|
+
include Pagy::Frontend
|
4
|
+
include Phlex::Rails::Helpers::Request
|
5
|
+
|
6
|
+
def initialize(pagy:, params:)
|
7
|
+
@pagy = pagy
|
8
|
+
@params = params
|
9
|
+
end
|
10
|
+
|
11
|
+
def view_template
|
12
|
+
div(class:"mt-4 flex items-center justify-between") do
|
13
|
+
|
14
|
+
div(class: "font-regular text-xs") do
|
15
|
+
raw pagy_info(@pagy).html_safe
|
16
|
+
end
|
17
|
+
|
18
|
+
div(class: "flex items-center gap-4") do
|
19
|
+
raw pagy_nav(@pagy).html_safe
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Oversee::Dashboard::Sidebar < Oversee::Base
|
4
|
+
def view_template
|
5
|
+
div(class: "bg-white p-4 rounded-lg") do
|
6
|
+
button(class: "mb-4 ") { collapse_icon } unless true
|
7
|
+
|
8
|
+
p(class: "text-[0.7rem] uppercase text-gray-400 font-medium mb-2") { "Menu" }
|
9
|
+
ul(class: "text-sm text-gray-700") do
|
10
|
+
li do
|
11
|
+
a(href: root_path, class:"flex items-center gap-2 hover:bg-gray-50 p-2") do
|
12
|
+
render Phlex::Icons::Iconoir::LayoutLeft.new(class: "size-4 text-gray-400")
|
13
|
+
span { "Dashboard" }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
li do
|
17
|
+
a(href: helpers.main_app.root_path, class: "flex items-center gap-2 hover:bg-gray-50 p-2 rounded-lg") do
|
18
|
+
render Phlex::Icons::Iconoir::HomeAltSlimHoriz.new(class: "size-4 text-gray-400")
|
19
|
+
span { "Return to app" }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
if Rails.env.development?
|
25
|
+
hr(class: "my-4 -mx-4")
|
26
|
+
details(
|
27
|
+
id: "links-menu",
|
28
|
+
class: "group",
|
29
|
+
# open: true,
|
30
|
+
data: {
|
31
|
+
controller: "sidebar--state",
|
32
|
+
action: "sidebar--state#persist",
|
33
|
+
sidebar__state_target: "expandable"
|
34
|
+
}
|
35
|
+
) do
|
36
|
+
summary(class: "flex items-center justify-between cursor-pointer") do
|
37
|
+
p(class: "text-[0.7rem] uppercase text-gray-400 font-medium") { "Links" }
|
38
|
+
render Phlex::Icons::Iconoir::NavArrowDown.new(class: "size-4 text-gray-400 transform transition-transform group-open:rotate-180 group-hover:text-blue-500")
|
39
|
+
end
|
40
|
+
ul(class: "mt-2 text-sm text-gray-700 overflow-x-hidden") do
|
41
|
+
li do
|
42
|
+
a(href: "https://github.com/primevise/oversee", target: "_blank", class: "flex items-center gap-2 hover:bg-gray-50 p-2 truncate") do
|
43
|
+
render Phlex::Icons::Iconoir::ArrowUpRightSquare.new(class: "size-4 text-gray-400")
|
44
|
+
span { "Repository" }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
hr(class: "my-4 -mx-4")
|
52
|
+
|
53
|
+
details(
|
54
|
+
id: "resources-menu",
|
55
|
+
class: "group",
|
56
|
+
# open: true,
|
57
|
+
data: {
|
58
|
+
controller: "sidebar--state",
|
59
|
+
action: "sidebar--state#persist",
|
60
|
+
sidebar__state_target: "expandable"
|
61
|
+
}
|
62
|
+
) do
|
63
|
+
summary(class: "flex items-center justify-between cursor-pointer") do
|
64
|
+
p(class: "text-[0.7rem] uppercase text-gray-400 font-medium") { "Resources" }
|
65
|
+
render Phlex::Icons::Iconoir::NavArrowDown.new(class: "size-4 text-gray-400 transform transition-transform group-open:rotate-180 group-hover:text-blue-500")
|
66
|
+
end
|
67
|
+
ul(class: "mt-2 text-sm text-gray-700 overflow-x-hidden") do
|
68
|
+
Oversee.application_resource_names.sort.each do |resource_class_name|
|
69
|
+
li do
|
70
|
+
a(href: helpers.resources_path(resource_class_name:), class: "flex items-center gap-2 hover:bg-gray-50 p-2 truncate") do
|
71
|
+
render Phlex::Icons::Iconoir::Folder.new(class: "size-4 text-gray-400")
|
72
|
+
span { resource_class_name }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
hr(class: "my-4 -mx-4")
|
80
|
+
p(class: "text-xs text-gray-500") do
|
81
|
+
plain("Powered by ")
|
82
|
+
a(
|
83
|
+
href: "https://github.com/primevise/oversee",
|
84
|
+
class: "text-blue-500 hover:underline",
|
85
|
+
target: "_blank"
|
86
|
+
) { "Oversee" }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Oversee::Dashboard::Tailwind < Phlex::HTML
|
2
|
+
def view_template
|
3
|
+
script(src: "https://cdn.tailwindcss.com/3.4.15?plugins=typography@0.5.15")
|
4
|
+
style(type:"text/tailwindcss") do
|
5
|
+
raw pagy_css.html_safe
|
6
|
+
raw trix_css.html_safe
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def trix_css
|
11
|
+
<<~CSS
|
12
|
+
trix-editor {
|
13
|
+
@apply rounded-b rounded-t-none border border-t-0 border-gray-200 !ring-0;
|
14
|
+
}
|
15
|
+
|
16
|
+
.trix-active {
|
17
|
+
@apply !bg-gray-100 !text-blue-500;
|
18
|
+
}
|
19
|
+
|
20
|
+
.trix-button-row {
|
21
|
+
@apply overflow-hidden rounded-t border;
|
22
|
+
}
|
23
|
+
|
24
|
+
.trix-button {
|
25
|
+
@apply !relative !inline-flex !h-7 !items-center !justify-center border-0 !border-b-0 !border-l-0 !px-7 !py-4;
|
26
|
+
}
|
27
|
+
|
28
|
+
.trix-button::before {
|
29
|
+
@apply !top-1.5 !h-5;
|
30
|
+
}
|
31
|
+
|
32
|
+
.trix-button-group {
|
33
|
+
@apply !mb-0 rounded-b-none !border-0 border-gray-200;
|
34
|
+
}
|
35
|
+
CSS
|
36
|
+
end
|
37
|
+
|
38
|
+
def pagy_css
|
39
|
+
<<~CSS
|
40
|
+
.pagy {
|
41
|
+
@apply flex gap-2 items-center text-sm text-gray-500;
|
42
|
+
a:not(.gap) {
|
43
|
+
@apply inline-flex items-center justify-center h-8 min-w-8 font-normal;
|
44
|
+
&:hover { @apply bg-gray-100; }
|
45
|
+
&:not([href]) { /* disabled links */
|
46
|
+
@apply text-gray-300 cursor-not-allowed;
|
47
|
+
}
|
48
|
+
&.current {
|
49
|
+
@apply font-semibold bg-gray-100 text-gray-900;
|
50
|
+
}
|
51
|
+
}
|
52
|
+
|
53
|
+
label {
|
54
|
+
@apply inline-block whitespace-nowrap bg-gray-200 px-3 py-0.5;
|
55
|
+
input {
|
56
|
+
@apply bg-gray-100 border-none rounded-md;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
}
|
60
|
+
CSS
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class Oversee::Field::Display < Oversee::Base
|
2
|
+
|
3
|
+
attr_reader :resource
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :value
|
6
|
+
attr_reader :datatype
|
7
|
+
|
8
|
+
def initialize(resource:, key: nil, value: nil, datatype: :string, **options)
|
9
|
+
@resource = resource
|
10
|
+
@key = key
|
11
|
+
@value = value
|
12
|
+
@datatype = datatype
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def view_template
|
17
|
+
html_tag(
|
18
|
+
id: dom_id(resource, key),
|
19
|
+
href: helpers.resource_input_path(resource_class_name:, key:, datatype:),
|
20
|
+
class: "bg-gray-100 h-10 flex items-center px-4 py-2 hover:bg-gray-200 transition-colors w-full cursor-pointer",
|
21
|
+
data: { turbo_stream: true }
|
22
|
+
) do
|
23
|
+
render Oversee::Field::Value.new(key:, value:, datatype:, **@options)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def resource_class_name
|
30
|
+
@resource_class_name ||= @resource.class.name
|
31
|
+
end
|
32
|
+
|
33
|
+
def html_tag(...)
|
34
|
+
edittable? ? a(...) : div(...)
|
35
|
+
end
|
36
|
+
|
37
|
+
def edittable? = @options[:edittable] || true
|
38
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Oversee::Field::Form < Oversee::Base
|
2
|
+
include Phlex::Rails::Helpers::FormWith
|
3
|
+
|
4
|
+
def initialize(resource:, key: nil, value: nil, datatype: :string, method: :patch, **options)
|
5
|
+
@resource = resource
|
6
|
+
@key = key
|
7
|
+
@value = value
|
8
|
+
@datatype = datatype
|
9
|
+
@method = method
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def view_template
|
14
|
+
form_with(id: dom_id(@resource, @key), model: @resource, url: helpers.update_resource_path(resource_class_name: resource_class_name, id: @resource.id), method: @method, class: "flex items-center w-full gap-4") do |form|
|
15
|
+
input type: :hidden, id: "oversee_key", name: "oversee_key", value: @key.to_s
|
16
|
+
input type: :hidden, id: "oversee_datatype", name: "oversee_datatype", value: @datatype
|
17
|
+
render Oversee::Field::Input.new(key: @key, value: @value, datatype: @datatype, **@options)
|
18
|
+
button(class:"h-9 bg-gray-900 hover:bg-gray-700 text-white inline-flex items-center cursor-pointer gap-2 px-4 rounded-full text-xs font-medium") {
|
19
|
+
render Phlex::Icons::Iconoir::Check.new(class: "size-4")
|
20
|
+
plain "Save"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def resource_class_name
|
28
|
+
@resource.class.name
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Oversee::Field::Input::BelongsTo < Oversee::Field::Input
|
2
|
+
def initialize(key:, value:, **options)
|
3
|
+
@key = key
|
4
|
+
@value = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
input type: "text", id: field_id, name: field_name, value: @value, class: "flex border px-4 py-2 text-sm w-full rounded-sm"
|
9
|
+
|
10
|
+
# select(id: field_id, name: field_name, class: "flex w-full border rounded-sm px-4 py-2 text-sm") do
|
11
|
+
|
12
|
+
# option(value: 1, selected: @value) { "True" }
|
13
|
+
# option(value: 0, selected: !@value) { "False" }
|
14
|
+
# end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def select_values
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Oversee::Field::Input::Boolean < Oversee::Field::Input
|
2
|
+
def initialize(key:, value:)
|
3
|
+
@key = key
|
4
|
+
@value = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
select(id: field_id, name: field_name, class: "flex w-full border rounded-sm px-4 py-2 text-sm") do
|
9
|
+
option(value: 1, selected: @value) { "True" }
|
10
|
+
option(value: 0, selected: !@value) { "False" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Oversee::Field::Input::Datetime < Oversee::Field::Input
|
2
|
+
def initialize(key:, value:)
|
3
|
+
@key = key
|
4
|
+
@value = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
input type: "datetime-local", id: field_id, name: field_name, value: @value&.strftime("%Y-%m-%dT%T"), class: "flex w-full border rounded-sm px-4 py-2 text-sm"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Oversee::Field::Input::Integer < Oversee::Field::Input
|
2
|
+
def initialize(key:, value:)
|
3
|
+
@key = key
|
4
|
+
@value = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
input type: "number", id: field_id, name: field_name, value: @value, class: "flex w-full border rounded-sm px-4 py-2 text-sm"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Oversee::Field::Input::RichText < Oversee::Field::Input
|
2
|
+
register_element :trix_editor
|
3
|
+
|
4
|
+
attr_reader :key
|
5
|
+
attr_reader :value
|
6
|
+
|
7
|
+
def initialize(key:, value:)
|
8
|
+
@key = key
|
9
|
+
@value = value
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_template
|
13
|
+
div do
|
14
|
+
input(type: "hidden", id: field_id, name: field_name, value:)
|
15
|
+
trix_editor(id: "#{field_id}_trix_editor", input: field_id)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Oversee::Field::Input::String < Oversee::Field::Input
|
2
|
+
def initialize(key:, value:)
|
3
|
+
@key = key
|
4
|
+
@value = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def view_template
|
8
|
+
input type: "text", id: field_id, name: field_name, value: @value, class: "flex border px-4 py-2 text-sm w-full rounded-sm"
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Oversee::Field::Input < Oversee::Base
|
2
|
+
MAP = {
|
3
|
+
belongs_to: Oversee::Field::Input::BelongsTo,
|
4
|
+
boolean: Oversee::Field::Input::Boolean,
|
5
|
+
datetime: Oversee::Field::Input::Datetime,
|
6
|
+
enum: Oversee::Field::Input::String,
|
7
|
+
integer: Oversee::Field::Input::Integer,
|
8
|
+
rich_text: Oversee::Field::Input::RichText,
|
9
|
+
string: Oversee::Field::Input::String,
|
10
|
+
text: Oversee::Field::Input::String,
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize(key: nil, value: nil, datatype: :string, **options)
|
14
|
+
@key = key
|
15
|
+
@value = value
|
16
|
+
@datatype = datatype
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
def view_template
|
21
|
+
render component_class.new(key: @key, value: @value, **@options)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def component_class
|
27
|
+
MAP[@datatype.to_sym] || Oversee::Field::Input::String
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def field_id = "resource_#{@key.to_s}"
|
33
|
+
def field_name = "resource[#{@key.to_s}]"
|
34
|
+
end
|