oversee 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +53 -14
- 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 +54 -0
- data/app/components/oversee/dashboard/header.rb +41 -0
- data/app/components/oversee/dashboard/index.rb +24 -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 +53 -0
- data/app/components/oversee/dashboard/tailwind.rb +33 -0
- data/app/components/oversee/field/display.rb +29 -0
- data/app/components/oversee/field/form.rb +49 -0
- data/app/components/oversee/field/input/boolean.rb +23 -0
- data/app/components/oversee/field/input/datetime.rb +20 -0
- data/app/components/oversee/field/input/integer.rb +20 -0
- data/app/components/oversee/field/input/string.rb +20 -0
- data/app/components/oversee/field/input.rb +26 -0
- data/app/components/oversee/field/label.rb +24 -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/string.rb +20 -0
- data/app/components/oversee/field/value/text.rb +9 -0
- data/app/components/oversee/field/value.rb +34 -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 +26 -0
- data/app/components/oversee/resources/new.rb +17 -0
- data/app/components/oversee/resources/show.rb +126 -0
- data/app/components/oversee/resources/table.rb +109 -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_controller.rb +62 -23
- data/app/models/oversee/resource.rb +36 -0
- data/app/oversee/filter.rb +39 -0
- data/app/oversee/search.rb +34 -0
- data/app/views/layouts/oversee/application.html.erb +5 -5
- data/config/locales/en.yml +4 -0
- data/config/locales/oversee.en.yml +4 -0
- data/config/routes.rb +10 -10
- data/lib/oversee/configuration.rb +3 -0
- data/lib/oversee/version.rb +1 -1
- data/lib/oversee.rb +14 -2
- metadata +71 -47
- data/app/assets/config/oversee_manifest.js +0 -1
- 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/mailers/oversee/application_mailer.rb +0 -6
- 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: af99387ad6126bddd510d80a2fd929a776c48ef101e61a3ecfd93c1f8106fa87
|
4
|
+
data.tar.gz: 1d8f34bc6c2fd6225b00edf30b833b4ba6bf52ced03c4c5b16be9a33162ad1a7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 577e608656e45f6797397728ec9820cd39854eccffa550d7d60b3735581f8480817a3d4ce235c49bcceab3b9394b06356637ec17c3c2808f2549158ad7a32ea1
|
7
|
+
data.tar.gz: 4ef646cc6bef74b8ffccfbd97c796bc147d6f9a144ff5f4068630235e1967ff6fe6f9b4e08bfcdb899c49bdbe106e8a37cbde59603377edf2fd7c8866c115cbc
|
data/README.md
CHANGED
@@ -1,40 +1,79 @@
|
|
1
|
-
[![Gem Version](https://badge.fury.io/rb/oversee.svg)](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
|
+
[![Gem Version](https://badge.fury.io/rb/oversee.svg)](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
|
+
![Oversee Screenshot](docs/images/screenshot.png)
|
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).
|
@@ -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,54 @@
|
|
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: "border-b p-4") do
|
12
|
+
div(class: "flex items-center justify-between") do
|
13
|
+
div(class: "flex items-center gap-2") do
|
14
|
+
if show_action_section?
|
15
|
+
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
|
16
|
+
render Phlex::Icons::Iconoir::FilterAlt.new(class: "size-3")
|
17
|
+
plain "Filters"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
if false
|
21
|
+
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
|
22
|
+
render Phlex::Icons::Iconoir::XMark.new(class: "size-4 text-gray-500")
|
23
|
+
plain "Clear sorting"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
div(class: "flex items-center gap-4") do
|
28
|
+
form(action: "", class: "flex items-center gap-2") do
|
29
|
+
input(type: :search, name: :query, class: "flex bg-gray-100 min-w-80 h-10 items-center pl-4 py-2 placeholder:text-gray-500 rounded-sm text-sm", placeholder: search_placeholder, value: @params[:query])
|
30
|
+
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") }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def show_action_section?
|
40
|
+
Rails.env.development? || @params[:experimental] == "true"
|
41
|
+
end
|
42
|
+
|
43
|
+
def sortless_path
|
44
|
+
sortless_query_params = @params.except(:sort_attribute, :sort_direction, :controller, :action)
|
45
|
+
# helpers.resources_path(sortless_query_params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def search_placeholder
|
49
|
+
context = Search.new(collection: nil, resource_class: @params[:resource_class_name].constantize)
|
50
|
+
attr = context.default_searchable_attribute
|
51
|
+
|
52
|
+
"Search by #{attr}"
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Oversee::Dashboard::Header < Oversee::Base
|
2
|
+
def initialize(title: nil, subtitle: nil, return_path: nil, show_back_button: true)
|
3
|
+
@title = title || "Dashboard"
|
4
|
+
@subtitle = subtitle || "Manage your account"
|
5
|
+
@return_path = return_path
|
6
|
+
@show_back_button = show_back_button
|
7
|
+
end
|
8
|
+
|
9
|
+
def view_template(&)
|
10
|
+
div(id: "dashboard_header", class: "p-8 border-b flex items-center justify-between") do
|
11
|
+
div(class: "flex items-center gap-4") do
|
12
|
+
back_button
|
13
|
+
div do
|
14
|
+
h1(class: "text-xl text-gray-800") { @title }
|
15
|
+
p(class: "text-sm text-gray-600") { @subtitle }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
div(class: "flex items-center gap-2") do
|
19
|
+
yield if block_given?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def back_button
|
27
|
+
if @show_back_button
|
28
|
+
a(
|
29
|
+
href: return_button_path,
|
30
|
+
class: "size-10 inline-flex items-center justify-center bg-gray-50 text-gray-500 hover:text-gray-900 hover:bg-gray-200 transition-colors",
|
31
|
+
data: { controller: "back", action: "back#navigate", turbo_action: "replace" }
|
32
|
+
) do
|
33
|
+
render Phlex::Icons::Iconoir::ArrowLeft.new(class: "size-4 text-gray-900")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def return_button_path
|
39
|
+
@return_path || root_path
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Oversee::Dashboard::Index < Oversee::Base
|
4
|
+
def view_template
|
5
|
+
div(class: "p-8") do
|
6
|
+
div(class: "flex items-center justify-between") do
|
7
|
+
div do
|
8
|
+
p(class: "text-xs uppercase font-medium text-gray-400") { "Dashboard" }
|
9
|
+
h1(class: "text-xl") { "Welcome" }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
if Oversee.card_class_names.present?
|
15
|
+
div(class: "p-8") do
|
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
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Oversee::Dashboard::Javascript < Phlex::HTML
|
2
|
+
def view_template
|
3
|
+
script(async: true, src:"https://unpkg.com/es-module-shims/dist/es-module-shims.js")
|
4
|
+
script(type: "importmap", data_turbo_track: "reload") do
|
5
|
+
raw %({"imports":{"@hotwired/stimulus":"https://unpkg.com/@hotwired/stimulus/dist/stimulus.js","@hotwired/turbo":"https://unpkg.com/@hotwired/turbo","@hotwired/turbo-rails":"https://unpkg.com/@hotwired/turbo-rails"}}).html_safe
|
6
|
+
end
|
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:"p-4 border-t 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,53 @@
|
|
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
|
+
hr(class: "my-4 -mx-4")
|
25
|
+
details(open: true, class: "group") do
|
26
|
+
summary(class: "flex items-center justify-between cursor-pointer") do
|
27
|
+
p(class: "text-[0.7rem] uppercase text-gray-400 font-medium") { "Resources" }
|
28
|
+
render Phlex::Icons::Iconoir::NavArrowDown.new(class: "size-4 text-gray-400 transform transition-transform group-open:rotate-180 group-hover:text-blue-500")
|
29
|
+
end
|
30
|
+
ul(class: "mt-2 text-sm text-gray-700 overflow-x-hidden") do
|
31
|
+
Oversee.application_resource_names.sort.each do |resource_class_name|
|
32
|
+
li do
|
33
|
+
a(href: helpers.resources_path(resource_class_name:), class: "flex items-center gap-2 hover:bg-gray-50 p-2 truncate") do
|
34
|
+
render Phlex::Icons::Iconoir::Folder.new(class: "size-4 text-gray-400")
|
35
|
+
span { resource_class_name }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
hr(class: "my-4 -mx-4")
|
43
|
+
p(class: "text-xs text-gray-500") do
|
44
|
+
plain("Powered by ")
|
45
|
+
a(
|
46
|
+
href: "https://github.com/primevise/oversee",
|
47
|
+
class: "text-blue-500 hover:underline",
|
48
|
+
target: "_blank"
|
49
|
+
) { "Oversee" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Oversee::Dashboard::Tailwind < Phlex::HTML
|
2
|
+
def view_template
|
3
|
+
script(src: "https://cdn.tailwindcss.com?plugins=typography")
|
4
|
+
style(type:"text/tailwindcss") do
|
5
|
+
raw pagy_css.html_safe
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def pagy_css
|
10
|
+
<<~CSS
|
11
|
+
.pagy {
|
12
|
+
@apply flex gap-2 items-center text-sm text-gray-500;
|
13
|
+
a:not(.gap) {
|
14
|
+
@apply inline-flex items-center justify-center h-8 min-w-8 font-normal;
|
15
|
+
&:hover { @apply bg-gray-100; }
|
16
|
+
&:not([href]) { /* disabled links */
|
17
|
+
@apply text-gray-300 cursor-not-allowed;
|
18
|
+
}
|
19
|
+
&.current {
|
20
|
+
@apply font-semibold bg-gray-100 text-gray-900;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
label {
|
25
|
+
@apply inline-block whitespace-nowrap bg-gray-200 px-3 py-0.5;
|
26
|
+
input {
|
27
|
+
@apply bg-gray-100 border-none rounded-md;
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
CSS
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Oversee::Field::Display < Oversee::Base
|
2
|
+
def initialize(resource:, key: nil, value: nil, datatype: :string, interactive: true)
|
3
|
+
@resource = resource
|
4
|
+
@key = key
|
5
|
+
@value = value
|
6
|
+
@datatype = datatype
|
7
|
+
@interactive = interactive
|
8
|
+
end
|
9
|
+
|
10
|
+
def view_template
|
11
|
+
wrapper_tag(
|
12
|
+
id: dom_id(@resource, @key),
|
13
|
+
href: helpers.resource_input_field_path(resource_class_name: resource_class_name, key: @key),
|
14
|
+
class: "bg-gray-100 h-10 flex items-center px-4 py-2 hover:bg-gray-200 transition-colors w-full cursor-pointer",
|
15
|
+
data: { turbo_stream: true }) do
|
16
|
+
render Oversee::Field::Value.new(key: @key, value: @resource.send(@key), datatype: @datatype)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def resource_class_name
|
23
|
+
@resource.class.name
|
24
|
+
end
|
25
|
+
|
26
|
+
def wrapper_tag(...)
|
27
|
+
@interactive ? a(...) : div(...)
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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)
|
5
|
+
@resource = resource
|
6
|
+
@key = key
|
7
|
+
@value = value
|
8
|
+
@datatype = datatype
|
9
|
+
@method = method
|
10
|
+
end
|
11
|
+
|
12
|
+
def view_template
|
13
|
+
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|
|
14
|
+
input type: :hidden, id: "oversee_key", name: "oversee_key", value: @key.to_s
|
15
|
+
input type: :hidden, id: "oversee_datatype", name: "oversee_datatype", value: @datatype
|
16
|
+
render Oversee::Field::Input.new(key: @key, value: @value, datatype: @datatype)
|
17
|
+
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") {
|
18
|
+
save_icon
|
19
|
+
plain "Save"
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def resource_class_name
|
27
|
+
@resource.class.name
|
28
|
+
end
|
29
|
+
|
30
|
+
def save_icon
|
31
|
+
svg(
|
32
|
+
stroke_width: "2.5",
|
33
|
+
viewbox: "0 0 24 24",
|
34
|
+
fill: "none",
|
35
|
+
xmlns: "http://www.w3.org/2000/svg",
|
36
|
+
class: "size-4",
|
37
|
+
color: "currentColor"
|
38
|
+
) do |s|
|
39
|
+
s.path(
|
40
|
+
d: "M5 13L9 17L19 7",
|
41
|
+
stroke: "currentColor",
|
42
|
+
stroke_width: "2.5",
|
43
|
+
stroke_linecap: "round",
|
44
|
+
stroke_linejoin: "round"
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Oversee::Field::Input::Boolean < Phlex::HTML
|
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
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def field_id
|
17
|
+
"resource_#{@key.to_s}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def field_name
|
21
|
+
"resource[#{@key.to_s}]"
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Oversee::Field::Input::Datetime < Phlex::HTML
|
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
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def field_id
|
14
|
+
"resource_#{@key.to_s}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def field_name
|
18
|
+
"resource[#{@key.to_s}]"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Oversee::Field::Input::Integer < Phlex::HTML
|
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
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def field_id
|
14
|
+
"resource_#{@key.to_s}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def field_name
|
18
|
+
"resource[#{@key.to_s}]"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Oversee::Field::Input::String < Phlex::HTML
|
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
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def field_id
|
14
|
+
"resource_#{@key.to_s}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def field_name
|
18
|
+
"resource[#{@key.to_s}]"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Oversee::Field::Input < Oversee::Base
|
2
|
+
MAP = {
|
3
|
+
string: Oversee::Field::Input::String,
|
4
|
+
boolean: Oversee::Field::Input::Boolean,
|
5
|
+
integer: Oversee::Field::Input::Integer,
|
6
|
+
datetime: Oversee::Field::Input::Datetime,
|
7
|
+
text: Oversee::Field::Input::String,
|
8
|
+
enum: Oversee::Field::Input::String
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(key: nil, value: nil, datatype: :string)
|
12
|
+
@key = key
|
13
|
+
@value = value
|
14
|
+
@datatype = datatype
|
15
|
+
end
|
16
|
+
|
17
|
+
def view_template
|
18
|
+
render component_class.new(key: @key, value: @value)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def component_class
|
24
|
+
MAP[@datatype.to_sym] || Oversee::Field::Input::String
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Oversee::Field::Label < Oversee::Base
|
2
|
+
ICON_MAP = {
|
3
|
+
string: Phlex::Icons::Iconoir::Text,
|
4
|
+
text: Phlex::Icons::Iconoir::TextSquare,
|
5
|
+
integer: Phlex::Icons::Iconoir::Number0Square,
|
6
|
+
datetime: Phlex::Icons::Iconoir::Calendar,
|
7
|
+
boolean: Phlex::Icons::Iconoir::SwitchOn,
|
8
|
+
data: Phlex::Icons::Iconoir::Page,
|
9
|
+
}
|
10
|
+
|
11
|
+
def initialize(datatype: :string, key: nil)
|
12
|
+
@datatype = datatype
|
13
|
+
@key = key
|
14
|
+
end
|
15
|
+
|
16
|
+
def view_template
|
17
|
+
div(class:"inline-flex items-center space-x-2") do
|
18
|
+
div(class: "size-5 bg-gray-100 inline-flex items-center justify-center") do
|
19
|
+
render ICON_MAP[@datatype] ? ICON_MAP[@datatype].new(class: "size-3") : ICON_MAP[:data].new(class: "size-3")
|
20
|
+
end
|
21
|
+
label(class: "uppercase text-xs text-gray-š00 font-medium block") { @key.to_s.humanize(keep_id_suffix: true) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Oversee::Field::Value::Boolean < Phlex::HTML
|
2
|
+
def initialize(key: nil, value: nil, kind: :value)
|
3
|
+
@value = value
|
4
|
+
end
|
5
|
+
|
6
|
+
def view_template
|
7
|
+
@value ? check_icon : x_icon
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def check_icon
|
13
|
+
svg(
|
14
|
+
stroke_width: "2",
|
15
|
+
viewbox: "0 0 24 24",
|
16
|
+
fill: "none",
|
17
|
+
xmlns: "http://www.w3.org/2000/svg",
|
18
|
+
color: "currentColor",
|
19
|
+
class: "size-4 text-emerald-500"
|
20
|
+
) do |s|
|
21
|
+
s.path(
|
22
|
+
d: "M5 13L9 17L19 7",
|
23
|
+
stroke: "currentColor",
|
24
|
+
stroke_width: "2",
|
25
|
+
stroke_linecap: "round",
|
26
|
+
stroke_linejoin: "round"
|
27
|
+
)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def x_icon
|
32
|
+
svg(
|
33
|
+
stroke_width: "1.5",
|
34
|
+
viewbox: "0 0 24 24",
|
35
|
+
fill: "none",
|
36
|
+
xmlns: "http://www.w3.org/2000/svg",
|
37
|
+
color: "currentColor",
|
38
|
+
class: "size-4 text-rose-500"
|
39
|
+
) do |s|
|
40
|
+
s.path(
|
41
|
+
d:
|
42
|
+
"M6.75827 17.2426L12.0009 12M17.2435 6.75736L12.0009 12M12.0009 12L6.75827 6.75736M12.0009 12L17.2435 17.2426",
|
43
|
+
stroke: "currentColor",
|
44
|
+
stroke_width: "1.5",
|
45
|
+
stroke_linecap: "round",
|
46
|
+
stroke_linejoin: "round"
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|