nodectl 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +1 -0
- data/TODO.md +7 -0
- data/assets/javascript/application.coffee +22 -0
- data/assets/javascript/bootstrap.js +2006 -0
- data/assets/javascript/controllers/application_controller.coffee +73 -0
- data/assets/javascript/controllers/service_controller.coffee +62 -0
- data/assets/javascript/event_handler.coffee +51 -0
- data/assets/javascript/models/action.coffee +28 -0
- data/assets/javascript/models/instance.coffee +7 -0
- data/assets/javascript/models/service.coffee +75 -0
- data/assets/javascript/models/version.coffee +3 -0
- data/assets/javascript/notifier.coffee +64 -0
- data/assets/javascript/router.coffee +3 -0
- data/assets/javascript/routes/application.coffee +6 -0
- data/assets/javascript/routes/log.coffee +4 -0
- data/assets/javascript/routes/service.coffee +3 -0
- data/assets/javascript/store.coffee +32 -0
- data/assets/javascript/templates/application.hbs +82 -0
- data/assets/javascript/templates/index.hbs +1 -0
- data/assets/javascript/templates/log.hbs +1 -0
- data/assets/javascript/templates/service.hbs +110 -0
- data/assets/javascript/templates/views/action.hbs +20 -0
- data/assets/javascript/templates/views/actions_list.hbs +26 -0
- data/assets/javascript/templates/views/environments_list.hbs +9 -0
- data/assets/javascript/templates/views/install_button.hbs +1 -0
- data/assets/javascript/templates/views/mass_run.hbs +30 -0
- data/assets/javascript/templates/views/notifier_control.hbs +7 -0
- data/assets/javascript/templates/views/param.hbs +6 -0
- data/assets/javascript/templates/views/terminal.hbs +8 -0
- data/assets/javascript/ui.coffee +24 -0
- data/assets/javascript/vendor/ansiparse.js +186 -0
- data/assets/javascript/vendor/audio.min.js +24 -0
- data/assets/javascript/vendor/audiojs.swf +0 -0
- data/assets/javascript/vendor/ember-data.js +10528 -0
- data/assets/javascript/vendor/ember.js +40583 -0
- data/assets/javascript/vendor/handlebars.js +2746 -0
- data/assets/javascript/vendor/jquery.js +8829 -0
- data/assets/javascript/vendor/ladda.js +157 -0
- data/assets/javascript/vendor/moment.min.js +6 -0
- data/assets/javascript/vendor/notify-osd.js +319 -0
- data/assets/javascript/vendor/player-graphics.gif +0 -0
- data/assets/javascript/vendor/spin.js +218 -0
- data/assets/javascript/views/action_view.coffee +18 -0
- data/assets/javascript/views/actions_list_view.coffee +2 -0
- data/assets/javascript/views/environments_list_view.coffee +2 -0
- data/assets/javascript/views/install_button_view.coffee +34 -0
- data/assets/javascript/views/mass_run_view.coffee +107 -0
- data/assets/javascript/views/notifier_control_view.coffee +29 -0
- data/assets/javascript/views/terminal_view.coffee +56 -0
- data/assets/stylesheets/application.css +137 -0
- data/assets/stylesheets/bootstrap-theme.css +347 -0
- data/assets/stylesheets/bootstrap.css +5785 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.svg +229 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/assets/stylesheets/ladda-themeless.css +330 -0
- data/assets/stylesheets/ladda.css +392 -0
- data/assets/stylesheets/notify-osd.css +49 -0
- data/assets/stylesheets/terminal.css +12 -0
- data/bin/nodectl +4 -0
- data/lib/nodectl.rb +137 -0
- data/lib/nodectl/action.rb +80 -0
- data/lib/nodectl/binding.rb +13 -0
- data/lib/nodectl/cli.rb +123 -0
- data/lib/nodectl/context.rb +34 -0
- data/lib/nodectl/database.rb +55 -0
- data/lib/nodectl/generators/init.rb +22 -0
- data/lib/nodectl/generators/templates/init/.gitignore +5 -0
- data/lib/nodectl/generators/templates/init/config/manifest.yml +1 -0
- data/lib/nodectl/generators/templates/init/config/server.yml +7 -0
- data/lib/nodectl/instance.rb +100 -0
- data/lib/nodectl/log.rb +13 -0
- data/lib/nodectl/manager.rb +71 -0
- data/lib/nodectl/multi_io.rb +16 -0
- data/lib/nodectl/options.rb +47 -0
- data/lib/nodectl/process.rb +145 -0
- data/lib/nodectl/promised_file.rb +37 -0
- data/lib/nodectl/recipe.rb +197 -0
- data/lib/nodectl/repository.rb +80 -0
- data/lib/nodectl/server.rb +103 -0
- data/lib/nodectl/service.rb +126 -0
- data/lib/nodectl/stream/buffer.rb +44 -0
- data/lib/nodectl/stream/events_session.rb +39 -0
- data/lib/nodectl/stream/file.rb +69 -0
- data/lib/nodectl/stream/file_session.rb +35 -0
- data/lib/nodectl/stream/file_with_memory.rb +17 -0
- data/lib/nodectl/stream/websocket.rb +90 -0
- data/lib/nodectl/version.rb +3 -0
- data/lib/nodectl/watchdog.rb +17 -0
- data/lib/nodectl/webapp/actions.rb +21 -0
- data/lib/nodectl/webapp/base.rb +29 -0
- data/lib/nodectl/webapp/instances.rb +46 -0
- data/lib/nodectl/webapp/public/sounds/alert.mp3 +0 -0
- data/lib/nodectl/webapp/public/sounds/error.mp3 +0 -0
- data/lib/nodectl/webapp/public/sounds/question.mp3 +0 -0
- data/lib/nodectl/webapp/services.rb +39 -0
- data/lib/nodectl/webapp/settings.rb +7 -0
- data/lib/nodectl/webapp/ui.rb +19 -0
- data/lib/nodectl/webapp/versions.rb +12 -0
- data/lib/nodectl/webapp/views/ui.haml +20 -0
- data/nodectl.gemspec +35 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/stream/buffer_spec.rb +33 -0
- 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,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,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,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"> </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>
|