britebox 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|