├── 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 │ └── 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 ├── tc_comp.sh ├── app ├── helpers │ ├── logs_helper.rb │ ├── markdown_helper.rb │ ├── users_helper.rb │ └── scenario_helper.rb ├── assets │ ├── stylesheets │ │ ├── jobs.scss │ │ ├── scenarios.scss │ │ ├── diagram.scss │ │ └── tables.scss │ ├── images │ │ ├── odin.jpg │ │ └── spinner-arrows.gif │ └── javascripts │ │ ├── ace.js.coffee │ │ ├── tweets.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 │ ├── devise │ │ ├── mailer │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── shared │ │ │ └── _links.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── passwords │ │ │ └── new.html.erb │ │ └── registrations │ │ │ └── _common_registration_fields.html.erb │ ├── admin │ │ └── users │ │ │ ├── new.html.erb │ │ │ ├── edit.html.erb │ │ │ └── _form.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 │ │ │ └── liquid_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 │ │ ├── new.html.erb │ │ ├── edit.html.erb │ │ └── _enable_agents_modal.html.erb │ ├── user_credentials │ │ ├── new.html.erb │ │ └── edit.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 │ └── user_credential.rb ├── controllers │ ├── home_controller.rb │ ├── users │ │ └── registrations_controller.rb │ ├── logs_controller.rb │ ├── diagrams_controller.rb │ ├── omniauth_callbacks_controller.rb │ ├── scenario_imports_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 │ ├── timezone.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── aws.rb │ ├── sanitizer.rb │ ├── filter_parameter_logging.rb │ ├── cookies_serializer.rb │ ├── silence_worker_status_logger.rb │ ├── faraday_patch.rb │ ├── liquid.rb │ ├── secret_token.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── mysqlpls.rb │ ├── action_mailer.rb │ ├── inflections.rb │ ├── assets.rb │ └── delayed_job.rb ├── spring.rb ├── environment.rb ├── deploy │ └── production.rb ├── boot.rb └── unicorn.rb.example ├── docker ├── multi-process │ ├── Makefile │ ├── scripts │ │ └── standalone-packages │ ├── docker-compose.yml │ └── Dockerfile ├── test │ ├── scripts │ │ └── test_env │ └── Dockerfile ├── single-process │ ├── environment.yml │ ├── scripts │ │ └── init │ ├── docker-compose.yml │ └── Dockerfile └── README.md ├── bin ├── docker_wrapper ├── bundle ├── rspec ├── rake ├── rails ├── threaded.rb ├── schedule.rb ├── pre_runner_boot.rb ├── agent_runner.rb ├── twitter_stream.rb ├── decrypt_backup.rb └── spring ├── .graphviz ├── spec ├── support │ ├── matchers.rb │ ├── vcr_support.rb │ ├── feature_helpers.rb │ ├── shared_examples │ │ └── has_guid.rb │ └── spec_helpers.rb ├── data_fixtures │ ├── private.key │ ├── iso-8859-1.rss │ ├── stubhub_data.json │ ├── adioso_parse.json │ ├── witai.json │ ├── imap2.eml │ ├── imap1.eml │ ├── basecamp.json │ ├── twitter_scenario.json │ ├── urlTest.html │ └── services │ │ └── 37signals.json ├── fixtures │ ├── links.yml │ ├── events.yml │ ├── users.yml │ ├── user_credentials.yml │ ├── agent_logs.yml │ ├── scenarios.yml │ ├── scenario_memberships.yml │ ├── user_credentials.json │ ├── services.yml │ └── multiple_user_credentials.json ├── helpers │ ├── markdown_helper_spec.rb │ ├── jobs_helper_spec.rb │ └── scenario_helper_spec.rb ├── features │ ├── form_configurable_feature_spec.rb │ ├── edit_an_agent_spec.rb │ ├── undefined_agents_spec.rb │ └── toggle_visibility_of_disabled_agents.rb ├── concerns │ ├── inheritance_tracking_spec.rb │ └── liquid_droppable_spec.rb ├── env.test ├── lib │ ├── time_tracker_spec.rb │ └── delayed_job_worker_spec.rb ├── jobs │ └── agent_propagate_job_spec.rb ├── models │ ├── agents │ │ ├── pdf_agent_spec.rb │ │ ├── weibo_user_agent_spec.rb │ │ └── weather_agent_spec.rb │ ├── user_credential_spec.rb │ └── concerns │ │ └── oauthable.rb ├── db │ └── seeds │ │ └── admin_and_default_scenario_spec.rb ├── controllers │ ├── scenario_imports_controller_spec.rb │ ├── omniauth_callbacks_controller_spec.rb │ └── users │ │ └── registrations_controller_spec.rb ├── capybara_helper.rb └── migrations │ └── 20161124065838_add_templates_to_resolve_url_spec.rb ├── doc ├── imgs │ ├── diagram.png │ ├── peaks.png │ ├── new-agent.png │ ├── the-name.png │ ├── my-locations.png │ └── your-agents.png ├── manual │ └── README.md ├── heroku │ └── update.md ├── README.md └── deployment │ └── unicorn │ └── production.rb ├── 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-114x114.png ├── apple-touch-icon-120x120.png ├── apple-touch-icon-144x144.png ├── apple-touch-icon-152x152.png ├── apple-touch-icon-180x180.png ├── apple-touch-icon-57x57.png ├── apple-touch-icon-60x60.png ├── apple-touch-icon-72x72.png ├── apple-touch-icon-76x76.png ├── robots.txt ├── 500.html ├── 422.html ├── 404.html └── manifest.json ├── run_tests_chks.sh ├── 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 ├── run_tests_no_chks.sh ├── tc_noncomp.sh ├── Procfile.CF ├── config.ru ├── 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 │ ├── 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 │ ├── 20130107050049_add_invitation_code_to_users.rb │ ├── 20150808115436_remove_requirement_from_users_invitation_code.rb │ ├── 20140602014917_add_indices_to_scenarios.rb │ ├── 20160807000122_remove_queue_from_email_digest_agent_memory.rb │ ├── 20161007030910_reset_data_output_agents.rb │ ├── 20140408150825_rename_webhook_to_web_request.rb │ ├── 20121222074732_create_links.rb │ ├── 20140216201250_add_propagate_immediately_to_agent.rb │ ├── 20140509170443_create_scenario_memberships.rb │ ├── 20140509170420_create_scenarios.rb │ ├── 20140906030139_set_events_count_default.rb │ ├── 20140605032822_add_guid_to_agents.rb │ ├── 20130819160603_create_agent_logs.rb │ ├── 20140531232016_add_fields_to_scenarios.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 │ ├── 20160823151303_set_emit_error_event_for_twitter_action_agents.rb │ ├── 20120919061122_enable_lockable_strategy_for_devise.rb │ ├── 20120919063304_add_username_to_users.rb │ ├── 20161124065838_add_templates_to_resolve_url.rb │ ├── 20140127164931_change_handler_to_medium_text.rb │ ├── 20170419073748_set_rss_content_type.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 │ ├── 20170307190555_add_min_events_option_to_peak_detector_agents.rb │ ├── 20170731191002_migrate_growl_agent_to_liquid.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 │ ├── 20160607055850_change_events_order_to_events_list_order.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 │ └── 20161124061256_convert_website_agent_template_for_merge.rb ├── seeds.rb └── seeds │ └── seeder.rb ├── .buildpacks ├── script ├── delayed_job └── rails ├── test_runner.sh ├── Capfile ├── Rakefile ├── deployment ├── heroku │ └── Procfile.heroku └── logrotate │ └── huginn ├── manifest.yml.sample ├── lib ├── tasks │ ├── database_test.rake │ └── rspec.rake ├── delayed_job_worker.rb ├── json_with_indifferent_access.rb ├── rdbms_functions.rb └── time_tracker.rb ├── .gitignore ├── .dockerignore ├── app.json ├── LICENSE └── Guardfile /tmp/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3 2 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tc_comp.sh: -------------------------------------------------------------------------------- 1 | RUBYOPT=W0 rails runner 'typecheck.rb' 2 | -------------------------------------------------------------------------------- /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 huginn/huginn . 3 | -------------------------------------------------------------------------------- /bin/docker_wrapper: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | chmod -R g=u . && docker "$@" 4 | -------------------------------------------------------------------------------- /.graphviz: -------------------------------------------------------------------------------- 1 | http://www.graphviz.org/pub/graphviz/stable/SOURCES/graphviz-2.38.0.tar.gz 2 | -------------------------------------------------------------------------------- /app/assets/stylesheets/jobs.scss: -------------------------------------------------------------------------------- 1 | .big-modal-dialog { 2 | width: 90% !important; 3 | } -------------------------------------------------------------------------------- /spec/support/matchers.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define_negated_matcher :not_change, :change 2 | -------------------------------------------------------------------------------- /doc/imgs/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/doc/imgs/diagram.png -------------------------------------------------------------------------------- /doc/imgs/peaks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/doc/imgs/peaks.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/favicon.ico -------------------------------------------------------------------------------- /run_tests_chks.sh: -------------------------------------------------------------------------------- 1 | TYPECHECK=1 rspec spec/models/user_spec.rb spec/models/service_spec.rb 2 | -------------------------------------------------------------------------------- /doc/imgs/new-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/doc/imgs/new-agent.png -------------------------------------------------------------------------------- /doc/imgs/the-name.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/doc/imgs/the-name.png -------------------------------------------------------------------------------- /media/huginn-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/huginn-logo.png -------------------------------------------------------------------------------- /media/iOS/Icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-40.png -------------------------------------------------------------------------------- /media/iOS/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-76.png -------------------------------------------------------------------------------- /run_tests_no_chks.sh: -------------------------------------------------------------------------------- 1 | NODYNCHECK=1 rspec spec/models/user_spec.rb spec/models/service_spec.rb 2 | -------------------------------------------------------------------------------- /vendor/gems/dotenv-2.0.1/lib/dotenv/version.rb: -------------------------------------------------------------------------------- 1 | module Dotenv 2 | VERSION = "2.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /doc/imgs/my-locations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/doc/imgs/my-locations.png -------------------------------------------------------------------------------- /doc/imgs/your-agents.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/doc/imgs/your-agents.png -------------------------------------------------------------------------------- /media/huginn-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/huginn-icon-16.png -------------------------------------------------------------------------------- /media/huginn-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/huginn-icon-64.png -------------------------------------------------------------------------------- /media/iOS/Icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-40@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-60@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-60@3x.png -------------------------------------------------------------------------------- /media/iOS/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-76@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-Small.png -------------------------------------------------------------------------------- /app/assets/images/odin.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/app/assets/images/odin.jpg -------------------------------------------------------------------------------- /media/iOS/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-Small@2x.png -------------------------------------------------------------------------------- /media/iOS/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/Icon-Small@3x.png -------------------------------------------------------------------------------- /media/iOS/iTunesArtwork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/iTunesArtwork.png -------------------------------------------------------------------------------- /media/iOS/iTunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/media/iOS/iTunesArtwork@2x.png -------------------------------------------------------------------------------- /spec/data_fixtures/private.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/spec/data_fixtures/private.key -------------------------------------------------------------------------------- /tc_noncomp.sh: -------------------------------------------------------------------------------- 1 | git checkout non-comp-types 2 | RUBYOPT=W0 rails runner 'typecheck.rb' 3 | git checkout master 4 | -------------------------------------------------------------------------------- /public/android-chrome-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/android-chrome-36x36.png -------------------------------------------------------------------------------- /public/android-chrome-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/android-chrome-48x48.png -------------------------------------------------------------------------------- /public/android-chrome-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/android-chrome-72x72.png -------------------------------------------------------------------------------- /public/android-chrome-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/android-chrome-96x96.png -------------------------------------------------------------------------------- /public/android-chrome-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/android-chrome-144x144.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /public/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/public/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /spec/data_fixtures/iso-8859-1.rss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/spec/data_fixtures/iso-8859-1.rss -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/images/spinner-arrows.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/app/assets/images/spinner-arrows.gif -------------------------------------------------------------------------------- /vendor/assets/images/json-editor/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/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 | -------------------------------------------------------------------------------- /vendor/assets/images/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/vendor/assets/images/glyphicons-halflings.png -------------------------------------------------------------------------------- /vendor/assets/images/json-editor/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/vendor/assets/images/json-editor/delete.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 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /vendor/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/vendor/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /vendor/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/vendor/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /vendor/assets/images/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/vendor/assets/images/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /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 %> -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Huginn::Application 6 | -------------------------------------------------------------------------------- /vendor/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngsankha/huginn/master/vendor/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require_relative 'application' 3 | 4 | # Initialize the rails application 5 | Huginn::Application.initialize! 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/20130124050117_add_indexes.rb: -------------------------------------------------------------------------------- 1 | class AddIndexes < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :links, [:receiver_id, :source_id] 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 | -------------------------------------------------------------------------------- /db/migrate/20121231170705_add_memory_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddMemoryToAgents < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :agents, :memory, :text 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 | -------------------------------------------------------------------------------- /db/migrate/20160419150930_add_icon_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddIconToScenarios < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :scenarios, :icon, :string 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 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | server ENV['CAPISTRANO_DEPLOY_SERVER'], 2 | user: ENV['CAPISTRANO_DEPLOY_USER'] || 'huginn', 3 | port: ENV['CAPISTRANO_DEPLOY_PORT'] || 22, 4 | roles: %w{app db web} 5 | -------------------------------------------------------------------------------- /db/migrate/20140525150040_add_service_id_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddServiceIdToAgents < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :agents, :service_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /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/20140809211540_remove_service_index_on_user_id.rb: -------------------------------------------------------------------------------- 1 | class RemoveServiceIndexOnUserId < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_index :services, :user_id 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/20120728215449_add_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddAdminToUsers < ActiveRecord::Migration[4.2] 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[4.2] 2 | def change 3 | add_column :agents, :last_webhook_at, :datetime 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', __dir__) 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 | -------------------------------------------------------------------------------- /db/migrate/20140403043556_add_disabled_to_agent.rb: -------------------------------------------------------------------------------- 1 | class AddDisabledToAgent < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :agents, :disabled, :boolean, :default => false, :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/home/about.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 7 |
8 |
9 |
-------------------------------------------------------------------------------- /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 style)) 3 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /db/migrate/20131105063248_add_expires_at_to_events.rb: -------------------------------------------------------------------------------- 1 | class AddExpiresAtToEvents < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :events, :expires_at, :datetime 4 | add_index :events, :expires_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20131222211558_add_keep_events_for_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddKeepEventsForToAgents < ActiveRecord::Migration[4.2] 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[4.2] 2 | def change 3 | add_column :user_credentials, :mode, :string, :default => 'text', :null => false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require 'bundler/setup' 8 | load Gem.bin_path('rspec-core', 'rspec') 9 | -------------------------------------------------------------------------------- /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] -------------------------------------------------------------------------------- /test_runner.sh: -------------------------------------------------------------------------------- 1 | echo "" > tests.out 2 | 3 | for i in {1..11}; do 4 | NODYNCHECK=1 TYPECHECK=1 bundle exec rspec spec/models/user_spec.rb spec/models/service_spec.rb >> tests.out 2>&1 5 | done 6 | grep "Finished in" tests.out | awk '{print $3}' 7 | -------------------------------------------------------------------------------- /db/migrate/20160302095413_add_deactivated_at_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddDeactivatedAtToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :deactivated_at, :datetime 4 | 5 | add_index :users, :deactivated_at 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20140820003139_add_tag_color_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddTagColorToScenarios < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :scenarios, :tag_bg_color, :string 4 | add_column :scenarios, :tag_fg_color, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /db/migrate/20140811200922_add_uid_column_to_services.rb: -------------------------------------------------------------------------------- 1 | class AddUidColumnToServices < ActiveRecord::Migration[4.2] 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[4.2] 2 | def change 3 | add_column :agents, :deactivated, :boolean, default: false 4 | add_index :agents, [:disabled, :deactivated] 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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/migrate/20130107050049_add_invitation_code_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddInvitationCodeToUsers < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :users, :invitation_code, :string 4 | change_column :users, :invitation_code, :string, :null => false 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_relative 'config/application' 6 | 7 | Huginn::Application.load_tasks 8 | -------------------------------------------------------------------------------- /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 | #= require ace/mode-sql.js 6 | #= require ace/mode-json.js 7 | #= require ace/mode-yaml.js 8 | #= require ace/mode-text.js 9 | -------------------------------------------------------------------------------- /db/migrate/20150808115436_remove_requirement_from_users_invitation_code.rb: -------------------------------------------------------------------------------- 1 | class RemoveRequirementFromUsersInvitationCode < ActiveRecord::Migration[4.2] 2 | def change 3 | change_column_null :users, :invitation_code, true, ENV['INVITATION_CODE'].presence || 'try-huginn' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/admin/users/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Create User" -%> 2 | 3 |
4 |
5 |
6 |

