diff --git a/app/assets/javascripts/app/controllers/_integration/_base.coffee b/app/assets/javascripts/app/controllers/_integration/_base.coffee
index 76fa0d629..dfe224a8d 100644
--- a/app/assets/javascripts/app/controllers/_integration/_base.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/_base.coffee
@@ -16,6 +16,8 @@ class App.ControllerIntegrationBase extends App.Controller
return if !@authenticate(false, 'Admin')
@title @featureName, true
+ @initalRender = true
+
@subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
switch: =>
@@ -23,17 +25,14 @@ class App.ControllerIntegrationBase extends App.Controller
App.Setting.set(@featureIntegration, value)
render: =>
- localEl = $( App.view('integration/base')(
- header: @featureName
- description: @description
- feature: @featureIntegration
- featureEnabled: App.Setting.get(@featureIntegration)
- ))
- @form localEl
- @html localEl
-
- form: (localEl) ->
- console.log('implement own form method')
+ if @initalRender
+ @html App.view('integration/base')(
+ header: @featureName
+ description: @description
+ feature: @featureIntegration
+ featureEnabled: App.Setting.get(@featureIntegration)
+ )
+ @initalRender = false
submit: (e) =>
e.preventDefault()
diff --git a/app/assets/javascripts/app/controllers/_integration/icinga.coffee b/app/assets/javascripts/app/controllers/_integration/icinga.coffee
index 3c486a06b..9784a5698 100644
--- a/app/assets/javascripts/app/controllers/_integration/icinga.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/icinga.coffee
@@ -7,10 +7,11 @@ class Index extends App.ControllerIntegrationBase
['If the host and service is recovered again, the ticket will be closed automatically.']
]
- form: (localeEl) ->
+ render: =>
+ super
new App.SettingsForm(
area: 'Integration::Icinga'
- el: localeEl.find('.js-form')
+ el: @$('.js-form')
)
class State
diff --git a/app/assets/javascripts/app/controllers/_integration/nagios.coffee b/app/assets/javascripts/app/controllers/_integration/nagios.coffee
index ab8bf6394..429c3913e 100644
--- a/app/assets/javascripts/app/controllers/_integration/nagios.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/nagios.coffee
@@ -7,10 +7,11 @@ class Index extends App.ControllerIntegrationBase
['If the host and service is recovered again, the ticket will be closed automatically.']
]
- form: (localeEl) ->
+ render: =>
+ super
new App.SettingsForm(
area: 'Integration::Nagios'
- el: localeEl.find('.js-form')
+ el: @$('.js-form')
)
class State
diff --git a/app/assets/javascripts/app/controllers/_integration/slack.coffee b/app/assets/javascripts/app/controllers/_integration/slack.coffee
index de3169477..906c92d69 100644
--- a/app/assets/javascripts/app/controllers/_integration/slack.coffee
+++ b/app/assets/javascripts/app/controllers/_integration/slack.coffee
@@ -7,7 +7,8 @@ class Index extends App.ControllerIntegrationBase
['To setup this Service you need to create a new |"Incoming webhook"| in your %s integration panel, and enter the Webhook URL below.', 'Slack']
]
- form: (localEl) =>
+ render: =>
+ super
params = App.Setting.get(@featureConfig)
if params && params.items
@@ -24,10 +25,10 @@ class Index extends App.ControllerIntegrationBase
{ name: 'types', display: 'Trigger', tag: 'checkbox', options: options, 'null': false, class: 'vertical', note: 'Where notification is sent.' },
{ name: 'group_id', display: 'Group', tag: 'select', relation: 'Group', multiple: true, 'null': false, note: 'Only for this groups.' },
{ name: 'webhook', display: 'Webhook', tag: 'input', type: 'text', limit: 200, 'null': false, placeholder: 'https://hooks.slack.com/services/...' },
- { name: 'username', display: 'username', tag: 'input', type: 'text', limit: 100, 'null': false, placeholder: 'username' },
- { name: 'channel', display: 'channel', tag: 'input', type: 'text', limit: 100, 'null': true, placeholder: '#channel' },
+ { name: 'username', display: 'Username', tag: 'input', type: 'text', limit: 100, 'null': false, placeholder: 'username' },
+ { name: 'channel', display: 'Channel', tag: 'input', type: 'text', limit: 100, 'null': true, placeholder: '#channel' },
]
- console.log('p', params)
+
settings = []
for item in configureAttributes
setting =
@@ -54,7 +55,12 @@ class Index extends App.ControllerIntegrationBase
params: localParams
)
- localEl.find('.js-form').html(formEl)
+ @$('.js-form').html(formEl)
+
+ new App.HttpLog(
+ el: @$('.js-log')
+ facility: 'slack_webhook'
+ )
class State
@current: ->
diff --git a/app/assets/javascripts/app/controllers/widget/http_log.coffee b/app/assets/javascripts/app/controllers/widget/http_log.coffee
new file mode 100644
index 000000000..01951edad
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/widget/http_log.coffee
@@ -0,0 +1,55 @@
+class App.HttpLog extends App.Controller
+ events:
+ 'click .js-record': 'show'
+
+ constructor: ->
+ super
+ @fetch()
+ @records = []
+
+ fetch: =>
+ @ajax(
+ id: 'http_logs'
+ type: 'GET'
+ url: "#{@apiPath}/http_logs/#{@facility}"
+ data:
+ limit: @limit || 50
+ processData: true
+ success: (data) =>
+ @records = data
+ @render()
+ )
+
+ render: =>
+ @html App.view('widget/http_log')(
+ records: @records
+ )
+ #@delay(message, 2000)
+
+ show: (e) =>
+ e.preventDefault()
+ record_id = $(e.currentTarget).data('id')
+ for record in @records
+ if record_id.toString() is record.id.toString()
+ new Show(
+ record: record
+ container: @el.closest('.content')
+ )
+ return
+
+class Show extends App.ControllerModal
+ authenticateRequired: true
+ large: true
+ head: 'HTTP Log'
+ buttonClose: true
+ buttonCancel: false
+ buttonSubmit: false
+
+ constructor: ->
+ super
+
+ content: ->
+ console.log('cont')
+ App.view('widget/http_log_show')(
+ record: @record
+ )
diff --git a/app/assets/javascripts/app/views/integration/base.jst.eco b/app/assets/javascripts/app/views/integration/base.jst.eco
index d941bd965..272620104 100644
--- a/app/assets/javascripts/app/views/integration/base.jst.eco
+++ b/app/assets/javascripts/app/views/integration/base.jst.eco
@@ -14,4 +14,5 @@
<% end %>
<% end %>
+
\ No newline at end of file
diff --git a/app/assets/javascripts/app/views/widget/http_log.jst.eco b/app/assets/javascripts/app/views/widget/http_log.jst.eco
new file mode 100644
index 000000000..4f3790b12
--- /dev/null
+++ b/app/assets/javascripts/app/views/widget/http_log.jst.eco
@@ -0,0 +1,21 @@
+
+
+<%- @T('Recent logs') %>
+
diff --git a/app/assets/javascripts/app/views/widget/http_log_show.jst.eco b/app/assets/javascripts/app/views/widget/http_log_show.jst.eco
new file mode 100644
index 000000000..d09625666
--- /dev/null
+++ b/app/assets/javascripts/app/views/widget/http_log_show.jst.eco
@@ -0,0 +1,27 @@
+
+
+
+
+ <%- @T('Direction') %>
+ | <%- @T(@record.direction) %>
+ |
+ <%- @T('URL') %>
+ | <%= @record.url %>
+ |
+ <%- @T('Method') %>
+ | <%= @record.method %>
+ |
+ <%- @T('Status') %>
+ | <%= @record.status %>
+ |
+ <%- @T('Request') %>
+ | <%- App.Utils.text2html(@record.request.content) %>
+ |
+ <%- @T('Response') %>
+ | <%- App.Utils.text2html(@record.response.content) %>
+ |
+ <%- @T('Created at') %>
+ | <%- @datetime(@record.created_at) %>
+ |
+
+
diff --git a/app/assets/stylesheets/zammad.scss b/app/assets/stylesheets/zammad.scss
index 4c7c0eb01..ac4904416 100644
--- a/app/assets/stylesheets/zammad.scss
+++ b/app/assets/stylesheets/zammad.scss
@@ -6929,7 +6929,9 @@ output {
background: white;
table-layout: auto;
margin-bottom: 20px;
-
+ word-break: break-all;
+ word-wrap: break-word;
+
&.is-invalid {
border-radius: 3px;
box-shadow:
@@ -6975,6 +6977,7 @@ output {
letter-spacing: 0.05em;
background: hsl(197,20%,93%);
border-bottom: none;
+ word-break: normal;
}
td.empty-cell {
diff --git a/app/controllers/http_logs_controller.rb b/app/controllers/http_logs_controller.rb
new file mode 100644
index 000000000..87ce0aece
--- /dev/null
+++ b/app/controllers/http_logs_controller.rb
@@ -0,0 +1,23 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class HttpLogsController < ApplicationController
+ before_action :authentication_check
+
+ # GET /http_logs/:facility
+ def index
+ return if deny_if_not_role(Z_ROLENAME_ADMIN)
+ list = if params[:facility]
+ HttpLog.where(facility: params[:facility]).order('created_at DESC').limit(params[:limit] || 50)
+ else
+ HttpLog.order('created_at DESC').limit(params[:limit] || 50)
+ end
+ model_index_render_result(list)
+ end
+
+ # POST /http_logs
+ def create
+ return if deny_if_not_role(Z_ROLENAME_ADMIN)
+ model_create_render(HttpLog, params)
+ end
+
+end
diff --git a/app/models/http_log.rb b/app/models/http_log.rb
new file mode 100644
index 000000000..364b9f9fa
--- /dev/null
+++ b/app/models/http_log.rb
@@ -0,0 +1,24 @@
+# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
+
+class HttpLog < ApplicationModel
+ store :request
+ store :response
+
+=begin
+
+cleanup old http logs
+
+ HttpLog.cleanup
+
+optional you can parse the max oldest chat entries
+
+ HttpLog.cleanup(1.month)
+
+=end
+
+ def self.cleanup(diff = 1.month)
+ HttpLog.where('created_at < ?', Time.zone.now - diff).delete_all
+ true
+ end
+
+end
diff --git a/app/models/transaction/slack.rb b/app/models/transaction/slack.rb
index 2499f7a18..e5ae6e880 100644
--- a/app/models/transaction/slack.rb
+++ b/app/models/transaction/slack.rb
@@ -2,6 +2,14 @@
class Transaction::Slack
=begin
+
+backend = Transaction::Slack.new(
+ object: 'Ticket',
+ type: 'create',
+ ticket_id: 1,
+)
+backend.perform
+
{
object: 'Ticket',
type: 'update',
@@ -83,24 +91,30 @@ class Transaction::Slack
# check action
if item['types']
- hit = false
- item['types'].each {|type|
- next if type.to_s != @item[:type].to_s
- hit = true
- break
- }
- next if !hit
+ if item['types'].class == Array
+ hit = false
+ item['types'].each {|type|
+ next if type.to_s != @item[:type].to_s
+ hit = true
+ break
+ }
+ next if !hit
+ end
+ next if item['types'].to_s != @item[:type].to_s
end
# check group
if item['group_ids']
- hit = false
- item['group_ids'].each {|group_id|
- next if group_id.to_s != ticket.group_id.to_s
- hit = true
- break
- }
- next if !hit
+ if item['group_ids'].class == Array
+ hit = false
+ item['group_ids'].each {|group_id|
+ next if group_id.to_s != ticket.group_id.to_s
+ hit = true
+ break
+ }
+ next if !hit
+ end
+ next if item['group_ids'].to_s != ticket.group_id.to_s
end
Rails.logger.debug "sent webhook (#{@item[:type]}/#{ticket.id}/#{item['webhook']})"
@@ -108,8 +122,9 @@ class Transaction::Slack
item['webhook'],
channel: item['channel'],
username: item['username'],
- icon_url: logo_url,
+ #icon_url: logo_url,
mrkdwn: true,
+ http_client: Transaction::Slack::Client,
)
if item['expand']
body = "#{result[:subject]}\n#{result[:body]}"
@@ -123,11 +138,9 @@ class Transaction::Slack
result = notifier.ping result[:subject],
attachments: [attachment]
end
- if !result
- Rails.logger.error "Unable to post webhook: #{item['webhook']}"
- end
- if result.code.to_s != '200' && result.code.to_s != '201'
+ if !result.success?
Rails.logger.error "Unable to post webhook: #{item['webhook']}: #{result.inspect}"
+ next
end
Rails.logger.debug "sent webhook (#{@item[:type]}/#{ticket.id}/#{item['webhook']})"
}
@@ -224,4 +237,21 @@ class Transaction::Slack
changes
end
+ class Transaction::Slack::Client
+ def self.post(uri, params = {})
+ UserAgent.post(
+ uri.to_s,
+ params,
+ {
+ open_timeout: 4,
+ read_timeout: 10,
+ total_timeout: 20,
+ log: {
+ facility: 'slack_webhook',
+ }
+ },
+ )
+ end
+ end
+
end
diff --git a/app/views/slack/ticket_escalation/en.md.erb b/app/views/slack/ticket_escalation/en.md.erb
index 330998489..e4853da66 100644
--- a/app/views/slack/ticket_escalation/en.md.erb
+++ b/app/views/slack/ticket_escalation/en.md.erb
@@ -1,6 +1,6 @@
# <%= d 'ticket.title' %>
_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Escalated at <%= d 'ticket.escalation_time' %>_
-A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" is escalated since "<%= d 'ticket.escalation_time' %>"!
+A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" is escalated since "<%= d 'ticket.escalation_time' %>"!
<% if @objects[:article] %>
<%= a_text 'article' %>
diff --git a/app/views/slack/ticket_escalation_warning/en.md.erb b/app/views/slack/ticket_escalation_warning/en.md.erb
index 15f48c0a3..806c49fba 100644
--- a/app/views/slack/ticket_escalation_warning/en.md.erb
+++ b/app/views/slack/ticket_escalation_warning/en.md.erb
@@ -1,6 +1,6 @@
# <%= d 'ticket.title' %>
_<<%= c 'http_type' %>://<%= c 'fqdn' %>/#ticket/zoom/<%= d 'ticket.id' %>|Ticket#<%= d 'ticket.number' %>>: Will escalate at <%= d 'ticket.escalation_time' %>_
-A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_time' %>"!
+A ticket (<%= d 'ticket.title' %>) from "<%= d 'ticket.customer.longname' %>" will escalate at "<%= d 'ticket.escalation_time' %>"!
<% if @objects[:article] %>
<%= a_text 'article' %>
diff --git a/config/routes/http_log.rb b/config/routes/http_log.rb
new file mode 100644
index 000000000..2dad26384
--- /dev/null
+++ b/config/routes/http_log.rb
@@ -0,0 +1,8 @@
+Zammad::Application.routes.draw do
+ api_path = Rails.configuration.api_path
+
+ match api_path + '/http_logs', to: 'http_logs#index', via: :get
+ match api_path + '/http_logs/:facility', to: 'http_logs#index', via: :get
+ match api_path + '/http_logs', to: 'http_logs#create', via: :post
+
+end
diff --git a/db/migrate/20160417000002_add_http_log.rb b/db/migrate/20160417000002_add_http_log.rb
new file mode 100644
index 000000000..8480981ef
--- /dev/null
+++ b/db/migrate/20160417000002_add_http_log.rb
@@ -0,0 +1,32 @@
+class AddHttpLog < ActiveRecord::Migration
+ def up
+
+ create_table :http_logs do |t|
+ t.column :direction, :string, limit: 20, null: false
+ t.column :facility, :string, limit: 100, null: false
+ t.column :method, :string, limit: 100, null: false
+ t.column :url, :string, limit: 255, null: false
+ t.column :status, :string, limit: 20, null: true
+ t.column :ip, :string, limit: 50, null: true
+ t.column :request, :string, limit: 10_000, null: false
+ t.column :response, :string, limit: 10_000, null: false
+ t.column :updated_by_id, :integer, null: true
+ t.column :created_by_id, :integer, null: true
+ t.timestamps null: false
+ end
+ add_index :http_logs, [:facility]
+ add_index :http_logs, [:created_by_id]
+ add_index :http_logs, [:created_at]
+
+ Scheduler.create_if_not_exists(
+ name: 'Cleanup HttpLog',
+ method: 'HttpLog.cleanup',
+ period: 24 * 60 * 60,
+ prio: 2,
+ active: true,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ end
+end
diff --git a/lib/user_agent.rb b/lib/user_agent.rb
index edf7ba483..8fb48b3bd 100644
--- a/lib/user_agent.rb
+++ b/lib/user_agent.rb
@@ -62,9 +62,10 @@ returns
total_timeout = options[:total_timeout] || 60
Timeout.timeout(total_timeout) do
response = http.request(request)
- return process(response, uri, count, params, options)
+ return process(request, response, uri, count, params, options)
end
rescue => e
+ log(url, request, nil, options)
return Result.new(
error: e.inspect,
success: false,
@@ -114,9 +115,10 @@ returns
total_timeout = options[:total_timeout] || 60
Timeout.timeout(total_timeout) do
response = http.request(request)
- return process(response, uri, count, params, options)
+ return process(request, response, uri, count, params, options)
end
rescue => e
+ log(url, request, nil, options)
return Result.new(
error: e.inspect,
success: false,
@@ -165,9 +167,10 @@ returns
total_timeout = options[:total_timeout] || 60
Timeout.timeout(total_timeout) do
response = http.request(request)
- return process(response, uri, count, params, options)
+ return process(request, response, uri, count, params, options)
end
rescue => e
+ log(url, request, nil, options)
return Result.new(
error: e.inspect,
success: false,
@@ -209,9 +212,10 @@ returns
total_timeout = options[:total_timeout] || 60
Timeout.timeout(total_timeout) do
response = http.request(request)
- return process(response, uri, count, {}, options)
+ return process(request, response, uri, count, {}, options)
end
rescue => e
+ log(url, request, nil, options)
return Result.new(
error: e.inspect,
success: false,
@@ -293,7 +297,64 @@ returns
request
end
- def self.process(response, uri, count, params, options)
+ def self.log(url, request, response, options)
+ return if !options[:log]
+
+ # request
+ request_data = {
+ content: '',
+ content_type: request['Content-Type'],
+ content_encoding: request['Content-Encoding'],
+ source: request['User-Agent'] || request['Server'],
+ }
+ request.each_header {|key, value|
+ request_data[:content] += "#{key}: #{value}\n"
+ }
+ body = request.body
+ if body
+ request_data[:content] += "\n" + body
+ end
+ request_data[:content] = request_data[:content].slice(0, 8000)
+
+ # response
+ response_data = {
+ code: 0,
+ content: '',
+ content_type: nil,
+ content_encoding: nil,
+ source: nil,
+ }
+ if response
+ response_data[:code] = response.code
+ response_data[:content_type] = response['Content-Type']
+ response_data[:content_encoding] = response['Content-Encoding']
+ response_data[:source] = response['User-Agent'] || response['Server']
+ response.each_header {|key, value|
+ response_data[:content] += "#{key}: #{value}\n"
+ }
+ body = response.body
+ if body
+ response_data[:content] += "\n" + body
+ end
+ response_data[:content] = response_data[:content].slice(0, 8000)
+ end
+
+ record = {
+ direction: 'out',
+ facility: options[:log][:facility],
+ url: url,
+ status: response_data[:code],
+ ip: nil,
+ request: request_data,
+ response: response_data,
+ method: request.method,
+ }
+ HttpLog.create(record)
+ end
+
+ def self.process(request, response, uri, count, params, options) # rubocop:disable Metrics/ParameterLists
+ log(uri.to_s, request, response, options)
+
if !response
return Result.new(
error: "Can't connect to #{uri}, got no response!",
diff --git a/test/integration/slack_test.rb b/test/integration/slack_test.rb
index 33320dfdb..ac000bd96 100644
--- a/test/integration/slack_test.rb
+++ b/test/integration/slack_test.rb
@@ -124,6 +124,99 @@ class SlackTest < ActiveSupport::TestCase
# check if message exists
assert(slack_check(channel, hash))
+ items = [
+ {
+ group_ids: slack_group.id.to_s,
+ types: 'create',
+ webhook: webhook,
+ channel: channel,
+ username: 'zammad bot',
+ expand: false,
+ }
+ ]
+ Setting.set('slack_config', { items: items })
+
+ # case 3
+ customer = User.find(2)
+ hash = hash_gen
+ text = "#{rand_word}... #{hash}"
+
+ default_group = Group.first
+ ticket3 = Ticket.create(
+ title: text,
+ customer_id: customer.id,
+ group_id: default_group.id,
+ state: Ticket::State.find_by(name: 'new'),
+ priority: Ticket::Priority.find_by(name: '2 normal'),
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ article3 = Ticket::Article.create(
+ ticket_id: ticket3.id,
+ body: text,
+ type: Ticket::Article::Type.find_by(name: 'note'),
+ sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+ internal: false,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert_not(slack_check(channel, hash))
+
+ ticket3.state = Ticket::State.find_by(name: 'open')
+ ticket3.save
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert_not(slack_check(channel, hash))
+
+ # case 4
+ hash = hash_gen
+ text = "#{rand_word}... #{hash}"
+
+ ticket4 = Ticket.create(
+ title: text,
+ customer_id: customer.id,
+ group_id: slack_group.id,
+ state: Ticket::State.find_by(name: 'new'),
+ priority: Ticket::Priority.find_by(name: '2 normal'),
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+ article4 = Ticket::Article.create(
+ ticket_id: ticket4.id,
+ body: text,
+ type: Ticket::Article::Type.find_by(name: 'note'),
+ sender: Ticket::Article::Sender.find_by(name: 'Customer'),
+ internal: false,
+ updated_by_id: 1,
+ created_by_id: 1,
+ )
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert(slack_check(channel, hash))
+
+ hash = hash_gen
+ text = "#{rand_word}... #{hash}"
+
+ ticket4.title = text
+ ticket4.save
+
+ Observer::Transaction.commit
+ Delayed::Worker.new.work_off
+
+ # check if message exists
+ assert_not(slack_check(channel, hash))
+
end
def hash_gen