├── tmp └── .gitkeep ├── VERSION ├── vendor ├── plugins │ └── .gitkeep ├── gems │ └── dotenv-2.0.1 │ │ ├── lib │ │ ├── dotenv-rails.rb │ │ └── dotenv │ │ │ ├── version.rb │ │ │ ├── tasks.rb │ │ │ ├── rails-now.rb │ │ │ ├── environment.rb │ │ │ ├── cli.rb │ │ │ └── substitutions │ │ │ └── variable.rb │ │ ├── bin │ │ └── dotenv │ │ ├── dotenv-rails.gemspec │ │ ├── dotenv.gemspec │ │ └── LICENSE └── assets │ ├── images │ ├── json-editor │ │ ├── add.png │ │ └── delete.png │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png │ ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff │ ├── javascripts │ ├── bootstrap.js │ └── jquery.serializeObject.js │ └── stylesheets │ ├── bootstrap │ ├── _wells.scss │ ├── _component-animations.scss │ ├── _breadcrumbs.scss │ ├── _close.scss │ ├── _thumbnails.scss │ ├── _utilities.scss │ ├── _jumbotron.scss │ ├── _media.scss │ ├── _pager.scss │ └── bootstrap.scss │ └── jquery.json-editor.css ├── doc ├── configuration │ ├── dotenv.md │ └── procfile.md ├── imgs │ ├── peaks.png │ ├── diagram.png │ ├── the-name.png │ ├── new-agent.png │ ├── my-locations.png │ └── your-agents.png ├── manual │ └── README.md ├── heroku │ └── update.md ├── README.md └── deployment │ └── unicorn │ └── production.rb ├── .openshift ├── cron │ ├── daily │ │ └── .gitignore │ ├── hourly │ │ └── .gitignore │ ├── minutely │ │ └── .gitignore │ ├── monthly │ │ └── .gitignore │ ├── weekly │ │ ├── chrono.dat │ │ ├── chronograph │ │ ├── jobs.deny │ │ ├── jobs.allow │ │ └── README │ └── README.cron ├── action_hooks │ ├── post_deploy │ ├── build │ ├── post_stop_ruby-2.0 │ ├── pre_stop_ruby-2.0 │ ├── post_start_ruby-2.0 │ ├── pre_start_ruby-2.0 │ ├── pre_build │ └── deploy └── markers │ └── README ├── app ├── helpers │ ├── logs_helper.rb │ ├── markdown_helper.rb │ ├── users_helper.rb │ └── scenario_helper.rb ├── assets │ ├── stylesheets │ │ ├── jobs.css.scss │ │ ├── scenarios.css.scss │ │ ├── diagram.css.scss │ │ └── tables.css.scss │ ├── images │ │ ├── odin.jpg │ │ └── spinner-arrows.gif │ └── javascripts │ │ ├── ace.js.coffee │ │ ├── application.js.coffee │ │ ├── pages │ │ ├── scenario-form-page.js.coffee │ │ ├── scenario-show-page.js.coffee │ │ └── user-credential-page.js.coffee │ │ ├── components │ │ ├── json-editor.js.coffee.erb │ │ ├── search.js.coffee │ │ └── core.js.coffee │ │ ├── diagram.js.coffee │ │ └── map_marker.js.coffee ├── views │ ├── home │ │ ├── index.html.erb │ │ ├── about.html.erb │ │ └── _signed_out_index.html.erb │ ├── admin │ │ └── users │ │ │ ├── edit.html.erb │ │ │ ├── new.html.erb │ │ │ └── _form.html.erb │ ├── devise │ │ ├── mailer │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── shared │ │ │ └── _links.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── passwords │ │ │ └── new.html.erb │ │ └── registrations │ │ │ └── _common_registration_fields.html.erb │ ├── system_mailer │ │ ├── send_message.text.erb │ │ └── send_message.html.erb │ ├── agents │ │ ├── _mini_action_menu.html.erb │ │ ├── agent_views │ │ │ ├── webhook_agent │ │ │ │ └── _show.html.erb │ │ │ └── data_output_agent │ │ │ │ └── _show.html.erb │ │ ├── _oauth_dropdown.html.erb │ │ ├── new.html.erb │ │ ├── dry_runs │ │ │ ├── index.html.erb │ │ │ └── create.html.erb │ │ ├── edit.html.erb │ │ └── index.html.erb │ ├── layouts │ │ └── _messages.html.erb │ ├── scenarios │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ └── _enable_agents_modal.html.erb │ ├── user_credentials │ │ ├── edit.html.erb │ │ └── new.html.erb │ └── scenario_imports │ │ ├── _step_one.html.erb │ │ └── new.html.erb ├── jobs │ ├── agent_cleanup_expired_job.rb │ ├── agent_run_schedule_job.rb │ ├── agent_check_job.rb │ ├── agent_receive_job.rb │ └── agent_propagate_job.rb ├── models │ ├── scenario_membership.rb │ ├── control_link.rb │ ├── link.rb │ ├── contact.rb │ ├── user_credential.rb │ └── agents │ │ └── read_file_agent.rb ├── controllers │ ├── home_controller.rb │ ├── users │ │ └── registrations_controller.rb │ ├── logs_controller.rb │ ├── diagrams_controller.rb │ ├── scenario_imports_controller.rb │ ├── omniauth_callbacks_controller.rb │ ├── services_controller.rb │ ├── worker_status_controller.rb │ └── events_controller.rb ├── concerns │ ├── has_guid.rb │ ├── inheritance_tracking.rb │ ├── working_helpers.rb │ ├── oauthable.rb │ ├── tumblr_concern.rb │ ├── weibo_concern.rb │ ├── dropbox_concern.rb │ └── markdown_class_attributes.rb ├── validators │ └── owned_by_validator.rb ├── importers │ └── default_scenario_importer.rb └── mailers │ └── system_mailer.rb ├── config ├── initializers │ ├── requires.rb │ ├── ar_mysql_column_charset.rb │ ├── timezone.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── aws.rb │ ├── sanitizer.rb │ ├── filter_parameter_logging.rb │ ├── action_mailer.rb │ ├── liquid.rb │ ├── secret_token.rb │ ├── silence_worker_status_logger.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── mysqlpls.rb │ ├── inflections.rb │ ├── assets.rb │ └── delayed_job.rb ├── deploy │ └── production.rb ├── boot.rb ├── environment.rb ├── unicorn.rb.example └── locales │ └── en.yml ├── docker ├── multi-process │ ├── Makefile │ ├── scripts │ │ └── standalone-packages │ ├── Dockerfile │ └── docker-compose.yml ├── single-process │ ├── environment.yml │ ├── Dockerfile │ └── docker-compose.yml └── README.md ├── lib ├── tasks │ ├── ar_mysql_column_charset.rake │ ├── database_test.rake │ └── rspec.rake ├── delayed_job_worker.rb ├── ar_mysql_column_charset.rb ├── json_with_indifferent_access.rb ├── rdbms_functions.rb └── time_tracker.rb ├── .graphviz ├── public ├── favicon.ico ├── android-chrome-36x36.png ├── android-chrome-48x48.png ├── android-chrome-72x72.png ├── android-chrome-96x96.png ├── android-chrome-144x144.png ├── android-chrome-192x192.png ├── apple-touch-icon-57x57.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-72x72.png ├── apple-touch-icon-76x76.png ├── apple-touch-icon-114x114.png ├── apple-touch-icon-120x120.png ├── apple-touch-icon-144x144.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── robots.txt ├── 500.html ├── 422.html ├── 404.html └── manifest.json ├── media ├── huginn-logo.png ├── iOS │ ├── Icon-40.png │ ├── Icon-76.png │ ├── Icon-40@2x.png │ ├── Icon-60@2x.png │ ├── Icon-60@3x.png │ ├── Icon-76@2x.png │ ├── Icon-Small.png │ ├── Icon-Small@2x.png │ ├── Icon-Small@3x.png │ ├── iTunesArtwork.png │ └── iTunesArtwork@2x.png ├── huginn-icon-16.png └── huginn-icon-64.png ├── spec ├── data_fixtures │ ├── private.key │ ├── stubhub_data.json │ ├── adioso_parse.json │ ├── witai.json │ ├── imap2.eml │ ├── imap1.eml │ ├── basecamp.json │ ├── urlTest.html │ └── services │ │ └── 37signals.json ├── fixtures │ ├── links.yml │ ├── events.yml │ ├── users.yml │ ├── services.yml │ ├── user_credentials.yml │ ├── agent_logs.yml │ ├── scenarios.yml │ ├── scenario_memberships.yml │ ├── user_credentials.json │ └── multiple_user_credentials.json ├── support │ ├── vcr_support.rb │ ├── shared_examples │ │ ├── has_guid.rb │ │ └── file_handling_consumer.rb │ └── spec_helpers.rb ├── helpers │ ├── markdown_helper_spec.rb │ ├── jobs_helper_spec.rb │ └── scenario_helper_spec.rb ├── concerns │ ├── inheritance_tracking_spec.rb │ └── liquid_droppable_spec.rb ├── env.test ├── features │ ├── edit_an_agent_spec.rb │ └── toggle_visibility_of_disabled_agents.rb ├── models │ ├── agents │ │ ├── weather_agent_spec.rb │ │ ├── pdf_agent_spec.rb │ │ ├── weibo_user_agent_spec.rb │ │ └── commander_agent_spec.rb │ ├── concerns │ │ └── oauthable.rb │ └── user_credential_spec.rb ├── lib │ ├── time_tracker_spec.rb │ └── delayed_job_worker_spec.rb ├── jobs │ └── agent_propagate_job_spec.rb ├── db │ └── seeds │ │ └── admin_and_default_scenario_spec.rb ├── controllers │ ├── admin │ │ └── users_controller_spec.rb │ ├── scenario_imports_controller_spec.rb │ ├── omniauth_callbacks_controller_spec.rb │ ├── users │ │ └── registrations_controller_spec.rb │ └── services_controller_spec.rb └── capybara_helper.rb ├── Procfile.CF ├── bin ├── bundle ├── rspec ├── rake ├── rails ├── threaded.rb ├── schedule.rb ├── pre_runner_boot.rb ├── agent_runner.rb ├── twitter_stream.rb ├── decrypt_backup.rb └── spring ├── db ├── migrate │ ├── 20130124050117_add_indexes.rb │ ├── 20121231170705_add_memory_to_agents.rb │ ├── 20160419150930_add_icon_to_scenarios.rb │ ├── 20140525150040_add_service_id_to_agents.rb │ ├── 20140809211540_remove_service_index_on_user_id.rb │ ├── 20120728215449_add_admin_to_users.rb │ ├── 20130509053743_add_last_webhook_at_to_agents.rb │ ├── 20130107050049_add_invitation_code_to_users.rb │ ├── 20140403043556_add_disabled_to_agent.rb │ ├── 20131105063248_add_expires_at_to_events.rb │ ├── 20131222211558_add_keep_events_for_to_agents.rb │ ├── 20140210062747_add_mode_to_user_credentials.rb │ ├── 20160302095413_add_deactivated_at_to_users.rb │ ├── 20140820003139_add_tag_color_to_scenarios.rb │ ├── 20140811200922_add_uid_column_to_services.rb │ ├── 20160307084729_add_deactivated_to_agents.rb │ ├── 20150808115436_remove_requirement_from_users_invitation_code.rb │ ├── 20140602014917_add_indices_to_scenarios.rb │ ├── 20140408150825_rename_webhook_to_web_request.rb │ ├── 20121222074732_create_links.rb │ ├── 20140216201250_add_propagate_immediately_to_agent.rb │ ├── 20140509170443_create_scenario_memberships.rb │ ├── 20140531232016_add_fields_to_scenarios.rb │ ├── 20140509170420_create_scenarios.rb │ ├── 20140906030139_set_events_count_default.rb │ ├── 20140605032822_add_guid_to_agents.rb │ ├── 20130819160603_create_agent_logs.rb │ ├── 20150507153436_update_keep_events_for_to_be_in_seconds.rb │ ├── 20140901143732_add_control_links.rb │ ├── 20140121075418_create_user_credentials.rb │ ├── 20160224120316_add_mode_option_to_ftpsite_agents.rb │ ├── 20160405072512_post_agent_set_event_header_style.rb │ ├── 20121216025930_create_events.rb │ ├── 20120919061122_enable_lockable_strategy_for_devise.rb │ ├── 20120919063304_add_username_to_users.rb │ ├── 20140127164931_change_handler_to_medium_text.rb │ ├── 20160423163416_add_xml_namespace_option_to_data_output_agents.rb │ ├── 20150219213604_add_type_option_attribute_to_pushbullet_agents.rb │ ├── 20140515211100_create_services.rb │ ├── 20121220053905_create_agents.rb │ ├── 20160301113717_add_confirmable_attributes_to_users.rb │ ├── 20131227000021_add_cached_dates_to_agent.rb │ ├── 20130126080736_change_memory_to_long_text.rb │ ├── 20140722131220_convert_efa_skip_agent.rb │ ├── 20140730005210_convert_efa_skip_created_at.rb │ ├── 20140603104211_rename_digest_email_to_email_digest.rb │ ├── 20160307085545_warn_about_duplicate_usernames.rb │ ├── 20140213053001_add_event_id_at_creation_to_links.rb │ ├── 20140723110551_adopt_xpath_in_website_agent.rb │ └── 20160108221620_website_agent_does_not_use_event_url.rb ├── seeds.rb └── seeds │ └── seeder.rb ├── config.ru ├── .buildpacks ├── script ├── delayed_job └── rails ├── Capfile ├── Rakefile ├── deployment ├── heroku │ └── Procfile.heroku └── logrotate │ └── huginn ├── manifest.yml.sample ├── nitrous.json ├── .gitignore ├── .dockerignore ├── app.json ├── LICENSE └── Guardfile /tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3 2 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/configuration/dotenv.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.openshift/cron/daily/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.openshift/cron/hourly/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /doc/configuration/procfile.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.openshift/cron/minutely/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.openshift/cron/monthly/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/logs_helper.rb: -------------------------------------------------------------------------------- 1 | module LogsHelper 2 | end 3 | -------------------------------------------------------------------------------- /config/initializers/requires.rb: -------------------------------------------------------------------------------- 1 | require 'pp' 2 | HuginnAgent.require! -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv-rails.rb: -------------------------------------------------------------------------------- 1 | require "dotenv/rails" 2 | -------------------------------------------------------------------------------- /docker/multi-process/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | docker build -t cantino/huginn . 3 | -------------------------------------------------------------------------------- /lib/tasks/ar_mysql_column_charset.rake: -------------------------------------------------------------------------------- 1 | require 'ar_mysql_column_charset' 2 | -------------------------------------------------------------------------------- /.graphviz: -------------------------------------------------------------------------------- 1 | http://www.graphviz.org/pub/graphviz/stable/SOURCES/graphviz-2.38.0.tar.gz 2 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/chrono.dat: -------------------------------------------------------------------------------- 1 | Time And Relative D...n In Execution (Open)Shift! 2 | -------------------------------------------------------------------------------- /config/initializers/ar_mysql_column_charset.rb: -------------------------------------------------------------------------------- 1 | require 'ar_mysql_column_charset' 2 | -------------------------------------------------------------------------------- /doc/imgs/peaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/doc/imgs/peaks.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/favicon.ico -------------------------------------------------------------------------------- /app/assets/stylesheets/jobs.css.scss: -------------------------------------------------------------------------------- 1 | .big-modal-dialog { 2 | width: 90% !important; 3 | } -------------------------------------------------------------------------------- /doc/imgs/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/doc/imgs/diagram.png -------------------------------------------------------------------------------- /doc/imgs/the-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/doc/imgs/the-name.png -------------------------------------------------------------------------------- /media/huginn-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/huginn-logo.png -------------------------------------------------------------------------------- /media/iOS/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-40.png -------------------------------------------------------------------------------- /media/iOS/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-76.png -------------------------------------------------------------------------------- /doc/imgs/new-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/doc/imgs/new-agent.png -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/version.rb: -------------------------------------------------------------------------------- 1 | module Dotenv 2 | VERSION = "2.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /app/assets/images/odin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/app/assets/images/odin.jpg -------------------------------------------------------------------------------- /doc/imgs/my-locations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/doc/imgs/my-locations.png -------------------------------------------------------------------------------- /doc/imgs/your-agents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/doc/imgs/your-agents.png -------------------------------------------------------------------------------- /media/huginn-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/huginn-icon-16.png -------------------------------------------------------------------------------- /media/huginn-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/huginn-icon-64.png -------------------------------------------------------------------------------- /media/iOS/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-40@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-60@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-60@3x.png -------------------------------------------------------------------------------- /media/iOS/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-76@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-Small.png -------------------------------------------------------------------------------- /media/iOS/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-Small@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/Icon-Small@3x.png -------------------------------------------------------------------------------- /media/iOS/iTunesArtwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/iTunesArtwork.png -------------------------------------------------------------------------------- /.openshift/cron/weekly/chronograph: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "`date`: `cat $(dirname \"$0\")/chrono.dat`" 4 | -------------------------------------------------------------------------------- /media/iOS/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/media/iOS/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /public/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/android-chrome-36x36.png -------------------------------------------------------------------------------- /public/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/android-chrome-48x48.png -------------------------------------------------------------------------------- /public/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/android-chrome-72x72.png -------------------------------------------------------------------------------- /public/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/android-chrome-96x96.png -------------------------------------------------------------------------------- /spec/data_fixtures/private.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/spec/data_fixtures/private.key -------------------------------------------------------------------------------- /public/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/android-chrome-144x144.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /app/assets/images/spinner-arrows.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/app/assets/images/spinner-arrows.gif -------------------------------------------------------------------------------- /public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/bin/dotenv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "dotenv/cli" 4 | Dotenv::CLI.new(ARGV).run 5 | -------------------------------------------------------------------------------- /vendor/assets/images/json-editor/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/images/json-editor/add.png -------------------------------------------------------------------------------- /Procfile.CF: -------------------------------------------------------------------------------- 1 | web: bundle exec rake db:migrate && bundle exec rails server -p $PORT 2 | jobs: bundle exec rails runner bin/threaded.rb 3 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | server ENV['CAPISTRANO_DEPLOY_SERVER'], user: ENV['CAPISTRANO_DEPLOY_USER'] || 'huginn', roles: %w{app db web} -------------------------------------------------------------------------------- /vendor/assets/images/json-editor/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/images/json-editor/delete.png -------------------------------------------------------------------------------- /vendor/assets/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /vendor/assets/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /vendor/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /vendor/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /vendor/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/simple/huginn/master/vendor/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 | <% if user_signed_in? %> 2 | <%= render "signed_in_index" %> 3 | <% else %> 4 | <%= render "signed_out_index" %> 5 | <% end %> -------------------------------------------------------------------------------- /app/assets/javascripts/ace.js.coffee: -------------------------------------------------------------------------------- 1 | #= require ace/ace 2 | #= require ace/mode-javascript.js 3 | #= require ace/mode-markdown.js 4 | #= require ace/mode-coffee.js 5 | -------------------------------------------------------------------------------- /config/initializers/timezone.rb: -------------------------------------------------------------------------------- 1 | # Set the timezone for the JavascriptAgent (libv8 only relies on the TZ variable) 2 | ENV['TZ'] = Time.zone.tzinfo.canonical_identifier 3 | -------------------------------------------------------------------------------- /db/migrate/20130124050117_add_indexes.rb: -------------------------------------------------------------------------------- 1 | class AddIndexes < ActiveRecord::Migration 2 | def change 3 | add_index :links, [:receiver_id, :source_id] 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_rails_session' -------------------------------------------------------------------------------- /db/migrate/20121231170705_add_memory_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddMemoryToAgents < ActiveRecord::Migration 2 | def change 3 | add_column :agents, :memory, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/markdown_helper.rb: -------------------------------------------------------------------------------- 1 | module MarkdownHelper 2 | 3 | def markdown(text) 4 | Kramdown::Document.new(text, :auto_ids => false).to_html.html_safe 5 | end 6 | 7 | end 8 | -------------------------------------------------------------------------------- /app/jobs/agent_cleanup_expired_job.rb: -------------------------------------------------------------------------------- 1 | class AgentCleanupExpiredJob < ActiveJob::Base 2 | queue_as :default 3 | 4 | def perform 5 | Event.cleanup_expired! 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/jobs/agent_run_schedule_job.rb: -------------------------------------------------------------------------------- 1 | class AgentRunScheduleJob < ActiveJob::Base 2 | queue_as :default 3 | 4 | def perform(time) 5 | Agent.run_schedule(time) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | 5 | run Huginn::Application 6 | -------------------------------------------------------------------------------- /db/migrate/20160419150930_add_icon_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddIconToScenarios < ActiveRecord::Migration 2 | def change 3 | add_column :scenarios, :icon, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.buildpacks: -------------------------------------------------------------------------------- 1 | https://github.com/cantino/heroku-selectable-procfile.git 2 | https://github.com/heroku/heroku-buildpack-ruby.git 3 | https://github.com/weibeld/heroku-buildpack-graphviz.git 4 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/jobs.deny: -------------------------------------------------------------------------------- 1 | # 2 | # Any script or job files listed in here (one entry per line) will NOT be 3 | # executed (read as ignored by run-parts). 4 | # 5 | 6 | README 7 | 8 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require 'bundler/setup' 7 | load Gem.bin_path('rspec-core', 'rspec') 8 | -------------------------------------------------------------------------------- /db/migrate/20140525150040_add_service_id_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddServiceIdToAgents < ActiveRecord::Migration 2 | def change 3 | add_column :agents, :service_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/tasks.rb: -------------------------------------------------------------------------------- 1 | desc "Load environment settings from .env" 2 | task :dotenv do 3 | require "dotenv" 4 | Dotenv.load 5 | end 6 | 7 | task :environment => :dotenv 8 | -------------------------------------------------------------------------------- /db/migrate/20140809211540_remove_service_index_on_user_id.rb: -------------------------------------------------------------------------------- 1 | class RemoveServiceIndexOnUserId < ActiveRecord::Migration 2 | def change 3 | remove_index :services, :user_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /db/migrate/20120728215449_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :admin, :boolean, :default => false, :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20130509053743_add_last_webhook_at_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddLastWebhookAtToAgents < ActiveRecord::Migration 2 | def change 3 | add_column :agents, :last_webhook_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /script/delayed_job: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment')) 4 | require 'delayed/command' 5 | Delayed::Command.new(ARGV).daemonize 6 | -------------------------------------------------------------------------------- /app/models/scenario_membership.rb: -------------------------------------------------------------------------------- 1 | class ScenarioMembership < ActiveRecord::Base 2 | belongs_to :agent, :inverse_of => :scenario_memberships 3 | belongs_to :scenario, :inverse_of => :scenario_memberships 4 | end 5 | -------------------------------------------------------------------------------- /db/migrate/20130107050049_add_invitation_code_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddInvitationCodeToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :invitation_code, :string, :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140403043556_add_disabled_to_agent.rb: -------------------------------------------------------------------------------- 1 | class AddDisabledToAgent < ActiveRecord::Migration 2 | def change 3 | add_column :agents, :disabled, :boolean, :default => false, :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' # Set up gems listed in the Gemfile. 7 | -------------------------------------------------------------------------------- /config/initializers/aws.rb: -------------------------------------------------------------------------------- 1 | if defined?(RTurk) && !Rails.env.test? 2 | RTurk::logger.level = Logger::DEBUG 3 | RTurk.setup(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_ACCESS_KEY'], :sandbox => ENV['AWS_SANDBOX'] == "true") 4 | end 5 | -------------------------------------------------------------------------------- /config/initializers/sanitizer.rb: -------------------------------------------------------------------------------- 1 | ActionView::Base.sanitized_allowed_tags += Set.new(%w(style table thead tbody tr th td)) 2 | ActionView::Base.sanitized_allowed_attributes += Set.new(%w(border cellspacing cellpadding valign)) -------------------------------------------------------------------------------- /app/views/home/about.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 |
8 |
9 |
-------------------------------------------------------------------------------- /db/migrate/20131105063248_add_expires_at_to_events.rb: -------------------------------------------------------------------------------- 1 | class AddExpiresAtToEvents < ActiveRecord::Migration 2 | def change 3 | add_column :events, :expires_at, :datetime 4 | add_index :events, :expires_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/fixtures/links.yml: -------------------------------------------------------------------------------- 1 | jane_rain_notifier_agent_link: 2 | receiver: jane_rain_notifier_agent 3 | source: jane_weather_agent 4 | 5 | bob_rain_notifier_agent_link: 6 | receiver: bob_rain_notifier_agent 7 | source: bob_weather_agent -------------------------------------------------------------------------------- /app/views/admin/users/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Edit User

