ditty 0.10.2 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ef6903491ad20b85916f196652705811820c13f3ed14552fc9b35687dbe7309
4
- data.tar.gz: 1431f00aee5e7093757936e5b12231d091d41bcfb798dda53e534d289f43c5dc
3
+ metadata.gz: 0ecdd202f458e55d205e3e77b295a3c8908f7d3ecaf3d894fa9ae09c62d8b4fd
4
+ data.tar.gz: bcc5967235a668d896cee25228130b1ab2d98761dc2fb2125d5f7f2d2912881b
5
5
  SHA512:
6
- metadata.gz: b8383f78d50a4b1a2379b34f5ea81969fc019130dd953e7218d499a7596217ea12eef13d804de7b888da7145fc865bb3e798453f664597951f953df5a06eec00
7
- data.tar.gz: c36d004df0b9ce15aba2fbf99f376fef6fa5f70a2cc4338163cd360c730f2398ab4a5b3b0c29362ba20a90502397475aa4b4a1769b3acb9f50965eae371b680a
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
@@ -52,7 +52,7 @@ module Ditty
52
52
  environment: ENV['APP_ENV'] || 'development',
53
53
  Port: ENV['APP_PORT'] || 9292,
54
54
  Host: ENV['APP_HOST'] || '0.0.0.0',
55
- config: "config.ru"
55
+ config: 'config.ru'
56
56
  }
57
57
  puts 'Starting the Ditty Server'
58
58
  Rack::Server.start(rack_opts)
@@ -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
 
@@ -39,9 +39,7 @@ module Ditty
39
39
  after do
40
40
  return if settings.environment == 'production'
41
41
 
42
- if (response.successful? || response.redirection?) && @skip_verify == false && (settings.environment != 'production')
43
- verify_authorized
44
- end
42
+ verify_authorized if (response.successful? || response.redirection?) && @skip_verify == false
45
43
  end
46
44
 
47
45
  after '/' do
@@ -67,7 +65,7 @@ module Ditty
67
65
 
68
66
  entity = settings.model_class.new(permitted_parameters(settings.model_class, :create))
69
67
  haml :"#{view_location}/new",
70
- locals: { entity: entity, title: heading(:new) },
68
+ locals: { entity: entity, title: heading(:new), actions: actions(action: :new) },
71
69
  layout: layout
72
70
  end
73
71
 
@@ -100,7 +98,7 @@ module Ditty
100
98
 
101
99
  flash[:redirect_to] = "#{base_path}/#{entity.display_id}" unless flash.keep(:redirect_to)
102
100
  haml :"#{view_location}/edit",
103
- locals: { entity: entity, title: heading(:edit) },
101
+ locals: { entity: entity, title: heading(:edit), actions: actions(entity: entity, action: :edit) },
104
102
  layout: layout
105
103
  end
106
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
@@ -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?
@@ -69,7 +69,7 @@ module Ditty
69
69
  end
70
70
 
71
71
  # Callbacks
72
- def before_save
72
+ def before_validation
73
73
  super
74
74
  encrypt_password unless password == '' || password.nil?
75
75
  end
@@ -81,7 +81,7 @@ module Ditty
81
81
  end
82
82
 
83
83
  def password_required
84
- crypted_password.blank? || !password.blank?
84
+ crypted_password.blank? || password.present?
85
85
  end
86
86
  end
87
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')
@@ -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.2'
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
@@ -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')
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.2
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-09-08 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