britebox 0.0.5 → 0.0.6
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.
- checksums.yaml +4 -4
- data/bin/britebox +3 -4
- data/lib/britebox/config.rb +58 -22
- data/lib/britebox/version.rb +1 -1
- data/lib/britebox/web_ui.rb +15 -1
- data/resources/assets/javascripts/britebox.coffee +1 -0
- data/resources/assets/javascripts/compiled-coffee.js +263 -0
- data/resources/assets/javascripts/controllers/file_jobs_ctrl.coffee +8 -0
- data/resources/assets/javascripts/controllers/settings_ctrl.coffee +54 -8
- data/resources/assets/javascripts/controllers/settings_observer_ctrl.coffee +13 -0
- data/resources/assets/javascripts/filters/capitalize.coffee +4 -0
- data/resources/public/javascripts/application.js +136 -7
- data/resources/views/_settings_modal.erb +12 -29
- data/resources/views/index.erb +9 -4
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: af52b5e18fb92d49ca360ef3a68e4a5b65825e3c
|
4
|
+
data.tar.gz: 1c70ba5f08bdadef985eee62dda15a4ff435aa10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 83d66b045e53371ab19a2f777cd8336474b41f6fbf6bc146c8af0c7e47b07541bf0fb49a02ef80cd3ead29dba115872f64e0b0107a6a6b03e3f3978293f3b7a1
|
7
|
+
data.tar.gz: 831f8252a4e93a07a4384eacfc88e722feba7b79ce25489ff2fb14b201ba7d010682ff5ab806506d11ef864bd2760f8f9fd686506c82ff029b690d7ebf07bbf1
|
data/bin/britebox
CHANGED
@@ -5,7 +5,6 @@ require 'britebox'
|
|
5
5
|
require 'britebox/cli'
|
6
6
|
|
7
7
|
Thread.abort_on_exception = true
|
8
|
-
THREAD_NUM_MAX = 10
|
9
8
|
|
10
9
|
# Register action on Ctrl-C
|
11
10
|
[:SIGINT, :TERM, :INT].each do |sig|
|
@@ -82,9 +81,9 @@ if command == 'watch'
|
|
82
81
|
|
83
82
|
opts.on('-t', '--threads THREADS', "Maximum number of parallel threads used for processing, "+
|
84
83
|
"Default is #{Britebox::Config::DEFAULT_OPTIONS[:threads]}, " +
|
85
|
-
"Maximum is #{
|
86
|
-
unless (1..
|
87
|
-
puts "Threads number should be in range 1..#{
|
84
|
+
"Maximum is #{Britebox::Config::MAX_THREADS}") do |v|
|
85
|
+
unless (1..Britebox::Config::MAX_THREADS).include? v.to_i
|
86
|
+
puts "Threads number should be in range 1..#{Britebox::Config::MAX_THREADS}"
|
88
87
|
exit 1
|
89
88
|
end
|
90
89
|
Britebox::Config.threads = v.to_i
|
data/lib/britebox/config.rb
CHANGED
@@ -1,11 +1,21 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'singleton'
|
3
|
+
require 'active_model'
|
3
4
|
|
4
5
|
module Britebox
|
5
6
|
class Config
|
6
7
|
include Singleton
|
8
|
+
include ActiveModel::Validations
|
7
9
|
|
8
10
|
OPTIONS = [:threads, :api_key, :watch_dir, :out_dir, :port, :ui_enabled, :simulate]
|
11
|
+
OPTIONS.each { |o| attr_accessor(o) }
|
12
|
+
|
13
|
+
MAX_THREADS = 10
|
14
|
+
|
15
|
+
validates_presence_of :threads, :api_key, :watch_dir, :out_dir, :port
|
16
|
+
validates_inclusion_of :port, in: 1..65535, message: ' should be in range 1..65535'
|
17
|
+
validates_inclusion_of :threads, in: 1..MAX_THREADS, message: "should be in range 1..#{MAX_THREADS}"
|
18
|
+
validate :watch_dir_present
|
9
19
|
|
10
20
|
DEFAULT_OPTIONS = {
|
11
21
|
threads: 10,
|
@@ -19,12 +29,28 @@ module Britebox
|
|
19
29
|
init_defaults
|
20
30
|
end
|
21
31
|
|
22
|
-
def self.
|
32
|
+
def self.method_missing(method, *args)
|
33
|
+
if self.instance.respond_to? method
|
34
|
+
self.instance.send(method, *args)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.respond_to?(method)
|
41
|
+
if self.instance.respond_to? method
|
42
|
+
true
|
43
|
+
else
|
44
|
+
super
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def load
|
23
49
|
data = read_config
|
24
50
|
|
25
51
|
if data && data['config']
|
26
52
|
data['config'].each do |k, v|
|
27
|
-
self.
|
53
|
+
self.send("#{k}=", v) if self.respond_to?(k)
|
28
54
|
end
|
29
55
|
true
|
30
56
|
else
|
@@ -32,30 +58,48 @@ module Britebox
|
|
32
58
|
end
|
33
59
|
end
|
34
60
|
|
35
|
-
def
|
61
|
+
def read_config
|
36
62
|
path = File.expand_path(CONFIG_PATH)
|
37
63
|
if File.exists? path
|
38
64
|
JSON.parse File.read path rescue nil
|
39
65
|
end
|
40
66
|
end
|
41
67
|
|
42
|
-
def
|
68
|
+
def values
|
43
69
|
data = {}
|
44
70
|
OPTIONS.each do |k|
|
45
|
-
data[k] = self.
|
71
|
+
data[k] = self.send(k)
|
46
72
|
end
|
47
73
|
data
|
48
74
|
end
|
49
75
|
|
50
|
-
def
|
76
|
+
def attributes
|
77
|
+
values
|
78
|
+
end
|
79
|
+
|
80
|
+
def update(new_values)
|
81
|
+
old_values = self.attributes
|
82
|
+
|
83
|
+
new_values.each do |k, v|
|
84
|
+
self.send("#{k}=", v)
|
85
|
+
end
|
86
|
+
if self.valid?
|
87
|
+
save
|
88
|
+
else
|
89
|
+
old_values.each do |k, v|
|
90
|
+
self.send("#{k}=", v)
|
91
|
+
end
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def save
|
51
97
|
path = File.expand_path(CONFIG_PATH)
|
52
98
|
|
53
99
|
data = read_config
|
54
100
|
data ||= {}
|
55
|
-
data['config']
|
56
|
-
|
57
|
-
data['config'][key.to_s] = self.instance.send(key)
|
58
|
-
end
|
101
|
+
data['config'] = attributes
|
102
|
+
|
59
103
|
begin
|
60
104
|
File.open(path, 'w+') do |file|
|
61
105
|
file.write JSON.pretty_generate data
|
@@ -67,25 +111,17 @@ module Britebox
|
|
67
111
|
end
|
68
112
|
end
|
69
113
|
|
114
|
+
private
|
115
|
+
|
70
116
|
def init_defaults
|
71
117
|
DEFAULT_OPTIONS.each do |key, val|
|
72
118
|
send("#{key}=", val) if send(key).nil?
|
73
119
|
end
|
74
120
|
end
|
75
121
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
define_singleton_method key do
|
80
|
-
self.instance.send(key)
|
81
|
-
end
|
82
|
-
|
83
|
-
define_singleton_method "#{key}=" do |val|
|
84
|
-
self.instance.send("#{key}=", val)
|
85
|
-
end
|
122
|
+
def watch_dir_present
|
123
|
+
errors.add(:watch_dir, 'is not exist') unless File.exists? self.watch_dir.to_s
|
86
124
|
end
|
87
125
|
|
88
|
-
|
89
|
-
|
90
126
|
end
|
91
127
|
end
|
data/lib/britebox/version.rb
CHANGED
data/lib/britebox/web_ui.rb
CHANGED
@@ -8,6 +8,7 @@ module Britebox
|
|
8
8
|
set :static, true
|
9
9
|
set :run, false
|
10
10
|
set :server, %w[webrick]
|
11
|
+
set :bind, 'localhost'
|
11
12
|
|
12
13
|
configure do
|
13
14
|
disable :logging
|
@@ -26,7 +27,6 @@ module Britebox
|
|
26
27
|
end
|
27
28
|
|
28
29
|
get '/' do
|
29
|
-
#send_file File.expand_path('index.html', settings.public_folder)
|
30
30
|
erb :index
|
31
31
|
end
|
32
32
|
|
@@ -66,6 +66,20 @@ module Britebox
|
|
66
66
|
Config.values.to_json
|
67
67
|
end
|
68
68
|
|
69
|
+
post '/settings' do
|
70
|
+
content_type :json
|
71
|
+
|
72
|
+
request.body.rewind
|
73
|
+
data = JSON.parse request.body.read
|
74
|
+
|
75
|
+
if Config.update(data)
|
76
|
+
Config.values.to_json
|
77
|
+
else
|
78
|
+
status 422
|
79
|
+
{errors: Config.errors.messages}.to_json
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
69
83
|
|
70
84
|
end
|
71
85
|
end
|
@@ -0,0 +1,263 @@
|
|
1
|
+
(function() {
|
2
|
+
var app, delay;
|
3
|
+
|
4
|
+
app = angular.module('BriteBox', ['ngResource']);
|
5
|
+
|
6
|
+
delay = function(ms, func) {
|
7
|
+
return setTimeout(func, ms);
|
8
|
+
};
|
9
|
+
|
10
|
+
app.controller('FileJobsCtrl', [
|
11
|
+
'$scope', '$resource', '$injector', function($scope, $resource, $injector) {
|
12
|
+
$scope.FileJob = $resource("/file_jobs/:id", {
|
13
|
+
id: '@id'
|
14
|
+
}, {});
|
15
|
+
$scope.file_jobs = $scope.FileJob.query();
|
16
|
+
$scope.texts = {
|
17
|
+
connection_lost: '<strong>Connection lost</strong>. Process died or been killed'
|
18
|
+
};
|
19
|
+
setInterval(function() {
|
20
|
+
$scope.FileJob.query().$then(function(response) {
|
21
|
+
$injector.get('$rootScope').$broadcast('closeMessageByText', $scope.texts.connection_lost);
|
22
|
+
return $scope.file_jobs = response.data;
|
23
|
+
}, function(error) {
|
24
|
+
return $injector.get('$rootScope').$broadcast('showMessage', 'danger', $scope.texts.connection_lost);
|
25
|
+
});
|
26
|
+
return $scope.$apply();
|
27
|
+
}, 1000);
|
28
|
+
$scope.statusClass = function(fj) {
|
29
|
+
switch (fj.status) {
|
30
|
+
case 'pending':
|
31
|
+
return '';
|
32
|
+
case 'verifying':
|
33
|
+
return 'label-info';
|
34
|
+
case 'error':
|
35
|
+
return 'label-danger';
|
36
|
+
case 'complete':
|
37
|
+
return 'label-success';
|
38
|
+
default:
|
39
|
+
return '';
|
40
|
+
}
|
41
|
+
};
|
42
|
+
$scope.pauseFileJob = function(fj) {
|
43
|
+
fj.status = 'paused';
|
44
|
+
return $scope.FileJob.save(fj);
|
45
|
+
};
|
46
|
+
$scope.resumeFileJob = function(fj) {
|
47
|
+
fj.status = 'pending';
|
48
|
+
return $scope.FileJob.save(fj);
|
49
|
+
};
|
50
|
+
return $scope.cancelFileJob = function(fj) {
|
51
|
+
fj.status = 'cancelled';
|
52
|
+
return $scope.FileJob.save(fj);
|
53
|
+
};
|
54
|
+
}
|
55
|
+
]);
|
56
|
+
|
57
|
+
app.controller('NotificationCtrl', [
|
58
|
+
'$scope', function($scope) {
|
59
|
+
var _this = this;
|
60
|
+
$scope.notifications = [];
|
61
|
+
$scope.$on('showMessage', function(data, type, message) {
|
62
|
+
return $scope.showMessage(type, message);
|
63
|
+
});
|
64
|
+
$scope.$on('closeMessageByText', function(data, message) {
|
65
|
+
return $scope.closeMessageByText(message);
|
66
|
+
});
|
67
|
+
$scope.closeMessage = function(index) {
|
68
|
+
return $scope.notifications.splice(index, 1);
|
69
|
+
};
|
70
|
+
$scope.closeMessageByText = function(message) {
|
71
|
+
return $.each($scope.notifications, function(k, v) {
|
72
|
+
if (v['message'] === message) {
|
73
|
+
return $scope.notifications.splice(k, 1);
|
74
|
+
}
|
75
|
+
});
|
76
|
+
};
|
77
|
+
return $scope.showMessage = function(type, message) {
|
78
|
+
var add;
|
79
|
+
add = true;
|
80
|
+
$.each($scope.notifications, function(k, v) {
|
81
|
+
if (v['message'] === message && v['type'] === type) {
|
82
|
+
return add = false;
|
83
|
+
}
|
84
|
+
});
|
85
|
+
if (add) {
|
86
|
+
return $scope.notifications.push({
|
87
|
+
type: type,
|
88
|
+
message: message
|
89
|
+
});
|
90
|
+
}
|
91
|
+
};
|
92
|
+
}
|
93
|
+
]);
|
94
|
+
|
95
|
+
app.controller('SettingsCtrl', [
|
96
|
+
'$scope', '$http', '$injector', function($scope, $http, $injector) {
|
97
|
+
$scope.settings = {};
|
98
|
+
$scope.errors = {};
|
99
|
+
$scope.modal_fields = [
|
100
|
+
{
|
101
|
+
key: 'api_key',
|
102
|
+
title: 'API Key',
|
103
|
+
help: ''
|
104
|
+
}, {
|
105
|
+
key: 'watch_dir',
|
106
|
+
title: 'Watch directory'
|
107
|
+
}, {
|
108
|
+
key: 'out_dir',
|
109
|
+
title: 'Output directory',
|
110
|
+
help: 'Processed files will be placed here. restart'
|
111
|
+
}, {
|
112
|
+
key: 'threads',
|
113
|
+
title: 'Threads',
|
114
|
+
help: 'Number of parallel threads used for processing, minimum is 1, maximum is 10'
|
115
|
+
}, {
|
116
|
+
key: 'port',
|
117
|
+
title: 'Port',
|
118
|
+
help: 'Launch web server on this port'
|
119
|
+
}
|
120
|
+
];
|
121
|
+
$scope.texts = {
|
122
|
+
get_error: 'Unable to load settings. Please reload page',
|
123
|
+
simulation_mode: 'Running in <b>Simulation mode</b>. No real requests to API will be made.',
|
124
|
+
saved: 'Settings succesfully saved'
|
125
|
+
};
|
126
|
+
$scope.reload = function() {
|
127
|
+
return $http.get('/settings').success(function(data, status, headers, config) {
|
128
|
+
$scope.settings = data;
|
129
|
+
$scope.errors = {};
|
130
|
+
$injector.get('$rootScope').$broadcast('settingsUpdated');
|
131
|
+
if ($scope.settings['simulate']) {
|
132
|
+
return $injector.get('$rootScope').$broadcast('showMessage', 'info', $scope.texts.simulation_mode);
|
133
|
+
}
|
134
|
+
}).error(function(data, status, headers, config) {
|
135
|
+
return $injector.get('$rootScope').$broadcast('showMessage', 'danger', $scope.texts.get_error);
|
136
|
+
});
|
137
|
+
};
|
138
|
+
$scope.save = function(cb_ok, cb_error) {
|
139
|
+
var sett;
|
140
|
+
if (cb_ok == null) {
|
141
|
+
cb_ok = null;
|
142
|
+
}
|
143
|
+
if (cb_error == null) {
|
144
|
+
cb_error = null;
|
145
|
+
}
|
146
|
+
sett = {};
|
147
|
+
$('.settings-field input.form-control').each(function(k, elem) {
|
148
|
+
var value;
|
149
|
+
value = $(elem).val();
|
150
|
+
if ($scope.isNumber(value)) {
|
151
|
+
value = parseFloat(value);
|
152
|
+
}
|
153
|
+
return sett[$(elem).attr('name')] = value;
|
154
|
+
});
|
155
|
+
return $http.post('/settings', sett).success(function(data, status, headers, config) {
|
156
|
+
$scope.errors = {};
|
157
|
+
if (cb_ok) {
|
158
|
+
cb_ok();
|
159
|
+
}
|
160
|
+
return $injector.get('$rootScope').$broadcast('settingsUpdated');
|
161
|
+
}).error(function(data, status, headers, config) {
|
162
|
+
console.log("error", data, status, headers, config);
|
163
|
+
$scope.errors = {};
|
164
|
+
$.each(data.errors, function(k, v) {
|
165
|
+
return $scope.errors[k] = v.join(", ");
|
166
|
+
});
|
167
|
+
console.log("errors", $scope.errors);
|
168
|
+
if (cb_error) {
|
169
|
+
return cb_error();
|
170
|
+
}
|
171
|
+
});
|
172
|
+
};
|
173
|
+
$scope.modalRevert = function() {
|
174
|
+
$scope.reload();
|
175
|
+
return $('#settingsModal button.close').click();
|
176
|
+
};
|
177
|
+
$scope.modalSave = function() {
|
178
|
+
return $scope.save(function() {
|
179
|
+
$scope.reload();
|
180
|
+
$('#settingsModal button.close').click();
|
181
|
+
return $injector.get('$rootScope').$broadcast('showMessage', 'success', $scope.texts.saved);
|
182
|
+
});
|
183
|
+
};
|
184
|
+
$scope.isValidField = function(field) {
|
185
|
+
return !$scope.errors[field];
|
186
|
+
};
|
187
|
+
$scope.formGroupClass = function(field) {
|
188
|
+
if ($scope.isValidField(field)) {
|
189
|
+
return '';
|
190
|
+
} else {
|
191
|
+
return 'has-error';
|
192
|
+
}
|
193
|
+
};
|
194
|
+
$scope.isNumber = function(n) {
|
195
|
+
return !isNaN(parseFloat(n)) && isFinite(n);
|
196
|
+
};
|
197
|
+
return $scope.reload();
|
198
|
+
}
|
199
|
+
]);
|
200
|
+
|
201
|
+
app.controller('SettingsObserverCtrl', [
|
202
|
+
'$scope', '$http', '$injector', function($scope, $http, $injector) {
|
203
|
+
var _this = this;
|
204
|
+
$scope.settings = {};
|
205
|
+
$scope.$on('settingsUpdated', function(data) {
|
206
|
+
return $scope.reload();
|
207
|
+
});
|
208
|
+
return $scope.reload = function() {
|
209
|
+
var elem;
|
210
|
+
elem = $('[ng-controller="SettingsCtrl"]')[0];
|
211
|
+
return delay(100, function() {
|
212
|
+
var sett;
|
213
|
+
sett = angular.element(elem).scope().settings;
|
214
|
+
return $scope.settings = JSON.parse(JSON.stringify(sett));
|
215
|
+
});
|
216
|
+
};
|
217
|
+
}
|
218
|
+
]);
|
219
|
+
|
220
|
+
app.filter('bytes', function() {
|
221
|
+
return function(bytes, precision) {
|
222
|
+
var number, units;
|
223
|
+
if (precision == null) {
|
224
|
+
precision = 1;
|
225
|
+
}
|
226
|
+
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) {
|
227
|
+
return '-';
|
228
|
+
}
|
229
|
+
if (bytes === 0) {
|
230
|
+
return '0 bytes';
|
231
|
+
}
|
232
|
+
units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'];
|
233
|
+
number = Math.floor(Math.log(bytes) / Math.log(1024));
|
234
|
+
return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number];
|
235
|
+
};
|
236
|
+
});
|
237
|
+
|
238
|
+
app.filter('capitalize', function() {
|
239
|
+
return function(string) {
|
240
|
+
if (string == null) {
|
241
|
+
string = '';
|
242
|
+
}
|
243
|
+
return string.toLowerCase().replace(/\b[a-z]/g, function(letter) {
|
244
|
+
return letter.toUpperCase();
|
245
|
+
});
|
246
|
+
};
|
247
|
+
});
|
248
|
+
|
249
|
+
app.filter('duration', function() {
|
250
|
+
return function(duration, options) {
|
251
|
+
var hr, min, sec;
|
252
|
+
if (isNaN(parseFloat(duration)) || !isFinite(duration)) {
|
253
|
+
return '-';
|
254
|
+
}
|
255
|
+
duration = parseInt(duration);
|
256
|
+
sec = duration % 60;
|
257
|
+
min = (duration - sec) / 60 % 60;
|
258
|
+
hr = (duration - sec - 60 * min) / 3600;
|
259
|
+
return "" + ("0" + hr).slice(-2) + ":" + ("0" + min).slice(-2) + ":" + ("0" + sec).slice(-2);
|
260
|
+
};
|
261
|
+
});
|
262
|
+
|
263
|
+
}).call(this);
|
@@ -18,6 +18,14 @@ app.controller 'FileJobsCtrl', ['$scope', '$resource', '$injector', ($scope, $re
|
|
18
18
|
$scope.$apply()
|
19
19
|
, 1000)
|
20
20
|
|
21
|
+
$scope.statusClass = (fj) ->
|
22
|
+
switch fj.status
|
23
|
+
when 'pending' then ''
|
24
|
+
when 'verifying' then 'label-info'
|
25
|
+
when 'error' then 'label-danger'
|
26
|
+
when 'complete' then 'label-success'
|
27
|
+
else ''
|
28
|
+
|
21
29
|
$scope.pauseFileJob = (fj) ->
|
22
30
|
fj.status = 'paused';
|
23
31
|
$scope.FileJob.save(fj)
|
@@ -1,28 +1,74 @@
|
|
1
1
|
app.controller 'SettingsCtrl', ['$scope', '$http', '$injector', ($scope, $http, $injector) ->
|
2
2
|
|
3
|
-
#$scope.Settings = $resource("/settings/:id", {id: '@id'}, {})
|
4
|
-
|
5
3
|
$scope.settings = {}
|
4
|
+
$scope.errors = {}
|
5
|
+
$scope.modal_fields = [
|
6
|
+
{key: 'api_key', title: 'API Key', help: ''}
|
7
|
+
{key: 'watch_dir',title: 'Watch directory'}
|
8
|
+
{key: 'out_dir', title: 'Output directory', help: 'Processed files will be placed here. restart'}
|
9
|
+
{key: 'threads', title: 'Threads', help: 'Number of parallel threads used for processing, minimum is 1, maximum is 10'}
|
10
|
+
{key: 'port', title: 'Port', help: 'Launch web server on this port'}
|
11
|
+
]
|
12
|
+
|
6
13
|
$scope.texts = {
|
7
14
|
get_error: 'Unable to load settings. Please reload page'
|
8
15
|
simulation_mode: 'Running in <b>Simulation mode</b>. No real requests to API will be made.'
|
16
|
+
saved: 'Settings succesfully saved'
|
9
17
|
}
|
10
18
|
|
11
19
|
$scope.reload = () ->
|
12
|
-
$http(
|
20
|
+
$http.get('/settings').success (data, status, headers, config) ->
|
13
21
|
$scope.settings = data
|
14
|
-
|
22
|
+
$scope.errors = {}
|
23
|
+
$injector.get('$rootScope').$broadcast('settingsUpdated')
|
15
24
|
if $scope.settings['simulate']
|
16
25
|
$injector.get('$rootScope').$broadcast('showMessage', 'info', $scope.texts.simulation_mode)
|
26
|
+
.error (data, status, headers, config) ->
|
27
|
+
$injector.get('$rootScope').$broadcast('showMessage', 'danger', $scope.texts.get_error)
|
28
|
+
|
29
|
+
|
30
|
+
$scope.save = (cb_ok = null, cb_error = null) ->
|
31
|
+
sett = {}
|
32
|
+
$('.settings-field input.form-control').each (k, elem) ->
|
33
|
+
value = $(elem).val()
|
34
|
+
value = parseFloat(value) if $scope.isNumber(value)
|
35
|
+
sett[$(elem).attr('name')] = value
|
17
36
|
|
37
|
+
$http.post('/settings', sett).success (data, status, headers, config) ->
|
38
|
+
$scope.errors = {}
|
39
|
+
cb_ok() if cb_ok
|
40
|
+
$injector.get('$rootScope').$broadcast('settingsUpdated')
|
18
41
|
|
19
42
|
.error (data, status, headers, config) ->
|
20
|
-
|
43
|
+
console.log "error", data, status, headers, config
|
44
|
+
$scope.errors = {}
|
45
|
+
$.each data.errors, (k, v) ->
|
46
|
+
$scope.errors[k] = v.join(", ")
|
47
|
+
console.log "errors", $scope.errors
|
48
|
+
cb_error() if cb_error
|
21
49
|
|
22
50
|
|
23
|
-
$scope.
|
24
|
-
|
51
|
+
$scope.modalRevert = () ->
|
52
|
+
$scope.reload()
|
53
|
+
$('#settingsModal button.close').click()
|
25
54
|
|
26
|
-
$scope.
|
55
|
+
$scope.modalSave = () ->
|
56
|
+
$scope.save ->
|
57
|
+
$scope.reload()
|
58
|
+
$('#settingsModal button.close').click()
|
59
|
+
$injector.get('$rootScope').$broadcast('showMessage', 'success', $scope.texts.saved)
|
60
|
+
|
61
|
+
$scope.isValidField = (field) ->
|
62
|
+
!$scope.errors[field]
|
27
63
|
|
64
|
+
$scope.formGroupClass = (field) ->
|
65
|
+
if $scope.isValidField(field)
|
66
|
+
''
|
67
|
+
else
|
68
|
+
'has-error'
|
69
|
+
|
70
|
+
$scope.isNumber = (n) ->
|
71
|
+
!isNaN(parseFloat(n)) && isFinite(n)
|
72
|
+
|
73
|
+
$scope.reload()
|
28
74
|
]
|
@@ -0,0 +1,13 @@
|
|
1
|
+
app.controller 'SettingsObserverCtrl', ['$scope', '$http', '$injector', ($scope, $http, $injector) ->
|
2
|
+
|
3
|
+
$scope.settings = {}
|
4
|
+
|
5
|
+
$scope.$on 'settingsUpdated', (data) => $scope.reload()
|
6
|
+
|
7
|
+
$scope.reload = () ->
|
8
|
+
elem = $('[ng-controller="SettingsCtrl"]')[0]
|
9
|
+
delay 100, ->
|
10
|
+
sett = angular.element(elem).scope().settings
|
11
|
+
$scope.settings = JSON.parse JSON.stringify(sett)
|
12
|
+
|
13
|
+
]
|
@@ -1,8 +1,12 @@
|
|
1
1
|
(function() {
|
2
|
-
var app;
|
2
|
+
var app, delay;
|
3
3
|
|
4
4
|
app = angular.module('BriteBox', ['ngResource']);
|
5
5
|
|
6
|
+
delay = function(ms, func) {
|
7
|
+
return setTimeout(func, ms);
|
8
|
+
};
|
9
|
+
|
6
10
|
app.controller('FileJobsCtrl', [
|
7
11
|
'$scope', '$resource', '$injector', function($scope, $resource, $injector) {
|
8
12
|
$scope.FileJob = $resource("/file_jobs/:id", {
|
@@ -21,6 +25,20 @@
|
|
21
25
|
});
|
22
26
|
return $scope.$apply();
|
23
27
|
}, 1000);
|
28
|
+
$scope.statusClass = function(fj) {
|
29
|
+
switch (fj.status) {
|
30
|
+
case 'pending':
|
31
|
+
return '';
|
32
|
+
case 'verifying':
|
33
|
+
return 'label-info';
|
34
|
+
case 'error':
|
35
|
+
return 'label-danger';
|
36
|
+
case 'complete':
|
37
|
+
return 'label-success';
|
38
|
+
default:
|
39
|
+
return '';
|
40
|
+
}
|
41
|
+
};
|
24
42
|
$scope.pauseFileJob = function(fj) {
|
25
43
|
fj.status = 'paused';
|
26
44
|
return $scope.FileJob.save(fj);
|
@@ -77,16 +95,39 @@
|
|
77
95
|
app.controller('SettingsCtrl', [
|
78
96
|
'$scope', '$http', '$injector', function($scope, $http, $injector) {
|
79
97
|
$scope.settings = {};
|
98
|
+
$scope.errors = {};
|
99
|
+
$scope.modal_fields = [
|
100
|
+
{
|
101
|
+
key: 'api_key',
|
102
|
+
title: 'API Key',
|
103
|
+
help: ''
|
104
|
+
}, {
|
105
|
+
key: 'watch_dir',
|
106
|
+
title: 'Watch directory'
|
107
|
+
}, {
|
108
|
+
key: 'out_dir',
|
109
|
+
title: 'Output directory',
|
110
|
+
help: 'Processed files will be placed here. restart'
|
111
|
+
}, {
|
112
|
+
key: 'threads',
|
113
|
+
title: 'Threads',
|
114
|
+
help: 'Number of parallel threads used for processing, minimum is 1, maximum is 10'
|
115
|
+
}, {
|
116
|
+
key: 'port',
|
117
|
+
title: 'Port',
|
118
|
+
help: 'Launch web server on this port'
|
119
|
+
}
|
120
|
+
];
|
80
121
|
$scope.texts = {
|
81
122
|
get_error: 'Unable to load settings. Please reload page',
|
82
|
-
simulation_mode: 'Running in <b>Simulation mode</b>. No real requests to API will be made.'
|
123
|
+
simulation_mode: 'Running in <b>Simulation mode</b>. No real requests to API will be made.',
|
124
|
+
saved: 'Settings succesfully saved'
|
83
125
|
};
|
84
126
|
$scope.reload = function() {
|
85
|
-
return $http({
|
86
|
-
method: 'GET',
|
87
|
-
url: '/settings'
|
88
|
-
}).success(function(data, status, headers, config) {
|
127
|
+
return $http.get('/settings').success(function(data, status, headers, config) {
|
89
128
|
$scope.settings = data;
|
129
|
+
$scope.errors = {};
|
130
|
+
$injector.get('$rootScope').$broadcast('settingsUpdated');
|
90
131
|
if ($scope.settings['simulate']) {
|
91
132
|
return $injector.get('$rootScope').$broadcast('showMessage', 'info', $scope.texts.simulation_mode);
|
92
133
|
}
|
@@ -94,11 +135,88 @@
|
|
94
135
|
return $injector.get('$rootScope').$broadcast('showMessage', 'danger', $scope.texts.get_error);
|
95
136
|
});
|
96
137
|
};
|
97
|
-
$scope.save = function() {
|
138
|
+
$scope.save = function(cb_ok, cb_error) {
|
139
|
+
var sett;
|
140
|
+
if (cb_ok == null) {
|
141
|
+
cb_ok = null;
|
142
|
+
}
|
143
|
+
if (cb_error == null) {
|
144
|
+
cb_error = null;
|
145
|
+
}
|
146
|
+
sett = {};
|
147
|
+
$('.settings-field input.form-control').each(function(k, elem) {
|
148
|
+
var value;
|
149
|
+
value = $(elem).val();
|
150
|
+
if ($scope.isNumber(value)) {
|
151
|
+
value = parseFloat(value);
|
152
|
+
}
|
153
|
+
return sett[$(elem).attr('name')] = value;
|
154
|
+
});
|
155
|
+
return $http.post('/settings', sett).success(function(data, status, headers, config) {
|
156
|
+
$scope.errors = {};
|
157
|
+
if (cb_ok) {
|
158
|
+
cb_ok();
|
159
|
+
}
|
160
|
+
return $injector.get('$rootScope').$broadcast('settingsUpdated');
|
161
|
+
}).error(function(data, status, headers, config) {
|
162
|
+
console.log("error", data, status, headers, config);
|
163
|
+
$scope.errors = {};
|
164
|
+
$.each(data.errors, function(k, v) {
|
165
|
+
return $scope.errors[k] = v.join(", ");
|
166
|
+
});
|
167
|
+
console.log("errors", $scope.errors);
|
168
|
+
if (cb_error) {
|
169
|
+
return cb_error();
|
170
|
+
}
|
171
|
+
});
|
172
|
+
};
|
173
|
+
$scope.modalRevert = function() {
|
174
|
+
$scope.reload();
|
175
|
+
return $('#settingsModal button.close').click();
|
176
|
+
};
|
177
|
+
$scope.modalSave = function() {
|
178
|
+
return $scope.save(function() {
|
179
|
+
$scope.reload();
|
180
|
+
$('#settingsModal button.close').click();
|
181
|
+
return $injector.get('$rootScope').$broadcast('showMessage', 'success', $scope.texts.saved);
|
182
|
+
});
|
183
|
+
};
|
184
|
+
$scope.isValidField = function(field) {
|
185
|
+
return !$scope.errors[field];
|
186
|
+
};
|
187
|
+
$scope.formGroupClass = function(field) {
|
188
|
+
if ($scope.isValidField(field)) {
|
189
|
+
return '';
|
190
|
+
} else {
|
191
|
+
return 'has-error';
|
192
|
+
}
|
193
|
+
};
|
194
|
+
$scope.isNumber = function(n) {
|
195
|
+
return !isNaN(parseFloat(n)) && isFinite(n);
|
196
|
+
};
|
98
197
|
return $scope.reload();
|
99
198
|
}
|
100
199
|
]);
|
101
200
|
|
201
|
+
app.controller('SettingsObserverCtrl', [
|
202
|
+
'$scope', '$http', '$injector', function($scope, $http, $injector) {
|
203
|
+
var _this = this;
|
204
|
+
$scope.settings = {};
|
205
|
+
$scope.$on('settingsUpdated', function(data) {
|
206
|
+
return $scope.reload();
|
207
|
+
});
|
208
|
+
return $scope.reload = function() {
|
209
|
+
var elem;
|
210
|
+
elem = $('[ng-controller="SettingsCtrl"]')[0];
|
211
|
+
return delay(100, function() {
|
212
|
+
var sett;
|
213
|
+
sett = angular.element(elem).scope().settings;
|
214
|
+
return $scope.settings = JSON.parse(JSON.stringify(sett));
|
215
|
+
});
|
216
|
+
};
|
217
|
+
}
|
218
|
+
]);
|
219
|
+
|
102
220
|
app.filter('bytes', function() {
|
103
221
|
return function(bytes, precision) {
|
104
222
|
var number, units;
|
@@ -117,6 +235,17 @@
|
|
117
235
|
};
|
118
236
|
});
|
119
237
|
|
238
|
+
app.filter('capitalize', function() {
|
239
|
+
return function(string) {
|
240
|
+
if (string == null) {
|
241
|
+
string = '';
|
242
|
+
}
|
243
|
+
return string.toLowerCase().replace(/\b[a-z]/g, function(letter) {
|
244
|
+
return letter.toUpperCase();
|
245
|
+
});
|
246
|
+
};
|
247
|
+
});
|
248
|
+
|
120
249
|
app.filter('duration', function() {
|
121
250
|
return function(duration, options) {
|
122
251
|
var hr, min, sec;
|
@@ -8,39 +8,22 @@
|
|
8
8
|
<div class="modal-body">
|
9
9
|
<form>
|
10
10
|
|
11
|
-
<div class="form-group">
|
12
|
-
<label for="settings-
|
13
|
-
<input type="text"
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
<
|
20
|
-
|
21
|
-
</div>
|
22
|
-
|
23
|
-
<div class="form-group">
|
24
|
-
<label for="settings-out_dir">Output directory</label>
|
25
|
-
<input type="text" class="form-control" id="settings-out_dir" placeholder="" value="{{settings.out_dir}}">
|
26
|
-
<p class="help-block">Processed files will be placed here.</p>
|
27
|
-
</div>
|
28
|
-
|
29
|
-
<div class="form-group">
|
30
|
-
<label for="settings-threads">Threads</label>
|
31
|
-
<input type="text" class="form-control" id="settings-threads" placeholder="" value="{{settings.threads}}">
|
32
|
-
<p class="help-block">Number of parallel threads used for processing, minimum is 1, maximum is 10</p>
|
33
|
-
</div>
|
34
|
-
|
35
|
-
<div class="form-group">
|
36
|
-
<label for="settings-port">Launch web server on port</label>
|
37
|
-
<input type="text" class="form-control" id="settings-port" placeholder="" value="{{settings.port}}">
|
11
|
+
<div ng-repeat="opts in modal_fields" class="form-group settings-field {{formGroupClass(opts.key)}}">
|
12
|
+
<label for="settings-{{opts.key}}">{{opts.title}}:</label>
|
13
|
+
<input type="text"
|
14
|
+
class="form-control"
|
15
|
+
id="settings-{{opts.key}}"
|
16
|
+
placeholder=""
|
17
|
+
name="{{opts.key}}"
|
18
|
+
ng-model="settings[opts.key]">
|
19
|
+
<p class="help-block" ng-if="opts.help && isValidField(opts.key)">{{opts.help}}</p>
|
20
|
+
<p class="help-block" ng-if="!isValidField(opts.key)">{{opts.title}} {{errors[opts.key]}}</p>
|
38
21
|
</div>
|
39
22
|
</form>
|
40
23
|
</div>
|
41
24
|
<div class="modal-footer">
|
42
|
-
<a href="#" class="btn">
|
43
|
-
<a href="#" class="btn btn-primary">Save changes</a>
|
25
|
+
<a href="#" class="btn close-button" ng-click="modalRevert()">Revert changes</a>
|
26
|
+
<a href="#" class="btn btn-primary" ng-click="modalSave()">Save changes</a>
|
44
27
|
</div>
|
45
28
|
</div><!-- /.modal-content -->
|
46
29
|
</div><!-- /.modal-dialog -->
|
data/resources/views/index.erb
CHANGED
@@ -17,6 +17,9 @@
|
|
17
17
|
.notifications {
|
18
18
|
padding: 10px 50px 10px;
|
19
19
|
}
|
20
|
+
.btn.close-button:hover, .btn.close-button:focus {
|
21
|
+
color: #000;
|
22
|
+
}
|
20
23
|
</style>
|
21
24
|
</head>
|
22
25
|
<body>
|
@@ -40,7 +43,7 @@
|
|
40
43
|
|
41
44
|
<%= erb :_settings_modal %>
|
42
45
|
|
43
|
-
<div ng-controller="
|
46
|
+
<div ng-controller="SettingsObserverCtrl">
|
44
47
|
<dl class="dl-horizontal text-muted">
|
45
48
|
<dt>Watching directory</dt>
|
46
49
|
<dd>{{settings.watch_dir}}</dd>
|
@@ -50,7 +53,7 @@
|
|
50
53
|
</div>
|
51
54
|
|
52
55
|
<div ng-controller="FileJobsCtrl">
|
53
|
-
<table class="table
|
56
|
+
<table class="table">
|
54
57
|
<thead>
|
55
58
|
<tr>
|
56
59
|
<th style="width: 50px;">#</th>
|
@@ -60,7 +63,7 @@
|
|
60
63
|
<th style="width: 100px;">Processed</th>
|
61
64
|
<th>
|
62
65
|
Progress
|
63
|
-
<div style="float: right;margin-right:
|
66
|
+
<div style="float: right;margin-right: 80px;">Actions</div>
|
64
67
|
</th>
|
65
68
|
</tr>
|
66
69
|
</thead>
|
@@ -69,7 +72,9 @@
|
|
69
72
|
<td>{{key + 1}}</td>
|
70
73
|
<td>{{file_job.file_name}}</td>
|
71
74
|
<td>{{file_job.size_total|bytes}}</td>
|
72
|
-
<td>
|
75
|
+
<td>
|
76
|
+
<span class="label {{statusClass(file_job)}}">{{file_job.status|capitalize}}</span>
|
77
|
+
</td>
|
73
78
|
<td>{{file_job.percent_complete|number:1}} %</td>
|
74
79
|
<td>
|
75
80
|
<span ng-if="file_job.duration">
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: britebox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Shapiotko
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-08-
|
12
|
+
date: 2013-08-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: brite-api
|
@@ -53,6 +53,20 @@ dependencies:
|
|
53
53
|
- - ~>
|
54
54
|
- !ruby/object:Gem::Version
|
55
55
|
version: 1.4.3
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: activemodel
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 4.0.0
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 4.0.0
|
56
70
|
description: CLI tool for bath csv file processing via BriteVerify API
|
57
71
|
email:
|
58
72
|
- support@briteverify.com
|
@@ -74,10 +88,13 @@ files:
|
|
74
88
|
- lib/britebox.rb
|
75
89
|
- resources/assets/images/screenshot.png
|
76
90
|
- resources/assets/javascripts/britebox.coffee
|
91
|
+
- resources/assets/javascripts/compiled-coffee.js
|
77
92
|
- resources/assets/javascripts/controllers/file_jobs_ctrl.coffee
|
78
93
|
- resources/assets/javascripts/controllers/notification_ctrl.coffee
|
79
94
|
- resources/assets/javascripts/controllers/settings_ctrl.coffee
|
95
|
+
- resources/assets/javascripts/controllers/settings_observer_ctrl.coffee
|
80
96
|
- resources/assets/javascripts/filters/bytes.coffee
|
97
|
+
- resources/assets/javascripts/filters/capitalize.coffee
|
81
98
|
- resources/assets/javascripts/filters/duration.coffee
|
82
99
|
- resources/public/javascripts/angular-resource.min.js
|
83
100
|
- resources/public/javascripts/angular.min.js
|