Create new User

7 | 8 | <%= render partial: 'form' %> 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/views/admin/users/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Edit " + @user.username -%> 2 | 3 |
4 |
5 |
6 |

Edit User

7 | 8 | <%= render partial: 'form' %> 9 |
10 |
11 |
12 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :hybrid 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker/test/scripts/test_env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | /scripts/setup_env 5 | source /app/.env 6 | 7 | export RAILS_ENV=test 8 | unset no_proxy 9 | 10 | bundle install --with test development --path vendor/bundle -j 4 11 | bundle exec rake db:create db:migrate 12 | bundle exec "$@" 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scenarios.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 | } -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /db/migrate/20140602014917_add_indices_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddIndicesToScenarios < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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/20160807000122_remove_queue_from_email_digest_agent_memory.rb: -------------------------------------------------------------------------------- 1 | class RemoveQueueFromEmailDigestAgentMemory < ActiveRecord::Migration[4.2] 2 | def up 3 | Agents::EmailDigestAgent.find_each do |agent| 4 | agent.memory.delete("queue") 5 | agent.save!(validate: false) 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20161007030910_reset_data_output_agents.rb: -------------------------------------------------------------------------------- 1 | class ResetDataOutputAgents < ActiveRecord::Migration[4.2] 2 | def up 3 | Agents::DataOutputAgent.find_each do |agent| 4 | agent.memory = {} 5 | agent.save(validate: false) 6 | agent.latest_events(true) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /config/initializers/silence_worker_status_logger.rb: -------------------------------------------------------------------------------- 1 | module SilencedLogger 2 | def call(env) 3 | return super(env) if env['PATH_INFO'] !~ %r{^/worker_status} 4 | Rails.logger.silence(Logger::ERROR) do 5 | super(env) 6 | end 7 | end 8 | end 9 | Rails::Rack::Logger.send(:prepend, SilencedLogger) 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 | -------------------------------------------------------------------------------- /db/migrate/20140408150825_rename_webhook_to_web_request.rb: -------------------------------------------------------------------------------- 1 | class RenameWebhookToWebRequest < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20121222074732_create_links.rb: -------------------------------------------------------------------------------- 1 | class CreateLinks < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /db/migrate/20140216201250_add_propagate_immediately_to_agent.rb: -------------------------------------------------------------------------------- 1 | class AddPropagateImmediatelyToAgent < ActiveRecord::Migration[4.2] 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[4.2] 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 | -------------------------------------------------------------------------------- /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/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 | belongs_to :controller, class_name: 'Agent', inverse_of: :control_links_as_controller 4 | belongs_to :control_target, class_name: 'Agent', inverse_of: :control_links_as_control_target 5 | end 6 | -------------------------------------------------------------------------------- /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 |

