upmin-admin 0.1.01 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +10 -18
- data/Rakefile +18 -18
- data/app/assets/javascripts/upmin/attributes/datetime.js +37 -11
- data/app/assets/stylesheets/upmin/application.css +0 -1
- data/app/assets/stylesheets/upmin/base.css.scss +8 -1
- data/app/assets/stylesheets/upmin/dashboard.css +16 -0
- data/app/assets/stylesheets/upmin/instances.css.scss +7 -0
- data/app/controllers/upmin/dashboard_controller.rb +11 -0
- data/app/controllers/upmin/models_controller.rb +28 -9
- data/app/views/layouts/upmin/application.html.haml +6 -2
- data/app/views/upmin/dashboard/_chart.html.haml +9 -0
- data/app/views/upmin/dashboard/index.html.haml +14 -0
- data/app/views/upmin/models/search.html.haml +20 -4
- data/app/views/upmin/partials/attributes/_boolean.html.haml +1 -6
- data/app/views/upmin/partials/attributes/_date.html.haml +26 -0
- data/app/views/upmin/partials/attributes/_enum.html.haml +9 -0
- data/app/views/upmin/partials/attributes/_money_cents.html.haml +10 -0
- data/app/views/upmin/partials/models/_model.html.haml +5 -6
- data/app/views/upmin/partials/models/_new_model.html.haml +1 -1
- data/app/views/upmin/partials/search_boxes/_ransack_search_box.html.haml +6 -1
- data/config/routes.rb +3 -5
- data/lib/generators/upmin/install_generator.rb +24 -0
- data/lib/generators/upmin/templates/initializer.rb +13 -0
- data/lib/upmin/active_record/model.rb +15 -1
- data/lib/upmin/admin.rb +2 -1
- data/lib/upmin/attribute.rb +4 -1
- data/lib/upmin/configuration.rb +1 -0
- data/lib/upmin/data_mapper/model.rb +10 -0
- data/lib/upmin/engine.rb +4 -3
- data/lib/upmin/model.rb +37 -5
- data/lib/upmin/railtie.rb +1 -0
- data/lib/upmin/railties/dashboard.rb +88 -0
- data/lib/upmin/railties/render_helpers.rb +2 -0
- data/lib/upmin/version.rb +1 -1
- data/spec/features/delete_model_spec.rb +38 -0
- data/spec/features/edit_model_spec.rb +1 -1
- data/spec/features/enum_attributes_spec.rb +24 -0
- data/spec/features/navbar_spec.rb +4 -4
- data/spec/features/new_model_spec.rb +1 -1
- data/spec/features/search_spec.rb +17 -27
- data/spec/lib/attribute_spec.rb +15 -0
- data/spec/lib/upmin/active_record/model_spec.rb +15 -0
- data/spec/spec_helper.rb +8 -0
- metadata +49 -4
- data/app/views/upmin/models/dashboard.html.haml +0 -9
@@ -0,0 +1,26 @@
|
|
1
|
+
- date = attribute.value.nil? ? nil : attribute.value
|
2
|
+
|
3
|
+
.form-group{class: attribute.errors? ? "has-error" : ""}
|
4
|
+
%label{for: attribute.form_id}
|
5
|
+
= attribute.label_name
|
6
|
+
|
7
|
+
- if attribute.editable? && f = form_builder
|
8
|
+
.row.date-attribute{class: attribute.form_id}
|
9
|
+
= f.hidden_field(attribute.name, value: date)
|
10
|
+
|
11
|
+
.col-md-12
|
12
|
+
.input-group.pikadate
|
13
|
+
%input.form-control{type: :text, value: date, id: "#{attribute.form_id}-date"}
|
14
|
+
%span.input-group-addon
|
15
|
+
%span.glyphicon.glyphicon-calendar
|
16
|
+
|
17
|
+
- content_for(:javascript) do
|
18
|
+
:javascript
|
19
|
+
$(document).ready(function() {
|
20
|
+
window.Upmin.Attributes.Date("#{attribute.form_id}");
|
21
|
+
});
|
22
|
+
|
23
|
+
- else
|
24
|
+
%p.well
|
25
|
+
-# TODO(jon): Make this show an uneditable date.
|
26
|
+
= date
|
@@ -0,0 +1,9 @@
|
|
1
|
+
.form-group{class: attribute.errors? ? "has-error" : ""}
|
2
|
+
%label{for: attribute.form_id}
|
3
|
+
= attribute.label_name
|
4
|
+
|
5
|
+
.enum
|
6
|
+
- attribute.enum_options.each do |option|
|
7
|
+
%label.radio-inline
|
8
|
+
= form_builder.radio_button attribute.name, option, disabled: !attribute.editable?
|
9
|
+
= option.humanize
|
@@ -0,0 +1,10 @@
|
|
1
|
+
-# based on "unknown" attribute type partial; does not allow editing (would
|
2
|
+
-# have to parse the string back into cents, which is fraught with danger)
|
3
|
+
.form-group{class: attribute.errors? ? "has-error" : ""}
|
4
|
+
%label{for: attribute.form_id}
|
5
|
+
= attribute.label_name.gsub('cents', '')
|
6
|
+
%p.well
|
7
|
+
- if attribute.value.respond_to?(:/)
|
8
|
+
= number_to_currency(attribute.value / 100.to_f)
|
9
|
+
- else
|
10
|
+
= attribute.value
|
@@ -2,23 +2,24 @@
|
|
2
2
|
%h3
|
3
3
|
= model.title
|
4
4
|
|
5
|
-
|
5
|
+
= link_to(model.path, method: :delete, class: "btn btn-sm btn-danger delete pull-right", title: "Delete #{model.title}.", data: {confirm: "Are you sure?"}) do
|
6
|
+
%span.glyphicon.glyphicon-trash.white
|
7
|
+
|
6
8
|
%br
|
7
9
|
%h3{style: "color: #333;"}
|
8
10
|
Attributes
|
9
|
-
%hr
|
10
11
|
|
12
|
+
%hr
|
11
13
|
.attributes
|
12
14
|
-# Yes this is meant to be model.model - this is the raw rails model instance.
|
13
15
|
= form_for(model, url: model.path, html: { method: :put }) do |f|
|
14
16
|
|
15
17
|
-# Render each attribute
|
16
|
-
- model.
|
18
|
+
- model.form_attributes.each do |attribute|
|
17
19
|
= up_render(attribute, locals: { form_builder: f })
|
18
20
|
|
19
21
|
= f.submit("Save", class: "btn btn-primary")
|
20
22
|
|
21
|
-
|
22
23
|
- if model.associations.any?
|
23
24
|
%br
|
24
25
|
%br
|
@@ -30,7 +31,6 @@
|
|
30
31
|
- model.associations.each do |association|
|
31
32
|
= up_render(association)
|
32
33
|
|
33
|
-
|
34
34
|
- if model.actions.any?
|
35
35
|
%br
|
36
36
|
%br
|
@@ -41,4 +41,3 @@
|
|
41
41
|
.actions
|
42
42
|
- model.actions.each do |action|
|
43
43
|
= up_render(action)
|
44
|
-
|
@@ -15,7 +15,7 @@
|
|
15
15
|
= form_for(model, url: model.create_path, html: { method: :post }) do |f|
|
16
16
|
|
17
17
|
-# Render each attribute
|
18
|
-
- model.
|
18
|
+
- model.form_attributes.each do |attribute|
|
19
19
|
= up_render(attribute, locals: { form_builder: f })
|
20
20
|
|
21
21
|
= f.submit("Create", class: "btn btn-primary")
|
@@ -22,7 +22,7 @@
|
|
22
22
|
.input-group-addon to
|
23
23
|
= number_field(:q, "#{attr_name}_lteq", class: "form-control")
|
24
24
|
|
25
|
-
- if
|
25
|
+
- if [:datetime, :date].include?(type) && Rails::VERSION::MAJOR == 4
|
26
26
|
-# TODO(jon): Add date fields to search boxes for Rails 3
|
27
27
|
.form-group
|
28
28
|
= label(:q, "#{attr_name}_cont", attr_name.to_s.capitalize.gsub("_", " "))
|
@@ -32,5 +32,10 @@
|
|
32
32
|
To
|
33
33
|
= date_field(:q, "#{attr_name}_lteq", class: "form-control")
|
34
34
|
|
35
|
+
- if type == :enum
|
36
|
+
.form-group
|
37
|
+
= label(:q, "#{attr_name}_cont", attr_name.to_s.capitalize.gsub("_", " "))
|
38
|
+
= select(:q, "#{attr_name}_eq", klass.model_class.defined_enums[attr_name.to_s], { include_blank: true }, { class: "form-control" })
|
39
|
+
|
35
40
|
= submit_tag("Search", class: "btn btn-primary btn-block")
|
36
41
|
= link_to("Clear All", klass.search_path, class: "btn btn-default btn-block")
|
data/config/routes.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
1
|
Upmin::Engine.routes.draw do
|
2
|
-
root to: "
|
3
|
-
|
4
|
-
# TODO(jon): Add support for dashboards (or some other main page).
|
5
|
-
# TODO(jon): Move dashboards to an appropriate controller
|
6
|
-
get "/", as: :upmin_dashboard, controller: :models, action: :dashboard
|
2
|
+
root to: "dashboard#index"
|
3
|
+
get "/", as: :upmin_dashboard, controller: :dashboard, action: :index
|
7
4
|
|
8
5
|
scope "m" do
|
9
6
|
scope "/:klass" do
|
@@ -15,6 +12,7 @@ Upmin::Engine.routes.draw do
|
|
15
12
|
scope "/i/:id" do
|
16
13
|
get "/", as: :upmin_model, controller: :models, action: :show
|
17
14
|
put "/", controller: :models, action: :update
|
15
|
+
delete "/", controller: :models, action: :destroy
|
18
16
|
|
19
17
|
post "/:method", as: :upmin_action, controller: :models, action: :action
|
20
18
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Upmin
|
2
|
+
module Generators
|
3
|
+
class InstallGenerator < Rails::Generators::Base
|
4
|
+
|
5
|
+
desc 'Creates an initializer file at config/initializers/upmin.rb and adds Upmin route to config/routes.rb'
|
6
|
+
|
7
|
+
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
def copy_initializer
|
10
|
+
template 'initializer.rb', 'config/initializers/upmin.rb'
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_route
|
14
|
+
route "mount Upmin::Engine => '/admin'"
|
15
|
+
end
|
16
|
+
|
17
|
+
def messages
|
18
|
+
log "\n # Global configuration can be edited in 'config/initializers/upmin.rb'\n"
|
19
|
+
log "\n # You can access Upmin at: '/admin'. Modify config/routes.rb if this conflicts with an existing route.\n\n"
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Upmin Initializer
|
2
|
+
|
3
|
+
Upmin.configure do |config|
|
4
|
+
# List of models you want to appear in your Upmin Admin pages - default is all Models
|
5
|
+
# config.models = [:user, :order, :product]
|
6
|
+
|
7
|
+
# Overrides the default list of colors used to highlight models.
|
8
|
+
# config.colors = [::light_blue, :blue_green, :red, :yellow, :orange, :purple, :dark_blue, :dark_red, :green]
|
9
|
+
|
10
|
+
# Overrides the default of number of items displayed per page.
|
11
|
+
# This can also be set per model, see: https://github.com/upmin/upmin-admin-ruby/wiki/Customizing-Models
|
12
|
+
# items_per_page = 30
|
13
|
+
end
|
@@ -27,10 +27,24 @@ module Upmin::ActiveRecord
|
|
27
27
|
|
28
28
|
def attribute_type(attribute)
|
29
29
|
adapter = model_class.columns_hash[attribute.to_s]
|
30
|
-
|
30
|
+
if adapter
|
31
|
+
if attribute.in? enum_attributes
|
32
|
+
return :enum
|
33
|
+
else
|
34
|
+
return adapter.type
|
35
|
+
end
|
36
|
+
end
|
31
37
|
return :unknown
|
32
38
|
end
|
33
39
|
|
40
|
+
def enum_attributes
|
41
|
+
if model_class.respond_to? :defined_enums
|
42
|
+
model_class.defined_enums.keys.map(&:to_sym)
|
43
|
+
else
|
44
|
+
[]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
34
48
|
def associations
|
35
49
|
return @associations if defined?(@associations)
|
36
50
|
|
data/lib/upmin/admin.rb
CHANGED
@@ -29,6 +29,7 @@ require "upmin/railties/active_record"
|
|
29
29
|
require "upmin/railties/paginator"
|
30
30
|
require "upmin/railties/render"
|
31
31
|
require "upmin/railties/render_helpers"
|
32
|
+
require "upmin/railties/dashboard"
|
32
33
|
require "upmin/railtie"
|
33
34
|
|
34
35
|
# gems and stuff we use
|
@@ -36,7 +37,7 @@ require "jquery-rails"
|
|
36
37
|
require "ransack"
|
37
38
|
require "haml"
|
38
39
|
require "sass-rails"
|
39
|
-
|
40
|
+
require "chartkick"
|
40
41
|
|
41
42
|
require "ostruct"
|
42
43
|
|
data/lib/upmin/attribute.rb
CHANGED
@@ -10,7 +10,7 @@ module Upmin
|
|
10
10
|
|
11
11
|
def value
|
12
12
|
# TODO(jon): Add some way to handle exceptions.
|
13
|
-
return model.send(name)
|
13
|
+
return model.model.send(name)
|
14
14
|
end
|
15
15
|
|
16
16
|
def type
|
@@ -62,6 +62,9 @@ module Upmin
|
|
62
62
|
return "#{form_id}_is_nil"
|
63
63
|
end
|
64
64
|
|
65
|
+
def enum_options
|
66
|
+
model.class.model_class.defined_enums[name.to_s].keys
|
67
|
+
end
|
65
68
|
|
66
69
|
private
|
67
70
|
|
data/lib/upmin/configuration.rb
CHANGED
@@ -60,3 +60,13 @@ module Upmin::DataMapper
|
|
60
60
|
|
61
61
|
end
|
62
62
|
end
|
63
|
+
|
64
|
+
module DataMapper
|
65
|
+
module ClassMethods
|
66
|
+
#Returns an ActiveModel::Name object needed by Kaminari to render page_entries_info.
|
67
|
+
def model_name
|
68
|
+
return@_model_name ||= ActiveModel::Name.new(self)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
Model.append_extensions(ClassMethods)
|
72
|
+
end
|
data/lib/upmin/engine.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
# require
|
2
|
-
#
|
3
|
-
require 'kaminari'
|
1
|
+
# require kaminari here to affect load order so that custom Kaminari (bootstrap) views load from upmin,
|
2
|
+
# unless will_paginate is used in the parent Rails app.
|
3
|
+
require 'kaminari' unless defined?(WillPaginate)
|
4
4
|
|
5
5
|
module Upmin
|
6
6
|
class Engine < ::Rails::Engine
|
@@ -9,3 +9,4 @@ module Upmin
|
|
9
9
|
config.autoload_paths << "#{::Rails.root}/app/upmin/models"
|
10
10
|
end
|
11
11
|
end
|
12
|
+
|
data/lib/upmin/model.rb
CHANGED
@@ -50,6 +50,15 @@ module Upmin
|
|
50
50
|
return @attributes
|
51
51
|
end
|
52
52
|
|
53
|
+
def form_attributes
|
54
|
+
return @form_attributes if defined?(@form_attributes)
|
55
|
+
@form_attributes = []
|
56
|
+
self.class.form_attributes.each do |attr_name|
|
57
|
+
@form_attributes << Upmin::Attribute.new(self, attr_name)
|
58
|
+
end
|
59
|
+
return @form_attributes
|
60
|
+
end
|
61
|
+
|
53
62
|
def associations
|
54
63
|
return @associations if defined?(@associations)
|
55
64
|
@associations = []
|
@@ -170,10 +179,12 @@ module Upmin
|
|
170
179
|
end
|
171
180
|
|
172
181
|
def Model.humanized_name(type = :plural)
|
173
|
-
names = model_class_name.split(/(?=[A-Z])/).map{|n| n.gsub(":", "")}
|
182
|
+
names = @display_name ? [@display_name] : model_class_name.split(/(?=[A-Z])/).map{|n| n.gsub(":", "")}
|
183
|
+
|
174
184
|
if type == :plural
|
175
185
|
names[names.length-1] = names.last.pluralize
|
176
186
|
end
|
187
|
+
|
177
188
|
return names.join(" ")
|
178
189
|
end
|
179
190
|
|
@@ -243,21 +254,38 @@ module Upmin
|
|
243
254
|
@extra_attrs << attribute.to_sym if attribute
|
244
255
|
end
|
245
256
|
|
257
|
+
def Model.form_attribute(attribute = nil)
|
258
|
+
@extra_form_attrs = [] unless defined?(@extra_form_attrs)
|
259
|
+
@extra_form_attrs << attribute.to_sym if attribute
|
260
|
+
end
|
261
|
+
|
246
262
|
# Sets the attributes to the provided attributes # if any are any provided.
|
247
263
|
# If no attributes are provided then the
|
248
264
|
# attributes are set to the default attributes of
|
249
265
|
# the model class.
|
250
|
-
def Model.attributes(*
|
266
|
+
def Model.attributes(*attrs)
|
251
267
|
@extra_attrs = [] unless defined?(@extra_attrs)
|
252
268
|
|
253
|
-
if
|
254
|
-
@attributes =
|
269
|
+
if attrs.any?
|
270
|
+
@attributes = attrs.map{|a| a.to_sym}
|
255
271
|
end
|
256
272
|
@attributes ||= default_attributes
|
257
273
|
|
258
274
|
return (@attributes + @extra_attrs).uniq
|
259
275
|
end
|
260
276
|
|
277
|
+
# Edit/Create form specific attributes
|
278
|
+
def Model.form_attributes(*attrs)
|
279
|
+
@extra_form_attrs = [] unless defined?(@extra_form_attrs)
|
280
|
+
|
281
|
+
if attrs.any?
|
282
|
+
@form_attributes = attrs.map{|a| a.to_sym}
|
283
|
+
end
|
284
|
+
@form_attributes ||= default_attributes
|
285
|
+
|
286
|
+
return (@form_attributes + @extra_form_attrs).uniq
|
287
|
+
end
|
288
|
+
|
261
289
|
# Add a single action to upmin actions. If this is called
|
262
290
|
# before upmin_actions the actions will not include any defaults
|
263
291
|
# actions.
|
@@ -276,7 +304,7 @@ module Upmin
|
|
276
304
|
def Model.actions(*actions)
|
277
305
|
if actions.any?
|
278
306
|
# set the actions
|
279
|
-
@actions = actions.map
|
307
|
+
@actions = actions.map(&:to_sym)
|
280
308
|
end
|
281
309
|
@actions ||= []
|
282
310
|
return @actions
|
@@ -286,6 +314,10 @@ module Upmin
|
|
286
314
|
return @items_per_page ||= items
|
287
315
|
end
|
288
316
|
|
317
|
+
def Model.display_name (name)
|
318
|
+
return @display_name ||= name
|
319
|
+
end
|
320
|
+
|
289
321
|
|
290
322
|
###########################################################
|
291
323
|
### Methods that need to be to be overridden. If the
|
data/lib/upmin/railtie.rb
CHANGED
@@ -0,0 +1,88 @@
|
|
1
|
+
module Upmin::Railties
|
2
|
+
module Dashboard
|
3
|
+
|
4
|
+
def group_by_best_fit(limit = 30)
|
5
|
+
return send "group_by_#{grouping limit}"
|
6
|
+
end
|
7
|
+
|
8
|
+
# Selects the time period with no more than <limit> entries
|
9
|
+
def grouping(limit = 30)
|
10
|
+
seconds = range_in_seconds
|
11
|
+
if seconds/1.day < limit
|
12
|
+
return 'day'
|
13
|
+
elsif seconds/1.week < limit
|
14
|
+
return 'week'
|
15
|
+
elsif seconds/1.month < limit
|
16
|
+
return 'month'
|
17
|
+
else
|
18
|
+
return 'year'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Date range manipulation
|
24
|
+
#
|
25
|
+
def range_in_seconds
|
26
|
+
return last_date - first_date
|
27
|
+
end
|
28
|
+
|
29
|
+
def first_date
|
30
|
+
order('date(created_at) ASC').first.try(:created_at) || Time.now
|
31
|
+
end
|
32
|
+
|
33
|
+
def last_date
|
34
|
+
order('date(created_at) ASC').last.try(:created_at) || Time.now
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Group by
|
39
|
+
#
|
40
|
+
def group_by_day
|
41
|
+
dates = where.not(created_at: nil).group('date(created_at)').order('date(created_at) ASC').count
|
42
|
+
# Convert sqlite String date keys to Date keys
|
43
|
+
dates.map { |k, v| [Date.parse(k), v] } if dates.keys.first.is_a? String
|
44
|
+
end
|
45
|
+
|
46
|
+
def group_by_week
|
47
|
+
result = Hash.new(0)
|
48
|
+
group_by_day.each_with_object(result) { |i, a| a[i[0].beginning_of_week.strftime] += i[1] }
|
49
|
+
return result
|
50
|
+
end
|
51
|
+
|
52
|
+
def group_by_month
|
53
|
+
return group_by_strftime('%b %Y')
|
54
|
+
end
|
55
|
+
|
56
|
+
def group_by_year
|
57
|
+
return group_by_strftime('%Y')
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Aggregate by
|
62
|
+
#
|
63
|
+
def group_by_day_of_week
|
64
|
+
template = Hash[Date::ABBR_DAYNAMES.map {|x| [x, 0]}]
|
65
|
+
return group_by_strftime('%a', template)
|
66
|
+
end
|
67
|
+
|
68
|
+
def group_by_day_of_month
|
69
|
+
return Hash[group_by_strftime('%d').sort]
|
70
|
+
end
|
71
|
+
|
72
|
+
def group_by_week_of_year
|
73
|
+
return Hash[group_by_strftime('%W').sort]
|
74
|
+
end
|
75
|
+
|
76
|
+
def group_by_month_of_year
|
77
|
+
template = Hash[Date::ABBR_MONTHNAMES.map {|x| [x, 0]}]
|
78
|
+
template.shift
|
79
|
+
return group_by_strftime( '%b', template)
|
80
|
+
end
|
81
|
+
|
82
|
+
def group_by_strftime(filter, result = Hash.new(0))
|
83
|
+
group_by_day.each_with_object(result) { |i, a| a[i[0].strftime(filter)] += i[1] }
|
84
|
+
return result
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|