ditty 0.10.1 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -2
  3. data/Readme.md +1 -1
  4. data/ditty.gemspec +5 -5
  5. data/lib/ditty/cli.rb +8 -1
  6. data/lib/ditty/components/ditty.rb +3 -2
  7. data/lib/ditty/controllers/application_controller.rb +0 -1
  8. data/lib/ditty/controllers/component_controller.rb +5 -5
  9. data/lib/ditty/controllers/users_controller.rb +10 -10
  10. data/lib/ditty/db.rb +2 -2
  11. data/lib/ditty/emails/base.rb +1 -1
  12. data/lib/ditty/generators/project_generator.rb +3 -3
  13. data/lib/ditty/helpers/authentication.rb +4 -0
  14. data/lib/ditty/helpers/pundit.rb +2 -2
  15. data/lib/ditty/helpers/response.rb +6 -6
  16. data/lib/ditty/helpers/views.rb +9 -0
  17. data/lib/ditty/listener.rb +4 -1
  18. data/lib/ditty/models/identity.rb +8 -4
  19. data/lib/ditty/models/user.rb +1 -15
  20. data/lib/ditty/services/pagination_wrapper.rb +1 -1
  21. data/lib/ditty/services/settings.rb +5 -1
  22. data/lib/ditty/tasks/ditty.rake +26 -4
  23. data/lib/ditty/templates/settings.yml.erb +10 -0
  24. data/lib/ditty/templates/views/index.haml.tt +26 -24
  25. data/lib/ditty/version.rb +1 -1
  26. data/views/audit_logs/index.haml +33 -31
  27. data/views/auth/identity.haml +2 -2
  28. data/views/layout.haml +7 -4
  29. data/views/partials/actions.haml +7 -9
  30. data/views/partials/footer.haml +1 -8
  31. data/views/partials/user_associations.haml +2 -2
  32. data/views/roles/index.haml +24 -22
  33. data/views/user_login_traits/index.haml +26 -23
  34. data/views/users/index.haml +32 -30
  35. data/views/users/profile.haml +1 -1
  36. data/views/users/user.haml +1 -1
  37. metadata +18 -13
  38. data/views/partials/timespan_selector.haml +0 -64
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e785b1b4a84258a46e98753a5de394897bc6d002b88d2fc6629b30b33b52614e
4
- data.tar.gz: '094a55847cc4ec09bf1dc9307c9efcffa5b59b498fd407de5950e656e5a5e516'
3
+ metadata.gz: 0ecdd202f458e55d205e3e77b295a3c8908f7d3ecaf3d894fa9ae09c62d8b4fd
4
+ data.tar.gz: bcc5967235a668d896cee25228130b1ab2d98761dc2fb2125d5f7f2d2912881b
5
5
  SHA512:
6
- metadata.gz: fa7d54c43ac02d1f04a690f2bca5a529b3c65eefa0f287ab44594910bca2b8022656b58e2aef805a52307364481c35be2ba1b1e4b3a97b3450cab1b9acb27d1f
7
- data.tar.gz: 847143f0f0d20c67b3451a86e780ec44b914210ceec4498c9218ebc05f7f7277107b1998ca5f273aad0ad1ad45ceb051b81204fe87e6cb91e88ee2ef6af7b49c
6
+ metadata.gz: 45c0c4fb7acb614ce8cbfda366908fcaee02482d9601c26fe797ff5012a7bbeff0e81897141a33ce2c3d31064529401ea0c35f194852969262b263a306760b3c
7
+ data.tar.gz: c471989b26228ad7a08f933e8c1c747a8f5fd9ec21fd0fc1515a496c63d98446a2a7505802d01c4110bbcb514d6876e85b0c611dc4c438750aaff4e507948f64
data/.travis.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
+ - 3.2
5
+ - 3.1
4
6
  - 3.0
5
7
  - 2.7
6
- - 2.6
7
8
  gemfile: Gemfile.ci
8
9
  env:
9
10
  matrix:
@@ -17,7 +18,7 @@ before_script:
17
18
  - bundle exec rake ditty:prep
18
19
  script:
19
20
  - bundle exec rake
20
- - bundle exec rubocop --fail-level W lib views spec
21
+ - bundle exec rubocop -c .rubocop.yml --fail-level W lib views spec
21
22
  after_script:
22
23
  - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
23
24
  after_success:
