diff --git a/app/assets/images/icons/webhooks.svg b/app/assets/images/icons/webhooks.svg new file mode 100644 index 00000000..1356bdb5 --- /dev/null +++ b/app/assets/images/icons/webhooks.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/controllers/admin/webhooks_controller.rb b/app/controllers/admin/webhooks_controller.rb new file mode 100644 index 00000000..cb3a8db7 --- /dev/null +++ b/app/controllers/admin/webhooks_controller.rb @@ -0,0 +1,44 @@ +class Admin::WebhooksController < Admin::BaseController + + before_action { params[:id] && @webhook = Webhook.find(params[:id]) } + + def index + @webhooks = Webhook.ordered.page(params[:page]) + end + + def new + @webhook = Webhook.new + end + + def create + @webhook = Webhook.new(safe_params) + if @webhook.save + redirect_to admin_webhooks_path, :notice => "#{@webhook.name} has been added successfully." + else + render 'new' + end + end + + def edit + end + + def update + if @webhook.update_attributes(safe_params) + redirect_to admin_webhooks_path, :notice => "#{@webhook.name} has been updated successfully." + else + render 'edit' + end + end + + def destroy + @webhook.destroy + redirect_to admin_webhooks_path, :notice => "#{@webhook.name} has been removed successfully." + end + + private + + def safe_params + params.require(:webhook).permit(:name, :url) + end + +end diff --git a/app/models/issue.rb b/app/models/issue.rb index 4d50d3ac..f30cddce 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -39,7 +39,7 @@ class Issue < ActiveRecord::Base has_one :latest_update, -> { order(:id => :desc) }, :class_name => 'IssueUpdate' after_create :add_initial_update - after_save :update_service_statuses + before_save :update_service_statuses after_create :create_history_item after_destroy :destroy_history_item after_commit :send_notifications_on_create, :on => :create @@ -86,9 +86,14 @@ def send_notifications end end + def call_webhook + Staytus::Webhookcaller.call(:new_issue, :object => self, :update => self.updates.last) + end + def send_notifications_on_create if self.notify? self.delay.send_notifications + self.delay.call_webhook end end diff --git a/app/models/issue_update.rb b/app/models/issue_update.rb index 492e33c8..8ad56d7a 100644 --- a/app/models/issue_update.rb +++ b/app/models/issue_update.rb @@ -57,9 +57,14 @@ def send_notifications end end + def call_webhook + Staytus::Webhookcaller.call(:issue_update, :object => self.issue, :update => self) + end + def send_notifications_on_create if self.notify? delay.send_notifications + delay.call_webhook end end diff --git a/app/models/maintenance.rb b/app/models/maintenance.rb index fb579005..a1e6e9ac 100644 --- a/app/models/maintenance.rb +++ b/app/models/maintenance.rb @@ -117,9 +117,14 @@ def send_notifications end end + def call_webhook + Staytus::Webhookcaller.call(:new_maintenance, :object => self, :update => self.updates.last) + end + def send_notifications_on_create if self.notify? self.delay.send_notifications + self.delay.call_webhook end end diff --git a/app/models/maintenance_update.rb b/app/models/maintenance_update.rb index cc7730be..db55028a 100644 --- a/app/models/maintenance_update.rb +++ b/app/models/maintenance_update.rb @@ -38,9 +38,14 @@ def send_notifications end end + def call_webhook + Staytus::Webhookcaller.call(:maintenance_update, :object => self.maintenance, :update => self) + end + def send_notifications_on_create if self.notify? self.delay.send_notifications + self.delay.call_webhook end end diff --git a/app/models/webhook.rb b/app/models/webhook.rb new file mode 100644 index 00000000..106a033f --- /dev/null +++ b/app/models/webhook.rb @@ -0,0 +1,18 @@ +# == Schema Information +# +# Table name: webhooks +# +# id :integer not null, primary key +# name :string(255) +# url :string(255) + + +class Webhook < ActiveRecord::Base + + validates :name, :presence => true + validates :url, :presence => true + + + scope :ordered, -> { order(:name => :asc) } + +end diff --git a/app/views/admin/settings/index.html.haml b/app/views/admin/settings/index.html.haml index 50cd023e..7742be4f 100644 --- a/app/views/admin/settings/index.html.haml +++ b/app/views/admin/settings/index.html.haml @@ -32,7 +32,7 @@ = image_tag 'icons/services.svg' %p= link_to "Service Groups", admin_service_groups_path %p.navGrid__text Configure the different groups which can be assigned to the services you're reporting on. -.navGrid +.navGrid.u-margin-2x .navGrid__item = image_tag 'icons/api_tokens.svg' %p= link_to "API Tokens", admin_api_tokens_path @@ -40,5 +40,10 @@ .navGrid__item = image_tag 'icons/email_templates.svg' %p= link_to "E-Mail Templates", admin_email_templates_path - %p.navGrid__text Customize the content of the emails which are sent to people who subscribe to your site by e-mail. - + %p.navGrid__text Customize the content of the emails which are sent to people who subscribe to your site by e-mail +.navGrid.u-margin-2x + .navGrid__item + = image_tag 'icons/webhooks.svg' + %p= link_to "WebHooks", admin_webhooks_path + %p.navGrid__text Manage WebHooks which are triggered on maintenance and issue updates + .navGrid__item diff --git a/app/views/admin/webhooks/_form.html.haml b/app/views/admin/webhooks/_form.html.haml new file mode 100644 index 00000000..3f3e874d --- /dev/null +++ b/app/views/admin/webhooks/_form.html.haml @@ -0,0 +1,19 @@ += form_for [:admin, @webhook] do |f| + = f.error_messages + .fieldSet.u-margin-2x + .row + .col.col--10 + %dl.fieldSet__field.u-margin + %dt.fieldSet__label= f.label :name + %dd.fieldSet__input= f.text_field :name, :class => 'textInput', :autofocus => true, :required => true + .col.col--10 + %dl.fieldSet__field.u-margin + %dt.fieldSet__label= f.label :url, "Webhook URL" + %dd.fieldSet__input= f.text_field :url, :class => 'textInput', :required => true + + + .formButtons + - unless @webhook.new_record? + .formButtons__secondary= link_to "Delete Webhook", [:admin, @webhook], :class => 'button button--grey button--solid', :method => :delete, :data => {:confirm => "Are you sure you wish to delete #{@webhook.name}?"}, :tabindex => -1 + = f.submit "Save Webhook", :class => 'button button--solid' + = link_to "Cancel", admin_webhooks_path, :class => 'button button--lightGrey' diff --git a/app/views/admin/webhooks/edit.html.haml b/app/views/admin/webhooks/edit.html.haml new file mode 100644 index 00000000..ef1afc0c --- /dev/null +++ b/app/views/admin/webhooks/edit.html.haml @@ -0,0 +1,4 @@ +- @active_nav_item = :settings += content_for :page_header do + .pageHeader__heading= @page_title = 'Edit Webhook' += render 'form' diff --git a/app/views/admin/webhooks/index.html.haml b/app/views/admin/webhooks/index.html.haml new file mode 100644 index 00000000..dfb73af9 --- /dev/null +++ b/app/views/admin/webhooks/index.html.haml @@ -0,0 +1,25 @@ +- @active_nav_item = :settings +- content_for :page_header do + .pageHeader__button= link_to "Add New Webhook", new_admin_webhook_path, :class => 'button button--green button--small button--solid' + %h1.pageHeader__heading.u-margin= @page_title = 'Webhooks' + %p.u-intro + Call Webhooks when a new Issue is created or an existing Issue is updated + +- if @webhooks.empty? + .noDataArea.noDataArea--large + %p.u-margin No webhooks have been configured yet. +- else + %table.dataTable.u-margin + %thead + %tr + %td{:width => '35%'} Name + %td{:width => '45%'} URL + %td{:width => '20%'} + %tbody + - for webhook in @webhooks + %tr + %td= link_to webhook.name, [:edit, :admin, webhook], :class => 'u-underline ' + %td= webhook.url + %td.u-align-right= link_to "Edit", [:edit, :admin, webhook], :class => 'button button--small' + + = paginate @webhooks diff --git a/app/views/admin/webhooks/new.html.haml b/app/views/admin/webhooks/new.html.haml new file mode 100644 index 00000000..908ed50f --- /dev/null +++ b/app/views/admin/webhooks/new.html.haml @@ -0,0 +1,4 @@ +- @active_nav_item = :settings += content_for :page_header do + .pageHeader__heading= @page_title = 'Add new Webhook' += render 'form' diff --git a/config/routes.rb b/config/routes.rb index 857a1d94..7902b7d5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,7 @@ resources :service_groups resources :email_templates, :only => [:index, :edit, :update, :destroy] resources :api_tokens + resources :webhooks # # Issues diff --git a/db/migrate/20180821114121_create_webhooks.rb b/db/migrate/20180821114121_create_webhooks.rb new file mode 100644 index 00000000..6d0d32b3 --- /dev/null +++ b/db/migrate/20180821114121_create_webhooks.rb @@ -0,0 +1,12 @@ +class CreateWebhooks < ActiveRecord::Migration[5.1] + def self.up + create_table :webhooks do |table| + table.string :name, null: false # Name of the Webhook + table.string :url, null: false # URL of the Webhook + end + end + + def self.down + drop_table :webhooks + end +end diff --git a/db/schema.rb b/db/schema.rb index bd1ae1a5..0c464c4c 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180425131827) do +ActiveRecord::Schema.define(version: 20180821114121) do create_table "api_tokens", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| t.string "name" @@ -228,4 +228,9 @@ t.datetime "updated_at", null: false end + create_table "webhooks", force: :cascade, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci" do |t| + t.string "name", null: false + t.string "url", null: false + end + end diff --git a/doc/webhooks/rocketchat_integration.md b/doc/webhooks/rocketchat_integration.md new file mode 100644 index 00000000..3869f087 --- /dev/null +++ b/doc/webhooks/rocketchat_integration.md @@ -0,0 +1,44 @@ +# Rocket.Chat WebHook integration + +Add the following script for formatting messages for Rocket.Chat + +```javascript +class Script { + process_incoming_request({ request }) { + var staytus = request.content; + var message = {}; + message.attachement = []; + + var attachement = {} + attachement.title = staytus.data.title + + " (" + staytus.data.class + ": " + staytus.data.state + ")"; + attachement.title_link = staytus.data.url; + attachement.text = staytus.data.text; + attachement.text += "\n\nAffected Services:"; + message.attachement.push(attachement); + + + // add attachements for each affected service + if (staytus.data.services.length > 0) { + for (i = 0; i < staytus.data.services.length; i++) { + var status_attachement = {} + status_attachement.text = " - " + staytus.data.services[i].name + " : " + staytus.data.services[i].status.name + status_attachement.color = "#" + staytus.data.services[i].status.color + message.attachement.push(status_attachement); + } + } else { + var status_attachement = {} + status_attachement.text = " - none"; + message.attachement.push(status_attachement); + } + + return { + content:{ + text: "", + "attachments": message.attachement + } + }; + + } +} +``` diff --git a/lib/staytus/webhookcaller.rb b/lib/staytus/webhookcaller.rb new file mode 100644 index 00000000..5dfd2d3b --- /dev/null +++ b/lib/staytus/webhookcaller.rb @@ -0,0 +1,57 @@ +module Staytus + class Webhookcaller + + class << self + # + # Send a message to a webhook + # + def call(type, attributes = {}) + # Get details about caller and update object + object = attributes[:object] + update = attributes[:update] + message = { "type" => type, "data" => {} } + + # Add Maintenance/Issue related data to the message + if object.is_a? Issue or object.is_a? Maintenance + data = object.as_json + data["class"] = object.class.to_s + data["services"] = object.services.map do |service| + service.as_json.merge( "status" => service.status.as_json ) + end + data["text"] = update ? update.text : object.description + # Add a URL reference + data["url"] = "#{Site.first.domain_with_protocol}/#{object.class.to_s.downcase}/#{object.identifier}" + message["data"] = data + end + + if object.is_a? Maintenance + message["data"]["state"] = object.status.to_s + end + + Webhook.all.each do |webhook| + Rails.logger.info "Call Webhook #{webhook.name} with URL: #{webhook.url}" + + header = {"Content-Type" => "application/json"} + uri = URI(webhook.url) + + # Create the HTTP objects + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true if uri.scheme == "https" + + request = Net::HTTP::Post.new(uri.request_uri, header) + request.body = message.to_json + + Rails.logger.debug "WebHook message: #{message.to_json}" + + # Send the request + begin + http.request(request) + rescue => e + Rails.logger.error "Error calling webhook: #{e.message}" + end + end + end # call + + end + end +end