nodectl 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +4 -0
  6. data/README.md +3 -0
  7. data/Rakefile +1 -0
  8. data/TODO.md +7 -0
  9. data/assets/javascript/application.coffee +22 -0
  10. data/assets/javascript/bootstrap.js +2006 -0
  11. data/assets/javascript/controllers/application_controller.coffee +73 -0
  12. data/assets/javascript/controllers/service_controller.coffee +62 -0
  13. data/assets/javascript/event_handler.coffee +51 -0
  14. data/assets/javascript/models/action.coffee +28 -0
  15. data/assets/javascript/models/instance.coffee +7 -0
  16. data/assets/javascript/models/service.coffee +75 -0
  17. data/assets/javascript/models/version.coffee +3 -0
  18. data/assets/javascript/notifier.coffee +64 -0
  19. data/assets/javascript/router.coffee +3 -0
  20. data/assets/javascript/routes/application.coffee +6 -0
  21. data/assets/javascript/routes/log.coffee +4 -0
  22. data/assets/javascript/routes/service.coffee +3 -0
  23. data/assets/javascript/store.coffee +32 -0
  24. data/assets/javascript/templates/application.hbs +82 -0
  25. data/assets/javascript/templates/index.hbs +1 -0
  26. data/assets/javascript/templates/log.hbs +1 -0
  27. data/assets/javascript/templates/service.hbs +110 -0
  28. data/assets/javascript/templates/views/action.hbs +20 -0
  29. data/assets/javascript/templates/views/actions_list.hbs +26 -0
  30. data/assets/javascript/templates/views/environments_list.hbs +9 -0
  31. data/assets/javascript/templates/views/install_button.hbs +1 -0
  32. data/assets/javascript/templates/views/mass_run.hbs +30 -0
  33. data/assets/javascript/templates/views/notifier_control.hbs +7 -0
  34. data/assets/javascript/templates/views/param.hbs +6 -0
  35. data/assets/javascript/templates/views/terminal.hbs +8 -0
  36. data/assets/javascript/ui.coffee +24 -0
  37. data/assets/javascript/vendor/ansiparse.js +186 -0
  38. data/assets/javascript/vendor/audio.min.js +24 -0
  39. data/assets/javascript/vendor/audiojs.swf +0 -0
  40. data/assets/javascript/vendor/ember-data.js +10528 -0
  41. data/assets/javascript/vendor/ember.js +40583 -0
  42. data/assets/javascript/vendor/handlebars.js +2746 -0
  43. data/assets/javascript/vendor/jquery.js +8829 -0
  44. data/assets/javascript/vendor/ladda.js +157 -0
  45. data/assets/javascript/vendor/moment.min.js +6 -0
  46. data/assets/javascript/vendor/notify-osd.js +319 -0
  47. data/assets/javascript/vendor/player-graphics.gif +0 -0
  48. data/assets/javascript/vendor/spin.js +218 -0
  49. data/assets/javascript/views/action_view.coffee +18 -0
  50. data/assets/javascript/views/actions_list_view.coffee +2 -0
  51. data/assets/javascript/views/environments_list_view.coffee +2 -0
  52. data/assets/javascript/views/install_button_view.coffee +34 -0
  53. data/assets/javascript/views/mass_run_view.coffee +107 -0
  54. data/assets/javascript/views/notifier_control_view.coffee +29 -0
  55. data/assets/javascript/views/terminal_view.coffee +56 -0
  56. data/assets/stylesheets/application.css +137 -0
  57. data/assets/stylesheets/bootstrap-theme.css +347 -0
  58. data/assets/stylesheets/bootstrap.css +5785 -0
  59. data/assets/stylesheets/fonts/glyphicons-halflings-regular.eot +0 -0
  60. data/assets/stylesheets/fonts/glyphicons-halflings-regular.svg +229 -0
  61. data/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf +0 -0
  62. data/assets/stylesheets/fonts/glyphicons-halflings-regular.woff +0 -0
  63. data/assets/stylesheets/ladda-themeless.css +330 -0
  64. data/assets/stylesheets/ladda.css +392 -0
  65. data/assets/stylesheets/notify-osd.css +49 -0
  66. data/assets/stylesheets/terminal.css +12 -0
  67. data/bin/nodectl +4 -0
  68. data/lib/nodectl.rb +137 -0
  69. data/lib/nodectl/action.rb +80 -0
  70. data/lib/nodectl/binding.rb +13 -0
  71. data/lib/nodectl/cli.rb +123 -0
  72. data/lib/nodectl/context.rb +34 -0
  73. data/lib/nodectl/database.rb +55 -0
  74. data/lib/nodectl/generators/init.rb +22 -0
  75. data/lib/nodectl/generators/templates/init/.gitignore +5 -0
  76. data/lib/nodectl/generators/templates/init/config/manifest.yml +1 -0
  77. data/lib/nodectl/generators/templates/init/config/server.yml +7 -0
  78. data/lib/nodectl/instance.rb +100 -0
  79. data/lib/nodectl/log.rb +13 -0
  80. data/lib/nodectl/manager.rb +71 -0
  81. data/lib/nodectl/multi_io.rb +16 -0
  82. data/lib/nodectl/options.rb +47 -0
  83. data/lib/nodectl/process.rb +145 -0
  84. data/lib/nodectl/promised_file.rb +37 -0
  85. data/lib/nodectl/recipe.rb +197 -0
  86. data/lib/nodectl/repository.rb +80 -0
  87. data/lib/nodectl/server.rb +103 -0
  88. data/lib/nodectl/service.rb +126 -0
  89. data/lib/nodectl/stream/buffer.rb +44 -0
  90. data/lib/nodectl/stream/events_session.rb +39 -0
  91. data/lib/nodectl/stream/file.rb +69 -0
  92. data/lib/nodectl/stream/file_session.rb +35 -0
  93. data/lib/nodectl/stream/file_with_memory.rb +17 -0
  94. data/lib/nodectl/stream/websocket.rb +90 -0
  95. data/lib/nodectl/version.rb +3 -0
  96. data/lib/nodectl/watchdog.rb +17 -0
  97. data/lib/nodectl/webapp/actions.rb +21 -0
  98. data/lib/nodectl/webapp/base.rb +29 -0
  99. data/lib/nodectl/webapp/instances.rb +46 -0
  100. data/lib/nodectl/webapp/public/sounds/alert.mp3 +0 -0
  101. data/lib/nodectl/webapp/public/sounds/error.mp3 +0 -0
  102. data/lib/nodectl/webapp/public/sounds/question.mp3 +0 -0
  103. data/lib/nodectl/webapp/services.rb +39 -0
  104. data/lib/nodectl/webapp/settings.rb +7 -0
  105. data/lib/nodectl/webapp/ui.rb +19 -0
  106. data/lib/nodectl/webapp/versions.rb +12 -0
  107. data/lib/nodectl/webapp/views/ui.haml +20 -0
  108. data/nodectl.gemspec +35 -0
  109. data/spec/spec_helper.rb +12 -0
  110. data/spec/stream/buffer_spec.rb +33 -0
  111. metadata +365 -0