data/Readme.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/EagerELK/ditty.svg?branch=master)](https://travis-ci.org/EagerELK/ditty)
1
+ [![Build Status](https://app.travis-ci.com/EagerELK/ditty.svg?branch=master)](https://app.travis-ci.com/github/EagerELK/ditty)
2
2
  [![Code Climate](https://codeclimate.com/github/EagerELK/ditty/badges/gpa.svg)](https://codeclimate.com/github/EagerELK/ditty)
3
3
  [![Test Coverage](https://codeclimate.com/github/EagerELK/ditty/badges/coverage.svg)](https://codeclimate.com/github/EagerELK/ditty/coverage)
4
4
  [![Inline docs](http://inch-ci.org/github/EagerELK/ditty.svg?branch=master)](http://inch-ci.org/github/EagerELK/ditty)
data/ditty.gemspec CHANGED
@@ -41,13 +41,13 @@ Gem::Specification.new do |spec|
41
41
  spec.add_dependency 'bcrypt', '>= 3.1'
42
42
  spec.add_dependency 'browser', '>= 5.3'
43
43
  spec.add_dependency 'dotenv', '>= 2'
44
- spec.add_dependency 'haml', '>= 5.1.2'
44
+ spec.add_dependency 'haml', '~> 5.1', '>= 5.1.2' # Going to 6 creates a `undefined method capture_html` and breaks a lot of other stuff
45
45
  spec.add_dependency 'logger', '>= 1.0'
46
46
  spec.add_dependency 'mail', '>= 1.7'
47
47
  spec.add_dependency 'oga', '>= 2.14'
48
- spec.add_dependency 'omniauth', '~> 1.0'
49
- spec.add_dependency 'omniauth-identity', '~> 1.0'
50
- spec.add_dependency 'pundit', '~> 1.0'
48
+ spec.add_dependency 'omniauth', '>= 1.0'
49
+ spec.add_dependency 'omniauth-identity', '>= 1.0'
50
+ spec.add_dependency 'pundit', '>= 2.0'
51
51
  spec.add_dependency 'rack-contrib', '>= 2.0'
52
52
  spec.add_dependency 'rack_csrf', '>= 2.0'
53
53
  spec.add_dependency 'rake', '>= 13.0'
@@ -59,5 +59,5 @@ Gem::Specification.new do |spec|
59
59
  spec.add_dependency 'thor', '>= 0.20'
60
60
  spec.add_dependency 'tilt', '>= 2'
61
61
  spec.add_dependency 'will_paginate', '>= 3.1'
62
- spec.add_dependency 'wisper', '~> 2.0'
62
+ spec.add_dependency 'wisper', '>= 2.0'
63
63
  end
data/lib/ditty/cli.rb CHANGED
@@ -31,6 +31,7 @@ module Ditty
31
31
  require './application' if File.exist?('application.rb')
32
32
  require 'ditty/db' unless defined?(DB)
33
33
  ::Ditty::Components.tasks
34
+
34
35
  def server
35
36
  # Ensure the token files are present
36
37
  Rake::Task['ditty:generate_tokens'].invoke
@@ -47,8 +48,14 @@ module Ditty
47
48
  Rake::Task['ditty:seed'].invoke
48
49
 
49
50
  # RackUP!
51
+ rack_opts = {
52
+ environment: ENV['APP_ENV'] || 'development',
53
+ Port: ENV['APP_PORT'] || 9292,
54
+ Host: ENV['APP_HOST'] || '0.0.0.0',
55
+ config: 'config.ru'
56
+ }
50
57
  puts 'Starting the Ditty Server'
51
- Rack::Server.start(config: 'config.ru')
58
+ Rack::Server.start(rack_opts)
52
59
  end
53
60
 
54
61
  desc 'migrate', 'Run the Ditty migrations'
@@ -66,8 +66,9 @@ module Ditty
66
66
  load
67
67
 
68
68
  sa = ::Ditty::Role.find_or_create(name: 'super_admin')
69
- admin = ::Ditty::Role.find_or_create(name: 'admin') { |e| e.parent = sa }
70
- ::Ditty::Role.find_or_create(name: 'user') { |e| e.parent = admin }
69
+ admin = ::Ditty::Role.find_or_create(name: 'admin') { |rec| rec.parent = sa }
70
+ ::Ditty::Role.find_or_create(name: 'user') { |rec| rec.parent = admin }
71
+ ::Ditty::Role.find_or_create(name: 'anonymous')
71
72
  end
72
73
  end
73
74
 
@@ -36,7 +36,6 @@ module Ditty
36
36
 
37
37
  register Sinatra::Flash, Sinatra::RespondWith
38
38
 
39
- use Rack::Csrf, raise: ENV['APP_ENV'] == 'development' unless ENV['APP_ENV'] == 'test'
40
39
  use Rack::JSONBodyParser
41
40
  use Rack::MethodOverride
42
41
  use Rack::NestedParams
@@ -7,6 +7,8 @@ require 'sinatra/json'
7
7
 
8
8
  module Ditty
9
9
  class ComponentController < ApplicationController
10
+ use Rack::Csrf, raise: ENV['APP_ENV'] == 'development' unless ENV['APP_ENV'] == 'test'
11
+
10
12
  helpers Helpers::Component, Helpers::Response
11
13
 
12
14
  set base_path: nil
@@ -37,9 +39,7 @@ module Ditty
37
39
  after do
38
40
  return if settings.environment == 'production'
39
41
 
40
- if (response.successful? || response.redirection?) && @skip_verify == false && (settings.environment != 'production')
41
- verify_authorized
42
- end
42
+ verify_authorized if (response.successful? || response.redirection?) && @skip_verify == false
43
43
  end
44
44
 
45
45
  after '/' do
@@ -65,7 +65,7 @@ module Ditty
65
65
 
66
66
  entity = settings.model_class.new(permitted_parameters(settings.model_class, :create))
67
67
  haml :"#{view_location}/new",
68
- locals: { entity: entity, title: heading(:new) },
68
+ locals: { entity: entity, title: heading(:new), actions: actions(action: :new) },
69
69
  layout: layout
70
70
  end
71
71
 
@@ -98,7 +98,7 @@ module Ditty
98
98
 
99
99
  flash[:redirect_to] = "#{base_path}/#{entity.display_id}" unless flash.keep(:redirect_to)
100
100
  haml :"#{view_location}/edit",
101
- locals: { entity: entity, title: heading(:edit) },
101
+ locals: { entity: entity, title: heading(:edit), actions: actions(entity: entity, action: :edit) },
102
102
  layout: layout
103
103
  end
104
104
 
@@ -15,10 +15,10 @@ module Ditty
15
15
  set track_actions: true
16
16
 
17
17
  # New
18
- get '/new' do
18
+ get '/new/?' do
19
19
  authorize settings.model_class, :create
20
20
 
21
- locals = { title: heading(:new), entity: User.new, identity: Identity.new }
21
+ locals = { title: heading(:new), entity: User.new, identity: Identity.new, actions: actions(action: :new) }
22
22
  haml :"#{view_location}/new", locals: locals
23
23
  end
24
24
 
@@ -60,7 +60,7 @@ module Ditty
60
60
  end
61
61
 
62
62
  # Update
63
- put '/:id' do |id|
63
+ put '/:id/?' do |id|
64
64
  entity = dataset.first(settings.model_class.primary_key => id)
65
65
  halt 404 unless entity
66
66
  authorize entity, :update
@@ -80,7 +80,7 @@ module Ditty
80
80
  update_response(entity)
81
81
  end
82
82
 
83
- put '/:id/identity' do |id|
83
+ put '/:id/identity/?' do |id|
84
84
  entity = dataset.first(settings.model_class.primary_key => id)
85
85
  halt 404 unless entity
86
86
  authorize entity, :update
@@ -88,13 +88,13 @@ module Ditty
88
88
  identity = entity.identity.first
89
89
  identity_params = params['identity']
90
90
 
91
- unless current_user.super_admin? || identity.authenticate(identity_params['old_password'])
91
+ if (current_user.super_admin? == false || current_user_id == entity.id) && identity.authenticate(identity_params['old_password']) == false
92
92
  broadcast(:identity_update_password_failed, target: self)
93
93
  flash[:danger] = 'Old Password didn\'t match'
94
- return redirect back
94
+ return redirect(with_layout(params[:redirect_to] || flash[:redirect_to] || back))
95
95
  end
96
96
 
97
- values = permitted_parameters(Identity, :create)
97
+ values = permitted_parameters(Identity, :update)
98
98
  identity.set values
99
99
  if identity.valid?
100
100
  identity.save_changes
@@ -111,7 +111,7 @@ module Ditty
111
111
  end
112
112
 
113
113
  # Delete
114
- delete '/:id', provides: %i[html json] do |id|
114
+ delete '/:id/?', provides: %i[html json] do |id|
115
115
  entity = dataset.first(settings.model_class.primary_key => id)
116
116
  halt 404 unless entity
117
117
  authorize entity, :delete
@@ -125,13 +125,13 @@ module Ditty
125
125
  end
126
126
 
127
127
  # Profile
128
- get '/profile' do
128
+ get '/profile/?' do
129
129
  entity = current_user
130
130
  halt 404 unless entity
131
131
  authorize entity, :read
132
132
 
133
133
  flash[:redirect_to] = request.path
134
- haml :"#{view_location}/profile", locals: { entity: entity, identity: entity.identity.first, title: 'My Account' }
134
+ haml :"#{view_location}/profile", locals: { entity: entity, identity: entity.identity.first, title: 'My Account', actions: actions(entity: entity, action: :read) }
135
135
  end
136
136
  end
137
137
  end
data/lib/ditty/db.rb CHANGED
@@ -8,7 +8,7 @@ require 'active_support/core_ext/object/blank'
8
8
  pool_timeout = (ENV['DB_POOL_TIMEOUT'] || 5).to_i
9
9
 
10
10
  if defined? DB
11
- ::Ditty::Services::Logger.warn 'Database connection already set up'
11
+ ::Ditty::Services::Logger.warn '** Database connection already set up **'
12
12
  elsif ENV['DATABASE_URL'].blank? == false
13
13
  # Delete DATABASE_URL from the environment, so it isn't accidently
14
14
  # passed to subprocesses. DATABASE_URL may contain passwords.
@@ -29,5 +29,5 @@ elsif ENV['DATABASE_URL'].blank? == false
29
29
  Sequel::Model.plugin :update_or_create
30
30
  Sequel::Model.plugin :validation_helpers
31
31
  else
32
- ::Ditty::Services::Logger.error 'No database connection set up'
32
+ ::Ditty::Services::Logger.error '*** NO DATABASE CONNECTION SET UP ***'
33
33
  end
@@ -71,7 +71,7 @@ module Ditty
71
71
  template = File.expand_path("./views/#{file}.haml")
72
72
  return template if File.file? template
73
73
 
74
- template = File.expand_path("./#{file}.haml", App.view_folder)
74
+ template = File.expand_path("./#{file}.haml", ::Ditty::Ditty.view_folder)
75
75
  return template if File.file? template
76
76
 
77
77
  file
@@ -41,9 +41,9 @@ module Ditty
41
41
  copy_file 'sidekiq.rb', './config/sidekiq.rb'
42
42
  copy_file 'sidekiq.yml', './config/sidekiq.yml'
43
43
 
44
- copy_file 'spec_helper.rb', './specs/spec_helper.rb'
45
- copy_file '../../../spec/support/api_shared_examples.rb', './specs/support/api_shared_examples.rb'
46
- copy_file '../../../spec/support/crud_shared_examples.rb', './specs/support/crud_shared_examples.rb'
44
+ copy_file 'spec_helper.rb', './spec/spec_helper.rb'
45
+ copy_file '../../../spec/support/api_shared_examples.rb', './spec/support/api_shared_examples.rb'
46
+ copy_file '../../../spec/support/crud_shared_examples.rb', './spec/support/crud_shared_examples.rb'
47
47
 
48
48
  template 'settings.yml.erb', './config/settings.yml'
49
49
  end
@@ -9,6 +9,10 @@ module Ditty
9
9
  return nil if current_user_id.nil?
10
10
 
11
11
  @current_user ||= User[current_user_id]
12
+ rescue Sequel::DatabaseError => e
13
+ Services::Logger.warn "Could not fetch current user: #{e.message} / #{current_user_id}"
14
+ Services::Logger.debug e
15
+ nil
12
16
  end
13
17
 
14
18
  def current_user=(user)
@@ -5,7 +5,7 @@ require 'pundit'
5
5
  module Ditty
6
6
  module Helpers
7
7
  module Pundit
8
- include ::Pundit
8
+ include ::Pundit::Authorization
9
9
 
10
10
  def authorize(record, query)
11
11
  query = :"#{query}?" unless query[-1] == '?'
@@ -24,7 +24,7 @@ module Ditty
24
24
  end
25
25
 
26
26
  def permitted_parameters(record, action = nil)
27
- param_key = PolicyFinder.new(record).param_key
27
+ param_key = ::Pundit::PolicyFinder.new(record).param_key
28
28
  policy_fields = permitted_attributes(record, action)
29
29
  request.params.fetch(param_key, {}).select do |key, _value|
30
30
  policy_fields.include? key.to_sym
@@ -6,10 +6,9 @@ module Ditty
6
6
  module Helpers
7
7
  module Response
8
8
  def list_response(result, view: 'index')
9
+ actions = actions(action: :list)
9
10
  respond_to do |format|
10
11
  format.html do
11
- actions = {}
12
- actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
13
12
  haml :"#{view_location}/#{view}",
14
13
  locals: { list: result, title: heading(:list), actions: actions },
15
14
  layout: layout
@@ -49,15 +48,16 @@ module Ditty
49
48
  end
50
49
  end
51
50
 
52
- def actions(entity = nil)
51
+ def actions(entity: nil, action: nil)
53
52
  actions = {}
54
- actions["#{base_path}/#{entity.display_id}/edit"] = "Edit #{heading}" if entity && policy(entity).update?
55
- actions["#{base_path}/new"] = "New #{heading}" if policy(settings.model_class).create?
53
+ actions["#{base_path}/#{entity.display_id}/edit"] = "Edit #{heading}" if entity && policy(entity).update? && action != :edit
54
+ actions[base_path] = "List #{heading(:list)}" if policy(entity || settings.model_class).list? && action != :list
55
+ actions["#{base_path}/new"] = "New #{heading}" if policy(entity || settings.model_class).create? && action != :new
56
56
  actions
57
57
  end
58
58
 
59
59
  def read_response(entity)
60
- actions = actions(entity)
60
+ actions = actions(entity: entity, action: :read)
61
61
  respond_to do |format|
62
62
  format.html do
63
63
  title = heading(:read) + (entity.respond_to?(:name) ? ": #{entity.name}" : '')
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ditty/services/pagination_wrapper'
4
+ require 'digest/sha2'
4
5
 
5
6
  module Ditty
6
7
  module Helpers
@@ -156,6 +157,14 @@ module Ditty
156
157
 
157
158
  'fa-sort-down'
158
159
  end
160
+
161
+ def static_headers(view)
162
+ view_path = "views/#{view}.haml"
163
+ return unless File.exist?(view_path)
164
+
165
+ last_modified File.mtime(view_path)
166
+ etag Digest::SHA2.hexdigest(File.read(view_path))
167
+ end
159
168
  end
160
169
  end
161
170
  end
@@ -70,11 +70,14 @@ module Ditty
70
70
  end
71
71
 
72
72
  def log_action(values)
73
- values[:user] ||= values[:target].current_user if values[:target]
73
+ require 'ditty/models/audit_log'
74
+
75
+ values[:user] ||= values[:target].current_user if values[:target] && values[:target].respond_to?(:current_user)
74
76
  @mutex.synchronize { ::Ditty::AuditLog.create values }
75
77
  end
76
78
 
77
79
  def user_traits(target)
80
+ return {} unless target.respond_to?(:current_user) && target.respond_to?(:browser)
78
81
  {
79
82
  user_id: target.current_user&.id,
80
83
  platform: target.browser.platform.name,
@@ -21,9 +21,9 @@ module Ditty
21
21
  end
22
22
 
23
23
  def authenticate(unencrypted)
24
- return false if crypted_password.blank?
24
+ return false if crypted_password.blank? || ::BCrypt::Password.new(crypted_password) != unencrypted
25
25
 
26
- self if ::BCrypt::Password.new(crypted_password) == unencrypted
26
+ self
27
27
  end
28
28
 
29
29
  def persisted?
@@ -37,6 +37,10 @@ module Ditty
37
37
  }
38
38
  end
39
39
 
40
+ def uid
41
+ user&.id
42
+ end
43
+
40
44
  # Validation
41
45
  def validate
42
46
  super
@@ -65,7 +69,7 @@ module Ditty
65
69
  end
66
70
 
67
71
  # Callbacks
68
- def before_save
72
+ def before_validation
69
73
  super
70
74
  encrypt_password unless password == '' || password.nil?
71
75
  end
@@ -77,7 +81,7 @@ module Ditty
77
81
  end
78
82
 
79
83
  def password_required
80
- crypted_password.blank? || !password.blank?
84
+ crypted_password.blank? || password.present?
81
85
  end
82
86
  end
83
87
  end
@@ -4,6 +4,7 @@ require 'ditty/models/base'
4
4
  require 'digest/md5'
5
5
  require 'active_support'
6
6
  require 'active_support/core_ext/object/blank'
7
+ require 'ditty/models/role'
7
8
 
8
9
  # Why not store this in Elasticsearch?
9
10
  module Ditty
@@ -88,20 +89,5 @@ module Ditty
88
89
  def display_name
89
90
  name || username
90
91
  end
91
-
92
- class << self
93
- def anonymous_user
94
- role = ::Ditty::Role.find_or_create(name: 'anonymous')
95
- ::Ditty::User.where(roles: role).first
96
- end
97
-
98
- def create_anonymous_user(email = 'anonymous@ditty.io')
99
- return if anonymous_user
100
-
101
- user = ::Ditty::User.find_or_create(email: email)
102
- user.remove_role ::Ditty::Role.find_or_create(name: 'user')
103
- user.add_role ::Ditty::Role.find_or_create(name: 'anonymous') unless user.role?('anonymous')
104
- end
105
- end
106
92
  end
107
93
  end
@@ -75,7 +75,7 @@ module Ditty
75
75
  else
76
76
  return (0..0) if list.current_page > page_count
77
77
 
78
- a = 1 + (list.current_page - 1) * page_size
78
+ a = 1 + ((list.current_page - 1) * page_size)
79
79
  b = a + page_size - 1
80
80
  b = pagination_record_count if b > pagination_record_count
81
81
  a..b
@@ -55,7 +55,11 @@ module Ditty
55
55
  attr_writer :values
56
56
 
57
57
  def read(filename)
58
- base = YAML.safe_load(ERB.new(File.read(filename)).result).deep_symbolize_keys
58
+ base = if Gem::Version.new(Psych::VERSION) >= Gem::Version.new('3.1.0.pre1')
59
+ ::YAML.safe_load(ERB.new(File.read(filename)).result, permitted_classes: [Symbol])
60
+ else
61
+ ::YAML.safe_load(ERB.new(File.read(filename)).result, [Symbol])
62
+ end.deep_symbolize_keys
59
63
  base.deep_merge!(base[ENV['APP_ENV'].to_sym]) unless ENV['APP_ENV'].nil? || base[ENV['APP_ENV'].to_sym].nil?
60
64
  base
61
65
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
4
+
3
5
  namespace :ditty do
4
6
  desc 'Run the ditty console'
5
7
  task :console do
@@ -34,17 +36,37 @@ namespace :ditty do
34
36
  end
35
37
 
36
38
  namespace :prep do
39
+ desc 'Create a user'
40
+ task :user, [:email, :password] do |_t, args|
41
+ require 'ditty/listener'
42
+
43
+ identity = Ditty::Identity.new(username: args[:email], password: args[:password])
44
+ identity.password_confirmation = identity.password
45
+ user = Ditty::User.new(email: identity.username)
46
+ begin
47
+ identity.valid?
48
+ DB.transaction do
49
+ user.save_changes
50
+ user.add_identity identity
51
+ Ditty::Listener.new.user_register(target: self, values: { user: user })
52
+ end
53
+ rescue StandardError => e
54
+ Ditty::Services::Logger.error "Could not regster super user: #{e.message}"
55
+ Ditty::Services::Logger.debug e
56
+ end
57
+ end
58
+
37
59
  desc 'Check that the required Ditty folders are present'
38
60
  task :folders do
39
61
  puts 'Prepare the Ditty folders'
40
- Dir.mkdir 'pids' unless File.exist?('pids')
41
- Dir.mkdir 'logs' unless File.exist?('logs')
62
+ FileUtils.mkdir_p 'pids'
63
+ FileUtils.mkdir_p 'logs'
42
64
  end
43
65
 
44
66
  desc 'Check that the public folder is present and populated'
45
67
  task :public do
46
68
  puts 'Preparing the Ditty public folder'
47
- Dir.mkdir 'public' unless File.exist?('public')
69
+ FileUtils.mkdir_p 'public'
48
70
  ::Ditty::Components.public_folder.each do |path|
49
71
  puts "Checking #{path}"
50
72
  path = "#{path}/."
@@ -55,7 +77,7 @@ namespace :ditty do
55
77
  desc 'Check that the migrations folder is present and populated'
56
78
  task :migrations do
57
79
  puts 'Preparing the Ditty migrations folder'
58
- Dir.mkdir 'migrations' unless File.exist?('migrations')
80
+ FileUtils.mkdir_p 'migrations'
59
81
  ::Ditty::Components.migrations.each do |path|
60
82
  path = "#{path}/."
61
83
  FileUtils.cp_r path, 'migrations' unless File.expand_path(path).eql? File.expand_path('migrations')
@@ -7,6 +7,16 @@ logger:
7
7
  class: Logger
8
8
  level: DEBUG
9
9
  options: logs/<%%= ENV['APP_ENV'] %>.log
10
+ # email:
11
+ # from_address: 'no-reply@ditty.io'
12
+ # delivery_method: smtp
13
+ # options:
14
+ # address: smtp.server
15
+ # port: 25
16
+ # user_name: 'SMTPUSERNAME'
17
+ # password: 'SMTPPASSWORD'
18
+ # authentication: login
19
+ # enable_starttls_auto: true
10
20
  # authentication:
11
21
  # google_oauth2:
12
22
  # login_prompt: Log in with Google
@@ -1,29 +1,31 @@
1
1
  .row
2
2
  .col-md-12
3
3
  = haml :'partials/search'
4
- %table.table.table-striped.table-bordered.table-hover
5
- %thead.thead-dark
6
- %tr
7
- <%- columns.each do |col| -%>
8
- %th <%= col.to_s.titleize %>
9
- <%- end %>
10
- %th
11
- %tbody
12
- - if list.count > 0
13
- - list.all.each do |entity|
14
- %tr
15
- <%- columns.each do |col| -%>
16
- %td= entity.<%= col %>
17
- <%- end %>
18
- %td
19
- %a{ href: "#{base_path}/#{entity.display_id}", title: 'Show' }
20
- %i.fa.fa-search
21
- - if policy(entity).update?
22
- %a{ href: "#{base_path}/#{entity.display_id}/edit", title: 'Edit' }
23
- %i.fa.fa-edit
24
- - else
4
+ .card.card-default.shadow.table-responsive
5
+ %table.table.table-striped.table-bordered.table-hover.mb-0
6
+ %thead.thead-dark
25
7
  %tr
26
- %td.text-center{ colspan: <%= columns.count + 1 %> } No <%= model_name.pluralize %>
8
+ <%- columns.each do |col| -%>
9
+ %th <%= col.to_s.titleize %>
10
+ <%- end %>
11
+ %th
12
+ %tbody
13
+ - if list.count > 0
14
+ - list.all.each do |entity|
15
+ %tr
16
+ <%- columns.each do |col| -%>
17
+ %td= entity.<%= col %>
18
+ <%- end %>
19
+ %td
20
+ %a{ href: "#{base_path}/#{entity.display_id}", title: 'Show' }
21
+ %i.fa.fa-search
22
+ - if policy(entity).update?
23
+ %a{ href: "#{base_path}/#{entity.display_id}/edit", title: 'Edit' }
24
+ %i.fa.fa-edit
25
+ - else
26
+ %tr
27
+ %td.text-center{ colspan: <%= columns.count + 1 %> } No <%= model_name.pluralize %>
27
28
 
28
- - if list.count > 0
29
- = pagination(list, base_path)
29
+ - if list.count > 0
30
+ .card-body
31
+ = pagination(list, base_path)
data/lib/ditty/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Ditty
4
- VERSION = '0.10.1'
4
+ VERSION = '0.11.1'
5
5
  end
@@ -1,36 +1,38 @@
1
1
  .row
2
2
  .col-md-12
3
3
  = haml :'partials/search'
4
- %table.table.table-striped.table-bordered.table-hover
5
- %thead.thead-dark
6
- %tr
7
- %th= "User&nbsp;#{sort_ui(:user)}"
8
- %th= "Action&nbsp;#{sort_ui(:action)}"
9
- %th= "Details&nbsp;#{sort_ui(:details)}"
10
- %th= "IP Address&nbsp;#{sort_ui(:ip_address)}"
11
- %th= "Browser&nbsp;#{sort_ui(:browser)}"
12
- %th= "Device&nbsp;#{sort_ui(:device)}"
13
- %th= "Platform&nbsp;#{sort_ui(:platform)}"
14
- %th= "Created&nbsp;At&nbsp;#{sort_ui(:created_at)}"
15
- %tbody
16
- - if list.count > 0
17
- - list.all.each do |entity|
18
- %tr
19
- %td
20
- -if entity.user
21
- %a{ href: "#{settings.map_path}/users/#{entity.user.id}" }= entity.user.email
22
- -else
23
- None
24
- %td= entity.action
25
- %td= entity.details
26
- %td= entity.ip_address || 'Unknown'
27
- %td= entity.browser || 'Unknown'
28
- %td= entity.device || 'Unknown'
29
- %td= entity.platform || 'Unknown'
30
- %td= entity.created_at&.strftime('%Y-%m-%d %H:%M:%S') || 'Unknown'
31
- - else
4
+ .card.card-default.shadow.table-responsive
5
+ %table.table.table-striped.table-bordered.table-hover.mb-0
6
+ %thead.thead-dark
32
7
  %tr
33
- %td.text-center{ colspan: 4 } No records
8
+ %th= "User&nbsp;#{sort_ui(:user)}"
9
+ %th= "Action&nbsp;#{sort_ui(:action)}"
10
+ %th= "Details&nbsp;#{sort_ui(:details)}"
11
+ %th= "IP Address&nbsp;#{sort_ui(:ip_address)}"
12
+ %th= "Browser&nbsp;#{sort_ui(:browser)}"
13
+ %th= "Device&nbsp;#{sort_ui(:device)}"
14
+ %th= "Platform&nbsp;#{sort_ui(:platform)}"
15
+ %th= "Created&nbsp;At&nbsp;#{sort_ui(:created_at)}"
16
+ %tbody
17
+ - if list.count > 0
18
+ - list.all.each do |entity|
19
+ %tr
20
+ %td
21
+ -if entity.user
22
+ %a{ href: "#{settings.map_path}/users/#{entity.user.id}" }= entity.user.email
23
+ -else
24
+ None
25
+ %td= entity.action
26
+ %td= entity.details
27
+ %td= entity.ip_address || 'Unknown'
28
+ %td= entity.browser || 'Unknown'
29
+ %td= entity.device || 'Unknown'
30
+ %td= entity.platform || 'Unknown'
31
+ %td= entity.created_at&.strftime('%Y-%m-%d %H:%M:%S') || 'Unknown'
32
+ - else
33
+ %tr
34
+ %td.text-center{ colspan: 4 } No records
34
35
 
35
- - if list.count > 0
36
- = pagination(list, base_path)
36
+ - if list.count > 0
37
+ .card-body
38
+ = pagination(list, base_path)
@@ -1,8 +1,8 @@
1
1
  = form_tag("#{settings.map_path}/auth/identity/callback", attributes: { class: 'user' }) do
2
2
  .form-group
3
- %input.form-control.form-control-user{ name: 'username', type: 'email', 'aria-describedby': 'emailHelp', placeholder: 'Enter Email Address...' }
3
+ %input.form-control.form-control-user{ name: 'username', type: 'email', 'aria-describedby': 'emailHelp', placeholder: 'Enter Email Address...', required: true }
4
4
  .form-group
5
- %input.form-control.form-control-user{ name: 'password', type: 'password', placeholder: 'Password' }
5
+ %input.form-control.form-control-user{ name: 'password', type: 'password', placeholder: 'Password', required: true }
6
6
  / .form-group
7
7
  / .custom-control.custom-checkbox.small
8
8
  / <input type="checkbox" class="custom-control-input" id="customCheck">
data/views/layout.haml CHANGED
@@ -45,11 +45,14 @@
45
45
 
46
46
  #content-wrapper.d-flex.flex-column
47
47
  #content
48
- = haml :'partials/topbar'
48
+ = haml :'partials/topbar', locals: { title: (defined?(title) ? title : nil) }
49
49
  .container-fluid
50
- / TODO
51
- / .row
52
- / .col-md-12
50
+ - if (defined?(hide_title).nil? || hide_title == false) && defined?(title)
51
+ .row.mb-2
52
+ .col-md-9.my-auto
53
+ %h1.text-dark= title
54
+ .col.md-3.text-right.my-auto
55
+ = haml :'partials/actions', locals: { actions: defined?(actions) ? actions : {} }
53
56
  = haml :'partials/notifications'
54
57
 
55
58
  = yield
@@ -1,15 +1,13 @@
1
- - if defined?(actions)
1
+ - if defined?(actions) && actions != nil
2
2
  - if actions.count > 1
3
3
  - link, text = actions.shift
4
4
  .btn-group.text-right
5
5
  %a.btn.btn-primary{ href: link }= text
6
- %button.btn.btn-primary.dropdown-toggle{ type: 'button', id: 'actions-toggle', data: { toggle: 'dropdown' } }
6
+ %button.btn.btn-primary.dropdown-toggle{ type: 'button', id: 'actions-toggle', data: { toggle: 'dropdown' }, 'aria-haspopup': 'true', 'aria-expanded': 'false' }
7
7
  %span.caret
8
8
  %span.sr-only Toggle Dropdown
9
- %ul.dropdown-menu{ 'aria-labelledby': 'actions-toggle' }
10
- -actions.each do |k, v|
11
- %li
12
- %a{ href: k }= v
13
- - elsif actions.count > 0
14
- -actions.each do |k, v|
15
- %a.btn.btn-primary{ href: k }= v
9
+ .dropdown-menu
10
+ - actions.each do |k, v|
11
+ %a.dropdown-item{ href: k }= v
12
+ - elsif actions.count == 1
13
+ %a.btn.btn-primary{ href: actions.keys.first }= actions.values.first
@@ -3,11 +3,4 @@
3
3
  .container.my-auto
4
4
  -if ENV['SKIP_BRANDING'].blank?
5
5
  .copyright.text-center.my-auto.text-dark
6
- %p
7
- Copyright &copy;
8
- %a{ href: 'https://hackerpla.net/requestd', target: '_blank' }
9
- HackerPlanet
10
- 2020
11
- %p.mb-0
12
- %a{ href: 'https://hackerpla.net/requestd', target: '_blank' }
13
- %img.rounded{ src: 'http://hackerpla.net/wp-content/uploads/2014/07/HP_1C_128x128.png', height: 60 }
6
+ %span Copyright &copy; Ditty.io 2019
@@ -21,8 +21,8 @@
21
21
  - users = user_options.select { |k, v| entity.users.map(&:id).include?(k) == false }
22
22
  - if users && users.count.positive?
23
23
  = new_form_tag "#{base_path}/#{entity.display_id}/users" do
24
- .input-group
25
- %select.form-control{ name: "#{group}[user_id]", id: 'user_id' }
24
+ .input-group.tall-select2
25
+ %select.form-control.select2{ name: "#{group}[user_id]", id: 'user_id' }
26
26
  %option{ value: '' } Select User
27
27
  - users.each do |key, value|
28
28
  %option{ value: key }= value
@@ -1,27 +1,29 @@
1
1
  .row
2
2
  .col-md-12
3
3
  = haml :'partials/search'
4
- %table.table.table-striped.table-bordered.table-hover
5
- %thead.thead-dark
6
- %tr
7
- %th= "Name#{sort_ui(:name)}"
8
- %th Parent
9
- %th
10
- %tbody
11
- - if list.count > 0
12
- - list.all.each do |entity|
13
- %tr
14
- %td
15
- %a{ href: "#{base_path}/#{entity.display_id}" }= entity.name
16
- %td
17
- %a{ href: "#{base_path}/#{entity.parent_id}" }= entity.parent&.name || '(None)'
18
- %td
19
- - if policy(entity).update?
20
- %a{ href: "#{base_path}/#{entity.display_id}/edit", title: 'Edit' }
21
- %i.fa.fa-edit
22
- - else
4
+ .card.card-default.shadow.table-responsive
5
+ %table.table.table-striped.table-bordered.table-hover.mb-0
6
+ %thead.thead-dark
23
7
  %tr
24
- %td.text-center{ colspan: 3 } No Roles
8
+ %th= "Name#{sort_ui(:name)}"
9
+ %th Parent
10
+ %th
11
+ %tbody
12
+ - if list.count > 0
13
+ - list.all.each do |entity|
14
+ %tr
15
+ %td
16
+ %a{ href: "#{base_path}/#{entity.display_id}" }= entity.name
17
+ %td
18
+ %a{ href: "#{base_path}/#{entity.parent_id}" }= entity.parent&.name || '(None)'
19
+ %td
20
+ - if policy(entity).update?
21
+ %a{ href: "#{base_path}/#{entity.display_id}/edit", title: 'Edit' }
22
+ %i.fa.fa-edit
23
+ - else
24
+ %tr
25
+ %td.text-center{ colspan: 3 } No Roles
25
26
 
26
- - if list.count > 0
27
- = pagination(list, base_path)
27
+ - if list.count > 0
28
+ .card-body
29
+ = pagination(list, base_path)
@@ -1,28 +1,31 @@
1
1
  .row
2
2
  .col-md-12
3
3
  = haml :'partials/search'
4
- %table.table.table-striped.table-bordered.table-hover
5
- %thead.thead-dark
6
- %tr
7
- %th= "User&nbsp;#{sort_ui(:user_id)}"
8
- %th= "IP Address&nbsp;#{sort_ui(:ip_address)}"
9
- %th= "Device&nbsp;#{sort_ui(:device)}"
10
- %th= "Platform&nbsp;#{sort_ui(:platform)}"
11
- %th= "Browser&nbsp;#{sort_ui(:browser)}"
12
- %th= "Last Seen&nbsp;#{sort_ui(:updated_at)}"
13
- %tbody
14
- - if list.count.positive?
15
- - list.all.each do |entity|
16
- %tr
17
- %td= entity.user&.email || 'Unknown'
18
- %td
19
- %a{ href: "#{base_path}/#{entity.display_id}" }= entity.ip_address || 'Unknown'
20
- %td= entity.device || 'Unknown'
21
- %td= entity.platform || 'Unknown'
22
- %td= entity.browser || 'Unknown'
23
- %td= entity.updated_at
24
- - else
4
+ .card.card-default.shadow.table-responsive
5
+ %table.table.table-striped.table-bordered.table-hover.mb-0
6
+ %thead.thead-dark
25
7
  %tr
26
- %td.text-center{ colspan: 6 } No records
8
+ %th= "User&nbsp;#{sort_ui(:user_id)}"
9
+ %th= "IP Address&nbsp;#{sort_ui(:ip_address)}"
10
+ %th= "Device&nbsp;#{sort_ui(:device)}"
11
+ %th= "Platform&nbsp;#{sort_ui(:platform)}"
12
+ %th= "Browser&nbsp;#{sort_ui(:browser)}"
13
+ %th= "Last Seen&nbsp;#{sort_ui(:updated_at)}"
14
+ %tbody
15
+ - if list.count.positive?
16
+ - list.all.each do |entity|
17
+ %tr
18
+ %td= entity.user&.email || 'Unknown'
19
+ %td
20
+ %a{ href: "#{base_path}/#{entity.display_id}" }= entity.ip_address || 'Unknown'
21
+ %td= entity.device || 'Unknown'
22
+ %td= entity.platform || 'Unknown'
23
+ %td= entity.browser || 'Unknown'
24
+ %td= entity.updated_at
25
+ - else
26
+ %tr
27
+ %td.text-center{ colspan: 6 } No records
27
28
 
28
- =pagination(list, base_path)
29
+ - if list.count > 0
30
+ .card-body
31
+ = pagination(list, base_path)
@@ -1,35 +1,37 @@
1
1
  .row
2
2
  .col-md-12
3
3
  = haml :'partials/search'
4
- %table.table.table-striped.table-bordered.table-hover
5
- %thead.thead-dark
6
- %tr
7
- %th= "Email&nbsp;#{sort_ui(:email)}"
8
- %th= "Name&nbsp;#{sort_ui(:name)}"
9
- %th= "Surname&nbsp;#{sort_ui(:surname)}"
10
- %th Roles
11
- %th= "Signed&nbsp;Up&nbsp;#{sort_ui(:created_at)}"
12
- %th
13
- %tbody
14
- - if list.count > 0
15
- - list.all.each do |entity|
16
- %tr
17
- %td
18
- - if policy(entity).read?
19
- %a{ href: "#{base_path}/#{entity.display_id}" }= entity.email
20
- - else
21
- = entity.email
22
- %td= entity.name
23
- %td= entity.surname
24
- %td= entity.all_roles.map(&:name).map(&:titlecase).join(', ')
25
- %td= entity.created_at.strftime('%Y-%m-%d')
26
- %td
27
- - if policy(entity).update?
28
- %a{ href: "#{base_path}/#{entity.display_id}/edit", title: 'Edit' }
29
- %i.fa.fa-edit
30
- - else
4
+ .card.card-default.shadow.table-responsive
5
+ %table.table.table-striped.table-bordered.table-hover.mb-0
6
+ %thead.thead-dark
31
7
  %tr
32
- %td.text-center{ colspan: 6 } No records
8
+ %th= "Email&nbsp;#{sort_ui(:email)}"
9
+ %th= "Name&nbsp;#{sort_ui(:name)}"
10
+ %th= "Surname&nbsp;#{sort_ui(:surname)}"
11
+ %th Roles
12
+ %th= "Signed&nbsp;Up&nbsp;#{sort_ui(:created_at)}"
13
+ %th
14
+ %tbody
15
+ - if list.count > 0
16
+ - list.all.each do |entity|
17
+ %tr
18
+ %td
19
+ - if policy(entity).read?
20
+ %a{ href: "#{base_path}/#{entity.display_id}" }= entity.email
21
+ - else
22
+ = entity.email
23
+ %td= entity.name
24
+ %td= entity.surname
25
+ %td= entity.all_roles.map(&:name).map(&:titlecase).join(', ')
26
+ %td= entity.created_at.strftime('%Y-%m-%d')
27
+ %td
28
+ - if policy(entity).update?
29
+ %a{ href: "#{base_path}/#{entity.display_id}/edit", title: 'Edit' }
30
+ %i.fa.fa-edit
31
+ - else
32
+ %tr
33
+ %td.text-center{ colspan: 6 } No records
33
34
 
34
- - if list.count > 0
35
- = pagination(list, base_path)
35
+ - if list.count > 0
36
+ .card-body
37
+ = pagination(list, base_path)
@@ -37,7 +37,7 @@
37
37
  .card-body
38
38
  %h4.card-title Change Password
39
39
  = edit_form_tag("#{base_path}/#{entity.display_id}/identity") do
40
- - if current_user.super_admin? == false
40
+ - if current_user.super_admin? == false || current_user_id == entity.id
41
41
  = form_control(:old_password, identity, type: 'password', placeholder: 'Your current password', value: '')
42
42
  = form_control(:password, identity, type: 'password', placeholder: 'Your new password')
43
43
  = form_control(:password_confirmation, identity, type: 'password', label: 'Confirm Password', placeholder: 'Confirm your password')
@@ -1,4 +1,4 @@
1
1
  = form_control(:name, user)
2
2
  = form_control(:surname, user)
3
3
  - if policy(user).permitted_attributes.include? :role_id
4
- = form_control(:role_id, user, type: 'select', options: Ditty::Role.order(:name).as_hash(:id, :name), name: 'user[role_id][]', value: user.new? ? [] : user.roles_dataset.map(:id), multiple: true)
4
+ = form_control(:role_id, user, type: 'select', options: Ditty::Role.select(:id, :name).order(:name).as_hash(:id, :name), name: 'user[role_id][]', value: user.new? ? [] : user.roles_dataset.map(:id), multiple: true)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ditty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgens du Toit
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-21 00:00:00.000000000 Z
11
+ date: 2023-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -294,6 +294,9 @@ dependencies:
294
294
  name: haml
295
295
  requirement: !ruby/object:Gem::Requirement
296
296
  requirements:
297
+ - - "~>"
298
+ - !ruby/object:Gem::Version
299
+ version: '5.1'
297
300
  - - ">="
298
301
  - !ruby/object:Gem::Version
299
302
  version: 5.1.2
@@ -301,6 +304,9 @@ dependencies:
301
304
  prerelease: false
302
305
  version_requirements: !ruby/object:Gem::Requirement
303
306
  requirements:
307
+ - - "~>"
308
+ - !ruby/object:Gem::Version
309
+ version: '5.1'
304
310
  - - ">="
305
311
  - !ruby/object:Gem::Version
306
312
  version: 5.1.2
@@ -350,44 +356,44 @@ dependencies:
350
356
  name: omniauth
351
357
  requirement: !ruby/object:Gem::Requirement
352
358
  requirements:
353
- - - "~>"
359
+ - - ">="
354
360
  - !ruby/object:Gem::Version
355
361
  version: '1.0'
356
362
  type: :runtime
357
363
  prerelease: false
358
364
  version_requirements: !ruby/object:Gem::Requirement
359
365
  requirements:
360
- - - "~>"
366
+ - - ">="
361
367
  - !ruby/object:Gem::Version
362
368
  version: '1.0'
363
369
  - !ruby/object:Gem::Dependency
364
370
  name: omniauth-identity
365
371
  requirement: !ruby/object:Gem::Requirement
366
372
  requirements:
367
- - - "~>"
373
+ - - ">="
368
374
  - !ruby/object:Gem::Version
369
375
  version: '1.0'
370
376
  type: :runtime
371
377
  prerelease: false
372
378
  version_requirements: !ruby/object:Gem::Requirement
373
379
  requirements:
374
- - - "~>"
380
+ - - ">="
375
381
  - !ruby/object:Gem::Version
376
382
  version: '1.0'
377
383
  - !ruby/object:Gem::Dependency
378
384
  name: pundit
379
385
  requirement: !ruby/object:Gem::Requirement
380
386
  requirements:
381
- - - "~>"
387
+ - - ">="
382
388
  - !ruby/object:Gem::Version
383
- version: '1.0'
389
+ version: '2.0'
384
390
  type: :runtime
385
391
  prerelease: false
386
392
  version_requirements: !ruby/object:Gem::Requirement
387
393
  requirements:
388
- - - "~>"
394
+ - - ">="
389
395
  - !ruby/object:Gem::Version
390
- version: '1.0'
396
+ version: '2.0'
391
397
  - !ruby/object:Gem::Dependency
392
398
  name: rack-contrib
393
399
  requirement: !ruby/object:Gem::Requirement
@@ -546,14 +552,14 @@ dependencies:
546
552
  name: wisper
547
553
  requirement: !ruby/object:Gem::Requirement
548
554
  requirements:
549
- - - "~>"
555
+ - - ">="
550
556
  - !ruby/object:Gem::Version
551
557
  version: '2.0'
552
558
  type: :runtime
553
559
  prerelease: false
554
560
  version_requirements: !ruby/object:Gem::Requirement
555
561
  requirements:
556
- - - "~>"
562
+ - - ">="
557
563
  - !ruby/object:Gem::Version
558
564
  version: '2.0'
559
565
  description: Sinatra Based Application Framework
@@ -733,7 +739,6 @@ files:
733
739
  - views/partials/search.haml
734
740
  - views/partials/sidebar.haml
735
741
  - views/partials/sort_ui.haml
736
- - views/partials/timespan_selector.haml
737
742
  - views/partials/topbar.haml
738
743
  - views/partials/user_associations.haml
739
744
  - views/quick_start.haml
@@ -1,64 +0,0 @@
1
- - if defined?(context)
2
- - if context.interval
3
- %li.nav-item.dropdown.no-arrow
4
- %a#intervalDropdown.nav-link.dropdown-toggle{ href: '#', role: 'button', 'aria-haspopup': 'true', 'area-expanded': 'false', 'data-toggle': 'dropdown' }
5
- %span.mr-2.d-none.d-lg-inline.text-gray-600
6
- Interval /
7
- %strong
8
- = context.interval
9
- .dropdown-menu.dropdown-menu-right.shadow.animated--grow-in{ 'aria-labelledby': 'intervalDropdown' }
10
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '1m'))}", class: context.interval == '1m' ? 'active' : '' }
11
- 1 Minute
12
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '5m'))}", class: context.interval == '5m' ? 'active' : '' }
13
- 5 Minutes
14
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '15m'))}", class: context.interval == '15m' ? 'active' : '' }
15
- 15 Minutes
16
- .dropdown-divider
17
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '1h'))}", class: context.interval == '1h' ? 'active' : '' }
18
- 1 Hour
19
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '3h'))}", class: context.interval == '3h' ? 'active' : '' }
20
- 3 Hours
21
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '6h'))}", class: context.interval == '6h' ? 'active' : '' }
22
- 6 Hours
23
- .dropdown-divider
24
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '1d'))}", class: context.interval == '1d' ? 'active' : '' }
25
- 1 Day
26
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '2d'))}", class: context.interval == '2d' ? 'active' : '' }
27
- 2 Days
28
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(interval: '1w'))}", class: context.interval == '1w' ? 'active' : '' }
29
- 1 Week
30
-
31
- - if context.timespan
32
- %li.nav-item.dropdown.no-arrow
33
- %a#timespanDropdown.nav-link.dropdown-toggle{ href: '#', role: 'button', 'aria-haspopup': 'true', 'area-expanded': 'false', 'data-toggle': 'dropdown' }
34
- %span.mr-2.d-none.d-lg-inline.text-gray-600
35
- Timespan /
36
- %strong
37
- = context.timespan.split('/').first
38
- .dropdown-menu.dropdown-menu-right.shadow.animated--grow-in{ 'aria-labelledby': 'timespanDropdown' }
39
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '1h', interval: ''))}", class: context.timespan == '1h' ? 'active' : '' }
40
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
41
- 1 Hour
42
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '1d', interval: ''))}", class: context.timespan == '1d' ? 'active' : '' }
43
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
44
- 1 Day
45
- .dropdown-divider
46
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '1w/d', interval: ''))}", class: context.timespan == '1w/d' ? 'active' : '' }
47
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
48
- 1 Week
49
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '2w/d', interval: ''))}", class: context.timespan == '2w/d' ? 'active' : '' }
50
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
51
- 2 Weeks
52
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '1M/d', interval: ''))}", class: context.timespan == '1M/d' ? 'active' : '' }
53
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
54
- 1 Month
55
- .dropdown-divider
56
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '3M/d', interval: ''))}", class: context.timespan == '3M/d' ? 'active' : '' }
57
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
58
- 3 Months
59
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '6M/d', interval: ''))}", class: context.timespan == '6M/d' ? 'active' : '' }
60
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
61
- 6 Months
62
- %a.dropdown-item{ href: "?#{URI.encode_www_form(and_params.merge(timespan: '1y/d', interval: ''))}", class: context.timespan == '1y/d' ? 'active' : '' }
63
- %i.fas.fa-chevron-right.fa-sm.fa-fw.mr-2.text-gray-400
64
- 1 Year