5 | 6 | <%= render partial: 'form' %> 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /db/migrate/20131222211558_add_keep_events_for_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddKeepEventsForToAgents < ActiveRecord::Migration 2 | def change 3 | add_column :agents, :keep_events_for, :integer, :null => false, :default => 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20140210062747_add_mode_to_user_credentials.rb: -------------------------------------------------------------------------------- 1 | class AddModeToUserCredentials < ActiveRecord::Migration 2 | def change 3 | add_column :user_credentials, :mode, :string, :default => 'text', :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /app/views/admin/users/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Create new User

5 | 6 | <%= render partial: 'form' %> 7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] -------------------------------------------------------------------------------- /db/migrate/20160302095413_add_deactivated_at_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDeactivatedAtToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :deactivated_at, :datetime 4 | 5 | add_index :users, :deactivated_at 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20140820003139_add_tag_color_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddTagColorToScenarios < ActiveRecord::Migration 2 | def change 3 | add_column :scenarios, :tag_bg_color, :string 4 | add_column :scenarios, :tag_fg_color, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require 'capistrano/setup' 3 | # Include default deployment tasks 4 | require 'capistrano/deploy' 5 | 6 | require 'capistrano/bundler' 7 | require 'capistrano/rails/assets' 8 | require 'capistrano/rails/migrations' 9 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /.openshift/action_hooks/post_deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple post deploy hook executed after your application 3 | # is deployed and started. This script gets executed directly, so 4 | # it could be python, php, ruby, etc. 5 | 6 | echo "-> Post deploy step" -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | skip_before_action :authenticate_user! 3 | 4 | before_action :upgrade_warning, only: :index 5 | 6 | def index 7 | end 8 | 9 | def about 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20140811200922_add_uid_column_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddUidColumnToServices < ActiveRecord::Migration 2 | def change 3 | add_column :services, :uid, :string 4 | add_index :services, :uid 5 | add_index :services, :provider 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160307084729_add_deactivated_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddDeactivatedToAgents < ActiveRecord::Migration 2 | def change 3 | add_column :agents, :deactivated, :boolean, default: false 4 | add_index :agents, [:disabled, :deactivated] 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/concerns/has_guid.rb: -------------------------------------------------------------------------------- 1 | module HasGuid 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_save :make_guid 6 | end 7 | 8 | protected 9 | 10 | def make_guid 11 | self.guid = SecureRandom.hex unless guid.present? 12 | end 13 | end -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

6 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | 4 | require_relative 'seeds/seeder' 5 | 6 | Seeder.seed 7 | -------------------------------------------------------------------------------- /db/migrate/20150808115436_remove_requirement_from_users_invitation_code.rb: -------------------------------------------------------------------------------- 1 | class RemoveRequirementFromUsersInvitationCode < ActiveRecord::Migration 2 | def change 3 | change_column_null :users, :invitation_code, true, ENV['INVITATION_CODE'].presence || 'try-huginn' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/support/vcr_support.rb: -------------------------------------------------------------------------------- 1 | require 'vcr' 2 | 3 | VCR.configure do |c| 4 | c.cassette_library_dir = 'spec/cassettes' 5 | c.allow_http_connections_when_no_cassette = false 6 | c.hook_into :webmock 7 | c.default_cassette_options = { record: :new_episodes} 8 | c.configure_rspec_metadata! 9 | end -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | Huginn::Application.load_tasks 8 | -------------------------------------------------------------------------------- /deployment/heroku/Procfile.heroku: -------------------------------------------------------------------------------- 1 | # This Procfile is intended for Heroku, and is detected by the Gemfile. DO NOT REMOVE THIS LINE! 2 | 3 | # deployment/heroku/unicorn.rb is a special Unicorn config file that also spawns workers. 4 | web: bundle exec unicorn -p $PORT -c ./deployment/heroku/unicorn.rb 5 | -------------------------------------------------------------------------------- /manifest.yml.sample: -------------------------------------------------------------------------------- 1 | ## This file is used for deployment to cloudfoundry. 2 | 3 | --- 4 | applications: 5 | - name: huginn 6 | url: 7 | path: . 8 | memory: 512M 9 | command: nohup foreman start --procfile Procfile.CF 10 | instances: 1 11 | services: 12 | - huginn-db 13 | -------------------------------------------------------------------------------- /app/validators/owned_by_validator.rb: -------------------------------------------------------------------------------- 1 | class OwnedByValidator < ActiveModel::EachValidator 2 | def validate_each(record, attribute, association) 3 | return if association.all? {|s| s[options[:with]] == record[options[:with]] } 4 | record.errors[attribute] << "must be owned by you" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/database_test.rake: -------------------------------------------------------------------------------- 1 | namespace :database_test do 2 | desc "Ping the database" 3 | task :ping do 4 | require 'active_record' 5 | require 'mysql2' 6 | require 'pg' 7 | ActiveRecord::Base.establish_connection 8 | ActiveRecord::Base.connection.verify! 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /nitrous.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "rails", 3 | "ports": [3000], 4 | "name": "Huginn", 5 | "description": "Build agents that monitor and act on your behalf. Your agents are standing by!", 6 | "scripts": { 7 | "Start Huginn": "cd ~/code/huginn && bundle exec foreman start" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /config/initializers/action_mailer.rb: -------------------------------------------------------------------------------- 1 | # Read smtp config out of a config/smtp.yml file 2 | 3 | smtp_config = YAML::load(ERB.new(File.read(Rails.root.join('config', 'smtp.yml'))).result) 4 | if smtp_config.keys.include? Rails.env 5 | ActionMailer::Base.smtp_settings = smtp_config[Rails.env].symbolize_keys 6 | end -------------------------------------------------------------------------------- /db/migrate/20140602014917_add_indices_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddIndicesToScenarios < ActiveRecord::Migration 2 | def change 3 | add_index :scenarios, [:user_id, :guid], :unique => true 4 | add_index :scenario_memberships, :agent_id 5 | add_index :scenario_memberships, :scenario_id 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /.openshift/markers/README: -------------------------------------------------------------------------------- 1 | Markers 2 | =========== 3 | 4 | Adding marker files to this directory will have the following effects: 5 | 6 | force_clean_build - Previous output from bundle install --deployment will be 7 | removed and all gems will be reinstalled according to the current 8 | Gemfile/Gemfile.lock. 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scenarios.css.scss: -------------------------------------------------------------------------------- 1 | .scenario-import { 2 | .agent-import-list { 3 | .agent-import { 4 | margin-bottom: 20px; 5 | 6 | .instructions { 7 | margin-bottom: 10px; 8 | } 9 | 10 | .current { 11 | font-weight: bold; 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /app/views/system_mailer/send_message.text.erb: -------------------------------------------------------------------------------- 1 | <% if @body.present? %><%= sanitize @body -%> 2 | <% else -%> 3 | <% if @headline %><%= @headline %> 4 | 5 | <% end %><% @groups.each do |group| %><%= group[:title] %> 6 | <% group[:entries].each do |entry| %> <%= entry %> 7 | <% end %> 8 | 9 | <% end %> 10 | <% end %> -------------------------------------------------------------------------------- /bin/threaded.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative './pre_runner_boot' 4 | 5 | agent_runner = AgentRunner.new 6 | 7 | # We need to wait a bit to let delayed_job set it's traps so we can override them 8 | Thread.new do 9 | sleep 5 10 | agent_runner.set_traps 11 | end 12 | 13 | agent_runner.run 14 | -------------------------------------------------------------------------------- /db/migrate/20140408150825_rename_webhook_to_web_request.rb: -------------------------------------------------------------------------------- 1 | class RenameWebhookToWebRequest < ActiveRecord::Migration 2 | def up 3 | rename_column :agents, :last_webhook_at, :last_web_request_at 4 | end 5 | 6 | def down 7 | rename_column :agents, :last_web_request_at, :last_webhook_at 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /.openshift/action_hooks/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple build script and will be executed on your CI system if 3 | # available. Otherwise it will execute while your application is stopped 4 | # before the deploy step. This script gets executed directly, so it 5 | # could be python, php, ruby, etc. 6 | 7 | echo "-> Build step" -------------------------------------------------------------------------------- /db/migrate/20121222074732_create_links.rb: -------------------------------------------------------------------------------- 1 | class CreateLinks < ActiveRecord::Migration 2 | def change 3 | create_table :links do |t| 4 | t.integer :source_id 5 | t.integer :receiver_id 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :links, [:source_id, :receiver_id] 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/delayed_job_worker.rb: -------------------------------------------------------------------------------- 1 | class DelayedJobWorker < LongRunnable::Worker 2 | include LongRunnable 3 | 4 | def run 5 | @dj = Delayed::Worker.new 6 | @dj.start 7 | end 8 | 9 | def stop 10 | @dj.stop if @dj 11 | end 12 | 13 | def self.setup_worker 14 | [new(id: self.to_s)] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /doc/manual/README.md: -------------------------------------------------------------------------------- 1 | # Manual Installation 2 | 3 | - [Requirements](requirements.md) Software and hardware requirements to run the Huginn installation 4 | - [Install](installation.md) Installation guide for Ubuntu/Debian 5 | - [Update](update.md) Update an existing Huginn installation 6 | - Deploy updates via [Capistrano](capistrano.md) 7 | -------------------------------------------------------------------------------- /lib/tasks/rspec.rake: -------------------------------------------------------------------------------- 1 | if defined? RSpec 2 | namespace :spec do 3 | desc 'Run all specs in spec directory (exluding feature specs)' 4 | RSpec::Core::RakeTask.new(:nofeatures) do |task| 5 | ENV['RSPEC_TASK'] = 'spec:nofeatures' 6 | task.exclude_pattern = "spec/features/**/*_spec.rb" 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/agents/_mini_action_menu.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 5 | <%= render 'agents/action_menu', agent: agent, return_to: (defined?(return_to) && return_to) || request.path %> 6 |
7 | -------------------------------------------------------------------------------- /db/migrate/20140216201250_add_propagate_immediately_to_agent.rb: -------------------------------------------------------------------------------- 1 | class AddPropagateImmediatelyToAgent < ActiveRecord::Migration 2 | def up 3 | add_column :agents, :propagate_immediately, :boolean, :default => false, :null => false 4 | end 5 | 6 | def down 7 | remove_column :agents, :propagate_immediately 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140509170443_create_scenario_memberships.rb: -------------------------------------------------------------------------------- 1 | class CreateScenarioMemberships < ActiveRecord::Migration 2 | def change 3 | create_table :scenario_memberships do |t| 4 | t.integer :agent_id, :null => false 5 | t.integer :scenario_id, :null => false 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /bin/schedule.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This process is used to maintain Huginn's upkeep behavior, automatically running scheduled Agents and 4 | # periodically propagating and expiring Events. It's typically run via foreman and the included Procfile. 5 | 6 | require_relative './pre_runner_boot' 7 | 8 | AgentRunner.new(only: HuginnScheduler).run -------------------------------------------------------------------------------- /spec/fixtures/events.yml: -------------------------------------------------------------------------------- 1 | bob_website_agent_event: 2 | user: bob 3 | agent: bob_website_agent 4 | payload: <%= { :title => "foo", :url => "http://foo.com" }.to_json.inspect %> 5 | 6 | jane_website_agent_event: 7 | user: jane 8 | agent: jane_website_agent 9 | payload: <%= { :title => "foo", :url => "http://foo.com" }.to_json.inspect %> 10 | -------------------------------------------------------------------------------- /app/controllers/users/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | module Users 2 | class RegistrationsController < Devise::RegistrationsController 3 | after_action :create_default_scenario, only: :create 4 | 5 | private 6 | 7 | def create_default_scenario 8 | DefaultScenarioImporter.import(@user) if @user.persisted? 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/agents/agent_views/webhook_agent/_show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Send WebHooks (POST requests) to this Agent at <%= link_to web_requests_url(:agent_id => @agent.id, :user_id => current_user.id, :secret => @agent.options['secret']), web_requests_url(:agent_id => @agent.id, :user_id => current_user.id, :secret => @agent.options['secret']), :target => :blank %> 3 |

