putsreq 0.0.3 → 0.0.4

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 (141) hide show
  1. checksums.yaml +5 -5
  2. data/.babelrc +26 -0
  3. data/.codeclimate.yml +29 -5
  4. data/.env.docker +6 -0
  5. data/.eslintignore +4 -0
  6. data/.eslintrc +22 -0
  7. data/.gitignore +8 -0
  8. data/.postcssrc.yml +3 -0
  9. data/.prettierrc +4 -0
  10. data/.reek +12 -0
  11. data/.rubocop.yml +47 -25
  12. data/.ruby-version +1 -1
  13. data/.simplecov +6 -0
  14. data/.travis.yml +38 -4
  15. data/Dockerfile +44 -0
  16. data/Gemfile +40 -61
  17. data/Gemfile.lock +294 -186
  18. data/Procfile +1 -1
  19. data/README.md +51 -46
  20. data/Rakefile +1 -1
  21. data/{public → app/assets}/images/logo.png +0 -0
  22. data/app/assets/javascripts/application.js +1 -1
  23. data/app/assets/javascripts/buckets.js.coffee +1 -21
  24. data/app/assets/stylesheets/{application.css → application.scss} +2 -1
  25. data/app/assets/stylesheets/buckets.css.scss +0 -2
  26. data/app/controllers/application_controller.rb +11 -9
  27. data/app/controllers/buckets_controller.rb +24 -18
  28. data/app/controllers/home_controller.rb +5 -5
  29. data/app/controllers/requests_controller.rb +13 -2
  30. data/app/helpers/application_helper.rb +6 -2
  31. data/app/interactors/create_or_retrieve_bucket.rb +17 -0
  32. data/app/interactors/create_request.rb +25 -7
  33. data/app/interactors/eval_response_builder.rb +9 -9
  34. data/app/interactors/filter_headers.rb +3 -3
  35. data/app/interactors/forward_request.rb +4 -4
  36. data/app/interactors/record_request.rb +1 -1
  37. data/app/interactors/track.rb +44 -0
  38. data/app/javascript/actionTypes.js +7 -0
  39. data/app/javascript/actions/index.js +64 -0
  40. data/app/javascript/components/Bucket.jsx +91 -0
  41. data/app/javascript/components/Pagination.jsx +67 -0
  42. data/app/javascript/components/RequestCount.jsx +21 -0
  43. data/app/javascript/components/request/Header.jsx +31 -0
  44. data/app/javascript/components/request/Response.jsx +28 -0
  45. data/app/javascript/components/request/index.jsx +47 -0
  46. data/app/javascript/packs/application.js +25 -0
  47. data/app/javascript/packs/hello_react.jsx +26 -0
  48. data/app/javascript/reducers/index.js +23 -0
  49. data/app/javascript/request_poller.js +19 -0
  50. data/app/javascript/store.js +9 -0
  51. data/app/models/bucket.rb +16 -10
  52. data/app/models/request.rb +2 -3
  53. data/app/models/user.rb +2 -2
  54. data/app/serializers/bucket_serializer.rb +11 -0
  55. data/app/serializers/request_serializer.rb +69 -0
  56. data/app/views/buckets/_buttons.html.erb +3 -3
  57. data/app/views/buckets/_form.html.erb +6 -6
  58. data/app/views/buckets/_readonly_buttons.html.erb +1 -1
  59. data/app/views/buckets/show.html.erb +12 -25
  60. data/app/views/home/index.html.erb +22 -16
  61. data/app/views/layouts/application.html.erb +21 -22
  62. data/app/views/layouts/devise.html.erb +16 -18
  63. data/app/views/shared/_flash.html.erb +3 -1
  64. data/bin/byebug +21 -0
  65. data/bin/cc-tddium-post-worker +21 -0
  66. data/bin/codeclimate-test-reporter +21 -0
  67. data/bin/coderay +21 -0
  68. data/bin/dotenv +21 -0
  69. data/bin/htmldiff +21 -0
  70. data/bin/httparty +21 -0
  71. data/bin/httpclient +21 -0
  72. data/bin/ldiff +21 -0
  73. data/bin/mongo_console +21 -0
  74. data/bin/nokogiri +21 -0
  75. data/bin/pry +21 -0
  76. data/bin/putsreq +18 -71
  77. data/bin/rackup +21 -0
  78. data/bin/rails +1 -1
  79. data/bin/ri +21 -0
  80. data/bin/rollbar-rails-runner +21 -0
  81. data/bin/rspec +10 -5
  82. data/bin/safe_yaml +21 -0
  83. data/bin/sass +21 -0
  84. data/bin/sass-convert +21 -0
  85. data/bin/scss +21 -0
  86. data/bin/sdoc +21 -0
  87. data/bin/sdoc-merge +21 -0
  88. data/bin/setup +38 -0
  89. data/bin/sprockets +21 -0
  90. data/bin/thor +21 -0
  91. data/bin/tilt +21 -0
  92. data/bin/unicorn +21 -0
  93. data/bin/unicorn_rails +21 -0
  94. data/bin/update +29 -0
  95. data/bin/webpack +15 -0
  96. data/bin/webpack-dev-server +15 -0
  97. data/bin/yarn +11 -0
  98. data/config/application.rb +6 -4
  99. data/config/boot.rb +1 -1
  100. data/config/environment.rb +1 -1
  101. data/config/environments/development.rb +7 -3
  102. data/config/environments/production.rb +5 -2
  103. data/config/environments/test.rb +3 -1
  104. data/config/initializers/devise.rb +2 -2
  105. data/config/initializers/rack_attack.rb +24 -0
  106. data/config/initializers/rollbar.rb +58 -0
  107. data/config/initializers/setup_email.rb +7 -7
  108. data/config/mongoid.yml +2 -2
  109. data/config/newrelic.yml +45 -0
  110. data/config/puma.rb +15 -0
  111. data/config/routes.rb +2 -0
  112. data/config/webpack/development.js +5 -0
  113. data/config/webpack/environment.js +3 -0
  114. data/config/webpack/production.js +5 -0
  115. data/config/webpack/test.js +5 -0
  116. data/config/webpacker.yml +70 -0
  117. data/docker-compose.yml +25 -0
  118. data/lib/putsreq/cli_helper.rb +114 -0
  119. data/lib/putsreq/version.rb +1 -1
  120. data/package.json +30 -0
  121. data/putsreq.gemspec +6 -7
  122. data/spec/controllers/buckets_controller_spec.rb +54 -46
  123. data/spec/interactors/create_or_retrieve_bucket_spec.rb +25 -0
  124. data/spec/interactors/create_request_spec.rb +11 -8
  125. data/spec/interactors/create_response_spec.rb +1 -1
  126. data/spec/interactors/eval_response_builder_spec.rb +14 -17
  127. data/spec/interactors/forward_request_spec.rb +4 -4
  128. data/spec/lib/putsreq/cli_helper_spec.rb +114 -0
  129. data/spec/models/bucket_spec.rb +3 -3
  130. data/spec/spec_helper.rb +25 -25
  131. data/yarn.lock +7043 -0
  132. metadata +85 -20
  133. data/.hound.yml +0 -14
  134. data/app/interactors/notify_count.rb +0 -15
  135. data/app/models/.keep +0 -0
  136. data/config/initializers/redis.rb +0 -17
  137. data/config/unicorn.rb +0 -16
  138. data/lib/assets/.keep +0 -0
  139. data/public/images/.keep +0 -0
  140. data/vendor/assets/javascripts/.keep +0 -0
  141. data/vendor/assets/stylesheets/.keep +0 -0
