proxes 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +24 -0
  3. data/.gitignore +13 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +12 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +18 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.ci +15 -0
  10. data/Gemfile.dev +10 -0
  11. data/Gemfile.dev.lock +155 -0
  12. data/LICENSE.txt +8 -0
  13. data/README.md +83 -0
  14. data/Rakefile +9 -0
  15. data/Vagrantfile +46 -0
  16. data/bin/console +15 -0
  17. data/bin/setup +8 -0
  18. data/config.ru +64 -0
  19. data/config/logger.yml +3 -0
  20. data/gulpfile.js +80 -0
  21. data/lib/proxes.rb +3 -0
  22. data/lib/proxes/app.rb +48 -0
  23. data/lib/proxes/controllers/application.rb +53 -0
  24. data/lib/proxes/controllers/audit_logs.rb +34 -0
  25. data/lib/proxes/controllers/auth_identity.rb +21 -0
  26. data/lib/proxes/controllers/component.rb +108 -0
  27. data/lib/proxes/controllers/permissions.rb +10 -0
  28. data/lib/proxes/controllers/roles.rb +10 -0
  29. data/lib/proxes/controllers/users.rb +119 -0
  30. data/lib/proxes/db.rb +17 -0
  31. data/lib/proxes/helpers/authentication.rb +45 -0
  32. data/lib/proxes/helpers/component.rb +40 -0
  33. data/lib/proxes/helpers/indices.rb +16 -0
  34. data/lib/proxes/helpers/pundit.rb +39 -0
  35. data/lib/proxes/helpers/views.rb +41 -0
  36. data/lib/proxes/loggers/elasticsearch.rb +9 -0
  37. data/lib/proxes/models/audit_log.rb +12 -0
  38. data/lib/proxes/models/identity.rb +67 -0
  39. data/lib/proxes/models/permission.rb +17 -0
  40. data/lib/proxes/models/role.rb +14 -0
  41. data/lib/proxes/models/user.rb +57 -0
  42. data/lib/proxes/policies/application_policy.rb +20 -0
  43. data/lib/proxes/policies/audit_log_policy.rb +40 -0
  44. data/lib/proxes/policies/identity_policy.rb +24 -0
  45. data/lib/proxes/policies/permission_policy.rb +40 -0
  46. data/lib/proxes/policies/request/root_policy.rb +12 -0
  47. data/lib/proxes/policies/request/search_policy.rb +15 -0
  48. data/lib/proxes/policies/request/snapshot_policy.rb +12 -0
  49. data/lib/proxes/policies/request/stats_policy.rb +15 -0
  50. data/lib/proxes/policies/request_policy.rb +69 -0
  51. data/lib/proxes/policies/role_policy.rb +40 -0
  52. data/lib/proxes/policies/token_policy.rb +46 -0
  53. data/lib/proxes/policies/user_policy.rb +46 -0
  54. data/lib/proxes/rake_tasks.rb +59 -0
  55. data/lib/proxes/request.rb +51 -0
  56. data/lib/proxes/request/root.rb +10 -0
  57. data/lib/proxes/request/search.rb +37 -0
  58. data/lib/proxes/request/snapshot.rb +16 -0
  59. data/lib/proxes/request/stats.rb +30 -0
  60. data/lib/proxes/security.rb +59 -0
  61. data/lib/proxes/seed.rb +10 -0
  62. data/lib/proxes/services/logger.rb +50 -0
  63. data/lib/proxes/version.rb +4 -0
  64. data/migrate/001_tables.rb +47 -0
  65. data/migrate/002_audit_log.rb +11 -0
  66. data/package.json +34 -0
  67. data/proxes.gemspec +44 -0
  68. data/public/js/bundle.js +28988 -0
  69. data/src/scripts/app.js +10 -0
  70. data/views/404.haml +1 -0
  71. data/views/audit_logs/index.haml +18 -0
  72. data/views/error.haml +4 -0
  73. data/views/getting_started.haml +16 -0
  74. data/views/identity/login.haml +19 -0
  75. data/views/identity/register.haml +17 -0
  76. data/views/index.haml +3 -0
  77. data/views/layout.haml +48 -0
  78. data/views/partials/delete_form.haml +4 -0
  79. data/views/partials/form_control.haml +21 -0
  80. data/views/partials/navbar.haml +25 -0
  81. data/views/partials/notifications.haml +24 -0
  82. data/views/partials/pager.haml +19 -0
  83. data/views/partials/sidebar.haml +32 -0
  84. data/views/permissions/display.haml +24 -0
  85. data/views/permissions/edit.haml +11 -0
  86. data/views/permissions/form.haml +3 -0
  87. data/views/permissions/index.haml +14 -0
  88. data/views/permissions/new.haml +10 -0
  89. data/views/roles/display.haml +33 -0
  90. data/views/roles/edit.haml +11 -0
  91. data/views/roles/form.haml +1 -0
  92. data/views/roles/index.haml +17 -0
  93. data/views/roles/new.haml +10 -0
  94. data/views/users/display.haml +32 -0
  95. data/views/users/edit.haml +11 -0
  96. data/views/users/identity.haml +3 -0
  97. data/views/users/index.haml +20 -0
  98. data/views/users/new.haml +11 -0
  99. data/views/users/profile.haml +37 -0
  100. data/views/users/user.haml +3 -0
  101. metadata +424 -0
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import ProxesComponents from 'react-proxes-components/react-proxes-components';
4
+
5
+ ReactDOM.render(<ProxesComponents pollInterval="30000"/>, document.getElementById('react-dashboard'));
6
+
7
+ // ReactDOM.render(
8
+ // <Health store={new ESStore()}/>,
9
+ // document.getElementById('indexlist')
10
+ // );
@@ -0,0 +1 @@
1
+ %h2 4 oh 4
@@ -0,0 +1,18 @@
1
+ .row
2
+ .col-md-12
3
+ .panel.panel-default
4
+ %table.table.table-striped
5
+ %thead
6
+ %tr
7
+ %th User email
8
+ %th Action
9
+ %th Created at
10
+ %tbody
11
+ -list.each do |entity|
12
+ %tr
13
+ %td
14
+ %a{ href: "/_proxes/users/#{entity.user.id}" }= entity.user.email
15
+ %td
16
+ = entity.action
17
+ %td
18
+ = entity.created_at.strftime('%Y-%m-%d %H:%M:%S')
@@ -0,0 +1,4 @@
1
+ %h2 Whoops
2
+
3
+ %p.lead
4
+ = error.class
@@ -0,0 +1,16 @@
1
+ .row
2
+ .col-md-12
3
+ .panel.panel-success
4
+ .panel-heading
5
+ You can now start using this Elasticsearch cluster:
6
+ .panel-body
7
+ %h4 Send Data
8
+ %pre
9
+ :plain
10
+ curl -X POST '#{ request.base_url }/#{ current_user.email }/test' \
11
+ -u #{ current_user.email }:yourpassword -d '{"message":"Get cracking!"}'
12
+
13
+ %h4 Search
14
+ %pre
15
+ curl -X GET '#{ request.base_url }/_search' -u #{ current_user.email }:yourpassword
16
+
@@ -0,0 +1,19 @@
1
+ .row
2
+ .col-sm-3
3
+ .col-sm-6
4
+ .panel.panel-default
5
+ .panel-heading
6
+ ProxES Login
7
+ .panel-body
8
+ %form{ method: 'post', action: '/_proxes/auth/identity/callback' }
9
+ .form-group
10
+ %label.control-label Username
11
+ %input.form-control.border-input{ name: 'username' }
12
+ .form-group
13
+ %label.control-label Password
14
+ %input.form-control.border-input{ name: 'password', type: 'password' }
15
+ %button.btn.btn-primary{ type: 'submit' } Log In
16
+ .pull-right
17
+ No account yet?
18
+ %a.btn.btn-default{ href: '/auth/identity/register' } Register
19
+ .col-sm-3
@@ -0,0 +1,17 @@
1
+ .row
2
+ .col-md-12
3
+ %h1 ProxES Registration
4
+ .row
5
+ .col-md-2
6
+ .col-md-8
7
+ .panel.panel-default
8
+ .panel-heading
9
+ ProxES Registration
10
+ .panel-body
11
+ %form.form-horizontal{ method: 'post', action: '/_proxes/auth/identity/new' }
12
+ = form_control(:username, identity, label: 'Email', placeholder: 'Your email address')
13
+ = form_control(:password, identity, label: 'Password', type: :password)
14
+ = form_control(:password_confirmation, identity, label: 'Confirm Password', type: :password)
15
+
16
+ %button.btn.btn-primary{ type: 'submit' } Register
17
+ .col-md-2
@@ -0,0 +1,3 @@
1
+ = haml :getting_started
2
+
3
+ #react-dashboard
@@ -0,0 +1,48 @@
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
+ %link{ rel: 'apple-touch-icon', sizes: '76x76', href: '/assets/img/apple-icon.png' }
8
+
9
+ %title
10
+ ProxES
11
+ - if defined? title
12
+ = "- #{title}"
13
+
14
+ %meta{ name: 'description', content: '' }
15
+ %meta{ name: 'author', content: '' }
16
+
17
+ / Le styles
18
+ %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css', media: 'screen' }
19
+ %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' }
20
+ %link{ rel: 'stylesheet', href: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.css', media: 'screen' }
21
+ %link{ rel: 'stylesheet', href: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css', media: 'screen' }
22
+ /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.min.js"></script>
23
+ /[if lt IE 9] <script src="https://cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
24
+
25
+ %body
26
+ #wrapper
27
+ = haml :'partials/navbar', locals: { title: (defined?(title) ? title : 'ProxES') }
28
+ #page-wrapper
29
+ .row
30
+ .col-md-12
31
+ -if defined? title
32
+ %h1.page-header= title
33
+ = haml :'partials/notifications'
34
+ = yield
35
+ %footer.footer.text-muted.text-center
36
+ %hr
37
+ .copyright
38
+ :plain
39
+ &copy; <script>document.write(new Date().getFullYear())</script>, JadeIT
40
+
41
+
42
+ / Placed at the end of the document so the pages load faster
43
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.0/jquery.min.js' }
44
+ %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' }
45
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/metisMenu/2.5.2/metisMenu.min.js' }
46
+ %script{ type: 'text/javascript', src: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js' }
47
+ %script{ type: 'text/javascript', src: 'https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js' }
48
+ %script{ type: 'text/javascript', src: '/js/bundle.js' }
@@ -0,0 +1,4 @@
1
+ <form method="post" action="<%= base_path %>/<%= entity.id %>">
2
+ <input type="hidden" name="_method" value="DELETE">
3
+ <button type="submit" class="btn btn-danger"><%= delete_label %></button>
4
+ </form>
@@ -0,0 +1,21 @@
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}
8
+ - 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: (k.to_s == model[field].to_s)}= v
15
+ - elsif type == 'textarea'
16
+ %textarea{attributes}
17
+ = model[field]
18
+ - else
19
+ %input{attributes, type: type, value: model[field]}
20
+ - if model.errors[field]
21
+ %p.help-block.text-danger= model.errors[field].join(', ')
@@ -0,0 +1,25 @@
1
+ .navbar.navbar-default.navbar-static-top{ role: 'navigation', style: 'margin-bottom: 0' }
2
+ .navbar-header
3
+ %span.navbar-brand
4
+ ProxES
5
+ - if defined? title
6
+ = "- #{title}"
7
+ %button.navbar-toggle.collapsed{ 'type' => 'button', 'data-toggle' => 'collapse', 'data-target' => '#navbar', 'aria-expanded' => 'false', 'aria-controls' => 'navbar' }
8
+ %span.sr-only Toggle navigation
9
+ %span.icon-bar.bar1
10
+ %span.icon-bar.bar2
11
+ %span.icon-bar.bar3
12
+ -if current_user
13
+ %form.nav.navbar-top-links.navbar-form.navbar-right{ action: '/_proxes/auth/identity', method: 'post' }
14
+ %a.btn.btn-default{ href: "/_proxes/users/profile" } My Account
15
+ %input{ name: '_method', value: 'DELETE', type: 'hidden' }
16
+ %button.btn.btn-default{ type: 'submit' }
17
+ / %i.ti-panel
18
+ Logout
19
+ - else
20
+ %ul.nav.navbar-top-links.navbar-right
21
+ %li
22
+ %a.btn.btn-link{ href: '/auth/identity' }
23
+ Log In
24
+ .navbar-default.sidebar{ role: 'navigation' }
25
+ = haml :'partials/sidebar'
@@ -0,0 +1,24 @@
1
+ - if flash[:danger]
2
+ .alert.alert-danger.alert-dismissible{ role: "alert" }
3
+ %button.close{ 'type' => 'button', 'aria-hidden' => 'true', 'area-label' => 'true', 'data-dismiss' => 'alert' } &times;
4
+ - (flash[:danger].is_a?(Array) ? flash[:danger] : [flash[:danger]]).each do |msg|
5
+ %span= msg
6
+
7
+ - if flash[:warning]
8
+ .alert.alert-warning.alert-dismissible{ role: "alert" }
9
+ %button.close{ 'type' => 'button', 'aria-hidden' => 'true', 'area-label' => 'true', 'data-dismiss' => 'alert' } &times;
10
+ - (flash[:warning].is_a?(Array) ? flash[:warning] : [flash[:warning]]).each do |msg|
11
+ %span= msg
12
+
13
+ - if flash[:success]
14
+ .alert.alert-success.alert-dismissible{ role: "alert" }
15
+ %button.close{ 'type' => 'button', 'aria-hidden' => 'true', 'area-label' => 'true', 'data-dismiss' => 'alert' } &times;
16
+ - (flash[:success].is_a?(Array) ? flash[:success] : [flash[:success]]).each do |msg|
17
+ %span= msg
18
+
19
+
20
+ - if flash[:info]
21
+ .alert.alert-info.alert-dismissible{ role: "alert" }
22
+ %button.close{ 'type' => 'button', 'aria-hidden' => 'true', 'area-label' => 'true', 'data-dismiss' => 'alert' } &times;
23
+ - (flash[:info].is_a?(Array) ? flash[:info] : [flash[:info]]).each do |msg|
24
+ %span= msg
@@ -0,0 +1,19 @@
1
+ <nav aria-label="Page navigation">
2
+ <p class="text-center">
3
+ Showing <%= list.current_page_record_range %> of <%= list.pagination_record_count %> records
4
+ </p>
5
+ <ul class="pager">
6
+ <li>
7
+ <a href="<%= base_path %>?page=1&count=<%= list.page_size %>">First</a>
8
+ </li>
9
+ <li<% if list.first_page? %> class="disabled"<% end %>>
10
+ <a href="<%= prev_link %>">Previous</a>
11
+ </li>
12
+ <li<% if list.last_page? %> class="disabled"<% end %>>
13
+ <a href="<%= next_link %>">Next</a>
14
+ </li>
15
+ <li>
16
+ <a href="<%= base_path %>?page=<%= list.page_count %>&count=<%= list.page_size %>">Last</a>
17
+ </li>
18
+ </ul>
19
+ </nav>
@@ -0,0 +1,32 @@
1
+ %ul.nav.nav-pills.nav-stacked
2
+ - if defined?(current_user) && current_user
3
+ %li
4
+ %a{ href: '/_proxes' }
5
+ %i.fa.fa-dashboard.fa-fw
6
+ Dashboard
7
+ - if current_user.super_admin?
8
+ %li
9
+ %a{ href: '/_proxes/users' }
10
+ %i.fa.fa-user.fa-fw
11
+ Users
12
+ %li
13
+ %a{ href: '/_proxes/roles' }
14
+ %i.fa.fa-group.fa-fw
15
+ Roles
16
+ %li
17
+ %a{ href: '/_proxes/permissions' }
18
+ %i.fa.fa-check-square-o.fa-fw
19
+ Permissions
20
+ %li
21
+ %a{ href: '/_proxes/settings' }
22
+ %i.fa.fa-cog.fa-fw
23
+ Settings
24
+ - else
25
+ %li.active
26
+ %a{ href: '/auth/identity' }
27
+ %i.fa.fa-user.fa-fw
28
+ Log In
29
+ %li
30
+ %a{ href: '/auth/identity/register' }
31
+ %i.fa.fa-pencil-square-o.fa-fw
32
+ Register
@@ -0,0 +1,24 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ %p.description
7
+ %label Role:
8
+ = entity.role.name
9
+ %p.description
10
+ %label Verb:
11
+ = entity.verb
12
+ %p.description
13
+ %label Pattern:
14
+ = entity.pattern
15
+
16
+ .row
17
+ .col-md-6
18
+ %a.btn.btn-default{ href: "/_proxes/permissions/#{entity.id}/edit" } Edit
19
+ .col-md-6.text-right
20
+ - if policy(entity).delete?
21
+ %form{ method: 'post', action: "/_proxes/permissions/#{entity.id}" }
22
+ %input{ name: '_method', value: 'DELETE', type: 'hidden' }
23
+ %button.btn.btn-warning{ type: 'submit' } Delete
24
+ .col-md-2
@@ -0,0 +1,11 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ %form.form-horizontal{ method: 'post', action: "/_proxes/permissions/#{entity.id}" }
7
+ %input{ name: '_method', value: 'PUT', type: 'hidden' }
8
+ = haml :'permissions/form', locals: { entity: entity }
9
+ %button.btn.btn-primary{ type: 'submit' }
10
+ Update Permission
11
+ .col-md-2
@@ -0,0 +1,3 @@
1
+ = form_control(:role_id, entity, type: 'select', options: ProxES::Role.to_hash(:id, :name))
2
+ = form_control(:verb, entity, type: 'select', options: ProxES::Permission.verbs)
3
+ = form_control(:pattern, entity)
@@ -0,0 +1,14 @@
1
+ .row
2
+ .col-md-12
3
+ .panel.panel-default
4
+ .panel-heading VERB Pattern
5
+ %table.table.table-striped
6
+ %tbody
7
+ -list.each do |entity|
8
+ %tr
9
+ %td
10
+ %a.btn-block{ href: "/_proxes/permissions/#{entity.id}" }
11
+ = entity.verb
12
+ = entity.pattern
13
+ .panel-body.text-right
14
+ %a.btn.btn-primary{ href: '/_proxes/permissions/new' } New Permission
@@ -0,0 +1,10 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ %form.form-horizontal{ method: 'post', action: '/_proxes/permissions' }
7
+ = haml :'permissions/form', locals: { entity: entity }
8
+ %button.btn.btn-primary.btn{ type: 'submit' }
9
+ Create Permission
10
+ .col-md-2
@@ -0,0 +1,33 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ %p.description
7
+ %label Name:
8
+ = entity.name
9
+
10
+ .row
11
+ .col-md-6
12
+ %a.btn.btn-default{ href: "/_proxes/roles/#{entity.id}/edit" } Edit
13
+ .col-md-6.text-right
14
+ - if policy(entity).delete?
15
+ %form{ method: 'post', action: "/_proxes/roles/#{entity.id}" }
16
+ %input{ name: '_method', value: 'DELETE', type: 'hidden' }
17
+ %button.btn.btn-warning{ type: 'submit' } Delete
18
+ .col-md-2
19
+
20
+ .row
21
+ .col-md-2
22
+ .col-md-8
23
+ .panel.panel-default
24
+ .panel-heading
25
+ %h3 Permissions
26
+ %table.table
27
+ - entity.permissions.each do |permission|
28
+ %tr
29
+ %td
30
+ %a.btn-block{ href: "/_proxes/permissions/#{entity.id}" }
31
+ = permission.verb
32
+ = permission.pattern
33
+ .col-md-2
@@ -0,0 +1,11 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ %form.form-horizontal{ method: 'post', action: "/_proxes/roles/#{entity.id}" }
7
+ %input{ name: '_method', value: 'PUT', type: 'hidden' }
8
+ = haml :'roles/form', locals: { entity: entity }
9
+ %button.btn.btn-primary{ type: 'submit' }
10
+ Update Role
11
+ .col-md-2
@@ -0,0 +1 @@
1
+ = form_control(:name, entity)
@@ -0,0 +1,17 @@
1
+ .row
2
+ .col-md-12
3
+ .panel.panel-default
4
+ %table.table.table-striped
5
+ %thead
6
+ %tr
7
+ %th Name
8
+ %th Permissions
9
+ %tbody
10
+ -list.each do |entity|
11
+ %tr
12
+ %td
13
+ %a{ href: "/_proxes/roles/#{entity.id}" }= entity.name
14
+ %td
15
+ = entity.permissions_dataset.count
16
+ .panel-body.text-right
17
+ %a.btn.btn-primary{ href: '/_proxes/roles/new' } New Role
@@ -0,0 +1,10 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ %form.form-horizontal{ method: 'post', action: '/_proxes/roles' }
7
+ = haml :'roles/form', locals: { entity: entity }
8
+ %button.btn.btn-primary.btn{ type: 'submit' }
9
+ Create Role
10
+ .col-md-2
@@ -0,0 +1,32 @@
1
+ .row
2
+ .col-md-2
3
+ .col-md-8
4
+ .panel.panel-default
5
+ .panel-body
6
+ .author
7
+ %img.pull-right.thumbnail{ src: entity.gravatar }
8
+ %h4.title= entity.email
9
+
10
+ %hr
11
+ %p.description
12
+ %label Name:
13
+ = entity.name
14
+ %p.description
15
+ %label Surname:
16
+ = entity.surname
17
+ %p.description
18
+ %label Roles:
19
+ = entity.roles_dataset.map(:name).map(&:titlecase).join(', ')
20
+ %p.description
21
+ %label Signed up:
22
+ = entity.created_at.strftime('%Y-%m-%d %H:%M:%S')
23
+
24
+ .row
25
+ .col-md-6
26
+ %a.btn.btn-default{ href: "/_proxes/users/#{entity.id}/edit" } Edit
27
+ .col-md-6.text-right
28
+ - if policy(entity).delete?
29
+ %form{ method: 'post', action: "/_proxes/users/#{entity.id}" }
30
+ %input{ name: '_method', value: 'DELETE', type: 'hidden' }
31
+ %button.btn.btn-warning{ type: 'submit' } Delete
32
+ .col-md-2