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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 828f33e21107bef719e0cfe79663172057ae4191
4
- data.tar.gz: f073980c5916b567d366e502f6830c8c4d233672
3
+ metadata.gz: af52b5e18fb92d49ca360ef3a68e4a5b65825e3c
4
+ data.tar.gz: 1c70ba5f08bdadef985eee62dda15a4ff435aa10
5
5
  SHA512:
6
- metadata.gz: 98f40a0354d69a0d3d8f210da9f6835923aa4e6daf75986843e8b1700e84cec8b403395d84e5a6e11666f80a8a0f2a0dbf84e7c0148f2587c4456ba8b6a1e70a
7
- data.tar.gz: 62e5d74ced143d6bb1d7d6f9b9739736ad6eea7463a41b83a9cd434e0729fb7d10fc75b95dadde58eea48dbc6627d753284d9548b2429bf8ae2dc14cbec33bf9
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 #{THREAD_NUM_MAX}") do |v|
86
- unless (1..THREAD_NUM_MAX).include? v.to_i
87
- puts "Threads number should be in range 1..#{THREAD_NUM_MAX}"
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
@@ -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.load
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.instance.send("#{k}=", v) if self.instance.respond_to?(k)
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 self.read_config
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 self.values
68
+ def values
43
69
  data = {}
44
70
  OPTIONS.each do |k|
45
- data[k] = self.instance.send(k)
71
+ data[k] = self.send(k)
46
72
  end
47
73
  data
48
74
  end
49
75
 
50
- def self.save
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
- OPTIONS.each do |key|
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
- OPTIONS.each do |key|
77
- attr_accessor key
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
@@ -1,3 +1,3 @@
1
1
  module Britebox
2
- VERSION = '0.0.5'
2
+ VERSION = '0.0.6'
3
3
  end
@@ -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
@@ -1 +1,2 @@
1
1
  app = angular.module('BriteBox', ['ngResource'])
2
+ delay = (ms, func) -> setTimeout func, ms
@@ -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({method: 'GET', url: '/settings'}).success (data, status, headers, config) ->
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
- $injector.get('$rootScope').$broadcast('showMessage', 'danger', $scope.texts.get_error)
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.save = () ->
24
- # ...
51
+ $scope.modalRevert = () ->
52
+ $scope.reload()
53
+ $('#settingsModal button.close').click()
25
54
 
26
- $scope.reload()
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
+ ]
@@ -0,0 +1,4 @@
1
+ app.filter 'capitalize', ->
2
+ (string) ->
3
+ string ?= ''
4
+ string.toLowerCase().replace /\b[a-z]/g, (letter) -> letter.toUpperCase()
@@ -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-api_key">API Key</label>
13
- <input type="text" class="form-control" id="settings-api_key" placeholder="" value="{{settings.api_key}}">
14
- <!--<p class="help-block">Example block-level help text here.</p>-->
15
- </div>
16
-
17
- <div class="form-group">
18
- <label for="settings-watch_dir">Watch directory</label>
19
- <input type="text" class="form-control" id="settings-watch_dir" placeholder="" value="{{settings.watch_dir}}">
20
- <!--<p class="help-block">Example block-level help text here.</p>-->
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">Close</a>
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 -->
@@ -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="SettingsCtrl">
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 table-hover">
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: 70px;">Actions</div>
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>{{file_job.status}}</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.5
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-05 00:00:00.000000000 Z
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