web47core 3.2.9 → 3.2.12

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/core_breadcrumb_helper.rb +11 -0
  3. data/app/helpers/core_card_nav_items_helper.rb +60 -0
  4. data/app/helpers/core_dropdown_helper.rb +89 -0
  5. data/app/helpers/core_floating_action_button_helper.rb +82 -0
  6. data/app/helpers/core_html5_form_helper.rb +163 -0
  7. data/app/helpers/core_job_state_helper.rb +21 -0
  8. data/app/helpers/core_remix_icon_helper.rb +23 -0
  9. data/app/helpers/core_select_two_helper.rb +73 -0
  10. data/app/helpers/model_modal_helper.rb +1 -1
  11. data/app/views/cron_servers/index.html.haml +28 -0
  12. data/app/views/cron_tabs/edit.html.haml +17 -0
  13. data/app/views/cron_tabs/index.html.haml +35 -0
  14. data/app/views/delayed_job_metrics/index.html.haml +22 -1
  15. data/app/views/delayed_job_workers/index.html.haml +30 -1
  16. data/app/views/delayed_jobs/index.html.haml +28 -1
  17. data/app/views/delayed_jobs/show.html.haml +55 -1
  18. data/app/views/system_configurations/edit.html.haml +14 -1
  19. data/app/views/system_configurations/show.html.haml +18 -1
  20. data/lib/app/controllers/concerns/core_controller.rb +14 -0
  21. data/lib/app/controllers/concerns/core_cron_servers_controller.rb +31 -0
  22. data/lib/app/controllers/concerns/core_cron_tabs_controller.rb +41 -0
  23. data/lib/app/controllers/concerns/core_delayed_job_metrics_controller.rb +2 -2
  24. data/lib/app/jobs/cron/trim_command_jobs.rb +1 -1
  25. data/lib/app/models/concerns/class_name.rb +26 -0
  26. data/lib/app/models/concerns/tag_able.rb +34 -0
  27. data/lib/app/models/tag.rb +21 -0
  28. data/lib/app/models/user_audit_log.rb +2 -2
  29. data/lib/web47core/version.rb +1 -1
  30. data/lib/web47core.rb +5 -1
  31. metadata +18 -25
  32. data/app/views/admin/cron/edit.html.haml +0 -1
  33. data/app/views/admin/cron/index.html.haml +0 -1
  34. data/app/views/admin/delayed_jobs/index.html.haml +0 -1
  35. data/app/views/admin/delayed_jobs/show.html.haml +0 -1
  36. data/app/views/admin/system_configurations/edit.html.haml +0 -1
  37. data/app/views/admin/system_configurations/show.html.haml +0 -1
  38. data/app/views/cron/_edit.html.haml +0 -19
  39. data/app/views/cron/_index.html.haml +0 -77
  40. data/app/views/cron/edit.html.haml +0 -1
  41. data/app/views/cron/index.html.haml +0 -1
  42. data/app/views/delayed_job_metrics/_index.html.haml +0 -27
  43. data/app/views/delayed_job_workers/_index.html.haml +0 -27
  44. data/app/views/delayed_jobs/_index.html.haml +0 -49
  45. data/app/views/delayed_jobs/_show.html.haml +0 -60
  46. data/app/views/stack/cron/edit.html.haml +0 -1
  47. data/app/views/stack/cron/index.html.haml +0 -1
  48. data/app/views/stack/delayed_jobs/index.html.haml +0 -1
  49. data/app/views/stack/delayed_jobs/show.html.haml +0 -1
  50. data/app/views/stack/system_configurations/edit.html.haml +0 -1
  51. data/app/views/stack/system_configurations/show.html.haml +0 -1
  52. data/app/views/system_configurations/_edit.html.haml +0 -15
  53. data/app/views/system_configurations/_show.html.haml +0 -22
  54. data/lib/app/controllers/concerns/core_cron_controller.rb +0 -79
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d49fb16ecfe61d18dbc79f37425422d9e7655db689aae499816be0d145c3922a
4
- data.tar.gz: 0125066bb00ecfdb0b423d09493fbd34ed457b8805a7e400f82b0bcf5d78458f
3
+ metadata.gz: 327cae83d882b83a5d99350fdf424929fad212fcf77f2d611ad6bb0fd3ac3516
4
+ data.tar.gz: f6ee9926c857f10177ea4b4357b5d70c6dca7de71538e804f7f3ccf4b6165e50
5
5
  SHA512:
