ditty 0.7.1 → 0.10.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/.env.test +2 -0
  3. data/.gitignore +3 -0
  4. data/.pryrc +2 -0
  5. data/.rubocop.yml +24 -8
  6. data/.travis.yml +4 -8
  7. data/CNAME +1 -0
  8. data/Dockerfile +18 -0
  9. data/Gemfile.ci +0 -15
  10. data/Rakefile +5 -4
  11. data/Readme.md +24 -2
  12. data/_config.yml +1 -0
  13. data/config.ru +4 -4
  14. data/ditty.gemspec +31 -20
  15. data/docs/CNAME +1 -0
  16. data/docs/_config.yml +1 -0
  17. data/docs/index.md +34 -0
  18. data/exe/ditty +2 -0
  19. data/lib/ditty.rb +30 -4
  20. data/lib/ditty/cli.rb +38 -5
  21. data/lib/ditty/components/ditty.rb +82 -0
  22. data/lib/ditty/controllers/application_controller.rb +267 -0
  23. data/lib/ditty/controllers/{audit_logs.rb → audit_logs_controller.rb} +5 -7
  24. data/lib/ditty/controllers/{auth.rb → auth_controller.rb} +56 -32
  25. data/lib/ditty/controllers/{component.rb → component_controller.rb} +35 -24
  26. data/lib/ditty/controllers/{main.rb → main_controller.rb} +7 -7
  27. data/lib/ditty/controllers/roles_controller.rb +23 -0
  28. data/lib/ditty/controllers/user_login_traits_controller.rb +46 -0
  29. data/lib/ditty/controllers/{users.rb → users_controller.rb} +17 -20
  30. data/lib/ditty/db.rb +9 -5
  31. data/lib/ditty/emails/base.rb +48 -34
  32. data/lib/ditty/generators/crud_generator.rb +114 -0
  33. data/lib/ditty/generators/migration_generator.rb +26 -0
  34. data/lib/ditty/generators/project_generator.rb +52 -0
  35. data/lib/ditty/helpers/authentication.rb +6 -5
  36. data/lib/ditty/helpers/component.rb +11 -2
  37. data/lib/ditty/helpers/pundit.rb +24 -8
  38. data/lib/ditty/helpers/response.rb +38 -15
  39. data/lib/ditty/helpers/views.rb +48 -6
  40. data/lib/ditty/listener.rb +44 -14
  41. data/lib/ditty/memcached.rb +8 -0
  42. data/lib/ditty/middleware/accept_extension.rb +4 -2
  43. data/lib/ditty/middleware/error_catchall.rb +4 -2
  44. data/lib/ditty/models/audit_log.rb +1 -0
  45. data/lib/ditty/models/base.rb +13 -0
  46. data/lib/ditty/models/identity.rb +10 -7
  47. data/lib/ditty/models/role.rb +2 -0
  48. data/lib/ditty/models/user.rb +40 -3
  49. data/lib/ditty/models/user_login_trait.rb +17 -0
  50. data/lib/ditty/policies/audit_log_policy.rb +6 -6
  51. data/lib/ditty/policies/role_policy.rb +3 -3
  52. data/lib/ditty/policies/user_login_trait_policy.rb +45 -0
  53. data/lib/ditty/policies/user_policy.rb +3 -3
  54. data/lib/ditty/rubocop.rb +3 -0
  55. data/lib/ditty/seed.rb +2 -0
  56. data/lib/ditty/services/authentication.rb +31 -15
  57. data/lib/ditty/services/email.rb +22 -12
  58. data/lib/ditty/services/logger.rb +30 -13
  59. data/lib/ditty/services/pagination_wrapper.rb +9 -5
  60. data/lib/ditty/services/settings.rb +19 -7
  61. data/lib/ditty/tasks/ditty.rake +127 -0
  62. data/lib/ditty/tasks/omniauth-ldap.rake +43 -0
  63. data/lib/ditty/templates/.gitignore +5 -0
  64. data/lib/ditty/templates/.rspec +2 -0
  65. data/lib/ditty/templates/.rubocop.yml +7 -0
  66. data/lib/ditty/templates/Rakefile +12 -0
  67. data/lib/ditty/templates/application.rb +12 -0
  68. data/lib/ditty/templates/config.ru +37 -0
  69. data/lib/ditty/templates/controller.rb.erb +64 -0
  70. data/lib/ditty/templates/env.example +4 -0
  71. data/lib/ditty/templates/lib/project.rb.erb +5 -0
  72. data/lib/ditty/templates/migration.rb.erb +7 -0
  73. data/lib/ditty/templates/model.rb.erb +26 -0
  74. data/lib/ditty/templates/pids/.empty_directory +0 -0
  75. data/lib/ditty/templates/policy.rb.erb +48 -0
  76. data/{public → lib/ditty/templates/public}/browserconfig.xml +0 -0
  77. data/lib/ditty/templates/public/css/sb-admin-2.min.css +10 -0
  78. data/lib/ditty/templates/public/css/styles.css +13 -0
  79. data/lib/ditty/templates/public/favicon.ico +0 -0
  80. data/{public → lib/ditty/templates/public}/images/apple-icon.png +0 -0
  81. data/{public → lib/ditty/templates/public}/images/favicon-16x16.png +0 -0
  82. data/{public → lib/ditty/templates/public}/images/favicon-32x32.png +0 -0
  83. data/{public → lib/ditty/templates/public}/images/launcher-icon-1x.png +0 -0
  84. data/{public → lib/ditty/templates/public}/images/launcher-icon-2x.png +0 -0
  85. data/{public → lib/ditty/templates/public}/images/launcher-icon-4x.png +0 -0
  86. data/{public → lib/ditty/templates/public}/images/mstile-150x150.png +0 -0
  87. data/{public → lib/ditty/templates/public}/images/safari-pinned-tab.svg +0 -0
  88. data/lib/ditty/templates/public/js/sb-admin-2.min.js +7 -0
  89. data/lib/ditty/templates/public/js/scripts.js +1 -0
  90. data/{public/manifest.json → lib/ditty/templates/public/manifest.json.erb} +2 -2
  91. data/lib/ditty/templates/settings.yml.erb +19 -0
  92. data/lib/ditty/templates/sidekiq.rb +18 -0
  93. data/lib/ditty/templates/sidekiq.yml +9 -0
  94. data/lib/ditty/templates/spec_helper.rb +43 -0
  95. data/lib/ditty/templates/type.rb.erb +21 -0
  96. data/lib/ditty/templates/views/display.haml.tt +20 -0
  97. data/lib/ditty/templates/views/edit.haml.tt +10 -0
  98. data/lib/ditty/templates/views/form.haml.tt +11 -0
  99. data/lib/ditty/templates/views/index.haml.tt +29 -0
  100. data/lib/ditty/templates/views/new.haml.tt +10 -0
  101. data/lib/ditty/version.rb +1 -1
  102. data/lib/rubocop/cop/ditty/call_services_directly.rb +42 -0
  103. data/migrate/20181209_add_user_login_traits.rb +16 -0
  104. data/migrate/20181209_extend_audit_log.rb +12 -0
  105. data/migrate/20190220_add_parent_id_to_roles.rb +9 -0
  106. data/spec/ditty/api_spec.rb +51 -0
  107. data/spec/ditty/controllers/roles_spec.rb +67 -0
  108. data/spec/ditty/controllers/user_login_traits_spec.rb +72 -0
  109. data/spec/ditty/controllers/users_spec.rb +72 -0
  110. data/spec/ditty/emails/base_spec.rb +76 -0
  111. data/spec/ditty/emails/forgot_password_spec.rb +20 -0
  112. data/spec/ditty/helpers/component_spec.rb +85 -0
  113. data/spec/ditty/models/user_spec.rb +36 -0
  114. data/spec/ditty/services/email_spec.rb +36 -0
  115. data/spec/ditty/services/logger_spec.rb +68 -0
  116. data/spec/ditty/services/settings_spec.rb +63 -0
  117. data/spec/ditty_spec.rb +9 -0
  118. data/spec/factories.rb +46 -0
  119. data/spec/fixtures/logger.yml +17 -0
  120. data/spec/fixtures/section.yml +3 -0
  121. data/spec/fixtures/settings.yml +8 -0
  122. data/spec/spec_helper.rb +51 -0
  123. data/spec/support/api_shared_examples.rb +250 -0
  124. data/spec/support/crud_shared_examples.rb +145 -0
  125. data/views/403.haml +2 -0
  126. data/views/404.haml +2 -4
  127. data/views/500.haml +11 -0
  128. data/views/audit_logs/index.haml +32 -28
  129. data/views/auth/forgot_password.haml +32 -16
  130. data/views/auth/identity.haml +14 -13
  131. data/views/auth/ldap.haml +17 -0
  132. data/views/auth/login.haml +23 -17
  133. data/views/auth/register.haml +20 -18
  134. data/views/auth/register_identity.haml +27 -12
  135. data/views/auth/reset_password.haml +36 -19
  136. data/views/blank.haml +43 -0
  137. data/views/emails/forgot_password.haml +1 -1
  138. data/views/emails/layouts/action.haml +10 -6
  139. data/views/emails/layouts/alert.haml +2 -1
  140. data/views/emails/layouts/billing.haml +2 -1
  141. data/views/embedded.haml +17 -11
  142. data/views/error.haml +8 -3
  143. data/views/index.haml +1 -1
  144. data/views/layout.haml +45 -30
  145. data/views/partials/actions.haml +15 -14
  146. data/views/partials/content_tag.haml +0 -0
  147. data/views/partials/delete_form.haml +1 -1
  148. data/views/partials/filter_control.haml +2 -2
  149. data/views/partials/footer.haml +13 -5
  150. data/views/partials/form_control.haml +30 -19
  151. data/views/partials/form_tag.haml +1 -1
  152. data/views/partials/navitems.haml +42 -0
  153. data/views/partials/notifications.haml +12 -8
  154. data/views/partials/pager.haml +44 -25
  155. data/views/partials/search.haml +15 -11
  156. data/views/partials/sidebar.haml +15 -37
  157. data/views/partials/sort_ui.haml +2 -0
  158. data/views/partials/timespan_selector.haml +64 -0
  159. data/views/partials/topbar.haml +53 -0
  160. data/views/partials/user_associations.haml +32 -0
  161. data/views/quick_start.haml +23 -0
  162. data/views/roles/display.haml +27 -6
  163. data/views/roles/edit.haml +3 -3
  164. data/views/roles/form.haml +1 -0
  165. data/views/roles/index.haml +23 -14
  166. data/views/roles/new.haml +2 -2
  167. data/views/user_login_traits/display.haml +32 -0
  168. data/views/user_login_traits/edit.haml +10 -0
  169. data/views/user_login_traits/form.haml +5 -0
  170. data/views/user_login_traits/index.haml +28 -0
  171. data/views/user_login_traits/new.haml +10 -0
  172. data/views/users/display.haml +15 -16
  173. data/views/users/edit.haml +3 -3
  174. data/views/users/form.haml +0 -0
  175. data/views/users/index.haml +31 -24
  176. data/views/users/login_traits.haml +25 -0
  177. data/views/users/new.haml +2 -2
  178. data/views/users/profile.haml +17 -15
  179. data/views/users/user.haml +1 -1
  180. metadata +314 -76
  181. data/lib/ditty/components/app.rb +0 -77
  182. data/lib/ditty/controllers/application.rb +0 -175
  183. data/lib/ditty/controllers/roles.rb +0 -16
  184. data/lib/ditty/rake_tasks.rb +0 -102
  185. data/views/partials/navbar.haml +0 -23
