Repository: ryotarai/waker Branch: master Commit: 24b15020a036 Files: 270 Total size: 156.8 KB Directory structure: gitextract_ao1b4jon/ ├── .dockerignore ├── .github/ │ └── workflows/ │ └── rails.yml ├── .gitignore ├── .rspec ├── .rubocop.yml ├── Dockerfile ├── Gemfile ├── LICENSE.txt ├── Procfile ├── Procfile.docker ├── README.md ├── Rakefile ├── app/ │ ├── assets/ │ │ ├── images/ │ │ │ └── .keep │ │ ├── javascripts/ │ │ │ ├── application.js │ │ │ ├── comments.coffee │ │ │ ├── escalation_series.coffee │ │ │ ├── escalations.coffee │ │ │ ├── home.coffee │ │ │ ├── incident_events.coffee │ │ │ ├── incidents.coffee │ │ │ ├── maintenances.coffee │ │ │ ├── notifier_providers.coffee │ │ │ ├── notifiers.coffee │ │ │ ├── sessions.coffee │ │ │ ├── slack.coffee │ │ │ ├── topics.coffee │ │ │ └── users.coffee │ │ └── stylesheets/ │ │ ├── application.css │ │ ├── comments.scss │ │ ├── escalation_series.scss │ │ ├── escalations.scss │ │ ├── home.scss │ │ ├── incident_events.scss │ │ ├── incidents.scss │ │ ├── maintenances.scss │ │ ├── notifier_providers.scss │ │ ├── notifiers.scss │ │ ├── scaffolds.scss │ │ ├── sessions.scss │ │ ├── slack.scss │ │ ├── topics.scss │ │ └── users.scss │ ├── controllers/ │ │ ├── application_controller.rb │ │ ├── comments_controller.rb │ │ ├── concerns/ │ │ │ └── .keep │ │ ├── escalation_series_controller.rb │ │ ├── escalations_controller.rb │ │ ├── home_controller.rb │ │ ├── incident_events_controller.rb │ │ ├── incidents_controller.rb │ │ ├── maintenances_controller.rb │ │ ├── notifier_providers_controller.rb │ │ ├── notifiers_controller.rb │ │ ├── sessions_controller.rb │ │ ├── slack_controller.rb │ │ ├── topics_controller.rb │ │ └── users_controller.rb │ ├── helpers/ │ │ ├── application_helper.rb │ │ ├── comments_helper.rb │ │ ├── escalation_series_helper.rb │ │ ├── escalations_helper.rb │ │ ├── home_helper.rb │ │ ├── incident_events_helper.rb │ │ ├── incidents_helper.rb │ │ ├── maintenances_helper.rb │ │ ├── notifier_providers_helper.rb │ │ ├── notifiers_helper.rb │ │ ├── sessions_helper.rb │ │ ├── slack_helper.rb │ │ ├── topics_helper.rb │ │ └── users_helper.rb │ ├── mailers/ │ │ └── .keep │ ├── models/ │ │ ├── .keep │ │ ├── application_record.rb │ │ ├── comment.rb │ │ ├── concerns/ │ │ │ └── .keep │ │ ├── escalation.rb │ │ ├── escalation_series.rb │ │ ├── escalation_update_worker.rb │ │ ├── escalation_worker.rb │ │ ├── incident.rb │ │ ├── incident_event.rb │ │ ├── maintenance.rb │ │ ├── notification_worker.rb │ │ ├── notifier.rb │ │ ├── notifier_provider.rb │ │ ├── topic.rb │ │ └── user.rb │ └── views/ │ ├── comments/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── escalation_series/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── escalations/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── home/ │ │ └── index.html.erb │ ├── incident_events/ │ │ └── twilio.html.erb │ ├── incidents/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── layouts/ │ │ └── application.html.erb │ ├── maintenances/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── notifier_providers/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── hipchat/ │ │ │ ├── acknowledged.text.erb │ │ │ ├── escalated.text.erb │ │ │ ├── opened.text.erb │ │ │ └── resolved.text.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── mailgun/ │ │ │ ├── default.html.erb │ │ │ └── default.text.erb │ │ ├── new.html.erb │ │ ├── rails_logger/ │ │ │ └── default.text.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── notifiers/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ ├── sessions/ │ │ └── create.html.erb │ ├── slack/ │ │ └── interactive.html.erb │ ├── topics/ │ │ ├── _form.html.erb │ │ ├── edit.html.erb │ │ ├── index.html.erb │ │ ├── index.json.jbuilder │ │ ├── new.html.erb │ │ ├── show.html.erb │ │ └── show.json.jbuilder │ └── users/ │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder ├── bin/ │ ├── bundle │ ├── rails │ ├── rake │ ├── setup │ └── spring ├── config/ │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments/ │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers/ │ │ ├── assets.rb │ │ ├── backtrace_silencers.rb │ │ ├── cookies_serializer.rb │ │ ├── field_with_errors.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ ├── mime_types.rb │ │ ├── omniauth.rb │ │ ├── session_store.rb │ │ ├── sidekiq.rb │ │ ├── url_options.rb │ │ └── wrap_parameters.rb │ ├── locales/ │ │ └── en.yml │ ├── routes.rb │ └── secrets.yml ├── config.ru ├── db/ │ ├── migrate/ │ │ ├── 20150120134616_create_topics.rb │ │ ├── 20150120134747_create_users.rb │ │ ├── 20150120134905_create_notifiers.rb │ │ ├── 20150120135017_create_shifts.rb │ │ ├── 20150120135123_create_escalations.rb │ │ ├── 20150120135244_create_escalation_series.rb │ │ ├── 20150120135351_add_escalation_series_to_escalation.rb │ │ ├── 20150120141627_rename_type_with_kind_of_topic.rb │ │ ├── 20150120142452_create_incidents.rb │ │ ├── 20150120151642_add_escalation_series_to_topic.rb │ │ ├── 20150120154438_add_name_to_shift.rb │ │ ├── 20150121150043_add_user_to_notifier.rb │ │ ├── 20150121150857_rename_type_with_kind_of_notifier.rb │ │ ├── 20150123132415_remove_shift.rb │ │ ├── 20150123150518_add_status_to_incident.rb │ │ ├── 20150123150947_create_incident_events.rb │ │ ├── 20150125050529_create_notifier_providers.rb │ │ ├── 20150125050556_add_provider_to_notifier.rb │ │ ├── 20150125101901_remove_kind_from_notifier.rb │ │ ├── 20150127142530_remove_user_by_from_incident_event.rb │ │ ├── 20150127152127_add_info_to_incident_event.rb │ │ ├── 20150128064248_add_email_to_user.rb │ │ ├── 20150131120557_add_enable_to_topic.rb │ │ ├── 20150131121143_set_default_of_enable_of_topic_true.rb │ │ ├── 20150131122151_rename_enable_with_enabled_of_topic.rb │ │ ├── 20150201033946_add_login_token_to_user.rb │ │ ├── 20150202144538_add_token_to_user.rb │ │ ├── 20150202151740_add_refresh_token_to_user.rb │ │ ├── 20150202152015_add_settings_to_escalation_series.rb │ │ ├── 20150202155726_add_token_expires_at_to_user.rb │ │ ├── 20150203010332_remove_escalation_series_from_escalation_series.rb │ │ ├── 20150203010417_add_settings_to_escalation_series_again.rb │ │ ├── 20150207164010_add_topic_to_notifier.rb │ │ ├── 20150218071007_add_enable_to_notifier.rb │ │ ├── 20151117011141_add_active_to_user.rb │ │ ├── 20151117013824_add_provider_and_uid_to_user.rb │ │ ├── 20151118061253_add_credentials_to_user.rb │ │ ├── 20151118061938_delete_token_from_user.rb │ │ ├── 20160210010310_create_maintenances.rb │ │ ├── 20160907123728_create_comments.rb │ │ ├── 20160914063913_add_comment_index.rb │ │ └── 20161207045554_add_filter_to_maintenance.rb │ ├── schema.rb │ └── seeds.rb ├── docker/ │ └── puma.rb ├── docker-compose.yml ├── lib/ │ ├── assets/ │ │ └── .keep │ └── tasks/ │ └── .keep ├── log/ │ └── .keep ├── public/ │ ├── 404.html │ ├── 422.html │ ├── 500.html │ └── robots.txt ├── script/ │ └── update-escalations-from-google-calendar ├── spec/ │ ├── controllers/ │ │ └── slack_controller_spec.rb │ ├── factories/ │ │ ├── escalation.rb │ │ ├── escalation_series.rb │ │ ├── incident.rb │ │ ├── incident_events.rb │ │ ├── maintenances.rb │ │ ├── notifier.rb │ │ ├── notifier_provider.rb │ │ ├── topic.rb │ │ └── user.rb │ ├── helpers/ │ │ └── slack_helper_spec.rb │ ├── models/ │ │ ├── comment_spec.rb │ │ ├── escalation_series_spec.rb │ │ ├── incident_spec.rb │ │ └── maintenance_spec.rb │ ├── rails_helper.rb │ ├── requests/ │ │ ├── incident_envets_spec.rb │ │ ├── maintenances_spec.rb │ │ └── topics_spec.rb │ ├── spec_helper.rb │ ├── support/ │ │ ├── factory_girl.rb │ │ └── sidekiq.rb │ └── views/ │ └── slack/ │ └── interactive.html.erb_spec.rb └── vendor/ └── assets/ ├── javascripts/ │ └── .keep └── stylesheets/ └── .keep ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ logs/* doc/* vendor/* coverage/* tmp/* ================================================ FILE: .github/workflows/rails.yml ================================================ name: Rails on: [push] jobs: build: runs-on: ubuntu-latest services: mysql: image: mysql:5.7 env: MYSQL_ALLOW_EMPTY_PASSWORD: "yes" container: image: ruby:2.6.5 env: MYSQL_HOST: mysql steps: - uses: actions/checkout@v1 - name: Setup YARN and NodeJS run: | curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list curl -sL https://deb.nodesource.com/setup_12.x | bash - apt-get install -y yarn nodejs - name: Build and setup run: | gem install bundler --no-document bundle install --jobs 4 --retry 3 --deployment bundle exec rails yarn:install db:setup assets:precompile bundle exec rake env: RAILS_ENV: "test" ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: # git config --global core.excludesfile '~/.gitignore_global' # Ignore bundler config. /.bundle # Ignore the default SQLite database. /db/*.sqlite3 /db/*.sqlite3-journal # Ignore all logfiles and tempfiles. /log/* !/log/.keep /tmp *.rdb .env coverage ================================================ FILE: .rspec ================================================ --color --require spec_helper ================================================ FILE: .rubocop.yml ================================================ AllCops: TargetRubyVersion: 2.4 DisplayCopNames: true DisabledByDefault: true Exclude: - 'db/**/*' - 'vendor/**/*' Rails: Enabled: true Rails/ActionFilter: EnforcedStyle: action Enabled: true Style/HashSyntax: Enabled: true Style/MethodDefParentheses: Enabled: true Style/Encoding: Enabled: true Style/For: EnforcedStyle: each Enabled: true Style/FrozenStringLiteralComment: EnforcedStyle: never Enabled: true Style/NumericLiteralPrefix: EnforcedOctalStyle: zero_only Enabled: true Style/StabbyLambdaParentheses: EnforcedStyle: require_parentheses Enabled: true Layout/EmptyLines: Enabled: true Layout/TrailingBlankLines: Enabled: true Layout/TrailingWhitespace: Enabled: true Layout/AccessModifierIndentation: EnforcedStyle: indent Enabled: true Layout/CaseIndentation: EnforcedStyle: end Enabled: true Layout/MultilineHashBraceLayout: EnforcedStyle: symmetrical Enabled: true ================================================ FILE: Dockerfile ================================================ FROM ruby:2.6.5 RUN apt update -qqy && apt -qqy install nodejs WORKDIR /tmp ADD Gemfile* /tmp/ RUN gem install bundler:2.1.4 RUN bundle install --deployment -j4 --without development test ADD . /app WORKDIR /app RUN cp -a /tmp/vendor/bundle /app/vendor/bundle && \ bundle exec rake assets:precompile CMD ["bundle", "exec", "foreman", "start", "-f", "Procfile.docker"] ================================================ FILE: Gemfile ================================================ source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '5.2.4.1' # Use SCSS for stylesheets gem 'sass-rails', '~> 5.0' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '>= 1.3.0' # Use CoffeeScript for .coffee assets and views gem 'coffee-rails' # See https://github.com/sstephenson/execjs#readme for more supported runtimes # gem 'therubyracer', platforms: :ruby # Use jquery as the JavaScript library gem 'jquery-rails' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.0' # bundle exec rake doc:rails generates the API under doc/api. gem 'sdoc' # Use ActiveModel has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Unicorn as the app server # gem 'unicorn' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development gem 'sidekiq', '~> 3.3.0' gem 'sinatra' gem 'faraday', '~> 0.15.4' gem 'holiday_jp' gem 'hipchat' gem 'kaminari' gem 'puma' gem 'rack-health' gem 'omniauth-google-oauth2', '~> 0.6' gem 'retriable', '3.0.1' gem 'twilio-ruby' gem 'mysql2' gem 'google-api-client', '~> 0.7.1' gem 'foreman' group :development do gem 'web-console' end group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' # Access an IRB console on exception pages or by using <%= console %> in views # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'rspec-rails' gem 'database_rewinder' gem 'pry-byebug' gem 'dotenv-rails' gem 'factory_bot_rails' gem 'rubocop', require: false end group :test do gem 'simplecov', require: false end gem 'dogapi' gem 'aws-sdk' gem 'rails_autolink' ================================================ FILE: LICENSE.txt ================================================ The MIT License (MIT) Copyright (c) 2014-2015 Ryota Arai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Procfile ================================================ web: bin/rails s worker: bundle exec sidekiq update_escalations: while true; do bundle exec rails runner 'EscalationUpdateWorker.perform_async()'; sleep 60; done ================================================ FILE: Procfile.docker ================================================ web: puma -C docker/puma.rb worker: bundle exec sidekiq update_escalations: while true; do bundle exec rails runner 'EscalationUpdateWorker.perform_async()'; sleep 60; done ================================================ FILE: README.md ================================================ # Waker [](https://travis-ci.org/ryotarai/waker) Alert Escalation System  ## Overview   ## Tutorial ### 1. (Optional) Configure auth provider You can use external auth provider **optionally**. Currently, Google Auth is only supported (Patches are welcome :) ) ``` $ echo 'GOOGLE_CLIENT_ID=...' >> .env $ echo 'GOOGLE_CLIENT_SECRET=...' >> .env $ echo 'GOOGLE_DOMAIN=...' >> .env # If you restrict to use Google Apps domain ``` ### 2. Start the server ``` $ bundle install $ foreman start ``` It starts an application server and a Sidekiq worker. ### 3. (If you uses auth provider) Log in Visit [http://localhost:3000](http://localhost:3000) and log in with your credentials. A new user account is automatically created and suspended by default. You can activate a user from [http://localhost:3000/users](http://localhost:3000/users) but you have to activate it from `rails console` because you are the first user: ``` $ bundle exec rails c > User.first.update!(active: true) ``` ### 4. Create users Visit [http://localhost:3000/users/new](http://localhost:3000/users/new) and create new users. ### 5. Create a notifier provider Visit [http://localhost:3000/notifier_providers/new](http://localhost:3000/notifier_providers/new) and create a notifier provider. See [Notifier Providers](https://github.com/ryotarai/waker#notifier-providers) section for detailed information. ### 6. Create a notifier Visit [http://localhost:3000/notifiers/new](http://localhost:3000/notifiers/new) and create a notifier. See [Notifier](https://github.com/ryotarai/waker#notifiers) section for detailed information. ### 7. Create an escalation series Visit [http://localhost:3000/escalation_series/new](http://localhost:3000/escalation_series/new) and create a escalation series. Escalation series is a series of escalations. ### 8. Create escalations Visit [http://localhost:3000/escalations/new](http://localhost:3000/escalations/new) and create escalations. - `Escalate to`: Who gets escalated incidents - `Escalate after sec`: Seconds to escalate incidents since the incidents created ### 9. Create a topic Visit [http://localhost:3000/topics/new](http://localhost:3000/topics/new) and create topics. ### 10. Send alerts to the topic Suppoted alerts generaters are below: - Mailgun ( `http://localhost:3000/topics/1/mailgun` ) - Mackerel ( `http://localhost:3000/topics/1/mackerel` ) - Alertmanager ( `http://localhost:3000/topics/1/alertmanager` ) - Slack( `http://localhost:3000/topics/1/slack` ) If you want to use Mailgun, you can configure Mailgun route setting with Mailgun endpoint you can see in [http://localhost:3000/topics/1/mailgun](http://localhost:3000/topics/1/mailgun) ## Configuration ### Notifier Providers #### HipChat - `api_token` - `api_version`: `v1` or `v2` #### Twilio - `account_sid` - `auth_token` - `from`: Phone number #### Mailgun - `api_key` - `from`: Email address ### Notifiers #### Common fields These are supported by all notifier provider ``` or_conditions: - japanese_weekday: true not_between: 9:30+0900-18:30+0900 - not_japanese_weekday: true ``` #### HipChat - `room`: Room name or ID #### Twilio - `to`: Phone number #### Mailgun - `to`: Email address ================================================ FILE: Rakefile ================================================ # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. require File.expand_path('../config/application', __FILE__) Rails.application.load_tasks ================================================ FILE: app/assets/images/.keep ================================================ ================================================ FILE: app/assets/javascripts/application.js ================================================ // This is a manifest file that'll be compiled into application.js, which will include all the files // listed below. // // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // compiled file. // // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details // about supported directives. // //= require jquery //= require jquery_ujs //= require_tree . ================================================ FILE: app/assets/javascripts/comments.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/escalation_series.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/escalations.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/home.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/incident_events.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/incidents.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/maintenances.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/notifier_providers.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/notifiers.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/sessions.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/slack.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/topics.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/javascripts/users.coffee ================================================ # Place all the behaviors and hooks related to the matching controller here. # All this logic will automatically be available in application.js. # You can use CoffeeScript in this file: http://coffeescript.org/ ================================================ FILE: app/assets/stylesheets/application.css ================================================ /* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. * * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require_tree . *= require_self */ ================================================ FILE: app/assets/stylesheets/comments.scss ================================================ // Place all the styles related to the comments controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/escalation_series.scss ================================================ // Place all the styles related to the EscalationSeries controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/escalations.scss ================================================ // Place all the styles related to the escalations controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/home.scss ================================================ // Place all the styles related to the home controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/incident_events.scss ================================================ // Place all the styles related to the incident_events controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/incidents.scss ================================================ // Place all the styles related to the incidents controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/maintenances.scss ================================================ // Place all the styles related to the maintenances controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/notifier_providers.scss ================================================ // Place all the styles related to the NotifierProviders controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/notifiers.scss ================================================ // Place all the styles related to the notifiers controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/scaffolds.scss ================================================ body { background-color: #fff; color: #333; font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; } p, ol, ul, td { font-family: verdana, arial, helvetica, sans-serif; font-size: 13px; line-height: 18px; } pre { background-color: #eee; padding: 10px; font-size: 11px; } a { color: #000; &:visited { color: #666; } &:hover { color: #fff; background-color: #000; } } div { &.field, &.actions { margin-bottom: 10px; } } #notice { color: green; } .field_with_errors { padding: 2px; background-color: red; display: table; } #error_explanation { width: 450px; border: 2px solid red; padding: 7px; padding-bottom: 0; margin-bottom: 20px; background-color: #f0f0f0; h2 { text-align: left; font-weight: bold; padding: 5px 5px 5px 15px; font-size: 12px; margin: -7px; margin-bottom: 0px; background-color: #c00; color: #fff; } ul li { font-size: 12px; list-style: square; } } ================================================ FILE: app/assets/stylesheets/sessions.scss ================================================ // Place all the styles related to the sessions controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/slack.scss ================================================ // Place all the styles related to the slack controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/topics.scss ================================================ // Place all the styles related to the topics controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/assets/stylesheets/users.scss ================================================ // Place all the styles related to the users controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ ================================================ FILE: app/controllers/application_controller.rb ================================================ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :null_session helper_method :current_user if ENV["GOOGLE_CLIENT_ID"] before_action :login_required end def login_required unless current_user session[:user_id] = nil redirect_to '/auth/google_oauth2_with_calendar' return end unless current_user.active render text: "You are not activated yet. Please ask administrator to activate you" return end end private def current_user=(user) session[:user_id] = user.id end def current_user if user_id = session[:user_id] User.find(user_id) elsif login_token = request.headers['X-Login-Token'] User.find_by(login_token: login_token) end end end ================================================ FILE: app/controllers/comments_controller.rb ================================================ class CommentsController < ApplicationController before_action :set_comment, only: [:show, :edit, :update, :destroy] before_action :set_incident # GET /comments # GET /comments.json def index @comments = Comment.where(incident: @incident) end # GET /comments/1 # GET /comments/1.json def show end # GET /comments/new def new @comment = Comment.new end # GET /comments/1/edit def edit end # POST /comments # POST /comments.json def create p comment_params @comment = Comment.new(comment_params) respond_to do |format| if @comment.save format.html { redirect_to [@incident, @comment], notice: 'Comment was successfully created.' } format.json { render :show, status: :created, location: [@incident, @comment] } else format.html { render :new } format.json { render json: @comment.errors, status: :unprocessable_entity } end end end # PATCH/PUT /comments/1 # PATCH/PUT /comments/1.json def update respond_to do |format| if @comment.update(comment_params) format.html { redirect_to [@incident, @comment], notice: 'Comment was successfully updated.' } format.json { render :show, status: :ok, location: [@incident, @comment] } else format.html { render :edit } format.json { render json: @comment.errors, status: :unprocessable_entity } end end end # DELETE /comments/1 # DELETE /comments/1.json def destroy @comment.destroy respond_to do |format| format.html { redirect_to incident_comments_url, notice: 'Comment was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_comment @comment = Comment.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def comment_params params.require(:comment).permit(:incident_id, :user_id, :comment) end def set_incident @incident = Incident.find(params[:incident_id]) end end ================================================ FILE: app/controllers/concerns/.keep ================================================ ================================================ FILE: app/controllers/escalation_series_controller.rb ================================================ class EscalationSeriesController < ApplicationController before_action :set_escalation_series, only: [:show, :edit, :update, :destroy, :update_escalations] # GET /escalation_series # GET /escalation_series.json def index @escalation_series = EscalationSeries.all end # GET /escalation_series/1 # GET /escalation_series/1.json def show end # GET /escalation_series/new def new @escalation_series = EscalationSeries.new end # GET /escalation_series/1/edit def edit end # POST /escalation_series # POST /escalation_series.json def create @escalation_series = EscalationSeries.new(escalation_series_params) respond_to do |format| if @escalation_series.save format.html { redirect_to @escalation_series, notice: 'Escalation series was successfully created.' } format.json { render :show, status: :created, location: @escalation_series } else format.html { render :new } format.json { render json: @escalation_series.errors, status: :unprocessable_entity } end end end # PATCH/PUT /escalation_series/1 # PATCH/PUT /escalation_series/1.json def update respond_to do |format| if @escalation_series.update(escalation_series_params) format.html { redirect_to @escalation_series, notice: 'Escalation series was successfully updated.' } format.json { render :show, status: :ok, location: @escalation_series } else format.html { render :edit } format.json { render json: @escalation_series.errors, status: :unprocessable_entity } end end end # DELETE /escalation_series/1 # DELETE /escalation_series/1.json def destroy @escalation_series.destroy respond_to do |format| format.html { redirect_to escalation_series_index_url, notice: 'Escalation series was successfully destroyed.' } format.json { head :no_content } end end def update_escalations @escalation_series.update_escalations! end private # Use callbacks to share common setup or constraints between actions. def set_escalation_series @escalation_series = EscalationSeries.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def escalation_series_params params.require(:escalation_series).permit(:name, :settings).tap do |v| v[:settings] = YAML.load(v[:settings]) end end end ================================================ FILE: app/controllers/escalations_controller.rb ================================================ class EscalationsController < ApplicationController before_action :set_escalation, only: [:show, :edit, :update, :destroy] # GET /escalations # GET /escalations.json def index @escalations = Escalation.all.order('escalate_after_sec') end # GET /escalations/1 # GET /escalations/1.json def show end # GET /escalations/new def new @escalation = Escalation.new end # GET /escalations/1/edit def edit end # POST /escalations # POST /escalations.json def create @escalation = Escalation.new(escalation_params) respond_to do |format| if @escalation.save format.html { redirect_to @escalation, notice: 'Escalation was successfully created.' } format.json { render :show, status: :created, location: @escalation } else format.html { render :new } format.json { render json: @escalation.errors, status: :unprocessable_entity } end end end # PATCH/PUT /escalations/1 # PATCH/PUT /escalations/1.json def update respond_to do |format| if @escalation.update(escalation_params) format.html { redirect_to @escalation, notice: 'Escalation was successfully updated.' } format.json { render :show, status: :ok, location: @escalation } else format.html { render :edit } format.json { render json: @escalation.errors, status: :unprocessable_entity } end end end # DELETE /escalations/1 # DELETE /escalations/1.json def destroy @escalation.destroy respond_to do |format| format.html { redirect_to escalations_url, notice: 'Escalation was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_escalation @escalation = Escalation.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def escalation_params params.require(:escalation).permit(:escalate_to_id, :escalate_after_sec, :escalation_series_id) end end ================================================ FILE: app/controllers/home_controller.rb ================================================ class HomeController < ApplicationController def index redirect_to incidents_path end end ================================================ FILE: app/controllers/incident_events_controller.rb ================================================ require 'securerandom' class IncidentEventsController < ApplicationController skip_before_action :login_required, only: [:twilio], raise: false def twilio @event = IncidentEvent.find(params[:id]) language = ENV['TWILIO_LANGUAGE'] || 'en-US' resp = nil if params[:Digits] case params[:Digits] when '1' @event.incident.acknowledge! rescue nil when '2' @event.incident.resolve! rescue nil end resp = Twilio::TwiML::VoiceResponse.new do |r| r.say message: @event.incident.status, voice: 'alice', language: language r.hangup end else resp = Twilio::TwiML::VoiceResponse.new do |r| r.gather timeout: 10, numDigits: 1 do |g| g.say message: "This is Waker alert.", voice: 'alice', language: language g.say message: @event.incident.subject, voice: 'alice', language: language g.say message: "To acknowledge, press 1.", voice: 'alice', language: language g.say message: "To resolve, press 2.", voice: 'alice', language: language end end end render xml: resp.to_s end end ================================================ FILE: app/controllers/incidents_controller.rb ================================================ class IncidentsController < ApplicationController before_action :set_incidents, only: [:index, :bulk_acknowledge, :bulk_resolve] before_action :set_incident, only: [:show, :edit, :update, :destroy, :acknowledge, :resolve] before_action :ensure_hash, only: [:acknowledge, :resolve] skip_before_action :login_required, only: [:acknowledge, :resolve], raise: false # GET /incidents # GET /incidents.json def index @page = (params[:page] || 1).to_i @incidents = @incidents.order('id DESC').page(@page).per(25) end # GET /incidents/1 # GET /incidents/1.json def show end # GET /incidents/new def new @incident = Incident.new end # GET /incidents/1/edit def edit end # POST /incidents # POST /incidents.json def create @incident = Incident.new(incident_params) respond_to do |format| if @incident.save format.html { redirect_to @incident, notice: 'Incident was successfully created.' } format.json { render :show, status: :created, location: @incident } else format.html { render :new } format.json { render json: @incident.errors, status: :unprocessable_entity } end end end # PATCH/PUT /incidents/1 # PATCH/PUT /incidents/1.json def update respond_to do |format| if @incident.update(incident_params) format.html { redirect_to @incident, notice: 'Incident was successfully updated.' } format.json { render :show, status: :ok, location: @incident } else format.html { render :edit } format.json { render json: @incident.errors, status: :unprocessable_entity } end end end def bulk_acknowledge @incidents.opened.update_all(status: Incident.statuses[:acknowledged]) respond_to do |format| format.html { redirect_to incidents_url, notice: 'Incidents were successfully acknowledged.' } format.json { head :no_content } end end def bulk_resolve @incidents.update_all(status: Incident.statuses[:resolved]) respond_to do |format| format.html { redirect_to incidents_url, notice: 'Incidents were successfully resolved.' } format.json { head :no_content } end end # DELETE /incidents/1 # DELETE /incidents/1.json def destroy @incident.destroy respond_to do |format| format.html { redirect_to incidents_url, notice: 'Incident was successfully destroyed.' } format.json { head :no_content } end end def acknowledge @incident.acknowledge! respond_to do |format| if current_user format.html { redirect_to incidents_url, notice: 'Incident was successfully acknowledged.' } else format.html { render text: "Acknowledged" } end format.json { render json: {status: 'ok'} } end end def resolve @incident.resolve! respond_to do |format| if current_user format.html { redirect_to incidents_url, notice: 'Incident was successfully resolved.' } else format.html { render text: "Resolved" } end format.json { render json: {status: 'ok'} } end end private # Use callbacks to share common setup or constraints between actions. def set_incident @incident = Incident.find(params[:id]) end def set_incidents set_visible_statuses set_visible_topic @incidents = Incident.all if @visible_statuses @incidents = @incidents.where(status: @visible_statuses) end if @visible_topic @incidents = @incidents.where(topic: @visible_topic) end end def set_visible_statuses @visible_statuses = session[:incidents_statuses] # for transition from rev 0a2dd42 or earlier @visible_statuses = nil if @visible_statuses.try(:empty?) if params[:statuses] if params[:statuses] == '' @visible_statuses = nil # all else @visible_statuses = params[:statuses].split(',').map(&:to_i) end session[:incidents_statuses] = @visible_statuses end end def set_visible_topic @visible_topic = session[:incidents_topic] # for transition from rev 0a2dd42 or earlier @visible_topic = nil if @visible_topic == 'all' if params[:topic] if params[:topic] == 'all' @visible_topic = nil # all else @visible_topic = params[:topic].to_i end session[:incidents_topic] = @visible_topic end end # Never trust parameters from the scary internet, only allow the white list through. def incident_params params.require(:incident).permit(:subject, :description, :topic_id, :occured_at) end def ensure_hash unless params[:hash] == @incident.confirmation_hash render text: "Wrong hash", status: 403 end end end ================================================ FILE: app/controllers/maintenances_controller.rb ================================================ class MaintenancesController < ApplicationController before_action :set_maintenance, only: [:show, :edit, :update, :destroy] # GET /maintenances # GET /maintenances.json def index @maintenances = Maintenance.not_expired end # GET /maintenances/1 # GET /maintenances/1.json def show end # GET /maintenances/new def new now = Time.now @maintenance = Maintenance.new( start_time: now, end_time: now + 60*60 ) end # GET /maintenances/1/edit def edit end # POST /maintenances # POST /maintenances.json def create @maintenance = Maintenance.new(maintenance_params) respond_to do |format| if @maintenance.save format.html { redirect_to @maintenance, notice: 'Maintenance was successfully created.' } format.json { render :show, status: :created, location: @maintenance } else format.html { render :new } format.json { render json: @maintenance.errors, status: :unprocessable_entity } end end end # PATCH/PUT /maintenances/1 # PATCH/PUT /maintenances/1.json def update respond_to do |format| if @maintenance.update(maintenance_params) format.html { redirect_to @maintenance, notice: 'Maintenance was successfully updated.' } format.json { render :show, status: :ok, location: @maintenance } else format.html { render :edit } format.json { render json: @maintenance.errors, status: :unprocessable_entity } end end end # DELETE /maintenances/1 # DELETE /maintenances/1.json def destroy @maintenance.destroy respond_to do |format| format.html { redirect_to maintenances_url, notice: 'Maintenance was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_maintenance @maintenance = Maintenance.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def maintenance_params params.require(:maintenance).permit(:topic_id, :start_time, :end_time, :filter) end end ================================================ FILE: app/controllers/notifier_providers_controller.rb ================================================ class NotifierProvidersController < ApplicationController before_action :set_notifier_provider, only: [:show, :edit, :update, :destroy] # GET /notifier_providers # GET /notifier_providers.json def index @notifier_providers = NotifierProvider.all end # GET /notifier_providers/1 # GET /notifier_providers/1.json def show end # GET /notifier_providers/new def new @notifier_provider = NotifierProvider.new end # GET /notifier_providers/1/edit def edit end # POST /notifier_providers # POST /notifier_providers.json def create @notifier_provider = NotifierProvider.new(notifier_provider_params) respond_to do |format| if @notifier_provider.save format.html { redirect_to @notifier_provider, notice: 'Notifier provider was successfully created.' } format.json { render :show, status: :created, location: @notifier_provider } else format.html { render :new } format.json { render json: @notifier_provider.errors, status: :unprocessable_entity } end end end # PATCH/PUT /notifier_providers/1 # PATCH/PUT /notifier_providers/1.json def update respond_to do |format| if @notifier_provider.update(notifier_provider_params) format.html { redirect_to @notifier_provider, notice: 'Notifier provider was successfully updated.' } format.json { render :show, status: :ok, location: @notifier_provider } else format.html { render :edit } format.json { render json: @notifier_provider.errors, status: :unprocessable_entity } end end end # DELETE /notifier_providers/1 # DELETE /notifier_providers/1.json def destroy @notifier_provider.destroy respond_to do |format| format.html { redirect_to notifier_providers_url, notice: 'Notifier provider was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_notifier_provider @notifier_provider = NotifierProvider.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def notifier_provider_params params.require(:notifier_provider).permit(:name, :kind, :settings).tap do |v| v[:settings] = YAML.load(v[:settings]) end end end ================================================ FILE: app/controllers/notifiers_controller.rb ================================================ class NotifiersController < ApplicationController before_action :set_notifier, only: [:show, :edit, :update, :destroy] # GET /notifiers # GET /notifiers.json def index @notifiers = Notifier.all end # GET /notifiers/1 # GET /notifiers/1.json def show end # GET /notifiers/new def new @notifier = Notifier.new end # GET /notifiers/1/edit def edit end # POST /notifiers # POST /notifiers.json def create @notifier = Notifier.new(notifier_params) respond_to do |format| if @notifier.save format.html { redirect_to @notifier, notice: 'Notifier was successfully created.' } format.json { render :show, status: :created, location: @notifier } else format.html { render :new } format.json { render json: @notifier.errors, status: :unprocessable_entity } end end end # PATCH/PUT /notifiers/1 # PATCH/PUT /notifiers/1.json def update respond_to do |format| if @notifier.update(notifier_params) format.html { redirect_to @notifier, notice: 'Notifier was successfully updated.' } format.json { render :show, status: :ok, location: @notifier } else format.html { render :edit } format.json { render json: @notifier.errors, status: :unprocessable_entity } end end end # DELETE /notifiers/1 # DELETE /notifiers/1.json def destroy @notifier.destroy respond_to do |format| format.html { redirect_to notifiers_url, notice: 'Notifier was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_notifier @notifier = Notifier.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def notifier_params params.require(:notifier).permit(:user_id, :kind, :settings, :notify_after_sec, :provider_id, :topic_id, :enabled).tap do |v| v[:settings] = YAML.load(v[:settings]) end end end ================================================ FILE: app/controllers/sessions_controller.rb ================================================ class SessionsController < ApplicationController skip_before_action :login_required, only: [:create] def create @user = User.find_or_create_from_auth_hash(auth_hash) @user.update_credentials_from_auth_hash(auth_hash) self.current_user = @user redirect_to '/' end private def auth_hash request.env['omniauth.auth'] end end ================================================ FILE: app/controllers/slack_controller.rb ================================================ class SlackController < ApplicationController skip_before_action :login_required, only: [:interactive], raise: false def interactive verify! message = payload['original_message'] message['attachments'][0].delete('actions') user = payload['user']['name'] text = '' payload['actions'].each do |a| case a['value'] when 'acknowledge' incident.acknowledge! text = ":white_check_mark: @#{user} acknowledged" when 'resolve' incident.resolve! text = ":white_check_mark: @#{user} resolved" end end message['attachments'][0]['fields'] = [{ 'title' => text, 'value' => '', 'short' => false, }] render json: message end private def payload JSON.parse(params[:payload]) end private def verify! verified = false Notifier.preload(:provider).find_each do |n| settings = n.provider.settings.merge(n.settings) token = settings['verification_token'] if n.provider.slack? && settings['enable_buttons'] && payload['token'] == token verified = true break end end unless verified raise 'token verification failed' end end private def incident_id payload['callback_id'].match(/\Aincident\.(\d+)\z/)[1].to_i end private def incident Incident.find(incident_id) end end ================================================ FILE: app/controllers/topics_controller.rb ================================================ class TopicsController < ApplicationController before_action :set_topic, only: [:show, :edit, :update, :destroy, :mailgun, :mackerel, :alertmanager, :slack] skip_before_action :login_required, only: [:mailgun, :mackerel, :alertmanager, :slack], raise: false # GET /topics # GET /topics.json def index @topics = Topic.all end # GET /topics/1 # GET /topics/1.json def show end # GET /topics/new def new @topic = Topic.new end # GET /topics/1/edit def edit end # POST /topics # POST /topics.json def create @topic = Topic.new(topic_params) respond_to do |format| if @topic.save format.html { redirect_to @topic, notice: 'Topic was successfully created.' } format.json { render :show, status: :created, location: @topic } else format.html { render :new } format.json { render json: @topic.errors, status: :unprocessable_entity } end end end # PATCH/PUT /topics/1 # PATCH/PUT /topics/1.json def update respond_to do |format| if @topic.update(topic_params) format.html { redirect_to @topic, notice: 'Topic was successfully updated.' } format.json { render :show, status: :ok, location: @topic } else format.html { render :edit } format.json { render json: @topic.errors, status: :unprocessable_entity } end end end # DELETE /topics/1 # DELETE /topics/1.json def destroy @topic.destroy respond_to do |format| format.html { redirect_to topics_url, notice: 'Topic was successfully destroyed.' } format.json { head :no_content } end end # POST /topics/1/mailgun def mailgun unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end # http://documentation.mailgun.com/user_manual.html#routes subject = params[:subject] description = params['body-plain'] if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance." render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {}, status: 200 end # POST /topics/1/mackerel def mackerel data = JSON.parse(request.body.read) return render json: {}, status: 200 if data.dig('alert', 'status') == 'ok' || data.dig('alertGroup', 'status') == 'OK' unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end if data['event'] == 'alertGroup' then subject = "[#{data['alertGroup']['status']}] #{data['alertGroupSetting']['name']}" else name = data.key?('host') ? data['host']['name'] : data['alert']['monitorName'] subject = "[#{data['alert']['status']}] #{name}" end description = JSON.pretty_generate(data) if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance" render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {}, status: 200 end # POST /topics/1/alertmanager def alertmanager data = JSON.parse(request.body.read) unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end subject = "[#{data['commonLabels']['severity']}] #{data['commonLabels']['alertname']}: #{data['commonAnnotations']['summary']}" description = JSON.pretty_generate(data) if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance" render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {}, status: 200 end def slack unless @topic.enabled Rails.logger.info "Incident creation is skipped because the topic is disabled." render json: {}, status: 200 return end subject = "escalation from slack,channel name:#{params['channel_name']} #{params['text']}" description = "channel:#{params['channel_name']} user:#{params['user_name']} #{params['text']}" if @topic.in_maintenance?(subject, description) Rails.logger.info "Incident creation is skipped because the topic is in maintenance." render json: {}, status: 200 return end @topic.incidents.create!( subject: subject, description: description, ) render json: {text: 'accept your page'}, status: 200 end private # Use callbacks to share common setup or constraints between actions. def set_topic @topic = Topic.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def topic_params params.require(:topic).permit(:name, :kind, :escalation_series_id, :enabled) end end ================================================ FILE: app/controllers/users_controller.rb ================================================ class UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy, :activation, :deactivation] # GET /users # GET /users.json def index @users = User.all end # GET /users/1 # GET /users/1.json def show end # GET /users/new def new @user = User.new end # GET /users/1/edit def edit end # POST /users # POST /users.json def create @user = User.new(user_params) respond_to do |format| if @user.save format.html { redirect_to @user, notice: 'User was successfully created.' } format.json { render :show, status: :created, location: @user } else format.html { render :new } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # PATCH/PUT /users/1 # PATCH/PUT /users/1.json def update respond_to do |format| if @user.update(user_params) format.html { redirect_to @user, notice: 'User was successfully updated.' } format.json { render :show, status: :ok, location: @user } else format.html { render :edit } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # DELETE /users/1 # DELETE /users/1.json def destroy @user.destroy respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully destroyed.' } format.json { head :no_content } end end # PATCH/PUT /users/1/activation def activation @user.update!(active: true) respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully activated.' } end end # PATCH/PUT /users/1/deactivation def deactivation @user.update!(active: false) respond_to do |format| format.html { redirect_to users_url, notice: 'User was successfully deactivated.' } end end private # Use callbacks to share common setup or constraints between actions. def set_user @user = User.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def user_params params.require(:user).permit(:name) end end ================================================ FILE: app/helpers/application_helper.rb ================================================ module ApplicationHelper end ================================================ FILE: app/helpers/comments_helper.rb ================================================ module CommentsHelper end ================================================ FILE: app/helpers/escalation_series_helper.rb ================================================ module EscalationSeriesHelper end ================================================ FILE: app/helpers/escalations_helper.rb ================================================ module EscalationsHelper end ================================================ FILE: app/helpers/home_helper.rb ================================================ module HomeHelper end ================================================ FILE: app/helpers/incident_events_helper.rb ================================================ module IncidentEventsHelper end ================================================ FILE: app/helpers/incidents_helper.rb ================================================ module IncidentsHelper end ================================================ FILE: app/helpers/maintenances_helper.rb ================================================ module MaintenancesHelper end ================================================ FILE: app/helpers/notifier_providers_helper.rb ================================================ module NotifierProvidersHelper end ================================================ FILE: app/helpers/notifiers_helper.rb ================================================ module NotifiersHelper end ================================================ FILE: app/helpers/sessions_helper.rb ================================================ module SessionsHelper end ================================================ FILE: app/helpers/slack_helper.rb ================================================ module SlackHelper end ================================================ FILE: app/helpers/topics_helper.rb ================================================ module TopicsHelper end ================================================ FILE: app/helpers/users_helper.rb ================================================ module UsersHelper end ================================================ FILE: app/mailers/.keep ================================================ ================================================ FILE: app/models/.keep ================================================ ================================================ FILE: app/models/application_record.rb ================================================ # Base ApplicationRecord Class class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end ================================================ FILE: app/models/comment.rb ================================================ class Comment < ApplicationRecord belongs_to :incident belongs_to :user validates :incident, presence: true validates :user, presence: true validates :comment, presence: true end ================================================ FILE: app/models/concerns/.keep ================================================ ================================================ FILE: app/models/escalation.rb ================================================ class Escalation < ApplicationRecord belongs_to :escalation_series belongs_to :escalate_to, class_name: 'User' validates :escalation_series, presence: true validates :escalate_to, presence: true validates :escalate_after_sec, numericality: {greater_than_or_equal_to: 5} end ================================================ FILE: app/models/escalation_series.rb ================================================ class EscalationSeries < ApplicationRecord has_many :escalations, dependent: :destroy has_many :topics, dependent: :destroy validates :name, presence: true serialize :settings, JSON after_initialize :set_defaults def set_defaults self.settings ||= {} end def update_escalations! updater_class = case self.settings['update_by'] when 'google_calendar' GoogleCalendarEscalationUpdater else nil end if updater_class updater = updater_class.new(self) updater.update! end end class EscalationUpdater def initialize(series) @series = series end def update! raise NotImplementedError end private def settings @series.settings end end class GoogleCalendarEscalationUpdater < EscalationUpdater def initialize(*) super require 'google/api_client' end def update! Rails.logger.info "Update #{@series.inspect} by Google Calendar" if user_as.provider && user_as.provider != 'google_oauth2_with_calendar' raise "User ##{user_as.id} is not authenticated by 'google_oauth2_with_calendar' provider" end client = Google::APIClient.new( application_name: "Waker", application_version: "2.0.0", user_agent: "Waker/2.0.0 google-api-client" ) auth = client.authorization expired = Time.at(user_as.credentials.fetch('expires_at')) < Time.now if user_as.credentials.fetch('expires') && expired Rails.logger.info "Refreshing access token..." auth.client_id = ENV["GOOGLE_CLIENT_ID"] auth.client_secret = ENV["GOOGLE_CLIENT_SECRET"] auth.refresh_token = user_as.credentials.fetch('refresh_token') auth.grant_type = "refresh_token" auth.refresh! user_as.update!( credentials: user_as.credentials.merge( 'token' => auth.access_token, 'expires_at' => auth.expires_at.to_i, ) ) else auth.access_token = user_as.credentials.fetch('token') end calendar_api = client.discovered_api('calendar', 'v3') calendar = client.execute( api_method: calendar_api.calendar_list.list, parameters: {}, ).data.items.find do |cal| cal['summary'] == calendar_name end events = client.execute( api_method: calendar_api.events.list, parameters: { 'calendarId' => calendar['id'], 'timeMax' => (Time.now + 1).iso8601, 'timeMin' => (Time.now).iso8601, 'singleEvents' => true, }, ).data.items events.each do |event| unless event['end']['dateTime'] && event['start']['dateTime'] raise "dateTime field is not found (The event may be all-day event)\n#{event}" end end events.sort! do |a, b| a['end']['dateTime'] - a['start']['dateTime'] <=> b['end']['dateTime'] - b['start']['dateTime'] end # shortest event event = events.first persons = event['summary'].split(event_delimiter).map(&:strip) escalations = @series.escalations.order('escalate_after_sec') persons.each_with_index do |name, i| user = User.find_by(name: name) raise "User '#{name}' is not found." unless user escalation = escalations[i] escalation.update!(escalate_to: user) end end private def user_as User.find(settings.fetch('user_as_id')) end def calendar_name settings.fetch('calendar') end def event_delimiter settings.fetch('event_delimiter') end end end ================================================ FILE: app/models/escalation_update_worker.rb ================================================ class EscalationUpdateWorker include Sidekiq::Worker def perform EscalationSeries.all.each do |series| handle_series(series) end rescue => err Rails.logger.error "#{err.class}: #{err}\n#{err.backtrace.join("\n")}" end private def handle_series(series) series.update_escalations! end end ================================================ FILE: app/models/escalation_worker.rb ================================================ class EscalationWorker include Sidekiq::Worker def self.enqueue(incident, escalation) Rails.logger.info "Enqueue EscalaionWorker job" self.perform_in(escalation.escalate_after_sec, incident.id, escalation.id) end def perform(incident_id, escalation_id) incident = Incident.find(incident_id) escalation = Escalation.find(escalation_id) if incident.opened? incident.events.create( kind: :escalated, info: { escalation: escalation, escalated_to: escalation.escalate_to, }, ) end end end ================================================ FILE: app/models/incident.rb ================================================ require 'digest/sha1' class Incident < ApplicationRecord STATUSES = [:opened, :acknowledged, :resolved] belongs_to :topic has_many :events, class_name: 'IncidentEvent', dependent: :destroy enum status: STATUSES has_many :comments validates :topic, presence: true validates :subject, presence: true validates :description, presence: true validates :occured_at, presence: true after_initialize :set_defaults after_create :enqueue def acknowledge! return if self.acknowledged? || self.resolved? self.acknowledged! events.create(kind: :acknowledged) end def resolve! return if self.resolved? self.resolved! events.create(kind: :resolved) end def confirmation_hash Digest::SHA1.hexdigest("#{Rails.application.secrets.secret_key_base}#{self.id}") end private def set_defaults self.status ||= :opened self.occured_at ||= Time.now end def enqueue topic.escalation_series.escalations.each do |escalation| EscalationWorker.enqueue(self, escalation) end events.create(kind: :opened) end end ================================================ FILE: app/models/incident_event.rb ================================================ class IncidentEvent < ApplicationRecord belongs_to :incident enum kind: [:opened, :acknowledged, :resolved, :escalated, :commented, :notified] validates :incident, presence: true validates :kind, presence: true serialize :info, JSON after_create :notify after_initialize :set_defaults def set_defaults self.info ||= {} end def notify return if self.notified? Notifier.all.each do |notifier| next if notifier.topic && self.incident.topic != notifier.topic notifier.notify(self) end end def escalated_to self.info['escalated_to'] && User.find(self.info['escalated_to']['id']) end def escalation self.info['escalation'] && Escalation.find(self.info['escalation']['id']) end def notifier self.info['notifier'] && Notifier.find(self.info['notifier']['id']) end def event self.info['event'] && IncidentEvent.find(self.info['event']['id']) end end ================================================ FILE: app/models/maintenance.rb ================================================ class Maintenance < ApplicationRecord scope :active, -> { t = Time.now; where('start_time <= ? AND ? <= end_time', t, t) } scope :not_expired, -> { where('? <= end_time', Time.now) } scope :expired, -> { where('end_time < ?', Time.now) } belongs_to :topic def filter_regexp Regexp.new(filter) end end ================================================ FILE: app/models/notification_worker.rb ================================================ class NotificationWorker include Sidekiq::Worker def self.enqueue(event:, notifier:) Rails.logger.info "Enqueue NotificationWorker job" self.perform_in(notifier.notify_after_sec, event.id, notifier.id) end def perform(event_id, notifier_id) event = IncidentEvent.find(event_id) notifier = Notifier.find(notifier_id) if notifier.enabled notifier.notify_immediately(event) end end end ================================================ FILE: app/models/notifier.rb ================================================ class Notifier < ApplicationRecord belongs_to :provider, class_name: 'NotifierProvider' belongs_to :user belongs_to :topic validates :provider, presence: true validates :notify_after_sec, numericality: {greater_than_or_equal_to: 5} serialize :settings, JSON after_initialize :set_defaults def set_defaults self.settings ||= {} end def notify(event) NotificationWorker.enqueue(event: event, notifier: self) end def notify_immediately(event) provider.notify(event: event, notifier: self) end end ================================================ FILE: app/models/notifier_provider.rb ================================================ class NotifierProvider < ApplicationRecord serialize :settings, JSON enum kind: [:mailgun, :file, :rails_logger, :hipchat, :twilio, :slack, :datadog, :sns] validates :name, presence: true after_initialize :set_defaults def set_defaults self.settings ||= {} end def concrete_class self.class.const_get("#{kind.to_s.camelize}ConcreteProvider") end def notify(event:, notifier:) concrete_class.new(provider: self, notifier: notifier, event: event).notify end class ConcreteProvider def initialize(provider:, notifier:, event:) @provider = provider @notifier = notifier @event = event end def notify if skip? Rails.logger.info "Notification skipped." else if target_events.include?(kind_of_event) _notify @event.incident.events.create( kind: :notified, info: {notifier: @notifier, event: @event} ) else Rails.logger.info "Notification skipped due to target events (#{target_events} doesn't include #{kind_of_event})" end end end def _notify raise NotImplementedError end def settings @provider.settings.merge(@notifier.settings) end def skip? # or_conditions = [ # { # 'only_japanese_weekday' => true, # 'not_between' => '9:30-18:30', # }, # { # 'not_japanese_weekday' => true, # } # ] return skip_due_to_or_conditions? || skip_due_to_status_of_incident? end def skip_due_to_status_of_incident? if !@event.incident.opened? && !([:acknowledged, :resolved].include?(kind_of_event)) return true end false end def skip_due_to_or_conditions? or_conditions = settings['or_conditions'] return false unless or_conditions matched = or_conditions.any? do |condition| # japanese_weekday holiday = HolidayJp.holiday?(Date.today) || Time.now.saturday? || Time.now.sunday? if holiday && condition['japanese_weekday'] next false end if !holiday && condition['not_japanese_weekday'] next false end # between skip_due_to_between_condition = %w!between not_between!.any? do |k| if s = condition[k] start_time, end_time = s.split('-') start_time = Time.parse(start_time) end_time = Time.parse(end_time) between = start_time < Time.now && Time.now < end_time if (between && k == 'not_between') || (!between && k == 'between') next true end end false end next false if skip_due_to_between_condition true end return !matched end def all_events [:escalated, :escalated_to_me, :opened, :acknowledged, :resolved, :commented] end def target_events settings['events'] && settings['events'].map {|v| v.to_sym } end def body(formats: [:text]) template_names = [kind_of_event, 'default'] template_names.each_with_index do |template_name, i| begin rendered = ApplicationController.new.render_to_string( template: "notifier_providers/#{@provider.kind}/#{template_name}", formats: formats, layout: nil, locals: {event: @event}, ) return rendered.strip rescue ActionView::MissingTemplate raise if template_names.size - 1 == i end end end def kind_of_event if @event.escalated? && @event.escalated_to == @notifier.user :escalated_to_me else @event.kind.to_sym end end end class FileConcreteProvider < ConcreteProvider def _notify end end class RailsLoggerConcreteProvider < ConcreteProvider def _notify Rails.logger.info(body) end def target_events all_events end end class HipchatConcreteProvider < ConcreteProvider def _notify case kind_of_event when :opened color = 'red' when :acknowledged, :escalated color = 'yellow' when :resolved color = 'green' else return end client = HipChat::Client.new(api_token, api_version: api_version) client[room].send('Waker', body, color: color, notify: notify?) end private def api_token settings.fetch('api_token') end def room settings.fetch('room') end def api_version case settings.fetch('api_version') when '2', 'v2' 'v2' when '1', 'v1' 'v1' else 'v2' end end def notify? !!settings['notify'] end def target_events super || [:escalated, :opened, :acknowledged, :resolved] end end class MailgunConcreteProvider < ConcreteProvider def _notify conn = Faraday.new(url: 'https://api.mailgun.net') do |faraday| faraday.request :url_encoded faraday.response :logger faraday.adapter Faraday.default_adapter end conn.basic_auth('api', api_key) response = conn.post "/v2/#{domain}/messages", { from: from, to: to, subject: "[Waker] #{@event.incident.subject}", text: body, html: body(formats: [:html]), } Rails.logger.info "response status: #{response.status}" Rails.logger.info JSON.parse(response.body) end private def api_key settings.fetch('api_key') end def domain from.split('@').last end def from settings.fetch('from') end def to settings.fetch('to') end def target_events super || [:escalated_to_me] end end class TwilioConcreteProvider < ConcreteProvider def _notify options = {} options[:user] = basic_auth_user if basic_auth_user options[:password] = basic_auth_password if basic_auth_password url = Rails.application.routes.url_helpers.twilio_incident_event_url( @event, options ) Twilio::REST::Client.new(account_sid, auth_token).calls.create( from: from, to: to, url: url, ) end def account_sid settings.fetch('account_sid') end def auth_token settings.fetch('auth_token') end def from settings.fetch('from') end def to settings.fetch('to') end def target_events super || [:escalated_to_me] end def basic_auth_user ENV['BASIC_AUTH_USER'] end def basic_auth_password ENV['BASIC_AUTH_PASSWORD'] end end class SlackConcreteProvider < ConcreteProvider def _notify fields = [] acknowledge_url = Rails.application.routes.url_helpers.acknowledge_incident_url(@event.incident, hash: @event.incident.confirmation_hash) resolve_url = Rails.application.routes.url_helpers.resolve_incident_url(@event.incident, hash: @event.incident.confirmation_hash) comment_url = Rails.application.routes.url_helpers.new_incident_comment_url(@event.incident) actions = [] case kind_of_event when :opened color = 'danger' title = 'New incident opened' if buttons_enabled? action_links = "<#{comment_url}|Comment>" actions = [:acknowledge, :resolve] else action_links = "<#{acknowledge_url}|Acknowledge> or <#{resolve_url}|Resolve> | <#{comment_url}|Comment>" end when :acknowledged color = 'warning' title = 'Incident acknowledged' if buttons_enabled? action_links = "<#{comment_url}|Comment>" actions = [:resolve] else action_links = "<#{resolve_url}|Resolve> | <#{comment_url}|Comment>" end when :escalated color = 'warning' title = "Incident escalated to #{@event.escalated_to.name}" if buttons_enabled? action_links = "<#{comment_url}|Comment>" actions = [:acknowledge, :resolve] else action_links = "<#{acknowledge_url}|Acknowledge> or <#{resolve_url}|Resolve> | <#{comment_url}|Comment>" end when :resolved color = 'good' title = 'Incident resolved' action_links = "<#{comment_url}|Comment>" end text = @event.incident.subject if action_links text += " (#{action_links})" end action_types = { acknowledge: { "name" => "response", "text" => "Acknowledge", "type" => "button", "value" => "acknowledge", }, resolve: { "name" => "response", "text" => "Resolve", "type" => "button", "value" => "resolve", "style" => "primary", }, } attachments = [{ "fallback" => "[#{kind_of_event.to_s.capitalize}] #{@event.incident.subject}", "color" => color, "title" => title, "text" => text, "fields" => fields, "callback_id" => "incident.#{@event.incident.id}", "actions" => actions.map {|t| action_types[t] }, }] payload = {'attachments' => attachments} if channel payload['channel'] = channel end url = URI.parse(webhook_url) conn = Faraday.new(url: "#{url.scheme}://#{url.host}") do |faraday| faraday.adapter Faraday.default_adapter end conn.post do |req| req.url url.path req.headers['Content-Type'] = 'application/json' req.body = payload.to_json end end private def webhook_url settings.fetch('webhook_url') end def channel settings['channel'] end def buttons_enabled? settings['enable_buttons'] end def target_events [:escalated, :opened, :acknowledged, :resolved] end end class DatadogConcreteProvider < ConcreteProvider def _notify dog = Dogapi::Client.new(api_key, app_key) res = dog.emit_event(Dogapi::Event.new( @event.incident.description, { msg_title: @event.incident.subject, alert_type: alert_type, tags: tags, source_type_name: source_type_name, } )) Rails.logger.info res end private def api_key settings.fetch('api_key') end def app_key settings.fetch('app_key') end def alert_type settings['alert_type'] || 'error' end def tags settings['tags'] || 'waker' end def source_type_name settings['source_type_name'] || 'waker' end def target_events [:opened] end end class SnsConcreteProvider < ConcreteProvider def _notify aws_config = {} aws_config[:region] = region if region aws_config[:access_key_id] = access_key_id if access_key_id aws_config[:secret_access_key] = secret_access_key if secret_access_key sns = Aws::SNS::Client.new(aws_config) res = sns.publish( topic_arn: topic_arn, message: @event.incident.description, subject: @event.incident.subject, message_attributes: { 'kind_of_event' => { data_type: 'String', string_value: kind_of_event.to_s, } } ) Rails.logger.info res end private def topic_arn settings.fetch('topic_arn') end def region settings['region'] end def access_key_id settings['access_key_id'] end def secret_access_key settings['secret_access_key'] end def target_events all_events end end end ================================================ FILE: app/models/topic.rb ================================================ class Topic < ApplicationRecord enum kind: [:api] belongs_to :escalation_series has_many :incidents validates :name, presence: true validates :kind, presence: true validates :escalation_series, presence: true def in_maintenance?(*bodies) maints = Maintenance.active.where(topic: self) maints.any? do |m| if m.filter.blank? true else r = m.filter_regexp bodies.any? do |body| !!r.match(body) end end end end end ================================================ FILE: app/models/user.rb ================================================ require 'securerandom' class User < ApplicationRecord scope :active, -> { where(active: true) } has_many :notifiers serialize :credentials, JSON validates :name, presence: true before_save :set_defaults def self.find_or_create_from_auth_hash(auth_hash) user = self.find_by(provider: auth_hash[:provider], uid: auth_hash[:uid]) return user if user user = self.find_by(email: auth_hash[:info][:email]) if user # email is deprecated user.update!(provider: auth_hash[:provider], uid: auth_hash[:uid], email: nil) return user end self.create!(provider: auth_hash[:provider], uid: auth_hash[:uid], name: auth_hash[:info][:name]) end def update_credentials_from_auth_hash(auth_hash) self.update!(credentials: auth_hash.fetch(:credentials)) end private def set_defaults self.login_token ||= SecureRandom.hex end end ================================================ FILE: app/views/comments/_form.html.erb ================================================ <%= form_for([@incident, @comment]) do |f| %> <% if @comment.errors.any? %>
Incident:
<%= @incident.subject %>
Incident:
<%= @incident.subject %>
| Comment | |||
|---|---|---|---|
| <%= comment.comment %> (<%= comment.user.name %>) | <%= link_to 'Show', [@incident, comment] %> | <%= link_to 'Edit', edit_incident_comment_path(@incident, comment) %> | <%= link_to 'Destroy', [@incident, comment], method: :delete, data: { confirm: 'Are you sure?' } %> |
Incident:
<%= @incident.subject %>
Comment:
<%= auto_link(@comment.comment, :html => { :target => '_blank' }) %>
User:
<%= @comment.user.name %><%= link_to 'Edit', edit_incident_comment_path(@incident, @comment) %> | <%= link_to 'Back', incident_comments_path %> ================================================ FILE: app/views/comments/show.json.jbuilder ================================================ json.extract! @comment, @user, :id, :created_at, :updated_at ================================================ FILE: app/views/escalation_series/_form.html.erb ================================================ <%= form_for(@escalation_series) do |f| %> <% if @escalation_series.errors.any? %>
| Name | |||
|---|---|---|---|
| <%= escalation_series.name %> | <%= link_to 'Show', escalation_series %> | <%= link_to 'Edit', edit_escalation_series_path(escalation_series) %> | <%= link_to 'Destroy', escalation_series, method: :delete, data: { confirm: 'Are you sure?' } %> |
Name: <%= @escalation_series.name %>
| <%= escalation.escalate_to.name %> | escalate after <%= escalation.escalate_after_sec %> sec |
| Escalation Series | Escalate to | Escalate after sec | |||
|---|---|---|---|---|---|
| <%= escalation.escalation_series.name %> | <%= escalation.escalate_to.name %> | <%= escalation.escalate_after_sec %> | <%= link_to 'Show', escalation %> | <%= link_to 'Edit', edit_escalation_path(escalation) %> | <%= link_to 'Destroy', escalation, method: :delete, data: { confirm: 'Are you sure?' } %> |
Escalation series: <%= @escalation.escalation_series.name %>
Escalate to: <%= @escalation.escalate_to.name %>
Escalate after sec: <%= @escalation.escalate_after_sec %>
<%= link_to 'Edit', edit_escalation_path(@escalation) %> | <%= link_to 'Back', escalations_path %> ================================================ FILE: app/views/escalations/show.json.jbuilder ================================================ json.extract! @escalation, :id, :escalate_to_id, :escalate_after_sec, :created_at, :updated_at ================================================ FILE: app/views/home/index.html.erb ================================================Find me in app/views/home/index.html.erb
================================================ FILE: app/views/incident_events/twilio.html.erb ================================================Find me in app/views/incident_events/twilio.html.erb
================================================ FILE: app/views/incidents/_form.html.erb ================================================ <%= form_for(@incident) do |f| %> <% if @incident.errors.any? %>| Status | Subject | Topic | Occured at | ||||
|---|---|---|---|---|---|---|---|
| <% if incident.opened? %> Opened <% elsif incident.acknowledged? %> Acknowledged <% elsif incident.resolved? %> Resolved <% end %> | <%= incident.subject %> | <%= incident.topic.name %> | <%= incident.occured_at %> | <%= link_to 'Show', incident %> | <%= link_to 'Ack', acknowledge_incident_path(incident, hash: incident.confirmation_hash) %> | <%= link_to 'Resolve', resolve_incident_path(incident, hash: incident.confirmation_hash) %> | <%= link_to 'Comment', new_incident_comment_path(incident) %> |
<% incident.comments.each_with_index do |comment, i| %>
<%= auto_link(comment.comment, :html => { :target => '_blank' }) %>
<% end %>
|
|||||||
Subject: <%= @incident.subject %>
Description:
<%= @incident.description %>
Topic: <%= @incident.topic.name %>
Occured at: <%= @incident.occured_at %>
| Time | Event | Description |
|---|---|---|
| <%= event.created_at %> | <%= event.kind %> | <% if event.escalated? %> to <%= link_to(event.escalated_to.name, event.escalated_to) %> <% elsif event.notified? %> notified by <%= link_to('notifier', event.notifier) %> <% end %> |
Comment:
| Time | Comment |
|---|---|
| <%= comment.created_at %> | <%= comment.comment %> (<%= comment.user.name %>) |
Expired maintanances are not shown
| Topic | Filter Regexp | Start time | End time | |||
|---|---|---|---|---|---|---|
| <%= maintenance.topic.name %> | <%= maintenance.filter %> |
<%= maintenance.start_time %> | <%= maintenance.end_time %> | <%= link_to 'Show', maintenance %> | <%= link_to 'Edit', edit_maintenance_path(maintenance) %> | <%= link_to 'Destroy', maintenance, method: :delete, data: { confirm: 'Are you sure?' } %> |
<%= notice %>
Topic: <%= @maintenance.topic.name %>
Filter (regexp):
<%= @maintenance.filter %>
Start time: <%= @maintenance.start_time %>
End time: <%= @maintenance.end_time %>
<%= link_to 'Edit', edit_maintenance_path(@maintenance) %> | <%= link_to 'Back', maintenances_path %> ================================================ FILE: app/views/maintenances/show.json.jbuilder ================================================ json.extract! @maintenance, :id, :topic_id, :start_time, :end_time, :created_at, :updated_at ================================================ FILE: app/views/notifier_providers/_form.html.erb ================================================ <%= form_for(@notifier_provider) do |f| %> <% if @notifier_provider.errors.any? %>| Name | Kind | Settings | |||
|---|---|---|---|---|---|
| <%= notifier_provider.name %> | <%= notifier_provider.kind %> | <%= notifier_provider.settings %> | <%= link_to 'Show', notifier_provider %> | <%= link_to 'Edit', edit_notifier_provider_path(notifier_provider) %> | <%= link_to 'Destroy', notifier_provider, method: :delete, data: { confirm: 'Are you sure?' } %> |
<%= event.incident.description %>
Name: <%= @notifier_provider.name %>
Kind: <%= @notifier_provider.kind %>
Settings: <%= @notifier_provider.settings %>
<%= link_to 'Edit', edit_notifier_provider_path(@notifier_provider) %> | <%= link_to 'Back', notifier_providers_path %> ================================================ FILE: app/views/notifier_providers/show.json.jbuilder ================================================ json.extract! @notifier_provider, :id, :name, :kind, :settings, :created_at, :updated_at ================================================ FILE: app/views/notifiers/_form.html.erb ================================================ <%= form_for(@notifier) do |f| %> <% if @notifier.errors.any? %>| User | Topic | Provider | Settings | Notify after sec | Enabled | |||
|---|---|---|---|---|---|---|---|---|
| <%= notifier.user && notifier.user.name %> | <%= notifier.topic && notifier.topic.name %> | <%= notifier.provider.name %> | <%= notifier.settings %> | <%= notifier.notify_after_sec %> | <%= notifier.enabled %> | <%= link_to 'Show', notifier %> | <%= link_to 'Edit', edit_notifier_path(notifier) %> | <%= link_to 'Destroy', notifier, method: :delete, data: { confirm: 'Are you sure?' } %> |
Provider: <%= @notifier.provider.name %>
User: <%= @notifier.user && @notifier.user.name %>
Topic: <%= @notifier.topic && @notifier.topic.name %>
Settings: <%= @notifier.settings %>
Notify after sec: <%= @notifier.notify_after_sec %>
Enabled: <%= @notifier.enabled %>
<%= link_to 'Edit', edit_notifier_path(@notifier) %> | <%= link_to 'Back', notifiers_path %> ================================================ FILE: app/views/notifiers/show.json.jbuilder ================================================ json.extract! @notifier, :id, :settings, :provider_id, :provider, :topic_id, :user_id, :notify_after_sec, :created_at, :updated_at ================================================ FILE: app/views/sessions/create.html.erb ================================================Find me in app/views/sessions/create.html.erb
================================================ FILE: app/views/slack/interactive.html.erb ================================================Find me in app/views/slack/interactive.html.erb
================================================ FILE: app/views/topics/_form.html.erb ================================================ <%= form_for(@topic) do |f| %> <% if @topic.errors.any? %>| Name | Kind | Escalation Series | Enabled | |||
|---|---|---|---|---|---|---|
| <%= topic.name %> | <%= topic.kind %> | <%= topic.escalation_series.name %> | <%= topic.enabled ? 'Yes' : 'No' %> | <%= link_to 'Show', topic %> | <%= link_to 'Edit', edit_topic_path(topic) %> | <%= link_to 'Destroy', topic, method: :delete, data: { confirm: 'Are you sure?' } %> |
Name: <%= @topic.name %>
Kind: <%= @topic.kind %>
Escalation Series: <%= @topic.escalation_series.name %>
Enabled: <%= @topic.enabled ? 'Yes' : 'No' %>
Mailgun Endpoint:
<%= mailgun_topic_url(@topic, format: :json) %>
Mackerel Endpoint:
<%= mackerel_topic_url(@topic, format: :json) %>
AlertManagerEndpoint:
<%= alertmanager_topic_url(@topic, format: :json) %>
SlackEndpoint:
<%= slack_topic_url(@topic, format: :json) %><%= link_to 'Edit', edit_topic_path(@topic) %> | <%= link_to 'Back', topics_path %> ================================================ FILE: app/views/topics/show.json.jbuilder ================================================ json.extract! @topic, :id, :name, :type, :created_at, :updated_at ================================================ FILE: app/views/users/_form.html.erb ================================================ <%= form_for(@user) do |f| %> <% if @user.errors.any? %>
| Name | Status | ||||
|---|---|---|---|---|---|
| <%= user.name %> | <%- unless user.active -%> Deactivated <%- end -%> | <%= link_to 'Show', user %> | <%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %> | <%- if user.active -%><%= link_to 'Deactivate', deactivation_user_path(user), method: :patch, data: { confirm: 'Are you sure?' } %> | <%- else -%><%= link_to 'Activate', activation_user_path(user), method: :patch, data: { confirm: 'Are you sure?' } %> | <%- end -%>
Name: <%= @user.name %>
Provider: <%= @user.provider %>
UID: <%= @user.uid %>
<%= link_to 'Back', users_path %> ================================================ FILE: app/views/users/show.json.jbuilder ================================================ json.extract! @user, :id, :name, :created_at, :updated_at ================================================ FILE: bin/bundle ================================================ #!/usr/bin/env ruby ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) load Gem.bin_path('bundler', 'bundle') ================================================ FILE: bin/rails ================================================ #!/usr/bin/env ruby begin load File.expand_path("../spring", __FILE__) rescue LoadError end APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' ================================================ FILE: bin/rake ================================================ #!/usr/bin/env ruby begin load File.expand_path("../spring", __FILE__) rescue LoadError end require_relative '../config/boot' require 'rake' Rake.application.run ================================================ FILE: bin/setup ================================================ #!/usr/bin/env ruby require 'pathname' # path to your application root. APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) Dir.chdir APP_ROOT do # This script is a starting point to setup your application. # Add necessary setup steps to this file: puts "== Installing dependencies ==" system "gem install bundler --conservative" system "bundle check || bundle install" # puts "\n== Copying sample files ==" # unless File.exist?("config/database.yml") # system "cp config/database.yml.sample config/database.yml" # end puts "\n== Preparing database ==" system "bin/rake db:setup" puts "\n== Removing old logs and tempfiles ==" system "rm -f log/*" system "rm -rf tmp/cache" puts "\n== Restarting application server ==" system "touch tmp/restart.txt" end ================================================ FILE: bin/spring ================================================ #!/usr/bin/env ruby # This file loads spring without using Bundler, in order to be fast # It gets overwritten when you run the `spring binstub` command unless defined?(Spring) require "rubygems" require "bundler" if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) ENV["GEM_HOME"] = "" Gem.paths = ENV gem "spring", match[1] require "spring/binstub" end end ================================================ FILE: config/application.rb ================================================ require File.expand_path('../boot', __FILE__) # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" require "sprockets/railtie" # require "rails/test_unit/railtie" # Require the gems listed in Gemfile, including any gems # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) module Waker class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. if time_zone = ENV['TIME_ZONE'] config.time_zone = time_zone end # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de # Do not swallow errors in after_commit/after_rollback callbacks. config.middleware.use Rack::Health config.middleware.use OmniAuth::Builder do config = {} config[:hd] = ENV['GOOGLE_DOMAIN'] if ENV['GOOGLE_DOMAIN'] provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"], config.merge(scope: 'userinfo.profile,userinfo.email,calendar', name: 'google_oauth2_with_calendar', access_type: 'offline', approval_prompt: 'force', prompt: 'consent') end end end ================================================ FILE: config/boot.rb ================================================ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) require 'bundler/setup' # Set up gems listed in the Gemfile. ================================================ FILE: config/database.yml ================================================ production: adapter: mysql2 database: waker encoding: utf8mb4 username : <%= ENV['MYSQL_USER'] || 'root' %> password: <%= ENV['MYSQL_PASSWORD'] %> host: <%= ENV['MYSQL_HOST'] || 'localhost' %> development: adapter: mysql2 database: waker_development encoding: utf8mb4 username : <%= ENV['MYSQL_USER'] || 'root' %> password: <%= ENV['MYSQL_PASSWORD'] %> host: <%= ENV['MYSQL_HOST'] || 'localhost' %> test: adapter: mysql2 database: waker_test encoding: utf8mb4 username : <%= ENV['MYSQL_USER'] || 'root' %> password: <%= ENV['MYSQL_PASSWORD'] %> host: <%= ENV['MYSQL_HOST'] || 'localhost' %> ================================================ FILE: config/environment.rb ================================================ # Load the Rails application. require File.expand_path('../application', __FILE__) # Initialize the Rails application. Rails.application.initialize! ================================================ FILE: config/environments/development.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false # Do not eager load code on boot. config.eager_load = false # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Don't care if the mailer can't send. config.action_mailer.raise_delivery_errors = false # Print deprecation notices to the Rails logger. config.active_support.deprecation = :log # Raise an error on page load if there are pending migrations. config.active_record.migration_error = :page_load # Debug mode disables concatenation and preprocessing of assets. # This option may cause significant delays in view rendering with a large # number of complex assets. config.assets.debug = true # Asset digests allow you to set far-future HTTP expiration dates on all assets, # yet still be able to expire them through the digest params. config.assets.digest = true # Adds additional error checking when serving assets at runtime. # Checks for improperly declared sprockets dependencies. # Raises helpful error messages. config.assets.raise_runtime_errors = true # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end Rails.application.routes.default_url_options[:host] = 'localhost:3000' ================================================ FILE: config/environments/production.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # Code is not reloaded between requests. config.cache_classes = true # Eager load code on boot. This eager loads most of Rails and # your application in memory, allowing both threaded web servers # and those relying on copy on write to perform better. # Rake tasks automatically ignore this option for performance. config.eager_load = true # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true # Enable Rack::Cache to put a simple HTTP cache in front of your application # Add `rack-cache` to your Gemfile before enabling this. # For large-scale production use, consider using a caching reverse proxy like # NGINX, varnish or squid. # config.action_dispatch.rack_cache = true # Disable serving static files from the `/public` folder by default since # Apache or NGINX already handles this. config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false # Asset digests allow you to set far-future HTTP expiration dates on all assets, # yet still be able to expire them through the digest params. config.assets.digest = true # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true # Use the lowest log level to ensure availability of diagnostic information # when problems arise. config.log_level = :debug # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) if log_dir = ENV['LOG_DIR'] config.logger = ::Logger.new(File.expand_path('production.log', log_dir)) end # Use a different cache store in production. # config.cache_store = :mem_cache_store # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = 'http://assets.example.com' # Ignore bad email addresses and do not raise email delivery errors. # Set this to true and configure the email server for immediate delivery to raise delivery errors. # config.action_mailer.raise_delivery_errors = false # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation cannot be found). config.i18n.fallbacks = true # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify # Use default logging formatter so that PID and timestamp are not suppressed. config.log_formatter = ::Logger::Formatter.new if ENV["RAILS_LOG_TO_STDOUT"].present? logger = ActiveSupport::Logger.new(STDOUT) logger.formatter = config.log_formatter config.logger = ActiveSupport::TaggedLogging.new(logger) end # Do not dump schema after migrations. config.active_record.dump_schema_after_migration = false end ================================================ FILE: config/environments/test.rb ================================================ Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! config.cache_classes = true # Do not eager load code on boot. This avoids loading your whole application # just for the purpose of running a single test. If you are using a tool that # preloads Rails for running tests, you may have to set it to true. config.eager_load = false # Configure static file server for tests with Cache-Control for performance. config.serve_static_files = true config.static_cache_control = 'public, max-age=3600' # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment. config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. config.action_mailer.delivery_method = :test # Randomize the order test cases are executed. config.active_support.test_order = :random # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr # Raises error for missing translations # config.action_view.raise_on_missing_translations = true end ================================================ FILE: config/initializers/assets.rb ================================================ # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. Rails.application.config.assets.version = '1.0' # Add additional assets to the asset load path # Rails.application.config.assets.paths << Emoji.images_path # Precompile additional assets. # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # Rails.application.config.assets.precompile += %w( search.js ) ================================================ FILE: config/initializers/backtrace_silencers.rb ================================================ # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. # Rails.backtrace_cleaner.remove_silencers! ================================================ FILE: config/initializers/cookies_serializer.rb ================================================ # Be sure to restart your server when you modify this file. Rails.application.config.action_dispatch.cookies_serializer = :json ================================================ FILE: config/initializers/field_with_errors.rb ================================================ Rails.application.configure do config.action_view.field_error_proc = lambda do |html_tag, instance| %Q{You may have mistyped the address or the page may have moved.
If you are the application owner check the logs for more information.
Maybe you tried to change something you didn't have access to.
If you are the application owner check the logs for more information.
If you are the application owner check the logs for more information.