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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +65 -0
- data/app/assets/javascripts/airview/application.js +425 -0
- data/app/assets/stylesheets/airview/application.css +1239 -0
- data/app/controllers/airview/application_controller.rb +7 -0
- data/app/controllers/airview/resources_controller.rb +261 -0
- data/app/controllers/airview/setup_controller.rb +90 -0
- data/app/controllers/airview/views_controller.rb +76 -0
- data/app/helpers/airview/application_helper.rb +479 -0
- data/app/javascript/airview/controllers/grid_controller.js +7 -0
- data/app/models/airview/application_record.rb +7 -0
- data/app/models/airview/field_definition.rb +22 -0
- data/app/models/airview/resource_definition.rb +48 -0
- data/app/models/airview/view.rb +27 -0
- data/app/views/airview/resources/_delete_modal.html.erb +49 -0
- data/app/views/airview/resources/_record_modal.html.erb +62 -0
- data/app/views/airview/resources/empty.html.erb +5 -0
- data/app/views/airview/resources/show.html.erb +258 -0
- data/app/views/airview/setup/_form.html.erb +73 -0
- data/app/views/airview/setup/edit.html.erb +16 -0
- data/app/views/airview/setup/index.html.erb +52 -0
- data/app/views/airview/setup/new.html.erb +14 -0
- data/app/views/airview/shared/_sidebar.html.erb +76 -0
- data/app/views/layouts/airview/application.html.erb +21 -0
- data/config/routes.rb +23 -0
- data/db/migrate/20260806000000_create_airview_views.rb +57 -0
- data/lib/airview/configuration.rb +6 -0
- data/lib/airview/engine.rb +15 -0
- data/lib/airview/field.rb +81 -0
- data/lib/airview/model_discovery.rb +38 -0
- data/lib/airview/query.rb +194 -0
- data/lib/airview/resource.rb +51 -0
- data/lib/airview/resource_builder.rb +62 -0
- data/lib/airview/schema_inference.rb +196 -0
- data/lib/airview/version.rb +5 -0
- data/lib/airview.rb +93 -0
- data/lib/generators/airview/install/install_generator.rb +38 -0
- data/lib/generators/airview/install/templates/add_folders_to_airview_views.rb +8 -0
- data/lib/generators/airview/install/templates/airview.rb +12 -0
- data/lib/generators/airview/install/templates/create_airview_views.rb +57 -0
- data/sig/airview.rbs +54 -0
- 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,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
|