diff options
author | Matthew Sartori <msartori@google.com> | 2015-08-26 11:02:58 -0700 |
---|---|---|
committer | ChromeOS Commit Bot <chromeos-commit-bot@chromium.org> | 2015-08-27 01:49:47 +0000 |
commit | 9245f45dcdac07e8807cd813bc799becba1861da (patch) | |
tree | 0858d09426c2d9c18c365c272434a1b0611f1081 /mobmonitor | |
parent | c72db8536609c8836b4ab7c455a3b4f16c08f361 (diff) | |
download | chromite-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.py | 66 | ||||
-rw-r--r-- | mobmonitor/checkfile/manager_unittest.py | 126 | ||||
-rw-r--r-- | mobmonitor/rpc/rpc.py | 27 | ||||
-rwxr-xr-x | mobmonitor/scripts/mobmoncli.py | 7 | ||||
-rw-r--r-- | mobmonitor/scripts/mobmoncli_unittest.py | 8 | ||||
-rwxr-xr-x | mobmonitor/scripts/mobmonitor.py | 15 | ||||
-rw-r--r-- | mobmonitor/scripts/mobmonitor_unittest.py | 15 | ||||
-rw-r--r-- | mobmonitor/static/css/style.css | 20 | ||||
-rw-r--r-- | mobmonitor/static/js/actionrepairdialog.js | 105 | ||||
-rw-r--r-- | mobmonitor/static/js/rpc.js | 21 | ||||
-rw-r--r-- | mobmonitor/static/templates/actionrepairdialog.html | 27 |
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> |