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 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