-------------------------------------------------------------------------------- /spec/support/feature_helpers.rb: -------------------------------------------------------------------------------- 1 | module FeatureHelpers 2 | def select_agent_type(type) 3 | select2(type, from: "Type") 4 | 5 | # Wait for all parts of the Agent form to load: 6 | expect(page).to have_css("div.function_buttons") # Options editor 7 | expect(page).to have_css(".well.description > p") # Markdown description 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/tweets.js.coffee: -------------------------------------------------------------------------------- 1 | $ -> 2 | $('.tweet-body').each -> 3 | $(this).click -> 4 | $(this).off('click') 5 | twttr.widgets.createTweet( 6 | this.dataset.tweetId 7 | this 8 | # conversation: 'none' 9 | # cards: 'hidden' 10 | ).then (el) -> 11 | el.previousSibling.style.display = 'none' 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/features/form_configurable_feature_spec.rb: -------------------------------------------------------------------------------- 1 | require 'capybara_helper' 2 | 3 | describe "form configuring agents", js: true do 4 | it 'completes fields with predefined array values' do 5 | login_as(users(:bob)) 6 | visit edit_agent_path(agents(:bob_csv_agent)) 7 | check('Propagate immediately') 8 | select2("serialize", from: "Mode") 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /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[4.2] 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 rails-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/faraday_patch.rb: -------------------------------------------------------------------------------- 1 | # Monkey patch https://github.com/lostisland/faraday/pull/513 2 | # Fixes encoding issue when passing an URL with non UTF-8 characters 3 | module Faraday 4 | module Utils 5 | def unescape(s) 6 | string = s.to_s 7 | string.force_encoding(Encoding::BINARY) if RUBY_VERSION >= '1.9' 8 | CGI.unescape string 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /db/migrate/20140906030139_set_events_count_default.rb: -------------------------------------------------------------------------------- 1 | class SetEventsCountDefault < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/20140605032822_add_guid_to_agents.rb: -------------------------------------------------------------------------------- 1 | class AddGuidToAgents < ActiveRecord::Migration[4.2] 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 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20130819160603_create_agent_logs.rb: -------------------------------------------------------------------------------- 1 | class CreateAgentLogs < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /db/migrate/20140531232016_add_fields_to_scenarios.rb: -------------------------------------------------------------------------------- 1 | class AddFieldsToScenarios < ActiveRecord::Migration[4.2] 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 6 | change_column :scenarios, :guid, :string, :null => false 7 | add_column :scenarios, :source_url, :string 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/data_fixtures/stubhub_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "response":{ 3 | "docs":[ 4 | { 5 | "url": "https://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/20150507153436_update_keep_events_for_to_be_in_seconds.rb: -------------------------------------------------------------------------------- 1 | class UpdateKeepEventsForToBeInSeconds < ActiveRecord::Migration[4.2] 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 14 | -------------------------------------------------------------------------------- /docker/single-process/environment.yml: -------------------------------------------------------------------------------- 1 | # This needs at least compose 1.6.0 2 | version: '2' 3 | 4 | services: 5 | huginn_base: 6 | environment: 7 | DATABASE_ADAPTER: mysql2 8 | DATABASE_NAME: huginn 9 | DATABASE_USERNAME: huginn 10 | DATABASE_PASSWORD: myhuginnpassword 11 | APP_SECRET_TOKEN: 3bd139f9186b31a85336bb89cd1a1337078921134b2f48e022fd09c234d764d3e19b018b2ab789c6e0e04a1ac9e3365116368049660234c2038dc9990513d49c 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20140901143732_add_control_links.rb: -------------------------------------------------------------------------------- 1 | class AddControlLinks < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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[4.2] 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[4.2] 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[4.2] 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 | .local/ 31 | -------------------------------------------------------------------------------- /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/) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20121216025930_create_events.rb: -------------------------------------------------------------------------------- 1 | class CreateEvents < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | belongs_to :source, :class_name => "Agent", :inverse_of => :links_as_source 4 | belongs_to :receiver, :class_name => "Agent", :inverse_of => :links_as_receiver 5 | 6 | before_create :store_event_id_at_creation 7 | 8 | def store_event_id_at_creation 9 | self.event_id_at_creation = source.events.limit(1).reorder("id desc").pluck(:id).first || 0 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /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.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20160823151303_set_emit_error_event_for_twitter_action_agents.rb: -------------------------------------------------------------------------------- 1 | class SetEmitErrorEventForTwitterActionAgents < ActiveRecord::Migration[4.2] 2 | def up 3 | Agents::TwitterActionAgent.find_each do |agent| 4 | agent.options['emit_error_events'] = 'true' 5 | agent.save!(validate: false) 6 | end 7 | end 8 | 9 | def down 10 | Agents::TwitterActionAgent.find_each do |agent| 11 | agent.options.delete('emit_error_events') 12 | agent.save!(validate: false) 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /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[4.2] 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[4.2] 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/20161124065838_add_templates_to_resolve_url.rb: -------------------------------------------------------------------------------- 1 | class AddTemplatesToResolveUrl < ActiveRecord::Migration[5.0] 2 | def up 3 | Agents::WebsiteAgent.find_each do |agent| 4 | if agent.event_keys.try!(:include?, 'url') 5 | agent.options['template'] = (agent.options['template'] || {}).tap { |template| 6 | template['url'] ||= '{{ url | to_uri: _response_.url }}' 7 | } 8 | agent.save!(validate: false) 9 | end 10 | end 11 | end 12 | 13 | def down 14 | # No need to revert 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /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.2] 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 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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] 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/20170419073748_set_rss_content_type.rb: -------------------------------------------------------------------------------- 1 | class SetRssContentType < ActiveRecord::Migration[5.0] 2 | def up 3 | Agents::DataOutputAgent.find_each do |agent| 4 | if agent.options['rss_content_type'].nil? 5 | agent.options['rss_content_type'] = 'text/xml' 6 | agent.save(validate: false) 7 | end 8 | end 9 | end 10 | 11 | def down 12 | Agents::DataOutputAgent.find_each do |agent| 13 | if agent.options.delete('rss_content_type') 14 | agent.save(validate: false) 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /doc/heroku/update.md: -------------------------------------------------------------------------------- 1 | ## Update an existing Heroku deployment 2 | 3 | Once you've run `bin/setup_heroku`, you should have 'huginn/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 | -------------------------------------------------------------------------------- /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 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /db/migrate/20160423163416_add_xml_namespace_option_to_data_output_agents.rb: -------------------------------------------------------------------------------- 1 | class AddXmlNamespaceOptionToDataOutputAgents < ActiveRecord::Migration[4.2] 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 18 | -------------------------------------------------------------------------------- /db/migrate/20150219213604_add_type_option_attribute_to_pushbullet_agents.rb: -------------------------------------------------------------------------------- 1 | class AddTypeOptionAttributeToPushbulletAgents < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20140515211100_create_services.rb: -------------------------------------------------------------------------------- 1 | class CreateServices < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20121220053905_create_agents.rb: -------------------------------------------------------------------------------- 1 | class CreateAgents < ActiveRecord::Migration[4.2] 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/views/scenarios/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Edit " + @scenario.name -%> 2 | 3 |
4 |
5 |
6 | 11 | 12 | <%= render 'form' %> 13 | 14 |
15 | 16 |
17 |
18 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, scenarios_path, class: "btn btn-default" %> 19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /app/views/user_credentials/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Create Credential" -%> 2 | 3 |
4 |
5 |
6 | 11 | 12 | <%= render 'form' %> 13 | 14 |
15 | 16 |
17 |
18 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, user_credentials_path, class: "btn btn-default" %> 19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /db/migrate/20170307190555_add_min_events_option_to_peak_detector_agents.rb: -------------------------------------------------------------------------------- 1 | class AddMinEventsOptionToPeakDetectorAgents < ActiveRecord::Migration[5.0] 2 | def up 3 | Agents::PeakDetectorAgent.find_each do |agent| 4 | if agent.options['min_events'].nil? 5 | agent.options['min_events'] = '4' 6 | agent.save(validate: false) 7 | end 8 | end 9 | end 10 | 11 | def down 12 | Agents::PeakDetectorAgent.find_each do |agent| 13 | if agent.options['min_events'].present? 14 | agent.options.delete 'min_events' 15 | agent.save(validate: false) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /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/views/user_credentials/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, @user_credential.credential_name -%> 2 | 3 |
4 |
5 |
6 | 11 | 12 | <%= render 'form' %> 13 | 14 |
15 | 16 |
17 |
18 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, user_credentials_path, class: "btn btn-default" %> 19 |
20 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/features/undefined_agents_spec.rb: -------------------------------------------------------------------------------- 1 | require 'capybara_helper' 2 | 3 | describe "handling undefined agents" do 4 | before do 5 | login_as(users(:bob)) 6 | agent = agents(:bob_website_agent) 7 | agent.update_attribute(:type, 'Agents::UndefinedAgent') 8 | end 9 | 10 | it 'renders the error page' do 11 | visit agents_path 12 | expect(page).to have_text("Error: Agent(s) are 'missing in action'") 13 | expect(page).to have_text('Undefined Agent') 14 | end 15 | 16 | it 'deletes all undefined agents' do 17 | visit agents_path 18 | click_on('Delete Missing Agents') 19 | expect(page).to have_text('Your Agents') 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /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/20170731191002_migrate_growl_agent_to_liquid.rb: -------------------------------------------------------------------------------- 1 | class MigrateGrowlAgentToLiquid < ActiveRecord::Migration[5.1] 2 | def up 3 | Agents::GrowlAgent.find_each do |agent| 4 | agent.options['subject'] = '{{subject}}' if agent.options['subject'].blank? 5 | agent.options['message'] = '{{ message | default: text }}' if agent.options['message'].blank? 6 | agent.save(validate: false) 7 | end 8 | end 9 | 10 | def down 11 | Agents::GrowlAgent.find_each do |agent| 12 | %w(subject message sticky priority).each do |key| 13 | agent.options.delete(key) 14 | end 15 | agent.save(validate: false) 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/agents/agent_views/liquid_output_agent/_show.html.erb: -------------------------------------------------------------------------------- 1 |

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