6
- metadata.gz: f64b6ef1ff5cb4c9bdd72a87d2f4fc3a6fe04a29959ef16704bb3cd3d9b5099dbdc3eb2b43122fec0c47d57d3457aacba57ae5dc8bda37377d3cdb7b07d1127a
7
- data.tar.gz: ecc76a77d5fda33705260ef6de28b34bca7bdc456587b9124b87c3463e53318e1529a9aa298a513140f9de4d14a2a880341f975600217bec93ec7769263d5a08
6
+ metadata.gz: 062d8c47cabbbeb0bf02ad9c2884cadff4621981cd106340f2111a4c2520d395affa20691de161e6b136007da72e54f78b2100e20640a22df7ac18d6dff7ff95
7
+ data.tar.gz: 12c2bc3603918649caef8b9086c564eb056a5683deeeacf63d0a8b91673f6ae5a9e7a38d5ef8bb1b5bf7beb76fa57eb4edc8a0d39db1f149f0525308792cb1df
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Render avatar based on a user's profile
4
+ module CoreBreadcrumbHelper
5
+ # @abstract Render a single step for the breadcrumb
6
+ def breadcrumb_step(path, title)
7
+ content_tag(:li, class: "breadcrumb-item active") do
8
+ concat(content_tag(:a, href: path) { title })
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Help with nav items at the top of cards
5
+ #
6
+ module CoreCardNavItemsHelper
7
+ # @abstract Yield the main block for card nav items
8
+ def card_nav_items(&block)
9
+ content_tag(:ul, class: 'nav nav-pills float-end') do
10
+ yield block
11
+ end
12
+ end
13
+
14
+ # @abstract Link to restart, replay a given object
15
+ # @param [Object] obj - The object to operate on, for permission checks
16
+ # @param [String] path - The path/URL for the action
17
+ # @param [String] confirm - Override the default confirmation
18
+ # @param [String] title - Override the default title of the button
19
+ # @return HTML - The HTML for the given tag
20
+ def restart_nav_link(obj, path, confirm: nil, title: 'Restart')
21
+ return unless can?(:edit, obj)
22
+
23
+ confirm ||= "Are you sure you want to restart this #{obj.class_title}?"
24
+ card_nav_item_link(path, title, 'delete-bin', confirm: confirm)
25
+ end
26
+
27
+ def edit_nav_link(obj, path, title: 'Edit')
28
+ return unless can?(:edit, obj)
29
+
30
+ card_nav_item_link(path, title, 'edit')
31
+ end
32
+
33
+ # @abstract Link to delete an object
34
+ # @return HTML - The HTML for the given tag
35
+ def delete_nav_link(obj, path, confirm: nil, title: 'Delete')
36
+ return unless can?(:manage, obj)
37
+
38
+ confirm ||= "Are you sure you want to delete this #{obj.class_title}?"
39
+ card_nav_item_link(path, title, 'delete-bin', confirm: confirm, method: :delete, btn_class: 'btn-danger')
40
+ end
41
+
42
+ # @abstract The work horse for the card nav item
43
+ # @param [String] path - The path/URL for the action
44
+ # @param [String] title - The title of the action
45
+ # @param [String] icon_name - Name of the icon to render
46
+ # @param [String] confirm - If the action requires confirmation before completing
47
+ # @param [String|Symbol] method - The HTTP method to use for the link, default is :get
48
+ # @param [String] btn_class - The class of button that should be used, btn-primary is the default
49
+ # @return HTML - The HTML for the given tag
50
+ def card_nav_item_link(path, title, icon_name, confirm: nil, method: :get, btn_class: 'btn-primary')
51
+ data = { method: method }
52
+ data[:confirm] = confirm if confirm.present?
53
+ content_tag(:li, class: 'nav-item me-2') do
54
+ link_to path, class: "#{btn_class} btn nav-link active", data: data do
55
+ concat(remix_icon(icon_name, classes: ['me-2'], tooltip_text: title))
56
+ concat(content_tag(:span, class: 'd-none d-md-inline') { title })
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Helpful methods rendering tables
5
+ #
6
+ module CoreDropdownHelper
7
+ # @abstract Drop down menu for actions
8
+ def dropdown_menu(&block)
9
+ content_tag(:div, class: 'dropdown') do
10
+ concat(content_tag(:button,
11
+ class: 'btn p-0 dropdown-toggle hide-arrow',
12
+ type: :button,
13
+ data: { 'bs-toggle': :dropdown }) do
14
+ concat(remix_icon('more-2'))
15
+ end)
16
+ concat(content_tag(:div, class: 'dropdown-menu') { yield block })
17
+ end
18
+ end
19
+
20
+ def dropdown_item(path, icon_name, action_name, method: :get, confirm: nil, classes: [])
21
+ data = { method: method }
22
+ data[:confirm] = confirm if confirm.present?
23
+ classes << 'dropdown-item'
24
+ content_tag(:a, class: classes.join(' '), href: path, data: data) do
25
+ concat(menu_remix_icon(icon_name))
26
+ concat(action_name)
27
+ end
28
+ end
29
+
30
+ def demote_dropdown_item(obj, path)
31
+ return unless can? :edit, obj
32
+
33
+ dropdown_item(path, 'thumb-down', 'Demote')
34
+ end
35
+
36
+ def info_dropdown_item(obj, path, name: 'Info')
37
+ return unless can? :view, obj
38
+
39
+ dropdown_item(path, 'information', name)
40
+ end
41
+
42
+ def dashboard_dropdown_item(obj, path, name: 'Show')
43
+ return unless can? :view, obj
44
+
45
+ dropdown_item(path, 'dashboard', name)
46
+ end
47
+
48
+ def jobs_dropdown_item(obj, path, name: 'Jobs')
49
+ return unless can? :view, obj
50
+
51
+ dropdown_item(path, 'file-list', name)
52
+ end
53
+
54
+ def current_job_dropdown_item(obj, path)
55
+ return unless can? :view, obj
56
+
57
+ dropdown_item(path, 'hourglass-2', 'Current Job')
58
+ end
59
+
60
+ def edit_dropdown_item(obj, path)
61
+ return unless can? :edit, obj
62
+
63
+ dropdown_item(path, 'pencil', 'Edit')
64
+ end
65
+
66
+ def delete_dropdown_item(obj, path)
67
+ return unless can? :delete, obj
68
+
69
+ dropdown_item(path, 'delete-bin', 'Delete', method: :delete, confirm: 'are you sure?')
70
+ end
71
+
72
+ def cancel_dropdown_item(obj, path, confirm: 'Are you sure?', method: :delete)
73
+ return unless can? :edit, obj
74
+
75
+ dropdown_item(path, 'close-circle', 'Cancel', method: method, confirm: confirm, classes: %w(text-danger))
76
+ end
77
+
78
+ def run_dropdown_item(obj, path)
79
+ return unless can? :read, obj
80
+
81
+ dropdown_item(path, 'run', 'Run', method: :post)
82
+ end
83
+
84
+ def replay_dropdown_item(obj, path, confirm: nil, title: 'Replay')
85
+ return unless can? :read, obj
86
+
87
+ dropdown_item(path, 'reset-left', title, confirm: confirm)
88
+ end
89
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Draw floating action buttons for the page
5
+ #
6
+ module CoreFloatingActionButtonHelper
7
+ # Single action buttons
8
+ # @abstract Render a creation FAB
9
+ def add_fab_button(clazz, path)
10
+ return unless can?(:create, clazz)
11
+
12
+ floating_action_button(path, title: "Add #{clazz.to_s.humanize}", icon_name: 'add-circle')
13
+ end
14
+
15
+ # @abstract Render a creation FAB
16
+ def edit_fab_button(obj, path)
17
+ return unless can?(:edit, obj)
18
+
19
+ floating_action_button(path, title: 'Edit', icon_name: 'edit')
20
+ end
21
+
22
+ # @abstract Render a floating action button
23
+ def floating_action_button(path, title: 'Add', icon_name: 'add-circle')
24
+ content_tag(:div, class: 'btn-fab') do
25
+ concat(content_tag(:a, class: 'btn btn-primary btn-large rounded-circle', href: path, title: title) do
26
+ concat(content_tag(:i, class: "ri-#{icon_name}-line ri-36px") {})
27
+ end)
28
+ end
29
+ end
30
+
31
+ # Multiple action buttons
32
+ # @abstract Build the FAB that has a "more" icon and yields the buttons
33
+ def multiple_floating_action_button(&block)
34
+ content_tag(:div, class: 'btn-fab') do
35
+ concat(content_tag(:button, class: 'btn btn-primary btn-lg rounded-circle',
36
+ type: :button,
37
+ 'data-bs-toggle': :dropdown,
38
+ haspopup: true,
39
+ 'aria-expanded': false) do
40
+ remix_icon('more-2', classes: %w[ri-24px])
41
+ end)
42
+ concat(content_tag(:ul, class: 'dropdown-menu') { yield block })
43
+ end
44
+ end
45
+
46
+ def delete_floating_action_button(clazz, path, title: 'Delete', icon_name: 'delete-bin', confirm: 'Are you sure you want to delete this item?')
47
+ return unless can?(:destroy, clazz)
48
+
49
+ floating_action_link(path, title, icon_name, confirm: confirm, method: :delete)
50
+ end
51
+
52
+ # @abstract add floating action
53
+ def add_floating_action_link(clazz, path)
54
+ return unless can?(:create, clazz)
55
+
56
+ floating_action_link(path, "New #{clazz.to_s.humanize}", 'add-circle')
57
+ end
58
+
59
+ # @abstract Edit floating action, if the user is allowed to edit the object
60
+ def edit_floating_action_link(clazz, path)
61
+ return unless can?(:edit, clazz)
62
+
63
+ floating_action_link(path, 'Edit', 'edit')
64
+ end
65
+
66
+ # @abstract Edit floating action, if the user is allowed to edit the object
67
+ def refresh_floating_action_link(clazz, path, title: 'Refresh', icon_name: 'refresh', confirm: 'Are you sure you want to refresh this item?')
68
+ return unless can?(:edit, clazz)
69
+
70
+ floating_action_link(path, title, icon_name, confirm: confirm)
71
+ end
72
+
73
+ def floating_action_link(path, title, icon_name, confirm: nil, method: :get)
74
+ data = { confirm: confirm, title: title, method: method }
75
+ content_tag(:li) do
76
+ concat(content_tag(:a, class: 'dropdown-item', href: path, data: data) do
77
+ concat(menu_remix_icon(icon_name))
78
+ concat(content_tag(:span) { title })
79
+ end)
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Helpful methods HTML5 elements in a form
5
+ #
6
+ module CoreHtml5FormHelper
7
+ # @abstract Render a password field
8
+ # @todo Combine this with the html5_text_field, the only thing different is the field type
9
+ def html5_password(model, field, options = {})
10
+ classes = options[:classes] || %w[col-sm-12 col-lg-6]
11
+ value = model.send(field)
12
+ options[:type] = :password
13
+ options[:place_holder] ||= mask_value(value)
14
+ tag_options = html5_text_field_options(model, field, options)
15
+ content_tag(:div, class: (%w[form-floating form-floating-outline mb-3 col] + classes).join(' ')) do
16
+ concat(tag(:input, tag_options))
17
+ concat(form_label_tag(model, field, value, options))
18
+ end
19
+ end
20
+
21
+ def html5_text_field(model, field, options = {})
22
+ classes = options[:classes] || %w[col-sm-12 col-lg-6]
23
+ value = model.send(field)
24
+ options[:type] ||= :text
25
+ options[:value] = value
26
+ options[:disabled] ||= false
27
+ tag_options = html5_text_field_options(model, field, options)
28
+ content_tag(:div, class: (%w[form-floating form-floating-outline mb-3 col] + classes).join(' ')) do
29
+ concat(tag(:input, tag_options))
30
+ concat(html5_label_tag(model, field, value, options))
31
+ end
32
+ end
33
+
34
+ def html5_label_tag(model, field, value, options = {})
35
+ # dont do a label if we are in default browser mode
36
+ return if options[:no_label]
37
+ return if options[:input_classes].present? && options[:input_classes].include?('browser-default')
38
+
39
+ # or if we have a prompt with now value
40
+ place_holder = field_placeholder_text(model, field, default: options[:placeholder])
41
+ return if place_holder.blank? && value.blank? && options[:prompt].present?
42
+
43
+ error = model.errors[field]
44
+ key = "ui_form.#{model.class.to_s.underscore}.labels.#{field}"
45
+ classes = value.nil? && place_holder.blank? ? '' : 'active'
46
+ classes += error.present? ? ' invalid red-text' : ' valid'
47
+ options[:class] = classes
48
+ options[:for] = html5_field_id(model, field, options)
49
+ options['data-error'] = error.join(', ') if error.present?
50
+ content_tag(:label, options) do
51
+ concat(I18n.exists?(key) ? I18n.t(key) : field.to_s.humanize)
52
+ concat(" #{error.join(', ')}") if error.present?
53
+ end
54
+ end
55
+
56
+ def html5_text_field_options(model, field, options)
57
+ hint_key = "ui_form.#{model.class.to_s.underscore}.hints.#{field}"
58
+ if I18n.exists?(hint_key)
59
+ classes = %w[form-control validate tooltipped]
60
+ options[:data] = { tooltip: I18n.t(hint_key), position: :top }
61
+ else
62
+ classes = %w[form-control validate]
63
+ end
64
+ classes += options[:input_classes] if options[:input_classes].present?
65
+ options[:name] = html5_field_name(model, field, options)
66
+ options[:id] = html5_field_id(model, field, options)
67
+ place_holder = options[:place_holder] || html5_field_place_holder(model, field)
68
+ if place_holder.present?
69
+ classes << 'active'
70
+ options[:placeholder] = place_holder
71
+ end
72
+ classes << 'active' if options[:value].present?
73
+ options[:class] = classes.uniq
74
+ options
75
+ end
76
+
77
+ def html5_field_name(model, field, options = {})
78
+ return options[:form_name] if options[:form_name].present?
79
+
80
+ # TODO: Need to handle the other side of the 1:M use case where
81
+ # the field name needs to end in _ids, not _id.
82
+ field = "#{field}_id" if model.class.reflect_on_association(field).present?
83
+ if options[:index].present?
84
+ if options[:array_name].present?
85
+ if options[:base_name].present?
86
+ "#{options[:base_name]}[#{options[:array_name]}[#{options[:index]}][#{field}]]"
87
+ else
88
+ "#{options[:array_name]}[#{options[:index]}][#{field}]"
89
+ end
90
+ else
91
+ "#{model.class.to_s.underscore}[#{options[:index]}][#{field}]"
92
+ end
93
+ else
94
+ "#{model.class.to_s.underscore}[#{field}]"
95
+ end
96
+ end
97
+
98
+ def html5_field_id(model, field, options = {})
99
+ return options[:form_id] if options[:form_id].present?
100
+
101
+ # TODO: Need to handle the other side of the 1:M use case where
102
+ # the field name needs to end in _ids, not _id.
103
+ field = "#{field}_id" if model.class.reflect_on_association(field).present?
104
+ if options[:index].present?
105
+ if options[:array_name].present?
106
+ if options[:base_name].present?
107
+ "#{options[:form_id_prefix]}#{options[:base_name]}_#{options[:array_name]}_#{options[:index]}_#{field}"
108
+ else
109
+ "#{options[:form_id_prefix]}#{options[:array_name]}_#{options[:index]}_#{field}"
110
+ end
111
+ else
112
+ "#{options[:form_id_prefix]}#{model.class.to_s.underscore}_#{options[:index]}_#{field}"
113
+ end
114
+ else
115
+ "#{options[:form_id_prefix]}#{model.class.to_s.underscore}_#{field}"
116
+ end
117
+ end
118
+
119
+ def html5_field_place_holder(model, field)
120
+ place_holder_key = "ui_form.#{model.class.to_s.underscore}.placeholders.#{field}"
121
+ I18n.exists?(place_holder_key) ? I18n.t(place_holder_key) : nil
122
+ end
123
+
124
+ def html5_checkbox(model, field, options = {})
125
+ classes = options[:classes] || %w[col-sm-12 col-lg-6]
126
+ value = model.send(field)
127
+ options[:disabled] ||= false
128
+ properties = {
129
+ class: 'form-check-input',
130
+ id: html5_field_id(model, field, options),
131
+ name: html5_field_name(model, field, options),
132
+ type: :checkbox,
133
+ disabled: options[:disabled]
134
+ }
135
+ properties[:checked] = true if model.send(field)
136
+ checkbox_tag = tag(:input, properties)
137
+ content_tag(:div, class: (%w[form-check mt-4] + classes).join(' ')) do
138
+ concat(content_tag(:label) do
139
+ concat(checkbox_tag)
140
+ concat(html5_checkbox_label_tag(model, field, value, input_classes: classes))
141
+ end)
142
+ end
143
+ end
144
+
145
+ def html5_checkbox_label_tag(model, field, value, options = {})
146
+ # dont do a label if we are in default browser mode
147
+ return if options[:input_classes].present? && options[:input_classes].include?('browser-default')
148
+
149
+ # or if we have a prompt with now value
150
+ place_holder = html5_field_place_holder(model, field)
151
+ return if place_holder.blank? && value.blank? && options[:prompt].present?
152
+
153
+ error = model.errors[field]
154
+ key = "ui_form.#{model.class.to_s.underscore}.labels.#{field}"
155
+ classes = value.nil? && place_holder.blank? ? '' : 'active'
156
+ classes += error.present? ? ' invalid red-text' : ' valid'
157
+ options[:class] = classes
158
+ options['data-error'] = error.join(', ') if error.present?
159
+ content_tag(:span, options) do
160
+ concat(I18n.exists?(key) ? I18n.t(key) : field.to_s.humanize)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Helpful methods job states
5
+ #
6
+ module CoreJobStateHelper
7
+ # @bstract Color mapping for states
8
+ JOB_STATE_COLORS = {
9
+ CommandJob::STATE_NEW => 'primary',
10
+ CommandJob::STATE_WIP => 'secondary',
11
+ CommandJob::STATE_RETRYING => 'warning',
12
+ CommandJob::STATE_SUCCESS => 'success',
13
+ CommandJob::STATE_FAIL => 'danger',
14
+ CommandJob::STATE_CANCELLED => 'dark'
15
+ }.freeze
16
+
17
+ def job_state_badge(state)
18
+ color = JOB_STATE_COLORS[state] || 'info'
19
+ content_tag(:span, state.humanize, class: "badge rounded-pill bg-label-#{color}")
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # Draw remix icons on the page, includes tooltip mark up
5
+ #
6
+ module CoreRemixIconHelper
7
+ def menu_remix_icon(icon_name, classes: [], type: :line, tooltip_text: nil, tooltip_placement: 'top')
8
+ classes += %w[menu-icon tf-icons]
9
+ remix_icon(icon_name, classes: classes, type: type, tooltip_text: tooltip_text, tooltip_placement: tooltip_placement)
10
+ end
11
+
12
+ # Render a material icon tag
13
+ def remix_icon(icon_name, classes: [], type: :line, tooltip_text: nil, tooltip_placement: 'top')
14
+ classes << "ri-#{icon_name}-#{type}"
15
+ options = { class: classes }
16
+ if tooltip_text.present?
17
+ options['data-bs-toggle'] = 'tooltip'
18
+ options['data-bs-placement'] = tooltip_placement
19
+ options['data-bs-title'] = tooltip_text
20
+ end
21
+ content_tag(:i, options) {}
22
+ end
23
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Helpers to render select form fields using select 2 framework: https://select2.org
4
+ module CoreSelectTwoHelper
5
+ # @abstract Create the select two field for the model and field
6
+ # @todo convert generic options hash to parameters, maybe....
7
+ def select_two_field(model, field, options = {})
8
+ value = model.send(field)
9
+ hint_key = "ui_form.#{model.class.to_s.underscore}.hints.#{field}"
10
+ if I18n.exists?(hint_key)
11
+ base_classes = %w[form-floating form-floating-outline col tooltipped]
12
+ data = { tooltip: I18n.t(hint_key), position: :top }
13
+ else
14
+ base_classes = %w[form-floating form-floating-outline col mb-3]
15
+ data = {}
16
+ end
17
+ classes = (base_classes + (options[:classes] || %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 col-xxl-2])).join(' ')
18
+ content_tag(:div, class: classes, data: data) do
19
+ concat(select_two_tag(model, field, options))
20
+ concat(html5_label_tag(model, field, value, options))
21
+ end
22
+ end
23
+
24
+ def select_two_tag(model, field, options = {})
25
+ select_content = {
26
+ id: html5_field_id(model, field, options),
27
+ name: html5_field_name(model, field, options),
28
+ class: [options[:input_classes], 'select2', 'form-select', 'form-select-lg'].compact.join(' '),
29
+ disabled: options[:disabled],
30
+ data: { 'allow-clear': true, placeholder: field_placeholder_text(model, field, default: options[:prompt]) }
31
+ }
32
+ content_tag(:select, select_content) do
33
+ select_two_option_key_values(model.send(field), options).each do |value|
34
+ concat(content_tag(:option, value: value[:key], selected: value[:selected]) do
35
+ concat(value[:value])
36
+ end)
37
+ end
38
+ end
39
+ end
40
+
41
+ def field_placeholder_text(model, field, default: 'Select Option')
42
+ hint_key = "ui_form.#{model.class.to_s.underscore}.placeholders.#{field}"
43
+ I18n.exists?(hint_key) ? I18n.t(hint_key) : default
44
+ end
45
+
46
+ def select_two_option_key_values(value, options)
47
+ select_options = []
48
+ # select_options << { key: '', value: options[:prompt], selected: false } if options[:prompt].present?
49
+ options[:options].each do |option_value|
50
+ option_value[:display_name] = option_value.display_name if option_value.respond_to?(:display_name)
51
+ option_value[:display_name] = option_value.user_display_name if option_value.respond_to?(:user_display_name)
52
+ select_options << case option_value
53
+ when String, Integer
54
+ { key: option_value,
55
+ value: option_value,
56
+ selected: value.eql?(option_value) }
57
+ when Array
58
+ { key: option_value.last,
59
+ value: option_value.first,
60
+ selected: value.to_s.eql?(option_value.last.to_s) }
61
+ when Hash
62
+ { key: option_value[:key],
63
+ value: option_value[:value],
64
+ selected: value.eql?(option_value[:key]) }
65
+ else
66
+ { key: option_value[:_id].to_s,
67
+ value: option_value[:display_name] || option_value[:name],
68
+ selected: value.eql?(option_value) }
69
+ end
70
+ end
71
+ select_options
72
+ end
73
+ end
@@ -198,7 +198,7 @@ module ModelModalHelper
198
198
  value ? 'Yes' : 'No'
