perus 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +16 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/exe/perus-pinger +23 -0
  12. data/exe/perus-server +23 -0
  13. data/lib/perus/options.rb +30 -0
  14. data/lib/perus/pinger/chrome_command.rb +54 -0
  15. data/lib/perus/pinger/command.rb +123 -0
  16. data/lib/perus/pinger/commands/chrome_execute.rb +26 -0
  17. data/lib/perus/pinger/commands/chrome_navigate.rb +25 -0
  18. data/lib/perus/pinger/commands/chrome_reload.rb +29 -0
  19. data/lib/perus/pinger/commands/kill_process.rb +13 -0
  20. data/lib/perus/pinger/commands/remove_path.rb +15 -0
  21. data/lib/perus/pinger/commands/replace.rb +18 -0
  22. data/lib/perus/pinger/commands/restart.rb +9 -0
  23. data/lib/perus/pinger/commands/script.rb +59 -0
  24. data/lib/perus/pinger/commands/upgrade.rb +16 -0
  25. data/lib/perus/pinger/commands/upload.rb +15 -0
  26. data/lib/perus/pinger/metrics/chrome.rb +33 -0
  27. data/lib/perus/pinger/metrics/cpu.rb +17 -0
  28. data/lib/perus/pinger/metrics/hd.rb +14 -0
  29. data/lib/perus/pinger/metrics/mem.rb +11 -0
  30. data/lib/perus/pinger/metrics/process.rb +27 -0
  31. data/lib/perus/pinger/metrics/screenshot.rb +36 -0
  32. data/lib/perus/pinger/metrics/temp.rb +18 -0
  33. data/lib/perus/pinger/metrics/uptime.rb +10 -0
  34. data/lib/perus/pinger/metrics/value.rb +18 -0
  35. data/lib/perus/pinger/pinger.rb +218 -0
  36. data/lib/perus/pinger.rb +38 -0
  37. data/lib/perus/server/admin.rb +83 -0
  38. data/lib/perus/server/app.rb +304 -0
  39. data/lib/perus/server/db.rb +29 -0
  40. data/lib/perus/server/form.rb +73 -0
  41. data/lib/perus/server/helpers.rb +36 -0
  42. data/lib/perus/server/migrations/001_create_systems.rb +20 -0
  43. data/lib/perus/server/migrations/002_create_configs.rb +12 -0
  44. data/lib/perus/server/migrations/003_create_values.rb +23 -0
  45. data/lib/perus/server/migrations/004_create_groups.rb +12 -0
  46. data/lib/perus/server/migrations/005_create_errors.rb +15 -0
  47. data/lib/perus/server/migrations/006_create_alerts.rb +14 -0
  48. data/lib/perus/server/migrations/007_create_actions.rb +18 -0
  49. data/lib/perus/server/migrations/008_create_metrics.rb +16 -0
  50. data/lib/perus/server/migrations/009_create_command_config.rb +13 -0
  51. data/lib/perus/server/migrations/010_create_scripts.rb +13 -0
  52. data/lib/perus/server/migrations/011_create_script_commands.rb +14 -0
  53. data/lib/perus/server/migrations/012_create_config_metrics.rb +14 -0
  54. data/lib/perus/server/models/action.rb +71 -0
  55. data/lib/perus/server/models/alert.rb +9 -0
  56. data/lib/perus/server/models/command_config.rb +58 -0
  57. data/lib/perus/server/models/config.rb +30 -0
  58. data/lib/perus/server/models/config_metric.rb +15 -0
  59. data/lib/perus/server/models/error.rb +5 -0
  60. data/lib/perus/server/models/group.rb +12 -0
  61. data/lib/perus/server/models/metric.rb +53 -0
  62. data/lib/perus/server/models/script.rb +39 -0
  63. data/lib/perus/server/models/script_command.rb +15 -0
  64. data/lib/perus/server/models/system.rb +160 -0
  65. data/lib/perus/server/models/value.rb +11 -0
  66. data/lib/perus/server/public/css/reset.css +102 -0
  67. data/lib/perus/server/public/css/style.css +461 -0
  68. data/lib/perus/server/public/fonts/opensans/OpenSans-Italic.ttf +0 -0
  69. data/lib/perus/server/public/fonts/opensans/OpenSans-Light.ttf +0 -0
  70. data/lib/perus/server/public/fonts/opensans/OpenSans-LightItalic.ttf +0 -0
  71. data/lib/perus/server/public/fonts/opensans/OpenSans-Regular.ttf +0 -0
  72. data/lib/perus/server/public/fonts/opensans/OpenSans-Semibold.ttf +0 -0
  73. data/lib/perus/server/public/fonts/opensans/OpenSans-SemiboldItalic.ttf +0 -0
  74. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.eot +0 -0
  75. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.svg +1824 -0
  76. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.ttf +0 -0
  77. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.woff +0 -0
  78. data/lib/perus/server/public/fonts/opensans/opensans-light-webfont.woff2 +0 -0
  79. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.eot +0 -0
  80. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.svg +1824 -0
  81. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.ttf +0 -0
  82. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.woff +0 -0
  83. data/lib/perus/server/public/fonts/opensans/opensans-regular-webfont.woff2 +0 -0
  84. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.eot +0 -0
  85. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.svg +1824 -0
  86. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.ttf +0 -0
  87. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.woff +0 -0
  88. data/lib/perus/server/public/fonts/opensans/opensans-semibold-webfont.woff2 +0 -0
  89. data/lib/perus/server/public/fonts/opensans/stylesheet.css +35 -0
  90. data/lib/perus/server/public/js/dygraph-combined.js +6 -0
  91. data/lib/perus/server/public/js/jquery.js +4 -0
  92. data/lib/perus/server/server.rb +32 -0
  93. data/lib/perus/server/views/admin/edit.erb +9 -0
  94. data/lib/perus/server/views/admin/index.erb +18 -0
  95. data/lib/perus/server/views/admin/new.erb +5 -0
  96. data/lib/perus/server/views/alerts/form.erb +3 -0
  97. data/lib/perus/server/views/command_config.erb +37 -0
  98. data/lib/perus/server/views/configs/edit.erb +36 -0
  99. data/lib/perus/server/views/configs/form.erb +1 -0
  100. data/lib/perus/server/views/errors.erb +6 -0
  101. data/lib/perus/server/views/groups/form.erb +1 -0
  102. data/lib/perus/server/views/index.erb +21 -0
  103. data/lib/perus/server/views/layout.erb +44 -0
  104. data/lib/perus/server/views/scripts/edit.erb +36 -0
  105. data/lib/perus/server/views/scripts/form.erb +2 -0
  106. data/lib/perus/server/views/system.erb +202 -0
  107. data/lib/perus/server/views/systems/form.erb +6 -0
  108. data/lib/perus/server/views/systems.erb +17 -0
  109. data/lib/perus/server.rb +24 -0
  110. data/lib/perus/version.rb +3 -0
  111. data/lib/perus.rb +10 -0
  112. data/perus.gemspec +36 -0
  113. metadata +354 -0
