summaryrefslogtreecommitdiff
path: root/mobmonitor
diff options
context:
space:
mode:
authorMatthew Sartori <msartori@google.com>2015-08-26 11:02:58 -0700
committerChromeOS Commit Bot <chromeos-commit-bot@chromium.org>2015-08-27 01:49:47 +0000
commit9245f45dcdac07e8807cd813bc799becba1861da (patch)
tree0858d09426c2d9c18c365c272434a1b0611f1081 /mobmonitor
parentc72db8536609c8836b4ab7c455a3b4f16c08f361 (diff)
downloadchromite-9245f45dcdac07e8807cd813bc799becba1861da.tar.gz
mobmonitor: UI updates for the 'Action/Repair Dialog'
This CL addresses most of the feedback received about the action/repair dialog in the Mob* Monitor web ui. New in this CL: - Provided more information to the user about the actions they can carry out. This includes a descriptions of the actions and the types of parameters that the actions expect. - Made the display of 'staged' actions clearer. - Re-arranged buttons on the dialog to make their purpose clearer. BUG=chromium:520749 TEST=Unittests for added RPCs. Manual testing for the UI. Change-Id: Ia0294c41e9842edb85cbcc47f3530cb821d4decb Reviewed-on: https://chromium-review.googlesource.com/295429 Reviewed-by: Matthew Sartori <msartori@chromium.org> Tested-by: Matthew Sartori <msartori@chromium.org> Commit-Queue: Matthew Sartori <msartori@chromium.org>
Diffstat (limited to 'mobmonitor')
-rw-r--r--mobmonitor/checkfile/manager.py66
-rw-r--r--mobmonitor/checkfile/manager_unittest.py126
-rw-r--r--mobmonitor/rpc/rpc.py27
-rwxr-xr-xmobmonitor/scripts/mobmoncli.py7
-rw-r--r--mobmonitor/scripts/mobmoncli_unittest.py8
-rwxr-xr-xmobmonitor/scripts/mobmonitor.py15
-rw-r--r--mobmonitor/scripts/mobmonitor_unittest.py15
-rw-r--r--mobmonitor/static/css/style.css20
-rw-r--r--mobmonitor/static/js/actionrepairdialog.js105
-rw-r--r--mobmonitor/static/js/rpc.js21
-rw-r--r--mobmonitor/static/templates/actionrepairdialog.html27
11 files changed, 399 insertions, 38 deletions
diff --git a/mobmonitor/checkfile/manager.py b/mobmonitor/checkfile/manager.py
index 2b44f00a7..fe20d25d2 100644
--- a/mobmonitor/checkfile/manager.py
+++ b/mobmonitor/checkfile/manager.py
@@ -43,6 +43,9 @@ CHECKFILE_ENDING = '_check.py'
SERVICE_STATUS = collections.namedtuple('service_status',
['service', 'health', 'healthchecks'])
+ACTION_INFO = collections.namedtuple('action_info',
+ ['action', 'info', 'args', 'kwargs'])
+
class CollectionError(Exception):
"""Raise when an error occurs during checkfile collection."""
@@ -77,6 +80,11 @@ def MapServiceStatusToDict(status):
'healthchecks': hcstatuses}
+def MapActionInfoToDict(actioninfo):
+ return {'action': actioninfo.action, 'info': actioninfo.info,
+ 'args': actioninfo.args, 'kwargs': actioninfo.kwargs}
+
+
def isHealthcheckHealthy(hcstatus):
"""Test if a health check is perfectly healthy.
@@ -378,6 +386,64 @@ class CheckFileManager(object):
return self.service_states.get(service, SERVICE_STATUS(service, False, []))
+ def ActionInfo(self, service, action):
+ """Describes a currently valid action for the given service and healthcheck.
+
+ An action is valid if the following hold:
+ The |service| is recognized and is in an unhealthy or quasi-healthy state.
+ The |action| is one specified as a suitable repair action by the
+ Diagnose method of some non-healthy healthcheck of |service|.
+
+ Args:
+ service: A string. The name of a service being monitored.
+ action: A string. The name of an action returned by some healthcheck's
+ Diagnose method.
+
+ Returns:
+ An ACTION_INFO named tuple which has the following fields:
+ action: A string. The given |action| string.
+ info: A string. The docstring of |action|.
+ args: A list of strings. The positional arguments for |action|.
+ kwargs: A dictionary representing the default keyword arguments
+ for |action|. The keys will be the kwarg names and the values
+ will be the default arguments.
+ """
+ status = self.service_states.get(service, None)
+ if not status:
+ return ACTION_INFO(action, 'Service not recognized.', [], {})
+ elif isServiceHealthy(status):
+ return ACTION_INFO(action, 'Service is healthy.', [], {})
+
+ def FindAction():
+ for hc in status.healthchecks:
+ if isHealthcheckHealthy(hc):
+ continue
+ for a in hc.actions:
+ if a.__name__ == action:
+ return a
+ return None
+
+ func = FindAction()
+
+ if not func:
+ return ACTION_INFO(action, 'Action not recognized.', [], {})
+
+ # Collect information on the repair action.
+ argspec = inspect.getargspec(func)
+ func_args = argspec.args or []
+ func_args = [x for x in func_args if x not in ['self', 'cls']]
+ func_defaults = argspec.defaults or {}
+
+ num_args = len(func_args)
+ num_defaults = len(func_defaults)
+
+ args = func_args[:num_args-num_defaults]
+ kwargs = dict(zip(func_args[num_args-num_defaults:], func_defaults))
+
+ info = func.__doc__
+
+ return ACTION_INFO(action, info, args, kwargs)
+
def RepairService(self, service, action, args, kwargs):
"""Execute the repair action on the specified service.
diff --git a/mobmonitor/checkfile/manager_unittest.py b/mobmonitor/checkfile/manager_unittest.py
index 3291d69c3..85a713918 100644
--- a/mobmonitor/checkfile/manager_unittest.py
+++ b/mobmonitor/checkfile/manager_unittest.py
@@ -66,6 +66,42 @@ class TestHealthCheckUnhealthy(object):
self.x = 0
+class TestHealthCheckMultipleActions(object):
+ """Unhealthy check with many actions that have different parameters."""
+
+ def __init__(self):
+ self.x = -1
+
+ def Check(self):
+ """Stub Check."""
+ return self.x
+
+ def Diagnose(self, errcode):
+ """Stub Diagnose."""
+ if errcode == -1:
+ return ('Stub Error.', [self.NoParams, self.PositionalParams,
+ self.DefaultParams, self.MixedParams])
+ return ('Unknown Error.', [])
+
+ def NoParams(self):
+ """NoParams Action."""
+ self.x = 0
+
+ # pylint: disable=unused-argument
+ def PositionalParams(self, x, y, z):
+ """PositionalParams Action."""
+ self.x = 0
+
+ def DefaultParams(self, x=1, y=2, z=3):
+ """DefaultParams Action."""
+ self.x = 0
+
+ def MixedParams(self, x, y, z=1):
+ """MixedParams Action."""
+ self.x = 0
+ # pylint: enable=unused-argument
+
+
class TestHealthCheckQuasihealthy(object):
"""Quasi-healthy test health check."""
@@ -76,10 +112,10 @@ class TestHealthCheckQuasihealthy(object):
def Diagnose(self, errcode):
"""Stub Diagnose."""
if errcode == 1:
- return ('Stub Error.', ['RepairStub'])
+ return ('Stub Error.', [self.RepairStub])
return ('Unknown Error.', [])
- def ActionRepairStub(self):
+ def RepairStub(self):
"""Stub repair action."""
@@ -230,6 +266,13 @@ class CheckFileManagerHelperTest(cros_test_lib.MockTestCase):
'healthchecks': [hcexpect]}
self.assertEquals(expect, manager.MapServiceStatusToDict(status))
+ def testMapActionInfoToDict(self):
+ """Test mapping a manager.ACTION_INFO to a dict."""
+ actioninfo = manager.ACTION_INFO('test', 'test', [1], {'a': 1})
+ expect = {'action': 'test', 'info': 'test', 'args': [1],
+ 'kwargs': {'a': 1}}
+ self.assertEquals(expect, manager.MapActionInfoToDict(actioninfo))
+
def testIsHealthcheckHealthy(self):
"""Test checking whether health check statuses are healthy."""
# Test a healthy health check.
@@ -778,6 +821,85 @@ class CheckFileManagerTest(cros_test_lib.MockTestCase):
self.assertTrue(status.health)
self.assertEquals(0, len(status.healthchecks))
+ def testActionInfoServiceNonExistent(self):
+ """Test the ActionInfo RPC when the service does not exist."""
+ cfm = manager.CheckFileManager(checkdir=CHECKDIR)
+
+ self.assertFalse(TEST_SERVICE_NAME in cfm.service_states)
+
+ expect = manager.ACTION_INFO('test', 'Service not recognized.',
+ [], {})
+ result = cfm.ActionInfo(TEST_SERVICE_NAME, 'test')
+ self.assertEquals(expect, result)
+
+ def testActionInfoServiceHealthy(self):
+ """Test the ActionInfo RPC when the service is healthy."""
+ cfm = manager.CheckFileManager(checkdir=CHECKDIR)
+
+ healthy_status = manager.SERVICE_STATUS(TEST_SERVICE_NAME, True, [])
+ cfm.service_states[TEST_SERVICE_NAME] = healthy_status
+
+ expect = manager.ACTION_INFO('test', 'Service is healthy.',
+ [], {})
+ result = cfm.ActionInfo(TEST_SERVICE_NAME, 'test')
+ self.assertEquals(expect, result)
+
+ def testActionInfoActionNonExistent(self):
+ """Test the ActionInfo RPC when the action does not exist."""
+ cfm = manager.CheckFileManager(checkdir=CHECKDIR)
+
+ hcobj = TestHealthCheckUnhealthy()
+ cfm.service_checks[TEST_SERVICE_NAME] = {
+ hcobj.__class__.__name__: (TEST_MTIME, hcobj)}
+
+ unhealthy_status = manager.SERVICE_STATUS(
+ TEST_SERVICE_NAME, False,
+ [manager.HEALTHCHECK_STATUS(hcobj.__class__.__name__,
+ False, 'Always fails', [hcobj.Repair])])
+ cfm.service_states[TEST_SERVICE_NAME] = unhealthy_status
+
+ expect = manager.ACTION_INFO('test', 'Action not recognized.', [], {})
+ result = cfm.ActionInfo(TEST_SERVICE_NAME, 'test')
+ self.assertEquals(expect, result)
+
+ def testActionInfo(self):
+ """Test the ActionInfo RPC to collect information on a repair action."""
+ cfm = manager.CheckFileManager(checkdir=CHECKDIR)
+
+ hcobj = TestHealthCheckMultipleActions()
+ actions = [hcobj.NoParams, hcobj.PositionalParams, hcobj.DefaultParams,
+ hcobj.MixedParams]
+
+ cfm.service_checks[TEST_SERVICE_NAME] = {
+ hcobj.__class__.__name__: (TEST_MTIME, hcobj)}
+
+ unhealthy_status = manager.SERVICE_STATUS(
+ TEST_SERVICE_NAME, False,
+ [manager.HEALTHCHECK_STATUS(hcobj.__class__.__name__,
+ False, 'Always fails', actions)])
+ cfm.service_states[TEST_SERVICE_NAME] = unhealthy_status
+
+ # Test ActionInfo when the action has no parameters.
+ expect = manager.ACTION_INFO('NoParams', 'NoParams Action.', [], {})
+ self.assertEquals(expect, cfm.ActionInfo(TEST_SERVICE_NAME, 'NoParams'))
+
+ # Test ActionInfo when the action has only positional parameters.
+ expect = manager.ACTION_INFO('PositionalParams', 'PositionalParams Action.',
+ ['x', 'y', 'z'], {})
+ self.assertEquals(expect,
+ cfm.ActionInfo(TEST_SERVICE_NAME, 'PositionalParams'))
+
+ # Test ActionInfo when the action has only default parameters.
+ expect = manager.ACTION_INFO('DefaultParams', 'DefaultParams Action.',
+ [], {'x': 1, 'y': 2, 'z': 3})
+ self.assertEquals(expect,
+ cfm.ActionInfo(TEST_SERVICE_NAME, 'DefaultParams'))
+
+ # Test ActionInfo when the action has positional and default parameters.
+ expect = manager.ACTION_INFO('MixedParams', 'MixedParams Action.',
+ ['x', 'y'], {'z': 1})
+ self.assertEquals(expect, cfm.ActionInfo(TEST_SERVICE_NAME, 'MixedParams'))
+
@cros_test_lib.NetworkTest()
class CheckFileModificationTest(cros_test_lib.MockTempDirTestCase):
diff --git a/mobmonitor/rpc/rpc.py b/mobmonitor/rpc/rpc.py
index 435f08499..1b9140fcb 100644
--- a/mobmonitor/rpc/rpc.py
+++ b/mobmonitor/rpc/rpc.py
@@ -16,7 +16,7 @@ from chromite.lib import retry_util
URLLIB_CALL_FORMAT_STR = '%(host)s/%(func)s/?%(args)s'
RPC_RETRY_TIMES = 5
RPC_SLEEP_SECS = 2
-RPC_LIST = ['GetServiceList', 'GetStatus', 'RepairService']
+RPC_LIST = ['GetServiceList', 'GetStatus', 'ActionInfo', 'RepairService']
class RpcError(Exception):
@@ -105,6 +105,31 @@ class RpcExecutor(object):
return self.Execute('GetStatus', service=service)
+ def ActionInfo(self, service=None, action=None):
+ """Collect argument and usage information for |action|.
+
+ See checkfile.manager.ActionInfo for more documentation on the
+ behaviour of this RPC.
+
+ Args:
+ service: A string. The name of a service being monitored.
+ action: A string. The name of an action returned by some healthcheck's
+ Diagnose method.
+
+ Returns:
+ A named tuple with the following fields:
+ action: The |action| string.
+ info: The docstring of |action|.
+ args: A list of the positional arguments for |action|.
+ kwargs: A dictionary of default arguments for |action|.
+ """
+ if service is None or action is None:
+ raise RpcError('ActionInfo requires both the service'
+ ' and action to be provided.'
+ ' Given: service=%s action=%s' % (service, action))
+
+ return self.Execute('ActionInfo', service=service, action=action)
+
def RepairService(self, service=None, action=None, args=None, kwargs=None):
"""Apply the specified action to the specified service.
diff --git a/mobmonitor/scripts/mobmoncli.py b/mobmonitor/scripts/mobmoncli.py
index 96420b41d..f69871984 100755
--- a/mobmonitor/scripts/mobmoncli.py
+++ b/mobmonitor/scripts/mobmoncli.py
@@ -78,6 +78,9 @@ class MobMonCli(object):
if 'GetStatus' == request:
return rpcexec.GetStatus(service=service)
+ if 'ActionInfo' == request:
+ return rpcexec.ActionInfo(service=service, action=action)
+
if 'RepairService' == request:
return rpcexec.RepairService(service=service, action=action, args=args,
kwargs=kwargs)
@@ -86,8 +89,8 @@ class MobMonCli(object):
def ParseArguments(argv):
parser = commandline.ArgumentParser()
parser.add_argument('request', choices=rpc.RPC_LIST)
- parser.add_argument('-s', '--service', help='The service to act upon')
- parser.add_argument('-a', '--action', help='The action to execute')
+ parser.add_argument('-s', '--service', help='The service to act upon.')
+ parser.add_argument('-a', '--action', help='The action to execute.')
parser.add_argument('--host', default='localhost',
help='The hostname of the Mob* Monitor.')
parser.add_argument('-p', '--port', type=int, default=9991,
diff --git a/mobmonitor/scripts/mobmoncli_unittest.py b/mobmonitor/scripts/mobmoncli_unittest.py
index 83f84b44d..cd4ad3699 100644
--- a/mobmonitor/scripts/mobmoncli_unittest.py
+++ b/mobmonitor/scripts/mobmoncli_unittest.py
@@ -64,6 +64,14 @@ class MobMonCliTest(cros_test_lib.MockTestCase):
self.cli.ExecuteRequest('GetStatus', 'TestService', '', '')
self.assertTrue(mock_executor.GetStatus.called)
+ def testActionInfo(self):
+ """Test that we correctly execute an ActionInfo RPC."""
+ with mock.patch('chromite.mobmonitor.rpc.rpc.RpcExecutor') as rpc_executor:
+ mock_executor = mock.MagicMock()
+ rpc_executor.return_value = mock_executor
+ self.cli.ExecuteRequest('ActionInfo', 'TestService', 'action', '')
+ self.assertTrue(mock_executor.ActionInfo.called)
+
def testRepairService(self):
"""Test that we correctly execute a RepairService RPC."""
with mock.patch('chromite.mobmonitor.rpc.rpc.RpcExecutor') as rpc_executor:
diff --git a/mobmonitor/scripts/mobmonitor.py b/mobmonitor/scripts/mobmonitor.py
index 7ce70964a..64ba0f27f 100755
--- a/mobmonitor/scripts/mobmonitor.py
+++ b/mobmonitor/scripts/mobmonitor.py
@@ -75,6 +75,21 @@ class MobMonitorRoot(object):
return json.dumps(result)
@cherrypy.expose
+ def ActionInfo(self, service, action):
+ """Return usage and argument information for |action|.
+
+ Args:
+ service: A string. The name of a service being monitored.
+ action: A string. The name of an action specified by some healthcheck's
+ Diagnose method.
+
+ Returns:
+ TBD
+ """
+ result = self.checkfile_manager.ActionInfo(service, action)
+ return json.dumps(manager.MapActionInfoToDict(result))
+
+ @cherrypy.expose
def RepairService(self, service, action, args, kwargs):
"""Execute the repair action on the specified service.
diff --git a/mobmonitor/scripts/mobmonitor_unittest.py b/mobmonitor/scripts/mobmonitor_unittest.py
index babce4bd9..731f63da7 100644
--- a/mobmonitor/scripts/mobmonitor_unittest.py
+++ b/mobmonitor/scripts/mobmonitor_unittest.py
@@ -25,6 +25,8 @@ class MockCheckFileManager(object):
manager.SERVICE_STATUS('service1', True, []),
manager.SERVICE_STATUS('service2', False, [failed_check])]
+ self.action_info = manager.ACTION_INFO('DummyAction', '', ['x'], {})
+
def GetServiceList(self):
"""Mock GetServiceList response."""
return ['test_service_1', 'test_service_2']
@@ -36,6 +38,10 @@ class MockCheckFileManager(object):
return self.service_statuses[0]
+ def ActionInfo(self, _service, _action):
+ """Mock ActionInfo response."""
+ return self.action_info
+
def RepairService(self, _service, _action, _args, _kwargs):
"""Mock RepairService response."""
return self.service_statuses[0]
@@ -80,6 +86,15 @@ class MobMonitorRootTest(cros_test_lib.MockTempDirTestCase):
'actions': []}]}]
self.assertEquals(expect, json.loads(root.GetStatus()))
+ def testActionInfo(self):
+ """Test the ActionInfo RPC."""
+ cfm = MockCheckFileManager()
+ root = mobmonitor.MobMonitorRoot(cfm, staticdir=self.staticdir)
+
+ expect = {'action': 'DummyAction', 'info': '', 'args': ['x'], 'kwargs': {}}
+ self.assertEquals(expect,
+ json.loads(root.ActionInfo('service2', 'DummyAction')))
+
def testRepairService(self):
"""Test the RepairService RPC."""
cfm = MockCheckFileManager()
diff --git a/mobmonitor/static/css/style.css b/mobmonitor/static/css/style.css
index 957ee230a..06a1b038f 100644
--- a/mobmonitor/static/css/style.css
+++ b/mobmonitor/static/css/style.css
@@ -108,19 +108,21 @@ td {
width:95%;
}
-.input-text {
- margin-bottom: 10px;
+.label-help {
width: 95%;
+ display: inline-block;
+ padding: 5px;
+ font-size: 12;
+ white-space: pre-wrap;
}
-.actiondisplay {
+.input-text {
width: 95%;
- height: 150px;
- resize: none;
+ margin-bottom: 10px;
}
-fieldset {
- padding: 0;
- border: 0;
- margin-top: 10px;
+.actionDisplay {
+ width: 95%;
+ height: 150px;
+ resize: vertical;
}
diff --git a/mobmonitor/static/js/actionrepairdialog.js b/mobmonitor/static/js/actionrepairdialog.js
index 8888df446..caf2dc0fc 100644
--- a/mobmonitor/static/js/actionrepairdialog.js
+++ b/mobmonitor/static/js/actionrepairdialog.js
@@ -5,6 +5,21 @@
'use strict';
+var ARGS_HELP_TEXT = 'Enter arguments as a comma separated list of the form: ' +
+ 'arg1,arg2,...,argN.';
+
+var ARGS_ACTION_HELP_TEXT = '\n\nYou must enter the arguments: ';
+
+var KWARGS_HELP_TEXT = 'Enter default arguments as a comma separated list of ' +
+ 'equal sign separated values of the form: ' +
+ 'default1=value1,default2=value2,...,defaultN=valueN.';
+
+var KWARGS_ACTION_HELP_TEXT = '\n\nYou may enter zero or more of the' +
+ ' following arguments: ';
+
+var NO_ARGS_TEXT = '\n\nNo arguments for you to add.';
+
+
function ActionRepairDialog(service, actions) {
var actionRepairDialog = this;
@@ -18,8 +33,7 @@ function ActionRepairDialog(service, actions) {
this.dialogElement_ = $(
renderTemplate('actionrepairdialog', templateData)).dialog({
autoOpen: false,
- height: 525,
- width: 550,
+ width: 575,
modal: true,
close: function(event, ui) {
@@ -30,20 +44,34 @@ function ActionRepairDialog(service, actions) {
'Clear Actions': function() {
actionRepairDialog.clear();
},
- 'Stage Action': function() {
- actionRepairDialog.stage();
- },
'Submit': function() {
actionRepairDialog.submit();
}
}
});
+ // Commonly used elements of the dialog ui.
var d = this.dialogElement_;
+ this.stageButton = $(d).find('#stageButton')[0];
this.dialogActionListDropdown = $(d).find('.actionlist-dropdown')[0];
+ this.dialogActionDescription = $(d).find('#actionDescription')[0];
this.dialogArgs = $(d).find('#args')[0];
+ this.dialogArgsHelp = $(d).find('#argsHelp')[0];
this.dialogKwargs = $(d).find('#kwargs')[0];
+ this.dialogKwargsHelp = $(d).find('#kwargsHelp')[0];
this.dialogStagedActions = $(d).find('#stagedActions')[0];
+
+ // Set up click handlers.
+ var self = this;
+ $(this.dialogActionListDropdown).on('input change', function() {
+ self.constructActionInfo(this.value);
+ });
+ $(this.stageButton).on('click', function() {
+ self.stage();
+ });
+
+ // Set default action info.
+ this.clear();
}
ActionRepairDialog.prototype.open = function() {
@@ -54,16 +82,67 @@ ActionRepairDialog.prototype.close = function() {
this.dialogElement_.dialog('close');
};
-ActionRepairDialog.prototype.clear = function() {
+ActionRepairDialog.prototype.clearActionInfo = function() {
+ // Clear action specific text.
this.dialogActionListDropdown.value = '';
+ this.dialogActionDescription.value = '';
this.dialogArgs.value = '';
this.dialogKwargs.value = '';
+ $(this.dialogArgsHelp).text(ARGS_HELP_TEXT);
+ $(this.dialogKwargsHelp).text(KWARGS_HELP_TEXT);
+
+ // Disable action argument input.
+ this.dialogArgs.disabled = true;
+ this.dialogKwargs.disabled = true;
+};
+
+ActionRepairDialog.prototype.clear = function() {
+ this.clearActionInfo();
this.dialogStagedActions.value = '';
this.actionsToSubmit = [];
};
+ActionRepairDialog.prototype.constructActionInfo = function(action) {
+ this.clearActionInfo();
+
+ if (!action) {
+ return;
+ }
+
+ var self = this;
+ rpcActionInfo(this.service, action, function(response) {
+ self.dialogActionListDropdown.value = action;
+ self.dialogActionDescription.value = response.info;
+
+ if (!isEmpty(response.args)) {
+ $(self.dialogArgsHelp).text(ARGS_HELP_TEXT + ARGS_ACTION_HELP_TEXT +
+ response.args.join(','));
+ self.dialogArgs.disabled = false;
+ }
+ else {
+ $(self.dialogArgsHelp).text(ARGS_HELP_TEXT + NO_ARGS_TEXT);
+ }
+
+ if (!isEmpty(response.kwargs)) {
+ var defaults = [];
+ Object.keys(response.kwargs).forEach(function(key) {
+ defaults.push(key + '=' + response.kwargs[key]);
+ });
+
+ self.dialogKwargs.value = defaults.join(',');
+ self.dialogKwargs.disabled = false;
+ $(self.dialogKwargsHelp).text(KWARGS_HELP_TEXT + KWARGS_ACTION_HELP_TEXT +
+ Object.keys(response.kwargs).join(','));
+ }
+ else {
+ $(self.dialogKwargsHelp).text(KWARGS_HELP_TEXT + NO_ARGS_TEXT);
+ }
+ });
+};
+
ActionRepairDialog.prototype.stage = function() {
var action = this.dialogActionListDropdown.value;
+
var args = this.dialogArgs.value;
var kwargs = this.dialogKwargs.value;
@@ -85,7 +164,7 @@ ActionRepairDialog.prototype.stage = function() {
return;
}
- // Store the action and add it to the staged action display.
+ // Store the validated action.
var storedArgs = args ? args.split(',') : [];
var storedKwargs = {};
kwargs.split(',').forEach(function(elem, index, array) {
@@ -100,7 +179,17 @@ ActionRepairDialog.prototype.stage = function() {
};
this.actionsToSubmit.push(stagedAction);
- this.dialogStagedActions.value += JSON.stringify(stagedAction);
+
+ // Add the action to the display.
+ var stagedActionDisplay = 'action: ' + action +
+ ', args: ' + args +
+ ', defaults: ' + kwargs;
+
+ if (!isEmpty(this.dialogStagedActions.value)) {
+ this.dialogStagedActions.value += '\n';
+ }
+
+ this.dialogStagedActions.value += stagedActionDisplay;
};
ActionRepairDialog.prototype.submit = function() {
diff --git a/mobmonitor/static/js/rpc.js b/mobmonitor/static/js/rpc.js
index b85367afb..1556c6e49 100644
--- a/mobmonitor/static/js/rpc.js
+++ b/mobmonitor/static/js/rpc.js
@@ -6,15 +6,20 @@
function rpcGetServiceList(callback) {
- $.getJSON('/GetServiceList', function(response) {
- callback(response);
- });
+ $.getJSON('/GetServiceList', callback);
}
function rpcGetStatus(service, callback) {
- $.getJSON('/GetStatus', {service: service}, function(response) {
- callback(response);
- });
+ $.getJSON('/GetStatus', {service: service}, callback);
+}
+
+function rpcActionInfo(service, action, callback) {
+ var data = {
+ service: service,
+ action: action
+ };
+
+ $.getJSON('/ActionInfo', data, callback);
}
function rpcRepairService(service, action, args, kwargs, callback) {
@@ -34,7 +39,5 @@ function rpcRepairService(service, action, args, kwargs, callback) {
kwargs: JSON.stringify(kwargs)
};
- $.post('/RepairService', data, function(response) {
- callback(response);
- }, 'json');
+ $.post('/RepairService', data, callback, 'json');
}
diff --git a/mobmonitor/static/templates/actionrepairdialog.html b/mobmonitor/static/templates/actionrepairdialog.html
index 1b65a0c27..5d181e5be 100644
--- a/mobmonitor/static/templates/actionrepairdialog.html
+++ b/mobmonitor/static/templates/actionrepairdialog.html
@@ -1,16 +1,29 @@
<div id="actionRepairDialog" title="Setup Repair Actions">
- <label for="actionlist">Actions</label>
+ <label for="actionlist">Choose an Action:</label>
<input list="actionlist" class="actionlist-dropdown">
<datalist id="actionlist">
{{#each actions}}
<option value={{this}}>
{{/each}}
</datalist>
- <label for="args">Arguments</label>
- <input type="text" name="args" id="args" value="" class="input-text">
- <label for="kwargs">Keyword Arguments</label>
- <input type="text" name="kwargs" id="kwargs" value="" class="input-text">
- <label for="stagedActions">Actions to Execute</label>
- <textarea readonly name="stagedActions" id="stagedActions" class="actiondisplay">
+ <br><br>
+
+ <label for="actionDescription">Action Description:</label>
+ <textarea style="font-size:10pt" readonly name="actionDescription" id="actionDescription" class="actionDisplay">
+ </textarea><br><br>
+
+ <label for="args">Arguments:</label><br>
+ <label for="args" id="argsHelp" class="label-help">Help text for args</label>
+ <input style="font-size:10pt" type="text" name="args" id="args" value="" class="input-text">
+
+ <label for="kwargs">Default Arguments:</label>
+ <label for="kwargs" id="kwargsHelp" class="label-help">Help text for kwargs</label>
+ <input style="font-size:10pt" type="text" name="kwargs" id="kwargs" value="" class="input-text">
+
+ <input type="button" name="stageButton" id="stageButton" value="Stage Action">
+ <br><br>
+
+ <label for="stagedActions">Actions to Execute:</label>
+ <textarea style="font-size:10pt" readonly name="stagedActions" id="stagedActions" class="actionDisplay">
</textarea>
</div>