airview 0.1.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/CODE_OF_CONDUCT.md +10 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +65 -0
  6. data/app/assets/javascripts/airview/application.js +425 -0
  7. data/app/assets/stylesheets/airview/application.css +1239 -0
  8. data/app/controllers/airview/application_controller.rb +7 -0
  9. data/app/controllers/airview/resources_controller.rb +261 -0
  10. data/app/controllers/airview/setup_controller.rb +90 -0
  11. data/app/controllers/airview/views_controller.rb +76 -0
  12. data/app/helpers/airview/application_helper.rb +479 -0
  13. data/app/javascript/airview/controllers/grid_controller.js +7 -0
  14. data/app/models/airview/application_record.rb +7 -0
  15. data/app/models/airview/field_definition.rb +22 -0
  16. data/app/models/airview/resource_definition.rb +48 -0
  17. data/app/models/airview/view.rb +27 -0
  18. data/app/views/airview/resources/_delete_modal.html.erb +49 -0
  19. data/app/views/airview/resources/_record_modal.html.erb +62 -0
  20. data/app/views/airview/resources/empty.html.erb +5 -0
  21. data/app/views/airview/resources/show.html.erb +258 -0
  22. data/app/views/airview/setup/_form.html.erb +73 -0
  23. data/app/views/airview/setup/edit.html.erb +16 -0
  24. data/app/views/airview/setup/index.html.erb +52 -0
  25. data/app/views/airview/setup/new.html.erb +14 -0
  26. data/app/views/airview/shared/_sidebar.html.erb +76 -0
  27. data/app/views/layouts/airview/application.html.erb +21 -0
  28. data/config/routes.rb +23 -0
  29. data/db/migrate/20260806000000_create_airview_views.rb +57 -0
  30. data/lib/airview/configuration.rb +6 -0
  31. data/lib/airview/engine.rb +15 -0
  32. data/lib/airview/field.rb +81 -0
  33. data/lib/airview/model_discovery.rb +38 -0
  34. data/lib/airview/query.rb +194 -0
  35. data/lib/airview/resource.rb +51 -0
  36. data/lib/airview/resource_builder.rb +62 -0
  37. data/lib/airview/schema_inference.rb +196 -0
  38. data/lib/airview/version.rb +5 -0
  39. data/lib/airview.rb +93 -0
  40. data/lib/generators/airview/install/install_generator.rb +38 -0
  41. data/lib/generators/airview/install/templates/add_folders_to_airview_views.rb +8 -0
  42. data/lib/generators/airview/install/templates/airview.rb +12 -0
  43. data/lib/generators/airview/install/templates/create_airview_views.rb +57 -0
  44. data/sig/airview.rbs +54 -0
  45. metadata +110 -0