199
199
  when Date, DateTime, Time
200
200
  current_user.local_time(value, :long)
201
- when Integer, Array, Hash
201
+ when Integer, Array, Hash, Float
202
202
  value.to_s
203
203
  else
204
204
  value
@@ -0,0 +1,28 @@
1
+ - title t('.title')
2
+ .card
3
+ .card-body
4
+ .table-responsive.text-nowrap
5
+ %table.table.card-table.border.table-striped
6
+ %thead
7
+ %tr
8
+ %th Host
9
+ %th State
10
+ %th Last Check In
11
+ %th Actions
12
+ %tbody.table-border-bottom-0
13
+ - Cron::Server.each do |server|
14
+ %tr
15
+ %td=[server.host_name, server.pid].join(':')
16
+ %td.align-content-center
17
+ - if server.alive?
18
+ = remix_icon('heart-2', type: :fill)
19
+ - if server.alive?
20
+ = remix_icon('router', type: :fill)
21
+ %td= current_user.local_time(server.last_check_in_at)
22
+ %td
23
+ =dropdown_menu do
24
+ - if server.primary?
25
+ =demote_dropdown_item(server, demote_cron_server_path(server))
26
+ - else
27
+ =delete_dropdown_item(server, cron_server_path(server))
28
+
@@ -0,0 +1,17 @@
1
+ - title t('.title', name: @cron_tab.name.humanize)
2
+ - content_for :breadcrumbs do
3
+ =breadcrumb_step(cron_tabs_path, t('cron_tabs.index.title'))
4
+ %form{action: cron_tab_path(@cron_tab), method: :post}
5
+ %input{type: :hidden, value: form_authenticity_token, name: :authenticity_token}
6
+ %input{type: :hidden, name: '_method', value: :put}
7
+ .card
8
+ .card-body
9
+ .container
10
+ .row
11
+ = html5_text_field(@cron_tab, :min, classes: %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 xol-xxl-2] )
12
+ = html5_text_field(@cron_tab, :hour, classes: %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 xol-xxl-2] )
13
+ = html5_text_field(@cron_tab, :mday, classes: %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 xol-xxl-2] )
14
+ = html5_text_field(@cron_tab, :month, classes: %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 xol-xxl-2] )
15
+ = html5_text_field(@cron_tab, :wday, classes: %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 xol-xxl-2] )
16
+ = html5_checkbox(@cron_tab, :enabled, classes: %w[col-sm-12 col-md-6 col-lg-4 col-xl-3 xol-xxl-2] )
17
+ .card-footer= render 'common/form_actions', form_cancel_path: cron_tabs_path
@@ -0,0 +1,35 @@
1
+ - title t('.title')
2
+ .card
3
+ .card-body
4
+ .table-responsive.text-nowrap
5
+ %table.table.card-table.border.table-striped
6
+ %thead
7
+ %tr
8
+ %th Name
9
+ %th Last Run At
10
+ %th Min
11
+ %th Hour
12
+ %th MDay
13
+ %th Month
14
+ %th WDay
15
+ %th Enabled
16
+ %th Actions
17
+ %tbody.table-border-bottom-0
18
+ - Cron::JobTab.each do |tab|
19
+ %tr
20
+ %td.name=tab.name.humanize
21
+ %td= current_user.local_time(tab.last_run_at)
22
+ %td=tab.min
23
+ %td=tab.hour
24
+ %td=tab.mday
25
+ %td=tab.month
26
+ %td=tab.wday
27
+ %td
28
+ - if tab.valid_environment?
29
+ = remix_icon('check', type: :fill)
30
+ %td
31
+ =dropdown_menu do
32
+ =edit_dropdown_item(tab, edit_cron_tab_path(tab))
33
+ - if tab.valid_environment?
34
+ =run_dropdown_item(tab, run_now_cron_tab_path(tab))
35
+
@@ -1 +1,22 @@
1
- =render 'index'
1
+ - title t('.title')
2
+ .card
3
+ .card-body
4
+ .table-responsive.text-nowrap
5
+ %table.table.card-table.border.table-striped
6
+ %thead
7
+ %tr
8
+ %th Name
9
+ %th Count
10
+ %th Min/Avg/Max
11
+ %th Last Run At
12
+ %th Actions
13
+ %tbody.table-border-bottom-0
14
+ - @metrics.each do |m|
15
+ %tr
16
+ %td.name=m.name
17
+ %td=m.count
18
+ %td=[m.min.round(3), m.avg.round(3), m.max.round(3)].join('/')
19
+ %td=current_user.local_time(m.last_run_at)
20
+ %td.actions
21
+ = delete_dropdown_item(m, model_path(m))
22
+