4 | 5 | 6 | 12 | 13 |

14 | ... or any other extension you wish, as the extension does not change the content or mime type. 15 |

16 | -------------------------------------------------------------------------------- /app/models/user_credential.rb: -------------------------------------------------------------------------------- 1 | class UserCredential < ActiveRecord::Base 2 | MODES = %w[text java_script] 3 | 4 | belongs_to :user 5 | 6 | validates_presence_of :credential_name 7 | validates_presence_of :credential_value 8 | validates_inclusion_of :mode, :in => MODES 9 | validates_presence_of :user_id 10 | validates_uniqueness_of :credential_name, :scope => :user_id 11 | 12 | before_validation :default_mode_to_text 13 | before_save :trim_fields 14 | 15 | protected 16 | 17 | def trim_fields 18 | credential_name.strip! 19 | credential_value.strip! 20 | end 21 | 22 | def default_mode_to_text 23 | self.mode = 'text' unless mode.present? 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /db/migrate/20160301113717_add_confirmable_attributes_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddConfirmableAttributesToUsers < ActiveRecord::Migration[4.2] 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: Time.zone.now) 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20131227000021_add_cached_dates_to_agent.rb: -------------------------------------------------------------------------------- 1 | class AddCachedDatesToAgent < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/initializers/action_mailer.rb: -------------------------------------------------------------------------------- 1 | ActionMailer::Base.smtp_settings = { 2 | address: ENV['SMTP_SERVER'] || "smtp.gmail.com", 3 | port: ENV['SMTP_PORT'] || 587, 4 | domain: ENV['SMTP_DOMAIN'], 5 | authentication: ENV['SMTP_AUTHENTICATION'] == 'none' ? nil : ENV['SMTP_AUTHENTICATION'] || "plain", 6 | enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true', 7 | user_name: ENV['SMTP_USER_NAME'] == 'none' ? nil : ENV['SMTP_USER_NAME'].presence, 8 | password: ENV['SMTP_USER_NAME'] == 'none' ? nil : ENV['SMTP_PASSWORD'].presence, 9 | openssl_verify_mode: ENV['SMTP_OPENSSL_VERIFY_MODE'].presence, 10 | ca_path: ENV['SMTP_OPENSSL_CA_PATH'].presence, 11 | ca_file: ENV['SMTP_OPENSSL_CA_FILE'].presence 12 | } 13 | -------------------------------------------------------------------------------- /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.2] 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 | -------------------------------------------------------------------------------- /docker/single-process/scripts/init: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd /app 5 | 6 | if [[ -z "$1" && -n "$WORKER_CMD" ]]; then 7 | set -- $WORKER_CMD 8 | fi 9 | 10 | source /scripts/setup_env 11 | 12 | if [[ -z "${DO_NOT_CREATE_DATABASE}" && -z "$1" ]]; then 13 | bundle exec rake db:create RAILS_ENV=${RAILS_ENV} 14 | fi 15 | 16 | if [ -z "$1" ]; then 17 | bundle exec rake db:migrate RAILS_ENV=${RAILS_ENV} 18 | fi 19 | 20 | if [[ -z "${DO_NOT_SEED}" && -z "$1" ]]; then 21 | set +e 22 | bundle exec rake db:seed RAILS_ENV=${RAILS_ENV} 23 | set -e 24 | fi 25 | 26 | if [ -z "$1" ]; then 27 | exec bundle exec unicorn -c config/unicorn.rb 28 | else 29 | exec bundle exec rails runner "$@" RAILS_ENV=${RAILS_ENV} 30 | fi 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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(scenario_import_params) 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 | 17 | private 18 | 19 | def scenario_import_params 20 | params.require(:scenario_import).permit(:url, :data, :file, :do_import, merges: {}) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/views/agents/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Create Agent" -%> 2 | 3 |
4 |
5 |
6 | 12 | 13 |
14 | <%= render 'form' %> 15 |
16 | 17 |
18 | 19 |
20 |
21 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, agents_path, class: "btn btn-default" %> 22 |
23 |
24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/16.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 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/concerns/oauthable.rb: -------------------------------------------------------------------------------- 1 | module Oauthable 2 | extend ActiveSupport::Concern 3 | 4 | included do |base| 5 | @valid_oauth_providers = :all 6 | validates_presence_of :service_id 7 | end 8 | 9 | def oauthable? 10 | true 11 | end 12 | 13 | def valid_services_for(user) 14 | if valid_oauth_providers == :all 15 | user.available_services 16 | else 17 | user.available_services.where(provider: valid_oauth_providers) 18 | end 19 | end 20 | 21 | def valid_oauth_providers 22 | self.class.valid_oauth_providers 23 | end 24 | 25 | module ClassMethods 26 | def valid_oauth_providers(*providers) 27 | return @valid_oauth_providers if providers == [] 28 | @valid_oauth_providers = providers 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /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 | case queue_adapter.class.name # not using class since it would load adapter dependent gems 10 | when 'ActiveJob::QueueAdapters::DelayedJobAdapter' 11 | return Delayed::Job.where(failed_at: nil, queue: 'propagation').count == 0 12 | when 'ActiveJob::QueueAdapters::ResqueAdapter' 13 | return Resque.size('propagation') == 0 && 14 | Resque.workers.select { |w| w.job && w.job['queue'] && w.job['queue']['propagation'] }.count == 0 15 | else 16 | raise NotImplementedError, "unsupported adapter: #{queue_adapter}" 17 | end 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /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[4.2] 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 | -------------------------------------------------------------------------------- /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 18 | twitter: 19 | token: 1234token 20 | secret: 56789secret 21 | refresh_token: refresh12345 22 | provider: twitter 23 | name: test 24 | expires_at: <%= Time.parse("2015-01-01 00:00:00") %> 25 | options: <%= { user_id: 12345 }.to_yaml.inspect %> 26 | user: bob 27 | -------------------------------------------------------------------------------- /db/migrate/20140730005210_convert_efa_skip_created_at.rb: -------------------------------------------------------------------------------- 1 | class ConvertEfaSkipCreatedAt < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /docker/multi-process/scripts/standalone-packages: -------------------------------------------------------------------------------- 1 | export DEBIAN_FRONTEND=noninteractive 2 | apt-get update 3 | apt-get install -y python2.7 python-docutils mysql-server \ 4 | supervisor python-pip && \ 5 | apt-get -y clean 6 | pip install supervisor-stdout 7 | rm -rf /var/lib/apt/lists/* 8 | rm -rf /usr/share/doc/ 9 | rm -rf /usr/share/man/ 10 | rm -rf /usr/share/locale/ 11 | rm -rf /var/log/* 12 | 13 | mkdir -p /var/log/supervisor /var/log/mysql 14 | chgrp -R 0 /etc/supervisor /var/lib/mysql /var/log/supervisor /var/log/mysql 15 | chmod -R g=u /etc/supervisor /var/lib/mysql /var/log/supervisor /var/log/mysql 16 | sed -r -i /etc/mysql/my.cnf \ 17 | -e 's/^ *user *.+/user=1001/' \ 18 | -e 's#/var/run/mysqld/mysqld.sock#/app/tmp/sockets/mysqld.sock#' \ 19 | -e 's#/var/run/mysqld/mysqld.pid#/app/tmp/pids/mysqld.pid#' 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 "cleaning fields" do 12 | it "should trim whitespace" do 13 | user_credential = user_credentials(:bob_aws_key) 14 | user_credential.credential_name = " new name " 15 | user_credential.credential_value = " new value " 16 | user_credential.save! 17 | expect(user_credential.credential_name).to eq("new name") 18 | expect(user_credential.credential_value).to eq("new value") 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20140603104211_rename_digest_email_to_email_digest.rb: -------------------------------------------------------------------------------- 1 | class RenameDigestEmailToEmailDigest < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /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 tweets.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 | -------------------------------------------------------------------------------- /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/edit.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Edit " + @agent.name -%> 2 | 3 |
4 |
5 |
6 | 12 |
13 |
14 |
15 | <%= render 'form' %> 16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back', agents_path, class: "btn btn-default" %> 24 | <%= link_to icon_tag('glyphicon-asterisk') + ' Show', agent_path(@agent), class: "btn btn-default" %> 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /docker/test/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM huginn/huginn-single-process 2 | 3 | ENV PHANTOM_VERSION "phantomjs-2.1.1" 4 | ENV PHANTOM_JS "${PHANTOM_VERSION}-linux-x86_64" 5 | 6 | USER 0 7 | 8 | RUN apt-get update && \ 9 | apt-get -y install \ 10 | build-essential \ 11 | chrpath \ 12 | libssl-dev \ 13 | libxft-dev \ 14 | libfreetype6 \ 15 | libfreetype6-dev \ 16 | libfontconfig1 \ 17 | libfontconfig1-dev curl && \ 18 | apt-get -y clean && \ 19 | curl -Ls https://bitbucket.org/ariya/phantomjs/downloads/${PHANTOM_JS}.tar.bz2 \ 20 | | tar jxvf - --strip-components=2 -C /usr/local/bin/ ${PHANTOM_JS}/bin/phantomjs 21 | 22 | RUN LC_ALL=en_US.UTF-8 ON_HEROKU=true bundle install --with test development --path vendor/bundle -j 4 23 | 24 | COPY docker/test/scripts/test_env /scripts/ 25 | ENTRYPOINT ["/scripts/test_env"] 26 | CMD ["rake spec"] 27 | 28 | USER 1001 29 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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, params: {: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 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | Huginn Docker images 2 | ==================== 3 | 4 | Huginn is packaged in two docker images. 5 | 6 | #### [huginn/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 | #### [huginn/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 | -------------------------------------------------------------------------------- /app/assets/stylesheets/tables.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 | -------------------------------------------------------------------------------- /db/migrate/20160607055850_change_events_order_to_events_list_order.rb: -------------------------------------------------------------------------------- 1 | class ChangeEventsOrderToEventsListOrder < ActiveRecord::Migration[4.2] 2 | def up 3 | Agents::DataOutputAgent.find_each do |agent| 4 | if value = agent.options.delete('events_order') 5 | agent.options['events_list_order'] = value 6 | agent.save!(validate: false) 7 | end 8 | end 9 | end 10 | 11 | def down 12 | Agents::DataOutputAgent.transaction do 13 | Agents::DataOutputAgent.find_each do |agent| 14 | if agent.options['events_order'] 15 | raise ActiveRecord::IrreversibleMigration, "Cannot revert migration because events_order is configured" 16 | end 17 | 18 | if value = agent.options.delete('events_list_order') 19 | agent.options['events_order'] = value 20 | agent.save!(validate: false) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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[4.2] 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 | -------------------------------------------------------------------------------- /spec/capybara_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'capybara/rails' 3 | require 'capybara/poltergeist' 4 | require 'capybara-screenshot/rspec' 5 | require 'capybara-select2' 6 | 7 | CAPYBARA_TIMEOUT = ENV['CI'] == 'true' ? 60 : 5 8 | 9 | Capybara.register_driver :poltergeist do |app| 10 | Capybara::Poltergeist::Driver.new(app, timeout: CAPYBARA_TIMEOUT) 11 | end 12 | 13 | Capybara.javascript_driver = :poltergeist 14 | Capybara.default_max_wait_time = CAPYBARA_TIMEOUT 15 | 16 | Capybara::Screenshot.prune_strategy = { keep: 3 } 17 | 18 | RSpec.configure do |config| 19 | config.include Warden::Test::Helpers 20 | config.include AlertConfirmer, type: :feature 21 | config.include FeatureHelpers, type: :feature 22 | 23 | config.before(:suite) do 24 | Warden.test_mode! 25 | end 26 | 27 | config.after(:each) do 28 | Warden.test_reset! 29 | end 30 | end 31 | 32 | VCR.configure do |config| 33 | config.ignore_localhost = true 34 | end 35 | -------------------------------------------------------------------------------- /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+|new)/) 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/data_fixtures/twitter_scenario.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema_version": 1, 3 | "name": "Twitter", 4 | "description": "No description provided", 5 | "source_url": false, 6 | "guid": "d91bf1508595a65b4a00b74d59857918", 7 | "tag_fg_color": "#ffffff", 8 | "tag_bg_color": "#5bc0de", 9 | "icon": "pencil", 10 | "exported_at": "2017-01-17T19:08:31Z", 11 | "agents": [ 12 | { 13 | "type": "Agents::TwitterStreamAgent", 14 | "name": "Twitter Counts", 15 | "disabled": false, 16 | "guid": "03c77bb9ec0fb2415f7c399ba849289d", 17 | "options": { 18 | "filters": [ 19 | "ruby vulnerability", 20 | "rails security", 21 | "huginn" 22 | ], 23 | "expected_update_period_in_days": "2", 24 | "generate": "counts" 25 | }, 26 | "schedule": "every_10m", 27 | "keep_events_for": 604800 28 | } 29 | ], 30 | "links": [ 31 | ], 32 | "control_links": [ 33 | 34 | ] 35 | } 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 | -------------------------------------------------------------------------------- /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/huginn/huginn", 5 | "repository": "https://github.com/huginn/huginn", 6 | "logo": "https://raw.githubusercontent.com/huginn/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_oauth2 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.client_id 21 | end 22 | 23 | def consumer_secret 24 | (config = Devise.omniauth_configs[:dropbox]) && config.strategy.client_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 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20140213053001_add_event_id_at_creation_to_links.rb: -------------------------------------------------------------------------------- 1 | class AddEventIdAtCreationToLinks < ActiveRecord::Migration[4.2] 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[4.2] 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'] = 'string(.)' 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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /db/seeds/seeder.rb: -------------------------------------------------------------------------------- 1 | class Seeder 2 | def self.seed 3 | user = User.find_or_initialize_by(:email => ENV['SEED_EMAIL'].presence || "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'].presence || "admin" 10 | user.password = ENV['SEED_PASSWORD'].presence || "password" 11 | user.password_confirmation = ENV['SEED_PASSWORD'].presence || "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/huginn/huginn/wiki" 19 | else 20 | raise('Unable to import the default scenario') 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /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(:twitter), 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 | -------------------------------------------------------------------------------- /app/views/agents/index.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Agents" -%> 2 | 3 |
4 |
5 |
6 | 9 | 10 | <%= render 'agents/table' %> 11 | 12 |
13 | 14 |
15 | <%= link_to icon_tag('glyphicon-plus') + ' New Agent', new_agent_path, class: "btn btn-default" %> 16 | <%= link_to icon_tag('glyphicon-refresh') + ' Run event propagation', propagate_agents_path, method: 'post', class: "btn btn-default" %> 17 | <%= link_to icon_tag('glyphicon-random') + ' View diagram', diagram_path, class: "btn btn-default" %> 18 | <%= link_to icon_tag('glyphicon-adjust') + toggle_disabled_text, toggle_visibility_agents_path, method: :put, class: "btn btn-default visibility-enabler" %> 19 |
20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /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 | end 9 | 10 | describe "accepting a callback url" do 11 | it "should update the user's credentials" do 12 | request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/twitter.json'))) 13 | expect { 14 | get :twitter 15 | }.to change { users(:bob).services.count }.by(1) 16 | end 17 | end 18 | 19 | describe "handling a provider with non-standard omniauth options" do 20 | it "should update the user's credentials" do 21 | request.env["omniauth.auth"] = JSON.parse(File.read(Rails.root.join('spec/data_fixtures/services/37signals.json'))) 22 | expect { 23 | get "37signals" 24 | }.to change { users(:bob).services.count }.by(1) 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/data_fixtures/urlTest.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test 5 | 6 | 7 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/views/scenarios/_enable_agents_modal.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /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 | it "is valid with put-your-key-here or your-key" do 24 | agent.options['api_key'] = 'put-your-key-here' 25 | expect(agent).to be_valid 26 | expect(agent.working?).to be_falsey 27 | 28 | agent.options['api_key'] = 'your-key' 29 | expect(agent).to be_valid 30 | expect(agent.working?).to be_falsey 31 | end 32 | 33 | describe "#service" do 34 | it "doesn't have a Service object attached" do 35 | expect(agent.service).to be_nil 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Resend unlock code" -%> 2 | 3 |
4 |
5 |
6 |

Resend unlock instructions

7 | 8 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f| %> 9 | <%= devise_error_messages! %> 10 | 11 |
12 | <%= f.label :login, class: 'col-md-2 col-md-offset-2 control-label' %> 13 |
14 | <%= f.text_field :login, autofocus: true, class: 'form-control' %> 15 |
16 |
17 | 18 |
19 |
20 | <%= f.submit "Resend unlock instructions", class: "btn btn-primary" %> 21 |
22 |
23 | <% end %> 24 | 25 |
26 | 27 | <%= render "devise/shared/links" %> 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Resend confirmation" -%> 2 | 3 |
4 |
5 |
6 |

Resend confirmation instructions

7 | 8 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f| %> 9 | <%= devise_error_messages! %> 10 | 11 |
12 | <%= f.label :login, class: 'col-md-2 col-md-offset-2 control-label' %> 13 |
14 | <%= f.text_field :login, autofocus: true, class: 'form-control' %> 15 |
16 |
17 | 18 |
19 |
20 | <%= f.submit "Resend confirmation instructions", class: "btn btn-primary" %> 21 |
22 |
23 | <% end %> 24 | 25 | <%= render "devise/shared/links" %> 26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Reset password" -%> 2 | 3 |
4 |
5 |
6 |

Forgot your password?

7 | 8 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post, class: 'form-horizontal' }) do |f| %> 9 | <%= devise_error_messages! %> 10 | 11 |
12 | <%= f.label :login, :class => 'col-md-2 col-md-offset-2 control-label' %> 13 |
14 | <%= f.text_field :login, autofocus: true, :class => 'form-control' %> 15 |
16 |
17 | 18 |
19 |
20 | <%= f.submit "Send me reset password instructions", class: "btn btn-primary" %> 21 |
22 |
23 | <% end %> 24 | 25 |
26 | 27 | <%= render "devise/shared/links" %> 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | <% if @user.persisted? && @user.active? && @user != current_user %> 26 | <%= link_to 'Become User', switch_to_user_admin_user_path(@user), class: "btn btn-default btn-info", data: { confirm: 'This will log you in as another user. Would you like to continue?' } %> 27 | <% end %> 28 |
29 |
30 | -------------------------------------------------------------------------------- /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_jobs.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 | } -------------------------------------------------------------------------------- /docker/single-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 | environment: 14 | MYSQL_ROOT_PASSWORD: myrootpassword 15 | MYSQL_DATABASE: huginn 16 | MYSQL_USER: huginn 17 | MYSQL_PASSWORD: myhuginnpassword 18 | 19 | huginn_web: 20 | image: huginn/huginn-single-process 21 | restart: always 22 | extends: 23 | file: environment.yml 24 | service: huginn_base 25 | ports: 26 | - 3000:3000 27 | links: 28 | - mysql 29 | environment: 30 | MYSQL_PORT_3306_TCP_ADDR: mysql 31 | MYSQL_PORT_3306_TCP_PORT: 3306 32 | 33 | huginn_threaded: 34 | image: huginn/huginn-single-process 35 | restart: always 36 | extends: 37 | file: environment.yml 38 | service: huginn_base 39 | links: 40 | - mysql 41 | command: /scripts/init bin/threaded.rb 42 | environment: 43 | MYSQL_PORT_3306_TCP_ADDR: mysql 44 | MYSQL_PORT_3306_TCP_PORT: 3306 45 | 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /docker/single-process/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | COPY docker/scripts/prepare /scripts/ 4 | RUN /scripts/prepare 5 | 6 | WORKDIR /app 7 | 8 | COPY ["Gemfile", "Gemfile.lock", "/app/"] 9 | COPY lib/gemfile_helper.rb /app/lib/ 10 | COPY vendor/gems/ /app/vendor/gems/ 11 | 12 | # Get rid of annoying "fatal: Not a git repository (or any of the parent directories): .git" messages 13 | RUN umask 002 && git init && \ 14 | LC_ALL=en_US.UTF-8 RAILS_ENV=production APP_SECRET_TOKEN=secret DATABASE_ADAPTER=mysql2 ON_HEROKU=true bundle install --without test development --path vendor/bundle -j 4 15 | 16 | COPY ./ /app/ 17 | 18 | ARG OUTDATED_DOCKER_IMAGE_NAMESPACE=false 19 | ENV OUTDATED_DOCKER_IMAGE_NAMESPACE ${OUTDATED_DOCKER_IMAGE_NAMESPACE} 20 | 21 | RUN umask 002 && \ 22 | LC_ALL=en_US.UTF-8 RAILS_ENV=production APP_SECRET_TOKEN=secret DATABASE_ADAPTER=mysql2 ON_HEROKU=true bundle exec rake assets:clean assets:precompile && \ 23 | chmod g=u /app/.env.example /app/Gemfile.lock /app/config/ /app/tmp/ 24 | 25 | EXPOSE 3000 26 | 27 | COPY ["docker/scripts/setup_env", "docker/single-process/scripts/init", "/scripts/"] 28 | CMD ["/scripts/init"] 29 | 30 | USER 1001 31 | -------------------------------------------------------------------------------- /app/views/scenario_imports/new.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :title, "Import Scenario" -%> 2 | 3 |
4 |
5 |
6 | <% if @scenario_import.errors.any? %> 7 |
8 |

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