@@ -0,0 +1,71 @@
1
+ module Perus::Server
2
+ class Action < Sequel::Model
3
+ plugin :validation_helpers
4
+ plugin :serialization
5
+
6
+ many_to_one :system
7
+ many_to_one :command_config
8
+ many_to_one :script
9
+
10
+ serialize_attributes :json, :file
11
+
12
+ def config_hash
13
+ if command_config_id
14
+ hash = command_config.config_hash
15
+ else
16
+ hash = script.config_hash
17
+ end
18
+
19
+ # replace the command config/script id with the action's id
20
+ hash['id'] = id
21
+ hash
22
+ end
23
+
24
+ def command_name
25
+ if script_id
26
+ script.name
27
+ else
28
+ command_config.command
29
+ end
30
+ end
31
+
32
+ def options
33
+ if script_id
34
+ {}
35
+ else
36
+ command_config.options
37
+ end
38
+ end
39
+
40
+ def file_name
41
+ file['original_name']
42
+ end
43
+
44
+ def file_url
45
+ prefix = URI(Server.options.uploads_url)
46
+ path = File.join(system_id.to_s, file['filename'])
47
+ (prefix + path).to_s
48
+ end
49
+
50
+ def file_path
51
+ File.join(system.uploads_dir, file['filename'])
52
+ end
53
+
54
+ def validate
55
+ super
56
+ validates_presence :system_id
57
+ end
58
+
59
+ def after_destroy
60
+ super
61
+
62
+ if command_config_id
63
+ command_config.destroy
64
+ end
65
+
66
+ if file
67
+ File.unlink(file_path)
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,9 @@
1
+ module Perus::Server
2
+ class Alert < Sequel::Model
3
+ def execute(systems)
4
+ systems.select do |system|
5
+ system.instance_eval(code)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,58 @@
1
+ module Perus::Server
2
+ class CommandConfig < Sequel::Model
3
+ plugin :validation_helpers
4
+ plugin :serialization
5
+ serialize_attributes :json, :options
6
+
7
+ def config_hash
8
+ {
9
+ id: id,
10
+ type: command,
11
+ options: options
12
+ }
13
+ end
14
+
15
+ def validate
16
+ super
17
+ validates_presence :command
18
+ end
19
+
20
+ def command_class
21
+ Perus::Pinger.const_get(command)
22
+ end
23
+
24
+ def update_options!(params)
25
+ self.options = self.class.process_options(params)
26
+ save
27
+ end
28
+
29
+ def self.process_options(params)
30
+ return {} if params['options'].nil?
31
+
32
+ # ignore empty options (will use default)
33
+ options = params['options'].reject do |attr, value|
34
+ value.empty?
35
+ end
36
+
37
+ # replace 'true' and 'false' with actual boolean values
38
+ # but only for boolean options
39
+ command = Perus::Pinger.const_get(params['command'])
40
+ command.options.each do |option|
41
+ next unless option.boolean?
42
+ next unless options.include?(option.name.to_s)
43
+ value = options[option.name.to_s]
44
+ options[option.name.to_s] = value == 'true'
45
+ end
46
+
47
+ options
48
+ end
49
+
50
+ def self.create_with_params(params)
51
+ options = process_options(params)
52
+ CommandConfig.create(
53
+ command: params['command'],
54
+ options: options
55
+ )
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,30 @@
1
+ module Perus::Server
2
+ class Config < Sequel::Model
3
+ plugin :validation_helpers
4
+ one_to_many :systems
5
+ one_to_many :config_metrics, order: 'name asc'
6
+
7
+ def metric_hashes
8
+ config_metrics.collect(&:config_hash)
9
+ end
10
+
11
+ def largest_order
12
+ if config_metrics.empty?
13
+ 0
14
+ else
15
+ config_metrics.last.order
16
+ end
17
+ end
18
+
19
+ def validate
20
+ super
21
+ validates_presence :name
22
+ validates_unique :name
23
+ end
24
+
25
+ def after_destroy
26
+ super
27
+ config_metrics.each(&:destroy)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module Perus::Server
2
+ class ConfigMetric < Sequel::Model
3
+ many_to_one :config
4
+ many_to_one :command_config
5
+
6
+ def config_hash
7
+ command_config.config_hash
8
+ end
9
+
10
+ def after_destroy
11
+ super
12
+ command_config.destroy
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module Perus::Server
2
+ class Error < Sequel::Model
3
+ many_to_one :system
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module Perus::Server
2
+ class Group < Sequel::Model
3
+ plugin :validation_helpers
4
+ one_to_many :systems
5
+
6
+ def validate
7
+ super
8
+ validates_presence :name
9
+ validates_unique :name
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,53 @@
1
+ require 'uri'
2
+
3
+ module Perus::Server
4
+ class Metric < Sequel::Model
5
+ plugin :serialization
6
+ many_to_one :system
7
+ serialize_attributes :json, :file
8
+
9
+ def numeric?
10
+ type == 'num'
11
+ end
12
+
13
+ def string?
14
+ type == 'str'
15
+ end
16
+
17
+ def file?
18
+ type == 'file'
19
+ end
20
+
21
+ def url
22
+ prefix = URI(Server.options.uploads_url)
23
+ path = File.join(system_id.to_s, file['filename'])
24
+ (prefix + path).to_s
25
+ end
26
+
27
+ def self.add(name, system_id, type, file_data = nil)
28
+ existing = Metric.where(system_id: system_id, name: name, type: type).first
29
+
30
+ if existing
31
+ return if type != 'file'
32
+ record = existing
33
+ else
34
+ record = Metric.new(system_id: system_id, name: name, type: type)
35
+ end
36
+
37
+ record.file = file_data
38
+ record.save
39
+ end
40
+
41
+ def self.add_numeric(name, system_id)
42
+ self.add(name, system_id, 'num')
43
+ end
44
+
45
+ def self.add_string(name, system_id)
46
+ self.add(name, system_id, 'str')
47
+ end
48
+
49
+ def self.add_file(name, system_id, file_data)
50
+ self.add(name, system_id, 'file', file_data)
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ module Perus::Server
2
+ class Script < Sequel::Model
3
+ plugin :validation_helpers
4
+ one_to_many :script_commands, order: 'name asc'
5
+
6
+ def code_name
7
+ name.gsub(' ', '_').camelize
8
+ end
9
+
10
+ def config_hash
11
+ {
12
+ id: id,
13
+ type: 'Script',
14
+ options: {
15
+ commands: script_commands.collect(&:config_hash)
16
+ }
17
+ }
18
+ end
19
+
20
+ def largest_order
21
+ if script_commands.empty?
22
+ 0
23
+ else
24
+ script_commands.last.order
25
+ end
26
+ end
27
+
28
+ def validate
29
+ super
30
+ validates_presence :name
31
+ validates_unique :name
32
+ end
33
+
34
+ def after_destroy
35
+ super
36
+ script_commands.each(&:destroy)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Perus::Server
2
+ class ScriptCommand < Sequel::Model
3
+ many_to_one :script
4
+ many_to_one :command_config
5
+
6
+ def config_hash
7
+ command_config.config_hash
8
+ end
9
+
10
+ def after_destroy
11
+ super
12
+ command_config.destroy
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,160 @@
1
+ require 'fileutils'
2
+ require 'json'
3
+
4
+ module Perus::Server
5
+ class System < Sequel::Model
6
+ plugin :validation_helpers
7
+
8
+ many_to_one :config
9
+ many_to_one :group
10
+ one_to_many :metrics
11
+ one_to_many :values
12
+ one_to_many :actions
13
+ one_to_many :collection_errors, class_name: 'Perus::Server::Error'
14
+
15
+ def validate
16
+ super
17
+ validates_presence :name
18
+ validates_unique :name
19
+ end
20
+
21
+ def pending_actions
22
+ actions_dataset.where(timestamp: nil).all
23
+ end
24
+
25
+ def action_hashes
26
+ # actions referring to a command_config return a hash when calling
27
+ # config_hashes, actions referring to scripts return an array.
28
+ # flatten is needed to merge the two response types together.
29
+ pending_actions.collect(&:config_hashes).flatten
30
+ end
31
+
32
+
33
+ # ---------------------------------------
34
+ # metrics
35
+ # ---------------------------------------
36
+ def save_metric_errors(params, timestamp)
37
+ errors = JSON.parse(params['metric_errors'])
38
+
39
+ errors.each do |module_name, module_errors|
40
+ module_errors.each do |error|
41
+ Error.create(
42
+ system_id: id,
43
+ timestamp: timestamp,
44
+ module: module_name,
45
+ description: error
46
+ )
47
+ end
48
+ end
49
+ end
50
+
51
+ def save_values(params, timestamp)
52
+ # store the set of known metrics for this system. metrics have either
53
+ # str or num values.
54
+ values = JSON.parse(params['metrics'])
55
+
56
+ values.each do |(metric_name, value)|
57
+ # file uploads are treated specially and don't need a value
58
+ # record created for them
59
+ if value.kind_of?(Hash)
60
+ file_data = save_file(params, value, metric_name)
61
+ Metric.add_file(metric_name, id, file_data)
62
+ next
63
+ end
64
+
65
+ attrs = {
66
+ system_id: id,
67
+ timestamp: timestamp,
68
+ metric: metric_name
69
+ }
70
+
71
+ if value.kind_of?(Numeric)
72
+ attrs[:num_value] = value
73
+ Metric.add_numeric(metric_name, id)
74
+ else
75
+ attrs[:str_value] = value
76
+ Metric.add_string(metric_name, id)
77
+ end
78
+
79
+ Value.create(attrs)
80
+ end
81
+ end
82
+
83
+ def latest(name)
84
+ values_dataset.where(metric: name).order_by('timestamp desc').first
85
+ end
86
+
87
+
88
+ # ---------------------------------------
89
+ # actions
90
+ # ---------------------------------------
91
+ def save_actions(params, timestamp)
92
+ actions = JSON.parse(params['actions'])
93
+
94
+ actions.each do |id, result|
95
+ # JSON.dump turns integer keys into strings
96
+ action = Action.with_pk!(id.to_i)
97
+ action.timestamp = timestamp
98
+
99
+ if result == true
100
+ action.success = true
101
+ elsif result.is_a?(Hash)
102
+ action.success = true
103
+ action.file = save_file(params, result, action.id)
104
+ else
105
+ action.success = false
106
+ action.response = result.to_s
107
+ end
108
+
109
+ action.save
110
+ end
111
+ end
112
+
113
+
114
+ # ---------------------------------------
115
+ # files
116
+ # ---------------------------------------
117
+ def uploads_dir
118
+ @uploads_dir ||= File.join(Server.options.uploads_dir, id.to_s)
119
+ end
120
+
121
+ def save_file(params, entry, name)
122
+ # entry should be a hash with a single key 'file', which is the
123
+ # key name used to find the file in params
124
+ raise 'Invalid file upload' unless entry.is_a?(Hash) && entry.include?('file')
125
+
126
+ # ensure the uploads directory exists for this system
127
+ FileUtils.mkdir_p(uploads_dir)
128
+
129
+ # files are sent as top level parameters. metrics rename files
130
+ # with their own name (i.e 'screenshot' saves a file called
131
+ # 'screenshot'), while actions rename files to the action id
132
+ upload = params[entry['file']]
133
+
134
+ # store the file
135
+ name = name.to_s + File.extname(upload[:filename])
136
+ File.open(File.join(uploads_dir, name.to_s), 'wb') do |f|
137
+ f.write(upload[:tempfile].read)
138
+ end
139
+
140
+ # return details of the file so it can be served with the correct
141
+ # original filename and mimetype
142
+ {filename: name, original_name: upload[:filename], mimetype: upload[:type]}
143
+ end
144
+
145
+ def upload_urls
146
+ file_metrics = metrics_dataset.where(type: 'file').all
147
+ pairs = file_metrics.collect do |metric|
148
+ [metric.name, metric.url]
149
+ end
150
+
151
+ # convert to name => url hash
152
+ Hash[pairs]
153
+ end
154
+
155
+ def screenshot_url
156
+ screenshot = metrics_dataset.where(name: 'screenshot').first
157
+ screenshot ? screenshot.url : ''
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,11 @@
1
+ module Perus::Server
2
+ class Value < Sequel::Model
3
+ plugin :validation_helpers
4
+ many_to_one :system
5
+
6
+ def validate
7
+ super
8
+ validates_presence [:system_id, :metric, :timestamp]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,102 @@
1
+ /*
2
+ html5doctor.com Reset Stylesheet
3
+ v1.6.1
4
+ Last Updated: 2010-09-17
5
+ Author: Richard Clark - http://richclarkdesign.com
6
+ Twitter: @rich_clark
7
+ */
8
+
9
+ html, body, div, span, object, iframe,
10
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
11
+ abbr, address, cite, code,
12
+ del, dfn, em, img, ins, kbd, q, samp,
13
+ small, strong, sub, sup, var,
14
+ b, i,
15
+ dl, dt, dd, ol, ul, li,
16
+ fieldset, form, label, legend,
17
+ table, caption, tbody, tfoot, thead, tr, th, td,
18
+ article, aside, canvas, details, figcaption, figure,
19
+ footer, header, hgroup, menu, nav, section, summary,
20
+ time, mark, audio, video {
21
+ margin:0;
22
+ padding:0;
23
+ border:0;
24
+ outline:0;
25
+ font-size:100%;
26
+ vertical-align:baseline;
27
+ background:transparent;
28
+ }
29
+
30
+ body {
31
+ line-height:1;
32
+ }
33
+
34
+ article,aside,details,figcaption,figure,
35
+ footer,header,hgroup,menu,nav,section {
36
+ display:block;
37
+ }
38
+
39
+ nav ul {
40
+ list-style:none;
41
+ }
42
+
43
+ blockquote, q {
44
+ quotes:none;
45
+ }
46
+
47
+ blockquote:before, blockquote:after,
48
+ q:before, q:after {
49
+ content:'';
50
+ content:none;
51
+ }
52
+
53
+ a {
54
+ margin:0;
55
+ padding:0;
56
+ font-size:100%;
57
+ vertical-align:baseline;
58
+ background:transparent;
59
+ }
60
+
61
+ /* change colours to suit your needs */
62
+ ins {
63
+ background-color:#ff9;
64
+ color:#000;
65
+ text-decoration:none;
66
+ }
67
+
68
+ /* change colours to suit your needs */
69
+ mark {
70
+ background-color:#ff9;
71
+ color:#000;
72
+ font-style:italic;
73
+ font-weight:bold;
74
+ }
75
+
76
+ del {
77
+ text-decoration: line-through;
78
+ }
79
+
80
+ abbr[title], dfn[title] {
81
+ border-bottom:1px dotted;
82
+ cursor:help;
83
+ }
84
+
85
+ table {
86
+ border-collapse:collapse;
87
+ border-spacing:0;
88
+ }
89
+
90
+ /* change border colour to suit your needs */
91
+ hr {
92
+ display:block;
93
+ height:1px;
94
+ border:0;
95
+ border-top:1px solid #cccccc;
96
+ margin:1em 0;
97
+ padding:0;
98
+ }
99
+
100
+ input, select {
101
+ vertical-align:middle;
102
+ }