@@ -0,0 +1,73 @@
1
+ UI.ApplicationController = Ember.ArrayController.extend
2
+ running_actions: (->
3
+ @store.find('action')
4
+ ).property()
5
+
6
+ version: (->
7
+ window.settings.version
8
+ ).property()
9
+
10
+ environments: (->
11
+ UI.get('settings').environments
12
+ ).property()
13
+
14
+ environmentClass: (->
15
+ UI.get('currentEnvironment').toString()
16
+ ).property('UI.currentEnvironment')
17
+
18
+ current_action: null
19
+
20
+ massRunOpened: false
21
+
22
+ current_action_hook: (->
23
+ # TODO: move it away
24
+ @escape_handler ||= (e) =>
25
+ if e.keyCode == 27
26
+ @send('closeAction')
27
+
28
+ if @get('current_action')
29
+ $("body").css('overflow', 'hidden')
30
+ $(document).on('keyup', @escape_handler)
31
+ else
32
+ $("body").css('overflow', 'auto')
33
+ $(document).off('keyup', @escape_handler)
34
+ ).observes('current_action')
35
+
36
+ actions:
37
+ install_service: (service) ->
38
+ service.run_slot('install')
39
+
40
+ uninstall_service: (service) ->
41
+ service.run_slot('uninstall')
42
+
43
+ updateAll: ->
44
+ @get('model').forEach (service) =>
45
+ if service.get('version_set_supported')
46
+ service.set_latest_version()
47
+
48
+ openAction: (action) ->
49
+ @set('current_action', action)
50
+
51
+ closeAction: ->
52
+ @set('current_action', null)
53
+
54
+ runAll: ->
55
+ @get('store').find('service').then (list) =>
56
+ list.forEach (service) =>
57
+ if !service.get('isRunning') && service.get('start_supported')
58
+ instance = @get('store').createRecord 'instance', service: service
59
+ instance.save().then =>
60
+ service.reload()
61
+
62
+ stopAll: ->
63
+ @get('store').find('instance').then (list) =>
64
+ list.forEach (instance) =>
65
+ if instance.get('status') == 'running'
66
+ instance.set('status', 'stopping')
67
+ instance.save()
68
+
69
+ toggleMassRunOpened: ->
70
+ @set('massRunOpened', !@get('massRunOpened'))
71
+
72
+ setEnvironment: (environment) ->
73
+ UI.set('currentEnvironment', environment)
@@ -0,0 +1,62 @@
1
+ UI.ServiceController = Ember.ObjectController.extend
2
+
3
+ testme: null
4
+ inst_options: {}
5
+
6
+ currentEnvironmentObserver: (->
7
+ console.log 'set to ', UI.get('currentEnvironment').toString()
8
+ @set('inst_options.env', UI.get('currentEnvironment').toString())
9
+ ).observes('UI.currentEnvironment')
10
+
11
+ init: ->
12
+ @set('inst_options.env', UI.get('currentEnvironment').toString())
13
+
14
+ versions_opened: false
15
+
16
+ actions:
17
+ run_slot: (slot_name) ->
18
+ service = @get('model')
19
+ service.run_slot(slot_name.toString(), @get('inst_options'))
20
+
21
+ run_action: (action_name) ->
22
+ action = @store.createRecord 'action',
23
+ name: action_name.toString()
24
+ service: @get('model')
25
+ options: @get('inst_options')
26
+
27
+ action.save().then =>
28
+ @controllerFor('application').send('openAction', action)
29
+ @get('model').reload()
30
+
31
+ startInstance: ->
32
+ instance = @store.createRecord 'instance',
33
+ service: @get('model')
34
+ options: @get('inst_options')
35
+
36
+ instance.save().then =>
37
+ @get('model').reload()
38
+
39
+ stopInstance: (instance) ->
40
+ instance.set('status', 'stopping')
41
+ instance.save()
42
+
43
+ restartInstance: (instance) ->
44
+ instance.set('status', 'restarting')
45
+ instance.save()
46
+
47
+ killInstance: (instance) ->
48
+ instance.set('status', 'stopping')
49
+ instance.set('force_stop', true)
50
+ instance.save()
51
+
52
+ versions_toggle: ->
53
+ @set('versions_opened', !@get('versions_opened'))
54
+
55
+ setVersion: (version_id) ->
56
+ @get('model').set_version(version_id)
57
+
58
+ setLatestVersion: ->
59
+ @get('model').set_latest_version()
60
+
61
+ installService: (service) ->
62
+ @get('model').run_slot('install')
@@ -0,0 +1,51 @@
1
+ class @EventHandler
2
+ constructor: (store) ->
3
+ @store = store
4
+ @ws = new WebSocket("#{window.settings.ws}/events")
5
+
6
+ @ws.onmessage = (message) =>
7
+ event = JSON.parse(message.data)
8
+
9
+ switch event.resource
10
+ when "instance"
11
+ @instance_event(event)
12
+ when "action"
13
+ @action_event(event)
14
+ else
15
+ console.error "unknown event resource: '#{event.resource}'"
16
+
17
+ instance_event: (event) ->
18
+ switch event.event_name
19
+ when "started"
20
+ @store.find('instance', event.id).then (instance) ->
21
+ instance.get('service').reload()
22
+
23
+ when "stopped"
24
+ @store.find('instance', event.id).then (instance) ->
25
+ if event.payload.reason == "crashed"
26
+ service_id = instance.get('service.id')
27
+ instance_id = instance.get('id')
28
+ window.notifier.error("#{service_id} instance #{instance_id} was crashed")
29
+
30
+ instance.get('service').reload()
31
+ else
32
+ console.error "unknown instance event: '#{event.event_name}'"
33
+
34
+ action_event: (event) ->
35
+ switch event.event_name
36
+ when "started"
37
+ @store.find('action')
38
+
39
+ when "stopped"
40
+ @store.find('action').then =>
41
+ @store.find('action', event.id).then (action) ->
42
+ service_id = action.get('service.id')
43
+ action_name = action.get('name')
44
+
45
+ if action.get('isSucceed')
46
+ window.notifier.alert("#{service_id} action #{action_name} succeed")
47
+ else
48
+ window.notifier.error("#{service_id} action #{action_name} failed")
49
+
50
+ else
51
+ console.error "unknown action event: '#{event.event_name}'"
@@ -0,0 +1,28 @@
1
+ @UI.Action = DS.Model.extend
2
+ ws: DS.attr('string')
3
+ name: DS.attr('string')
4
+ service: DS.belongsTo('service')
5
+ options: DS.attr()
6
+ started_at: DS.attr("string")
7
+ exit_status: DS.attr("number")
8
+
9
+ started_at_formatted: (->
10
+ moment(@get('started_at')).format("MM-DD-YYYY HH:MM:SS")
11
+ ).property('started_at')
12
+
13
+ result: (->
14
+ if @get('exit_status') is null
15
+ null
16
+ else if @get('exit_status') is 0
17
+ "succeed"
18
+ else
19
+ "failed"
20
+ ).property('exit_status')
21
+
22
+ isSucceed: (->
23
+ @get('exit_status') is 0
24
+ ).property('exit_status')
25
+
26
+ isFailed: (->
27
+ @get('exit_status') isnt null && @get('exit_status') isnt 0
28
+ ).property('exit_status')
@@ -0,0 +1,7 @@
1
+ @UI.Instance = DS.Model.extend
2
+ status: DS.attr('string')
3
+ service: DS.belongsTo('service')
4
+ options: DS.attr()
5
+
6
+ # Fake attrs for API actions
7
+ force_stop: DS.attr('boolean')
@@ -0,0 +1,75 @@
1
+ @UI.Service = DS.Model.extend
2
+ status: DS.attr('string')
3
+ instances: DS.hasMany('instance', async: true)
4
+ recipe_name: DS.attr('string')
5
+ supported_slots: DS.attr('array')
6
+ supported_actions: DS.attr('array')
7
+ logs: DS.attr()
8
+ params: DS.attr()
9
+
10
+ version_id: DS.attr('string')
11
+ version_timestamp: DS.attr('string')
12
+ version_message: DS.attr('string')
13
+
14
+ slot: DS.attr('string')
15
+ slot_params: DS.attr()
16
+
17
+ start_supported: (->
18
+ @get('supported_slots').indexOf('start') >= 0 and @get('status') == 'installed'
19
+ ).property('status')
20
+
21
+ stop_supported: (->
22
+ @get('supported_slots').indexOf('start') >= 0
23
+ ).property('status')
24
+
25
+ install_supported: (->
26
+ @get('supported_slots').indexOf('install') >= 0 and @get('status') == 'available'
27
+ ).property('status')
28
+
29
+ uninstall_supported: (->
30
+ @get('supported_slots').indexOf('uninstall') >= 0 and @get('status') == 'installed'
31
+ ).property('status')
32
+
33
+ version_set_supported: (->
34
+ @get('supported_slots').indexOf('version_set') >= 0
35
+ )
36
+
37
+ params_iterable: (->
38
+ for key, value of @get('params')
39
+ $.extend({}, value, {name: key})
40
+ ).property()
41
+
42
+ run_slot: (slot_name, slot_params = {}, callback = null) ->
43
+ unless @get('supported_slots').indexOf(slot_name) >= 0
44
+ throw new Error("slot '#{slot_name}' is not supported for service '#{@get('id')}'")
45
+
46
+ @set('slot', slot_name)
47
+ @set('slot_params', slot_params)
48
+ @save().then =>
49
+ if slot_name == "install"
50
+ @get('store').find('service')
51
+
52
+ callback() if callback
53
+
54
+ set_version: (version_id) ->
55
+ @set('version_id', version_id)
56
+ @save().then =>
57
+ @reload()
58
+
59
+
60
+ set_latest_version: ->
61
+ @get('versions').then =>
62
+ @set_version(@get('versions.firstObject.id'))
63
+
64
+ versions: ( ->
65
+ @store.find('version', service_id: @get('id'))
66
+ ).property()
67
+
68
+ isInstalled: (->
69
+ @get('status') == 'installed'
70
+ ).property('status')
71
+
72
+ isRunning: (->
73
+ # FIXME: it does not work
74
+ @get('instances.length') > 0
75
+ ).property('instances.length')
@@ -0,0 +1,3 @@
1
+ @UI.Version = DS.Model.extend
2
+ timestamp: DS.attr('string')
3
+ message: DS.attr('string')
@@ -0,0 +1,64 @@
1
+ class @Notifier
2
+ constructor: ->
3
+ $ =>
4
+ wrapper = $("#sound")
5
+
6
+ wrapper.append("<audio src='/sounds/error.mp3' preload='auto' id='sound_error'/>")
7
+ wrapper.append("<audio src='/sounds/alert.mp3' preload='auto' id='sound_alert'/>")
8
+
9
+ @sound =
10
+ error: $('#sound_error')[0];
11
+ alert: $('#sound_alert')[0];
12
+
13
+ @popupEnabled = true
14
+ @soundEnabled = false
15
+
16
+ notify: (text, type = 'alert') ->
17
+ @_showPopup(text, type)
18
+ @_playSound(type)
19
+
20
+ error: (text) ->
21
+ @notify(text, 'error')
22
+
23
+ alert: (text) ->
24
+ @notify(text, 'alert')
25
+
26
+ popupEnable: ->
27
+ @popupEnabled = true
28
+
29
+ popupDisable: ->
30
+ @popupEnabled = false
31
+
32
+ isPopupEnabled: ->
33
+ @popupEnabled
34
+
35
+ soundEnable: ->
36
+ @soundEnabled = true
37
+
38
+ soundDisable: ->
39
+ @soundEnabled = false
40
+
41
+ isSoundEnabled: ->
42
+ @soundEnabled
43
+
44
+ _showPopup: (text, type) ->
45
+ if @popupEnabled
46
+ if Notification && Notification.permission == 'granted'
47
+ new Notification('Nodectl', body: text)
48
+ else
49
+ $.notify_osd.create(text: text)
50
+
51
+ _playSound: (type) ->
52
+ if @soundEnabled && @sound && @sound[type]
53
+ if @current_sound
54
+ @current_sound.pause()
55
+ @current_sound.currentTime = 0
56
+
57
+ @current_sound = @sound[type]
58
+ @sound[type].play()
59
+
60
+ $.notify_osd.setup
61
+ visible_max: 10
62
+ dismissable: true
63
+
64
+ window.notifier = new Notifier()
@@ -0,0 +1,3 @@
1
+ UI.Router.map ->
2
+ @resource 'service', path: '/:service_id', ->
3
+ @resource 'log', path: '/:log_name'
@@ -0,0 +1,6 @@
1
+ @UI.ApplicationRoute = Em.Route.extend
2
+ model: ->
3
+ @store.find('service')
4
+
5
+ afterModel: ->
6
+ new EventHandler(@store)
@@ -0,0 +1,4 @@
1
+ UI.LogRoute = Ember.Route.extend
2
+ model: (params) ->
3
+ @modelFor('service').get('logs').find (log) ->
4
+ log.name == params.log_name
@@ -0,0 +1,3 @@
1
+ UI.ServiceRoute = Ember.Route.extend
2
+ model: (params) ->
3
+ @store.find('service', params.service_id)
@@ -0,0 +1,32 @@
1
+ UI.ApplicationSerializer = DS.ActiveModelSerializer.extend {}
2
+
3
+ UI.ServiceSerializer = DS.ActiveModelSerializer.extend
4
+ serialize: (record, options) ->
5
+ json = @_super.apply(@, arguments)
6
+
7
+ action = record.get('action')
8
+
9
+ if action
10
+ json.action = action
11
+ json.action_params = record.get('action_params')
12
+ record.set('action', undefined)
13
+ record.set('action_params', undefined)
14
+
15
+ json
16
+
17
+
18
+ UI.ArrayTransform = DS.Transform.extend
19
+ serialize: (jsonData)->
20
+ if Em.typeOf(jsonData) is 'array'
21
+ jsonData
22
+ else
23
+ []
24
+
25
+ deserialize: (externalData)->
26
+ switch Em.typeOf(externalData)
27
+ when 'array'
28
+ externalData
29
+ when 'string'
30
+ externalData.split(',').map((item)-> jQuery.trim(item))
31
+ else
32
+ []
@@ -0,0 +1,82 @@
1
+ <div {{bind-attr class=":main-wrapper environmentClass"}}>
2
+ <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
3
+ <div class="container-fluid">
4
+
5
+ <div class="navbar-header">
6
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar">
7
+ <span class="sr-only">Toggle navigation</span>
8
+ <span class="icon-bar"></span>
9
+ <span class="icon-bar"></span>
10
+ <span class="icon-bar"></span>
11
+ </button>
12
+ <a class="navbar-brand" href="#">Nodectl {{version}}</a>
13
+ </div>
14
+
15
+ <div class="collapse navbar-collapse" id="navbar">
16
+ <ul class="nav navbar-nav navbar-right">
17
+ {{view UI.NotifierControlView }}
18
+ {{view UI.ActionsListView tagName='li'}}
19
+ {{view UI.EnvironmentsListView tagName='li'}}
20
+ </ul>
21
+ </div>
22
+
23
+ </div>
24
+ </nav>
25
+
26
+ {{#if current_action}}
27
+ <div class="modal-window">
28
+ <span class="glyphicon glyphicon-remove modal-window-close" {{action 'closeAction'}}></span>
29
+ {{view UI.ActionView action=current_action}}
30
+ </div>
31
+ {{/if}}
32
+
33
+ <div class="container-fluid">
34
+ <div class="row">
35
+ <div class="col-xs-12 col-sm-4 col-md-4 col-lg-3 sidebar">
36
+ <div class="sidebar-services-list list-group">
37
+ {{#if massRunOpened}}
38
+ {{view UI.MassRunView services=this}}
39
+ {{else}}
40
+ <div class="list-group-item list-group-item-service list-group-item-service-controlls">
41
+ <div class="btn-group">
42
+ <button class="btn btn-primary btn-xs" {{action 'toggleMassRunOpened'}}>Run...</button>
43
+ <button class="btn btn-danger btn-xs" {{action 'stopAll'}}>Stop all</button>
44
+ </div>
45
+
46
+ {{view UI.InstallButtonView class="pull-right"}}
47
+ </div>
48
+
49
+ {{#each}}
50
+ {{#if isInstalled}}
51
+ {{#link-to 'service' this class="list-group-item list-group-item-service" classNameBindings="isRunning"}}
52
+ <div class="service-run-indicator">&nbsp</div>
53
+
54
+ <div class="sidebar-services-id">
55
+ {{id}}
56
+ </div>
57
+
58
+ {{#if start_supported}}
59
+ <span class="badge">{{instances.length}}</span>
60
+ {{/if}}
61
+ {{/link-to}}
62
+ {{else}}
63
+ <div class="list-group-item list-group-item-service list-group-item-uninstalled">
64
+ {{id}}
65
+ {{#if install_supported}}
66
+ {{view UI.InstallButtonView service=this class="pull-right"}}
67
+ {{/if}}
68
+ </div>
69
+ {{/if}}
70
+ {{/each}}
71
+ {{/if}}
72
+ </div>
73
+ </div>
74
+
75
+ <div class="clearfix visible-xs"></div>
76
+
77
+ <div class="col-xs-12 col-sm-8 col-md-8 col-lg-9">
78
+ {{outlet}}
79
+ </div>
80
+ </div>
81
+ </div>
82
+ </div>