@@ -0,0 +1,62 @@
1
+ <div class="airview-modal-backdrop" data-airview-modal>
2
+ <section class="airview-record-modal" role="dialog" aria-modal="true" aria-labelledby="airview-record-modal-title">
3
+ <header class="airview-record-modal-header">
4
+ <div>
5
+ <h2 id="airview-record-modal-title"><%= @resource.record_label(record).presence || "#{@resource.label} ##{record.id}" %></h2>
6
+ <p><%= @resource.label %> record</p>
7
+ </div>
8
+ <%= link_to "×", airview_modal_close_path, class: "airview-modal-close", data: { airview_modal_close: true } %>
9
+ </header>
10
+
11
+ <% visible_modal_fields = Array(@visible_fields) %>
12
+ <% hidden_modal_fields = @resource.fields.values - visible_modal_fields %>
13
+
14
+ <%= form_with url: resource_record_path(@resource.key, record), method: :patch, class: "airview-record-form" do %>
15
+ <% visible_modal_fields.each do |field| %>
16
+ <div class="airview-record-field">
17
+ <label>
18
+ <span><%= field.label %></span>
19
+ <% if field.editable? %>
20
+ <%= airview_input_for(record, field) %>
21
+ <% else %>
22
+ <div class="airview-record-readonly"><%= airview_cell_value(record, field) %></div>
23
+ <% end %>
24
+ </label>
25
+ </div>
26
+ <% end %>
27
+
28
+ <% if hidden_modal_fields.any? %>
29
+ <details class="airview-record-hidden-fields">
30
+ <summary>Hidden fields</summary>
31
+ <div class="airview-record-hidden-picker">
32
+ <% hidden_modal_fields.each do |field| %>
33
+ <label class="airview-check-row">
34
+ <%= check_box_tag nil, field.name, false, data: { airview_modal_field_toggle: field.name } %>
35
+ <span><%= field.label %></span>
36
+ </label>
37
+ <% end %>
38
+ </div>
39
+ </details>
40
+
41
+ <% hidden_modal_fields.each do |field| %>
42
+ <div class="airview-record-field is-hidden" data-airview-modal-hidden-field="<%= field.name %>">
43
+ <label>
44
+ <span><%= field.label %></span>
45
+ <% if field.editable? %>
46
+ <%= airview_input_for(record, field) %>
47
+ <% else %>
48
+ <div class="airview-record-readonly"><%= airview_cell_value(record, field) %></div>
49
+ <% end %>
50
+ </label>
51
+ </div>
52
+ <% end %>
53
+ <% end %>
54
+
55
+ <footer class="airview-record-modal-actions">
56
+ <%= submit_tag "Save changes" %>
57
+ <%= link_to "Delete...", request.path + "?#{request.query_parameters.except(:record_id).merge(delete_record_id: record.id).to_query}", class: "airview-danger-link" %>
58
+ <%= link_to "Cancel", airview_modal_close_path %>
59
+ </footer>
60
+ <% end %>
61
+ </section>
62
+ </div>
@@ -0,0 +1,5 @@
1
+ <main class="airview-empty">
2
+ <h1>Airview</h1>
3
+ <p>No resources are enabled yet.</p>
4
+ <%= link_to "Configure resources", setup_path %>
5
+ </main>
@@ -0,0 +1,258 @@
1
+ <div class="airview-shell">
2
+ <%= render "airview/shared/sidebar", resources: @resources, views: @views, active: :resource %>
3
+
4
+ <main class="airview-main airview-main--grid">
5
+ <header class="airview-basebar">
6
+ <div>
7
+ <h1><%= @resource.label %></h1>
8
+ <p><%= number_with_delimiter(@query.count) %> records</p>
9
+ </div>
10
+
11
+ <%= form_with url: (@active_view ? resource_view_records_path(@resource.key, @active_view) : resource_path(@resource.key)), method: :get, class: "airview-search" do %>
12
+ <% @visible_fields.each do |field| %>
13
+ <%= hidden_field_tag "columns[]", field.name unless @active_view %>
14
+ <% end %>
15
+ <% @filter_conditions.each_with_index do |condition, index| %>
16
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
17
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
18
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
19
+ <% end %>
20
+ <%= search_field_tag :q, params[:q], placeholder: "Search", autocomplete: "off" %>
21
+ <%= submit_tag "Search" %>
22
+ <% end %>
23
+ </header>
24
+
25
+ <section class="airview-gridbar">
26
+ <div class="airview-gridbar-left">
27
+ <span class="airview-view-name">▦ <%= @active_view&.name || "Default view" %></span>
28
+
29
+ <details class="airview-popover">
30
+ <summary>Hide fields</summary>
31
+ <div class="airview-popover-panel airview-popover-panel--narrow">
32
+ <% if @active_view %>
33
+ <%= form_with url: resource_view_path(@resource.key, @active_view), method: :patch do %>
34
+ <%= hidden_field_tag :columns_present, "1" %>
35
+ <% @filter_conditions.each_with_index do |condition, index| %>
36
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
37
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
38
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
39
+ <% end %>
40
+ <%= hidden_field_tag :sort, params[:sort] || @active_view.sorts&.dig("sort") %>
41
+ <%= hidden_field_tag :direction, params[:direction] || @active_view.sorts&.dig("direction") %>
42
+ <%= search_field_tag nil, nil, placeholder: "Find a field", class: "airview-popover-search", data: { airview_field_list_search: true } %>
43
+ <div class="airview-field-actions">
44
+ <button type="button" data-airview-fields-hide-all>Hide all</button>
45
+ <button type="button" data-airview-fields-show-all>Show all</button>
46
+ </div>
47
+ <div class="airview-field-option-list">
48
+ <% @sorted_fields.each do |field| %>
49
+ <label class="airview-check-row" data-airview-field-list-item="<%= "#{field.label} #{field.name}".downcase %>">
50
+ <%= check_box_tag "columns[]", field.name, @visible_fields.include?(field) %>
51
+ <span><%= field.label %></span>
52
+ </label>
53
+ <% end %>
54
+ </div>
55
+ <%= submit_tag "Apply" %>
56
+ <% end %>
57
+ <% else %>
58
+ <%= form_with url: resource_path(@resource.key), method: :get do %>
59
+ <%= hidden_field_tag :columns_present, "1" %>
60
+ <% @filter_conditions.each_with_index do |condition, index| %>
61
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
62
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
63
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
64
+ <% end %>
65
+ <%= hidden_field_tag :sort, params[:sort] %>
66
+ <%= hidden_field_tag :direction, params[:direction] %>
67
+ <%= search_field_tag nil, nil, placeholder: "Find a field", class: "airview-popover-search", data: { airview_field_list_search: true } %>
68
+ <div class="airview-field-actions">
69
+ <button type="button" data-airview-fields-hide-all>Hide all</button>
70
+ <button type="button" data-airview-fields-show-all>Show all</button>
71
+ </div>
72
+ <div class="airview-field-option-list">
73
+ <% @sorted_fields.each do |field| %>
74
+ <label class="airview-check-row" data-airview-field-list-item="<%= "#{field.label} #{field.name}".downcase %>">
75
+ <%= check_box_tag "columns[]", field.name, @visible_fields.include?(field) %>
76
+ <span><%= field.label %></span>
77
+ </label>
78
+ <% end %>
79
+ </div>
80
+ <%= submit_tag "Apply" %>
81
+ <% end %>
82
+ <% end %>
83
+ </div>
84
+ </details>
85
+
86
+ <details class="airview-popover">
87
+ <summary>Filter</summary>
88
+ <div class="airview-popover-panel airview-popover-panel--filter">
89
+ <%= form_with url: (@active_view ? resource_view_path(@resource.key, @active_view) : resource_path(@resource.key)), method: (@active_view ? :patch : :get) do %>
90
+ <% @visible_fields.each do |field| %>
91
+ <%= hidden_field_tag "columns[]", field.name %>
92
+ <% end %>
93
+ <%= hidden_field_tag :filters_present, "1" %>
94
+ <%= hidden_field_tag :sort, params[:sort] || @active_view&.sorts&.dig("sort") %>
95
+ <%= hidden_field_tag :direction, params[:direction] || @active_view&.sorts&.dig("direction") %>
96
+ <div class="airview-filter-title">In this view, show <%= @resource.label.downcase %></div>
97
+ <% rows = @filter_conditions %>
98
+ <% rows.each_with_index do |condition, index| %>
99
+ <% field = @resource.fields[(condition["field"] || condition[:field]).to_s.to_sym] || @visible_fields.first %>
100
+ <div class="airview-filter-condition" data-airview-filter-condition>
101
+ <span>Where</span>
102
+ <%= airview_filter_field_picker(index, field) %>
103
+ <%= select_tag "filters[#{index}][operator]", airview_filter_operator_options(field, condition["operator"] || condition[:operator]) %>
104
+ <%= airview_filter_value_input(index, field, condition["value"] || condition[:value]) %>
105
+ <button type="button" class="airview-filter-remove" data-airview-remove-filter>Remove</button>
106
+ </div>
107
+ <% end %>
108
+ <% template_field = @visible_fields.first %>
109
+ <template data-airview-filter-template>
110
+ <div class="airview-filter-condition" data-airview-filter-condition>
111
+ <span>Where</span>
112
+ <%= airview_filter_field_picker("__INDEX__", template_field) %>
113
+ <%= select_tag "filters[__INDEX__][operator]", airview_filter_operator_options(template_field) %>
114
+ <%= airview_filter_value_input("__INDEX__", template_field) %>
115
+ <button type="button" class="airview-filter-remove" data-airview-remove-filter>Remove</button>
116
+ </div>
117
+ </template>
118
+ <div class="airview-filter-actions">
119
+ <button type="button" data-airview-add-filter>Add condition</button>
120
+ </div>
121
+ <%= submit_tag @active_view ? "Save filters" : "Apply filters" %>
122
+ <% end %>
123
+ </div>
124
+ </details>
125
+
126
+ <details class="airview-popover">
127
+ <summary>Sort</summary>
128
+ <div class="airview-popover-panel airview-popover-panel--narrow">
129
+ <%= form_with url: (@active_view ? resource_view_path(@resource.key, @active_view) : resource_path(@resource.key)), method: (@active_view ? :patch : :get) do %>
130
+ <% @visible_fields.each do |field| %>
131
+ <%= hidden_field_tag "columns[]", field.name %>
132
+ <% end %>
133
+ <% @filter_conditions.each_with_index do |condition, index| %>
134
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
135
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
136
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
137
+ <% end %>
138
+ <label>
139
+ <span>Field</span>
140
+ <%= select_tag :sort, options_for_select(@sorted_fields.map { |field| [field.label, field.name] }, params[:sort] || @active_view&.sorts&.dig("sort")), include_blank: true %>
141
+ </label>
142
+ <label>
143
+ <span>Direction</span>
144
+ <%= select_tag :direction, options_for_select([["Ascending", "asc"], ["Descending", "desc"]], params[:direction] || @active_view&.sorts&.dig("direction")) %>
145
+ </label>
146
+ <%= submit_tag @active_view ? "Save sort" : "Apply sort" %>
147
+ <% end %>
148
+ </div>
149
+ </details>
150
+ </div>
151
+
152
+ <div class="airview-gridbar-right">
153
+ <details class="airview-popover">
154
+ <summary>Create record</summary>
155
+ <div class="airview-popover-panel airview-popover-panel--create">
156
+ <%= form_with url: resource_records_path(@resource.key), method: :post do %>
157
+ <% @resource.editable_fields.each do |field| %>
158
+ <label>
159
+ <span><%= field.label %></span>
160
+ <%= airview_input_for(@record, field) %>
161
+ </label>
162
+ <% end %>
163
+ <%= submit_tag "Create record" %>
164
+ <% end %>
165
+ </div>
166
+ </details>
167
+
168
+ <%= form_with url: resource_views_path(@resource.key), method: :post, class: "airview-save-view" do %>
169
+ <% @visible_fields.each do |field| %>
170
+ <%= hidden_field_tag "columns[]", field.name %>
171
+ <% end %>
172
+ <% @filter_conditions.each_with_index do |condition, index| %>
173
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
174
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
175
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
176
+ <% end %>
177
+ <%= hidden_field_tag :sort, params[:sort] %>
178
+ <%= hidden_field_tag :direction, params[:direction] %>
179
+ <%= text_field_tag :name, nil, placeholder: "New view name" %>
180
+ <%= submit_tag "Save view" %>
181
+ <% end %>
182
+ </div>
183
+ </section>
184
+
185
+ <section class="airview-grid-wrap" data-airview-grid>
186
+ <table class="airview-grid">
187
+ <colgroup>
188
+ <col class="airview-expand-col">
189
+ <% @visible_fields.each do |field| %>
190
+ <col data-airview-column-width="<%= field.name %>">
191
+ <% end %>
192
+ <col class="airview-actions-col">
193
+ </colgroup>
194
+ <thead>
195
+ <tr>
196
+ <th class="airview-expand-cell"></th>
197
+ <% @visible_fields.each do |field| %>
198
+ <th data-airview-column="<%= field.name %>">
199
+ <div class="airview-column-header">
200
+ <%= airview_sort_link(@resource, field) %>
201
+ <details class="airview-column-menu">
202
+ <summary>⋯</summary>
203
+ <div class="airview-column-menu-panel">
204
+ <%= button_to "Move left", airview_column_action_path, method: airview_column_action_method, params: airview_column_action_params(:left, field) %>
205
+ <%= button_to "Move right", airview_column_action_path, method: airview_column_action_method, params: airview_column_action_params(:right, field) %>
206
+ <%= button_to "Hide field", airview_column_action_path, method: airview_column_action_method, params: airview_column_action_params(:hide, field) %>
207
+ </div>
208
+ </details>
209
+ </div>
210
+ <span class="airview-column-resizer" data-airview-resizer></span>
211
+ </th>
212
+ <% end %>
213
+ <th class="airview-actions">Actions</th>
214
+ </tr>
215
+ </thead>
216
+ <tbody>
217
+ <% @records.each do |record| %>
218
+ <tr>
219
+ <td class="airview-expand-cell">
220
+ <%= link_to "↗", request.path + "?#{request.query_parameters.merge(record_id: record.id).to_query}", class: "airview-expand-button", title: "Expand record" %>
221
+ </td>
222
+ <% @visible_fields.each do |field| %>
223
+ <td tabindex="0" class="airview-cell airview-cell--<%= field.type %>">
224
+ <% if field.editable? %>
225
+ <%= form_with url: resource_record_path(@resource.key, record), method: :patch, class: "airview-cell-form" do %>
226
+ <%= airview_input_for(record, field) %>
227
+ <%= submit_tag "Save" %>
228
+ <% end %>
229
+ <% else %>
230
+ <%= airview_cell_value(record, field) %>
231
+ <% end %>
232
+ </td>
233
+ <% end %>
234
+ <td class="airview-actions">
235
+ <%= link_to "Delete", request.path + "?#{request.query_parameters.merge(delete_record_id: record.id).to_query}", class: "airview-danger-link" %>
236
+ </td>
237
+ </tr>
238
+ <% end %>
239
+ <tr class="airview-add-row">
240
+ <td colspan="<%= @visible_fields.size + 2 %>">+</td>
241
+ </tr>
242
+ </tbody>
243
+ </table>
244
+ </section>
245
+
246
+ <footer class="airview-pagination">
247
+ <% if @query.page > 1 %>
248
+ <%= link_to "Previous", request.path + "?#{request.query_parameters.merge(page: @query.page - 1).to_query}" %>
249
+ <% end %>
250
+ <% if @records.size == @query.limit %>
251
+ <%= link_to "Next", request.path + "?#{request.query_parameters.merge(page: @query.page + 1).to_query}" %>
252
+ <% end %>
253
+ </footer>
254
+ </main>
255
+
256
+ <%= render "record_modal", record: @expanded_record if @expanded_record %>
257
+ <%= render "delete_modal", record: @delete_record if @delete_record %>
258
+ </div>
@@ -0,0 +1,73 @@
1
+ <% if definition.errors.any? %>
2
+ <section class="airview-flash airview-flash--alert">
3
+ <%= definition.errors.full_messages.to_sentence %>
4
+ </section>
5
+ <% end %>
6
+
7
+ <%= form_with model: definition, url: url, method: method, scope: :resource_definition, class: "airview-setup-form" do |form| %>
8
+ <section class="airview-create">
9
+ <div class="airview-create-grid">
10
+ <label>
11
+ <span>Key</span>
12
+ <%= form.text_field :key, readonly: definition.persisted? %>
13
+ </label>
14
+ <label>
15
+ <span>Model</span>
16
+ <%= form.text_field :record_class_name, readonly: definition.persisted? %>
17
+ </label>
18
+ <label>
19
+ <span>Label</span>
20
+ <%= form.text_field :label %>
21
+ </label>
22
+ <label>
23
+ <span>Label method</span>
24
+ <%= form.text_field :label_method %>
25
+ </label>
26
+ <label class="airview-checkbox-label">
27
+ <%= form.check_box :enabled %>
28
+ <span>Enabled in Airview</span>
29
+ </label>
30
+ </div>
31
+ </section>
32
+
33
+ <section class="airview-grid-wrap">
34
+ <table class="airview-grid airview-setup-fields">
35
+ <thead>
36
+ <tr>
37
+ <th>Visible</th>
38
+ <th>Readonly</th>
39
+ <th>Name</th>
40
+ <th>Label</th>
41
+ <th>Type</th>
42
+ <th>Order</th>
43
+ <th>Target model</th>
44
+ <th>Target label</th>
45
+ </tr>
46
+ </thead>
47
+ <tbody>
48
+ <%= form.fields_for :field_definitions do |field_form| %>
49
+ <tr>
50
+ <td><%= field_form.check_box :visible %></td>
51
+ <td><%= field_form.check_box :read_only %></td>
52
+ <td>
53
+ <%= field_form.hidden_field :id if field_form.object.persisted? %>
54
+ <%= field_form.hidden_field :name %>
55
+ <%= field_form.hidden_field :association_name %>
56
+ <%= field_form.object.name %>
57
+ </td>
58
+ <td><%= field_form.text_field :label %></td>
59
+ <td><%= field_form.select :field_type, Airview::Field::TYPES.map { |type| [type.to_s.humanize, type] } %></td>
60
+ <td><%= field_form.number_field :position, min: 0 %></td>
61
+ <td><%= field_form.text_field :target_model_name %></td>
62
+ <td><%= field_form.text_field :target_label_method %></td>
63
+ </tr>
64
+ <% end %>
65
+ </tbody>
66
+ </table>
67
+ </section>
68
+
69
+ <footer class="airview-pagination">
70
+ <%= form.submit "Save resource" %>
71
+ <%= link_to "Cancel", setup_path %>
72
+ </footer>
73
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <div class="airview-shell">
2
+ <%= render "airview/shared/sidebar", resources: Airview.resources.values, active: :setup %>
3
+
4
+ <main class="airview-main">
5
+ <header class="airview-toolbar">
6
+ <div>
7
+ <h1>Edit <%= @definition.label %></h1>
8
+ <p><%= @definition.record_class_name %></p>
9
+ </div>
10
+
11
+ <%= button_to "Remove resource", setup_resource_path(@definition), method: :delete, form: { data: { turbo_confirm: "Remove this Airview resource?" } } %>
12
+ </header>
13
+
14
+ <%= render "form", definition: @definition, url: setup_resource_path(@definition), method: :patch %>
15
+ </main>
16
+ </div>
@@ -0,0 +1,52 @@
1
+ <div class="airview-shell">
2
+ <%= render "airview/shared/sidebar", resources: Airview.resources.values, active: :setup %>
3
+
4
+ <main class="airview-main">
5
+ <header class="airview-toolbar">
6
+ <div>
7
+ <h1>Resource setup</h1>
8
+ <p>Enable Rails models and configure the fields Airview exposes.</p>
9
+ </div>
10
+ </header>
11
+
12
+ <section class="airview-grid-wrap">
13
+ <table class="airview-grid airview-setup-table">
14
+ <thead>
15
+ <tr>
16
+ <th>Model</th>
17
+ <th>Table</th>
18
+ <th>Columns</th>
19
+ <th>Status</th>
20
+ <th class="airview-actions">Actions</th>
21
+ </tr>
22
+ </thead>
23
+ <tbody>
24
+ <% @models.each do |model| %>
25
+ <% definition = @definitions_by_model[model.name] %>
26
+ <tr>
27
+ <td><strong><%= model.name %></strong></td>
28
+ <td><%= model.table_name %></td>
29
+ <td><%= model.columns.size %></td>
30
+ <td>
31
+ <% if definition&.enabled? %>
32
+ Enabled
33
+ <% elsif definition %>
34
+ Draft
35
+ <% else %>
36
+ Not configured
37
+ <% end %>
38
+ </td>
39
+ <td class="airview-actions">
40
+ <% if definition %>
41
+ <%= link_to "Edit", edit_setup_resource_path(definition) %>
42
+ <% else %>
43
+ <%= link_to "Configure", new_setup_resource_path(model_name: model.name) %>
44
+ <% end %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+ </section>
51
+ </main>
52
+ </div>
@@ -0,0 +1,14 @@
1
+ <div class="airview-shell">
2
+ <%= render "airview/shared/sidebar", resources: Airview.resources.values, active: :setup %>
3
+
4
+ <main class="airview-main">
5
+ <header class="airview-toolbar">
6
+ <div>
7
+ <h1>Configure <%= @model.name %></h1>
8
+ <p>Review inferred fields before exposing this model in Airview.</p>
9
+ </div>
10
+ </header>
11
+
12
+ <%= render "form", definition: @definition, url: setup_resources_path, method: :post %>
13
+ </main>
14
+ </div>
@@ -0,0 +1,76 @@
1
+ <aside class="airview-sidebar">
2
+ <div class="airview-brand">Airview</div>
3
+ <nav class="airview-resource-list">
4
+ <%= link_to "Setup", setup_path, class: ["airview-resource-link", ("is-active" if active == :setup)] %>
5
+ <% resources.each do |resource| %>
6
+ <%= link_to resource.label, resource_path(resource.key), class: ["airview-resource-link", ("is-active" if defined?(@resource) && @resource&.key == resource.key)] %>
7
+ <% if defined?(views) && defined?(@resource) && @resource&.key == resource.key %>
8
+ <div class="airview-view-list">
9
+ <%= link_to "▦ Default view", resource_path(resource.key), class: ["airview-view-link", ("is-active" unless defined?(@active_view) && @active_view)] %>
10
+ <% grouped_views = views.group_by { |view| view.folder.presence } %>
11
+ <% unfiled_views = grouped_views.delete(nil) || [] %>
12
+ <% folder_groups = [[nil, unfiled_views]] + grouped_views.sort_by { |folder, _folder_views| folder.downcase } %>
13
+
14
+ <% folder_groups.each do |folder, folder_views| %>
15
+ <% next if folder_views.empty? %>
16
+ <% if folder.present? %>
17
+ <div class="airview-view-folder"><%= folder %></div>
18
+ <% end %>
19
+ <% folder_views.each do |view| %>
20
+ <% view_filters = view.filters.is_a?(Hash) ? view.filters.values : Array(view.filters) %>
21
+ <div class="airview-view-row">
22
+ <%= link_to "▦ #{view.name}", resource_view_records_path(resource.key, view), class: ["airview-view-link", ("is-active" if defined?(@active_view) && @active_view == view)] %>
23
+ <details class="airview-view-menu">
24
+ <summary>⋯</summary>
25
+ <div class="airview-view-menu-panel">
26
+ <%= form_with url: resource_view_path(resource.key, view), method: :patch, class: "airview-view-edit-form" do %>
27
+ <% view.columns.each do |column| %>
28
+ <%= hidden_field_tag "columns[]", column %>
29
+ <% end %>
30
+ <% view_filters.each_with_index do |condition, index| %>
31
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
32
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
33
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
34
+ <% end %>
35
+ <%= hidden_field_tag :sort, view.sorts&.dig("sort") %>
36
+ <%= hidden_field_tag :direction, view.sorts&.dig("direction") %>
37
+ <%= text_field_tag :name, view.name, aria: { label: "View name" } %>
38
+ <%= text_field_tag :folder, view.folder, placeholder: "Folder", aria: { label: "Folder" } %>
39
+ <%= submit_tag "Save" %>
40
+ <% end %>
41
+ <%= button_to "Delete view", resource_view_path(resource.key, view), method: :delete, class: "airview-view-delete" %>
42
+ </div>
43
+ </details>
44
+ </div>
45
+ <% end %>
46
+ <% end %>
47
+
48
+ <details class="airview-new-view">
49
+ <summary>+ Create view</summary>
50
+ <div class="airview-new-view-panel">
51
+ <%= form_with url: resource_views_path(resource.key), method: :post, class: "airview-view-edit-form" do %>
52
+ <% if defined?(@visible_fields) && @visible_fields %>
53
+ <% @visible_fields.each do |field| %>
54
+ <%= hidden_field_tag "columns[]", field.name %>
55
+ <% end %>
56
+ <% end %>
57
+ <% if defined?(@filter_conditions) && @filter_conditions %>
58
+ <% @filter_conditions.each_with_index do |condition, index| %>
59
+ <%= hidden_field_tag "filters[#{index}][field]", condition["field"] %>
60
+ <%= hidden_field_tag "filters[#{index}][operator]", condition["operator"] %>
61
+ <%= hidden_field_tag "filters[#{index}][value]", condition["value"] %>
62
+ <% end %>
63
+ <% end %>
64
+ <%= hidden_field_tag :sort, params[:sort] || @active_view&.sorts&.dig("sort") %>
65
+ <%= hidden_field_tag :direction, params[:direction] || @active_view&.sorts&.dig("direction") %>
66
+ <%= text_field_tag :name, nil, placeholder: "View name", aria: { label: "View name" } %>
67
+ <%= text_field_tag :folder, nil, placeholder: "Folder", aria: { label: "Folder" } %>
68
+ <%= submit_tag "Create view" %>
69
+ <% end %>
70
+ </div>
71
+ </details>
72
+ </div>
73
+ <% end %>
74
+ <% end %>
75
+ </nav>
76
+ </aside>
@@ -0,0 +1,21 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Airview</title>
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= stylesheet_link_tag "airview/application", media: "all" %>
9
+ <%= javascript_include_tag "airview/application", defer: true %>
10
+ </head>
11
+ <body class="airview">
12
+ <% if notice.present? %>
13
+ <div class="airview-flash airview-flash--notice"><%= notice %></div>
14
+ <% end %>
15
+ <% if alert.present? %>
16
+ <div class="airview-flash airview-flash--alert"><%= alert %></div>
17
+ <% end %>
18
+
19
+ <%= yield %>
20
+ </body>
21
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ Airview::Engine.routes.draw do
4
+ root to: "resources#index"
5
+
6
+ get "/setup", to: "setup#index", as: :setup
7
+ get "/setup/resources/new", to: "setup#new", as: :new_setup_resource
8
+ post "/setup/resources", to: "setup#create", as: :setup_resources
9
+ get "/setup/resources/:id/edit", to: "setup#edit", as: :edit_setup_resource
10
+ patch "/setup/resources/:id", to: "setup#update", as: :setup_resource
11
+ delete "/setup/resources/:id", to: "setup#destroy"
12
+
13
+ get "/resources", to: "resources#index", as: :resources
14
+ get "/resources/:key/views/:view_id", to: "resources#show", as: :resource_view_records
15
+ get "/resources/:key", to: "resources#show", as: :resource
16
+ post "/resources/:key/records", to: "resources#create", as: :resource_records
17
+ patch "/resources/:key/records/:id", to: "resources#update", as: :resource_record
18
+ delete "/resources/:key/records/:id", to: "resources#destroy"
19
+ get "/resources/:key/references/:field", to: "resources#references", as: :resource_references
20
+ post "/resources/:key/views", to: "views#create", as: :resource_views
21
+ patch "/resources/:key/views/:id", to: "views#update"
22
+ delete "/resources/:key/views/:id", to: "views#destroy", as: :resource_view
23
+ end