@@ -0,0 +1,31 @@
1
+ import React, { Component } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import PropTypes from 'prop-types'
4
+
5
+ export default class Header extends Component {
6
+ render() {
7
+ return (
8
+ <div className="panel panel-default">
9
+ <span className="pull-right label label-info" title={this.props.created_at}>{this.props.time_ago_in_words}</span>
10
+ <div className="panel-heading" role="tab" id="headingOne">
11
+ <h4 className="panel-title">
12
+ <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
13
+ Headers
14
+ </a>
15
+ </h4>
16
+ </div>
17
+ <div id="collapseOne" className="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingOne">
18
+ <div className="panel-body">
19
+ <pre>{this.props.headers_as_string}</pre>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ )
24
+ }
25
+ }
26
+
27
+ Header.propTypes = {
28
+ created_at: PropTypes.string.isRequired,
29
+ time_ago_in_words: PropTypes.string.isRequired,
30
+ headers_as_string: PropTypes.string.isRequired
31
+ }
@@ -0,0 +1,28 @@
1
+ import React, { Component } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import PropTypes from 'prop-types'
4
+
5
+ export default class Response extends Component {
6
+ render() {
7
+ return (
8
+ <div className="panel panel-default">
9
+ <div className="panel-heading" role="tab" id="headingThree">
10
+ <h4 className="panel-title">
11
+ <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseThree" aria-expanded="true" aria-controls="collapseThree">
12
+ Response
13
+ </a>
14
+ </h4>
15
+ </div>
16
+ <div id="collapseThree" className="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingThree">
17
+ <div className="panel-body">
18
+ <pre>{this.props.response_body_as_string}</pre>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ )
23
+ }
24
+ }
25
+
26
+ Response.propTypes = {
27
+ response_body_as_string: PropTypes.string.isRequired
28
+ }
@@ -0,0 +1,47 @@
1
+ import React, { Component } from 'react'
2
+ import { connect } from 'react-redux'
3
+ import Header from './Header'
4
+ import Response from './Response'
5
+ import PropTypes from 'prop-types'
6
+
7
+ export default class Request extends Component {
8
+ render() {
9
+ if(!this.props.id) {
10
+ return (
11
+ <p>No requests found</p>
12
+ )
13
+ }
14
+
15
+ return (
16
+ <div className="panel-group request-show" id="accordion" role="tablist" aria-multiselectable="true">
17
+ <Header headers_as_string={this.props.headers_as_string} time_ago_in_words={this.props.time_ago_in_words} created_at={this.props.created_at} />
18
+ <div className="panel panel-default">
19
+ <div className="panel-heading" role="tab" id="headingTwo">
20
+ <h4 className="panel-title">
21
+ <a role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseTwo" aria-expanded="true" aria-controls="collapseTwo">
22
+ {this.props.request_method} {this.props.path}
23
+ </a>
24
+ </h4>
25
+ </div>
26
+ <div id="collapseTwo" className="panel-collapse collapse in" role="tabpanel" aria-labelledby="headingTwo">
27
+ <div className="panel-body">
28
+ <pre>{this.props.request_body_as_string}</pre>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ <Response response_body_as_string={this.props.response_body_as_string} />
33
+ </div>
34
+ )
35
+ }
36
+ }
37
+
38
+ Response.propTypes = {
39
+ request_method: PropTypes.string,
40
+ path: PropTypes.string,
41
+ request_body_as_string: PropTypes.string,
42
+ response_body_as_string: PropTypes.string,
43
+ created_at: PropTypes.string,
44
+ id: PropTypes.string,
45
+ time_ago_in_words: PropTypes.string,
46
+ headers_as_string: PropTypes.string
47
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ import { Provider } from 'react-redux'
4
+ import store from '../store'
5
+ import Bucket from '../components/Bucket'
6
+ import RequestCount from '../components/RequestCount'
7
+ import startRequestPoller from '../request_poller'
8
+
9
+ document.addEventListener('DOMContentLoaded', () => {
10
+ ReactDOM.render(
11
+ <Provider store={store}>
12
+ <Bucket />
13
+ </Provider>,
14
+ document.getElementById('react-root')
15
+ )
16
+
17
+ ReactDOM.render(
18
+ <Provider store={store}>
19
+ <RequestCount />
20
+ </Provider>,
21
+ document.getElementById('request-count-react-root')
22
+ )
23
+ })
24
+
25
+ $(() => startRequestPoller(store))
@@ -0,0 +1,26 @@
1
+ // Run this example by adding <%= javascript_pack_tag 'hello_react' %> to the head of your layout file,
2
+ // like app/views/layouts/application.html.erb. All it does is render <div>Hello React</div> at the bottom
3
+ // of the page.
4
+
5
+ import React from 'react'
6
+ import ReactDOM from 'react-dom'
7
+ import PropTypes from 'prop-types'
8
+
9
+ const Hello = props => (
10
+ <div>Hello {props.name}!</div>
11
+ )
12
+
13
+ Hello.defaultProps = {
14
+ name: 'David'
15
+ }
16
+
17
+ Hello.propTypes = {
18
+ name: PropTypes.string
19
+ }
20
+
21
+ document.addEventListener('DOMContentLoaded', () => {
22
+ ReactDOM.render(
23
+ <Hello name="React" />,
24
+ document.body.appendChild(document.createElement('div')),
25
+ )
26
+ })
@@ -0,0 +1,23 @@
1
+ import { combineReducers } from 'redux'
2
+ import { bucketsActions } from '../actionTypes'
3
+
4
+ const bucket = (state = {}, action) => {
5
+ switch (action.type) {
6
+ case bucketsActions.populate:
7
+ return { ...action.bucket, loading: false, page: action.page }
8
+ case bucketsActions.loading:
9
+ return { ...state, loading: true }
10
+ case bucketsActions.updateRequestsCount:
11
+ return {
12
+ ...state,
13
+ requests_count: action.requests_count,
14
+ page: action.page
15
+ }
16
+ default:
17
+ return state
18
+ }
19
+ }
20
+
21
+ const rootReducer = combineReducers({ bucket })
22
+
23
+ export default rootReducer
@@ -0,0 +1,19 @@
1
+ import { updateRequestsCount } from './actions'
2
+
3
+ export default function startRequestPoller(store) {
4
+ const bucket = $('#putsreq-url-input').data('bucket-token')
5
+
6
+ const url = `/${bucket}/requests_count`
7
+ const poll = (previousRequestsCount = 0) => {
8
+ $.getJSON(url)
9
+ .done((data) => {
10
+ updateRequestsCount(data.requests_count)(store.dispatch, store.getState)
11
+ // use setTimeout instead of setInterval to ensure a new request will be made
12
+ // only when the previous one was completed
13
+ const timeout = previousRequestsCount == data.requests_count ? 5000 : 2500
14
+ setTimeout(() => { poll(data.requests_count) }, timeout)
15
+ })
16
+ }
17
+
18
+ poll()
19
+ }
@@ -0,0 +1,9 @@
1
+ import { createStore, applyMiddleware } from 'redux'
2
+ import thunk from 'redux-thunk'
3
+ import { composeWithDevTools } from 'redux-devtools-extension'
4
+ import rootReducers from './reducers'
5
+
6
+ export default createStore(
7
+ rootReducers,
8
+ composeWithDevTools(applyMiddleware(thunk))
9
+ )
@@ -4,8 +4,8 @@ class Bucket
4
4
 
5
5
  has_many :forks, class_name: 'Bucket'
6
6
 
7
- belongs_to :fork, class_name: 'Bucket'
8
- belongs_to :user
7
+ belongs_to :fork, class_name: 'Bucket', optional: true
8
+ belongs_to :user, optional: true
9
9
 
10
10
  field :token
11
11
  field :name
@@ -13,6 +13,9 @@ class Bucket
13
13
  field :response_builder, default: -> { default_response_builder }
14
14
  field :history_start_at, type: Time
15
15
 
16
+ # temporally hack
17
+ attr_accessor :request
18
+
16
19
  index token: 1
17
20
  index owner_token: 1
18
21
  index fork_id: 1
@@ -33,6 +36,7 @@ class Bucket
33
36
  # I couldn't make has_many + conditions work with Mongoid
34
37
  # responses must be filtered by created_at
35
38
  # see clear_history
39
+
36
40
  Response.where(bucket_id: id).gte(created_at: history_start_at || created_at).order(:created_at.desc)
37
41
  end
38
42
 
@@ -41,11 +45,11 @@ class Bucket
41
45
  # so we filter these objects by the history_start_at to "clear"
42
46
  # db.runCommand({ "convertToCapped": "requests", size: 25000000 });
43
47
  # db.runCommand({ "convertToCapped": "responses", size: 25000000 });
44
- update_attribute :history_start_at, Time.now
48
+ update_attribute :history_start_at, Time.zone.now
45
49
  end
46
50
 
47
51
  def name
48
- if (name = read_attribute(:name)).blank?
52
+ if (name = self[:name]).blank?
49
53
  token
50
54
  else
51
55
  name
@@ -61,21 +65,23 @@ class Bucket
61
65
  end
62
66
 
63
67
  def last_request_at
64
- last_request.try(:created_at)
68
+ last_request&.created_at
65
69
  end
66
70
 
67
- def first_request_at
68
- requests.order(:created_at.asc).first.try(:created_at)
71
+ def first_request
72
+ requests.order(:created_at.asc).first
69
73
  end
70
74
 
71
- def requests_count
72
- requests.count
75
+ def first_request_at
76
+ first_request&.created_at
73
77
  end
74
78
 
79
+ delegate :count, to: :requests, prefix: true
80
+
75
81
  private
76
82
 
77
83
  def generate_token
78
- self.token = loop do
84
+ self.token ||= loop do
79
85
  random_token = SecureRandom.urlsafe_base64(15).tr('_-', '0a')
80
86
  break random_token unless Bucket.where(token: random_token).exists?
81
87
  end
@@ -12,7 +12,6 @@ class Request
12
12
  field :request_method
13
13
  field :ip
14
14
  field :url
15
- field :params, type: Hash
16
15
 
17
16
  index bucket_id: 1, created_at: -1
18
17
 
@@ -25,12 +24,12 @@ class Request
25
24
 
26
25
  u = URI(url)
27
26
 
28
- url.gsub(/.*#{Regexp.escape(u.host)}(\:#{Regexp.escape(u.port.to_s)})?/, '')
27
+ url.gsub(/.*#{::Regexp.escape(u.host)}(\:#{::Regexp.escape(u.port.to_s)})?/, '')
29
28
  end
30
29
 
31
30
  private
32
31
 
33
32
  def bump_requests_recorded
34
- REDIS.incr 'requests_recorded'
33
+ Rails.cache.increment 'requests_recorded'
35
34
  end
36
35
  end
@@ -6,8 +6,8 @@ class User
6
6
  :recoverable, :rememberable, :trackable, :validatable
7
7
 
8
8
  ## Database authenticatable
9
- field :email, type: String, default: ""
10
- field :encrypted_password, type: String, default: ""
9
+ field :email, type: String, default: ''
10
+ field :encrypted_password, type: String, default: ''
11
11
 
12
12
  ## Recoverable
13
13
  field :reset_password_token, type: String
@@ -0,0 +1,11 @@
1
+ class BucketSerializer < ActiveModel::Serializer
2
+ attributes :requests_count, :path
3
+
4
+ belongs_to :request
5
+ belongs_to :first_request
6
+ belongs_to :last_request
7
+
8
+ def path
9
+ Rails.application.routes.url_helpers.bucket_path(token: object.token)
10
+ end
11
+ end
@@ -0,0 +1,69 @@
1
+ class RequestSerializer < ActiveModel::Serializer
2
+ attributes(
3
+ :id,
4
+ :headers,
5
+ :time_ago_in_words,
6
+ :created_at,
7
+ :request_method,
8
+ :request_body_as_string,
9
+ :response_body_as_string,
10
+ :headers_as_string,
11
+ :path
12
+ )
13
+
14
+ def id
15
+ object.id.to_s
16
+ end
17
+
18
+ def time_ago_in_words
19
+ "#{ApplicationController.helpers.time_ago_in_words(object.created_at)} ago"
20
+ end
21
+
22
+ def headers_as_string
23
+ JSON.pretty_generate(object.headers.to_h)
24
+ end
25
+
26
+ def request_body_as_string
27
+ body_as_string(object)
28
+ end
29
+
30
+ def response_body_as_string
31
+ body_as_string(object.response)
32
+ end
33
+
34
+ private
35
+
36
+ def body_as_string(req_or_res)
37
+ body = req_or_res.body
38
+
39
+ if body_json?(req_or_res) && body.is_a?(String)
40
+ # See https://github.com/phstc/putsreq/issues/31#issuecomment-271681249
41
+ return JSON.pretty_generate(JSON.parse(body))
42
+ end
43
+
44
+ if body.is_a?(Hash)
45
+ # For responses body can be a hash
46
+ # body.to_h because body can be a BSON::Document
47
+ # which for some reason does format well with
48
+ # pretty_generate
49
+ return JSON.pretty_generate(body.to_h)
50
+ end
51
+
52
+ if body.is_a?(Array)
53
+ # see https://github.com/phstc/putsreq/issues/33
54
+ return JSON.pretty_generate(body.to_a)
55
+ end
56
+
57
+ body.to_s
58
+ rescue
59
+ body.to_s
60
+ end
61
+
62
+ def body_json?(req_or_res)
63
+ req_or_res.headers.to_h.each do |key, value|
64
+ return !!(value =~ /application\/json/i) if key =~ /^content-type$/i
65
+ end
66
+
67
+ false
68
+ end
69
+ end
@@ -1,4 +1,4 @@
1
1
  <button class="btn btn-primary tm10">Update</button>
2
- <%= link_to 'Fork', bucket_fork_path(@bucket.token), class: 'btn btn-default tm10 lm5', method: :post %>
3
- <%= link_to 'Clear History', bucket_clear_path(@bucket.token), class: 'btn btn-default tm10 lm5', method: :delete, data: { confirm: 'Are you sure?' } %>
4
- <%= link_to 'Destroy', bucket_destroy_path(@bucket.token), class: 'btn btn-default tm10 lm5', method: :delete, data: { confirm: 'Are you sure?' } %>
2
+ <%= link_to 'Fork', bucket_fork_path(bucket.token), class: 'btn btn-default tm10 lm5', method: :post %>
3
+ <%= link_to 'Clear History', bucket_clear_path(bucket.token), class: 'btn btn-default tm10 lm5', method: :delete, data: { confirm: 'Are you sure?' } %>
4
+ <%= link_to 'Destroy', bucket_destroy_path(bucket.token), class: 'btn btn-default tm10 lm5', method: :delete, data: { confirm: 'Are you sure?' } %>
@@ -1,6 +1,6 @@
1
- <%= form_for @bucket, url: update_bucket_path(@bucket.token), method: :put do |f| %>
1
+ <%= form_for bucket, url: update_bucket_path(bucket.token), method: :put do |f| %>
2
2
  <%= f.hidden_field :response_builder %>
3
- <script id="response-builder-container" type='application/vns.putsreq-response_builder'><%== @bucket.response_builder %></script>
3
+ <script id="response-builder-container" type='application/vns.putsreq-response_builder'><%== bucket.response_builder %></script>
4
4
  <div class="row">
5
5
  <div class="col-md-6">
6
6
  <h4>Bucket name
@@ -8,8 +8,8 @@
8
8
  </h4>
9
9
  <div class="input-group putsreq-name bm30">
10
10
  <%= f.text_field :name %>
11
- <% if @bucket.fork %>
12
- <p><small>forked from <%= link_to @bucket.fork.name, bucket_path(@bucket.fork.token) %></small></p>
11
+ <% if bucket.fork %>
12
+ <p><small>forked from <%= link_to bucket.fork.name, bucket_path(bucket.fork.token) %></small></p>
13
13
  <% end %>
14
14
  </div>
15
15
  </div>
@@ -17,14 +17,14 @@
17
17
  <h4>Requests
18
18
  <small><em>The number of requests made to this bucket.</em></small>
19
19
  </h4>
20
- <h3 id="bucket-request-count"><%= @bucket.requests_count %></h3>
20
+ <div id="request-count-react-root"></div>
21
21
  </div>
22
22
  </div>
23
23
  <h4>Response Builder&nbsp;<a href="https://github.com/phstc/putsreq#response-builder" target="_blank"><span class="glyphicon glyphicon-info-sign"></span></a></h4>
24
24
  <div id="editor" name="editor" style="width: 100%; height: 150px"></div>
25
25
  <textarea id="response_builder" name="response_builder" style="display:none;"></textarea>
26
26
  <p>
27
- <% if owner?(@bucket) %>
27
+ <% if owner?(bucket) %>
28
28
  <%= render 'buttons' %>
29
29
  <% else %>
30
30
  <%= render 'readonly_buttons' %>