9 | <% @scenario_import.errors.full_messages.each do |msg| %> 10 |

<%= msg %>

11 | <% end %> 12 |
13 | <% end %> 14 |
15 |
16 | 17 | <%= form_for @scenario_import, :multipart => true do |f| %> 18 | <%= f.hidden_field :data %> 19 | 20 | <% if @scenario_import.step_one? %> 21 | <%= render 'step_one', :f => f %> 22 | <% elsif @scenario_import.step_two? %> 23 | <%= render 'step_two', :f => f %> 24 | <% end %> 25 | <% end %> 26 | 27 |
28 | 29 |
30 |
31 | <%= link_to icon_tag('glyphicon-chevron-left') + ' Back'.html_safe, scenarios_path, class: "btn btn-default" %> 32 |
33 |
34 |
35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | $.rails.handleMethod.apply $("").appendTo($("body")).get(0) 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 | -------------------------------------------------------------------------------- /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: 'bin/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 | -------------------------------------------------------------------------------- /db/migrate/20160108221620_website_agent_does_not_use_event_url.rb: -------------------------------------------------------------------------------- 1 | class WebsiteAgentDoesNotUseEventUrl < ActiveRecord::Migration[4.2] 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 | -------------------------------------------------------------------------------- /db/migrate/20161124061256_convert_website_agent_template_for_merge.rb: -------------------------------------------------------------------------------- 1 | class ConvertWebsiteAgentTemplateForMerge < ActiveRecord::Migration[5.0] 2 | def up 3 | Agents::WebsiteAgent.find_each do |agent| 4 | extract = agent.options['extract'].presence 5 | template = agent.options['template'].presence 6 | next unless extract.is_a?(Hash) && template.is_a?(Hash) 7 | 8 | (extract.keys - template.keys).each do |key| 9 | extract[key]['hidden'] = true 10 | end 11 | 12 | template.delete_if { |key, value| 13 | extract.key?(key) && 14 | value.match(/\A\{\{\s*#{Regexp.quote(key)}\s*\}\}\z/) 15 | } 16 | 17 | agent.save!(validate: false) 18 | end 19 | end 20 | 21 | def down 22 | Agents::WebsiteAgent.find_each do |agent| 23 | extract = agent.options['extract'].presence 24 | template = agent.options['template'].presence 25 | next unless extract.is_a?(Hash) && template.is_a?(Hash) 26 | 27 | (extract.keys - template.keys).each do |key| 28 | unless extract[key].delete('hidden').in?([true, 'true']) 29 | template[key] = "{{ #{key} }}" 30 | end 31 | end 32 | 33 | agent.save!(validate: false) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ActiveSupport.on_load(:delayed_job_active_record) do 14 | class Delayed::Job 15 | scope :pending, -> { where("locked_at IS NULL AND attempts = 0") } 16 | scope :awaiting_retry, -> { where("failed_at IS NULL AND attempts > 0 AND locked_at IS NULL") } 17 | scope :failed_jobs, -> { where("failed_at IS NOT NULL") } 18 | end 19 | 20 | database_deadlocks_when_using_optimized_strategy = lambda do 21 | ENV["DATABASE_ADAPTER"] == "mysql2" 22 | end 23 | 24 | Delayed::Backend::ActiveRecord.configure do |config| 25 | config.reserve_sql_strategy = :default_sql 26 | end if database_deadlocks_when_using_optimized_strategy.call 27 | end 28 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/migrations/20161124065838_add_templates_to_resolve_url_spec.rb: -------------------------------------------------------------------------------- 1 | load 'spec/rails_helper.rb' 2 | load File.join('db/migrate', File.basename(__FILE__, '_spec.rb') + '.rb') 3 | 4 | describe AddTemplatesToResolveUrl do 5 | let :valid_options do 6 | { 7 | 'name' => "XKCD", 8 | 'expected_update_period_in_days' => "2", 9 | 'type' => "html", 10 | 'url' => "http://xkcd.com", 11 | 'mode' => 'on_change', 12 | 'extract' => { 13 | 'url' => { 'css' => "#comic img", 'value' => "@src" }, 14 | 'title' => { 'css' => "#comic img", 'value' => "@alt" }, 15 | 'hovertext' => { 'css' => "#comic img", 'value' => "@title" } 16 | } 17 | } 18 | end 19 | 20 | let :agent do 21 | Agents::WebsiteAgent.create!( 22 | user: users(:bob), 23 | name: "xkcd", 24 | options: valid_options, 25 | keep_events_for: 2.days 26 | ) 27 | end 28 | 29 | it 'should add a template for an existing WebsiteAgent with `url`' do 30 | expect(agent.options).not_to include('template') 31 | AddTemplatesToResolveUrl.new.up 32 | agent.reload 33 | expect(agent.options).to include( 34 | 'template' => { 35 | 'url' => '{{ url | to_uri: _response_.url }}' 36 | } 37 | ) 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/controllers/users/registrations_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | module Users 4 | describe RegistrationsController do 5 | describe "POST create" do 6 | before do 7 | @request.env["devise.mapping"] = Devise.mappings[:user] 8 | end 9 | 10 | context 'with valid params' do 11 | it "imports the default scenario for the new user" do 12 | mock(DefaultScenarioImporter).import(is_a(User)) 13 | 14 | post :create, params: { 15 | :user => {username: 'jdoe', email: 'jdoe@example.com', 16 | password: 's3cr3t55', password_confirmation: 's3cr3t55', invitation_code: 'try-huginn'} 17 | } 18 | end 19 | end 20 | 21 | context 'with invalid params' do 22 | it "does not import the default scenario" do 23 | stub(DefaultScenarioImporter).import(is_a(User)) { fail "Should not attempt import" } 24 | 25 | setup_controller_for_warden 26 | post :create, params: {:user => {}} 27 | end 28 | 29 | it 'does not allow to set the admin flag' do 30 | expect { post :create, params: {:user => {admin: 'true'}} }.to raise_error(ActionController::UnpermittedParameters) 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker/multi-process/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | 3 | COPY docker/scripts/prepare /scripts/ 4 | RUN /scripts/prepare 5 | 6 | COPY docker/multi-process/scripts/standalone-packages /scripts/ 7 | RUN /scripts/standalone-packages 8 | 9 | WORKDIR /app 10 | 11 | COPY ["Gemfile", "Gemfile.lock", "/app/"] 12 | COPY lib/gemfile_helper.rb /app/lib/ 13 | COPY vendor/gems/ /app/vendor/gems/ 14 | 15 | # Get rid of annoying "fatal: Not a git repository (or any of the parent directories): .git" messages 16 | RUN umask 002 && git init && \ 17 | LC_ALL=en_US.UTF-8 RAILS_ENV=production APP_SECRET_TOKEN=secret DATABASE_ADAPTER=mysql2 ON_HEROKU=true bundle install --without test development --path vendor/bundle -j 4 18 | 19 | COPY ./ /app/ 20 | 21 | ARG OUTDATED_DOCKER_IMAGE_NAMESPACE=false 22 | ENV OUTDATED_DOCKER_IMAGE_NAMESPACE ${OUTDATED_DOCKER_IMAGE_NAMESPACE} 23 | 24 | RUN umask 002 && \ 25 | LC_ALL=en_US.UTF-8 RAILS_ENV=production APP_SECRET_TOKEN=secret DATABASE_ADAPTER=mysql2 ON_HEROKU=true bundle exec rake assets:clean assets:precompile && \ 26 | chmod g=u /app/.env.example /app/Gemfile.lock /app/config/ /app/tmp/ 27 | 28 | 29 | EXPOSE 3000 30 | 31 | COPY ["docker/scripts/setup_env", "docker/multi-process/scripts/init", "/scripts/"] 32 | CMD ["/scripts/init"] 33 | 34 | USER 1001 35 | 36 | VOLUME /var/lib/mysql 37 | -------------------------------------------------------------------------------- /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')) --------------------------------------------------------------------------------