@@ -1,19 +1,36 @@
1
- .row
2
- .col-md-2
3
- .col-md-8
4
- .panel.panel-default
5
- .panel-body
6
- = edit_form_tag("#{settings.map_path}/auth/reset-password") do
7
- %input{ name: 'token', value: params[:token], type: 'hidden' }
8
- - if identity.errors[:password] && identity.errors[:password].include?('is not strong enough')
9
- .alert.alert-warning
10
- %p Make sure your password is at least 8 characters long, and including the following
11
- %ul
12
- %li Upper- and lowercase letters
13
- %li Numbers
14
- %li Special Characters
15
- = form_control(:password, identity, type: 'password', placeholder: 'Your password', group: 'identity')
16
- = form_control(:password_confirmation, identity, type: 'password', label: 'Confirm Password', placeholder: 'Confirm your password', group: 'identity')
17
- %button.btn.btn-primary{ type: 'submit' }
18
- Reset Password
19
- .col-md-2
1
+ .container
2
+ / Outer Row
3
+ .row.justify-content-center
4
+ .col-xl-10.col-lg-12.col-md-9
5
+ .card.card-default.o-hidden.border-0.shadow-lg.my-5
6
+ .card-body.p-0
7
+ / Nested Row within Card Body
8
+ .row
9
+ .col-lg-6.d-none.d-lg-block.bg-password-image
10
+ .col-lg-6
11
+ .p-5
12
+ .text-center
13
+ %h1.h4.text-gray-900.mb-2
14
+ Reset your Password
15
+ = edit_form_tag("#{settings.map_path}/auth/reset-password") do
16
+ %input{ name: 'token', value: params[:token], type: 'hidden' }
17
+ - if identity.errors[:password] && identity.errors[:password].include?('is not strong enough')
18
+ .alert.alert-warning
19
+ %p Make sure your password is at least 8 characters long, and including the following
20
+ %ul
21
+ %li Upper- and lowercase letters
22
+ %li Numbers
23
+ %li Special Characters
24
+ = form_control(:password, identity, type: 'password', placeholder: 'Your password', group: 'identity')
25
+ = form_control(:password_confirmation, identity, type: 'password', label: 'Confirm Password', placeholder: 'Confirm your password', group: 'identity')
26
+ %button.btn.btn-primary{ type: 'submit' }
27
+ Reset Password
28
+ %hr
29
+ - if policy(::Ditty::User).register?
30
+ .text-center
31
+ %a.small{ href: "#{settings.map_path}/auth/register" } Create an Account!
32
+ .text-center
33
+ %a.small{ href: "#{settings.map_path}/auth/login" } Already have an account? Login!
34
+
35
+ .row.justify-content-center
36
+ .col-xl-10.col-lg-12.col-md-9
data/views/blank.haml ADDED
@@ -0,0 +1,43 @@
1
+ !!! 5
2
+ %html{ lang: 'en' }
3
+ %head
4
+ %meta{ charset: 'utf-8' }
5
+ %meta{ 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1' }
6
+ %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
7
+ %meta{ name: 'theme-color', content: '#ffffff' }
8
+ = Rack::Csrf.csrf_metatag(env)
9
+ %link{ rel: 'manifest', href: '/manifest.json' }
10
+ %link{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/images/favicon-32x32.png' }
11
+ %link{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/images/favicon-16x16.png' }
12
+ %link{ rel: 'apple-touch-icon', sizes: '76x76', href: '/images/apple-icon.png' }
13
+ %link{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' }
14
+
15
+ %title
16
+ = config('ditty.title', 'Ditty')
17
+ - if defined? title
18
+ = "- #{title}"
19
+
20
+ %meta{ name: 'description', content: '' }
21
+ %meta{ name: 'author', content: '' }
22
+
23
+ / Le styles
24
+ %link{ rel: 'stylesheet', href: '/css/sb-admin-2.min.css', media: 'screen' }
25
+ %link{ rel: 'stylesheet', href: '/css/styles.css', media: 'screen' }
26
+ %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css', media: 'screen' }
27
+ %body.bg-gradient-primary
28
+ = yield
29
+
30
+ / Placed at the end of the document so the pages load faster
31
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js' }
32
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js' }
33
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js' }
34
+ / %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js' }
35
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js' }
36
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.min.js' }
37
+ %script{ type: 'text/javascript', src: '/js/sb-admin-2.min.js' }
38
+ %script{ type: 'text/javascript', src: '/js/scripts.js' }
39
+ :javascript
40
+ $(function() {
41
+ $('.select2').select2();
42
+ });
43
+
@@ -8,7 +8,7 @@
8
8
  This reset is only valid for 24 hours
9
9
 
10
10
  %p.text-center
11
- %a{ href: reset_url }
11
+ %a{ href: reset_url } Reset Password
12
12
 
13
13
  %p
14
14
  For security purposes, if you did not request this password reset,
@@ -3,7 +3,8 @@
3
3
  %head
4
4
  %meta{:content => "width=device-width", :name => "viewport"}/
5
5
  %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
6
- %title Actionable emails e.g. reset password
6
+ %title
7
+ = subject
7
8
  :css
8
9
  img {
9
10
  max-width: 100%;
@@ -59,10 +60,13 @@
59
60
  %td.container{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;", :valign => "top", :width => "600"}
60
61
  .content{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;"}
61
62
  = content
62
- - if defined? footer
63
- .footer{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;"}
64
- %table{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;", :width => "100%"}
65
- %tr{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"}
66
- %td.aligncenter.content-block{:align => "center", :style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;", :valign => "top"}
63
+ .footer{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;"}
64
+ %table{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;", :width => "100%"}
65
+ %tr{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;"}
66
+ %td.aligncenter.content-block{:align => "center", :style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;", :valign => "top"}
67
+ - if defined? footer
67
68
  = footer
69
+ - else
70
+ This email was sent to
71
+ = to
68
72
  %td{:style => "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;", :valign => "top"}
@@ -3,7 +3,8 @@
3
3
  %head
4
4
  %meta{:content => "width=device-width", :name => "viewport"}/
5
5
  %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
6
- %title Alerts e.g. approaching your limit
6
+ %title
7
+ = subject
7
8
  :css
8
9
  img {
9
10
  max-width: 100%;
@@ -3,7 +3,8 @@
3
3
  %head
4
4
  %meta{:content => "width=device-width", :name => "viewport"}/
5
5
  %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
6
- %title Billing e.g. invoices and receipts
6
+ %title
7
+ = subject
7
8
  :css
8
9
  img {
9
10
  max-width: 100%;
data/views/embedded.haml CHANGED
@@ -5,6 +5,7 @@
5
5
  %meta{ 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1' }
6
6
  %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
7
7
  %meta{ name: 'theme-color', content: '#ffffff' }
8
+ = Rack::Csrf.csrf_metatag(env)
8
9
  %link{ rel: 'manifest', href: '/manifest.json' }
9
10
  %link{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '#{request.base_url}/images/favicon-32x32.png' }
10
11
  %link{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '#{request.base_url}/images/favicon-16x16.png' }
@@ -20,14 +21,9 @@
20
21
  %meta{ name: 'author', content: '' }
21
22
 
22
23
  / Le styles
23
- %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', media: 'screen' }
24
- %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/css/sb-admin-2.min.css', media: 'screen' }
25
- %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', media: 'screen' }
26
- /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
27
- /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
28
-
29
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js' }
30
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/js/sb-admin-2.min.js' }
24
+ %link{ rel: 'stylesheet', href: '/css/sb-admin-2.min.css', media: 'screen' }
25
+ %link{ rel: 'stylesheet', href: '/css/styles.css', media: 'screen' }
26
+ %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css', media: 'screen' }
31
27
  %body
32
28
  .container-fluid
33
29
  .row
@@ -37,6 +33,16 @@
37
33
  = yield
38
34
 
39
35
  / Placed at the end of the document so the pages load faster
40
- %script{ type: 'text/javascript', src: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' }
41
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js' }
42
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js' }
36
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js' }
37
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js' }
38
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js' }
39
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js' }
40
+ / %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js' }
41
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js' }
42
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.min.js' }
43
+ %script{ type: 'text/javascript', src: '/js/sb-admin-2.min.js' }
44
+ %script{ type: 'text/javascript', src: '/js/scripts.js' }
45
+ :javascript
46
+ $(function() {
47
+ $('.select2').select2();
48
+ });
data/views/error.haml CHANGED
@@ -1,4 +1,9 @@
1
- %h2 Whoops!
1
+ - if ENV['APP_ENV'] != 'production'
2
+ %p.lead
3
+ = error.class
4
+ %br
5
+ %small
6
+ = error.message
2
7
 
3
- %p.lead
4
- = error.class
8
+ %pre
9
+ = error.backtrace.join("\n")
data/views/index.haml CHANGED
@@ -1 +1 @@
1
- %h2 Hello! Ready to get this Ditty started?
1
+ %h1.h3.mb-4.text-gray-800 Home Page
data/views/layout.haml CHANGED
@@ -5,6 +5,7 @@
5
5
  %meta{ 'http-equiv' => 'X-UA-Compatible', 'content' => 'IE=edge,chrome=1' }
6
6
  %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
7
7
  %meta{ name: 'theme-color', content: '#ffffff' }
8
+ = Rack::Csrf.csrf_metatag(env)
8
9
  %link{ rel: 'manifest', href: '/manifest.json' }
9
10
  %link{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/images/favicon-32x32.png' }
10
11
  %link{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/images/favicon-16x16.png' }
@@ -12,7 +13,7 @@
12
13
  %link{ rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' }
13
14
 
14
15
  %title
15
- Ditty
16
+ = config('ditty.title', 'Ditty')
16
17
  - if defined? title
17
18
  = "- #{title}"
18
19
 
@@ -20,43 +21,57 @@
20
21
  %meta{ name: 'author', content: '' }
21
22
 
22
23
  / Le styles
23
- %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', media: 'screen' }
24
- %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/css/sb-admin-2.min.css', media: 'screen' }
25
- %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.css', media: 'screen' }
26
- %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', media: 'screen' }
27
- /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
28
- /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
29
-
30
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js' }
31
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/startbootstrap-sb-admin-2/3.3.7+1/js/sb-admin-2.min.js' }
32
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.js' }
33
- %body
24
+ %link{ rel: 'stylesheet', href: '/css/sb-admin-2.min.css', media: 'screen' }
25
+ %link{ rel: 'stylesheet', href: '/css/styles.css', media: 'screen' }
26
+ %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.8.2/css/all.min.css', media: 'screen' }
27
+ %body#page-top
28
+ / Logout Modal
29
+ #logoutModal.modal.fade{ tabindex: -1, role: 'dialog', 'aria-hidden': 'true' }
30
+ .modal-dialog{ role: 'document' }
31
+ .modal-content
32
+ .modal-header
33
+ %h5.modal-title Ready to Leave?
34
+ %button.close{ type: 'button', 'data-dismiss': 'modal', 'aria-label': 'Close' }
35
+ %span{ 'aria-hidden': 'true' } ×
36
+ .modal-body
37
+ Select "Logout" below if you are ready to end your current session.
38
+ .modal-footer
39
+ %button.btn.btn-secondary{ type: 'button', 'data-dismiss': 'modal' } Cancel
40
+ = delete_form_tag("#{settings.map_path}/auth") do
41
+ %button.btn.btn-primary{ type: 'submit' } Logout
42
+ / Page Wrapper
34
43
  #wrapper
35
- = haml :'partials/navbar', locals: { title: (defined?(title) ? title : 'Ditty') }
36
- #page-wrapper
37
- -if defined?(title) || defined?(actions)
38
- .row
39
- %h1.col-md-9= defined?(title) ? title : '&nbsp;'
40
- .col-md-3.text-right{ style: 'margin-top: 20px' }
41
- = haml :'partials/actions', locals: { actions: defined?(actions) ? actions : {} }
42
- -else
43
- %div{ style: 'padding-top: 20px' }
44
-
45
- .row
46
- .col-md-12
44
+ = haml :'partials/sidebar', locals: { title: (defined?(title) ? title : 'Ditty') }
45
+
46
+ #content-wrapper.d-flex.flex-column
47
+ #content
48
+ = haml :'partials/topbar'
49
+ .container-fluid
50
+ / TODO
51
+ / .row
52
+ / .col-md-12
47
53
  = haml :'partials/notifications'
48
54
 
49
- = yield
55
+ = yield
56
+
50
57
  = haml :'partials/footer'
51
58
 
59
+ / Scroll to Top Button
60
+ %a.scroll-to-top.rounded{ href: '#page-top' }
61
+ %i.fas.fa-angle-up
62
+
52
63
  / Placed at the end of the document so the pages load faster
53
- %script{ type: 'text/javascript', src: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' }
54
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js' }
55
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.bundle.min.js' }
56
- %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/js/select2.min.js' }
64
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js' }
65
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/js/bootstrap.min.js' }
66
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js' }
67
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.4.1/jquery.easing.min.js' }
68
+ / %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js' }
69
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.8.0/Chart.min.js' }
70
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.7/js/select2.min.js' }
71
+ %script{ type: 'text/javascript', src: '/js/sb-admin-2.min.js' }
72
+ %script{ type: 'text/javascript', src: '/js/scripts.js' }
57
73
  :javascript
58
74
  $(function() {
59
- $('.sidebar-nav').metisMenu();
60
75
  $('.select2').select2();
61
76
  });
62
77
 
@@ -1,14 +1,15 @@
1
- - if actions.count > 1
2
- - link, text = actions.shift
3
- .btn-group.text-right
4
- %a.btn.btn-primary{ href: link }= text
5
- %button.btn.btn-primary.dropdown-toggle{ type: 'button', id: 'actions-toggle', data: { toggle: 'dropdown' } }
6
- %span.caret
7
- %span.sr-only Toggle Dropdown
8
- %ul.dropdown-menu{ 'aria-labelledby': 'actions-toggle' }
9
- -actions.each do |k, v|
10
- %li
11
- %a{ href: k }= v
12
- - elsif actions.count > 0
13
- -actions.each do |k, v|
14
- %a.btn.btn-primary{ href: k }= v
1
+ - if defined?(actions)
2
+ - if actions.count > 1
3
+ - link, text = actions.shift
4
+ .btn-group.text-right
5
+ %a.btn.btn-primary{ href: link }= text
6
+ %button.btn.btn-primary.dropdown-toggle{ type: 'button', id: 'actions-toggle', data: { toggle: 'dropdown' } }
7
+ %span.caret
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
File without changes
@@ -1,2 +1,2 @@
1
- = delete_form_tag "#{base_path}/#{entity.id}" do
1
+ = delete_form_tag "#{base_path}/#{entity.display_id}" do
2
2
  %button.btn.btn-danger{ type: 'submit' }= delete_label
@@ -1,6 +1,6 @@
1
- .form-group
1
+ .form-group{ class: total_filters.to_i > 4 ? 'col-3' : 'col-md'}
2
2
  %label{ for: "filter_#{name}" }= label
3
- %select.form-control{ name: name, id: "filter_#{name}" }
3
+ %select.form-control.select2{ style: 'width: 100%', name: name, id: "filter_#{name}" }
4
4
  %option{ value: '' } -- Select One --
5
5
  - options.each do |k,v| k ||= v; v ||= k;
6
6
  %option{ value: k, selected: (params[name].eql? k)}= v
@@ -1,5 +1,13 @@
1
- %footer.footer.text-muted.text-center
2
- %hr
3
- .copyright
4
- :plain
5
- &copy; <script>document.write(new Date().getFullYear())</script>, Ditty
1
+ / Footer
2
+ %footer.sticky-footer.bg-light
3
+ .container.my-auto
4
+ -if ENV['SKIP_BRANDING'].blank?
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 }
@@ -1,20 +1,31 @@
1
- %div{ class: "form-group#{model.errors[field] ? ' has-error' : ''}" }
2
- %label.col-sm-3.control-label{ for: attributes[:id] }= label
3
- .col-sm-9
4
- - type = attributes.delete(:type)
5
- - if type == 'select'
6
- - options = attributes.delete(:options)
7
- %select{attributes}
1
+ - type = attributes.delete(:type)
2
+ - attributes[:value] ||= model.send(field) || default
3
+ - if help_text
4
+ - attributes[:'aria-describedby'] = "#{attributes[:id]}_helptext"
5
+ - if type == 'hidden'
6
+ %input{attributes, type: type, value: model.send(field) || default}
7
+ - else
8
+ .form-group
9
+ - if type != 'file'
10
+ %label{ for: attributes[:id] }= label
11
+ - if type == 'select'
12
+ - options = attributes.delete(:options)
13
+ - value = attributes.delete(:value)
8
14
  - if attributes[:multiple]
9
- - options.each do |k,v| k ||= v; v ||= k;
10
- %option{ value: k, selected: (model.send(field).map(&:id).include? k)}= v
11
- - else
12
- %option{ value: ""} -- Select One --
13
- - options.each do |k,v| k ||= v; v ||= k;
14
- %option{ value: k, selected: [model[field].to_s, default].include?(k.to_s)}= v
15
- - elsif type == 'textarea'
16
- %textarea{attributes}= preserve(model[field])
17
- - else
18
- %input{attributes, type: type, value: model[field] || default}
19
- - if model.errors[field]
20
- %p.help-block.text-danger= model.errors[field].join(', ')
15
+ - attributes[:name] = "#{attributes[:name]}[]" unless attributes[:name][-2..-1] == '[]'
16
+ %select{attributes}
17
+ - if attributes[:multiple]
18
+ - options.each do |k, v| k = v if k.nil?; v = k if v.nil?;
19
+ %option{ value: k.to_s, selected: value&.include?(k) }= v
20
+ - else
21
+ %option{ value: "" } -- Select One --
22
+ - options.each do |k, v| k = v if k.nil?; v = k if v.nil?;
23
+ %option{ value: k.to_s, selected: value == k }= v
24
+ - elsif type == 'textarea'
25
+ %textarea{attributes}= preserve(model.send(field) || default)
26
+ - else
27
+ %input{ attributes, type: type }
28
+ - if model.errors[field]
29
+ .invalid-feedback= model.errors[field].join(', ')
30
+ - if help_text
31
+ %small.form-text.text-muted{ id: "#{attributes[:id]}_helptext" }= help_text