-------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Remove the XML parser from the list that will be used to initialize the application's XML parser list. 5 | ActionDispatch::ParamsParser::DEFAULT_PARSERS.delete(Mime::XML) 6 | 7 | # Initialize the rails application 8 | Huginn::Application.initialize! 9 | -------------------------------------------------------------------------------- /docker/single-process/environment.yml: -------------------------------------------------------------------------------- 1 | huginn_base: 2 | environment: 3 | DATABASE_ADAPTER: mysql2 4 | DATABASE_NAME: huginn 5 | DATABASE_USERNAME: huginn 6 | DATABASE_PASSWORD: myhuginnpassword 7 | APP_SECRET_TOKEN: 3bd139f9186b31a85336bb89cd1a1337078921134b2f48e022fd09c234d764d3e19b018b2ab789c6e0e04a1ac9e3365116368049660234c2038dc9990513d49c 8 | -------------------------------------------------------------------------------- /app/views/agents/_oauth_dropdown.html.erb: -------------------------------------------------------------------------------- 1 | <% if agent.try(:oauthable?) %> 2 |
3 | <%= label_tag :service %> 4 | <%= select_tag 'agent[service_id]', options_for_select(agent.valid_services_for(current_user).collect { |s| [service_label_text(s), s.id] }, agent.service_id), class: 'form-control' %> 5 |
6 | <% end %> 7 | -------------------------------------------------------------------------------- /docker/multi-process/scripts/standalone-packages: -------------------------------------------------------------------------------- 1 | DEBIAN_FRONTEND=noninteractive 2 | apt-get update 3 | apt-get install -y python2.7 python-docutils mysql-server \ 4 | supervisor python-pip && \ 5 | pip install supervisor-stdout 6 | rm -rf /var/lib/apt/lists/* 7 | rm -rf /usr/share/doc/ 8 | rm -rf /usr/share/man/ 9 | rm -rf /usr/share/locale/ 10 | -------------------------------------------------------------------------------- /bin/pre_runner_boot.rb: -------------------------------------------------------------------------------- 1 | unless defined?(Rails) 2 | puts 3 | puts "Please run me with rails runner, for example:" 4 | puts " RAILS_ENV=production bundle exec rails runner bin/#{File.basename($0)}" 5 | puts 6 | exit 1 7 | end 8 | 9 | Rails.configuration.cache_classes = true 10 | 11 | Dotenv.load if ENV['APP_SECRET_TOKEN'].blank? 12 | 13 | require 'agent_runner' 14 | -------------------------------------------------------------------------------- /spec/helpers/markdown_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe MarkdownHelper do 4 | 5 | describe '#markdown' do 6 | 7 | it 'renders HTML from a markdown text' do 8 | expect(markdown('# Header')).to match(/

Header<\/h1>/) 9 | expect(markdown('## Header 2')).to match(/

Header 2<\/h2>/) 10 | end 11 | 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140531232016_add_fields_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddFieldsToScenarios < ActiveRecord::Migration 2 | def change 3 | add_column :scenarios, :description, :text 4 | add_column :scenarios, :public, :boolean, :default => false, :null => false 5 | add_column :scenarios, :guid, :string, :null => false 6 | add_column :scenarios, :source_url, :string 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/ar_mysql_column_charset.rb: -------------------------------------------------------------------------------- 1 | require 'active_support' 2 | 3 | ActiveSupport.on_load :active_record do 4 | class << ActiveRecord::Base 5 | def establish_connection(spec = nil) 6 | super.tap { |ret| 7 | if /mysql/i === connection.adapter_name 8 | require 'ar_mysql_column_charset/main' 9 | end 10 | } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /bin/agent_runner.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This process is used to maintain Huginn's upkeep behavior, automatically running scheduled Agents and 4 | # periodically propagating and expiring Events. It also running TwitterStreamAgents and Agents that support long running 5 | # background jobs. 6 | 7 | require_relative './pre_runner_boot' 8 | 9 | AgentRunner.new(except: DelayedJobWorker).run -------------------------------------------------------------------------------- /db/migrate/20140509170420_create_scenarios.rb: -------------------------------------------------------------------------------- 1 | class CreateScenarios < ActiveRecord::Migration 2 | def change 3 | create_table :scenarios do |t| 4 | t.string :name, :null => false 5 | t.integer :user_id, :null => false 6 | 7 | t.timestamps 8 | end 9 | 10 | add_column :users, :scenario_count, :integer, :null => false, :default => 0 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js.coffee: -------------------------------------------------------------------------------- 1 | #= require jquery 2 | #= require jquery_ujs 3 | #= require typeahead.bundle 4 | #= require bootstrap 5 | #= require select2 6 | #= require json2 7 | #= require jquery.json-editor 8 | #= require jquery.serializeObject 9 | #= require latlon_and_geo 10 | #= require spectrum 11 | #= require_tree ./components 12 | #= require_tree ./pages 13 | #= require_self 14 | -------------------------------------------------------------------------------- /config/initializers/liquid.rb: -------------------------------------------------------------------------------- 1 | module Liquid 2 | # https://github.com/Shopify/liquid/pull/623 3 | remove_const :PartialTemplateParser 4 | remove_const :TemplateParser 5 | 6 | PartialTemplateParser = /#{TagStart}.*?#{TagEnd}|#{VariableStart}(?:(?:[^'"{}]+|#{QuotedString})*?|.*?)#{VariableIncompleteEnd}/m 7 | TemplateParser = /(#{PartialTemplateParser}|#{AnyStartingTag})/m 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20140906030139_set_events_count_default.rb: -------------------------------------------------------------------------------- 1 | class SetEventsCountDefault < ActiveRecord::Migration 2 | def up 3 | change_column_default(:agents, :events_count, 0) 4 | change_column_null(:agents, :events_count, false, 0) 5 | end 6 | 7 | def down 8 | change_column_null(:agents, :events_count, true) 9 | change_column_default(:agents, :events_count, nil) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /bin/twitter_stream.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This process is used by TwitterStreamAgents to watch the Twitter stream in real time. It periodically checks for 4 | # new or changed TwitterStreamAgents and starts to follow the stream for them. It is typically run by foreman via 5 | # the included Procfile. 6 | 7 | require_relative './pre_runner_boot' 8 | 9 | AgentRunner.new(only: Agents::TwitterStreamAgent).run -------------------------------------------------------------------------------- /app/models/control_link.rb: -------------------------------------------------------------------------------- 1 | # A ControlLink connects Agents in a control flow from the `controller` to the `control_target`. 2 | class ControlLink < ActiveRecord::Base 3 | attr_accessible :controller_id, :target_id 4 | 5 | belongs_to :controller, class_name: 'Agent', inverse_of: :control_links_as_controller 6 | belongs_to :control_target, class_name: 'Agent', inverse_of: :control_links_as_control_target 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/shared_examples/has_guid.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | shared_examples_for HasGuid do 4 | it "gets created before_save, but only if it's not present" do 5 | instance = new_instance 6 | expect(instance.guid).to be_nil 7 | instance.save! 8 | expect(instance.guid).not_to be_nil 9 | 10 | expect { instance.save! }.not_to change { instance.reload.guid } 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140605032822_add_guid_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddGuidToAgents < ActiveRecord::Migration 2 | class Agent < ActiveRecord::Base; end 3 | 4 | def change 5 | add_column :agents, :guid, :string 6 | 7 | Agent.find_each do |agent| 8 | agent.update_attribute :guid, SecureRandom.hex 9 | end 10 | 11 | change_column_null :agents, :guid, false 12 | 13 | add_index :agents, :guid 14 | end 15 | end -------------------------------------------------------------------------------- /spec/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html 2 | 3 | bob: 4 | email: "bob@example.com" 5 | username: bob 6 | invitation_code: <%= User::INVITATION_CODES.last %> 7 | scenario_count: 1 8 | 9 | jane: 10 | email: "jane@example.com" 11 | username: jane 12 | invitation_code: <%= User::INVITATION_CODES.last %> 13 | scenario_count: 1 14 | admin: true -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/rails-now.rb: -------------------------------------------------------------------------------- 1 | # If you use gems that require environment variables to be set before they are 2 | # loaded, then list `dotenv-rails` in the `Gemfile` before those other gems and 3 | # require `dotenv/rails-now`. 4 | # 5 | # gem "dotenv-rails", :require => "dotenv/rails-now" 6 | # gem "gem-that-requires-env-variables" 7 | # 8 | 9 | require "dotenv/rails" 10 | Dotenv::Railtie.load 11 | -------------------------------------------------------------------------------- /app/controllers/logs_controller.rb: -------------------------------------------------------------------------------- 1 | class LogsController < ApplicationController 2 | before_action :load_agent 3 | 4 | def index 5 | @logs = @agent.logs.all 6 | render :action => :index, :layout => false 7 | end 8 | 9 | def clear 10 | @agent.delete_logs! 11 | index 12 | end 13 | 14 | protected 15 | 16 | def load_agent 17 | @agent = current_user.agents.find(params[:agent_id]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20130819160603_create_agent_logs.rb: -------------------------------------------------------------------------------- 1 | class CreateAgentLogs < ActiveRecord::Migration 2 | def change 3 | create_table :agent_logs do |t| 4 | t.integer :agent_id, :null => false 5 | t.text :message, :null => false 6 | t.integer :level, :default => 3, :null => false 7 | t.integer :inbound_event_id 8 | t.integer :outbound_event_id 9 | 10 | t.timestamps 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | Huginn::Application.config.secret_key_base = ENV['APP_SECRET_TOKEN'] 8 | -------------------------------------------------------------------------------- /config/initializers/silence_worker_status_logger.rb: -------------------------------------------------------------------------------- 1 | Rails::Rack::Logger.class_eval do 2 | def call_with_silence_worker_status(env) 3 | previous_level = Rails.logger.level 4 | Rails.logger.level = Logger::ERROR if env['PATH_INFO'] =~ %r{^/worker_status} 5 | call_without_silence_worker_status(env) 6 | ensure 7 | Rails.logger.level = previous_level 8 | end 9 | alias_method_chain :call, :silence_worker_status 10 | end 11 | -------------------------------------------------------------------------------- /lib/json_with_indifferent_access.rb: -------------------------------------------------------------------------------- 1 | class JSONWithIndifferentAccess 2 | def self.load(json) 3 | ActiveSupport::HashWithIndifferentAccess.new(JSON.parse(json || '{}')) 4 | rescue JSON::ParserError 5 | Rails.logger.error "Unparsable JSON in JSONWithIndifferentAccess: #{json}" 6 | { 'error' => 'unparsable json detected during de-serialization' } 7 | end 8 | 9 | def self.dump(hash) 10 | JSON.dump(hash) 11 | end 12 | end -------------------------------------------------------------------------------- /vendor/assets/javascripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | //= require bootstrap/affix 2 | //= require bootstrap/alert 3 | //= require bootstrap/button 4 | //= require bootstrap/carousel 5 | //= require bootstrap/collapse 6 | //= require bootstrap/dropdown 7 | //= require bootstrap/tab 8 | //= require bootstrap/transition 9 | //= require bootstrap/scrollspy 10 | //= require bootstrap/modal 11 | //= require bootstrap/tooltip 12 | //= require bootstrap/popover 13 | -------------------------------------------------------------------------------- /db/migrate/20150507153436_update_keep_events_for_to_be_in_seconds.rb: -------------------------------------------------------------------------------- 1 | class UpdateKeepEventsForToBeInSeconds < ActiveRecord::Migration 2 | class Agent < ActiveRecord::Base; end 3 | 4 | SECONDS_IN_DAY = 60 * 60 * 24 5 | 6 | def up 7 | Agent.update_all ['keep_events_for = keep_events_for * ?', SECONDS_IN_DAY] 8 | end 9 | 10 | def down 11 | Agent.update_all ['keep_events_for = keep_events_for / ?', SECONDS_IN_DAY] 12 | end 13 | end -------------------------------------------------------------------------------- /spec/data_fixtures/stubhub_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "response":{ 3 | "docs":[ 4 | { 5 | "url": "http://www.stubhub.com/event/name-1-1-2014-12345", 6 | "seo_description_en_US": "name", 7 | "event_date_local": "2014-01-01", 8 | "maxPrice": "100", 9 | "minPrice": "50", 10 | "totalPostings": "100", 11 | "totalTickets": "200", 12 | "venue_name": "Venue Name" 13 | } 14 | ] 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /db/migrate/20140901143732_add_control_links.rb: -------------------------------------------------------------------------------- 1 | class AddControlLinks < ActiveRecord::Migration 2 | def change 3 | create_table :control_links do |t| 4 | t.integer :controller_id, null: false 5 | t.integer :control_target_id, null: false 6 | 7 | t.timestamps 8 | end 9 | 10 | add_index :control_links, [:controller_id, :control_target_id], unique: true 11 | add_index :control_links, :control_target_id 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

9 | -------------------------------------------------------------------------------- /db/migrate/20140121075418_create_user_credentials.rb: -------------------------------------------------------------------------------- 1 | class CreateUserCredentials < ActiveRecord::Migration 2 | def change 3 | create_table :user_credentials do |t| 4 | t.integer :user_id, :null => false 5 | t.string :credential_name, :null => false 6 | t.text :credential_value, :null => false 7 | 8 | t.timestamps 9 | end 10 | add_index :user_credentials, [:user_id, :credential_name], :unique => true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20160224120316_add_mode_option_to_ftpsite_agents.rb: -------------------------------------------------------------------------------- 1 | class AddModeOptionToFtpsiteAgents < ActiveRecord::Migration 2 | def up 3 | Agents::FtpsiteAgent.find_each do |agent| 4 | agent.options['mode'] = 'read' 5 | agent.save!(validate: false) 6 | end 7 | end 8 | 9 | def down 10 | Agents::FtpsiteAgent.find_each do |agent| 11 | agent.options.delete 'mode' 12 | agent.save!(validate: false) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20160405072512_post_agent_set_event_header_style.rb: -------------------------------------------------------------------------------- 1 | class PostAgentSetEventHeaderStyle < ActiveRecord::Migration 2 | def up 3 | Agent.of_type("Agents::PostAgent").each do |post_agent| 4 | if post_agent.send(:boolify, post_agent.options['emit_events']) && 5 | !post_agent.options.key?('event_headers_style') 6 | post_agent.options['event_headers_style'] = 'raw' 7 | post_agent.save! 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.sassc 3 | .sass-cache 4 | capybara-*.html 5 | .rspec 6 | /.bundle 7 | /vendor/bundle 8 | /log/* 9 | /tmp/* 10 | !/tmp/.gitkeep 11 | /db/*.sqlite3 12 | /public/system/* 13 | /coverage/ 14 | /spec/tmp/* 15 | **.orig 16 | rerun.txt 17 | pickle-email-*.html 18 | .idea/ 19 | .DS_Store 20 | .env 21 | deployment/tmp 22 | deployment/cookbooks 23 | .vagrant 24 | .*un~ 25 | .ruby-gemset 26 | .ruby-version 27 | manifest.yml 28 | config/unicorn.rb 29 | db/schema.rb 30 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/jobs.allow: -------------------------------------------------------------------------------- 1 | # 2 | # Script or job files listed in here (one entry per line) will be 3 | # executed on a weekly-basis. 4 | # 5 | # Example: The chronograph script will be executed weekly but the README 6 | # and chrono.dat files in this directory will be ignored. 7 | # 8 | # The README file is actually ignored due to the entry in the 9 | # jobs.deny which is checked before jobs.allow (this file). 10 | # 11 | chronograph 12 | 13 | -------------------------------------------------------------------------------- /app/views/layouts/_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if flash.keys.length > 0 %> 2 |
3 | <% flash.each do |name, msg| %> 4 |
alert-dismissable"> 5 | 6 | <%= content_tag :div, msg, :id => "flash_#{name}" if msg.is_a?(String) %> 7 |
8 | <% end %> 9 |
10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/controllers/diagrams_controller.rb: -------------------------------------------------------------------------------- 1 | class DiagramsController < ApplicationController 2 | def show 3 | if params[:scenario_id].present? 4 | @scenario = current_user.scenarios.find(params[:scenario_id]) 5 | agents = @scenario.agents 6 | else 7 | agents = current_user.agents 8 | end 9 | @disabled_agents = agents.inactive 10 | agents = agents.active if params[:exclude_disabled].present? 11 | @agents = agents.includes(:receivers) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rdbms_functions.rb: -------------------------------------------------------------------------------- 1 | module RDBMSFunctions 2 | def rdbms_date_add(source, unit, amount) 3 | adapter_type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym 4 | case adapter_type 5 | when :mysql, :mysql2 6 | "DATE_ADD(`#{source}`, INTERVAL #{amount} #{unit})" 7 | when :postgresql 8 | "(#{source} + INTERVAL '#{amount} #{unit}')" 9 | else 10 | raise NotImplementedError, "Unknown adapter type '#{adapter_type}'" 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/pages/scenario-form-page.js.coffee: -------------------------------------------------------------------------------- 1 | class @ScenarioFormPage 2 | constructor:() -> 3 | @enabledSelect2() 4 | 5 | format: (icon) -> 6 | originalOption = icon.element 7 | ' ' + icon.text 8 | 9 | enabledSelect2: () -> 10 | $('.select2-fountawesome-icon').select2 11 | width: '100%' 12 | formatResult: @format 13 | 14 | $ -> 15 | Utils.registerPage(ScenarioFormPage, forPathsMatching: /^scenarios/) -------------------------------------------------------------------------------- /db/migrate/20121216025930_create_events.rb: -------------------------------------------------------------------------------- 1 | class CreateEvents < ActiveRecord::Migration 2 | def change 3 | create_table :events do |t| 4 | t.integer :user_id 5 | t.integer :agent_id 6 | t.decimal :lat, :precision => 15, :scale => 10 7 | t.decimal :lng, :precision => 15, :scale => 10 8 | t.text :payload 9 | 10 | t.timestamps 11 | end 12 | 13 | add_index :events, [:user_id, :created_at] 14 | add_index :events, [:agent_id, :created_at] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /bin/decrypt_backup.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # If you're using the backup gem, described on the Huginn wiki and at doc/deployment/backup, then you can use this 4 | # utility to decrypt backups. 5 | 6 | in_file = ARGV.shift 7 | out_file = ARGV.shift || "decrypted_backup.tar" 8 | 9 | puts "About to decrypt #{in_file} and write it to #{out_file}." 10 | 11 | cmd = "bundle exec backup decrypt --encryptor openssl --base64 --salt --in #{in_file} --out #{out_file}" 12 | puts "Executing: #{cmd}" 13 | puts `#{cmd}` 14 | -------------------------------------------------------------------------------- /spec/support/spec_helpers.rb: -------------------------------------------------------------------------------- 1 | module SpecHelpers 2 | def build_events(options = {}) 3 | options[:values].map.with_index do |tuple, index| 4 | event = Event.new 5 | event.agent = agents(:bob_weather_agent) 6 | event.payload = (options[:pattern] || {}).dup.merge((options[:keys].zip(tuple)).inject({}) { |memo, (key, value)| memo[key] = value; memo }) 7 | event.created_at = (100 - index).hours.ago 8 | event.updated_at = (100 - index).hours.ago 9 | event 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /app/assets/stylesheets/diagram.css.scss: -------------------------------------------------------------------------------- 1 | .agent-diagram { 2 | position: relative; 3 | z-index: auto; 4 | 5 | svg.diagram { 6 | position: absolute; 7 | z-index: 1; 8 | } 9 | 10 | .overlay-container { 11 | z-index: auto; 12 | 13 | .overlay { 14 | z-index: auto; 15 | width: 100%; 16 | height: 100%; 17 | 18 | .badge { 19 | position: absolute; 20 | display: none; 21 | color: white !important; 22 | z-index: 2; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) 11 | Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem "spring", match[1] 13 | require "spring/binstub" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/services.yml: -------------------------------------------------------------------------------- 1 | generic: 2 | token: 1234token 3 | secret: 56789secret 4 | refresh_token: refresh12345 5 | provider: testprovider 6 | name: test 7 | expires_at: <%= Time.parse("2015-01-01 00:00:00") %> 8 | options: <%= { user_id: 12345 }.to_yaml.inspect %> 9 | user: bob 10 | global: 11 | token: 1234token 12 | provider: testprovider 13 | name: test 14 | expires_at: <%= Time.parse("2015-01-01 00:00:00") %> 15 | options: <%= { user_id: 12345 }.to_yaml.inspect %> 16 | user: jane 17 | global: true -------------------------------------------------------------------------------- /spec/fixtures/user_credentials.yml: -------------------------------------------------------------------------------- 1 | bob_aws_key: 2 | user: bob 3 | credential_name: aws_key 4 | credential_value: 2222222222-bob 5 | mode: text 6 | bob_aws_secret: 7 | user: bob 8 | credential_name: aws_secret 9 | credential_value: 1111111111-bob 10 | mode: text 11 | jane_aws_key: 12 | user: jane 13 | credential_name: aws_key 14 | credential_value: 2222222222-jane 15 | mode: text 16 | jane_aws_secret: 17 | user: jane 18 | credential_name: aws_secret 19 | credential_value: 1111111111-jabe 20 | mode: text -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | def user_account_state(user) 3 | if !user.active? 4 | content_tag :span, 'inactive', class: 'label label-danger' 5 | elsif user.access_locked? 6 | content_tag :span, 'locked', class: 'label label-danger' 7 | elsif ENV['REQUIRE_CONFIRMED_EMAIL'] == 'true' && !user.confirmed? 8 | content_tag :span, 'unconfirmed', class: 'label label-warning' 9 | else 10 | content_tag :span, 'active', class: 'label label-success' 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/jobs/agent_check_job.rb: -------------------------------------------------------------------------------- 1 | class AgentCheckJob < ActiveJob::Base 2 | # Given an Agent id, load the Agent, call #check on it, and then save it with an updated `last_check_at` timestamp. 3 | def perform(agent_id) 4 | agent = Agent.find(agent_id) 5 | begin 6 | return if agent.unavailable? 7 | agent.check 8 | agent.last_check_at = Time.now 9 | agent.save! 10 | rescue => e 11 | agent.error "Exception during check. #{e.message}: #{e.backtrace.join("\n")}" 12 | raise 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /app/views/agents/agent_views/data_output_agent/_show.html.erb: -------------------------------------------------------------------------------- 1 |

2 | Data for this Agent is available at these URLs: 3 |

4 | 5 | 6 | 13 | -------------------------------------------------------------------------------- /db/migrate/20120919061122_enable_lockable_strategy_for_devise.rb: -------------------------------------------------------------------------------- 1 | class EnableLockableStrategyForDevise < ActiveRecord::Migration 2 | def up 3 | add_column :users, :failed_attempts, :integer, :default => 0 4 | add_column :users, :unlock_token, :string 5 | add_column :users, :locked_at, :datetime 6 | 7 | add_index :users, :unlock_token, :unique => true 8 | end 9 | 10 | def down 11 | remove_column :users, :failed_attempts 12 | remove_column :users, :unlock_token 13 | remove_column :users, :locked_at 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20120919063304_add_username_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddUsernameToUsers < ActiveRecord::Migration 2 | class User < ActiveRecord::Base 3 | end 4 | 5 | def up 6 | add_column :users, :username, :string 7 | 8 | User.find_each do |user| 9 | user.update_attribute :username, user.email.gsub(/@.*$/, '') 10 | end 11 | 12 | change_column :users, :username, :string, :null => false 13 | add_index :users, :username, :unique => true 14 | end 15 | 16 | def down 17 | remove_column :users, :username 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20140127164931_change_handler_to_medium_text.rb: -------------------------------------------------------------------------------- 1 | # Increase handler size to 16MB (consistent with events.payload) 2 | 3 | class ChangeHandlerToMediumText < ActiveRecord::Migration 4 | def up 5 | if mysql? 6 | change_column :delayed_jobs, :handler, :text, :limit => 16777215 7 | end 8 | end 9 | 10 | def down 11 | if mysql? 12 | change_column :delayed_jobs, :handler, :text, :limit => 65535 13 | end 14 | end 15 | 16 | def mysql? 17 | ActiveRecord::Base.connection.adapter_name =~ /mysql/i 18 | end 19 | end -------------------------------------------------------------------------------- /app/views/scenarios/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 | 10 | <%= render 'form' %> 11 | 12 |
13 | 14 |
15 |
16 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, scenarios_path, class: "btn btn-default" %> 17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/views/scenarios/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 | 10 | <%= render 'form' %> 11 | 12 |
13 | 14 |
15 |
16 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, scenarios_path, class: "btn btn-default" %> 17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/models/link.rb: -------------------------------------------------------------------------------- 1 | # A Link connects Agents in a directed Event flow from the `source` to the `receiver`. 2 | class Link < ActiveRecord::Base 3 | attr_accessible :source_id, :receiver_id 4 | 5 | belongs_to :source, :class_name => "Agent", :inverse_of => :links_as_source 6 | belongs_to :receiver, :class_name => "Agent", :inverse_of => :links_as_receiver 7 | 8 | before_create :store_event_id_at_creation 9 | 10 | def store_event_id_at_creation 11 | self.event_id_at_creation = source.events.limit(1).reorder("id desc").pluck(:id).first || 0 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /doc/heroku/update.md: -------------------------------------------------------------------------------- 1 | ## Update an existing Heroku deployment 2 | 3 | Once you've run `bin/setup_heroku`, you should have 'cantino/huginn' as a remote in git. (Check with `git remote -v`.) Now, you can update your Heroku installation with the following commands: 4 | 5 | ```sh 6 | git fetch origin 7 | git merge origin/master 8 | git push -f heroku master # note: this will wipe out any code changes that only exist on Heroku! 9 | heroku run rake db:migrate # this will migrate the database to the latest state (not needed for every update, but always safe to run) 10 | ``` 11 | -------------------------------------------------------------------------------- /spec/fixtures/agent_logs.yml: -------------------------------------------------------------------------------- 1 | log_for_jane_website_agent: 2 | agent: jane_website_agent 3 | outbound_event: jane_website_agent_event 4 | message: "fetching some website data" 5 | 6 | log_for_bob_website_agent: 7 | agent: bob_website_agent 8 | outbound_event: bob_website_agent_event 9 | message: "fetching some other website data" 10 | 11 | first_log_for_bob_weather_agent: 12 | agent: bob_weather_agent 13 | message: "checking the weather" 14 | 15 | second_log_for_bob_weather_agent: 16 | agent: bob_weather_agent 17 | message: "checking the weather again" 18 | -------------------------------------------------------------------------------- /spec/concerns/inheritance_tracking_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'inheritance_tracking' 3 | 4 | describe InheritanceTracking do 5 | class Class1 6 | include InheritanceTracking 7 | end 8 | 9 | class Class2 < Class1; end 10 | class Class3 < Class1; end 11 | 12 | it "tracks subclasses" do 13 | expect(Class1.subclasses).to eq([Class2, Class3]) 14 | end 15 | 16 | it "can be temporarily overridden with #with_subclasses" do 17 | Class1.with_subclasses(Class2) do 18 | expect(Class1.subclasses).to eq([Class2]) 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /spec/fixtures/scenarios.yml: -------------------------------------------------------------------------------- 1 | jane_weather: 2 | name: Jane's weather alert Scenario 3 | user: jane 4 | description: Jane's weather alert system 5 | public: false 6 | guid: random-guid-generated-by-bob 7 | 8 | jane_weather_duplicate: 9 | name: Jane's duplicated, incomplete weather alert Scenario 10 | user: jane 11 | public: false 12 | guid: random-guid-generated-by-jane2 13 | 14 | bob_weather: 15 | name: Bob's weather alert Scenario 16 | user: bob 17 | description: Bob's weather alert system 18 | public: false 19 | guid: random-guid-generated-by-jane 20 | -------------------------------------------------------------------------------- /app/concerns/inheritance_tracking.rb: -------------------------------------------------------------------------------- 1 | module InheritanceTracking 2 | extend ActiveSupport::Concern 3 | 4 | module ClassMethods 5 | def inherited(subclass) 6 | @subclasses ||= [] 7 | @subclasses << subclass 8 | @subclasses.uniq! 9 | super 10 | end 11 | 12 | def subclasses 13 | @subclasses 14 | end 15 | 16 | def with_subclasses(*subclasses) 17 | original_subclasses = @subclasses 18 | @subclasses = subclasses.flatten 19 | yield 20 | ensure 21 | @subclasses = original_subclasses 22 | end 23 | end 24 | end -------------------------------------------------------------------------------- /app/controllers/scenario_imports_controller.rb: -------------------------------------------------------------------------------- 1 | class ScenarioImportsController < ApplicationController 2 | def new 3 | @scenario_import = ScenarioImport.new(:url => params[:url]) 4 | end 5 | 6 | def create 7 | @scenario_import = ScenarioImport.new(params[:scenario_import]) 8 | @scenario_import.set_user(current_user) 9 | 10 | if @scenario_import.valid? && @scenario_import.import_confirmed? && @scenario_import.import 11 | redirect_to @scenario_import.scenario, notice: "Import successful!" 12 | else 13 | render action: "new" 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /app/views/user_credentials/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 | 10 | <%= render 'form' %> 11 | 12 |
13 | 14 |
15 |
16 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, user_credentials_path, class: "btn btn-default" %> 17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/views/user_credentials/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 | 10 | <%= render 'form' %> 11 | 12 |
13 | 14 |
15 |
16 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, user_credentials_path, class: "btn btn-default" %> 17 |
18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /db/migrate/20160423163416_add_xml_namespace_option_to_data_output_agents.rb: -------------------------------------------------------------------------------- 1 | class AddXmlNamespaceOptionToDataOutputAgents < ActiveRecord::Migration 2 | def up 3 | Agents::DataOutputAgent.find_each do |agent| 4 | agent.options['ns_media'] = 'true' 5 | agent.options['ns_itunes'] = 'true' 6 | agent.save!(validate: false) 7 | end 8 | end 9 | 10 | def down 11 | Agents::DataOutputAgent.find_each do |agent| 12 | agent.options.delete 'ns_media' 13 | agent.options.delete 'ns_itunes' 14 | agent.save!(validate: false) 15 | end 16 | end 17 | end -------------------------------------------------------------------------------- /spec/data_fixtures/adioso_parse.json: -------------------------------------------------------------------------------- 1 | { 2 | "cost_max": null, 3 | "currency": "USD", 4 | "date_end": 1403740799, 5 | "date_start": 1372032000, 6 | "dest_codes": [ 7 | "CGX", 8 | "MDW", 9 | "ORD", 10 | "GYY" 11 | ], 12 | "duration_max": null, 13 | "origin_codes": [ 14 | "PDX", 15 | "SLE" 16 | ], 17 | "search_url": "http://api.adioso.com/v2/search/fares?currency=USD&date_end=1403740799&date_start=1372032000&dest_codes=CGX%2CMDW%2CORD%2CGYY&origin_codes=PDX%2CSLE", 18 | "segments_max": null 19 | } 20 | -------------------------------------------------------------------------------- /db/migrate/20150219213604_add_type_option_attribute_to_pushbullet_agents.rb: -------------------------------------------------------------------------------- 1 | class AddTypeOptionAttributeToPushbulletAgents < ActiveRecord::Migration 2 | def up 3 | Agents::PushbulletAgent.find_each do |agent| 4 | if agent.options['type'].nil? 5 | agent.options['type'] = 'note' 6 | agent.save! 7 | end 8 | end 9 | end 10 | 11 | def down 12 | Agents::PushbulletAgent.find_each do |agent| 13 | if agent.options['type'].present? 14 | agent.options.delete 'type' 15 | agent.save(validate: false) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /db/migrate/20140515211100_create_services.rb: -------------------------------------------------------------------------------- 1 | class CreateServices < ActiveRecord::Migration 2 | def change 3 | create_table :services do |t| 4 | t.integer :user_id, null: false 5 | t.string :provider, null: false 6 | t.string :name, null: false 7 | t.text :token, null: false 8 | t.text :secret 9 | t.text :refresh_token 10 | t.datetime :expires_at 11 | t.boolean :global, default: false 12 | t.text :options 13 | t.timestamps 14 | end 15 | add_index :services, :user_id 16 | add_index :services, [:user_id, :global] 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/env.test: -------------------------------------------------------------------------------- 1 | APP_SECRET_TOKEN=notarealappsecrettoken 2 | TWITTER_OAUTH_KEY=twitteroauthkey 3 | TWITTER_OAUTH_SECRET=twitteroauthsecret 4 | TUMBLR_OAUTH_KEY=tumblroauthsecret 5 | TUMBLR_OAUTH_SECRET=tumblroauthsecret 6 | THIRTY_SEVEN_SIGNALS_OAUTH_KEY=TESTKEY 7 | THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=TESTSECRET 8 | DROPBOX_OAUTH_KEY=dropboxoauthkey 9 | DROPBOX_OAUTH_SECRET=dropboxoauthsecret 10 | WUNDERLIST_OAUTH_KEY=wunderoauthkey 11 | EVERNOTE_OAUTH_KEY=evernoteoauthkey 12 | EVERNOTE_OAUTH_SECRET=evernoteoauthsecret 13 | FAILED_JOBS_TO_KEEP=2 14 | REQUIRE_CONFIRMED_EMAIL=false 15 | ENABLE_INSECURE_AGENTS=true 16 | -------------------------------------------------------------------------------- /app/models/contact.rb: -------------------------------------------------------------------------------- 1 | # Contacts are used only for the contact form on the Huginn website. If you host a public Huginn instance, you can use 2 | # these to receive messages from visitors. 3 | 4 | class Contact < ActiveRecord::Base 5 | attr_accessible :email, :message, :name 6 | 7 | validates_format_of :email, :with => /\A[A-Z0-9._%+-]+@[A-Z0-9.-]+\.(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|asia|jobs|museum)\Z/i 8 | validates_presence_of :name, :message 9 | 10 | after_create :send_contact 11 | 12 | def send_contact 13 | ContactMailer.send_contact(self).deliver 14 | end 15 | end -------------------------------------------------------------------------------- /db/migrate/20121220053905_create_agents.rb: -------------------------------------------------------------------------------- 1 | class CreateAgents < ActiveRecord::Migration 2 | def change 3 | create_table :agents do |t| 4 | t.integer :user_id 5 | t.text :options 6 | t.string :type 7 | t.string :name 8 | t.string :schedule 9 | t.integer :events_count 10 | t.datetime :last_check_at 11 | t.datetime :last_receive_at 12 | t.integer :last_checked_event_id 13 | 14 | t.timestamps 15 | end 16 | 17 | add_index :agents, [:user_id, :created_at] 18 | add_index :agents, :type 19 | add_index :agents, :schedule 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/assets/javascripts/pages/scenario-show-page.js.coffee: -------------------------------------------------------------------------------- 1 | class @ScenarioShowPage 2 | constructor:() -> 3 | @changeModalText() 4 | 5 | changeModalText: () -> 6 | $('#disable-all').click -> 7 | $('#enable-disable-agents .modal-body').text 'Would you like to disable all agents?' 8 | $('#scenario-disabled-value').val 'true' 9 | $('#enable-all').click -> 10 | $('#enable-disable-agents .modal-body').text 'Would you like to enable all agents?' 11 | $('#scenario-disabled-value').val 'false' 12 | 13 | $ -> 14 | Utils.registerPage(ScenarioShowPage, forPathsMatching: /^scenarios/) 15 | 16 | -------------------------------------------------------------------------------- /lib/time_tracker.rb: -------------------------------------------------------------------------------- 1 | class TimeTracker 2 | attr_accessor :elapsed_time, :result 3 | 4 | def self.track 5 | start = Process.clock_gettime(Process::CLOCK_MONOTONIC) 6 | result = yield 7 | new(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start, result) 8 | end 9 | 10 | def initialize(elapsed_time, result) 11 | @elapsed_time = elapsed_time 12 | @result = result 13 | end 14 | 15 | def method_missing(method_sym, *arguments, &block) 16 | if @result.respond_to?(method_sym) 17 | @result.send(method_sym, *arguments, &block) 18 | else 19 | super 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/features/edit_an_agent_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe "Editing an agent", js: true do 4 | it "creates an alert if a agent with invalid json is submitted" do 5 | login_as(users(:bob)) 6 | visit("/agents/#{agents(:bob_website_agent).id}/edit") 7 | click_on("Toggle View") 8 | 9 | fill_in(:agent_options, with: '{ 10 | "expected_receive_period_in_days": "2" 11 | "keep_event": "false" 12 | }') 13 | expect(get_alert_text_from { click_on "Save" }).to have_text("Sorry, there appears to be an error in your JSON input. Please fix it before continuing.") 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/fixtures/scenario_memberships.yml: -------------------------------------------------------------------------------- 1 | jane_weather_agent_scenario_membership: 2 | agent: jane_weather_agent 3 | scenario: jane_weather 4 | 5 | jane_rain_notifier_agent_scenario_membership: 6 | agent: jane_rain_notifier_agent 7 | scenario: jane_weather 8 | 9 | jane_rain_notifier_agent_scenario_membership_duplicate: 10 | agent: jane_weather_agent 11 | scenario: jane_weather_duplicate 12 | 13 | bob_weather_agent_scenario_membership: 14 | agent: bob_weather_agent 15 | scenario: bob_weather 16 | 17 | bob_rain_notifier_agent_scenario_membership: 18 | agent: bob_rain_notifier_agent 19 | scenario: bob_weather 20 | -------------------------------------------------------------------------------- /deployment/logrotate/huginn: -------------------------------------------------------------------------------- 1 | /home/huginn/huginn/log/*.log { 2 | daily 3 | missingok 4 | rotate 180 5 | # must use with delaycompress below 6 | compress 7 | dateext 8 | 9 | # this is important if using "compress" since we need to call 10 | # the "lastaction" script below before compressing: 11 | delaycompress 12 | 13 | # note the lack of the evil "copytruncate" option in this 14 | # config. Unicorn supports the USR1 signal and we send it 15 | # as our "lastaction" action: 16 | lastaction 17 | pid=/home/huginn/huginn/tmp/pids/unicorn.pid 18 | test -s $pid && kill -USR1 "$(cat $pid)" 19 | endscript 20 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | tmp 3 | log 4 | doc 5 | spec 6 | media 7 | .openshift 8 | .bundle 9 | vendor/bundle 10 | db/*.sqlite3 11 | public/system/* 12 | coverage 13 | spec/tmp/* 14 | .travis.yml 15 | build_docker_image.sh 16 | 17 | # Copied from .gitignore 18 | *.rbc 19 | *.sassc 20 | .sass-cache 21 | capybara-*.html 22 | .rspec 23 | !/tmp/.gitkeep 24 | **.orig 25 | rerun.txt 26 | pickle-email-*.html 27 | .idea/ 28 | .DS_Store 29 | .env 30 | deployment/tmp 31 | deployment/cookbooks 32 | .vagrant 33 | .*un~ 34 | .ruby-gemset 35 | .ruby-version 36 | manifest.yml 37 | config/unicorn.rb 38 | db/schema.rb 39 | nitrous-post-create.sh 40 | nitrous.json 41 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/json-editor.js.coffee.erb: -------------------------------------------------------------------------------- 1 | window.setupJsonEditor = ($editors = $(".live-json-editor")) -> 2 | JSONEditor.prototype.ADD_IMG = '<%= image_path 'json-editor/add.png' %>' 3 | JSONEditor.prototype.DELETE_IMG = '<%= image_path 'json-editor/delete.png' %>' 4 | editors = [] 5 | $editors.each -> 6 | $editor = $(this) 7 | jsonEditor = new JSONEditor($editor, $editor.data('width') || 400, $editor.data('height') || 500) 8 | jsonEditor.doTruncation true 9 | jsonEditor.showFunctionButtons() 10 | editors.push jsonEditor 11 | return editors 12 | 13 | $ -> 14 | window.jsonEditor = setupJsonEditor()[0] 15 | -------------------------------------------------------------------------------- /app/controllers/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | class OmniauthCallbacksController < Devise::OmniauthCallbacksController 2 | def action_missing(name) 3 | case name.to_sym 4 | when *Devise.omniauth_providers 5 | service = current_user.services.initialize_or_update_via_omniauth(request.env['omniauth.auth']) 6 | if service && service.save 7 | redirect_to services_path, notice: "The service was successfully created." 8 | else 9 | redirect_to services_path, error: "Error creating the service." 10 | end 11 | else 12 | raise ActionController::RoutingError, 'not found' 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/jobs/agent_receive_job.rb: -------------------------------------------------------------------------------- 1 | class AgentReceiveJob < ActiveJob::Base 2 | # Given an Agent id and an array of Event ids, load the Agent, call #receive on it with the Event objects, and then 3 | # save it with an updated `last_receive_at` timestamp. 4 | def perform(agent_id, event_ids) 5 | agent = Agent.find(agent_id) 6 | begin 7 | return if agent.unavailable? 8 | agent.receive(Event.where(:id => event_ids).order(:id)) 9 | agent.last_receive_at = Time.now 10 | agent.save! 11 | rescue => e 12 | agent.error "Exception during receive. #{e.message}: #{e.backtrace.join("\n")}" 13 | raise 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/environment.rb: -------------------------------------------------------------------------------- 1 | module Dotenv 2 | # This class inherits from Hash and represents the environemnt into which 3 | # Dotenv will load key value pairs from a file. 4 | class Environment < Hash 5 | attr_reader :filename 6 | 7 | def initialize(filename) 8 | @filename = filename 9 | load 10 | end 11 | 12 | def load 13 | update Parser.call(read) 14 | end 15 | 16 | def read 17 | File.read(@filename) 18 | end 19 | 20 | def apply 21 | each { |k, v| ENV[k] ||= v } 22 | end 23 | 24 | def apply! 25 | each { |k, v| ENV[k] = v } 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/jobs/agent_propagate_job.rb: -------------------------------------------------------------------------------- 1 | class AgentPropagateJob < ActiveJob::Base 2 | queue_as :propagation 3 | 4 | def perform 5 | Agent.receive! 6 | end 7 | 8 | def self.can_enqueue? 9 | if Rails.configuration.active_job.queue_adapter == :delayed_job && 10 | Delayed::Job.where(failed_at: nil, queue: 'propagation').count > 0 11 | return false 12 | elsif Rails.configuration.active_job.queue_adapter == :resque && 13 | (Resque.size('propagation') > 0 || 14 | Resque.workers.select { |w| w.job && w.job['queue'] && w.job['queue']['propagation'] }.count > 0) 15 | return false 16 | end 17 | true 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /config/initializers/mysqlpls.rb: -------------------------------------------------------------------------------- 1 | # see https://github.com/rails/rails/issues/9855#issuecomment-28874587 2 | # circumvents the default InnoDB limitation for index prefix bytes maximum when using proper 4byte UTF8 (utf8mb4) 3 | # (for server-side workaround see http://dev.mysql.com/doc/refman/5.7/en/innodb-parameters.html#sysvar_innodb_large_prefix) 4 | if ENV['ON_HEROKU'].nil? 5 | require 'active_record/connection_adapters/abstract_mysql_adapter' 6 | 7 | module ActiveRecord 8 | module ConnectionAdapters 9 | class AbstractMysqlAdapter 10 | NATIVE_DATABASE_TYPES[:string] = { :name => "varchar", :limit => 191 } 11 | end 12 | end 13 | end 14 | end -------------------------------------------------------------------------------- /spec/data_fixtures/witai.json: -------------------------------------------------------------------------------- 1 | { 2 | "msg_id":"x", 3 | "_text":"x", 4 | "outcomes": [ 5 | { 6 | "_text": "set the temperature to 22 degrees at 7 PM", 7 | "intent": "get_temparature", 8 | "entities": { 9 | "datetime": [ 10 | { 11 | "grain": "hour", 12 | "type": "value", 13 | "value": "2015-03-27T21:00:00.000-07:00" 14 | } 15 | ], 16 | "temperature": [ 17 | { 18 | "type": "value", 19 | "value": 34, 20 | "unit": "degree" 21 | } 22 | ] 23 | }, 24 | "confidence": 0.554 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /spec/fixtures/user_credentials.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 23, 4 | "user_id": 30, 5 | "credential_name": "Google_api_key", 6 | "credential_value": "gcperfxrtqymqmluvskxzyiyxxfnjduzncoukyqehkrkamofwz", 7 | "created_at": "2016-04-01 10:50:59 -0700", 8 | "updated_at": "2016-04-01 10:50:59 -0700", 9 | "mode": "text" 10 | }, 11 | { 12 | "id": 24, 13 | "user_id": 30, 14 | "credential_name": "twitter_secret_key", 15 | "credential_value": "jhpswiebwhbrnabgkbvczrwcyxblxtyvvlvkhuoudjalcqmlwz", 16 | "created_at": "2016-04-01 10:50:59 -0700", 17 | "updated_at": "2016-04-01 10:50:59 -0700", 18 | "mode": "text" 19 | } 20 | ] 21 | -------------------------------------------------------------------------------- /app/helpers/scenario_helper.rb: -------------------------------------------------------------------------------- 1 | module ScenarioHelper 2 | 3 | def style_colors(scenario) 4 | colors = { 5 | color: scenario.tag_fg_color || default_scenario_fg_color, 6 | background_color: scenario.tag_bg_color || default_scenario_bg_color 7 | }.map { |key, value| "#{key.to_s.dasherize}:#{value}" }.join(';') 8 | end 9 | 10 | def scenario_label(scenario, text = nil) 11 | text ||= scenario.name 12 | content_tag :span, text, class: 'label scenario', style: style_colors(scenario) 13 | end 14 | 15 | def default_scenario_bg_color 16 | '#5BC0DE' 17 | end 18 | 19 | def default_scenario_fg_color 20 | '#FFFFFF' 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /db/migrate/20160301113717_add_confirmable_attributes_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddConfirmableAttributesToUsers < ActiveRecord::Migration 2 | def change 3 | change_table(:users) do |t| 4 | ## Confirmable 5 | t.string :confirmation_token 6 | t.datetime :confirmed_at 7 | t.datetime :confirmation_sent_at 8 | t.string :unconfirmed_email # Only if using reconfirmable 9 | end 10 | 11 | add_index :users, :confirmation_token, unique: true 12 | 13 | if ENV['REQUIRE_CONFIRMED_EMAIL'] != 'true' && ActiveRecord::Base.connection.column_exists?(:users, :confirmed_at) 14 | User.update_all('confirmed_at = NOW()') 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20131227000021_add_cached_dates_to_agent.rb: -------------------------------------------------------------------------------- 1 | class AddCachedDatesToAgent < ActiveRecord::Migration 2 | def up 3 | add_column :agents, :last_event_at, :datetime 4 | execute "UPDATE agents SET last_event_at = (SELECT created_at FROM events WHERE events.agent_id = agents.id ORDER BY id DESC LIMIT 1)" 5 | 6 | add_column :agents, :last_error_log_at, :datetime 7 | execute "UPDATE agents SET last_error_log_at = (SELECT created_at FROM agent_logs WHERE agent_logs.agent_id = agents.id AND agent_logs.level >= 4 ORDER BY id DESC LIMIT 1)" 8 | end 9 | 10 | def down 11 | remove_column :agents, :last_event_at 12 | remove_column :agents, :last_error_log_at 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/data_fixtures/imap2.eml: -------------------------------------------------------------------------------- 1 | From: John 2 | Date: Fri, 9 May 2014 17:00:00 +0900 3 | Message-ID: 4 | Subject: Re: some subject 5 | To: Jane , Nanashi 6 | MIME-Version: 1.0 7 | Content-Type: multipart/alternative; boundary=d8c92622e09101e4bc833685557b 8 | 9 | --d8c92622e09101e4bc833685557b 10 | Content-Type: text/plain; charset=UTF-8 11 | 12 | Some reply 13 | 14 | --d8c92622e09101e4bc833685557b 15 | Content-Type: text/html; charset=UTF-8 16 | Content-Transfer-Encoding: quoted-printable 17 | 18 |
Some HTML reply
19 | 20 | --d8c92622e09101e4bc833685557b-- 21 | -------------------------------------------------------------------------------- /spec/features/toggle_visibility_of_disabled_agents.rb: -------------------------------------------------------------------------------- 1 | require 'capybara_helper' 2 | 3 | describe "Toggling the visibility of an agent", js: true do 4 | it "hides them if they are disabled" do 5 | login_as(users(:bob)) 6 | visit("/agents") 7 | 8 | expect { 9 | click_on("Show/Hide Disabled Agents") 10 | }.to change{ find_all(".table-striped tr").count }.by(-1) 11 | end 12 | 13 | it "shows them when they are hidden" do 14 | login_as(users(:bob)) 15 | visit("/agents") 16 | click_on("Show/Hide Disabled Agents") 17 | 18 | expect { 19 | click_on("Show/Hide Disabled Agents") 20 | }.to change{ find_all(".table-striped tr").count }.by(1) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_wells.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Wells 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base class 7 | .well { 8 | min-height: 20px; 9 | padding: 19px; 10 | margin-bottom: 20px; 11 | background-color: $well-bg; 12 | border: 1px solid $well-border; 13 | border-radius: $border-radius-base; 14 | @include box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); 15 | blockquote { 16 | border-color: #ddd; 17 | border-color: rgba(0,0,0,.15); 18 | } 19 | } 20 | 21 | // Sizes 22 | .well-lg { 23 | padding: 24px; 24 | border-radius: $border-radius-large; 25 | } 26 | .well-sm { 27 | padding: 9px; 28 | border-radius: $border-radius-small; 29 | } 30 | -------------------------------------------------------------------------------- /app/views/agents/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 10 | 11 |
12 | <%= render 'form' %> 13 |
14 | 15 |
16 | 17 |
18 |
19 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, agents_path, class: "btn btn-default" %> 20 |
21 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_component-animations.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | @include transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | &.in { 21 | display: block; 22 | } 23 | } 24 | .collapsing { 25 | position: relative; 26 | height: 0; 27 | overflow: hidden; 28 | @include transition(height .35s ease); 29 | } 30 | -------------------------------------------------------------------------------- /spec/models/agents/weather_agent_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Agents::WeatherAgent do 4 | let(:agent) do 5 | Agents::WeatherAgent.create( 6 | name: 'weather', 7 | options: { 8 | :location => 94103, 9 | :lat => 37.779329, 10 | :lng => -122.41915, 11 | :api_key => 'test' 12 | } 13 | ).tap do |agent| 14 | agent.user = users(:bob) 15 | agent.save! 16 | end 17 | end 18 | 19 | it "creates a valid agent" do 20 | expect(agent).to be_valid 21 | end 22 | 23 | describe "#service" do 24 | it "doesn't have a Service object attached" do 25 | expect(agent.service).to be_nil 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/importers/default_scenario_importer.rb: -------------------------------------------------------------------------------- 1 | require 'open-uri' 2 | class DefaultScenarioImporter 3 | def self.import(user) 4 | return unless ENV['IMPORT_DEFAULT_SCENARIO_FOR_ALL_USERS'] == 'true' 5 | seed(user) 6 | end 7 | 8 | def self.seed(user) 9 | scenario_import = ScenarioImport.new() 10 | scenario_import.set_user(user) 11 | scenario_file = ENV['DEFAULT_SCENARIO_FILE'].presence || File.join(Rails.root, "data", "default_scenario.json") 12 | begin 13 | scenario_import.file = open(scenario_file) 14 | raise "Import failed" unless scenario_import.valid? && scenario_import.import 15 | ensure 16 | scenario_import.file.close 17 | end 18 | return true 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/lib/time_tracker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe TimeTracker do 4 | describe "#track" do 5 | it "tracks execution time" do 6 | tracked_result = TimeTracker.track { sleep(0.01) } 7 | expect(tracked_result.elapsed_time).to satisfy {|v| v > 0.01 && v < 0.1} 8 | end 9 | 10 | it "returns the proc return value" do 11 | tracked_result = TimeTracker.track { 42 } 12 | expect(tracked_result.result).to eq(42) 13 | end 14 | 15 | it "returns an object that behaves like the proc result" do 16 | tracked_result = TimeTracker.track { 42 } 17 | expect(tracked_result.to_i).to eq(42) 18 | expect(tracked_result + 1).to eq(43) 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20130126080736_change_memory_to_long_text.rb: -------------------------------------------------------------------------------- 1 | # PG allows arbitrarily long text fields but MySQL has default limits. Make those limits larger if we're using MySQL. 2 | 3 | class ChangeMemoryToLongText < ActiveRecord::Migration 4 | def up 5 | if mysql? 6 | change_column :agents, :memory, :text, :limit => 4294967295 7 | change_column :events, :payload, :text, :limit => 16777215 8 | end 9 | end 10 | 11 | def down 12 | if mysql? 13 | change_column :agents, :memory, :text, :limit => 65535 14 | change_column :events, :payload, :text, :limit => 65535 15 | end 16 | end 17 | 18 | def mysql? 19 | ActiveRecord::Base.connection.adapter_name =~ /mysql/i 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /app/mailers/system_mailer.rb: -------------------------------------------------------------------------------- 1 | class SystemMailer < ActionMailer::Base 2 | default :from => ENV['EMAIL_FROM_ADDRESS'].presence || 'you@example.com' 3 | 4 | def send_message(options) 5 | @groups = options[:groups] 6 | @headline = options[:headline] 7 | @body = options[:body] 8 | 9 | mail_options = { to: options[:to], subject: options[:subject] } 10 | mail_options[:from] = options[:from] if options[:from].present? 11 | if options[:content_type].present? 12 | mail(mail_options) do |format| 13 | format.text if options[:content_type] == "text/plain" 14 | format.html if options[:content_type] == "text/html" 15 | end 16 | else 17 | mail(mail_options) 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /doc/README.md: -------------------------------------------------------------------------------- 1 | # Documentation 2 | 3 | ## Installation and upgrading 4 | 5 | ### Docker (not recommended for production) 6 | 7 | - [Check out Huginn with Docker](docker/install.md) Run a local Huginn installation using Docker 8 | 9 | ### Manual installation 10 | 11 | Manual installation instructions which will guide through the steps to install Huginn on any Ubuntu 12.04/14.04 or Debian 6/7 server. 12 | 13 | - [Install](manual/README.md) Requirements, directory structures and installation from source. 14 | - [Update](manual/update.md) Update your installation. 15 | - Deploy updates via [Capistrano](manual/capistrano.md). 16 | 17 | ### Heroku 18 | 19 | - [Deploy to Heroku](heroku/install.md) 20 | - [Update](heroku/update.md) an existing Heroku deployment -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: $breadcrumb-padding-vertical $breadcrumb-padding-horizontal; 8 | margin-bottom: $line-height-computed; 9 | list-style: none; 10 | background-color: $breadcrumb-bg; 11 | border-radius: $border-radius-base; 12 | 13 | > li { 14 | display: inline-block; 15 | 16 | + li:before { 17 | content: "#{$breadcrumb-separator}\00a0"; // Unicode space added since inline-block means non-collapsing white-space 18 | padding: 0 5px; 19 | color: $breadcrumb-color; 20 | } 21 | } 22 | 23 | > .active { 24 | color: $breadcrumb-active-color; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/dotenv-rails.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path("../lib/dotenv/version", __FILE__) 2 | require "English" 3 | 4 | Gem::Specification.new "dotenv-rails", Dotenv::VERSION do |gem| 5 | gem.authors = ["Brandon Keepers"] 6 | gem.email = ["brandon@opensoul.org"] 7 | gem.description = gem.summary = "Autoload dotenv in Rails." 8 | gem.homepage = "https://github.com/bkeepers/dotenv" 9 | gem.license = "MIT" 10 | gem.files = `git ls-files lib | grep rails` 11 | .split($OUTPUT_RECORD_SEPARATOR) + ["README.md", "LICENSE"] 12 | 13 | gem.add_dependency "dotenv", Dotenv::VERSION 14 | 15 | gem.add_development_dependency "spring" 16 | gem.add_development_dependency "railties", "~>4.0" 17 | end 18 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

We're sorry, but something went wrong.

23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /spec/data_fixtures/imap1.eml: -------------------------------------------------------------------------------- 1 | From: Nanashi 2 | Date: Fri, 9 May 2014 16:00:00 +0900 3 | Message-ID: 4 | Subject: some subject 5 | To: Jane , John 6 | MIME-Version: 1.0 7 | Content-Type: multipart/alternative; boundary=d8c92622e09101e4bc833685557b 8 | 9 | --d8c92622e09101e4bc833685557b 10 | Content-Type: text/plain; charset=UTF-8 11 | 12 | Some plain text 13 | Some second line 14 | 15 | --d8c92622e09101e4bc833685557b 16 | Content-Type: text/html; charset=UTF-8 17 | Content-Transfer-Encoding: quoted-printable 18 | 19 |
Some HTML document
20 | Some second line of HTML
21 | 22 | --d8c92622e09101e4bc833685557b-- 23 | -------------------------------------------------------------------------------- /.openshift/action_hooks/post_stop_ruby-2.0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The pre_start_cartridge and pre_stop_cartridge hooks are *SOURCED* 4 | # immediately before (re)starting or stopping the specified cartridge. 5 | # They are able to make any desired environment variable changes as 6 | # well as other adjustments to the application environment. 7 | 8 | # The post_start_cartridge and post_stop_cartridge hooks are executed 9 | # immediately after (re)starting or stopping the specified cartridge. 10 | 11 | # Exercise caution when adding commands to these hooks. They can 12 | # prevent your application from stopping cleanly or starting at all. 13 | # Application start and stop is subject to different timeouts 14 | # throughout the system. 15 | 16 | echo "-> Post stop ruby step" -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_stop_ruby-2.0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The pre_start_cartridge and pre_stop_cartridge hooks are *SOURCED* 4 | # immediately before (re)starting or stopping the specified cartridge. 5 | # They are able to make any desired environment variable changes as 6 | # well as other adjustments to the application environment. 7 | 8 | # The post_start_cartridge and post_stop_cartridge hooks are executed 9 | # immediately after (re)starting or stopping the specified cartridge. 10 | 11 | # Exercise caution when adding commands to these hooks. They can 12 | # prevent your application from stopping cleanly or starting at all. 13 | # Application start and stop is subject to different timeouts 14 | # throughout the system. 15 | 16 | echo "-> Pre stop ruby step" -------------------------------------------------------------------------------- /app/concerns/working_helpers.rb: -------------------------------------------------------------------------------- 1 | module WorkingHelpers 2 | extend ActiveSupport::Concern 3 | 4 | def event_created_within?(days) 5 | last_event_at && last_event_at > days.to_i.days.ago 6 | end 7 | 8 | def recent_error_logs? 9 | last_event_at && last_error_log_at && last_error_log_at > (last_event_at - 2.minutes) 10 | end 11 | 12 | def received_event_without_error? 13 | (last_receive_at.present? && last_error_log_at.blank?) || (last_receive_at.present? && last_error_log_at.present? && last_receive_at > last_error_log_at) 14 | end 15 | 16 | def checked_without_error? 17 | (last_check_at.present? && last_error_log_at.nil?) || (last_check_at.present? && last_error_log_at.present? && last_check_at > last_error_log_at) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/user_credential.rb: -------------------------------------------------------------------------------- 1 | class UserCredential < ActiveRecord::Base 2 | MODES = %w[text java_script] 3 | 4 | attr_accessible :credential_name, :credential_value, :mode 5 | 6 | belongs_to :user 7 | 8 | validates_presence_of :credential_name 9 | validates_presence_of :credential_value 10 | validates_inclusion_of :mode, :in => MODES 11 | validates_presence_of :user_id 12 | validates_uniqueness_of :credential_name, :scope => :user_id 13 | 14 | before_validation :default_mode_to_text 15 | before_save :trim_fields 16 | 17 | protected 18 | 19 | def trim_fields 20 | credential_name.strip! 21 | credential_value.strip! 22 | end 23 | 24 | def default_mode_to_text 25 | self.mode = 'text' unless mode.present? 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /.openshift/action_hooks/post_start_ruby-2.0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The pre_start_cartridge and pre_stop_cartridge hooks are *SOURCED* 4 | # immediately before (re)starting or stopping the specified cartridge. 5 | # They are able to make any desired environment variable changes as 6 | # well as other adjustments to the application environment. 7 | 8 | # The post_start_cartridge and post_stop_cartridge hooks are executed 9 | # immediately after (re)starting or stopping the specified cartridge. 10 | 11 | # Exercise caution when adding commands to these hooks. They can 12 | # prevent your application from stopping cleanly or starting at all. 13 | # Application start and stop is subject to different timeouts 14 | # throughout the system. 15 | 16 | echo "-> Post start ruby step" -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |

Resend confirmation instructions

6 | 7 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f| %> 8 | <%= devise_error_messages! %> 9 | 10 |
11 | <%= f.label :email %> 12 | <%= f.email_field :email, autofocus: true, class: 'form-control' %> 13 |
14 | 15 | <%= f.submit "Resend confirmation instructions", class: "btn btn-primary" %> 16 | <% end %> 17 | 18 | <%= render "devise/shared/links" %> 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/views/home/_signed_out_index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |

Your agents are standing by

7 |

Huginn monitors the world and acts on your behalf.

8 | 9 | <%= link_to "Login", new_user_session_path, :class => "btn btn-large" %> 10 | <%= link_to "Signup", new_user_registration_path, :class => "btn btn-primary btn-large" %> 11 |
12 | 13 |
14 | <%= image_tag 'odin.jpg', :class => 'img-responsive img-rounded', :title => "Wägner, Wilhelm. 1882. Nordisch-germanische Götter und Helden. Otto Spamer, Leipzig & Berlin. Page 7." %> 15 |
16 | 17 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /docker/single-process/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | MAINTAINER Dominik Sander 3 | 4 | ADD docker/scripts/prepare /scripts/prepare 5 | RUN /scripts/prepare 6 | 7 | WORKDIR /app 8 | 9 | ADD ["Gemfile", "Gemfile.lock", "/app/"] 10 | ADD lib/gemfile_helper.rb /app/lib/ 11 | ADD vendor/gems /app/vendor/gems 12 | 13 | RUN chown -R huginn:huginn /app && \ 14 | sudo -u huginn -H echo "gem 'sqlite3', '~> 1.3.11'" >> /app/Gemfile && \ 15 | sudo -u huginn -H LC_ALL=en_US.UTF-8 RAILS_ENV=production ON_HEROKU=true bundle install --without test development --path vendor/bundle -j 4 16 | COPY . /app 17 | 18 | ADD ["docker/scripts/setup", "docker/single-process/scripts/init", "/scripts/"] 19 | 20 | RUN /scripts/setup 21 | 22 | EXPOSE 3000 23 | 24 | CMD ["/scripts/init"] 25 | -------------------------------------------------------------------------------- /spec/support/shared_examples/file_handling_consumer.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | shared_examples_for 'FileHandlingConsumer' do 4 | it 'returns a file pointer' do 5 | expect(@checker.get_file_pointer('testfile')).to eq(file_pointer: { file: "testfile", agent_id: @checker.id}) 6 | end 7 | 8 | it 'get_io raises an exception when trying to access an agent of a different user' do 9 | @checker2 = @checker.dup 10 | @checker2.user = users(:bob) 11 | @checker2.save! 12 | expect(@checker2.user.id).not_to eq(@checker.user.id) 13 | event = Event.new(user: @checker.user, payload: {'file_pointer' => {'file' => 'test', 'agent_id' => @checker2.id}}) 14 | expect { @checker.get_io(event) }.to raise_error(ActiveRecord::RecordNotFound) 15 | end 16 | end -------------------------------------------------------------------------------- /app/views/system_mailer/send_message.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <% if @body.present? %> 8 | <%= sanitize @body %> 9 | <% else %> 10 | <% if @headline %> 11 |

<%= sanitize @headline %>

12 | <% end %> 13 | <% @groups.each do |group| %> 14 |
15 |
<%= sanitize group[:title] %>
16 | <% group[:entries].each do |entry| %> 17 |
18 | <%= sanitize entry %> 19 |
20 | <% end %> 21 |
22 | <% end %> 23 | <% end %> 24 | 25 | -------------------------------------------------------------------------------- /spec/lib/delayed_job_worker_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe DelayedJobWorker do 4 | before do 5 | @djw = DelayedJobWorker.new 6 | end 7 | 8 | it "should run" do 9 | mock.instance_of(Delayed::Worker).start 10 | @djw.run 11 | end 12 | 13 | it "should stop" do 14 | mock.instance_of(Delayed::Worker).start 15 | mock.instance_of(Delayed::Worker).stop 16 | @djw.run 17 | @djw.stop 18 | end 19 | 20 | context "#setup_worker" do 21 | it "should return an array with an instance of itself" do 22 | workers = DelayedJobWorker.setup_worker 23 | expect(workers).to be_a(Array) 24 | expect(workers.first).to be_a(DelayedJobWorker) 25 | expect(workers.first.id).to eq('DelayedJobWorker') 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /db/migrate/20140722131220_convert_efa_skip_agent.rb: -------------------------------------------------------------------------------- 1 | class ConvertEfaSkipAgent < ActiveRecord::Migration 2 | def up 3 | Agent.where(type: 'Agents::EventFormattingAgent').each do |agent| 4 | agent.options_will_change! 5 | unless agent.options.delete('skip_agent').to_s == 'true' 6 | agent.options['instructions'] = { 7 | 'agent' => '{{agent.type}}' 8 | }.update(agent.options['instructions'] || {}) 9 | end 10 | agent.save! 11 | end 12 | end 13 | 14 | def down 15 | Agent.where(type: 'Agents::EventFormattingAgent').each do |agent| 16 | agent.options_will_change! 17 | agent.options['skip_agent'] = (agent.options['instructions'] || {})['agent'] == '{{agent.type}}' 18 | agent.save! 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/concerns/oauthable.rb: -------------------------------------------------------------------------------- 1 | module Oauthable 2 | extend ActiveSupport::Concern 3 | 4 | included do |base| 5 | @valid_oauth_providers = :all 6 | attr_accessible :service_id 7 | validates_presence_of :service_id 8 | end 9 | 10 | def oauthable? 11 | true 12 | end 13 | 14 | def valid_services_for(user) 15 | if valid_oauth_providers == :all 16 | user.available_services 17 | else 18 | user.available_services.where(provider: valid_oauth_providers) 19 | end 20 | end 21 | 22 | def valid_oauth_providers 23 | self.class.valid_oauth_providers 24 | end 25 | 26 | module ClassMethods 27 | def valid_oauth_providers(*providers) 28 | return @valid_oauth_providers if providers == [] 29 | @valid_oauth_providers = providers 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /db/migrate/20140730005210_convert_efa_skip_created_at.rb: -------------------------------------------------------------------------------- 1 | class ConvertEfaSkipCreatedAt < ActiveRecord::Migration 2 | def up 3 | Agent.where(type: 'Agents::EventFormattingAgent').each do |agent| 4 | agent.options_will_change! 5 | unless agent.options.delete('skip_created_at').to_s == 'true' 6 | agent.options['instructions'] = { 7 | 'created_at' => '{{created_at}}' 8 | }.update(agent.options['instructions'] || {}) 9 | end 10 | agent.save! 11 | end 12 | end 13 | 14 | def down 15 | Agent.where(type: 'Agents::EventFormattingAgent').each do |agent| 16 | agent.options_will_change! 17 | agent.options['skip_created_at'] = (agent.options['instructions'] || {})['created_at'] == '{{created_at}}' 18 | agent.save! 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /docker/single-process/docker-compose.yml: -------------------------------------------------------------------------------- 1 | mysqldata: 2 | image: mysql:5.7 3 | command: /bin/true 4 | 5 | mysql: 6 | image: mysql:5.7 7 | volumes_from: 8 | - mysqldata 9 | environment: 10 | MYSQL_ROOT_PASSWORD: myrootpassword 11 | MYSQL_DATABASE: huginn 12 | MYSQL_USER: huginn 13 | MYSQL_PASSWORD: myhuginnpassword 14 | 15 | huginn_web: 16 | image: cantino/huginn-single-process 17 | restart: always 18 | extends: 19 | file: environment.yml 20 | service: huginn_base 21 | ports: 22 | - 3000:3000 23 | links: 24 | - mysql 25 | 26 | huginn_threaded: 27 | image: cantino/huginn-single-process 28 | restart: always 29 | extends: 30 | file: environment.yml 31 | service: huginn_base 32 | links: 33 | - mysql 34 | command: /scripts/init bin/threaded.rb 35 | 36 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/dotenv.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path("../lib/dotenv/version", __FILE__) 2 | require "English" 3 | 4 | Gem::Specification.new "dotenv", Dotenv::VERSION do |gem| 5 | gem.authors = ["Brandon Keepers"] 6 | gem.email = ["brandon@opensoul.org"] 7 | gem.description = gem.summary = "Loads environment variables from `.env`." 8 | gem.homepage = "https://github.com/bkeepers/dotenv" 9 | gem.license = "MIT" 10 | 11 | gem.files = `git ls-files README.md LICENSE lib bin | grep -v rails` 12 | .split($OUTPUT_RECORD_SEPARATOR) 13 | gem.executables = gem.files.grep(/^bin\//).map { |f| File.basename(f) } 14 | 15 | gem.add_development_dependency "rake" 16 | gem.add_development_dependency "rspec" 17 | gem.add_development_dependency "rubocop" 18 | end 19 | -------------------------------------------------------------------------------- /app/views/agents/dry_runs/index.html.erb: -------------------------------------------------------------------------------- 1 | <% if @events && @events.length > 0 %> 2 |
Recently received events:
3 | <% @events.each do |event| %> 4 | <%= link_to '#', class: 'dry-run-event-sample', 'data-payload' => event.payload.to_json do %> 5 |
<%= truncate event.payload.to_json, :length => 90, :omission => "" %>
6 | <% end %> 7 | <% end %> 8 | <% end %> 9 | 10 |
Event to send<%= params[:with_event_mode] == 'maybe' ? ' (Optional)' : '' %>
11 |
12 |
13 | 16 |
17 |
18 | 19 |
20 |
21 | -------------------------------------------------------------------------------- /spec/jobs/agent_propagate_job_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe AgentPropagateJob do 4 | it "calls Agent.receive! when run" do 5 | mock(Agent).receive! 6 | AgentPropagateJob.new.perform 7 | end 8 | 9 | context "#can_enqueue?" do 10 | it "is truthy when no propagation job is queued" do 11 | expect(AgentPropagateJob.can_enqueue?).to be_truthy 12 | end 13 | 14 | it "is falsy when a progation job is queued" do 15 | Delayed::Job.create!(queue: 'propagation') 16 | expect(AgentPropagateJob.can_enqueue?).to be_falsy 17 | end 18 | 19 | it "is truthy when a enqueued progation job failed" do 20 | Delayed::Job.create!(queue: 'propagation', failed_at: Time.now - 1.minute) 21 | expect(AgentPropagateJob.can_enqueue?).to be_truthy 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /.openshift/cron/weekly/README: -------------------------------------------------------------------------------- 1 | Run scripts or jobs on a weekly basis 2 | ===================================== 3 | Any scripts or jobs added to this directory will be run on a scheduled basis 4 | (weekly) using run-parts. 5 | 6 | run-parts ignores any files that are hidden or dotfiles (.*) or backup 7 | files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} and handles 8 | the files named jobs.deny and jobs.allow specially. 9 | 10 | In this specific example, the chronograph script is the only script or job file 11 | executed on a weekly basis (due to white-listing it in jobs.allow). And the 12 | README and chrono.dat file are ignored either as a result of being black-listed 13 | in jobs.deny or because they are NOT white-listed in the jobs.allow file. 14 | 15 | For more details, please see ../README.cron file. 16 | 17 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The change you wanted was rejected.

23 |

Maybe you tried to change something you didn't have access to.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /spec/models/agents/pdf_agent_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Agents::PdfInfoAgent do 4 | let(:agent) do 5 | _agent = Agents::PdfInfoAgent.new(name: "PDF Info Agent") 6 | _agent.user = users(:bob) 7 | _agent.sources << agents(:bob_website_agent) 8 | _agent.save! 9 | _agent 10 | end 11 | 12 | describe "#receive" do 13 | before do 14 | @event = Event.new(payload: {'url' => 'http://mypdf.com'}) 15 | end 16 | 17 | it "should call HyPDF" do 18 | expect { 19 | mock(agent).open('http://mypdf.com') { "data" } 20 | mock(HyPDF).pdfinfo('data') { {title: "Huginn"} } 21 | agent.receive([@event]) 22 | }.to change { Event.count }.by(1) 23 | event = Event.last 24 | expect(event.payload[:title]).to eq('Huginn') 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/agents/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 10 |
11 |
12 |
13 | <%= render 'form' %> 14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back', agents_path, class: "btn btn-default" %> 22 | <%= link_to icon_tag('glyphicon-asterisk') + ' Show', agent_path(@agent), class: "btn btn-default" %> 23 |
24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /spec/db/seeds/admin_and_default_scenario_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require_relative '../../../db/seeds/seeder' 3 | 4 | describe Seeder do 5 | before do 6 | stub_puts_to_prevent_spew_in_spec_output 7 | end 8 | 9 | describe '.seed' do 10 | it 'imports a default scenario' do 11 | expect { Seeder.seed }.to change(Agent, :count).by(7) 12 | end 13 | 14 | it 'creates an admin' do 15 | expect { Seeder.seed }.to change(User, :count).by(1) 16 | expect(User.last).to be_admin 17 | end 18 | 19 | it 'can be run multiple times and exit normally' do 20 | Seeder.seed 21 | expect { Seeder.seed }.to raise_error(SystemExit) 22 | end 23 | end 24 | 25 | def stub_puts_to_prevent_spew_in_spec_output 26 | stub(Seeder).puts(anything) 27 | stub(Seeder).puts 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/concerns/tumblr_concern.rb: -------------------------------------------------------------------------------- 1 | module TumblrConcern 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include Oauthable 6 | 7 | valid_oauth_providers :tumblr 8 | end 9 | 10 | def tumblr_consumer_key 11 | ENV['TUMBLR_OAUTH_KEY'] 12 | end 13 | 14 | def tumblr_consumer_secret 15 | ENV['TUMBLR_OAUTH_SECRET'] 16 | end 17 | 18 | def tumblr_oauth_token 19 | service.token 20 | end 21 | 22 | def tumblr_oauth_token_secret 23 | service.secret 24 | end 25 | 26 | def tumblr 27 | Tumblr.configure do |config| 28 | config.consumer_key = tumblr_consumer_key 29 | config.consumer_secret = tumblr_consumer_secret 30 | config.oauth_token = tumblr_oauth_token 31 | config.oauth_token_secret = tumblr_oauth_token_secret 32 | end 33 | 34 | Tumblr::Client.new 35 | end 36 | end -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
22 |

The page you were looking for doesn't exist.

23 |

You may have mistyped the address or the page may have moved.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /app/views/admin/users/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for([:admin, @user], html: { class: 'form-horizontal' }) do |f| %> 2 | <%= devise_error_messages! %> 3 | <%= render partial: '/devise/registrations/common_registration_fields', locals: { f: f } %> 4 | 5 |
6 |
7 | <%= f.label :admin do %> 8 | <%= f.check_box :admin %> Admin 9 | <% end %> 10 |
11 |
12 | 13 |
14 |
15 | <%= f.submit class: "btn btn-primary" %> 16 |
17 |
18 | <% end %> 19 | 20 |
21 | 22 |
23 |
24 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, admin_users_path, class: "btn btn-default" %> 25 |
26 |
27 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.assets.enabled = true 4 | Rails.application.config.assets.initialize_on_precompile = false 5 | 6 | # Version of your assets, change this if you want to expire all your assets. 7 | Rails.application.config.assets.version = '1.0' 8 | 9 | # Add additional assets to the asset load path 10 | # Rails.application.config.assets.paths << Emoji.images_path 11 | 12 | # Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added) 13 | Rails.application.config.assets.precompile += %w( diagram.js graphing.js map_marker.js ace.js ) 14 | 15 | Rails.application.config.assets.precompile += %w(*.png *.jpg *.jpeg *.gif) 16 | Rails.application.config.assets.precompile += %w(*.woff *.eot *.svg *.ttf) # Bootstrap fonts 17 | -------------------------------------------------------------------------------- /db/migrate/20140603104211_rename_digest_email_to_email_digest.rb: -------------------------------------------------------------------------------- 1 | class RenameDigestEmailToEmailDigest < ActiveRecord::Migration 2 | def up 3 | sql = <<-SQL 4 | UPDATE #{ActiveRecord::Base.connection.quote_table_name('agents')} 5 | SET #{ActiveRecord::Base.connection.quote_column_name('type')} = 'Agents::EmailDigestAgent' 6 | WHERE #{ActiveRecord::Base.connection.quote_column_name('type')} = 'Agents::DigestEmailAgent' 7 | SQL 8 | 9 | execute sql 10 | end 11 | 12 | def down 13 | sql = <<-SQL 14 | UPDATE #{ActiveRecord::Base.connection.quote_table_name('agents')} 15 | SET #{ActiveRecord::Base.connection.quote_column_name('type')} = 'Agents::DigestEmailAgent' 16 | WHERE #{ActiveRecord::Base.connection.quote_column_name('type')} = 'Agents::EmailDigestAgent' 17 | SQL 18 | 19 | execute sql 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_start_ruby-2.0: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # The pre_start_cartridge and pre_stop_cartridge hooks are *SOURCED* 4 | # immediately before (re)starting or stopping the specified cartridge. 5 | # They are able to make any desired environment variable changes as 6 | # well as other adjustments to the application environment. 7 | 8 | # The post_start_cartridge and post_stop_cartridge hooks are executed 9 | # immediately after (re)starting or stopping the specified cartridge. 10 | 11 | # Exercise caution when adding commands to these hooks. They can 12 | # prevent your application from stopping cleanly or starting at all. 13 | # Application start and stop is subject to different timeouts 14 | # throughout the system. 15 | 16 | echo "-> Pro start ruby step" 17 | 18 | if [ -f ${OPENSHIFT_REPO_DIR}/.env ] 19 | then 20 | source ${OPENSHIFT_REPO_DIR}/.env 21 | fi 22 | -------------------------------------------------------------------------------- /spec/controllers/admin/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Admin::UsersController do 4 | describe 'POST #create' do 5 | context 'with valid user params' do 6 | it 'imports the default scenario for the new user' do 7 | mock(DefaultScenarioImporter).import(is_a(User)) 8 | sign_in users(:jane) 9 | post :create, :user => {username: 'jdoe', email: 'jdoe@example.com', 10 | password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false } 11 | end 12 | end 13 | 14 | context 'with invalid user params' do 15 | it 'does not import the default scenario' do 16 | stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" } 17 | sign_in users(:jane) 18 | post :create, :user => {} 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/concerns/liquid_droppable_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe LiquidDroppable do 4 | before do 5 | class DroppableTest 6 | include LiquidDroppable 7 | 8 | def initialize(value) 9 | @value = value 10 | end 11 | 12 | attr_reader :value 13 | 14 | def to_s 15 | "[value:#{value}]" 16 | end 17 | end 18 | 19 | class DroppableTestDrop 20 | def value 21 | @object.value 22 | end 23 | end 24 | end 25 | 26 | describe 'test class' do 27 | it 'should be droppable' do 28 | five = DroppableTest.new(5) 29 | expect(five.to_liquid.class).to eq(DroppableTestDrop) 30 | expect(Liquid::Template.parse('{{ x.value | plus:3 }}').render('x' => five)).to eq('8') 31 | expect(Liquid::Template.parse('{{ x }}').render('x' => five)).to eq('[value:5]') 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/views/agents/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 | 8 | <%= render 'agents/table' %> 9 | 10 |
11 | 12 |
13 | <%= link_to icon_tag('glyphicon-plus') + ' New Agent', new_agent_path, class: "btn btn-default" %> 14 | <%= link_to icon_tag('glyphicon-refresh') + ' Run event propagation', propagate_agents_path, method: 'post', class: "btn btn-default" %> 15 | <%= link_to icon_tag('glyphicon-random') + ' View diagram', diagram_path, class: "btn btn-default" %> 16 | <%= link_to icon_tag('glyphicon-adjust') + toggle_disabled_text, toggle_visibility_agents_path, method: :put, class: "btn btn-default visibility-enabler" %> 17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /spec/controllers/scenario_imports_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe ScenarioImportsController do 4 | before do 5 | sign_in users(:bob) 6 | end 7 | 8 | describe "GET new" do 9 | it "initializes a new ScenarioImport and renders new" do 10 | get :new 11 | expect(assigns(:scenario_import)).to be_a(ScenarioImport) 12 | expect(response).to render_template(:new) 13 | end 14 | end 15 | 16 | describe "POST create" do 17 | it "initializes a ScenarioImport for current_user, passing in params" do 18 | post :create, :scenario_import => { :url => "bad url" } 19 | expect(assigns(:scenario_import).user).to eq(users(:bob)) 20 | expect(assigns(:scenario_import).url).to eq("bad url") 21 | expect(assigns(:scenario_import)).not_to be_valid 22 | expect(response).to render_template(:new) 23 | end 24 | end 25 | end 26 | 27 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/cli.rb: -------------------------------------------------------------------------------- 1 | require "dotenv" 2 | 3 | module Dotenv 4 | # The CLI is a class responsible of handling all the command line interface 5 | # logic. 6 | class CLI 7 | attr_reader :argv 8 | 9 | def initialize(argv = []) 10 | @argv = argv.dup 11 | end 12 | 13 | def run 14 | filenames = parse_filenames || [] 15 | begin 16 | Dotenv.load!(*filenames) 17 | rescue Errno::ENOENT => e 18 | abort e.message 19 | else 20 | exec(*argv) unless argv.empty? 21 | end 22 | end 23 | 24 | private 25 | 26 | def parse_filenames 27 | pos = argv.index("-f") 28 | return nil unless pos 29 | # drop the -f 30 | argv.delete_at pos 31 | # parse one or more comma-separated .env files 32 | require "csv" 33 | CSV.parse_line argv.delete_at(pos) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/concerns/weibo_concern.rb: -------------------------------------------------------------------------------- 1 | module WeiboConcern 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | gem_dependency_check { defined?(WeiboOAuth2) } 6 | 7 | self.validate :validate_weibo_options 8 | end 9 | 10 | def validate_weibo_options 11 | unless options['app_key'].present? && 12 | options['app_secret'].present? && 13 | options['access_token'].present? 14 | errors.add(:base, "app_key, app_secret and access_token are required") 15 | end 16 | end 17 | 18 | def weibo_client 19 | unless @weibo_client 20 | WeiboOAuth2::Config.api_key = options['app_key'] # WEIBO_APP_KEY 21 | WeiboOAuth2::Config.api_secret = options['app_secret'] # WEIBO_APP_SECRET 22 | @weibo_client = WeiboOAuth2::Client.new 23 | @weibo_client.get_token_from_hash :access_token => options['access_token'] 24 | end 25 | @weibo_client 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | Huginn Docker images 2 | ==================== 3 | 4 | Huginn is packaged in two docker images. 5 | 6 | #### [cantino/huginn](multi-process/README.md) multiple process image 7 | 8 | This image runs all processes needed by Huginn in one container, when the database is not linked it will also start MySQL internally. This is great to try huginn without having to set up anything, however maintenance and backups can be difficult. 9 | 10 | #### [cantino/huginn-single-process](single-process/README.md) multiple container image 11 | 12 | This image runs just one process per container and thus needs at least two container to be started, one for the Huginn application server and one for the threaded background worker. It is also possible to every background worker in a separate container to improve the performance. See [the PostgreSQL docker-compose configuration](single-process/postgresql.yml) for an example. 13 | -------------------------------------------------------------------------------- /spec/data_fixtures/basecamp.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "creator": { 4 | "fullsize_avatar_url": "https://dge9rmgqjs8m1.cloudfront.net/global/dfsdfsdfdsf/original.gif?r=3", 5 | "avatar_url": "http://dge9rmgqjs8m1.cloudfront.net/global/dfsdfsdfdsf/avatar.gif?r=3", 6 | "name": "Dominik Sander", 7 | "id": 123456 8 | }, 9 | "attachments": [], 10 | "raw_excerpt": "test test", 11 | "excerpt": "test test", 12 | "id": 6454342343, 13 | "created_at": "2014-04-17T10:25:31.000+02:00", 14 | "updated_at": "2014-04-17T10:25:31.000+02:00", 15 | "summary": "commented on whaat", 16 | "action": "commented on", 17 | "target": "whaat", 18 | "url": "https://basecamp.com/12456/api/v1/projects/5476464-explore-basecamp/messages/24598238-whaat.json", 19 | "html_url": "https://basecamp.com/12456/projects/5476464-explore-basecamp/messages/24598238-whaat#comment_150756301" 20 | } 21 | ] 22 | -------------------------------------------------------------------------------- /app/assets/stylesheets/tables.css.scss: -------------------------------------------------------------------------------- 1 | // Sortable table headers 2 | .table th a.selected { 3 | position: relative; 4 | text-decoration: underline; 5 | 6 | &.asc:after, &.desc:after { 7 | text-decoration: none; 8 | position: absolute; 9 | top: 0; 10 | right: -1em; 11 | font-family: FontAwesome; 12 | } 13 | 14 | &.asc:after { 15 | content: '\f0de'; //fa-sort-asc 16 | } 17 | 18 | &.desc:after { 19 | content: '\f0dd'; //fa-sort-desc 20 | } 21 | } 22 | 23 | .table-striped > tbody > tr.hl { 24 | &:nth-child(odd) { 25 | > td, > th { 26 | background-color: #ffeecc; 27 | } 28 | } 29 | 30 | &:nth-child(even) { 31 | > td, > th { 32 | background-color: #f9e8c6; 33 | } 34 | } 35 | } 36 | 37 | table.events { 38 | .payload { 39 | color: #999; 40 | font-size: 12px; 41 | //text-align: center; 42 | font-family: monospace; 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /spec/models/agents/weibo_user_agent_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require 'rails_helper' 3 | 4 | describe Agents::WeiboUserAgent do 5 | before do 6 | # intercept the twitter API request for @tectonic's user profile 7 | stub_request(:any, /api.weibo.com/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/one_weibo.json")), :status => 200) 8 | 9 | @opts = { 10 | :uid => "123456", 11 | :expected_update_period_in_days => "2", 12 | :app_key => "asdfe", 13 | :app_secret => "asdfe", 14 | :access_token => "asdfe" 15 | } 16 | 17 | @checker = Agents::WeiboUserAgent.new(:name => "123456 fetcher", :options => @opts) 18 | @checker.user = users(:bob) 19 | @checker.save! 20 | end 21 | 22 | describe "#check" do 23 | it "should check for changes" do 24 | expect { @checker.check }.to change { Event.count }.by(1) 25 | end 26 | end 27 | 28 | end 29 | -------------------------------------------------------------------------------- /docker/multi-process/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | MAINTAINER Andrew Cantino 3 | 4 | ADD docker/scripts/prepare /scripts/prepare 5 | RUN /scripts/prepare 6 | 7 | ADD docker/multi-process/scripts/standalone-packages /scripts/standalone-packages 8 | RUN /scripts/standalone-packages 9 | 10 | WORKDIR /app 11 | 12 | ADD ["Gemfile", "Gemfile.lock", "/app/"] 13 | ADD lib/gemfile_helper.rb /app/lib/ 14 | ADD vendor/gems /app/vendor/gems 15 | 16 | RUN chown -R huginn:huginn /app && \ 17 | sudo -u huginn -H echo "gem 'sqlite3', '~> 1.3.11'" >> /app/Gemfile && \ 18 | sudo -u huginn -H LC_ALL=en_US.UTF-8 RAILS_ENV=production ON_HEROKU=true bundle install --without test development --path vendor/bundle -j 4 19 | COPY . /app 20 | 21 | ADD ["docker/scripts/setup", "docker/multi-process/scripts/init", "/scripts/"] 22 | RUN /scripts/setup 23 | 24 | VOLUME /var/lib/mysql 25 | 26 | EXPOSE 3000 27 | 28 | CMD ["/scripts/init"] 29 | 30 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_close.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: ($font-size-base * 1.5); 9 | font-weight: $close-font-weight; 10 | line-height: 1; 11 | color: $close-color; 12 | text-shadow: $close-text-shadow; 13 | @include opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: $close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | @include opacity(.5); 21 | } 22 | 23 | // [converter] extracted button& to button.close 24 | } 25 | 26 | // Additional properties for button version 27 | // iOS requires the button element instead of an anchor tag. 28 | // If you want the anchor version, it requires `href="#"`. 29 | button.close { 30 | padding: 0; 31 | cursor: pointer; 32 | background: transparent; 33 | border: 0; 34 | -webkit-appearance: none; 35 | } 36 | -------------------------------------------------------------------------------- /.openshift/action_hooks/pre_build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This is a simple script and will be executed on your CI system if 3 | # available. Otherwise it will execute while your application is stopped 4 | # before the build step. This script gets executed directly, so it 5 | # could be python, php, ruby, etc. 6 | 7 | echo "-> Pre-build step" 8 | 9 | STORED_ASSETS="${OPENSHIFT_DATA_DIR}/assets" 10 | LIVE_ASSETS="${OPENSHIFT_REPO_DIR}/public/assets" 11 | 12 | # Ensure our stored assets directory exists 13 | if [ ! -d "${STORED_ASSETS}" ]; then 14 | echo " Creating permanent assets directory" 15 | mkdir "${STORED_ASSETS}" 16 | fi 17 | 18 | # Create symlink to stored assets unless we're uploading our own assets 19 | if [ -d "${LIVE_ASSETS}" ]; then 20 | echo " WARNING: Assets included in git repository, not using stored assets" 21 | else 22 | echo " Restoring stored assets" 23 | ln -s "${STORED_ASSETS}" "${LIVE_ASSETS}" 24 | fi 25 | -------------------------------------------------------------------------------- /app/assets/javascripts/diagram.js.coffee: -------------------------------------------------------------------------------- 1 | # This is not included in the core application.js bundle. 2 | 3 | $ -> 4 | svg = document.querySelector('.agent-diagram svg.diagram') 5 | overlay = document.querySelector('.agent-diagram .overlay') 6 | $(overlay).width($(svg).width()).height($(svg).height()) 7 | getTopLeft = (node) -> 8 | bbox = node.getBBox() 9 | point = svg.createSVGPoint() 10 | point.x = bbox.x + bbox.width 11 | point.y = bbox.y 12 | point.matrixTransform(node.getCTM()) 13 | $(svg).find('g.node[data-badge-id]').each -> 14 | tl = getTopLeft(this) 15 | $('#' + this.getAttribute('data-badge-id'), overlay).each -> 16 | badge = $(this) 17 | badge.css 18 | left: tl.x - badge.outerWidth() * (2/3) 19 | top: tl.y - badge.outerHeight() * (1/3) 20 | 'background-color': badge.find('.label').css('background-color') 21 | .show() 22 | return 23 | return 24 | -------------------------------------------------------------------------------- /db/migrate/20160307085545_warn_about_duplicate_usernames.rb: -------------------------------------------------------------------------------- 1 | class WarnAboutDuplicateUsernames < ActiveRecord::Migration 2 | def up 3 | names = User.group('LOWER(username)').having('count(*) > 1').pluck('LOWER(username)') 4 | if names.length > 0 5 | puts "-----------------------------------------------------" 6 | puts "--------------------- WARNiNG -----------------------" 7 | puts "-------- Found users with duplicate usernames -------" 8 | puts "-----------------------------------------------------" 9 | puts "For the users to log in using their username they have to change it to a unique name" 10 | names.each do |name| 11 | puts 12 | puts "'#{name}' is used multiple times:" 13 | User.where(['LOWER(username) = ?', name]).each do |u| 14 | puts "#{u.id}\t#{u.email}" 15 | end 16 | end 17 | puts 18 | puts 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /doc/deployment/unicorn/production.rb: -------------------------------------------------------------------------------- 1 | app_path = "/home/you/app/current" 2 | 3 | worker_processes 2 4 | preload_app true 5 | timeout 180 6 | listen '/home/you/app/shared/pids/unicorn.socket' 7 | 8 | working_directory app_path 9 | 10 | rails_env = ENV['RAILS_ENV'] || 'production' 11 | 12 | # Log everything to one file 13 | stderr_path "log/unicorn.log" 14 | stdout_path "log/unicorn.log" 15 | 16 | # Set master PID location 17 | pid '/home/you/app/shared/pids/unicorn.pid' 18 | 19 | before_fork do |server, worker| 20 | ActiveRecord::Base.connection.disconnect! 21 | old_pid = "#{server.config[:pid]}.oldbin" 22 | if File.exist?(old_pid) && server.pid != old_pid 23 | begin 24 | Process.kill("QUIT", File.read(old_pid).to_i) 25 | rescue Errno::ENOENT, Errno::ESRCH 26 | # someone else did our job for us 27 | end 28 | end 29 | end 30 | 31 | after_fork do |server, worker| 32 | ActiveRecord::Base.establish_connection 33 | end 34 | -------------------------------------------------------------------------------- /app/assets/javascripts/pages/user-credential-page.js.coffee: -------------------------------------------------------------------------------- 1 | class @UserCredentialPage 2 | constructor: -> 3 | editor = ace.edit("ace-credential-value") 4 | editor.getSession().setTabSize(2) 5 | editor.getSession().setUseSoftTabs(true) 6 | editor.getSession().setUseWrapMode(false) 7 | 8 | setMode = -> 9 | mode = $("#user_credential_mode").val() 10 | if mode == 'java_script' 11 | editor.getSession().setMode("ace/mode/javascript") 12 | else 13 | editor.getSession().setMode("ace/mode/text") 14 | 15 | setMode() 16 | $("#user_credential_mode").on 'change', setMode 17 | 18 | $textarea = $('#user_credential_credential_value').hide() 19 | editor.getSession().setValue($textarea.val()) 20 | 21 | $textarea.closest('form').on 'submit', -> 22 | $textarea.val(editor.getSession().getValue()) 23 | 24 | $ -> 25 | Utils.registerPage(UserCredentialPage, forPathsMatching: /^user_credentials\/\d+/) 26 | -------------------------------------------------------------------------------- /spec/data_fixtures/urlTest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /config/unicorn.rb.example: -------------------------------------------------------------------------------- 1 | wd = File.expand_path(File.join(File.dirname(__FILE__), '..')) 2 | 3 | app_path = wd 4 | 5 | worker_processes 2 6 | preload_app true 7 | timeout 180 8 | listen "#{wd}/tmp/sockets/unicorn.socket" 9 | 10 | working_directory app_path 11 | 12 | rails_env = ENV['RAILS_ENV'] || 'production' 13 | 14 | # Log everything to one file 15 | stderr_path "log/unicorn.log" 16 | stdout_path "log/unicorn.log" 17 | 18 | # Set master PID location 19 | pid "#{wd}/tmp/pids/unicorn.pid" 20 | 21 | before_fork do |server, worker| 22 | ActiveRecord::Base.connection.disconnect! 23 | old_pid = "#{server.config[:pid]}.oldbin" 24 | if File.exist?(old_pid) && server.pid != old_pid 25 | begin 26 | Process.kill("QUIT", File.read(old_pid).to_i) 27 | rescue Errno::ENOENT, Errno::ESRCH 28 | # someone else did our job for us 29 | end 30 | end 31 | end 32 | 33 | after_fork do |server, worker| 34 | ActiveRecord::Base.establish_connection 35 | end 36 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/substitutions/variable.rb: -------------------------------------------------------------------------------- 1 | require "English" 2 | 3 | module Dotenv 4 | module Substitutions 5 | # Substitute variables in a value. 6 | # 7 | # HOST=example.com 8 | # URL="https://$HOST" 9 | # 10 | module Variable 11 | class << self 12 | VARIABLE = / 13 | (\\)? # is it escaped with a backslash? 14 | (\$) # literal $ 15 | \{? # allow brace wrapping 16 | ([A-Z0-9_]+) # match the variable 17 | \}? # closing brace 18 | /xi 19 | 20 | def call(value, env) 21 | value.gsub(VARIABLE) do |variable| 22 | match = $LAST_MATCH_INFO 23 | 24 | if match[1] == '\\' 25 | variable[1..-1] 26 | else 27 | env.fetch(match[3]) { ENV[match[3]] } 28 | end 29 | end 30 | end 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /spec/controllers/omniauth_callbacks_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe OmniauthCallbacksController do 4 | before do 5 | sign_in users(:bob) 6 | OmniAuth.config.test_mode = true 7 | request.env["devise.mapping"] = Devise.mappings[:user] 8 | request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json'))) 9 | end 10 | 11 | describe "accepting a callback url" do 12 | it "should update the user's credentials" do 13 | expect { 14 | get :twitter 15 | }.to change { users(:bob).services.count }.by(1) 16 | end 17 | 18 | # it "should work with an unknown provider (for now)" do 19 | # request.env["omniauth.auth"]['provider'] = 'unknown' 20 | # expect { 21 | # get :unknown 22 | # }.to change { users(:bob).services.count }.by(1) 23 | # expect(users(:bob).services.first.provider).to eq('unknown') 24 | # end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/seeds/seeder.rb: -------------------------------------------------------------------------------- 1 | class Seeder 2 | def self.seed 3 | user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'] || "admin@example.com") 4 | if user.persisted? 5 | puts "User with email '#{user.email}' already exists, not seeding." 6 | exit 7 | end 8 | 9 | user.username = ENV['SEED_USERNAME'] || "admin" 10 | user.password = ENV['SEED_PASSWORD'] || "password" 11 | user.password_confirmation = ENV['SEED_PASSWORD'] || "password" 12 | user.invitation_code = User::INVITATION_CODES.first 13 | user.admin = true 14 | user.save! 15 | 16 | if DefaultScenarioImporter.seed(user) 17 | puts "NOTE: The example 'SF Weather Agent' will not work until you edit it and put in a free API key from http://www.wunderground.com/weather/api/" 18 | puts "See the Huginn Wiki for more Agent examples! https://github.com/cantino/huginn/wiki" 19 | else 20 | raise('Unable to import the default scenario') 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Huginn", 3 | "description": "Build agents that monitor and act on your behalf. Your agents are standing by!", 4 | "website": "https://github.com/cantino/huginn", 5 | "repository": "https://github.com/cantino/huginn", 6 | "logo": "https://raw.githubusercontent.com/cantino/huginn/master/media/huginn-icon-64.png", 7 | "env": { 8 | "BUILDPACK_URL": "https://github.com/heroku/heroku-buildpack-multi.git", 9 | "APP_SECRET_TOKEN": { 10 | "generator": "secret" 11 | }, 12 | "PROCFILE_PATH": "deployment/heroku/Procfile.heroku", 13 | "ON_HEROKU": "true", 14 | "FORCE_SSL": "true", 15 | "INVITATION_CODE": { 16 | "generator": "secret" 17 | }, 18 | "USE_GRAPHVIZ_DOT": "dot" 19 | }, 20 | "scripts": { 21 | "postdeploy": "bundle exec rake db:migrate" 22 | }, 23 | "addons": ["heroku-postgresql"], 24 | "success_url": "/users/sign_up" 25 | } 26 | -------------------------------------------------------------------------------- /app/concerns/dropbox_concern.rb: -------------------------------------------------------------------------------- 1 | module DropboxConcern 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include Oauthable 6 | valid_oauth_providers :dropbox 7 | gem_dependency_check { defined?(Dropbox) && Devise.omniauth_providers.include?(:dropbox) } 8 | end 9 | 10 | def dropbox 11 | Dropbox::API::Config.app_key = consumer_key 12 | Dropbox::API::Config.app_secret = consumer_secret 13 | Dropbox::API::Config.mode = 'dropbox' 14 | Dropbox::API::Client.new(token: oauth_token, secret: oauth_token_secret) 15 | end 16 | 17 | private 18 | 19 | def consumer_key 20 | (config = Devise.omniauth_configs[:dropbox]) && config.strategy.consumer_key 21 | end 22 | 23 | def consumer_secret 24 | (config = Devise.omniauth_configs[:dropbox]) && config.strategy.consumer_secret 25 | end 26 | 27 | def oauth_token 28 | service && service.token 29 | end 30 | 31 | def oauth_token_secret 32 | service && service.secret 33 | end 34 | 35 | end -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end -%> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end -%> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end -%> 20 | -------------------------------------------------------------------------------- /spec/models/concerns/oauthable.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | module Agents 4 | class OauthableTestAgent < Agent 5 | include Oauthable 6 | end 7 | end 8 | 9 | shared_examples_for Oauthable do 10 | before(:each) do 11 | @agent = described_class.new(:name => "somename") 12 | @agent.user = users(:jane) 13 | end 14 | 15 | it "should be oauthable" do 16 | expect(@agent.oauthable?).to eq(true) 17 | end 18 | 19 | describe "valid_services_for" do 20 | it "should return all available services without specifying valid_oauth_providers" do 21 | @agent = Agents::OauthableTestAgent.new 22 | expect(@agent.valid_services_for(users(:bob)).collect(&:id).sort).to eq([services(:generic), services(:global)].collect(&:id).sort) 23 | end 24 | 25 | it "should filter the services based on the agent defaults" do 26 | expect(@agent.valid_services_for(users(:bob)).to_a).to eq(Service.where(provider: @agent.valid_oauth_providers)) 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /db/migrate/20140213053001_add_event_id_at_creation_to_links.rb: -------------------------------------------------------------------------------- 1 | class AddEventIdAtCreationToLinks < ActiveRecord::Migration 2 | class Link < ActiveRecord::Base; end 3 | class Event < ActiveRecord::Base; end 4 | 5 | def up 6 | add_column :links, :event_id_at_creation, :integer, :null => false, :default => 0 7 | 8 | Link.all.find_each do |link| 9 | last_event_id = execute( 10 | <<-SQL 11 | SELECT #{ActiveRecord::Base.connection.quote_column_name('id')} 12 | FROM #{ActiveRecord::Base.connection.quote_table_name('events')} 13 | WHERE events.agent_id = #{link.source_id} ORDER BY events.id DESC limit 1 14 | SQL 15 | ).first.to_a.first 16 | if last_event_id.nil? 17 | link.event_id_at_creation = Event.last.id 18 | else 19 | link.event_id_at_creation = last_event_id 20 | end 21 | link.save 22 | end 23 | end 24 | 25 | def down 26 | remove_column :links, :event_id_at_creation 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20140723110551_adopt_xpath_in_website_agent.rb: -------------------------------------------------------------------------------- 1 | class AdoptXpathInWebsiteAgent < ActiveRecord::Migration 2 | class Agent < ActiveRecord::Base 3 | include JSONSerializedField 4 | json_serialize :options 5 | end 6 | 7 | def up 8 | Agent.where(type: 'Agents::WebsiteAgent').each do |agent| 9 | extract = agent.options['extract'] 10 | next unless extract.is_a?(Hash) && extract.all? { |name, detail| 11 | detail.key?('xpath') || detail.key?('css') 12 | } 13 | 14 | agent.options_will_change! 15 | agent.options['extract'].each { |name, extraction| 16 | case 17 | when extraction.delete('text') 18 | extraction['value'] = './/text()' 19 | when attr = extraction.delete('attr') 20 | extraction['value'] = "@#{attr}" 21 | end 22 | } 23 | agent.save! 24 | end 25 | end 26 | 27 | def down 28 | raise ActiveRecord::IrreversibleMigration, "Cannot revert this migration" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Resend unlock instructions

5 | 6 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f| %> 7 | <%= devise_error_messages! %> 8 | 9 |
10 | <%= f.label :email, class: 'col-md-2 col-md-offset-2 control-label' %> 11 |
12 | <%= f.email_field :email, autofocus: true, class: 'form-control' %> 13 |
14 |
15 | 16 |
17 |
18 | <%= f.submit "Resend unlock instructions", class: "btn btn-primary" %> 19 |
20 |
21 | <% end %> 22 | 23 |
24 | 25 | <%= render "devise/shared/links" %> 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Huginn", 3 | "icons": [ 4 | { 5 | "src": "/android-chrome-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "/android-chrome-72x72.png", 12 | "sizes": "72x72", 13 | "type": "image/png", 14 | "density": "1.5" 15 | }, 16 | { 17 | "src": "/android-chrome-144x144.png", 18 | "sizes": "144x144", 19 | "type": "image/png", 20 | "density": "3.0" 21 | }, 22 | { 23 | "src": "/android-chrome-48x48.png", 24 | "sizes": "48x48", 25 | "type": "image/png", 26 | "density": "1.0" 27 | }, 28 | { 29 | "src": "/android-chrome-96x96.png", 30 | "sizes": "96x96", 31 | "type": "image/png", 32 | "density": "2.0" 33 | }, 34 | { 35 | "src": "/android-chrome-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Forgot your password?

5 | 6 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f| %> 7 | <%= devise_error_messages! %> 8 | 9 |
10 | <%= f.label :login, :class => 'col-md-2 col-md-offset-2 control-label' %> 11 |
12 | <%= f.text_field :login, autofocus: true, :class => 'form-control' %> 13 |
14 |
15 | 16 |
17 |
18 | <%= f.submit "Send me reset password instructions", class: "btn btn-primary" %> 19 |
20 |
21 | <% end %> 22 | 23 |
24 | 25 | <%= render "devise/shared/links" %> 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /spec/models/agents/commander_agent_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe Agents::CommanderAgent do 4 | let(:valid_params) { 5 | { 6 | name: 'Example', 7 | schedule: 'every_1h', 8 | options: { 9 | 'action' => 'run', 10 | }, 11 | } 12 | } 13 | 14 | let(:agent) { 15 | described_class.create!(valid_params) { |agent| 16 | agent.user = users(:bob) 17 | } 18 | } 19 | 20 | it_behaves_like AgentControllerConcern 21 | 22 | describe "check" do 23 | it "should command targets" do 24 | stub(agent).control!.once { nil } 25 | agent.check 26 | end 27 | end 28 | 29 | describe "receive_events" do 30 | it "should command targets" do 31 | stub(agent).control!.once { nil } 32 | 33 | event = Event.new 34 | event.agent = agents(:bob_rain_notifier_agent) 35 | event.payload = { 36 | 'url' => 'http://xkcd.com', 37 | 'link' => 'Random', 38 | } 39 | agent.receive([event]) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /docker/multi-process/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This needs at least compose 1.6.0 2 | version: '2' 3 | 4 | services: 5 | mysqldata: 6 | image: mysql:5.7 7 | command: /bin/true 8 | 9 | mysql: 10 | image: mysql:5.7 11 | volumes_from: 12 | - mysqldata 13 | ports: 14 | - "3306:3306" 15 | environment: 16 | MYSQL_ROOT_PASSWORD: myrootpassword 17 | MYSQL_DATABASE: huginn 18 | MYSQL_USER: huginn 19 | MYSQL_PASSWORD: myhuginnpassword 20 | 21 | huginn: 22 | build: 23 | context: ../../ 24 | dockerfile: docker/multi-process/Dockerfile 25 | restart: always 26 | environment: 27 | HUGINN_DATABASE_NAME: huginn 28 | HUGINN_DATABASE_USERNAME: root 29 | HUGINN_DATABASE_PASSWORD: myrootpassword 30 | INTENTIONALLY_SLEEP: 10 31 | #DATABASE_INITIAL_CONNECT_MAX_RETRIES: 5 32 | PORT: 3000 33 | MYSQL_PORT_3306_TCP_ADDR: mysql 34 | MYSQL_PORT_3306_TCP_PORT: 3306 35 | ports: 36 | - 3000:3000 37 | links: 38 | - mysql 39 | -------------------------------------------------------------------------------- /spec/helpers/jobs_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe JobsHelper do 4 | let(:job) { Delayed::Job.new } 5 | 6 | describe '#status' do 7 | it "works for failed jobs" do 8 | job.failed_at = Time.now 9 | expect(status(job)).to eq('failed') 10 | end 11 | 12 | it "works for running jobs" do 13 | job.locked_at = Time.now 14 | job.locked_by = 'test' 15 | expect(status(job)).to eq('running') 16 | end 17 | 18 | it "works for queued jobs" do 19 | expect(status(job)).to eq('queued') 20 | end 21 | end 22 | 23 | describe '#relative_distance_of_time_in_words' do 24 | it "in the past" do 25 | expect(relative_distance_of_time_in_words(Time.now-5.minutes)).to eq('5m ago') 26 | end 27 | 28 | it "in the future" do 29 | expect(relative_distance_of_time_in_words(Time.now+5.minutes)).to eq('in 5m') 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /app/views/scenarios/_enable_agents_modal.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /app/controllers/services_controller.rb: -------------------------------------------------------------------------------- 1 | class ServicesController < ApplicationController 2 | include SortableTable 3 | 4 | before_action :upgrade_warning, only: :index 5 | 6 | def index 7 | set_table_sort sorts: %w[provider name global], default: { provider: :asc } 8 | 9 | @services = current_user.services.reorder(table_sort).page(params[:page]) 10 | 11 | respond_to do |format| 12 | format.html 13 | format.json { render json: @services } 14 | end 15 | end 16 | 17 | def destroy 18 | @services = current_user.services.find(params[:id]) 19 | @services.destroy 20 | 21 | respond_to do |format| 22 | format.html { redirect_to services_path } 23 | format.json { head :no_content } 24 | end 25 | end 26 | 27 | def toggle_availability 28 | @service = current_user.services.find(params[:id]) 29 | @service.toggle_availability! 30 | 31 | respond_to do |format| 32 | format.html { redirect_to services_path } 33 | format.json { render json: @service } 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/capybara_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'capybara/rails' 3 | require 'capybara/poltergeist' 4 | require 'capybara-select2' 5 | 6 | CAPYBARA_TIMEOUT = ENV['CI'] == 'true' ? 60 : 5 7 | 8 | Capybara.register_driver :poltergeist do |app| 9 | Capybara::Poltergeist::Driver.new(app, timeout: CAPYBARA_TIMEOUT) 10 | end 11 | 12 | Capybara.javascript_driver = :poltergeist 13 | Capybara.default_max_wait_time = CAPYBARA_TIMEOUT 14 | 15 | RSpec.configure do |config| 16 | config.include Warden::Test::Helpers 17 | config.include AlertConfirmer, type: :feature 18 | 19 | config.before(:suite) do 20 | Warden.test_mode! 21 | DatabaseCleaner.clean_with(:truncation) 22 | end 23 | 24 | config.before(:each) do |example| 25 | DatabaseCleaner.strategy = example.metadata[:js] ? :truncation : :transaction 26 | DatabaseCleaner.start 27 | end 28 | 29 | config.after(:each) do 30 | DatabaseCleaner.clean 31 | Warden.test_reset! 32 | end 33 | end 34 | 35 | VCR.configure do |config| 36 | config.ignore_localhost = true 37 | end 38 | -------------------------------------------------------------------------------- /app/views/scenario_imports/_step_one.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | 9 |
10 |
You can import Scenarios, either from a .json file, or via a public 11 | Scenario URL. When you import a Scenario, Huginn will keep track of where it came from and 12 | later let you update it.
13 |
14 | 15 |
16 |
17 |
18 | <%= f.label :url, 'Option 1: Provide a Public Scenario URL' %> 19 | <%= f.text_field :url, :class => 'form-control', :placeholder => "Public Scenario URL" %> 20 |
21 | 22 |
23 | <%= f.label :file, 'Option 2: Upload a Scenario JSON File' %> 24 | <%= f.file_field :file, :class => 'form-control' %> 25 |
26 | 27 |
28 | <%= f.submit "Start Import", :class => "btn btn-primary" %> 29 |
30 |
31 |
-------------------------------------------------------------------------------- /app/concerns/markdown_class_attributes.rb: -------------------------------------------------------------------------------- 1 | module MarkdownClassAttributes 2 | extend ActiveSupport::Concern 3 | 4 | module ClassMethods 5 | def markdown_class_attributes(*attributes) 6 | attributes.each do |attribute| 7 | class_eval <<-RUBY 8 | def html_#{attribute} 9 | Kramdown::Document.new(#{attribute}, :auto_ids => false).to_html.html_safe 10 | end 11 | 12 | def #{attribute} 13 | if self.class.#{attribute}.is_a?(Proc) 14 | Utils.unindent(self.instance_eval(&self.class.#{attribute}) || "No #{attribute} has been set.") 15 | else 16 | Utils.unindent(self.class.#{attribute} || "No #{attribute} has been set.") 17 | end 18 | end 19 | 20 | def self.#{attribute}(value = nil, &block) 21 | if block 22 | @#{attribute} = block 23 | elsif value 24 | @#{attribute} = value 25 | end 26 | @#{attribute} 27 | end 28 | RUBY 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/models/user_credential_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe UserCredential do 4 | describe "validation" do 5 | it { should validate_uniqueness_of(:credential_name).scoped_to(:user_id) } 6 | it { should validate_presence_of(:credential_name) } 7 | it { should validate_presence_of(:credential_value) } 8 | it { should validate_presence_of(:user_id) } 9 | end 10 | 11 | describe "mass assignment" do 12 | it { should allow_mass_assignment_of :credential_name } 13 | 14 | it { should allow_mass_assignment_of :credential_value } 15 | 16 | it { should_not allow_mass_assignment_of :user_id } 17 | end 18 | 19 | describe "cleaning fields" do 20 | it "should trim whitespace" do 21 | user_credential = user_credentials(:bob_aws_key) 22 | user_credential.credential_name = " new name " 23 | user_credential.credential_value = " new value " 24 | user_credential.save! 25 | expect(user_credential.credential_name).to eq("new name") 26 | expect(user_credential.credential_value).to eq("new value") 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.openshift/action_hooks/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This deploy hook gets executed after dependencies are resolved and the 3 | # build hook has been run but before the application has been started back 4 | # up again. This script gets executed directly, so it could be python, php, 5 | # ruby, etc. 6 | 7 | echo "-> Deploy step" 8 | 9 | pushd ${OPENSHIFT_REPO_DIR} > /dev/null 10 | 11 | echo "DATABASE_NAME=${OPENSHIFT_APP_NAME}" > ${OPENSHIFT_REPO_DIR}/.env 12 | echo "DATABASE_USERNAME=${OPENSHIFT_MYSQL_DB_USERNAME}" >> ${OPENSHIFT_REPO_DIR}/.env 13 | echo "DATABASE_PASSWORD=${OPENSHIFT_MYSQL_DB_PASSWORD}" >> ${OPENSHIFT_REPO_DIR}/.env 14 | echo "DATABASE_HOST=${OPENSHIFT_MYSQL_DB_HOST}" >> ${OPENSHIFT_REPO_DIR}/.env 15 | echo "DATABASE_PORT=${OPENSHIFT_MYSQL_DB_PORT}" >> ${OPENSHIFT_REPO_DIR}/.env 16 | echo "DATABASE_SOCKET=${OPENSHIFT_MYSQL_DB_SOCKET}" >> ${OPENSHIFT_REPO_DIR}/.env 17 | 18 | chmod ugo+r ${OPENSHIFT_REPO_DIR}/.env 19 | 20 | source ${OPENSHIFT_REPO_DIR}/.env 21 | 22 | gem install bundler 23 | echo "Migrating" 24 | RAILS_ENV="production" bundle exec rake db:migrate 25 | popd > /dev/null 26 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_thumbnails.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Thumbnails 3 | // -------------------------------------------------- 4 | 5 | 6 | // Mixin and adjust the regular image class 7 | .thumbnail { 8 | display: block; 9 | padding: $thumbnail-padding; 10 | margin-bottom: $line-height-computed; 11 | line-height: $line-height-base; 12 | background-color: $thumbnail-bg; 13 | border: 1px solid $thumbnail-border; 14 | border-radius: $thumbnail-border-radius; 15 | @include transition(all .2s ease-in-out); 16 | 17 | > img, 18 | a > img { 19 | @include img-responsive(); 20 | margin-left: auto; 21 | margin-right: auto; 22 | } 23 | 24 | // [converter] extracted a&:hover, a&:focus, a&.active to a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active 25 | 26 | // Image captions 27 | .caption { 28 | padding: $thumbnail-caption-padding; 29 | color: $thumbnail-caption-color; 30 | } 31 | } 32 | 33 | // Add a hover state for linked versions only 34 | a.thumbnail:hover, 35 | a.thumbnail:focus, 36 | a.thumbnail.active { 37 | border-color: $link-color; 38 | } 39 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_utilities.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Utility classes 3 | // -------------------------------------------------- 4 | 5 | 6 | // Floats 7 | // ------------------------- 8 | 9 | .clearfix { 10 | @include clearfix(); 11 | } 12 | .center-block { 13 | @include center-block(); 14 | } 15 | .pull-right { 16 | float: right !important; 17 | } 18 | .pull-left { 19 | float: left !important; 20 | } 21 | 22 | 23 | // Toggling content 24 | // ------------------------- 25 | 26 | // Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1 27 | .hide { 28 | display: none !important; 29 | } 30 | .show { 31 | display: block !important; 32 | } 33 | .invisible { 34 | visibility: hidden; 35 | } 36 | .text-hide { 37 | @include text-hide(); 38 | } 39 | 40 | 41 | // Hide from screenreaders and browsers 42 | // 43 | // Credit: HTML5 Boilerplate 44 | 45 | .hidden { 46 | display: none !important; 47 | visibility: hidden !important; 48 | } 49 | 50 | 51 | // For Affix plugin 52 | // ------------------------- 53 | 54 | .affix { 55 | position: fixed; 56 | } 57 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/jquery.json-editor.css: -------------------------------------------------------------------------------- 1 | .json-editor { 2 | background-color: #FFF; 3 | position: relative; } 4 | .json-editor textarea { 5 | width: 100%; 6 | font-family: monospace; } 7 | .json-editor .builder { 8 | background-color: white; 9 | overflow: auto; 10 | font-size: 0.9em; } 11 | .json-editor .builder .key { 12 | font-weight: bold; } 13 | .json-editor .builder .key .edit_field { 14 | width: 150px; } 15 | .json-editor .builder .val .edit_field { 16 | width: 200px; } 17 | .json-editor blockquote { 18 | margin: 0; 19 | padding: 0; 20 | clear: both; 21 | padding-left: 7px; } 22 | .json-editor div { 23 | background-color: #cfc; 24 | margin: 1px; 25 | padding: 2px; } 26 | .json-editor .val { 27 | font-style: italic; } 28 | .json-editor .key a, .json-editor .val a { 29 | color: black; 30 | text-decoration: none; } 31 | .json-editor .icon { 32 | display: block; 33 | float: right; 34 | text-decoration: none; 35 | padding-left: 5px; 36 | border: 0; 37 | color: blue; } 38 | -------------------------------------------------------------------------------- /app/views/devise/registrations/_common_registration_fields.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= f.label :email, class: 'col-md-4 control-label' %> 3 |
4 | <%= f.email_field :email, autofocus: true, class: 'form-control' %> 5 |
6 |
7 | 8 |
9 | <%= f.label :username, class: 'col-md-4 control-label' %> 10 |
11 | <%= f.text_field :username, class: 'form-control' %> 12 |
13 |
14 | 15 |
16 | <%= f.label :password, class: 'col-md-4 control-label' %> 17 |
18 | <%= f.password_field :password, autocomplete: "off", class: 'form-control' %> 19 | <% if @validatable %><%= @minimum_password_length %> characters minimum.<% end %> 20 |
21 |
22 | 23 |
24 | <%= f.label :password_confirmation, class: 'col-md-4 control-label' %> 25 |
26 | <%= f.password_field :password_confirmation, autocomplete: "off", class: 'form-control' %> 27 |
28 |
29 | -------------------------------------------------------------------------------- /spec/controllers/users/registrations_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | module Users 4 | describe RegistrationsController do 5 | include Devise::TestHelpers 6 | 7 | describe "POST create" do 8 | context 'with valid params' do 9 | it "imports the default scenario for the new user" do 10 | mock(DefaultScenarioImporter).import(is_a(User)) 11 | 12 | @request.env["devise.mapping"] = Devise.mappings[:user] 13 | post :create, :user => {username: 'jdoe', email: 'jdoe@example.com', 14 | password: 's3cr3t55', password_confirmation: 's3cr3t55', admin: false, invitation_code: 'try-huginn'} 15 | end 16 | end 17 | 18 | context 'with invalid params' do 19 | it "does not import the default scenario" do 20 | stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" } 21 | 22 | @request.env["devise.mapping"] = Devise.mappings[:user] 23 | setup_controller_for_warden 24 | post :create, :user => {} 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/scenario_imports/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <% if @scenario_import.errors.any? %> 5 |
6 |

<%= pluralize(@scenario_import.errors.count, "error") %> prohibited this Scenario from being imported:

7 | <% @scenario_import.errors.full_messages.each do |msg| %> 8 |

<%= msg %>

9 | <% end %> 10 |
11 | <% end %> 12 |
13 |
14 | 15 | <%= form_for @scenario_import, :multipart => true do |f| %> 16 | <%= f.hidden_field :data %> 17 | 18 | <% if @scenario_import.step_one? %> 19 | <%= render 'step_one', :f => f %> 20 | <% elsif @scenario_import.step_two? %> 21 | <%= render 'step_two', :f => f %> 22 | <% end %> 23 | <% end %> 24 | 25 |
26 | 27 |
28 |
29 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, scenarios_path, class: "btn btn-default" %> 30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_jumbotron.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding: $jumbotron-padding; 8 | margin-bottom: $jumbotron-padding; 9 | color: $jumbotron-color; 10 | background-color: $jumbotron-bg; 11 | 12 | h1, 13 | .h1 { 14 | color: $jumbotron-heading-color; 15 | } 16 | p { 17 | margin-bottom: ($jumbotron-padding / 2); 18 | font-size: $jumbotron-font-size; 19 | font-weight: 200; 20 | } 21 | 22 | .container & { 23 | border-radius: $border-radius-large; // Only round corners at higher resolutions if contained in a container 24 | } 25 | 26 | .container { 27 | max-width: 100%; 28 | } 29 | 30 | @media screen and (min-width: $screen-sm-min) { 31 | padding-top: ($jumbotron-padding * 1.6); 32 | padding-bottom: ($jumbotron-padding * 1.6); 33 | 34 | .container & { 35 | padding-left: ($jumbotron-padding * 2); 36 | padding-right: ($jumbotron-padding * 2); 37 | } 38 | 39 | h1, 40 | .h1 { 41 | font-size: ($font-size-base * 4.5); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_media.scss: -------------------------------------------------------------------------------- 1 | // Media objects 2 | // Source: http://stubbornella.org/content/?p=497 3 | // -------------------------------------------------- 4 | 5 | 6 | // Common styles 7 | // ------------------------- 8 | 9 | // Clear the floats 10 | .media, 11 | .media-body { 12 | overflow: hidden; 13 | zoom: 1; 14 | } 15 | 16 | // Proper spacing between instances of .media 17 | .media, 18 | .media .media { 19 | margin-top: 15px; 20 | } 21 | .media:first-child { 22 | margin-top: 0; 23 | } 24 | 25 | // For images and videos, set to block 26 | .media-object { 27 | display: block; 28 | } 29 | 30 | // Reset margins on headings for tighter default spacing 31 | .media-heading { 32 | margin: 0 0 5px; 33 | } 34 | 35 | 36 | // Media image alignment 37 | // ------------------------- 38 | 39 | .media { 40 | > .pull-left { 41 | margin-right: 10px; 42 | } 43 | > .pull-right { 44 | margin-left: 10px; 45 | } 46 | } 47 | 48 | 49 | // Media list variation 50 | // ------------------------- 51 | 52 | // Undo default ul/ol styles 53 | .media-list { 54 | padding-left: 0; 55 | list-style: none; 56 | } 57 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/_pager.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Pager pagination 3 | // -------------------------------------------------- 4 | 5 | 6 | .pager { 7 | padding-left: 0; 8 | margin: $line-height-computed 0; 9 | list-style: none; 10 | text-align: center; 11 | @include clearfix(); 12 | li { 13 | display: inline; 14 | > a, 15 | > span { 16 | display: inline-block; 17 | padding: 5px 14px; 18 | background-color: $pager-bg; 19 | border: 1px solid $pager-border; 20 | border-radius: $pager-border-radius; 21 | } 22 | 23 | > a:hover, 24 | > a:focus { 25 | text-decoration: none; 26 | background-color: $pager-hover-bg; 27 | } 28 | } 29 | 30 | .next { 31 | > a, 32 | > span { 33 | float: right; 34 | } 35 | } 36 | 37 | .previous { 38 | > a, 39 | > span { 40 | float: left; 41 | } 42 | } 43 | 44 | .disabled { 45 | > a, 46 | > a:hover, 47 | > a:focus, 48 | > span { 49 | color: $pager-disabled-color; 50 | background-color: $pager-bg; 51 | cursor: not-allowed; 52 | } 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap/bootstrap.scss: -------------------------------------------------------------------------------- 1 | // Core variables and mixins 2 | @import "variables"; 3 | @import "mixins"; 4 | 5 | // Reset 6 | @import "normalize"; 7 | @import "print"; 8 | 9 | // Core CSS 10 | @import "scaffolding"; 11 | @import "type"; 12 | @import "code"; 13 | @import "grid"; 14 | @import "tables"; 15 | @import "forms"; 16 | @import "buttons"; 17 | 18 | // Components 19 | @import "component-animations"; 20 | @import "glyphicons"; 21 | @import "dropdowns"; 22 | @import "button-groups"; 23 | @import "input-groups"; 24 | @import "navs"; 25 | @import "navbar"; 26 | @import "breadcrumbs"; 27 | @import "pagination"; 28 | @import "pager"; 29 | @import "labels"; 30 | @import "badges"; 31 | @import "jumbotron"; 32 | @import "thumbnails"; 33 | @import "alerts"; 34 | @import "progress-bars"; 35 | @import "media"; 36 | @import "list-group"; 37 | @import "panels"; 38 | @import "wells"; 39 | @import "close"; 40 | 41 | // Components w/ JavaScript 42 | @import "modals"; 43 | @import "tooltip"; 44 | @import "popovers"; 45 | @import "carousel"; 46 | 47 | // Utility classes 48 | @import "utilities"; 49 | @import "responsive-utilities"; 50 | -------------------------------------------------------------------------------- /app/controllers/worker_status_controller.rb: -------------------------------------------------------------------------------- 1 | class WorkerStatusController < ApplicationController 2 | def show 3 | start = Time.now 4 | events = current_user.events 5 | 6 | if params[:since_id].present? 7 | since_id = params[:since_id].to_i 8 | events = events.where('id > ?', since_id) 9 | end 10 | 11 | result = events.select('COUNT(id) AS count', 'MIN(id) AS min_id', 'MAX(id) AS max_id').reorder('min(created_at)').first 12 | count, min_id, max_id = result.count, result.min_id, result.max_id 13 | 14 | case max_id 15 | when nil 16 | when min_id 17 | events_url = events_path(hl: max_id) 18 | else 19 | events_url = events_path(hl: "#{min_id}-#{max_id}") 20 | end 21 | 22 | render json: { 23 | pending: Delayed::Job.pending.where("run_at <= ?", start).count, 24 | awaiting_retry: Delayed::Job.awaiting_retry.count, 25 | recent_failures: Delayed::Job.failed.where('failed_at > ?', 5.days.ago).count, 26 | event_count: count, 27 | max_id: max_id || 0, 28 | events_url: events_url, 29 | compute_time: Time.now - start 30 | } 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/data_fixtures/services/37signals.json: -------------------------------------------------------------------------------- 1 | { 2 | "provider": "37signals", 3 | "uid": 12345, 4 | "info": { 5 | "email": "basecamp@none.de", 6 | "first_name": "Dominik", 7 | "last_name": "Sander", 8 | "name": "Dominik Sander" 9 | }, 10 | "credentials": { 11 | "token": "abcde", 12 | "refresh_token": "fghrefresh", 13 | "expires_at": 1401554352, 14 | "expires": true 15 | }, 16 | "extra": { 17 | "accounts": [ 18 | { 19 | "product": "bcx", 20 | "name": "Dominik Sander's Basecamp", 21 | "id": 12345, 22 | "href": "https://basecamp.com/12345/api/v1" 23 | } 24 | ], 25 | "raw_info": { 26 | "expires_at": "2014-05-31T16:39:12Z", 27 | "identity": { 28 | "first_name": "Dominik", 29 | "last_name": "Sander", 30 | "email_address": "basecamp@none.de", 31 | "id": 12345 32 | }, 33 | "accounts": [ 34 | { 35 | "product": "bcx", 36 | "name": "Dominik Sander's Basecamp", 37 | "id": 12345, 38 | "href": "https://basecamp.com/12345/api/v1" 39 | } 40 | ] 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013, Andrew Cantino (Iteration Labs, LLC) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/initializers/delayed_job.rb: -------------------------------------------------------------------------------- 1 | Delayed::Worker.destroy_failed_jobs = false 2 | Delayed::Worker.max_attempts = 5 3 | Delayed::Worker.max_run_time = (ENV['DELAYED_JOB_MAX_RUNTIME'].presence || 2).to_i.minutes 4 | Delayed::Worker.read_ahead = 5 5 | Delayed::Worker.default_priority = 10 6 | Delayed::Worker.delay_jobs = !Rails.env.test? 7 | Delayed::Worker.sleep_delay = (ENV['DELAYED_JOB_SLEEP_DELAY'].presence || 10).to_f 8 | Delayed::Worker.logger = Rails.logger 9 | 10 | # Delayed::Worker.logger = Logger.new(Rails.root.join('log', 'delayed_job.log')) 11 | # Delayed::Worker.logger.level = Logger::DEBUG 12 | 13 | class Delayed::Job 14 | scope :pending, -> { where("locked_at IS NULL AND attempts = 0") } 15 | scope :awaiting_retry, -> { where("failed_at IS NULL AND attempts > 0 AND locked_at IS NULL") } 16 | scope :failed, -> { where("failed_at IS NOT NULL") } 17 | end 18 | 19 | def database_deadlocks_when_using_optimized_strategy? 20 | ENV["DATABASE_ADAPTER"] == "mysql2" 21 | end 22 | 23 | if database_deadlocks_when_using_optimized_strategy? 24 | Delayed::Backend::ActiveRecord.configure do |config| 25 | config.reserve_sql_strategy = :default_sql 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Brandon Keepers 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/assets/javascripts/components/search.js.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $agentNavigate = $('#agent-navigate') 3 | 4 | # initialize typeahead listener 5 | $agentNavigate.bind "typeahead:selected", (event, object, name) -> 6 | item = object['value'] 7 | $agentNavigate.typeahead('val', '') 8 | if window.agentPaths[item] 9 | $(".spinner").show() 10 | navigationData = window.agentPaths[item] 11 | if !(navigationData instanceof Object) || !navigationData.method || navigationData.method == 'GET' 12 | window.location = navigationData.url || navigationData 13 | else 14 | $("").appendTo($("body")).click() 15 | 16 | # substring matcher for typeahead 17 | substringMatcher = (strings) -> 18 | findMatches = (query, callback) -> 19 | matches = [] 20 | substrRegex = new RegExp(query, "i") 21 | $.each strings, (i, str) -> 22 | matches.push value: str if substrRegex.test(str) 23 | callback(matches.slice(0,6)) 24 | 25 | $agentNavigate.typeahead 26 | minLength: 1, 27 | highlight: true, 28 | , 29 | source: substringMatcher(window.agentNames) 30 | -------------------------------------------------------------------------------- /app/controllers/events_controller.rb: -------------------------------------------------------------------------------- 1 | class EventsController < ApplicationController 2 | before_action :load_event, except: :index 3 | 4 | def index 5 | if params[:agent_id] 6 | @agent = current_user.agents.find(params[:agent_id]) 7 | @events = @agent.events.page(params[:page]) 8 | else 9 | @events = current_user.events.preload(:agent).page(params[:page]) 10 | end 11 | 12 | respond_to do |format| 13 | format.html 14 | format.json { render json: @events } 15 | end 16 | end 17 | 18 | def show 19 | respond_to do |format| 20 | format.html 21 | format.json { render json: @event } 22 | end 23 | end 24 | 25 | def reemit 26 | @event.reemit! 27 | respond_to do |format| 28 | format.html { redirect_back event_path(@event), notice: 'Event re-emitted.' } 29 | end 30 | end 31 | 32 | def destroy 33 | @event.destroy 34 | 35 | respond_to do |format| 36 | format.html { redirect_back events_path, notice: 'Event deleted.' } 37 | format.json { head :no_content } 38 | end 39 | end 40 | 41 | private 42 | 43 | def load_event 44 | @event = current_user.events.find(params[:id]) 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /.openshift/cron/README.cron: -------------------------------------------------------------------------------- 1 | Run scripts or jobs on a periodic basis 2 | ======================================= 3 | Any scripts or jobs added to the minutely, hourly, daily, weekly or monthly 4 | directories will be run on a scheduled basis (frequency is as indicated by the 5 | name of the directory) using run-parts. 6 | 7 | run-parts ignores any files that are hidden or dotfiles (.*) or backup 8 | files (*~ or *,) or named *.{rpmsave,rpmorig,rpmnew,swp,cfsaved} 9 | 10 | The presence of two specially named files jobs.deny and jobs.allow controls 11 | how run-parts executes your scripts/jobs. 12 | jobs.deny ===> Prevents specific scripts or jobs from being executed. 13 | jobs.allow ===> Only execute the named scripts or jobs (all other/non-named 14 | scripts that exist in this directory are ignored). 15 | 16 | The principles of jobs.deny and jobs.allow are the same as those of cron.deny 17 | and cron.allow and are described in detail at: 18 | http://docs.redhat.com/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Deployment_Guide/ch-Automating_System_Tasks.html#s2-autotasks-cron-access 19 | 20 | See: man crontab or above link for more details and see the the weekly/ 21 | directory for an example. 22 | 23 | -------------------------------------------------------------------------------- /db/migrate/20160108221620_website_agent_does_not_use_event_url.rb: -------------------------------------------------------------------------------- 1 | class WebsiteAgentDoesNotUseEventUrl < ActiveRecord::Migration 2 | def up 3 | # Until this migration, if a WebsiteAgent received Events and did not have a `url_from_event` option set, 4 | # it would use the `url` from the Event's payload. If the Event did not have a `url` in its payload, the 5 | # WebsiteAgent would do nothing. This migration assumes that if someone has wired a WebsiteAgent to receive Events 6 | # and has not set `url_from_event` or `data_from_event`, they were trying to use the Event's `url` payload, so we 7 | # set `url_from_event` to `{{ url }}` for them. 8 | Agents::WebsiteAgent.find_each do |agent| 9 | next unless agent.sources.count > 0 10 | 11 | if !agent.options['data_from_event'].present? && !agent.options['url_from_event'].present? 12 | agent.options['url_from_event'] = '{{ url }}' 13 | agent.save! 14 | puts ">> Setting `url_from_event` on WebsiteAgent##{agent.id} to {{ url }} because it is wired" 15 | puts ">> to receive Events, and the WebsiteAgent no longer uses the Event's `url` value directly." 16 | end 17 | end 18 | end 19 | 20 | def down 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/assets/javascripts/map_marker.js.coffee: -------------------------------------------------------------------------------- 1 | window.map_marker = (map, options = {}) -> 2 | pos = new google.maps.LatLng(options.lat, options.lng) 3 | 4 | if options.radius > 0 5 | marker = new google.maps.Circle 6 | map: map 7 | strokeColor: '#FF0000' 8 | strokeOpacity: 0.8 9 | strokeWeight: 2 10 | fillColor: '#FF0000' 11 | fillOpacity: 0.35 12 | center: pos 13 | radius: options.radius 14 | return marker 15 | else 16 | marker = new google.maps.Marker 17 | map: map 18 | position: pos 19 | title: 'Recorded Location' 20 | return marker 21 | 22 | if options.course 23 | p1 = new LatLon(pos.lat(), pos.lng()) 24 | speed = options.speed ? 1 25 | p2 = p1.destinationPoint(options.course, Math.max(0.2, speed) * 0.1) 26 | 27 | lineCoordinates = [ 28 | pos 29 | new google.maps.LatLng(p2.lat(), p2.lon()) 30 | ] 31 | 32 | lineSymbol = 33 | path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW 34 | 35 | arrow = new google.maps.Polyline 36 | map: map 37 | path: lineCoordinates 38 | icons: [ 39 | { 40 | icon: lineSymbol 41 | offset: '100%' 42 | } 43 | ] 44 | 45 | return arrow 46 | -------------------------------------------------------------------------------- /app/views/agents/dry_runs/create.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 |
9 |
10 |
<%= Utils.pretty_print(@results[:events], false) %>
11 |
12 |
13 |
<%= @results[:log] %>
14 |
15 |
16 |
<%= Utils.pretty_print(@results[:memory], false) %>
17 |
18 |
19 | -------------------------------------------------------------------------------- /spec/helpers/scenario_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe ScenarioHelper do 4 | let(:scenario) { users(:bob).scenarios.build(name: 'Scene', tag_fg_color: '#AAAAAA', tag_bg_color: '#000000') } 5 | 6 | describe '#style_colors' do 7 | it 'returns a css style-formated version of the scenario foreground and background colors' do 8 | expect(style_colors(scenario)).to eq("color:#AAAAAA;background-color:#000000") 9 | end 10 | 11 | it 'defauls foreground and background colors' do 12 | scenario.tag_fg_color = nil 13 | scenario.tag_bg_color = nil 14 | expect(style_colors(scenario)).to eq("color:#FFFFFF;background-color:#5BC0DE") 15 | end 16 | end 17 | 18 | describe '#scenario_label' do 19 | it 'creates a scenario label with the scenario name' do 20 | expect(scenario_label(scenario)).to eq( 21 | 'Scene' 22 | ) 23 | end 24 | 25 | it 'creates a scenario label with the given text' do 26 | expect(scenario_label(scenario, 'Other')).to eq( 27 | 'Other' 28 | ) 29 | end 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | 2 | guard 'livereload' do 3 | watch(%r{app/views/.+\.(erb|haml|slim)$}) 4 | watch(%r{app/helpers/.+\.rb}) 5 | watch(%r{public/.+\.(css|js|html)}) 6 | watch(%r{config/locales/.+\.yml}) 7 | # Rails Assets Pipeline 8 | watch(%r{(app|vendor)(/assets/\w+/(.+\.(css|js|html|png|jpg))).*}) { |m| "/assets/#{m[3]}" } 9 | end 10 | 11 | guard :rspec, cmd: ENV['SPRING'] ? 'bundle exec spring rspec' : 'bundle exec rspec' do 12 | watch(%r{^spec/.+_spec\.rb$}) 13 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 14 | watch('spec/spec_helper.rb') { "spec" } 15 | 16 | # Rails example 17 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 18 | watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } 19 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } 20 | watch(%r{^spec/support/(.+)\.rb$}) { "spec" } 21 | watch('config/routes.rb') { "spec/routing" } 22 | watch('app/controllers/application_controller.rb') { "spec/controllers" } 23 | watch('spec/rails_helper.rb') { "spec" } 24 | end 25 | 26 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/jquery.serializeObject.js: -------------------------------------------------------------------------------- 1 | // 2 | // Use internal $.serializeArray to get list of form elements which is 3 | // consistent with $.serialize 4 | // 5 | // From version 2.0.0, $.serializeObject will stop converting [name] values 6 | // to camelCase format. This is *consistent* with other serialize methods: 7 | // 8 | // - $.serialize 9 | // - $.serializeArray 10 | // 11 | // If you require camel casing, you can either download version 1.0.4 or map 12 | // them yourself. 13 | // 14 | 15 | (function($){ 16 | $.fn.serializeObject = function () { 17 | "use strict"; 18 | 19 | var result = {}; 20 | var extend = function (i, element) { 21 | var node = result[element.name]; 22 | 23 | // If node with same name exists already, need to convert it to an array as it 24 | // is a multi-value field (i.e., checkboxes) 25 | 26 | if ('undefined' !== typeof node && node !== null) { 27 | if ($.isArray(node)) { 28 | node.push(element.value); 29 | } else { 30 | result[element.name] = [node, element.value]; 31 | } 32 | } else { 33 | result[element.name] = element.value; 34 | } 35 | }; 36 | 37 | $.each(this.serializeArray(), extend); 38 | return result; 39 | }; 40 | })(jQuery); 41 | -------------------------------------------------------------------------------- /spec/fixtures/multiple_user_credentials.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 23, 4 | "user_id": 30, 5 | "credential_name": "Google_api_key", 6 | "credential_value": "gcperfxrtqymqmluvskxzyiyxxfnjduzncoukyqehkrkamofwz", 7 | "created_at": "2016-04-01 10:50:59 -0700", 8 | "updated_at": "2016-04-01 10:50:59 -0700", 9 | "mode": "text" 10 | }, 11 | { 12 | "id": 24, 13 | "user_id": 30, 14 | "credential_name": "twitter_secret_key", 15 | "credential_value": "jhpswiebwhbrnabgkbvczrwcyxblxtyvvlvkhuoudjalcqmlwz", 16 | "created_at": "2016-04-01 10:50:59 -0700", 17 | "updated_at": "2016-04-01 10:50:59 -0700", 18 | "mode": "text" 19 | }, 20 | { 21 | "id": 23, 22 | "user_id": 30, 23 | "credential_name": "Google_api_key", 24 | "credential_value": "gcperfxrtqymqmluvskxzyiyxxfnjduzncoukyqehkrkamofwz", 25 | "created_at": "2016-04-01 10:50:59 -0700", 26 | "updated_at": "2016-04-01 10:50:59 -0700", 27 | "mode": "text" 28 | }, 29 | { 30 | "id": 24, 31 | "user_id": 30, 32 | "credential_name": "twitter_secret_key", 33 | "credential_value": "jhpswiebwhbrnabgkbvczrwcyxblxtyvvlvkhuoudjalcqmlwz", 34 | "created_at": "2016-04-01 10:50:59 -0700", 35 | "updated_at": "2016-04-01 10:50:59 -0700", 36 | "mode": "text" 37 | } 38 | ] 39 | -------------------------------------------------------------------------------- /spec/controllers/services_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe ServicesController do 4 | before do 5 | sign_in users(:bob) 6 | end 7 | 8 | describe "GET index" do 9 | it "only returns sevices of the current user" do 10 | get :index 11 | expect(assigns(:services).all? {|i| expect(i.user).to eq(users(:bob)) }).to eq(true) 12 | end 13 | end 14 | 15 | describe "POST toggle_availability" do 16 | it "should work for service of the user" do 17 | post :toggle_availability, :id => services(:generic).to_param 18 | expect(assigns(:service)).to eq(services(:generic)) 19 | redirect_to(services_path) 20 | end 21 | 22 | it "should not work for a service of another user" do 23 | expect { 24 | post :toggle_availability, :id => services(:global).to_param 25 | }.to raise_error(ActiveRecord::RecordNotFound) 26 | end 27 | end 28 | 29 | describe "DELETE destroy" do 30 | it "destroys only services owned by the current user" do 31 | expect { 32 | delete :destroy, :id => services(:generic).to_param 33 | }.to change(Service, :count).by(-1) 34 | 35 | expect { 36 | delete :destroy, :id => services(:global).to_param 37 | }.to raise_error(ActiveRecord::RecordNotFound) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /app/assets/javascripts/components/core.js.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | # Flash 3 | if $(".flash").length 4 | setTimeout((-> $(".flash").slideUp(-> $(".flash").remove())), 5000) 5 | 6 | # Help popovers 7 | $('.hover-help').popover(trigger: 'hover', html: true) 8 | 9 | # Pressing '/' selects the search box. 10 | $("body").on "keypress", (e) -> 11 | if e.keyCode == 47 # The '/' key 12 | if e.target.nodeName == "BODY" 13 | e.preventDefault() 14 | $agentNavigate.focus() 15 | 16 | # Select2 Selects 17 | $(".select2").select2(width: 'resolve') 18 | 19 | $(".select2-linked-tags").select2( 20 | width: 'resolve', 21 | formatSelection: (obj) -> 22 | "#{Utils.escape(obj.text)}" 23 | ) 24 | 25 | # Helper for selecting text when clicked 26 | $('.selectable-text').each -> 27 | $(this).click -> 28 | range = document.createRange() 29 | range.setStartBefore(this.firstChild) 30 | range.setEndAfter(this.lastChild) 31 | sel = window.getSelection() 32 | sel.removeAllRanges(); 33 | sel.addRange(range) 34 | 35 | # Agent navbar dropdown 36 | $('.navbar .dropdown.dropdown-hover').hover (-> $(this).addClass('open')), (-> $(this).removeClass('open')) -------------------------------------------------------------------------------- /app/models/agents/read_file_agent.rb: -------------------------------------------------------------------------------- 1 | module Agents 2 | class ReadFileAgent < Agent 3 | include FormConfigurable 4 | include FileHandling 5 | 6 | cannot_be_scheduled! 7 | consumes_file_pointer! 8 | 9 | def default_options 10 | { 11 | 'data_key' => 'data' 12 | } 13 | end 14 | 15 | description do 16 | <<-MD 17 | The ReadFileAgent takes events from `FileHandling` agents, reads the file, and emits the contents as a string. 18 | 19 | `data_key` specifies the key of the emitted event which contains the file contents. 20 | 21 | #{receiving_file_handling_agent_description} 22 | MD 23 | end 24 | 25 | event_description <<-MD 26 | { 27 | "data" => '...' 28 | } 29 | MD 30 | 31 | form_configurable :data_key, type: :string 32 | 33 | def validate_options 34 | if options['data_key'].blank? 35 | errors.add(:base, "The 'data_key' options is required.") 36 | end 37 | end 38 | 39 | def working? 40 | received_event_without_error? 41 | end 42 | 43 | def receive(incoming_events) 44 | incoming_events.each do |event| 45 | next unless io = get_io(event) 46 | create_event payload: { interpolated['data_key'] => io.read } 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | devise: 6 | failure: 7 | deactivated_account: "Your account has been deactivated by an administrator." 8 | datetime: 9 | distance_in_words: 10 | half_a_minute: "half a minute" 11 | less_than_x_seconds: 12 | one: "<1s" 13 | other: "<%{count}s" 14 | x_seconds: 15 | one: "1s" 16 | other: "%{count}s" 17 | less_than_x_minutes: 18 | one: "<1m" 19 | other: "<%{count}m" 20 | x_minutes: 21 | one: "1m" 22 | other: "%{count}m" 23 | about_x_hours: 24 | one: "~1h" 25 | other: "~%{count}h" 26 | x_days: 27 | one: "1d" 28 | other: "%{count}d" 29 | about_x_months: 30 | one: "~1mo" 31 | other: "~%{count}mo" 32 | x_months: 33 | one: "1mo" 34 | other: "%{count}mo" 35 | about_x_years: 36 | one: "~1yr" 37 | other: "~%{count}yr" 38 | over_x_years: 39 | one: ">1yr" 40 | other: ">%{count}yr" 41 | almost_x_years: 42 | one: "~1yr" 43 | other: "~%{count}yr" 44 | --------------------------------------------------------------------------------