suricate 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/suricate/application.rb +90 -0
- data/lib/suricate/charts/chart.rb +15 -0
- data/lib/suricate/charts/chart_builder.rb +22 -0
- data/lib/suricate/charts/chart_serie.rb +19 -0
- data/lib/suricate/charts/chart_serie_builder.rb +29 -0
- data/lib/suricate/configuration/configuration.rb +13 -0
- data/lib/suricate/configuration/configuration_builder.rb +29 -0
- data/lib/suricate/configuration/widget_configuration.rb +24 -0
- data/lib/suricate/configuration/widget_configurations_builder.rb +64 -0
- data/lib/suricate/delegation_callback.rb +22 -0
- data/lib/suricate/generator/assets/javascript/api.js +53 -0
- data/lib/suricate/generator/assets/javascript/application.js +97 -0
- data/lib/suricate/generator/assets/javascript/chart-js-chart-factory.js +54 -0
- data/lib/suricate/generator/assets/javascript/suricate.js +4 -0
- data/lib/suricate/generator/assets/javascript/vendor/Chart.min.js +11 -0
- data/lib/suricate/generator/assets/javascript/vendor/bootstrap.min.js +7 -0
- data/lib/suricate/generator/assets/javascript/vendor/jquery.min.js +4 -0
- data/lib/suricate/generator/assets/javascript/widget-factory.js +43 -0
- data/lib/suricate/generator/assets/javascript/widget-updater.js +48 -0
- data/lib/suricate/generator/assets/javascript/widget-view.js +46 -0
- data/lib/suricate/generator/assets/javascript/widgets/counter-widget.js +23 -0
- data/lib/suricate/generator/assets/javascript/widgets/line-chart-widget.js +26 -0
- data/lib/suricate/generator/assets/javascript/widgets/widget.js +46 -0
- data/lib/suricate/generator/assets/javascript/widgets-container.js +22 -0
- data/lib/suricate/generator/assets/stylesheets/suricate.css +85 -0
- data/lib/suricate/generator/assets/stylesheets/vendor/bootstrap-theme.css.map +1 -0
- data/lib/suricate/generator/assets/stylesheets/vendor/bootstrap-theme.min.css +5 -0
- data/lib/suricate/generator/assets/stylesheets/vendor/bootstrap.css.map +1 -0
- data/lib/suricate/generator/assets/stylesheets/vendor/bootstrap.min.css +5 -0
- data/lib/suricate/output_driver.rb +28 -0
- data/lib/suricate/refinements/string.rb +27 -0
- data/lib/suricate/request_context.rb +14 -0
- data/lib/suricate/sinatra_output_driver.rb +14 -0
- data/lib/suricate/template/template.rb +11 -0
- data/lib/suricate/template/template_repository.rb +41 -0
- data/lib/suricate/version.rb +3 -0
- data/lib/suricate/widget_repository.rb +23 -0
- data/lib/suricate/widgets/counter_widget.rb +10 -0
- data/lib/suricate/widgets/line_chart_widget.rb +9 -0
- data/lib/suricate/widgets/responses/chart_widget_response.rb +17 -0
- data/lib/suricate/widgets/responses/counter_widget_response.rb +18 -0
- data/lib/suricate/widgets/widget.rb +31 -0
- data/lib/suricate.rb +50 -0
- metadata +200 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: fafd76c3fca39a449326894d87061f3fb75d3bd3
|
4
|
+
data.tar.gz: a0e8c87b6667fdda4c24ec5b7a5a8499ad0e7110
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f7bc99b933c0fd737d6f930b4340ca66679ca367f4b924c1b65f4bc0e9ae5e4c40d103dfe74a80bbc7c09c550af9225d3e46d58a5435a67c707df860251befb6
|
7
|
+
data.tar.gz: a81dac32f5283ff2e91ec188c250592db16a734f86f2eb2bf50b0c3ce639cdd7045fb6400a6044d7c36d312eda2b4a1a4b6c97d45ca443999b8463dcd155316b
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Suricate
|
5
|
+
class Application < Sinatra::Base
|
6
|
+
extend Forwardable
|
7
|
+
def_delegators :@configuration, :widget_repository, :template_repository
|
8
|
+
|
9
|
+
def initialize(configuration)
|
10
|
+
super(nil)
|
11
|
+
# TODO : switch to Rack directly
|
12
|
+
# Good : hide Sinatra complexity from outside,
|
13
|
+
# the interface will be the same when switchting to Rack
|
14
|
+
# Bad : set class var from instance
|
15
|
+
self.class.set(:configuration, configuration)
|
16
|
+
@configuration = configuration
|
17
|
+
end
|
18
|
+
|
19
|
+
configure do
|
20
|
+
set :public_folder, Proc.new { configuration.public_directory }
|
21
|
+
set :views, Proc.new { configuration.templates_directory }
|
22
|
+
set :show_exceptions, :after_handler
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
#
|
29
|
+
# Errors
|
30
|
+
#
|
31
|
+
|
32
|
+
error WidgetRepository::WidgetNotFound do
|
33
|
+
output.api_error(404, env['sinatra.error'].message)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
# API
|
41
|
+
#
|
42
|
+
|
43
|
+
# Get widgets' configuration
|
44
|
+
get('/api/widgets') do
|
45
|
+
configurations = widget_repository.configurations.map(&:to_h)
|
46
|
+
output.api_success(widgets: configurations)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get widget's data
|
50
|
+
get('/api/widgets/:id') do
|
51
|
+
widget = widget_repository.instantiate(params['id'], context)
|
52
|
+
widget.execute
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
#
|
59
|
+
# Pages
|
60
|
+
#
|
61
|
+
|
62
|
+
# Pages
|
63
|
+
get('/:page') do
|
64
|
+
render_page(params['page'])
|
65
|
+
end
|
66
|
+
|
67
|
+
# Default page
|
68
|
+
get('/') do
|
69
|
+
render_page(@configuration.default_page)
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
|
75
|
+
private
|
76
|
+
def context
|
77
|
+
RequestContext.new(request: request, session: session, output: output)
|
78
|
+
end
|
79
|
+
|
80
|
+
def output
|
81
|
+
@output ||= SinatraOutputDriver.new(self)
|
82
|
+
end
|
83
|
+
|
84
|
+
def render_page(page)
|
85
|
+
template = template_repository.find_page(page)
|
86
|
+
output.render(template.render)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Suricate
|
2
|
+
class ChartBuilder
|
3
|
+
def initialize
|
4
|
+
@series = []
|
5
|
+
@labels = []
|
6
|
+
end
|
7
|
+
|
8
|
+
def serie
|
9
|
+
builder = ChartSerieBuilder.new
|
10
|
+
yield builder
|
11
|
+
@series << builder.serie
|
12
|
+
end
|
13
|
+
|
14
|
+
def labels(labels)
|
15
|
+
@labels = labels
|
16
|
+
end
|
17
|
+
|
18
|
+
def chart
|
19
|
+
Chart.new(@labels, @series)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Suricate
|
2
|
+
class ChartSerie
|
3
|
+
attr_reader :name, :values, :color
|
4
|
+
|
5
|
+
def initialize(name, values, color)
|
6
|
+
@name = name
|
7
|
+
@values = values
|
8
|
+
@color = color
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_h
|
12
|
+
{
|
13
|
+
name: @name,
|
14
|
+
values: @values,
|
15
|
+
color: @color
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Suricate
|
2
|
+
class ChartSerieBuilder
|
3
|
+
def initialize
|
4
|
+
@values = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def name(name)
|
8
|
+
@name = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def color(color)
|
12
|
+
@color = color
|
13
|
+
end
|
14
|
+
|
15
|
+
# Add multiple values
|
16
|
+
def values(values)
|
17
|
+
@values.concat(values)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add one value
|
21
|
+
def value(value)
|
22
|
+
@values << value
|
23
|
+
end
|
24
|
+
|
25
|
+
def serie
|
26
|
+
ChartSerie.new(@name, @values, @color)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Suricate
|
2
|
+
class Configuration
|
3
|
+
attr_reader :template_repository, :public_directory, :widget_repository,
|
4
|
+
:default_page
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@template_repository = options[:template_repository]
|
8
|
+
@public_directory = options[:public_directory]
|
9
|
+
@widget_repository = options[:widget_repository]
|
10
|
+
@default_page = options[:default_page]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Suricate
|
2
|
+
class ConfigurationBuilder
|
3
|
+
attr_accessor :templates_directory, :public_directory, :default_page
|
4
|
+
|
5
|
+
def configuration
|
6
|
+
Configuration.new(template_repository: template_repository,
|
7
|
+
widget_repository: widget_repository,
|
8
|
+
default_page: @default_page,
|
9
|
+
public_directory: @public_directory)
|
10
|
+
end
|
11
|
+
|
12
|
+
def widgets
|
13
|
+
yield widget_configurations_builder
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def template_repository
|
18
|
+
TemplateRepository.new(@templates_directory)
|
19
|
+
end
|
20
|
+
|
21
|
+
def widget_repository
|
22
|
+
WidgetRepository.new(widget_configurations_builder.configurations)
|
23
|
+
end
|
24
|
+
|
25
|
+
def widget_configurations_builder
|
26
|
+
@widgets_builder ||= WidgetConfigurationsBuilder.new(template_repository)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Suricate
|
2
|
+
class WidgetConfiguration
|
3
|
+
attr_reader :id, :klass, :collector, :options
|
4
|
+
|
5
|
+
def initialize(id, klass, collector, options = {})
|
6
|
+
@id = id
|
7
|
+
@klass = klass
|
8
|
+
@collector = collector
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def instantiate(context)
|
13
|
+
@klass.new(id: id, context: context, collector: collector, options: options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_h
|
17
|
+
{
|
18
|
+
id: @id,
|
19
|
+
type: @klass.type,
|
20
|
+
configuration: @options
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Suricate
|
2
|
+
class WidgetConfigurationsBuilder
|
3
|
+
using Refinements::String
|
4
|
+
class IDAlreadyUsedError < StandardError; end
|
5
|
+
attr_reader :configurations
|
6
|
+
|
7
|
+
def initialize(template_repository)
|
8
|
+
@template_repository = template_repository
|
9
|
+
@configurations = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def counter(id, collector, options = {})
|
13
|
+
register(id, CounterWidget, collector, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def line_chart(id, collector, options = {})
|
17
|
+
register(id, LineChartWidget, collector, options)
|
18
|
+
end
|
19
|
+
|
20
|
+
def register(id, klass, collector, options = {})
|
21
|
+
id = id.to_sym
|
22
|
+
if find_with_id(id)
|
23
|
+
raise IDAlreadyUsedError.new("id \"#{id}\" already taken")
|
24
|
+
else
|
25
|
+
configuration = build_configuration(id, klass, collector, options)
|
26
|
+
@configurations << configuration
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def build_configuration(id, klass, collector, options)
|
32
|
+
build_options(id, klass.type, options)
|
33
|
+
WidgetConfiguration.new(id.to_sym, klass, collector, options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_options(id, type, options)
|
37
|
+
options[:template] = find_widget_template(id, type, options[:template])
|
38
|
+
if options[:templates]
|
39
|
+
options[:templates].map! do |name|
|
40
|
+
@template_repository.find_widget(name)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def find_widget_template(id, type, wanted_template)
|
46
|
+
template = nil
|
47
|
+
default_template = type.underscore.sub(/_widget$/, '')
|
48
|
+
template_names = [wanted_template || id.to_s, default_template]
|
49
|
+
|
50
|
+
template_names.each do |name|
|
51
|
+
begin
|
52
|
+
template = @template_repository.find_widget(name) and break
|
53
|
+
rescue TemplateRepository::TemplateNotFound
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
template.render if template
|
58
|
+
end
|
59
|
+
|
60
|
+
def find_with_id(id)
|
61
|
+
@configurations.find { |conf| conf.id == id }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Suricate
|
2
|
+
class DelegationCallback
|
3
|
+
def initialize(*callbacks)
|
4
|
+
callbacks.each do |callback|
|
5
|
+
define_callback(callback)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(callback_name, *args)
|
10
|
+
callback = instance_variable_get(:"@#{callback_name}_callback")
|
11
|
+
callback.call(*args) if callback
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
private
|
16
|
+
def define_callback(callback)
|
17
|
+
define_singleton_method callback.to_sym do |&block|
|
18
|
+
instance_variable_set(:"@#{callback}_callback", block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
(function () {
|
2
|
+
"use strict";
|
3
|
+
window.Suricate = window.Suricate || {};
|
4
|
+
|
5
|
+
/*
|
6
|
+
* Public
|
7
|
+
*/
|
8
|
+
|
9
|
+
Suricate.API = function(baseURL) {
|
10
|
+
this.baseURL = baseURL;
|
11
|
+
};
|
12
|
+
|
13
|
+
// Get widget configurations
|
14
|
+
Suricate.API.prototype.getWidgets = function(callback) {
|
15
|
+
this.sendRequest("GET", "/widgets", {}, function(data) {
|
16
|
+
callback(data.widgets);
|
17
|
+
});
|
18
|
+
};
|
19
|
+
|
20
|
+
// Get updated data for widget
|
21
|
+
Suricate.API.prototype.getWidgetData = function(widgetID, callback) {
|
22
|
+
var path = "/widgets/" + widgetID;
|
23
|
+
this.sendRequest("GET", path, {}, callback);
|
24
|
+
};
|
25
|
+
|
26
|
+
|
27
|
+
/*
|
28
|
+
* Private
|
29
|
+
*/
|
30
|
+
|
31
|
+
// Send request to API
|
32
|
+
Suricate.API.prototype.sendRequest = function(method, path, params, callback) {
|
33
|
+
var self = this;
|
34
|
+
var url = this.baseURL + path;
|
35
|
+
$.ajax({
|
36
|
+
url: url,
|
37
|
+
method: method,
|
38
|
+
data: params,
|
39
|
+
success: function(data) {
|
40
|
+
self.handleResponse(url, data, callback);
|
41
|
+
}
|
42
|
+
});
|
43
|
+
};
|
44
|
+
|
45
|
+
Suricate.API.prototype.handleResponse = function(url, json, callback) {
|
46
|
+
if(json.status == 200) {
|
47
|
+
callback(json.data);
|
48
|
+
}
|
49
|
+
else {
|
50
|
+
console.error("Failed to load request", "url", url, "response", json);
|
51
|
+
}
|
52
|
+
};
|
53
|
+
}());
|
@@ -0,0 +1,97 @@
|
|
1
|
+
(function() {
|
2
|
+
"use strict";
|
3
|
+
window.Suricate = window.Suricate || {};
|
4
|
+
|
5
|
+
Suricate.Application = function(widgetsContainer, configuration) {
|
6
|
+
this.widgetsContainer = new Suricate.WidgetsContainer(widgetsContainer);
|
7
|
+
this.configuration = configuration;
|
8
|
+
this.chartFactory = configuration.chartFactory || new Suricate.ChartJSChartFactory();
|
9
|
+
this.initialized = false;
|
10
|
+
this.widgets = [];
|
11
|
+
this.updateInterval = undefined;
|
12
|
+
};
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
// Starts the application
|
17
|
+
Suricate.Application.prototype.start = function() {
|
18
|
+
var self = this;
|
19
|
+
this.init(function() {
|
20
|
+
self.startUpdates();
|
21
|
+
});
|
22
|
+
};
|
23
|
+
|
24
|
+
Suricate.Application.prototype.stop = function() {
|
25
|
+
this.stopUpdates();
|
26
|
+
};
|
27
|
+
|
28
|
+
|
29
|
+
// Creates and return an object
|
30
|
+
// to communicate with the API
|
31
|
+
Suricate.Application.prototype.createAPI = function() {
|
32
|
+
var baseURL = location.protocol + "//" + location.host + "/api";
|
33
|
+
return new Suricate.API(baseURL);
|
34
|
+
};
|
35
|
+
|
36
|
+
Suricate.Application.prototype.getWidgetsContainer = function() {
|
37
|
+
return this.widgetsContainer;
|
38
|
+
};
|
39
|
+
|
40
|
+
Suricate.Application.prototype.getChartFactory = function() {
|
41
|
+
return this.chartFactory;
|
42
|
+
};
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
/*
|
47
|
+
* Private
|
48
|
+
*/
|
49
|
+
|
50
|
+
Suricate.Application.prototype.init = function(callback) {
|
51
|
+
if(!this.initialized) {
|
52
|
+
var self = this;
|
53
|
+
this.initWidgets(function() {
|
54
|
+
self.initialized = true;
|
55
|
+
callback();
|
56
|
+
});
|
57
|
+
}
|
58
|
+
else {
|
59
|
+
callback();
|
60
|
+
}
|
61
|
+
};
|
62
|
+
|
63
|
+
Suricate.Application.prototype.initWidgets = function(callback) {
|
64
|
+
var api = this.createAPI();
|
65
|
+
var self = this;
|
66
|
+
api.getWidgets(function(configurations) {
|
67
|
+
var factory = new Suricate.WidgetFactory(self, configurations);
|
68
|
+
self.widgets = factory.buildWidgets();
|
69
|
+
callback();
|
70
|
+
});
|
71
|
+
};
|
72
|
+
|
73
|
+
Suricate.Application.prototype.startUpdates = function() {
|
74
|
+
if(this.updateInterval === undefined) {
|
75
|
+
var self = this;
|
76
|
+
this.update();
|
77
|
+
this.updateInterval = window.setInterval(function() {
|
78
|
+
self.update();
|
79
|
+
}, 20000);
|
80
|
+
}
|
81
|
+
};
|
82
|
+
|
83
|
+
Suricate.Application.prototype.stopUpdates = function() {
|
84
|
+
if(this.updateInterval !== undefined) {
|
85
|
+
window.clearInterval(this.updateInterval);
|
86
|
+
this.updateInterval = undefined;
|
87
|
+
}
|
88
|
+
};
|
89
|
+
|
90
|
+
Suricate.Application.prototype.update = function() {
|
91
|
+
var now = new Date();
|
92
|
+
for (var widgetIndex = 0, l = this.widgets.length; widgetIndex < l; widgetIndex ++) {
|
93
|
+
var widget = this.widgets[widgetIndex];
|
94
|
+
widget.update(now);
|
95
|
+
}
|
96
|
+
};
|
97
|
+
}());
|
@@ -0,0 +1,54 @@
|
|
1
|
+
(function() {
|
2
|
+
"use strict";
|
3
|
+
window.Suricate = window.Suricate || {};
|
4
|
+
|
5
|
+
Suricate.ChartJSChartFactory = function() {
|
6
|
+
|
7
|
+
};
|
8
|
+
|
9
|
+
Suricate.ChartJSChartFactory.prototype.createLineChart = function(container, options) {
|
10
|
+
var chart = {};
|
11
|
+
var libraryObject;
|
12
|
+
|
13
|
+
var convertData = function(data) {
|
14
|
+
var datasets = [];
|
15
|
+
for (var index = 0, seriesCount = data.series.length; index < seriesCount; index++) {
|
16
|
+
var serie = data.series[index];
|
17
|
+
datasets.push({
|
18
|
+
label: serie.name,
|
19
|
+
strokeColor: serie.color,
|
20
|
+
fillColor: "rgba(0, 0, 0, 0)",
|
21
|
+
pointStrokeColor: serie.color,
|
22
|
+
pointColor: serie.color,
|
23
|
+
data: serie.values
|
24
|
+
});
|
25
|
+
}
|
26
|
+
|
27
|
+
return {
|
28
|
+
labels: data.labels,
|
29
|
+
datasets: datasets
|
30
|
+
};
|
31
|
+
};
|
32
|
+
|
33
|
+
var init = function(data, options) {
|
34
|
+
var ctx = container.find(".chart")[0].getContext("2d");
|
35
|
+
libraryObject = new Chart(ctx).Line(data, options);
|
36
|
+
var legend = libraryObject.generateLegend();
|
37
|
+
container.find(".legend").html(legend);
|
38
|
+
};
|
39
|
+
|
40
|
+
var data = {
|
41
|
+
labels: [],
|
42
|
+
datasets: [ { } ]
|
43
|
+
};
|
44
|
+
init(data, options);
|
45
|
+
|
46
|
+
chart.update = function(data) {
|
47
|
+
var chartJSData = convertData(data);
|
48
|
+
libraryObject.destroy();
|
49
|
+
init(chartJSData, options);
|
50
|
+
};
|
51
|
+
|
52
|
+
return chart;
|
53
|
+
};
|
54
|
+
}());
|